From 93de2a6a4b91b72607136dd656edf03fb399d27f Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Wed, 23 Sep 2026 09:37:21 -0700 Subject: KVM: SEV: Do cache maintenance on the source VM during intra-host migration Manually perform cache maintenance on the source VM during intra-host migration to ensure no stale data is left in CPU caches after the VM is destroyed. Because the source VM is "converted" to a non-SEV VM, KVM's memory reclaim flows won't trigger cache maintenance, e.g. when all guest memory is reclaimed in response to detaching from the mmu_notifier. Note, relying on the destination VM to do cache maintenance isn't an option as KVM doesn't require identical guest memory configurations, i.e. the source VM may have access to memory that the destination VM does not. Enforcing equivalent memory configurations is infeasible, as it would require a *deep* comparison of memslots, e.g. to verify that not only are the memslot identical, but what the memslots point at is also identical. Fixes: b56639318bb2 ("KVM: SEV: Add support for SEV intra host migration") Cc: stable@vger.kernel.org Reported-by: Stefan Teodorescu Signed-off-by: Sean Christopherson Message-ID: <20260923163721.1584779-3-seanjc@google.com> Signed-off-by: Paolo Bonzini --- drivers/media/i2c/Kconfig | 1816 +++++++++ drivers/media/i2c/Makefile | 176 + drivers/media/i2c/ad5820.c | 380 ++ drivers/media/i2c/adp1653.c | 549 +++ drivers/media/i2c/adv7170.c | 395 ++ drivers/media/i2c/adv7175.c | 450 +++ drivers/media/i2c/adv7180.c | 1674 ++++++++ drivers/media/i2c/adv7183.c | 641 ++++ drivers/media/i2c/adv7183_regs.h | 95 + drivers/media/i2c/adv7343.c | 529 +++ drivers/media/i2c/adv7343_regs.h | 173 + drivers/media/i2c/adv7393.c | 462 +++ drivers/media/i2c/adv7393_regs.h | 180 + drivers/media/i2c/adv748x/Makefile | 8 + drivers/media/i2c/adv748x/adv748x-afe.c | 539 +++ drivers/media/i2c/adv748x/adv748x-core.c | 858 +++++ drivers/media/i2c/adv748x/adv748x-csi2.c | 393 ++ drivers/media/i2c/adv748x/adv748x-hdmi.c | 761 ++++ drivers/media/i2c/adv748x/adv748x.h | 443 +++ drivers/media/i2c/adv7511-v4l2.c | 2025 ++++++++++ drivers/media/i2c/adv7604.c | 3766 ++++++++++++++++++ drivers/media/i2c/adv7842.c | 3694 ++++++++++++++++++ drivers/media/i2c/ak7375.c | 357 ++ drivers/media/i2c/ak881x.c | 326 ++ drivers/media/i2c/alvium-csi2.c | 2553 +++++++++++++ drivers/media/i2c/alvium-csi2.h | 476 +++ drivers/media/i2c/aptina-pll.c | 161 + drivers/media/i2c/aptina-pll.h | 43 + drivers/media/i2c/ar0521.c | 1192 ++++++ drivers/media/i2c/bt819.c | 476 +++ drivers/media/i2c/bt856.c | 247 ++ drivers/media/i2c/bt866.c | 214 ++ drivers/media/i2c/ccs-pll.c | 910 +++++ drivers/media/i2c/ccs-pll.h | 235 ++ drivers/media/i2c/ccs/Kconfig | 9 + drivers/media/i2c/ccs/Makefile | 6 + drivers/media/i2c/ccs/ccs-core.c | 3732 ++++++++++++++++++ drivers/media/i2c/ccs/ccs-data-defs.h | 221 ++ drivers/media/i2c/ccs/ccs-data.c | 982 +++++ drivers/media/i2c/ccs/ccs-data.h | 230 ++ drivers/media/i2c/ccs/ccs-limits.c | 243 ++ drivers/media/i2c/ccs/ccs-limits.h | 263 ++ drivers/media/i2c/ccs/ccs-quirk.c | 217 ++ drivers/media/i2c/ccs/ccs-quirk.h | 79 + drivers/media/i2c/ccs/ccs-reg-access.c | 269 ++ drivers/media/i2c/ccs/ccs-reg-access.h | 39 + drivers/media/i2c/ccs/ccs-regs.h | 958 +++++ drivers/media/i2c/ccs/ccs.h | 276 ++ drivers/media/i2c/ccs/smiapp-reg-defs.h | 585 +++ drivers/media/i2c/cs3308.c | 126 + drivers/media/i2c/cs5345.c | 206 + drivers/media/i2c/cs53l32a.c | 217 ++ drivers/media/i2c/cvs/Kconfig | 21 + drivers/media/i2c/cvs/Makefile | 4 + drivers/media/i2c/cvs/core.c | 1045 +++++ drivers/media/i2c/cvs/icvs.h | 493 +++ drivers/media/i2c/cvs/v4l2.c | 617 +++ drivers/media/i2c/cx25840/Kconfig | 9 + drivers/media/i2c/cx25840/Makefile | 5 + drivers/media/i2c/cx25840/cx25840-audio.c | 558 +++ drivers/media/i2c/cx25840/cx25840-core.c | 4008 +++++++++++++++++++ drivers/media/i2c/cx25840/cx25840-core.h | 194 + drivers/media/i2c/cx25840/cx25840-firmware.c | 161 + drivers/media/i2c/cx25840/cx25840-ir.c | 1239 ++++++ drivers/media/i2c/cx25840/cx25840-vbi.c | 262 ++ drivers/media/i2c/ds90ub913.c | 901 +++++ drivers/media/i2c/ds90ub953.c | 1378 +++++++ drivers/media/i2c/ds90ub953.h | 104 + drivers/media/i2c/ds90ub960.c | 5299 ++++++++++++++++++++++++++ drivers/media/i2c/dw9714.c | 345 ++ drivers/media/i2c/dw9719.c | 477 +++ drivers/media/i2c/dw9768.c | 556 +++ drivers/media/i2c/dw9807-vcm.c | 321 ++ drivers/media/i2c/et8ek8/Kconfig | 6 + drivers/media/i2c/et8ek8/Makefile | 3 + drivers/media/i2c/et8ek8/et8ek8_driver.c | 1512 ++++++++ drivers/media/i2c/et8ek8/et8ek8_mode.c | 570 +++ drivers/media/i2c/et8ek8/et8ek8_reg.h | 87 + drivers/media/i2c/gc0308.c | 1443 +++++++ drivers/media/i2c/gc0310.c | 781 ++++ drivers/media/i2c/gc05a2.c | 1346 +++++++ drivers/media/i2c/gc08a3.c | 1326 +++++++ drivers/media/i2c/gc2145.c | 1484 ++++++++ drivers/media/i2c/hi556.c | 1455 +++++++ drivers/media/i2c/hi846.c | 2176 +++++++++++ drivers/media/i2c/hi847.c | 2956 ++++++++++++++ drivers/media/i2c/imx111.c | 1610 ++++++++ drivers/media/i2c/imx208.c | 1069 ++++++ drivers/media/i2c/imx214.c | 1535 ++++++++ drivers/media/i2c/imx219.c | 1361 +++++++ drivers/media/i2c/imx258.c | 1568 ++++++++ drivers/media/i2c/imx274.c | 2135 +++++++++++ drivers/media/i2c/imx283.c | 1637 ++++++++ drivers/media/i2c/imx290.c | 1728 +++++++++ drivers/media/i2c/imx296.c | 1160 ++++++ drivers/media/i2c/imx319.c | 2505 ++++++++++++ drivers/media/i2c/imx334.c | 1267 ++++++ drivers/media/i2c/imx335.c | 1491 ++++++++ drivers/media/i2c/imx355.c | 1362 +++++++ drivers/media/i2c/imx412.c | 1120 ++++++ drivers/media/i2c/imx415.c | 1482 +++++++ drivers/media/i2c/imx471.c | 957 +++++ drivers/media/i2c/imx678.c | 1447 +++++++ drivers/media/i2c/ir-kbd-i2c.c | 1004 +++++ drivers/media/i2c/isl7998x.c | 1626 ++++++++ drivers/media/i2c/ks0127.c | 696 ++++ drivers/media/i2c/ks0127.h | 38 + drivers/media/i2c/lm3560.c | 728 ++++ drivers/media/i2c/lm3646.c | 409 ++ drivers/media/i2c/lt6911uxe.c | 707 ++++ drivers/media/i2c/m52790.c | 180 + drivers/media/i2c/max2175.c | 1441 +++++++ drivers/media/i2c/max2175.h | 101 + drivers/media/i2c/max9271.c | 374 ++ drivers/media/i2c/max9271.h | 238 ++ drivers/media/i2c/max9286.c | 1687 ++++++++ drivers/media/i2c/max96714.c | 1017 +++++ drivers/media/i2c/max96717.c | 1104 ++++++ drivers/media/i2c/ml86v7667.c | 445 +++ drivers/media/i2c/msp3400-driver.c | 895 +++++ drivers/media/i2c/msp3400-driver.h | 149 + drivers/media/i2c/msp3400-kthreads.c | 1157 ++++++ drivers/media/i2c/mt9m001.c | 888 +++++ drivers/media/i2c/mt9m111.c | 1406 +++++++ drivers/media/i2c/mt9m114.c | 2696 +++++++++++++ drivers/media/i2c/mt9p031.c | 1243 ++++++ drivers/media/i2c/mt9t112.c | 1130 ++++++ drivers/media/i2c/mt9v011.c | 602 +++ drivers/media/i2c/mt9v032.c | 1294 +++++++ drivers/media/i2c/mt9v111.c | 1282 +++++++ drivers/media/i2c/og01a1b.c | 1142 ++++++ drivers/media/i2c/og0ve1b.c | 841 ++++ drivers/media/i2c/os05b10.c | 1130 ++++++ drivers/media/i2c/ov01a10.c | 1137 ++++++ drivers/media/i2c/ov02a10.c | 1001 +++++ drivers/media/i2c/ov02c10.c | 1016 +++++ drivers/media/i2c/ov02e10.c | 956 +++++ drivers/media/i2c/ov08d10.c | 1626 ++++++++ drivers/media/i2c/ov08x40.c | 2405 ++++++++++++ drivers/media/i2c/ov13858.c | 1781 +++++++++ drivers/media/i2c/ov13b10.c | 1723 +++++++++ drivers/media/i2c/ov2640.c | 1299 +++++++ drivers/media/i2c/ov2659.c | 1584 ++++++++ drivers/media/i2c/ov2680.c | 1323 +++++++ drivers/media/i2c/ov2685.c | 890 +++++ drivers/media/i2c/ov2732.c | 790 ++++ drivers/media/i2c/ov2735.c | 1109 ++++++ drivers/media/i2c/ov2740.c | 1487 ++++++++ drivers/media/i2c/ov4689.c | 1045 +++++ drivers/media/i2c/ov5640.c | 4026 +++++++++++++++++++ drivers/media/i2c/ov5645.c | 1252 ++++++ drivers/media/i2c/ov5647.c | 1309 +++++++ drivers/media/i2c/ov5648.c | 2635 +++++++++++++ drivers/media/i2c/ov5670.c | 2828 ++++++++++++++ drivers/media/i2c/ov5675.c | 1385 +++++++ drivers/media/i2c/ov5693.c | 1422 +++++++ drivers/media/i2c/ov5695.c | 1373 +++++++ drivers/media/i2c/ov6211.c | 818 ++++ drivers/media/i2c/ov64a40.c | 3676 ++++++++++++++++++ drivers/media/i2c/ov7251.c | 1823 +++++++++ drivers/media/i2c/ov7640.c | 93 + drivers/media/i2c/ov7670.c | 2021 ++++++++++ drivers/media/i2c/ov772x.c | 1575 ++++++++ drivers/media/i2c/ov7740.c | 1177 ++++++ drivers/media/i2c/ov8856.c | 2496 ++++++++++++ drivers/media/i2c/ov8858.c | 1997 ++++++++++ drivers/media/i2c/ov8865.c | 3157 +++++++++++++++ drivers/media/i2c/ov9282.c | 1417 +++++++ drivers/media/i2c/ov9640.c | 773 ++++ drivers/media/i2c/ov9640.h | 207 + drivers/media/i2c/ov9650.c | 1599 ++++++++ drivers/media/i2c/ov9734.c | 994 +++++ drivers/media/i2c/rdacm20.c | 672 ++++ drivers/media/i2c/rdacm21.c | 634 +++ drivers/media/i2c/rj54n1cb0c.c | 1434 +++++++ drivers/media/i2c/s5c73m3/Makefile | 3 + drivers/media/i2c/s5c73m3/s5c73m3-core.c | 1758 +++++++++ drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c | 553 +++ drivers/media/i2c/s5c73m3/s5c73m3-spi.c | 147 + drivers/media/i2c/s5c73m3/s5c73m3.h | 448 +++ drivers/media/i2c/s5k3m5.c | 1377 +++++++ drivers/media/i2c/s5k5baf.c | 2035 ++++++++++ drivers/media/i2c/s5k6a3.c | 373 ++ drivers/media/i2c/s5kjn1.c | 1487 ++++++++ drivers/media/i2c/saa6588.c | 512 +++ drivers/media/i2c/saa6752hs.c | 787 ++++ drivers/media/i2c/saa7110.c | 456 +++ drivers/media/i2c/saa7115.c | 1953 ++++++++++ drivers/media/i2c/saa711x_regs.h | 560 +++ drivers/media/i2c/saa7127.c | 818 ++++ drivers/media/i2c/saa717x.c | 1351 +++++++ drivers/media/i2c/saa7185.c | 351 ++ drivers/media/i2c/sony-btf-mpx.c | 382 ++ drivers/media/i2c/st-mipid02.c | 967 +++++ drivers/media/i2c/t4ka3.c | 1063 ++++++ drivers/media/i2c/tc358743.c | 2385 ++++++++++++ drivers/media/i2c/tc358743_regs.h | 782 ++++ drivers/media/i2c/tc358746.c | 1717 +++++++++ drivers/media/i2c/tda1997x.c | 2848 ++++++++++++++ drivers/media/i2c/tda1997x_regs.h | 644 ++++ drivers/media/i2c/tda7432.c | 417 ++ drivers/media/i2c/tda9840.c | 199 + drivers/media/i2c/tea6415c.c | 158 + drivers/media/i2c/tea6415c.h | 28 + drivers/media/i2c/tea6420.c | 140 + drivers/media/i2c/tea6420.h | 25 + drivers/media/i2c/thp7312.c | 2249 +++++++++++ drivers/media/i2c/ths7303.c | 388 ++ drivers/media/i2c/ths8200.c | 513 +++ drivers/media/i2c/ths8200_regs.h | 161 + drivers/media/i2c/tlv320aic23b.c | 205 + drivers/media/i2c/tvaudio.c | 2103 ++++++++++ drivers/media/i2c/tvp514x.c | 1158 ++++++ drivers/media/i2c/tvp514x_regs.h | 274 ++ drivers/media/i2c/tvp5150.c | 2292 +++++++++++ drivers/media/i2c/tvp5150_reg.h | 152 + drivers/media/i2c/tvp7002.c | 1097 ++++++ drivers/media/i2c/tvp7002_reg.h | 137 + drivers/media/i2c/tw2804.c | 435 +++ drivers/media/i2c/tw9900.c | 781 ++++ drivers/media/i2c/tw9903.c | 262 ++ drivers/media/i2c/tw9906.c | 230 ++ drivers/media/i2c/tw9910.c | 1017 +++++ drivers/media/i2c/uda1342.c | 99 + drivers/media/i2c/upd64031a.c | 236 ++ drivers/media/i2c/upd64083.c | 207 + drivers/media/i2c/vd55g1.c | 2106 ++++++++++ drivers/media/i2c/vd56g3.c | 1582 ++++++++ drivers/media/i2c/vgxy61.c | 1914 ++++++++++ drivers/media/i2c/video-i2c.c | 956 +++++ drivers/media/i2c/vp27smpx.c | 189 + drivers/media/i2c/vpx3220.c | 554 +++ drivers/media/i2c/wm8739.c | 260 ++ drivers/media/i2c/wm8775.c | 306 ++ 234 files changed, 234633 insertions(+) create mode 100644 drivers/media/i2c/Kconfig create mode 100644 drivers/media/i2c/Makefile create mode 100644 drivers/media/i2c/ad5820.c create mode 100644 drivers/media/i2c/adp1653.c create mode 100644 drivers/media/i2c/adv7170.c create mode 100644 drivers/media/i2c/adv7175.c create mode 100644 drivers/media/i2c/adv7180.c create mode 100644 drivers/media/i2c/adv7183.c create mode 100644 drivers/media/i2c/adv7183_regs.h create mode 100644 drivers/media/i2c/adv7343.c create mode 100644 drivers/media/i2c/adv7343_regs.h create mode 100644 drivers/media/i2c/adv7393.c create mode 100644 drivers/media/i2c/adv7393_regs.h create mode 100644 drivers/media/i2c/adv748x/Makefile create mode 100644 drivers/media/i2c/adv748x/adv748x-afe.c create mode 100644 drivers/media/i2c/adv748x/adv748x-core.c create mode 100644 drivers/media/i2c/adv748x/adv748x-csi2.c create mode 100644 drivers/media/i2c/adv748x/adv748x-hdmi.c create mode 100644 drivers/media/i2c/adv748x/adv748x.h create mode 100644 drivers/media/i2c/adv7511-v4l2.c create mode 100644 drivers/media/i2c/adv7604.c create mode 100644 drivers/media/i2c/adv7842.c create mode 100644 drivers/media/i2c/ak7375.c create mode 100644 drivers/media/i2c/ak881x.c create mode 100644 drivers/media/i2c/alvium-csi2.c create mode 100644 drivers/media/i2c/alvium-csi2.h create mode 100644 drivers/media/i2c/aptina-pll.c create mode 100644 drivers/media/i2c/aptina-pll.h create mode 100644 drivers/media/i2c/ar0521.c create mode 100644 drivers/media/i2c/bt819.c create mode 100644 drivers/media/i2c/bt856.c create mode 100644 drivers/media/i2c/bt866.c create mode 100644 drivers/media/i2c/ccs-pll.c create mode 100644 drivers/media/i2c/ccs-pll.h create mode 100644 drivers/media/i2c/ccs/Kconfig create mode 100644 drivers/media/i2c/ccs/Makefile create mode 100644 drivers/media/i2c/ccs/ccs-core.c create mode 100644 drivers/media/i2c/ccs/ccs-data-defs.h create mode 100644 drivers/media/i2c/ccs/ccs-data.c create mode 100644 drivers/media/i2c/ccs/ccs-data.h create mode 100644 drivers/media/i2c/ccs/ccs-limits.c create mode 100644 drivers/media/i2c/ccs/ccs-limits.h create mode 100644 drivers/media/i2c/ccs/ccs-quirk.c create mode 100644 drivers/media/i2c/ccs/ccs-quirk.h create mode 100644 drivers/media/i2c/ccs/ccs-reg-access.c create mode 100644 drivers/media/i2c/ccs/ccs-reg-access.h create mode 100644 drivers/media/i2c/ccs/ccs-regs.h create mode 100644 drivers/media/i2c/ccs/ccs.h create mode 100644 drivers/media/i2c/ccs/smiapp-reg-defs.h create mode 100644 drivers/media/i2c/cs3308.c create mode 100644 drivers/media/i2c/cs5345.c create mode 100644 drivers/media/i2c/cs53l32a.c create mode 100644 drivers/media/i2c/cvs/Kconfig create mode 100644 drivers/media/i2c/cvs/Makefile create mode 100644 drivers/media/i2c/cvs/core.c create mode 100644 drivers/media/i2c/cvs/icvs.h create mode 100644 drivers/media/i2c/cvs/v4l2.c create mode 100644 drivers/media/i2c/cx25840/Kconfig create mode 100644 drivers/media/i2c/cx25840/Makefile create mode 100644 drivers/media/i2c/cx25840/cx25840-audio.c create mode 100644 drivers/media/i2c/cx25840/cx25840-core.c create mode 100644 drivers/media/i2c/cx25840/cx25840-core.h create mode 100644 drivers/media/i2c/cx25840/cx25840-firmware.c create mode 100644 drivers/media/i2c/cx25840/cx25840-ir.c create mode 100644 drivers/media/i2c/cx25840/cx25840-vbi.c create mode 100644 drivers/media/i2c/ds90ub913.c create mode 100644 drivers/media/i2c/ds90ub953.c create mode 100644 drivers/media/i2c/ds90ub953.h create mode 100644 drivers/media/i2c/ds90ub960.c create mode 100644 drivers/media/i2c/dw9714.c create mode 100644 drivers/media/i2c/dw9719.c create mode 100644 drivers/media/i2c/dw9768.c create mode 100644 drivers/media/i2c/dw9807-vcm.c create mode 100644 drivers/media/i2c/et8ek8/Kconfig create mode 100644 drivers/media/i2c/et8ek8/Makefile create mode 100644 drivers/media/i2c/et8ek8/et8ek8_driver.c create mode 100644 drivers/media/i2c/et8ek8/et8ek8_mode.c create mode 100644 drivers/media/i2c/et8ek8/et8ek8_reg.h create mode 100644 drivers/media/i2c/gc0308.c create mode 100644 drivers/media/i2c/gc0310.c create mode 100644 drivers/media/i2c/gc05a2.c create mode 100644 drivers/media/i2c/gc08a3.c create mode 100644 drivers/media/i2c/gc2145.c create mode 100644 drivers/media/i2c/hi556.c create mode 100644 drivers/media/i2c/hi846.c create mode 100644 drivers/media/i2c/hi847.c create mode 100644 drivers/media/i2c/imx111.c create mode 100644 drivers/media/i2c/imx208.c create mode 100644 drivers/media/i2c/imx214.c create mode 100644 drivers/media/i2c/imx219.c create mode 100644 drivers/media/i2c/imx258.c create mode 100644 drivers/media/i2c/imx274.c create mode 100644 drivers/media/i2c/imx283.c create mode 100644 drivers/media/i2c/imx290.c create mode 100644 drivers/media/i2c/imx296.c create mode 100644 drivers/media/i2c/imx319.c create mode 100644 drivers/media/i2c/imx334.c create mode 100644 drivers/media/i2c/imx335.c create mode 100644 drivers/media/i2c/imx355.c create mode 100644 drivers/media/i2c/imx412.c create mode 100644 drivers/media/i2c/imx415.c create mode 100644 drivers/media/i2c/imx471.c create mode 100644 drivers/media/i2c/imx678.c create mode 100644 drivers/media/i2c/ir-kbd-i2c.c create mode 100644 drivers/media/i2c/isl7998x.c create mode 100644 drivers/media/i2c/ks0127.c create mode 100644 drivers/media/i2c/ks0127.h create mode 100644 drivers/media/i2c/lm3560.c create mode 100644 drivers/media/i2c/lm3646.c create mode 100644 drivers/media/i2c/lt6911uxe.c create mode 100644 drivers/media/i2c/m52790.c create mode 100644 drivers/media/i2c/max2175.c create mode 100644 drivers/media/i2c/max2175.h create mode 100644 drivers/media/i2c/max9271.c create mode 100644 drivers/media/i2c/max9271.h create mode 100644 drivers/media/i2c/max9286.c create mode 100644 drivers/media/i2c/max96714.c create mode 100644 drivers/media/i2c/max96717.c create mode 100644 drivers/media/i2c/ml86v7667.c create mode 100644 drivers/media/i2c/msp3400-driver.c create mode 100644 drivers/media/i2c/msp3400-driver.h create mode 100644 drivers/media/i2c/msp3400-kthreads.c create mode 100644 drivers/media/i2c/mt9m001.c create mode 100644 drivers/media/i2c/mt9m111.c create mode 100644 drivers/media/i2c/mt9m114.c create mode 100644 drivers/media/i2c/mt9p031.c create mode 100644 drivers/media/i2c/mt9t112.c create mode 100644 drivers/media/i2c/mt9v011.c create mode 100644 drivers/media/i2c/mt9v032.c create mode 100644 drivers/media/i2c/mt9v111.c create mode 100644 drivers/media/i2c/og01a1b.c create mode 100644 drivers/media/i2c/og0ve1b.c create mode 100644 drivers/media/i2c/os05b10.c create mode 100644 drivers/media/i2c/ov01a10.c create mode 100644 drivers/media/i2c/ov02a10.c create mode 100644 drivers/media/i2c/ov02c10.c create mode 100644 drivers/media/i2c/ov02e10.c create mode 100644 drivers/media/i2c/ov08d10.c create mode 100644 drivers/media/i2c/ov08x40.c create mode 100644 drivers/media/i2c/ov13858.c create mode 100644 drivers/media/i2c/ov13b10.c create mode 100644 drivers/media/i2c/ov2640.c create mode 100644 drivers/media/i2c/ov2659.c create mode 100644 drivers/media/i2c/ov2680.c create mode 100644 drivers/media/i2c/ov2685.c create mode 100644 drivers/media/i2c/ov2732.c create mode 100644 drivers/media/i2c/ov2735.c create mode 100644 drivers/media/i2c/ov2740.c create mode 100644 drivers/media/i2c/ov4689.c create mode 100644 drivers/media/i2c/ov5640.c create mode 100644 drivers/media/i2c/ov5645.c create mode 100644 drivers/media/i2c/ov5647.c create mode 100644 drivers/media/i2c/ov5648.c create mode 100644 drivers/media/i2c/ov5670.c create mode 100644 drivers/media/i2c/ov5675.c create mode 100644 drivers/media/i2c/ov5693.c create mode 100644 drivers/media/i2c/ov5695.c create mode 100644 drivers/media/i2c/ov6211.c create mode 100644 drivers/media/i2c/ov64a40.c create mode 100644 drivers/media/i2c/ov7251.c create mode 100644 drivers/media/i2c/ov7640.c create mode 100644 drivers/media/i2c/ov7670.c create mode 100644 drivers/media/i2c/ov772x.c create mode 100644 drivers/media/i2c/ov7740.c create mode 100644 drivers/media/i2c/ov8856.c create mode 100644 drivers/media/i2c/ov8858.c create mode 100644 drivers/media/i2c/ov8865.c create mode 100644 drivers/media/i2c/ov9282.c create mode 100644 drivers/media/i2c/ov9640.c create mode 100644 drivers/media/i2c/ov9640.h create mode 100644 drivers/media/i2c/ov9650.c create mode 100644 drivers/media/i2c/ov9734.c create mode 100644 drivers/media/i2c/rdacm20.c create mode 100644 drivers/media/i2c/rdacm21.c create mode 100644 drivers/media/i2c/rj54n1cb0c.c create mode 100644 drivers/media/i2c/s5c73m3/Makefile create mode 100644 drivers/media/i2c/s5c73m3/s5c73m3-core.c create mode 100644 drivers/media/i2c/s5c73m3/s5c73m3-ctrls.c create mode 100644 drivers/media/i2c/s5c73m3/s5c73m3-spi.c create mode 100644 drivers/media/i2c/s5c73m3/s5c73m3.h create mode 100644 drivers/media/i2c/s5k3m5.c create mode 100644 drivers/media/i2c/s5k5baf.c create mode 100644 drivers/media/i2c/s5k6a3.c create mode 100644 drivers/media/i2c/s5kjn1.c create mode 100644 drivers/media/i2c/saa6588.c create mode 100644 drivers/media/i2c/saa6752hs.c create mode 100644 drivers/media/i2c/saa7110.c create mode 100644 drivers/media/i2c/saa7115.c create mode 100644 drivers/media/i2c/saa711x_regs.h create mode 100644 drivers/media/i2c/saa7127.c create mode 100644 drivers/media/i2c/saa717x.c create mode 100644 drivers/media/i2c/saa7185.c create mode 100644 drivers/media/i2c/sony-btf-mpx.c create mode 100644 drivers/media/i2c/st-mipid02.c create mode 100644 drivers/media/i2c/t4ka3.c create mode 100644 drivers/media/i2c/tc358743.c create mode 100644 drivers/media/i2c/tc358743_regs.h create mode 100644 drivers/media/i2c/tc358746.c create mode 100644 drivers/media/i2c/tda1997x.c create mode 100644 drivers/media/i2c/tda1997x_regs.h create mode 100644 drivers/media/i2c/tda7432.c create mode 100644 drivers/media/i2c/tda9840.c create mode 100644 drivers/media/i2c/tea6415c.c create mode 100644 drivers/media/i2c/tea6415c.h create mode 100644 drivers/media/i2c/tea6420.c create mode 100644 drivers/media/i2c/tea6420.h create mode 100644 drivers/media/i2c/thp7312.c create mode 100644 drivers/media/i2c/ths7303.c create mode 100644 drivers/media/i2c/ths8200.c create mode 100644 drivers/media/i2c/ths8200_regs.h create mode 100644 drivers/media/i2c/tlv320aic23b.c create mode 100644 drivers/media/i2c/tvaudio.c create mode 100644 drivers/media/i2c/tvp514x.c create mode 100644 drivers/media/i2c/tvp514x_regs.h create mode 100644 drivers/media/i2c/tvp5150.c create mode 100644 drivers/media/i2c/tvp5150_reg.h create mode 100644 drivers/media/i2c/tvp7002.c create mode 100644 drivers/media/i2c/tvp7002_reg.h create mode 100644 drivers/media/i2c/tw2804.c create mode 100644 drivers/media/i2c/tw9900.c create mode 100644 drivers/media/i2c/tw9903.c create mode 100644 drivers/media/i2c/tw9906.c create mode 100644 drivers/media/i2c/tw9910.c create mode 100644 drivers/media/i2c/uda1342.c create mode 100644 drivers/media/i2c/upd64031a.c create mode 100644 drivers/media/i2c/upd64083.c create mode 100644 drivers/media/i2c/vd55g1.c create mode 100644 drivers/media/i2c/vd56g3.c create mode 100644 drivers/media/i2c/vgxy61.c create mode 100644 drivers/media/i2c/video-i2c.c create mode 100644 drivers/media/i2c/vp27smpx.c create mode 100644 drivers/media/i2c/vpx3220.c create mode 100644 drivers/media/i2c/wm8739.c create mode 100644 drivers/media/i2c/wm8775.c (limited to 'drivers/media/i2c') diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig new file mode 100644 index 000000000..5c52007f9 --- /dev/null +++ b/drivers/media/i2c/Kconfig @@ -0,0 +1,1816 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Multimedia Video device configuration +# + +if VIDEO_DEV + +comment "IR I2C driver auto-selected by 'Autoselect ancillary drivers'" + depends on MEDIA_SUBDRV_AUTOSELECT && I2C && RC_CORE + +config VIDEO_IR_I2C + tristate "I2C module for IR" if !MEDIA_SUBDRV_AUTOSELECT || EXPERT + depends on I2C && RC_CORE + default y + help + Most boards have an IR chip directly connected via GPIO. However, + some video boards have the IR connected via I2C bus. + + If your board doesn't have an I2C IR chip, you may disable this + option. + + In doubt, say Y. + +# +# V4L2 I2C drivers that are related with Camera support +# + +menuconfig VIDEO_CAMERA_SENSOR + bool "Camera sensor devices" + depends on MEDIA_CAMERA_SUPPORT && I2C && HAVE_CLK + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + default y + +if VIDEO_CAMERA_SENSOR + +config VIDEO_APTINA_PLL + tristate + +config VIDEO_CCS_PLL + tristate + +config VIDEO_ALVIUM_CSI2 + tristate "Allied Vision ALVIUM MIPI CSI-2 camera support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor-level driver for the Allied Vision + ALVIUM camera connected via MIPI CSI-2 interface. + + To compile this driver as a module, choose M here: the + module will be called alvium-csi2. + +config VIDEO_AR0521 + tristate "ON Semiconductor AR0521 sensor support" + help + This is a Video4Linux2 sensor driver for the ON Semiconductor + AR0521 camera. + + To compile this driver as a module, choose M here: the + module will be called ar0521. + +config VIDEO_GC0308 + tristate "GalaxyCore GC0308 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the GalaxyCore + GC0308 camera. + + To compile this driver as a module, choose M here: the + module will be called gc0308. + +config VIDEO_GC0310 + tristate "GalaxyCore GC0310 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor-level driver for the Galaxycore + GC0310 0.3MP sensor. + + To compile this driver as a module, choose M here: the + module will be called gc0310. + +config VIDEO_GC05A2 + tristate "GalaxyCore gc05a2 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the GalaxyCore gc05a2 + camera. + + To compile this driver as a module, choose M here: the + module will be called gc05a2. + +config VIDEO_GC08A3 + tristate "GalaxyCore gc08a3 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the GalaxyCore gc08a3 + camera. + + To compile this driver as a module, choose M here: the + module will be called gc08a3. + +config VIDEO_GC2145 + select V4L2_CCI_I2C + tristate "GalaxyCore GC2145 sensor support" + help + This is a V4L2 sensor-level driver for GalaxyCore GC2145 + 2 Mpixel camera. + + To compile this driver as a module, choose M here: the + module will be called gc2145. + +config VIDEO_HI556 + tristate "Hynix Hi-556 sensor support" + help + This is a Video4Linux2 sensor driver for the Hynix + Hi-556 camera. + + To compile this driver as a module, choose M here: the + module will be called hi556. + +config VIDEO_HI846 + tristate "Hynix Hi-846 sensor support" + help + This is a Video4Linux2 sensor driver for the Hynix + Hi-846 camera. + + To compile this driver as a module, choose M here: the + module will be called hi846. + +config VIDEO_HI847 + tristate "Hynix Hi-847 sensor support" + help + This is a Video4Linux2 sensor driver for the Hynix + Hi-847 camera. + + To compile this driver as a module, choose M here: the + module will be called hi847. + +config VIDEO_IMX111 + tristate "Sony IMX111 sensor support" + select V4L2_CCI_I2C + help + This is a V4L2 sensor driver for the Sony IMX111 camera + sensors. + + To compile this driver as a module, choose M here: the + module will be called imx111. + +config VIDEO_IMX208 + tristate "Sony IMX208 sensor support" + help + This is a Video4Linux2 sensor driver for the Sony + IMX208 camera. + + To compile this driver as a module, choose M here: the + module will be called imx208. + +config VIDEO_IMX214 + tristate "Sony IMX214 sensor support" + depends on GPIOLIB + select REGMAP_I2C + select V4L2_CCI_I2C + select VIDEO_CCS_PLL + help + This is a Video4Linux2 sensor driver for the Sony + IMX214 camera. + + To compile this driver as a module, choose M here: the + module will be called imx214. + +config VIDEO_IMX219 + tristate "Sony IMX219 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX219 camera. + + To compile this driver as a module, choose M here: the + module will be called imx219. + +config VIDEO_IMX258 + tristate "Sony IMX258 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX258 camera. + + To compile this driver as a module, choose M here: the + module will be called imx258. + +config VIDEO_IMX274 + tristate "Sony IMX274 sensor support" + select REGMAP_I2C + help + This is a V4L2 sensor driver for the Sony IMX274 + CMOS image sensor. + +config VIDEO_IMX283 + tristate "Sony IMX283 sensor support" + select V4L2_CCI_I2C + help + This is a V4L2 sensor driver for the Sony IMX283 + CMOS image sensor. + + To compile this driver as a module, choose M here: the + module will be called imx283. + +config VIDEO_IMX290 + tristate "Sony IMX290 sensor support" + select REGMAP_I2C + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX290 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called imx290. + +config VIDEO_IMX296 + tristate "Sony IMX296 sensor support" + help + This is a Video4Linux2 sensor driver for the Sony + IMX296 camera. + + To compile this driver as a module, choose M here: the + module will be called imx296. + +config VIDEO_IMX319 + tristate "Sony IMX319 sensor support" + help + This is a Video4Linux2 sensor driver for the Sony + IMX319 camera. + + To compile this driver as a module, choose M here: the + module will be called imx319. + +config VIDEO_IMX334 + tristate "Sony IMX334 sensor support" + depends on OF + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX334 camera. + + To compile this driver as a module, choose M here: the + module will be called imx334. + +config VIDEO_IMX335 + tristate "Sony IMX335 sensor support" + depends on OF + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX335 camera. + + To compile this driver as a module, choose M here: the + module will be called imx335. + +config VIDEO_IMX355 + tristate "Sony IMX355 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX355 camera. + + To compile this driver as a module, choose M here: the + module will be called imx355. + +config VIDEO_IMX412 + tristate "Sony IMX412 sensor support" + depends on OF + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX412 camera. + + To compile this driver as a module, choose M here: the + module will be called imx412. + +config VIDEO_IMX415 + tristate "Sony IMX415 sensor support" + depends on OF + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX415 camera. + + To compile this driver as a module, choose M here: the + module will be called imx415. + +config VIDEO_IMX471 + tristate "Sony IMX471 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX471 camera. + + To compile this driver as a module, choose M here: the + module will be called imx471. + +config VIDEO_IMX678 + tristate "Sony IMX678 sensor support" + depends on GPIOLIB + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX678 camera. + + To compile this driver as a module, choose M here: the + module will be called imx678. + +config VIDEO_MAX9271_LIB + tristate + +config VIDEO_MT9M001 + tristate "mt9m001 support" + help + This driver supports MT9M001 cameras from Micron, monochrome + and colour models. + +config VIDEO_MT9M111 + tristate "mt9m111, mt9m112 and mt9m131 support" + help + This driver supports MT9M111, MT9M112 and MT9M131 cameras from + Micron/Aptina + +config VIDEO_MT9M114 + tristate "onsemi MT9M114 sensor support" + select V4L2_CCI_I2C + select VIDEO_APTINA_PLL + help + This is a Video4Linux2 sensor-level driver for the onsemi MT9M114 + camera. + + To compile this driver as a module, choose M here: the + module will be called mt9m114. + +config VIDEO_MT9P031 + tristate "Aptina MT9P031 support" + select VIDEO_APTINA_PLL + help + This is a Video4Linux2 sensor driver for the Aptina + (Micron) mt9p031 5 Mpixel camera. + +config VIDEO_MT9T112 + tristate "Aptina MT9T111/MT9T112 support" + help + This is a Video4Linux2 sensor driver for the Aptina + (Micron) MT9T111 and MT9T112 3 Mpixel camera. + + To compile this driver as a module, choose M here: the + module will be called mt9t112. + +config VIDEO_MT9V011 + tristate "Micron mt9v011 sensor support" + help + This is a Video4Linux2 sensor driver for the Micron + mt0v011 1.3 Mpixel camera. It currently only works with the + em28xx driver. + +config VIDEO_MT9V032 + tristate "Micron MT9V032 sensor support" + depends on OF + select REGMAP_I2C + help + This is a Video4Linux2 sensor driver for the Micron + MT9V032 752x480 CMOS sensor. + +config VIDEO_MT9V111 + tristate "Aptina MT9V111 sensor support" + help + This is a Video4Linux2 sensor driver for the Aptina/Micron + MT9V111 sensor. + + To compile this driver as a module, choose M here: the + module will be called mt9v111. + +config VIDEO_OG01A1B + tristate "OmniVision OG01A1B sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OG01A1B camera. + + To compile this driver as a module, choose M here: the + module will be called og01a1b. + +config VIDEO_OG0VE1B + tristate "OmniVision OG0VE1B sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OG0VE1B camera. + + To compile this driver as a module, choose M here: the + module will be called og0ve1b. + +config VIDEO_OS05B10 + tristate "OmniVision OS05B10 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for Omnivision + OS05B10 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called os05b10. + +config VIDEO_OV01A10 + tristate "OmniVision OV01A10 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV01A10 camera. + + To compile this driver as a module, choose M here: the + module will be called ov01a10. + +config VIDEO_OV02A10 + tristate "OmniVision OV02A10 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV02A10 camera. + + To compile this driver as a module, choose M here: the + module will be called ov02a10. + +config VIDEO_OV02E10 + tristate "OmniVision OV02E10 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV02E10 camera. + + To compile this driver as a module, choose M here: the + module will be called ov02e10. + +config VIDEO_OV02C10 + tristate "OmniVision OV02C10 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV02C10 camera. + + To compile this driver as a module, choose M here: the + module will be called ov02c10. + +config VIDEO_OV08D10 + tristate "OmniVision OV08D10 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV08D10 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called ov08d10. + +config VIDEO_OV08X40 + tristate "OmniVision OV08X40 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV08X40 camera. + + To compile this driver as a module, choose M here: the + module will be called ov08x40. + +config VIDEO_OV13858 + tristate "OmniVision OV13858 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV13858 camera. + +config VIDEO_OV13B10 + tristate "OmniVision OV13B10 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV13B10 camera. + +config VIDEO_OV2640 + tristate "OmniVision OV2640 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV2640 camera. + + To compile this driver as a module, choose M here: the + module will be called ov2640. + +config VIDEO_OV2659 + tristate "OmniVision OV2659 sensor support" + depends on GPIOLIB + help + This is a Video4Linux2 sensor driver for the OmniVision + OV2659 camera. + + To compile this driver as a module, choose M here: the + module will be called ov2659. + +config VIDEO_OV2680 + tristate "OmniVision OV2680 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV2680 camera. + + To compile this driver as a module, choose M here: the + module will be called ov2680. + +config VIDEO_OV2685 + tristate "OmniVision OV2685 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV2685 camera. + + To compile this driver as a module, choose M here: the + module will be called ov2685. + +config VIDEO_OV2732 + tristate "OmniVision OV2732 sensor support" + depends on OF + select MEDIA_CONTROLLER + select V4L2_CCI_I2C + select VIDEO_V4L2_SUBDEV_API + help + This is a Video4Linux2 sensor driver for the OmniVision + OV2732 camera. + + To compile this driver as a module, choose M here: the + module will be called ov2732. + +config VIDEO_OV2735 + tristate "OmniVision OV2735 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV2735 camera. + + To compile this driver as a module, choose M here: the + module will be called ov2735. + +config VIDEO_OV2740 + tristate "OmniVision OV2740 sensor support" + depends on ACPI || COMPILE_TEST + select REGMAP_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV2740 camera. + + To compile this driver as a module, choose M here: the + module will be called ov2740. + +config VIDEO_OV4689 + tristate "OmniVision OV4689 sensor support" + depends on GPIOLIB + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor-level driver for the OmniVision + OV4689 camera. + + To compile this driver as a module, choose M here: the + module will be called ov4689. + +config VIDEO_OV5640 + tristate "OmniVision OV5640 sensor support" + depends on OF + depends on GPIOLIB + help + This is a Video4Linux2 sensor driver for the Omnivision + OV5640 camera sensor with a MIPI CSI-2 interface. + +config VIDEO_OV5645 + tristate "OmniVision OV5645 sensor support" + depends on OF + help + This is a Video4Linux2 sensor driver for the OmniVision + OV5645 camera. + + To compile this driver as a module, choose M here: the + module will be called ov5645. + +config VIDEO_OV5647 + tristate "OmniVision OV5647 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV5647 camera. + + To compile this driver as a module, choose M here: the + module will be called ov5647. + +config VIDEO_OV5648 + tristate "OmniVision OV5648 sensor support" + depends on PM + help + This is a Video4Linux2 sensor driver for the OmniVision + OV5648 camera. + + To compile this driver as a module, choose M here: the + module will be called ov5648. + +config VIDEO_OV5670 + tristate "OmniVision OV5670 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV5670 camera. + + To compile this driver as a module, choose M here: the + module will be called ov5670. + +config VIDEO_OV5675 + tristate "OmniVision OV5675 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV5675 camera. + + To compile this driver as a module, choose M here: the + module will be called ov5675. + +config VIDEO_OV5693 + tristate "OmniVision OV5693 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV5693 camera. + + To compile this driver as a module, choose M here: the + module will be called ov5693. + +config VIDEO_OV5695 + tristate "OmniVision OV5695 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV5695 camera. + + To compile this driver as a module, choose M here: the + module will be called ov5695. + +config VIDEO_OV6211 + tristate "OmniVision OV6211 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV6211 camera. + + To compile this driver as a module, choose M here: the + module will be called ov6211. + +config VIDEO_OV64A40 + tristate "OmniVision OV64A40 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV64A40 camera. + + To compile this driver as a module, choose M here: the + module will be called ov64a40. + +config VIDEO_OV7251 + tristate "OmniVision OV7251 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV7251 camera. + + To compile this driver as a module, choose M here: the + module will be called ov7251. + +config VIDEO_OV7640 + tristate "OmniVision OV7640 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV7640 camera. + + To compile this driver as a module, choose M here: the + module will be called ov7640. + +config VIDEO_OV7670 + tristate "OmniVision OV7670 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV7670 VGA camera. It currently only works with the M88ALP01 + controller. + +config VIDEO_OV772X + tristate "OmniVision OV772x sensor support" + select REGMAP_SCCB + help + This is a Video4Linux2 sensor driver for the OmniVision + OV772x camera. + + To compile this driver as a module, choose M here: the + module will be called ov772x. + +config VIDEO_OV7740 + tristate "OmniVision OV7740 sensor support" + select REGMAP_SCCB + help + This is a Video4Linux2 sensor driver for the OmniVision + OV7740 VGA camera sensor. + +config VIDEO_OV8856 + tristate "OmniVision OV8856 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV8856 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called ov8856. + +config VIDEO_OV8858 + tristate "OmniVision OV8858 sensor support" + depends on PM + help + This is a Video4Linux2 sensor driver for OmniVision + OV8858 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called ov8858. + +config VIDEO_OV8865 + tristate "OmniVision OV8865 sensor support" + depends on PM + help + This is a Video4Linux2 sensor driver for OmniVision + OV8865 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called ov8865. + +config VIDEO_OV9282 + tristate "OmniVision OV9282 sensor support" + depends on OF + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV9282 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called ov9282. + +config VIDEO_OV9640 + tristate "OmniVision OV9640 sensor support" + help + This is a Video4Linux2 sensor driver for the OmniVision + OV9640 camera sensor. + +config VIDEO_OV9650 + tristate "OmniVision OV9650/OV9652 sensor support" + select REGMAP_SCCB + help + This is a V4L2 sensor driver for the Omnivision + OV9650 and OV9652 camera sensors. + +config VIDEO_OV9734 + tristate "OmniVision OV9734 sensor support" + depends on ACPI || COMPILE_TEST + help + This is a Video4Linux2 sensor driver for the OmniVision + OV9734 camera. + + To compile this driver as a module, choose M here: the + module's name is ov9734. + +config VIDEO_RDACM20 + tristate "IMI RDACM20 camera support" + select VIDEO_MAX9271_LIB + help + This driver supports the IMI RDACM20 GMSL camera, used in + ADAS systems. + + This camera should be used in conjunction with a GMSL + deserialiser such as the MAX9286. + +config VIDEO_RDACM21 + tristate "IMI RDACM21 camera support" + select VIDEO_MAX9271_LIB + help + This driver supports the IMI RDACM21 GMSL camera, used in + ADAS systems. + + This camera should be used in conjunction with a GMSL + deserialiser such as the MAX9286. + +config VIDEO_RJ54N1 + tristate "Sharp RJ54N1CB0C sensor support" + help + This is a V4L2 sensor driver for Sharp RJ54N1CB0C CMOS image + sensor. + + To compile this driver as a module, choose M here: the + module will be called rj54n1. + +config VIDEO_S5C73M3 + tristate "Samsung S5C73M3 sensor support" + depends on SPI + help + This is a V4L2 sensor driver for Samsung S5C73M3 + 8 Mpixel camera. + +config VIDEO_S5K3M5 + tristate "Samsung S5K3M5 sensor support" + select V4L2_CCI_I2C + help + This is a V4L2 sensor driver for Samsung S5K3M5 13MP raw + camera sensor. + + To compile this driver as a module, choose M here: the + module will be called s5k3m5. + +config VIDEO_S5K5BAF + tristate "Samsung S5K5BAF sensor support" + help + This is a V4L2 sensor driver for Samsung S5K5BAF 2M + camera sensor with an embedded SoC image signal processor. + +config VIDEO_S5K6A3 + tristate "Samsung S5K6A3 sensor support" + help + This is a V4L2 sensor driver for Samsung S5K6A3 raw + camera sensor. + +config VIDEO_S5KJN1 + tristate "Samsung S5KJN1 sensor support" + select V4L2_CCI_I2C + help + This is a V4L2 sensor driver for Samsung S5KJN1 50MP raw + camera sensor. + + To compile this driver as a module, choose M here: the + module will be called s5kjn1. + +config VIDEO_T4KA3 + tristate "Toshiba T4KA3 sensor support" + depends on ACPI || COMPILE_TEST + depends on GPIOLIB + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Toshiba T4KA3 8 MP + camera sensor. + + To compile this driver as a module, choose M here: the + module will be called t4ka3. + +config VIDEO_VD55G1 + tristate "ST VD55G1 sensor support" + select V4L2_CCI_I2C + depends on GPIOLIB + help + This is a Video4Linux2 sensor driver for the ST VD55G1 + camera sensor. + + To compile this driver as a module, choose M here: the + module will be called vd55g1. + +config VIDEO_VD56G3 + tristate "ST VD56G3 sensor support" + select V4L2_CCI_I2C + depends on GPIOLIB + help + This is a Video4Linux2 sensor driver for the ST VD56G3 + camera sensor. + + To compile this driver as a module, choose M here: the + module will be called vd56g3. + +config VIDEO_VGXY61 + tristate "ST VGXY61 sensor support" + select V4L2_CCI_I2C + depends on OF && GPIOLIB + help + This is a Video4Linux2 sensor driver for the ST VGXY61 + camera sensor. + +source "drivers/media/i2c/ccs/Kconfig" +source "drivers/media/i2c/et8ek8/Kconfig" + +endif + +menu "Camera ISPs" + visible if MEDIA_CAMERA_SUPPORT + +config VIDEO_THP7312 + tristate "THine THP7312 support" + depends on I2C + select FW_LOADER + select FW_UPLOAD + select MEDIA_CONTROLLER + select V4L2_CCI_I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + This is a Video4Linux2 sensor-level driver for the THine + THP7312 ISP. + +endmenu + +menuconfig VIDEO_CAMERA_LENS + bool "Lens drivers" + depends on MEDIA_CAMERA_SUPPORT && I2C + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + default y + +if VIDEO_CAMERA_LENS + +config VIDEO_AD5820 + tristate "AD5820 lens voice coil support" + depends on GPIOLIB + help + This is a driver for the AD5820 camera lens voice coil. + It is used for example in Nokia N900 (RX-51). + +config VIDEO_AK7375 + tristate "AK7375 lens voice coil support" + help + This is a driver for the AK7375 camera lens voice coil. + AK7375 is a 12 bit DAC with 120mA output current sink + capability. This is designed for linear control of + voice coil motors, controlled via I2C serial interface. + +config VIDEO_DW9714 + tristate "DW9714 lens voice coil support" + depends on GPIOLIB + help + This is a driver for the DW9714 camera lens voice coil. + DW9714 is a 10 bit DAC with 120mA output current sink + capability. This is designed for linear control of + voice coil motors, controlled via I2C serial interface. + +config VIDEO_DW9719 + tristate "DW9719 lens voice coil support" + select V4L2_CCI_I2C + help + This is a driver for the DW9719 camera lens voice coil. + This is designed for linear control of voice coil motors, + controlled via I2C serial interface. + +config VIDEO_DW9768 + tristate "DW9768 lens voice coil support" + help + This is a driver for the DW9768 camera lens voice coil. + DW9768 is a 10 bit DAC with 100mA output current sink + capability. This is designed for linear control of + voice coil motors, controlled via I2C serial interface. + +config VIDEO_DW9807_VCM + tristate "DW9807 lens voice coil support" + help + This is a driver for the DW9807 camera lens voice coil. + DW9807 is a 10 bit DAC with 100mA output current sink + capability. This is designed for linear control of + voice coil motors, controlled via I2C serial interface. + +endif + +menu "Flash devices" + visible if MEDIA_CAMERA_SUPPORT + +config VIDEO_ADP1653 + tristate "ADP1653 flash support" + depends on I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select V4L2_ASYNC + help + This is a driver for the ADP1653 flash controller. It is used for + example in Nokia N900. + +config VIDEO_LM3560 + tristate "LM3560 dual flash driver support" + depends on I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select REGMAP_I2C + select V4L2_ASYNC + help + This is a driver for the lm3560 dual flash controllers. It controls + flash, torch LEDs. + +config VIDEO_LM3646 + tristate "LM3646 dual flash driver support" + depends on I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select REGMAP_I2C + select V4L2_ASYNC + help + This is a driver for the lm3646 dual flash controllers. It controls + flash, torch LEDs. + +endmenu + +# +# V4L2 I2C drivers that aren't related with Camera support +# + +comment "audio, video and radio I2C drivers auto-selected by 'Autoselect ancillary drivers'" + depends on MEDIA_HIDE_ANCILLARY_SUBDRV +# +# Encoder / Decoder module configuration +# + +menu "Audio decoders, processors and mixers" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config VIDEO_CS3308 + tristate "Cirrus Logic CS3308 audio ADC" + depends on VIDEO_DEV && I2C + help + Support for the Cirrus Logic CS3308 High Performance 8-Channel + Analog Volume Control + + To compile this driver as a module, choose M here: the + module will be called cs3308. + +config VIDEO_CS5345 + tristate "Cirrus Logic CS5345 audio ADC" + depends on VIDEO_DEV && I2C + help + Support for the Cirrus Logic CS5345 24-bit, 192 kHz + stereo A/D converter. + + To compile this driver as a module, choose M here: the + module will be called cs5345. + +config VIDEO_CS53L32A + tristate "Cirrus Logic CS53L32A audio ADC" + depends on VIDEO_DEV && I2C + help + Support for the Cirrus Logic CS53L32A low voltage + stereo A/D converter. + + To compile this driver as a module, choose M here: the + module will be called cs53l32a. + +config VIDEO_MSP3400 + tristate "Micronas MSP34xx audio decoders" + depends on VIDEO_DEV && I2C + help + Support for the Micronas MSP34xx series of audio decoders. + + To compile this driver as a module, choose M here: the + module will be called msp3400. + +config VIDEO_SONY_BTF_MPX + tristate "Sony BTF's internal MPX" + depends on VIDEO_DEV && I2C + help + Support for the internal MPX of the Sony BTF-PG472Z tuner. + + To compile this driver as a module, choose M here: the + module will be called sony-btf-mpx. + +config VIDEO_TDA1997X + tristate "NXP TDA1997x HDMI receiver" + depends on VIDEO_DEV && I2C + depends on SND_SOC + select HDMI + select SND_PCM + select V4L2_FWNODE + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + V4L2 subdevice driver for the NXP TDA1997x HDMI receivers. + + To compile this driver as a module, choose M here: the + module will be called tda1997x. + +config VIDEO_TDA7432 + tristate "Philips TDA7432 audio processor" + depends on VIDEO_DEV && I2C + help + Support for tda7432 audio decoder chip found on some bt8xx boards. + + To compile this driver as a module, choose M here: the + module will be called tda7432. + +config VIDEO_TDA9840 + tristate "Philips TDA9840 audio processor" + depends on I2C + help + Support for tda9840 audio decoder chip found on some Zoran boards. + + To compile this driver as a module, choose M here: the + module will be called tda9840. + +config VIDEO_TEA6415C + tristate "Philips TEA6415C audio processor" + depends on I2C + help + Support for tea6415c audio decoder chip found on some bt8xx boards. + + To compile this driver as a module, choose M here: the + module will be called tea6415c. + +config VIDEO_TEA6420 + tristate "Philips TEA6420 audio processor" + depends on I2C + help + Support for tea6420 audio decoder chip found on some bt8xx boards. + + To compile this driver as a module, choose M here: the + module will be called tea6420. + +config VIDEO_TLV320AIC23B + tristate "Texas Instruments TLV320AIC23B audio codec" + depends on VIDEO_DEV && I2C + help + Support for the Texas Instruments TLV320AIC23B audio codec. + + To compile this driver as a module, choose M here: the + module will be called tlv320aic23b. + +config VIDEO_TVAUDIO + tristate "Simple audio decoder chips" + depends on VIDEO_DEV && I2C + help + Support for several audio decoder chips found on some bt8xx boards: + Philips: tda9840, tda9873h, tda9874h/a, tda9850, tda985x, tea6300, + tea6320, tea6420, tda8425, ta8874z. + Microchip: pic16c54 based design on ProVideo PV951 board. + + To compile this driver as a module, choose M here: the + module will be called tvaudio. + +config VIDEO_UDA1342 + tristate "Philips UDA1342 audio codec" + depends on VIDEO_DEV && I2C + help + Support for the Philips UDA1342 audio codec. + + To compile this driver as a module, choose M here: the + module will be called uda1342. + +config VIDEO_VP27SMPX + tristate "Panasonic VP27's internal MPX" + depends on VIDEO_DEV && I2C + help + Support for the internal MPX of the Panasonic VP27s tuner. + + To compile this driver as a module, choose M here: the + module will be called vp27smpx. + +config VIDEO_WM8739 + tristate "Wolfson Microelectronics WM8739 stereo audio ADC" + depends on VIDEO_DEV && I2C + help + Support for the Wolfson Microelectronics WM8739 + stereo A/D Converter. + + To compile this driver as a module, choose M here: the + module will be called wm8739. + +config VIDEO_WM8775 + tristate "Wolfson Microelectronics WM8775 audio ADC with input mixer" + depends on VIDEO_DEV && I2C + help + Support for the Wolfson Microelectronics WM8775 high + performance stereo A/D Converter with a 4 channel input mixer. + + To compile this driver as a module, choose M here: the + module will be called wm8775. + +endmenu + +menu "RDS decoders" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config VIDEO_SAA6588 + tristate "SAA6588 Radio Chip RDS decoder support" + depends on VIDEO_DEV && I2C + + help + Support for this Radio Data System (RDS) decoder. This allows + seeing radio station identification transmitted using this + standard. + + To compile this driver as a module, choose M here: the + module will be called saa6588. + +endmenu + +menu "Video decoders" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config VIDEO_ADV7180 + tristate "Analog Devices ADV7180 decoder" + depends on GPIOLIB && VIDEO_DEV && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_ASYNC + help + Support for the Analog Devices ADV7180 video decoder. + + To compile this driver as a module, choose M here: the + module will be called adv7180. + +config VIDEO_ADV7183 + tristate "Analog Devices ADV7183 decoder" + depends on VIDEO_DEV && I2C + help + V4l2 subdevice driver for the Analog Devices + ADV7183 video decoder. + + To compile this driver as a module, choose M here: the + module will be called adv7183. + +config VIDEO_ADV748X + tristate "Analog Devices ADV748x decoder" + depends on VIDEO_DEV && I2C + depends on OF + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select REGMAP_I2C + select V4L2_FWNODE + help + V4L2 subdevice driver for the Analog Devices + ADV7481 and ADV7482 HDMI/Analog video decoders. + + To compile this driver as a module, choose M here: the + module will be called adv748x. + +config VIDEO_ADV7604 + tristate "Analog Devices ADV7604 decoder" + depends on VIDEO_DEV && I2C + depends on GPIOLIB || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select REGMAP_I2C + select HDMI + select V4L2_FWNODE + help + Support for the Analog Devices ADV7604 video decoder. + + This is a Analog Devices Component/Graphics Digitizer + with 4:1 Multiplexed HDMI Receiver. + + To compile this driver as a module, choose M here: the + module will be called adv7604. + +config VIDEO_ADV7604_CEC + bool "Enable Analog Devices ADV7604 CEC support" + depends on VIDEO_ADV7604 + select CEC_CORE + help + When selected the adv7604 will support the optional + HDMI CEC feature. + +config VIDEO_ADV7842 + tristate "Analog Devices ADV7842 decoder" + depends on VIDEO_DEV && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select HDMI + help + Support for the Analog Devices ADV7842 video decoder. + + This is a Analog Devices Component/Graphics/SD Digitizer + with 2:1 Multiplexed HDMI Receiver. + + To compile this driver as a module, choose M here: the + module will be called adv7842. + +config VIDEO_ADV7842_CEC + bool "Enable Analog Devices ADV7842 CEC support" + depends on VIDEO_ADV7842 + select CEC_CORE + help + When selected the adv7842 will support the optional + HDMI CEC feature. + +config VIDEO_BT819 + tristate "BT819A VideoStream decoder" + depends on VIDEO_DEV && I2C + help + Support for BT819A video decoder. + + To compile this driver as a module, choose M here: the + module will be called bt819. + +config VIDEO_BT856 + tristate "BT856 VideoStream decoder" + depends on VIDEO_DEV && I2C + help + Support for BT856 video decoder. + + To compile this driver as a module, choose M here: the + module will be called bt856. + +config VIDEO_BT866 + tristate "BT866 VideoStream decoder" + depends on VIDEO_DEV && I2C + help + Support for BT866 video decoder. + + To compile this driver as a module, choose M here: the + module will be called bt866. + +config VIDEO_ISL7998X + tristate "Intersil ISL7998x video decoder" + depends on VIDEO_DEV && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + Support for Intersil ISL7998x analog to MIPI-CSI2 or + BT.656 decoder. + +config VIDEO_LT6911UXE + tristate "Lontium LT6911UXE decoder" + depends on ACPI && VIDEO_DEV && I2C + select V4L2_FWNODE + select V4L2_CCI_I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This is a Video4Linux2 sensor-level driver for the Lontium + LT6911UXE HDMI to MIPI CSI-2 bridge. + + To compile this driver as a module, choose M here: the + module will be called lt6911uxe. + +config VIDEO_KS0127 + tristate "KS0127 video decoder" + depends on VIDEO_DEV && I2C + help + Support for KS0127 video decoder. + + This chip is used on AverMedia AVS6EYES Zoran-based MJPEG + cards. + + To compile this driver as a module, choose M here: the + module will be called ks0127. + +config VIDEO_MAX9286 + tristate "Maxim MAX9286 GMSL deserializer support" + depends on I2C && I2C_MUX + depends on VIDEO_DEV + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + help + This driver supports the Maxim MAX9286 GMSL deserializer. + + To compile this driver as a module, choose M here: the + module will be called max9286. + +config VIDEO_ML86V7667 + tristate "OKI ML86V7667 video decoder" + depends on VIDEO_DEV && I2C + help + Support for the OKI Semiconductor ML86V7667 video decoder. + + To compile this driver as a module, choose M here: the + module will be called ml86v7667. + +config VIDEO_SAA7110 + tristate "Philips SAA7110 video decoder" + depends on VIDEO_DEV && I2C + help + Support for the Philips SAA7110 video decoders. + + To compile this driver as a module, choose M here: the + module will be called saa7110. + +config VIDEO_SAA711X + tristate "Philips SAA7111/3/4/5 video decoders" + depends on VIDEO_DEV && I2C + help + Support for the Philips SAA7111/3/4/5 video decoders. + + To compile this driver as a module, choose M here: the + module will be called saa7115. + +config VIDEO_TC358743 + tristate "Toshiba TC358743 decoder" + depends on VIDEO_DEV && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select HDMI + select V4L2_FWNODE + help + Support for the Toshiba TC358743 HDMI to MIPI CSI-2 bridge. + + To compile this driver as a module, choose M here: the + module will be called tc358743. + +config VIDEO_TC358743_CEC + bool "Enable Toshiba TC358743 CEC support" + depends on VIDEO_TC358743 + select CEC_CORE + help + When selected the tc358743 will support the optional + HDMI CEC feature. + +config VIDEO_TC358746 + tristate "Toshiba TC358746 parallel-CSI2 bridge" + depends on VIDEO_DEV && PM && I2C + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + select V4L2_FWNODE + select GENERIC_PHY + select GENERIC_PHY_MIPI_DPHY + select REGMAP_I2C + help + Support for the Toshiba TC358746 parallel to MIPI CSI-2 bridge. + The bridge can work in both directions but currently only the + parallel-in / csi-out path is supported. + + To compile this driver as a module, choose M here: the + module will be called tc358746. + +config VIDEO_TVP514X + tristate "Texas Instruments TVP514x video decoder" + depends on VIDEO_DEV && I2C + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the TI TVP5146/47 + decoder. It is currently working with the TI OMAP3 camera + controller. + + To compile this driver as a module, choose M here: the + module will be called tvp514x. + +config VIDEO_TVP5150 + tristate "Texas Instruments TVP5150 video decoder" + depends on VIDEO_DEV && I2C + select V4L2_FWNODE + select REGMAP_I2C + help + Support for the Texas Instruments TVP5150 video decoder. + + To compile this driver as a module, choose M here: the + module will be called tvp5150. + +config VIDEO_TVP7002 + tristate "Texas Instruments TVP7002 video decoder" + depends on VIDEO_DEV && I2C + select V4L2_FWNODE + help + Support for the Texas Instruments TVP7002 video decoder. + + To compile this driver as a module, choose M here: the + module will be called tvp7002. + +config VIDEO_TW2804 + tristate "Techwell TW2804 multiple video decoder" + depends on VIDEO_DEV && I2C + help + Support for the Techwell tw2804 multiple video decoder. + + To compile this driver as a module, choose M here: the + module will be called tw2804. + +config VIDEO_TW9900 + tristate "Techwell TW9900 video decoder" + depends on GPIOLIB + depends on VIDEO_DEV && I2C + depends on PM + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_ASYNC + help + Support for the Techwell TW9900 multi-standard video decoder. + It supports NTSC, PAL standards with auto-detection features. + + To compile this driver as a module, choose M here: the + module will be called tw9900. + +config VIDEO_TW9903 + tristate "Techwell TW9903 video decoder" + depends on VIDEO_DEV && I2C + help + Support for the Techwell tw9903 multi-standard video decoder + with high quality down scaler. + + To compile this driver as a module, choose M here: the + module will be called tw9903. + +config VIDEO_TW9906 + tristate "Techwell TW9906 video decoder" + depends on VIDEO_DEV && I2C + help + Support for the Techwell tw9906 enhanced multi-standard comb filter + video decoder with YCbCr input support. + + To compile this driver as a module, choose M here: the + module will be called tw9906. + +config VIDEO_TW9910 + tristate "Techwell TW9910 video decoder" + depends on VIDEO_DEV && I2C + select V4L2_ASYNC + help + Support for Techwell TW9910 NTSC/PAL/SECAM video decoder. + + To compile this driver as a module, choose M here: the + module will be called tw9910. + +config VIDEO_VPX3220 + tristate "vpx3220a, vpx3216b & vpx3214c video decoders" + depends on VIDEO_DEV && I2C + help + Support for VPX322x video decoders. + + To compile this driver as a module, choose M here: the + module will be called vpx3220. + +comment "Video and audio decoders" + +config VIDEO_SAA717X + tristate "Philips SAA7171/3/4 audio/video decoders" + depends on VIDEO_DEV && I2C + help + Support for the Philips SAA7171/3/4 audio/video decoders. + + To compile this driver as a module, choose M here: the + module will be called saa717x. + +source "drivers/media/i2c/cx25840/Kconfig" + +endmenu + +menu "Video encoders" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config VIDEO_ADV7170 + tristate "Analog Devices ADV7170 video encoder" + depends on VIDEO_DEV && I2C + help + Support for the Analog Devices ADV7170 video encoder driver + + To compile this driver as a module, choose M here: the + module will be called adv7170. + +config VIDEO_ADV7175 + tristate "Analog Devices ADV7175 video encoder" + depends on VIDEO_DEV && I2C + help + Support for the Analog Devices ADV7175 video encoder driver + + To compile this driver as a module, choose M here: the + module will be called adv7175. + +config VIDEO_ADV7343 + tristate "ADV7343 video encoder" + depends on I2C + select V4L2_ASYNC + help + Support for Analog Devices I2C bus based ADV7343 encoder. + + To compile this driver as a module, choose M here: the + module will be called adv7343. + +config VIDEO_ADV7393 + tristate "ADV7393 video encoder" + depends on I2C + help + Support for Analog Devices I2C bus based ADV7393 encoder. + + To compile this driver as a module, choose M here: the + module will be called adv7393. + +config VIDEO_ADV7511 + tristate "Analog Devices ADV7511 encoder" + depends on VIDEO_DEV && I2C + depends on DRM_I2C_ADV7511=n || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select HDMI + help + Support for the Analog Devices ADV7511 video encoder. + + This is a Analog Devices HDMI transmitter. + + To compile this driver as a module, choose M here: the + module will be called adv7511. + +config VIDEO_ADV7511_CEC + bool "Enable Analog Devices ADV7511 CEC support" + depends on VIDEO_ADV7511 + select CEC_CORE + help + When selected the adv7511 will support the optional + HDMI CEC feature. + +config VIDEO_AK881X + tristate "AK8813/AK8814 video encoders" + depends on I2C + help + Video output driver for AKM AK8813 and AK8814 TV encoders + +config VIDEO_SAA7127 + tristate "Philips SAA7127/9 digital video encoders" + depends on VIDEO_DEV && I2C + help + Support for the Philips SAA7127/9 digital video encoders. + + To compile this driver as a module, choose M here: the + module will be called saa7127. + +config VIDEO_SAA7185 + tristate "Philips SAA7185 video encoder" + depends on VIDEO_DEV && I2C + help + Support for the Philips SAA7185 video encoder. + + To compile this driver as a module, choose M here: the + module will be called saa7185. + +config VIDEO_THS8200 + tristate "Texas Instruments THS8200 video encoder" + depends on VIDEO_DEV && I2C + select V4L2_ASYNC + help + Support for the Texas Instruments THS8200 video encoder. + + To compile this driver as a module, choose M here: the + module will be called ths8200. + +endmenu + +menu "Video improvement chips" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config VIDEO_UPD64031A + tristate "NEC Electronics uPD64031A Ghost Reduction" + depends on VIDEO_DEV && I2C + select V4L2_ASYNC + help + Support for the NEC Electronics uPD64031A Ghost Reduction + video chip. It is most often found in NTSC TV cards made for + Japan and is used to reduce the 'ghosting' effect that can + be present in analog TV broadcasts. + + To compile this driver as a module, choose M here: the + module will be called upd64031a. + +config VIDEO_UPD64083 + tristate "NEC Electronics uPD64083 3-Dimensional Y/C separation" + depends on VIDEO_DEV && I2C + help + Support for the NEC Electronics uPD64083 3-Dimensional Y/C + separation video chip. It is used to improve the quality of + the colors of a composite signal. + + To compile this driver as a module, choose M here: the + module will be called upd64083. + +endmenu + +menu "Audio/Video compression chips" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config VIDEO_SAA6752HS + tristate "Philips SAA6752HS MPEG-2 Audio/Video Encoder" + depends on VIDEO_DEV && I2C + select CRC32 + help + Support for the Philips SAA6752HS MPEG-2 video and MPEG-audio/AC-3 + audio encoder with multiplexer. + + To compile this driver as a module, choose M here: the + module will be called saa6752hs. + +endmenu + +menu "SDR tuner chips" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +config SDR_MAX2175 + tristate "Maxim 2175 RF to Bits tuner" + depends on VIDEO_DEV && MEDIA_SDR_SUPPORT && I2C + select REGMAP_I2C + select V4L2_ASYNC + help + Support for Maxim 2175 tuner. It is an advanced analog/digital + radio receiver with RF-to-Bits front-end designed for SDR solutions. + + To compile this driver as a module, choose M here; the + module will be called max2175. + +endmenu + +menu "Miscellaneous helper chips" + visible if !MEDIA_HIDE_ANCILLARY_SUBDRV + +source "drivers/media/i2c/cvs/Kconfig" + +config VIDEO_I2C + tristate "I2C transport video support" + depends on VIDEO_DEV && I2C + select VIDEOBUF2_VMALLOC + imply HWMON + help + Enable the I2C transport video support which supports the + following: + * Panasonic AMG88xx Grid-Eye Sensors + * Melexis MLX90640 Thermal Cameras + + To compile this driver as a module, choose M here: the + module will be called video-i2c + +config VIDEO_M52790 + tristate "Mitsubishi M52790 A/V switch" + depends on VIDEO_DEV && I2C + help + Support for the Mitsubishi M52790 A/V switch. + + To compile this driver as a module, choose M here: the + module will be called m52790. + +config VIDEO_ST_MIPID02 + tristate "STMicroelectronics MIPID02 CSI-2 to PARALLEL bridge" + depends on I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_CCI_I2C + select V4L2_FWNODE + help + Support for STMicroelectronics MIPID02 CSI-2 to PARALLEL bridge. + It is used to allow usage of CSI-2 sensor with PARALLEL port + controller. + + To compile this driver as a module, choose M here: the + module will be called st-mipid02. + +config VIDEO_THS7303 + tristate "THS7303/53 Video Amplifier" + depends on VIDEO_DEV && I2C + select V4L2_ASYNC + help + Support for TI THS7303/53 video amplifier + + To compile this driver as a module, choose M here: the + module will be called ths7303. + +endmenu + +# +# Video serializers and deserializers (e.g. FPD-Link) +# + +menu "Video serializers and deserializers" + +config VIDEO_DS90UB913 + tristate "TI DS90UB913 FPD-Link III Serializer" + depends on OF && I2C && VIDEO_DEV && COMMON_CLK + select I2C_ATR + select MEDIA_CONTROLLER + select GPIOLIB + select REGMAP_I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Device driver for the Texas Instruments DS90UB913 + FPD-Link III Serializer. + +config VIDEO_DS90UB953 + tristate "TI FPD-Link III/IV CSI-2 Serializers" + depends on OF && I2C && VIDEO_DEV && COMMON_CLK + select I2C_ATR + select MEDIA_CONTROLLER + select GPIOLIB + select REGMAP_I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Device driver for the Texas Instruments DS90UB953 + FPD-Link III Serializer and DS90UB971 FPD-Link IV Serializer. + +config VIDEO_DS90UB960 + tristate "TI FPD-Link III/IV Deserializers" + depends on OF && I2C && VIDEO_DEV && COMMON_CLK + select I2C_ATR + select MEDIA_CONTROLLER + select GPIOLIB + select REGMAP_I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Device driver for the Texas Instruments DS90UB954, DS90UB960 + FPD-Link III Deserializers and DS90UB9702 FPD-Link IV Deserializer. + +config VIDEO_MAX96714 + tristate "Maxim MAX96714 GMSL2 deserializer" + depends on OF && I2C && VIDEO_DEV + select I2C_MUX + select MEDIA_CONTROLLER + select GPIOLIB + select V4L2_CCI_I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Device driver for the Maxim MAX96714 GMSL2 Deserializer. + MAX96714 deserializers convert a GMSL2 input to MIPI CSI-2 + output. + + To compile this driver as a module, choose M here: the + module will be called max96714. + +config VIDEO_MAX96717 + tristate "Maxim MAX96717 GMSL2 Serializer support" + depends on I2C && VIDEO_DEV && COMMON_CLK + select I2C_MUX + select MEDIA_CONTROLLER + select GPIOLIB + select V4L2_CCI_I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Device driver for the Maxim MAX96717 GMSL2 Serializer. + MAX96717 serializers convert video on a MIPI CSI-2 + input to a GMSL2 output. + + To compile this driver as a module, choose M here: the + module will be called max96717. + +endmenu + +endif # VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile new file mode 100644 index 000000000..d04bd5724 --- /dev/null +++ b/drivers/media/i2c/Makefile @@ -0,0 +1,176 @@ +# SPDX-License-Identifier: GPL-2.0 + +msp3400-objs := msp3400-driver.o msp3400-kthreads.o + +obj-$(CONFIG_SDR_MAX2175) += max2175.o +obj-$(CONFIG_VIDEO_AD5820) += ad5820.o +obj-$(CONFIG_VIDEO_ADP1653) += adp1653.o +obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o +obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o +obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o +obj-$(CONFIG_VIDEO_ADV7183) += adv7183.o +obj-$(CONFIG_VIDEO_ADV7343) += adv7343.o +obj-$(CONFIG_VIDEO_ADV7393) += adv7393.o +obj-$(CONFIG_VIDEO_ADV748X) += adv748x/ +obj-$(CONFIG_VIDEO_ADV7511) += adv7511-v4l2.o +obj-$(CONFIG_VIDEO_ADV7604) += adv7604.o +obj-$(CONFIG_VIDEO_ADV7842) += adv7842.o +obj-$(CONFIG_VIDEO_AK7375) += ak7375.o +obj-$(CONFIG_VIDEO_AK881X) += ak881x.o +obj-$(CONFIG_VIDEO_ALVIUM_CSI2) += alvium-csi2.o +obj-$(CONFIG_VIDEO_APTINA_PLL) += aptina-pll.o +obj-$(CONFIG_VIDEO_AR0521) += ar0521.o +obj-$(CONFIG_VIDEO_BT819) += bt819.o +obj-$(CONFIG_VIDEO_BT856) += bt856.o +obj-$(CONFIG_VIDEO_BT866) += bt866.o +obj-$(CONFIG_VIDEO_CCS) += ccs/ +obj-$(CONFIG_VIDEO_CCS_PLL) += ccs-pll.o +obj-$(CONFIG_VIDEO_CS3308) += cs3308.o +obj-$(CONFIG_VIDEO_CS5345) += cs5345.o +obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o +obj-$(CONFIG_VIDEO_CX25840) += cx25840/ +obj-$(CONFIG_VIDEO_DS90UB913) += ds90ub913.o +obj-$(CONFIG_VIDEO_DS90UB953) += ds90ub953.o +obj-$(CONFIG_VIDEO_DS90UB960) += ds90ub960.o +obj-$(CONFIG_VIDEO_DW9714) += dw9714.o +obj-$(CONFIG_VIDEO_DW9719) += dw9719.o +obj-$(CONFIG_VIDEO_DW9768) += dw9768.o +obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o +obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ +obj-$(CONFIG_VIDEO_GC0308) += gc0308.o +obj-$(CONFIG_VIDEO_GC0310) += gc0310.o +obj-$(CONFIG_VIDEO_GC05A2) += gc05a2.o +obj-$(CONFIG_VIDEO_GC08A3) += gc08a3.o +obj-$(CONFIG_VIDEO_GC2145) += gc2145.o +obj-$(CONFIG_VIDEO_HI556) += hi556.o +obj-$(CONFIG_VIDEO_HI846) += hi846.o +obj-$(CONFIG_VIDEO_HI847) += hi847.o +obj-$(CONFIG_VIDEO_I2C) += video-i2c.o +obj-$(CONFIG_VIDEO_IMX111) += imx111.o +obj-$(CONFIG_VIDEO_IMX208) += imx208.o +obj-$(CONFIG_VIDEO_IMX214) += imx214.o +obj-$(CONFIG_VIDEO_IMX219) += imx219.o +obj-$(CONFIG_VIDEO_IMX258) += imx258.o +obj-$(CONFIG_VIDEO_IMX274) += imx274.o +obj-$(CONFIG_VIDEO_IMX283) += imx283.o +obj-$(CONFIG_VIDEO_IMX290) += imx290.o +obj-$(CONFIG_VIDEO_IMX296) += imx296.o +obj-$(CONFIG_VIDEO_IMX319) += imx319.o +obj-$(CONFIG_VIDEO_IMX334) += imx334.o +obj-$(CONFIG_VIDEO_IMX335) += imx335.o +obj-$(CONFIG_VIDEO_IMX355) += imx355.o +obj-$(CONFIG_VIDEO_IMX412) += imx412.o +obj-$(CONFIG_VIDEO_IMX415) += imx415.o +obj-$(CONFIG_VIDEO_IMX678) += imx678.o +obj-$(CONFIG_VIDEO_IMX471) += imx471.o +obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o +obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o +obj-$(CONFIG_VIDEO_KS0127) += ks0127.o +obj-$(CONFIG_VIDEO_LM3560) += lm3560.o +obj-$(CONFIG_VIDEO_LM3646) += lm3646.o +obj-$(CONFIG_VIDEO_LT6911UXE) += lt6911uxe.o +obj-$(CONFIG_VIDEO_M52790) += m52790.o +obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o +obj-$(CONFIG_VIDEO_MAX9286) += max9286.o +obj-$(CONFIG_VIDEO_MAX96714) += max96714.o +obj-$(CONFIG_VIDEO_MAX96717) += max96717.o +obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o +obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o +obj-$(CONFIG_VIDEO_MT9M001) += mt9m001.o +obj-$(CONFIG_VIDEO_MT9M111) += mt9m111.o +obj-$(CONFIG_VIDEO_MT9M114) += mt9m114.o +obj-$(CONFIG_VIDEO_MT9P031) += mt9p031.o +obj-$(CONFIG_VIDEO_MT9T112) += mt9t112.o +obj-$(CONFIG_VIDEO_MT9V011) += mt9v011.o +obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o +obj-$(CONFIG_VIDEO_MT9V111) += mt9v111.o +obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o +obj-$(CONFIG_VIDEO_OG0VE1B) += og0ve1b.o +obj-$(CONFIG_VIDEO_OS05B10) += os05b10.o +obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o +obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o +obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o +obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o +obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o +obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o +obj-$(CONFIG_VIDEO_OV13858) += ov13858.o +obj-$(CONFIG_VIDEO_OV13B10) += ov13b10.o +obj-$(CONFIG_VIDEO_OV2640) += ov2640.o +obj-$(CONFIG_VIDEO_OV2659) += ov2659.o +obj-$(CONFIG_VIDEO_OV2680) += ov2680.o +obj-$(CONFIG_VIDEO_OV2685) += ov2685.o +obj-$(CONFIG_VIDEO_OV2732) += ov2732.o +obj-$(CONFIG_VIDEO_OV2735) += ov2735.o +obj-$(CONFIG_VIDEO_OV2740) += ov2740.o +obj-$(CONFIG_VIDEO_OV4689) += ov4689.o +obj-$(CONFIG_VIDEO_OV5640) += ov5640.o +obj-$(CONFIG_VIDEO_OV5645) += ov5645.o +obj-$(CONFIG_VIDEO_OV5647) += ov5647.o +obj-$(CONFIG_VIDEO_OV5648) += ov5648.o +obj-$(CONFIG_VIDEO_OV5670) += ov5670.o +obj-$(CONFIG_VIDEO_OV5675) += ov5675.o +obj-$(CONFIG_VIDEO_OV5693) += ov5693.o +obj-$(CONFIG_VIDEO_OV5695) += ov5695.o +obj-$(CONFIG_VIDEO_OV6211) += ov6211.o +obj-$(CONFIG_VIDEO_OV64A40) += ov64a40.o +obj-$(CONFIG_VIDEO_OV7251) += ov7251.o +obj-$(CONFIG_VIDEO_OV7640) += ov7640.o +obj-$(CONFIG_VIDEO_OV7670) += ov7670.o +obj-$(CONFIG_VIDEO_OV772X) += ov772x.o +obj-$(CONFIG_VIDEO_OV7740) += ov7740.o +obj-$(CONFIG_VIDEO_OV8856) += ov8856.o +obj-$(CONFIG_VIDEO_OV8858) += ov8858.o +obj-$(CONFIG_VIDEO_OV8865) += ov8865.o +obj-$(CONFIG_VIDEO_OV9282) += ov9282.o +obj-$(CONFIG_VIDEO_OV9640) += ov9640.o +obj-$(CONFIG_VIDEO_OV9650) += ov9650.o +obj-$(CONFIG_VIDEO_OV9734) += ov9734.o +obj-$(CONFIG_VIDEO_RDACM20) += rdacm20.o +obj-$(CONFIG_VIDEO_RDACM21) += rdacm21.o +obj-$(CONFIG_VIDEO_RJ54N1) += rj54n1cb0c.o +obj-$(CONFIG_VIDEO_S5C73M3) += s5c73m3/ +obj-$(CONFIG_VIDEO_S5K3M5) += s5k3m5.o +obj-$(CONFIG_VIDEO_S5K5BAF) += s5k5baf.o +obj-$(CONFIG_VIDEO_S5K6A3) += s5k6a3.o +obj-$(CONFIG_VIDEO_S5KJN1) += s5kjn1.o +obj-$(CONFIG_VIDEO_SAA6588) += saa6588.o +obj-$(CONFIG_VIDEO_SAA6752HS) += saa6752hs.o +obj-$(CONFIG_VIDEO_SAA7110) += saa7110.o +obj-$(CONFIG_VIDEO_SAA711X) += saa7115.o +obj-$(CONFIG_VIDEO_SAA7127) += saa7127.o +obj-$(CONFIG_VIDEO_SAA717X) += saa717x.o +obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o +obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o +obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o +obj-$(CONFIG_VIDEO_T4KA3) += t4ka3.o +obj-$(CONFIG_VIDEO_TC358743) += tc358743.o +obj-$(CONFIG_VIDEO_TC358746) += tc358746.o +obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o +obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o +obj-$(CONFIG_VIDEO_TDA9840) += tda9840.o +obj-$(CONFIG_VIDEO_TEA6415C) += tea6415c.o +obj-$(CONFIG_VIDEO_TEA6420) += tea6420.o +obj-$(CONFIG_VIDEO_THP7312) += thp7312.o +obj-$(CONFIG_VIDEO_THS7303) += ths7303.o +obj-$(CONFIG_VIDEO_THS8200) += ths8200.o +obj-$(CONFIG_VIDEO_TLV320AIC23B) += tlv320aic23b.o +obj-$(CONFIG_VIDEO_TVAUDIO) += tvaudio.o +obj-$(CONFIG_VIDEO_TVP514X) += tvp514x.o +obj-$(CONFIG_VIDEO_TVP5150) += tvp5150.o +obj-$(CONFIG_VIDEO_TVP7002) += tvp7002.o +obj-$(CONFIG_VIDEO_TW2804) += tw2804.o +obj-$(CONFIG_VIDEO_TW9900) += tw9900.o +obj-$(CONFIG_VIDEO_TW9903) += tw9903.o +obj-$(CONFIG_VIDEO_TW9906) += tw9906.o +obj-$(CONFIG_VIDEO_TW9910) += tw9910.o +obj-$(CONFIG_VIDEO_UDA1342) += uda1342.o +obj-$(CONFIG_VIDEO_UPD64031A) += upd64031a.o +obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o +obj-$(CONFIG_VIDEO_VD55G1) += vd55g1.o +obj-$(CONFIG_VIDEO_VD56G3) += vd56g3.o +obj-$(CONFIG_VIDEO_VGXY61) += vgxy61.o +obj-$(CONFIG_VIDEO_VP27SMPX) += vp27smpx.o +obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o +obj-$(CONFIG_VIDEO_WM8739) += wm8739.o +obj-$(CONFIG_VIDEO_WM8775) += wm8775.o +obj-$(CONFIG_VIDEO_INTEL_CVS) += cvs/ diff --git a/drivers/media/i2c/ad5820.c b/drivers/media/i2c/ad5820.c new file mode 100644 index 000000000..fcce2857b --- /dev/null +++ b/drivers/media/i2c/ad5820.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/ad5820.c + * + * AD5820 DAC driver for camera voice coil focus. + * + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2007 Texas Instruments + * Copyright (C) 2016 Pavel Machek + * + * Contact: Tuukka Toivonen + * Sakari Ailus + * + * Based on af_d88.c by Texas Instruments. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Register definitions */ +#define AD5820_POWER_DOWN (1 << 15) +#define AD5820_DAC_SHIFT 4 +#define AD5820_RAMP_MODE_LINEAR (0 << 3) +#define AD5820_RAMP_MODE_64_16 (1 << 3) + +#define CODE_TO_RAMP_US(s) ((s) == 0 ? 0 : (1 << ((s) - 1)) * 50) +#define RAMP_US_TO_CODE(c) fls(((c) + ((c)>>1)) / 50) + +#define to_ad5820_device(sd) container_of(sd, struct ad5820_device, subdev) + +struct ad5820_device { + struct v4l2_subdev subdev; + struct ad5820_platform_data *platform_data; + struct regulator *vana; + + struct v4l2_ctrl_handler ctrls; + u32 focus_absolute; + u32 focus_ramp_time; + u32 focus_ramp_mode; + + struct gpio_desc *enable_gpio; + + struct mutex power_lock; + int power_count; + + bool standby; +}; + +static int ad5820_write(struct ad5820_device *coil, u16 data) +{ + struct i2c_client *client = v4l2_get_subdevdata(&coil->subdev); + struct i2c_msg msg; + __be16 be_data; + int r; + + if (!client->adapter) + return -ENODEV; + + be_data = cpu_to_be16(data); + msg.addr = client->addr; + msg.flags = 0; + msg.len = 2; + msg.buf = (u8 *)&be_data; + + r = i2c_transfer(client->adapter, &msg, 1); + if (r < 0) { + dev_err(&client->dev, "write failed, error %d\n", r); + return r; + } + + return 0; +} + +/* + * Calculate status word and write it to the device based on current + * values of V4L2 controls. It is assumed that the stored V4L2 control + * values are properly limited and rounded. + */ +static int ad5820_update_hw(struct ad5820_device *coil) +{ + u16 status; + + status = RAMP_US_TO_CODE(coil->focus_ramp_time); + status |= coil->focus_ramp_mode + ? AD5820_RAMP_MODE_64_16 : AD5820_RAMP_MODE_LINEAR; + status |= coil->focus_absolute << AD5820_DAC_SHIFT; + + if (coil->standby) + status |= AD5820_POWER_DOWN; + + return ad5820_write(coil, status); +} + +/* + * Power handling + */ +static int ad5820_power_off(struct ad5820_device *coil, bool standby) +{ + int ret = 0, ret2; + + /* + * Go to standby first as real power off my be denied by the hardware + * (single power line control for both coil and sensor). + */ + if (standby) { + coil->standby = true; + ret = ad5820_update_hw(coil); + } + + gpiod_set_value_cansleep(coil->enable_gpio, 0); + + ret2 = regulator_disable(coil->vana); + if (ret) + return ret; + return ret2; +} + +static int ad5820_power_on(struct ad5820_device *coil, bool restore) +{ + int ret; + + ret = regulator_enable(coil->vana); + if (ret < 0) + return ret; + + gpiod_set_value_cansleep(coil->enable_gpio, 1); + + if (restore) { + /* Restore the hardware settings. */ + coil->standby = false; + ret = ad5820_update_hw(coil); + if (ret) + goto fail; + } + return 0; + +fail: + gpiod_set_value_cansleep(coil->enable_gpio, 0); + coil->standby = true; + regulator_disable(coil->vana); + + return ret; +} + +/* + * V4L2 controls + */ +static int ad5820_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ad5820_device *coil = + container_of(ctrl->handler, struct ad5820_device, ctrls); + + switch (ctrl->id) { + case V4L2_CID_FOCUS_ABSOLUTE: + coil->focus_absolute = ctrl->val; + return ad5820_update_hw(coil); + } + + return 0; +} + +static const struct v4l2_ctrl_ops ad5820_ctrl_ops = { + .s_ctrl = ad5820_set_ctrl, +}; + + +static int ad5820_init_controls(struct ad5820_device *coil) +{ + v4l2_ctrl_handler_init(&coil->ctrls, 1); + + /* + * V4L2_CID_FOCUS_ABSOLUTE + * + * Minimum current is 0 mA, maximum is 100 mA. Thus, 1 code is + * equivalent to 100/1023 = 0.0978 mA. Nevertheless, we do not use [mA] + * for focus position, because it is meaningless for user. Meaningful + * would be to use focus distance or even its inverse, but since the + * driver doesn't have sufficiently knowledge to do the conversion, we + * will just use abstract codes here. In any case, smaller value = focus + * position farther from camera. The default zero value means focus at + * infinity, and also least current consumption. + */ + v4l2_ctrl_new_std(&coil->ctrls, &ad5820_ctrl_ops, + V4L2_CID_FOCUS_ABSOLUTE, 0, 1023, 1, 0); + + if (coil->ctrls.error) + return coil->ctrls.error; + + coil->focus_absolute = 0; + coil->focus_ramp_time = 0; + coil->focus_ramp_mode = 0; + + coil->subdev.ctrl_handler = &coil->ctrls; + + return 0; +} + +/* + * V4L2 subdev operations + */ +static int ad5820_registered(struct v4l2_subdev *subdev) +{ + struct ad5820_device *coil = to_ad5820_device(subdev); + + return ad5820_init_controls(coil); +} + +static int +ad5820_set_power(struct v4l2_subdev *subdev, int on) +{ + struct ad5820_device *coil = to_ad5820_device(subdev); + int ret = 0; + + mutex_lock(&coil->power_lock); + + /* + * If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (coil->power_count == !on) { + ret = on ? ad5820_power_on(coil, true) : + ad5820_power_off(coil, true); + if (ret < 0) + goto done; + } + + /* Update the power count. */ + coil->power_count += on ? 1 : -1; + WARN_ON(coil->power_count < 0); + +done: + mutex_unlock(&coil->power_lock); + return ret; +} + +static int ad5820_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return ad5820_set_power(sd, 1); +} + +static int ad5820_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return ad5820_set_power(sd, 0); +} + +static const struct v4l2_subdev_core_ops ad5820_core_ops = { + .s_power = ad5820_set_power, +}; + +static const struct v4l2_subdev_ops ad5820_ops = { + .core = &ad5820_core_ops, +}; + +static const struct v4l2_subdev_internal_ops ad5820_internal_ops = { + .registered = ad5820_registered, + .open = ad5820_open, + .close = ad5820_close, +}; + +/* + * I2C driver + */ +static int __maybe_unused ad5820_suspend(struct device *dev) +{ + struct v4l2_subdev *subdev = dev_get_drvdata(dev); + struct ad5820_device *coil = to_ad5820_device(subdev); + + if (!coil->power_count) + return 0; + + return ad5820_power_off(coil, false); +} + +static int __maybe_unused ad5820_resume(struct device *dev) +{ + struct v4l2_subdev *subdev = dev_get_drvdata(dev); + struct ad5820_device *coil = to_ad5820_device(subdev); + + if (!coil->power_count) + return 0; + + return ad5820_power_on(coil, true); +} + +static int ad5820_probe(struct i2c_client *client) +{ + struct ad5820_device *coil; + int ret; + + coil = devm_kzalloc(&client->dev, sizeof(*coil), GFP_KERNEL); + if (!coil) + return -ENOMEM; + + coil->vana = devm_regulator_get(&client->dev, "VANA"); + if (IS_ERR(coil->vana)) + return dev_err_probe(&client->dev, PTR_ERR(coil->vana), + "could not get regulator for vana\n"); + + coil->enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(coil->enable_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(coil->enable_gpio), + "could not get enable gpio\n"); + + mutex_init(&coil->power_lock); + + v4l2_i2c_subdev_init(&coil->subdev, client, &ad5820_ops); + coil->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + coil->subdev.internal_ops = &ad5820_internal_ops; + coil->subdev.entity.function = MEDIA_ENT_F_LENS; + strscpy(coil->subdev.name, "ad5820 focus", sizeof(coil->subdev.name)); + + ret = media_entity_pads_init(&coil->subdev.entity, 0, NULL); + if (ret < 0) + goto clean_mutex; + + ret = v4l2_async_register_subdev(&coil->subdev); + if (ret < 0) + goto clean_entity; + + return ret; + +clean_entity: + media_entity_cleanup(&coil->subdev.entity); +clean_mutex: + mutex_destroy(&coil->power_lock); + return ret; +} + +static void ad5820_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct ad5820_device *coil = to_ad5820_device(subdev); + + v4l2_async_unregister_subdev(&coil->subdev); + v4l2_ctrl_handler_free(&coil->ctrls); + media_entity_cleanup(&coil->subdev.entity); + mutex_destroy(&coil->power_lock); +} + +static const struct i2c_device_id ad5820_id_table[] = { + { .name = "ad5820" }, + { .name = "ad5821" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad5820_id_table); + +static const struct of_device_id ad5820_of_table[] = { + { .compatible = "adi,ad5820" }, + { .compatible = "adi,ad5821" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5820_of_table); + +static SIMPLE_DEV_PM_OPS(ad5820_pm, ad5820_suspend, ad5820_resume); + +static struct i2c_driver ad5820_i2c_driver = { + .driver = { + .name = "ad5820", + .pm = &ad5820_pm, + .of_match_table = ad5820_of_table, + }, + .probe = ad5820_probe, + .remove = ad5820_remove, + .id_table = ad5820_id_table, +}; + +module_i2c_driver(ad5820_i2c_driver); + +MODULE_AUTHOR("Tuukka Toivonen"); +MODULE_DESCRIPTION("AD5820 camera lens driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/adp1653.c b/drivers/media/i2c/adp1653.c new file mode 100644 index 000000000..177a6e8d7 --- /dev/null +++ b/drivers/media/i2c/adp1653.c @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/adp1653.c + * + * Copyright (C) 2008--2011 Nokia Corporation + * + * Contact: Sakari Ailus + * + * Contributors: + * Sakari Ailus + * Tuukka Toivonen + * Pavel Machek + * + * TODO: + * - fault interrupt handling + * - hardware strobe + * - power doesn't need to be ON if all lights are off + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TIMEOUT_MAX 820000 +#define TIMEOUT_STEP 54600 +#define TIMEOUT_MIN (TIMEOUT_MAX - ADP1653_REG_CONFIG_TMR_SET_MAX \ + * TIMEOUT_STEP) +#define TIMEOUT_US_TO_CODE(t) ((TIMEOUT_MAX + (TIMEOUT_STEP / 2) - (t)) \ + / TIMEOUT_STEP) +#define TIMEOUT_CODE_TO_US(c) (TIMEOUT_MAX - (c) * TIMEOUT_STEP) + +/* Write values into ADP1653 registers. */ +static int adp1653_update_hw(struct adp1653_flash *flash) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + u8 out_sel; + u8 config = 0; + int rval; + + out_sel = ADP1653_INDICATOR_INTENSITY_uA_TO_REG( + flash->indicator_intensity->val) + << ADP1653_REG_OUT_SEL_ILED_SHIFT; + + switch (flash->led_mode->val) { + case V4L2_FLASH_LED_MODE_NONE: + break; + case V4L2_FLASH_LED_MODE_FLASH: + /* Flash mode, light on with strobe, duration from timer */ + config = ADP1653_REG_CONFIG_TMR_CFG; + config |= TIMEOUT_US_TO_CODE(flash->flash_timeout->val) + << ADP1653_REG_CONFIG_TMR_SET_SHIFT; + break; + case V4L2_FLASH_LED_MODE_TORCH: + /* Torch mode, light immediately on, duration indefinite */ + out_sel |= ADP1653_FLASH_INTENSITY_mA_TO_REG( + flash->torch_intensity->val) + << ADP1653_REG_OUT_SEL_HPLED_SHIFT; + break; + } + + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, out_sel); + if (rval < 0) + return rval; + + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_CONFIG, config); + if (rval < 0) + return rval; + + return 0; +} + +static int adp1653_get_fault(struct adp1653_flash *flash) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + int fault; + int rval; + + fault = i2c_smbus_read_byte_data(client, ADP1653_REG_FAULT); + if (fault < 0) + return fault; + + flash->fault |= fault; + + if (!flash->fault) + return 0; + + /* Clear faults. */ + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, 0); + if (rval < 0) + return rval; + + flash->led_mode->val = V4L2_FLASH_LED_MODE_NONE; + + rval = adp1653_update_hw(flash); + if (rval) + return rval; + + return flash->fault; +} + +static int adp1653_strobe(struct adp1653_flash *flash, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + u8 out_sel = ADP1653_INDICATOR_INTENSITY_uA_TO_REG( + flash->indicator_intensity->val) + << ADP1653_REG_OUT_SEL_ILED_SHIFT; + int rval; + + if (flash->led_mode->val != V4L2_FLASH_LED_MODE_FLASH) + return -EBUSY; + + if (!enable) + return i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, + out_sel); + + out_sel |= ADP1653_FLASH_INTENSITY_mA_TO_REG( + flash->flash_intensity->val) + << ADP1653_REG_OUT_SEL_HPLED_SHIFT; + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, out_sel); + if (rval) + return rval; + + /* Software strobe using i2c */ + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_SW_STROBE, + ADP1653_REG_SW_STROBE_SW_STROBE); + if (rval) + return rval; + return i2c_smbus_write_byte_data(client, ADP1653_REG_SW_STROBE, 0); +} + +/* -------------------------------------------------------------------------- + * V4L2 controls + */ + +static int adp1653_get_ctrl(struct v4l2_ctrl *ctrl) +{ + struct adp1653_flash *flash = + container_of(ctrl->handler, struct adp1653_flash, ctrls); + int rval; + + rval = adp1653_get_fault(flash); + if (rval) + return rval; + + ctrl->cur.val = 0; + + if (flash->fault & ADP1653_REG_FAULT_FLT_SCP) + ctrl->cur.val |= V4L2_FLASH_FAULT_SHORT_CIRCUIT; + if (flash->fault & ADP1653_REG_FAULT_FLT_OT) + ctrl->cur.val |= V4L2_FLASH_FAULT_OVER_TEMPERATURE; + if (flash->fault & ADP1653_REG_FAULT_FLT_TMR) + ctrl->cur.val |= V4L2_FLASH_FAULT_TIMEOUT; + if (flash->fault & ADP1653_REG_FAULT_FLT_OV) + ctrl->cur.val |= V4L2_FLASH_FAULT_OVER_VOLTAGE; + + flash->fault = 0; + + return 0; +} + +static int adp1653_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct adp1653_flash *flash = + container_of(ctrl->handler, struct adp1653_flash, ctrls); + int rval; + + rval = adp1653_get_fault(flash); + if (rval) + return rval; + if ((rval & (ADP1653_REG_FAULT_FLT_SCP | + ADP1653_REG_FAULT_FLT_OT | + ADP1653_REG_FAULT_FLT_OV)) && + (ctrl->id == V4L2_CID_FLASH_STROBE || + ctrl->id == V4L2_CID_FLASH_TORCH_INTENSITY || + ctrl->id == V4L2_CID_FLASH_LED_MODE)) + return -EBUSY; + + switch (ctrl->id) { + case V4L2_CID_FLASH_STROBE: + return adp1653_strobe(flash, 1); + case V4L2_CID_FLASH_STROBE_STOP: + return adp1653_strobe(flash, 0); + } + + return adp1653_update_hw(flash); +} + +static const struct v4l2_ctrl_ops adp1653_ctrl_ops = { + .g_volatile_ctrl = adp1653_get_ctrl, + .s_ctrl = adp1653_set_ctrl, +}; + +static int adp1653_init_controls(struct adp1653_flash *flash) +{ + struct v4l2_ctrl *fault; + + v4l2_ctrl_handler_init(&flash->ctrls, 9); + + flash->led_mode = + v4l2_ctrl_new_std_menu(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_LED_MODE, + V4L2_FLASH_LED_MODE_TORCH, ~0x7, 0); + v4l2_ctrl_new_std_menu(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_STROBE_SOURCE, + V4L2_FLASH_STROBE_SOURCE_SOFTWARE, ~0x1, 0); + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_STROBE, 0, 0, 0, 0); + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_STROBE_STOP, 0, 0, 0, 0); + flash->flash_timeout = + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_TIMEOUT, TIMEOUT_MIN, + flash->platform_data->max_flash_timeout, + TIMEOUT_STEP, + flash->platform_data->max_flash_timeout); + flash->flash_intensity = + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_INTENSITY, + ADP1653_FLASH_INTENSITY_MIN, + flash->platform_data->max_flash_intensity, + 1, flash->platform_data->max_flash_intensity); + flash->torch_intensity = + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_TORCH_INTENSITY, + ADP1653_TORCH_INTENSITY_MIN, + flash->platform_data->max_torch_intensity, + ADP1653_FLASH_INTENSITY_STEP, + flash->platform_data->max_torch_intensity); + flash->indicator_intensity = + v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_INDICATOR_INTENSITY, + ADP1653_INDICATOR_INTENSITY_MIN, + flash->platform_data->max_indicator_intensity, + ADP1653_INDICATOR_INTENSITY_STEP, + ADP1653_INDICATOR_INTENSITY_MIN); + fault = v4l2_ctrl_new_std(&flash->ctrls, &adp1653_ctrl_ops, + V4L2_CID_FLASH_FAULT, 0, + V4L2_FLASH_FAULT_OVER_VOLTAGE + | V4L2_FLASH_FAULT_OVER_TEMPERATURE + | V4L2_FLASH_FAULT_SHORT_CIRCUIT, 0, 0); + + if (flash->ctrls.error) + return flash->ctrls.error; + + fault->flags |= V4L2_CTRL_FLAG_VOLATILE; + + flash->subdev.ctrl_handler = &flash->ctrls; + return 0; +} + +/* -------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static int +adp1653_init_device(struct adp1653_flash *flash) +{ + struct i2c_client *client = v4l2_get_subdevdata(&flash->subdev); + int rval; + + /* Clear FAULT register by writing zero to OUT_SEL */ + rval = i2c_smbus_write_byte_data(client, ADP1653_REG_OUT_SEL, 0); + if (rval < 0) { + dev_err(&client->dev, "failed writing fault register\n"); + return -EIO; + } + + mutex_lock(flash->ctrls.lock); + /* Reset faults before reading new ones. */ + flash->fault = 0; + rval = adp1653_get_fault(flash); + mutex_unlock(flash->ctrls.lock); + if (rval > 0) { + dev_err(&client->dev, "faults detected: 0x%1.1x\n", rval); + return -EIO; + } + + mutex_lock(flash->ctrls.lock); + rval = adp1653_update_hw(flash); + mutex_unlock(flash->ctrls.lock); + if (rval) { + dev_err(&client->dev, + "adp1653_update_hw failed at %s\n", __func__); + return -EIO; + } + + return 0; +} + +static int +__adp1653_set_power(struct adp1653_flash *flash, int on) +{ + int ret; + + if (flash->platform_data->power) { + ret = flash->platform_data->power(&flash->subdev, on); + if (ret < 0) + return ret; + } else { + gpiod_set_value(flash->platform_data->enable_gpio, on); + if (on) + /* Some delay is apparently required. */ + udelay(20); + } + + if (!on) + return 0; + + ret = adp1653_init_device(flash); + if (ret >= 0) + return ret; + + if (flash->platform_data->power) + flash->platform_data->power(&flash->subdev, 0); + else + gpiod_set_value(flash->platform_data->enable_gpio, 0); + + return ret; +} + +static int +adp1653_set_power(struct v4l2_subdev *subdev, int on) +{ + struct adp1653_flash *flash = to_adp1653_flash(subdev); + int ret = 0; + + mutex_lock(&flash->power_lock); + + /* If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (flash->power_count == !on) { + ret = __adp1653_set_power(flash, !!on); + if (ret < 0) + goto done; + } + + /* Update the power count. */ + flash->power_count += on ? 1 : -1; + WARN_ON(flash->power_count < 0); + +done: + mutex_unlock(&flash->power_lock); + return ret; +} + +static int adp1653_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return adp1653_set_power(sd, 1); +} + +static int adp1653_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return adp1653_set_power(sd, 0); +} + +static const struct v4l2_subdev_core_ops adp1653_core_ops = { + .s_power = adp1653_set_power, +}; + +static const struct v4l2_subdev_ops adp1653_ops = { + .core = &adp1653_core_ops, +}; + +static const struct v4l2_subdev_internal_ops adp1653_internal_ops = { + .open = adp1653_open, + .close = adp1653_close, +}; + +/* -------------------------------------------------------------------------- + * I2C driver + */ +#ifdef CONFIG_PM + +static int adp1653_suspend(struct device *dev) +{ + struct v4l2_subdev *subdev = dev_get_drvdata(dev); + struct adp1653_flash *flash = to_adp1653_flash(subdev); + + if (!flash->power_count) + return 0; + + return __adp1653_set_power(flash, 0); +} + +static int adp1653_resume(struct device *dev) +{ + struct v4l2_subdev *subdev = dev_get_drvdata(dev); + struct adp1653_flash *flash = to_adp1653_flash(subdev); + + if (!flash->power_count) + return 0; + + return __adp1653_set_power(flash, 1); +} + +#else + +#define adp1653_suspend NULL +#define adp1653_resume NULL + +#endif /* CONFIG_PM */ + +static int adp1653_of_init(struct i2c_client *client, + struct adp1653_flash *flash, + struct device_node *node) +{ + struct adp1653_platform_data *pd; + struct device_node *node_indicator = NULL; + struct device_node *node_flash; + + pd = devm_kzalloc(&client->dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + flash->platform_data = pd; + + node_flash = of_get_child_by_name(node, "flash"); + if (!node_flash) + return -EINVAL; + + if (of_property_read_u32(node_flash, "flash-timeout-us", + &pd->max_flash_timeout)) + goto err; + + if (of_property_read_u32(node_flash, "flash-max-microamp", + &pd->max_flash_intensity)) + goto err; + + pd->max_flash_intensity /= 1000; + + if (of_property_read_u32(node_flash, "led-max-microamp", + &pd->max_torch_intensity)) + goto err; + + pd->max_torch_intensity /= 1000; + + node_indicator = of_get_child_by_name(node, "indicator"); + if (!node_indicator) + goto err; + + if (of_property_read_u32(node_indicator, "led-max-microamp", + &pd->max_indicator_intensity)) + goto err; + + of_node_put(node_flash); + of_node_put(node_indicator); + + pd->enable_gpio = devm_gpiod_get(&client->dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(pd->enable_gpio)) { + dev_err(&client->dev, "Error getting GPIO\n"); + return PTR_ERR(pd->enable_gpio); + } + + return 0; +err: + dev_err(&client->dev, "Required property not found\n"); + of_node_put(node_flash); + of_node_put(node_indicator); + return -EINVAL; +} + + +static int adp1653_probe(struct i2c_client *client) +{ + struct adp1653_flash *flash; + int ret; + + flash = devm_kzalloc(&client->dev, sizeof(*flash), GFP_KERNEL); + if (flash == NULL) + return -ENOMEM; + + if (client->dev.of_node) { + ret = adp1653_of_init(client, flash, client->dev.of_node); + if (ret) + return ret; + } else { + if (!client->dev.platform_data) { + dev_err(&client->dev, + "Neither DT not platform data provided\n"); + return -EINVAL; + } + flash->platform_data = client->dev.platform_data; + } + + mutex_init(&flash->power_lock); + + v4l2_i2c_subdev_init(&flash->subdev, client, &adp1653_ops); + flash->subdev.internal_ops = &adp1653_internal_ops; + flash->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + ret = adp1653_init_controls(flash); + if (ret) + goto free_and_quit; + + ret = media_entity_pads_init(&flash->subdev.entity, 0, NULL); + if (ret < 0) + goto free_and_quit; + + flash->subdev.entity.function = MEDIA_ENT_F_FLASH; + + return 0; + +free_and_quit: + dev_err(&client->dev, "adp1653: failed to register device\n"); + v4l2_ctrl_handler_free(&flash->ctrls); + return ret; +} + +static void adp1653_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct adp1653_flash *flash = to_adp1653_flash(subdev); + + v4l2_device_unregister_subdev(&flash->subdev); + v4l2_ctrl_handler_free(&flash->ctrls); + media_entity_cleanup(&flash->subdev.entity); +} + +static const struct i2c_device_id adp1653_id_table[] = { + { .name = ADP1653_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adp1653_id_table); + +static const struct dev_pm_ops adp1653_pm_ops = { + .suspend = adp1653_suspend, + .resume = adp1653_resume, +}; + +static struct i2c_driver adp1653_i2c_driver = { + .driver = { + .name = ADP1653_NAME, + .pm = &adp1653_pm_ops, + }, + .probe = adp1653_probe, + .remove = adp1653_remove, + .id_table = adp1653_id_table, +}; + +module_i2c_driver(adp1653_i2c_driver); + +MODULE_AUTHOR("Sakari Ailus "); +MODULE_DESCRIPTION("Analog Devices ADP1653 LED flash driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/adv7170.c b/drivers/media/i2c/adv7170.c new file mode 100644 index 000000000..812998729 --- /dev/null +++ b/drivers/media/i2c/adv7170.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * adv7170 - adv7170, adv7171 video encoder driver version 0.0.1 + * + * Copyright (C) 2002 Maxim Yevtyushkin + * + * Based on adv7176 driver by: + * + * Copyright (C) 1998 Dave Perks + * Copyright (C) 1999 Wolfgang Scherr + * Copyright (C) 2000 Serguei Miridonov + * - some corrections for Pinnacle Systems Inc. DC10plus card. + * + * Changes by Ronald Bultje + * - moved over to linux>=2.4.x i2c protocol (1/1/2003) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("Analog Devices ADV7170 video encoder driver"); +MODULE_AUTHOR("Maxim Yevtyushkin"); +MODULE_LICENSE("GPL"); + + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +/* ----------------------------------------------------------------------- */ + +struct adv7170 { + struct v4l2_subdev sd; + unsigned char reg[128]; + + v4l2_std_id norm; + int input; +}; + +static inline struct adv7170 *to_adv7170(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7170, sd); +} + +static char *inputs[] = { "pass_through", "play_back" }; + +static u32 adv7170_codes[] = { + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_UYVY8_1X16, +}; + +/* ----------------------------------------------------------------------- */ + +static inline int adv7170_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv7170 *encoder = to_adv7170(sd); + + encoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int adv7170_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int adv7170_write_block(struct v4l2_subdev *sd, + const u8 *data, unsigned int len) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv7170 *encoder = to_adv7170(sd); + int ret = -1; + u8 reg; + + /* the adv7170 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + u8 block_data[32]; + int block_len; + + while (len >= 2) { + block_len = 0; + block_data[block_len++] = reg = data[0]; + do { + block_data[block_len++] = + encoder->reg[reg++] = data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && block_len < 32); + ret = i2c_master_send(client, block_data, block_len); + if (ret < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + ret = adv7170_write(sd, reg, *data++); + if (ret < 0) + break; + len -= 2; + } + } + return ret; +} + +/* ----------------------------------------------------------------------- */ + +#define TR0MODE 0x4c +#define TR0RST 0x80 + +#define TR1CAPT 0x00 +#define TR1PLAY 0x00 + +static const unsigned char init_NTSC[] = { + 0x00, 0x10, /* MR0 */ + 0x01, 0x20, /* MR1 */ + 0x02, 0x0e, /* MR2 RTC control: bits 2 and 1 */ + 0x03, 0x80, /* MR3 */ + 0x04, 0x30, /* MR4 */ + 0x05, 0x00, /* Reserved */ + 0x06, 0x00, /* Reserved */ + 0x07, TR0MODE, /* TM0 */ + 0x08, TR1CAPT, /* TM1 */ + 0x09, 0x16, /* Fsc0 */ + 0x0a, 0x7c, /* Fsc1 */ + 0x0b, 0xf0, /* Fsc2 */ + 0x0c, 0x21, /* Fsc3 */ + 0x0d, 0x00, /* Subcarrier Phase */ + 0x0e, 0x00, /* Closed Capt. Ext 0 */ + 0x0f, 0x00, /* Closed Capt. Ext 1 */ + 0x10, 0x00, /* Closed Capt. 0 */ + 0x11, 0x00, /* Closed Capt. 1 */ + 0x12, 0x00, /* Pedestal Ctl 0 */ + 0x13, 0x00, /* Pedestal Ctl 1 */ + 0x14, 0x00, /* Pedestal Ctl 2 */ + 0x15, 0x00, /* Pedestal Ctl 3 */ + 0x16, 0x00, /* CGMS_WSS_0 */ + 0x17, 0x00, /* CGMS_WSS_1 */ + 0x18, 0x00, /* CGMS_WSS_2 */ + 0x19, 0x00, /* Teletext Ctl */ +}; + +static const unsigned char init_PAL[] = { + 0x00, 0x71, /* MR0 */ + 0x01, 0x20, /* MR1 */ + 0x02, 0x0e, /* MR2 RTC control: bits 2 and 1 */ + 0x03, 0x80, /* MR3 */ + 0x04, 0x30, /* MR4 */ + 0x05, 0x00, /* Reserved */ + 0x06, 0x00, /* Reserved */ + 0x07, TR0MODE, /* TM0 */ + 0x08, TR1CAPT, /* TM1 */ + 0x09, 0xcb, /* Fsc0 */ + 0x0a, 0x8a, /* Fsc1 */ + 0x0b, 0x09, /* Fsc2 */ + 0x0c, 0x2a, /* Fsc3 */ + 0x0d, 0x00, /* Subcarrier Phase */ + 0x0e, 0x00, /* Closed Capt. Ext 0 */ + 0x0f, 0x00, /* Closed Capt. Ext 1 */ + 0x10, 0x00, /* Closed Capt. 0 */ + 0x11, 0x00, /* Closed Capt. 1 */ + 0x12, 0x00, /* Pedestal Ctl 0 */ + 0x13, 0x00, /* Pedestal Ctl 1 */ + 0x14, 0x00, /* Pedestal Ctl 2 */ + 0x15, 0x00, /* Pedestal Ctl 3 */ + 0x16, 0x00, /* CGMS_WSS_0 */ + 0x17, 0x00, /* CGMS_WSS_1 */ + 0x18, 0x00, /* CGMS_WSS_2 */ + 0x19, 0x00, /* Teletext Ctl */ +}; + + +static int adv7170_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7170 *encoder = to_adv7170(sd); + + v4l2_dbg(1, debug, sd, "set norm %llx\n", (unsigned long long)std); + + if (std & V4L2_STD_NTSC) { + adv7170_write_block(sd, init_NTSC, sizeof(init_NTSC)); + if (encoder->input == 0) + adv7170_write(sd, 0x02, 0x0e); /* Enable genlock */ + adv7170_write(sd, 0x07, TR0MODE | TR0RST); + adv7170_write(sd, 0x07, TR0MODE); + } else if (std & V4L2_STD_PAL) { + adv7170_write_block(sd, init_PAL, sizeof(init_PAL)); + if (encoder->input == 0) + adv7170_write(sd, 0x02, 0x0e); /* Enable genlock */ + adv7170_write(sd, 0x07, TR0MODE | TR0RST); + adv7170_write(sd, 0x07, TR0MODE); + } else { + v4l2_dbg(1, debug, sd, "illegal norm: %llx\n", + (unsigned long long)std); + return -EINVAL; + } + v4l2_dbg(1, debug, sd, "switched to %llx\n", (unsigned long long)std); + encoder->norm = std; + return 0; +} + +static int adv7170_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7170 *encoder = to_adv7170(sd); + + /* RJ: input = 0: input is from decoder + input = 1: input is from ZR36060 + input = 2: color bar */ + + v4l2_dbg(1, debug, sd, "set input from %s\n", + input == 0 ? "decoder" : "ZR36060"); + + switch (input) { + case 0: + adv7170_write(sd, 0x01, 0x20); + adv7170_write(sd, 0x08, TR1CAPT); /* TR1 */ + adv7170_write(sd, 0x02, 0x0e); /* Enable genlock */ + adv7170_write(sd, 0x07, TR0MODE | TR0RST); + adv7170_write(sd, 0x07, TR0MODE); + /* udelay(10); */ + break; + + case 1: + adv7170_write(sd, 0x01, 0x00); + adv7170_write(sd, 0x08, TR1PLAY); /* TR1 */ + adv7170_write(sd, 0x02, 0x08); + adv7170_write(sd, 0x07, TR0MODE | TR0RST); + adv7170_write(sd, 0x07, TR0MODE); + /* udelay(10); */ + break; + + default: + v4l2_dbg(1, debug, sd, "illegal input: %d\n", input); + return -EINVAL; + } + v4l2_dbg(1, debug, sd, "switched to %s\n", inputs[input]); + encoder->input = input; + return 0; +} + +static int adv7170_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index >= ARRAY_SIZE(adv7170_codes)) + return -EINVAL; + + code->code = adv7170_codes[code->index]; + return 0; +} + +static int adv7170_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + u8 val = adv7170_read(sd, 0x7); + + if (format->pad) + return -EINVAL; + + if ((val & 0x40) == (1 << 6)) + mf->code = MEDIA_BUS_FMT_UYVY8_1X16; + else + mf->code = MEDIA_BUS_FMT_UYVY8_2X8; + + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + mf->width = 0; + mf->height = 0; + mf->field = V4L2_FIELD_ANY; + + return 0; +} + +static int adv7170_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + u8 val = adv7170_read(sd, 0x7); + + if (format->pad) + return -EINVAL; + + switch (mf->code) { + case MEDIA_BUS_FMT_UYVY8_2X8: + val &= ~0x40; + break; + + case MEDIA_BUS_FMT_UYVY8_1X16: + val |= 0x40; + break; + + default: + v4l2_dbg(1, debug, sd, + "illegal v4l2_mbus_framefmt code: %d\n", mf->code); + return -EINVAL; + } + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return adv7170_write(sd, 0x7, val); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_video_ops adv7170_video_ops = { + .s_std_output = adv7170_s_std_output, + .s_routing = adv7170_s_routing, +}; + +static const struct v4l2_subdev_pad_ops adv7170_pad_ops = { + .enum_mbus_code = adv7170_enum_mbus_code, + .get_fmt = adv7170_get_fmt, + .set_fmt = adv7170_set_fmt, +}; + +static const struct v4l2_subdev_ops adv7170_ops = { + .video = &adv7170_video_ops, + .pad = &adv7170_pad_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int adv7170_probe(struct i2c_client *client) +{ + struct adv7170 *encoder; + struct v4l2_subdev *sd; + int i; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + encoder = devm_kzalloc(&client->dev, sizeof(*encoder), GFP_KERNEL); + if (encoder == NULL) + return -ENOMEM; + sd = &encoder->sd; + v4l2_i2c_subdev_init(sd, client, &adv7170_ops); + encoder->norm = V4L2_STD_NTSC; + encoder->input = 0; + + i = adv7170_write_block(sd, init_NTSC, sizeof(init_NTSC)); + if (i >= 0) { + i = adv7170_write(sd, 0x07, TR0MODE | TR0RST); + i = adv7170_write(sd, 0x07, TR0MODE); + i = adv7170_read(sd, 0x12); + v4l2_dbg(1, debug, sd, "revision %d\n", i & 1); + } + if (i < 0) + v4l2_dbg(1, debug, sd, "init error 0x%x\n", i); + return 0; +} + +static void adv7170_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id adv7170_id[] = { + { .name = "adv7170" }, + { .name = "adv7171" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7170_id); + +static struct i2c_driver adv7170_driver = { + .driver = { + .name = "adv7170", + }, + .probe = adv7170_probe, + .remove = adv7170_remove, + .id_table = adv7170_id, +}; + +module_i2c_driver(adv7170_driver); diff --git a/drivers/media/i2c/adv7175.c b/drivers/media/i2c/adv7175.c new file mode 100644 index 000000000..f1caab8e2 --- /dev/null +++ b/drivers/media/i2c/adv7175.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * adv7175 - adv7175a video encoder driver version 0.0.3 + * + * Copyright (C) 1998 Dave Perks + * Copyright (C) 1999 Wolfgang Scherr + * Copyright (C) 2000 Serguei Miridonov + * - some corrections for Pinnacle Systems Inc. DC10plus card. + * + * Changes by Ronald Bultje + * - moved over to linux>=2.4.x i2c protocol (9/9/2002) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("Analog Devices ADV7175 video encoder driver"); +MODULE_AUTHOR("Dave Perks"); +MODULE_LICENSE("GPL"); + +#define I2C_ADV7175 0xd4 +#define I2C_ADV7176 0x54 + + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +/* ----------------------------------------------------------------------- */ + +struct adv7175 { + struct v4l2_subdev sd; + v4l2_std_id norm; + int input; +}; + +static inline struct adv7175 *to_adv7175(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7175, sd); +} + +static char *inputs[] = { "pass_through", "play_back", "color_bar" }; + +static u32 adv7175_codes[] = { + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_UYVY8_1X16, +}; + +/* ----------------------------------------------------------------------- */ + +static inline int adv7175_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int adv7175_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int adv7175_write_block(struct v4l2_subdev *sd, + const u8 *data, unsigned int len) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = -1; + u8 reg; + + /* the adv7175 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + u8 block_data[32]; + int block_len; + + while (len >= 2) { + block_len = 0; + block_data[block_len++] = reg = data[0]; + do { + block_data[block_len++] = data[1]; + reg++; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && block_len < 32); + ret = i2c_master_send(client, block_data, block_len); + if (ret < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + ret = adv7175_write(sd, reg, *data++); + if (ret < 0) + break; + len -= 2; + } + } + + return ret; +} + +static void set_subcarrier_freq(struct v4l2_subdev *sd, int pass_through) +{ + /* for some reason pass_through NTSC needs + * a different sub-carrier freq to remain stable. */ + if (pass_through) + adv7175_write(sd, 0x02, 0x00); + else + adv7175_write(sd, 0x02, 0x55); + + adv7175_write(sd, 0x03, 0x55); + adv7175_write(sd, 0x04, 0x55); + adv7175_write(sd, 0x05, 0x25); +} + +/* ----------------------------------------------------------------------- */ +/* Output filter: S-Video Composite */ + +#define MR050 0x11 /* 0x09 */ +#define MR060 0x14 /* 0x0c */ + +/* ----------------------------------------------------------------------- */ + +#define TR0MODE 0x46 +#define TR0RST 0x80 + +#define TR1CAPT 0x80 +#define TR1PLAY 0x00 + +static const unsigned char init_common[] = { + + 0x00, MR050, /* MR0, PAL enabled */ + 0x01, 0x00, /* MR1 */ + 0x02, 0x0c, /* subc. freq. */ + 0x03, 0x8c, /* subc. freq. */ + 0x04, 0x79, /* subc. freq. */ + 0x05, 0x26, /* subc. freq. */ + 0x06, 0x40, /* subc. phase */ + + 0x07, TR0MODE, /* TR0, 16bit */ + 0x08, 0x21, /* */ + 0x09, 0x00, /* */ + 0x0a, 0x00, /* */ + 0x0b, 0x00, /* */ + 0x0c, TR1CAPT, /* TR1 */ + 0x0d, 0x4f, /* MR2 */ + 0x0e, 0x00, /* */ + 0x0f, 0x00, /* */ + 0x10, 0x00, /* */ + 0x11, 0x00, /* */ +}; + +static const unsigned char init_pal[] = { + 0x00, MR050, /* MR0, PAL enabled */ + 0x01, 0x00, /* MR1 */ + 0x02, 0x0c, /* subc. freq. */ + 0x03, 0x8c, /* subc. freq. */ + 0x04, 0x79, /* subc. freq. */ + 0x05, 0x26, /* subc. freq. */ + 0x06, 0x40, /* subc. phase */ +}; + +static const unsigned char init_ntsc[] = { + 0x00, MR060, /* MR0, NTSC enabled */ + 0x01, 0x00, /* MR1 */ + 0x02, 0x55, /* subc. freq. */ + 0x03, 0x55, /* subc. freq. */ + 0x04, 0x55, /* subc. freq. */ + 0x05, 0x25, /* subc. freq. */ + 0x06, 0x1a, /* subc. phase */ +}; + +static int adv7175_init(struct v4l2_subdev *sd, u32 val) +{ + /* This is just for testing!!! */ + adv7175_write_block(sd, init_common, sizeof(init_common)); + adv7175_write(sd, 0x07, TR0MODE | TR0RST); + adv7175_write(sd, 0x07, TR0MODE); + return 0; +} + +static int adv7175_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7175 *encoder = to_adv7175(sd); + + if (std & V4L2_STD_NTSC) { + adv7175_write_block(sd, init_ntsc, sizeof(init_ntsc)); + if (encoder->input == 0) + adv7175_write(sd, 0x0d, 0x4f); /* Enable genlock */ + adv7175_write(sd, 0x07, TR0MODE | TR0RST); + adv7175_write(sd, 0x07, TR0MODE); + } else if (std & V4L2_STD_PAL) { + adv7175_write_block(sd, init_pal, sizeof(init_pal)); + if (encoder->input == 0) + adv7175_write(sd, 0x0d, 0x4f); /* Enable genlock */ + adv7175_write(sd, 0x07, TR0MODE | TR0RST); + adv7175_write(sd, 0x07, TR0MODE); + } else if (std & V4L2_STD_SECAM) { + /* This is an attempt to convert + * SECAM->PAL (typically it does not work + * due to genlock: when decoder is in SECAM + * and encoder in in PAL the subcarrier can + * not be synchronized with horizontal + * quency) */ + adv7175_write_block(sd, init_pal, sizeof(init_pal)); + if (encoder->input == 0) + adv7175_write(sd, 0x0d, 0x49); /* Disable genlock */ + adv7175_write(sd, 0x07, TR0MODE | TR0RST); + adv7175_write(sd, 0x07, TR0MODE); + } else { + v4l2_dbg(1, debug, sd, "illegal norm: %llx\n", + (unsigned long long)std); + return -EINVAL; + } + v4l2_dbg(1, debug, sd, "switched to %llx\n", (unsigned long long)std); + encoder->norm = std; + return 0; +} + +static int adv7175_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7175 *encoder = to_adv7175(sd); + + /* RJ: input = 0: input is from decoder + input = 1: input is from ZR36060 + input = 2: color bar */ + + switch (input) { + case 0: + adv7175_write(sd, 0x01, 0x00); + + if (encoder->norm & V4L2_STD_NTSC) + set_subcarrier_freq(sd, 1); + + adv7175_write(sd, 0x0c, TR1CAPT); /* TR1 */ + if (encoder->norm & V4L2_STD_SECAM) + adv7175_write(sd, 0x0d, 0x49); /* Disable genlock */ + else + adv7175_write(sd, 0x0d, 0x4f); /* Enable genlock */ + adv7175_write(sd, 0x07, TR0MODE | TR0RST); + adv7175_write(sd, 0x07, TR0MODE); + /*udelay(10);*/ + break; + + case 1: + adv7175_write(sd, 0x01, 0x00); + + if (encoder->norm & V4L2_STD_NTSC) + set_subcarrier_freq(sd, 0); + + adv7175_write(sd, 0x0c, TR1PLAY); /* TR1 */ + adv7175_write(sd, 0x0d, 0x49); + adv7175_write(sd, 0x07, TR0MODE | TR0RST); + adv7175_write(sd, 0x07, TR0MODE); + /* udelay(10); */ + break; + + case 2: + adv7175_write(sd, 0x01, 0x80); + + if (encoder->norm & V4L2_STD_NTSC) + set_subcarrier_freq(sd, 0); + + adv7175_write(sd, 0x0d, 0x49); + adv7175_write(sd, 0x07, TR0MODE | TR0RST); + adv7175_write(sd, 0x07, TR0MODE); + /* udelay(10); */ + break; + + default: + v4l2_dbg(1, debug, sd, "illegal input: %d\n", input); + return -EINVAL; + } + v4l2_dbg(1, debug, sd, "switched to %s\n", inputs[input]); + encoder->input = input; + return 0; +} + +static int adv7175_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index >= ARRAY_SIZE(adv7175_codes)) + return -EINVAL; + + code->code = adv7175_codes[code->index]; + return 0; +} + +static int adv7175_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + u8 val = adv7175_read(sd, 0x7); + + if (format->pad) + return -EINVAL; + + if ((val & 0x40) == (1 << 6)) + mf->code = MEDIA_BUS_FMT_UYVY8_1X16; + else + mf->code = MEDIA_BUS_FMT_UYVY8_2X8; + + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + mf->width = 0; + mf->height = 0; + mf->field = V4L2_FIELD_ANY; + + return 0; +} + +static int adv7175_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + u8 val = adv7175_read(sd, 0x7); + int ret = 0; + + if (format->pad) + return -EINVAL; + + switch (mf->code) { + case MEDIA_BUS_FMT_UYVY8_2X8: + val &= ~0x40; + break; + + case MEDIA_BUS_FMT_UYVY8_1X16: + val |= 0x40; + break; + + default: + v4l2_dbg(1, debug, sd, + "illegal v4l2_mbus_framefmt code: %d\n", mf->code); + return -EINVAL; + } + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + ret = adv7175_write(sd, 0x7, val); + + return ret; +} + +static int adv7175_s_power(struct v4l2_subdev *sd, int on) +{ + if (on) + adv7175_write(sd, 0x01, 0x00); + else + adv7175_write(sd, 0x01, 0x78); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops adv7175_core_ops = { + .init = adv7175_init, + .s_power = adv7175_s_power, +}; + +static const struct v4l2_subdev_video_ops adv7175_video_ops = { + .s_std_output = adv7175_s_std_output, + .s_routing = adv7175_s_routing, +}; + +static const struct v4l2_subdev_pad_ops adv7175_pad_ops = { + .enum_mbus_code = adv7175_enum_mbus_code, + .get_fmt = adv7175_get_fmt, + .set_fmt = adv7175_set_fmt, +}; + +static const struct v4l2_subdev_ops adv7175_ops = { + .core = &adv7175_core_ops, + .video = &adv7175_video_ops, + .pad = &adv7175_pad_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int adv7175_probe(struct i2c_client *client) +{ + int i; + struct adv7175 *encoder; + struct v4l2_subdev *sd; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + encoder = devm_kzalloc(&client->dev, sizeof(*encoder), GFP_KERNEL); + if (encoder == NULL) + return -ENOMEM; + sd = &encoder->sd; + v4l2_i2c_subdev_init(sd, client, &adv7175_ops); + encoder->norm = V4L2_STD_NTSC; + encoder->input = 0; + + i = adv7175_write_block(sd, init_common, sizeof(init_common)); + if (i >= 0) { + i = adv7175_write(sd, 0x07, TR0MODE | TR0RST); + i = adv7175_write(sd, 0x07, TR0MODE); + i = adv7175_read(sd, 0x12); + v4l2_dbg(1, debug, sd, "revision %d\n", i & 1); + } + if (i < 0) + v4l2_dbg(1, debug, sd, "init error 0x%x\n", i); + return 0; +} + +static void adv7175_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id adv7175_id[] = { + { .name = "adv7175" }, + { .name = "adv7176" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7175_id); + +static struct i2c_driver adv7175_driver = { + .driver = { + .name = "adv7175", + }, + .probe = adv7175_probe, + .remove = adv7175_remove, + .id_table = adv7175_id, +}; + +module_i2c_driver(adv7175_driver); diff --git a/drivers/media/i2c/adv7180.c b/drivers/media/i2c/adv7180.c new file mode 100644 index 000000000..a1c7f6822 --- /dev/null +++ b/drivers/media/i2c/adv7180.c @@ -0,0 +1,1674 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * adv7180.c Analog Devices ADV7180 video decoder driver + * Copyright (c) 2009 Intel Corporation + * Copyright (C) 2013 Cogent Embedded, Inc. + * Copyright (C) 2013 Renesas Solutions Corp. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADV7180_STD_AD_PAL_BG_NTSC_J_SECAM 0x0 +#define ADV7180_STD_AD_PAL_BG_NTSC_J_SECAM_PED 0x1 +#define ADV7180_STD_AD_PAL_N_NTSC_J_SECAM 0x2 +#define ADV7180_STD_AD_PAL_N_NTSC_M_SECAM 0x3 +#define ADV7180_STD_NTSC_J 0x4 +#define ADV7180_STD_NTSC_M 0x5 +#define ADV7180_STD_PAL60 0x6 +#define ADV7180_STD_NTSC_443 0x7 +#define ADV7180_STD_PAL_BG 0x8 +#define ADV7180_STD_PAL_N 0x9 +#define ADV7180_STD_PAL_M 0xa +#define ADV7180_STD_PAL_M_PED 0xb +#define ADV7180_STD_PAL_COMB_N 0xc +#define ADV7180_STD_PAL_COMB_N_PED 0xd +#define ADV7180_STD_PAL_SECAM 0xe +#define ADV7180_STD_PAL_SECAM_PED 0xf + +#define ADV7180_REG_INPUT_CONTROL 0x0000 +#define ADV7180_INPUT_CONTROL_INSEL_MASK 0x0f + +#define ADV7182_REG_INPUT_VIDSEL 0x0002 +#define ADV7182_REG_INPUT_RESERVED BIT(2) + +#define ADV7180_REG_OUTPUT_CONTROL 0x0003 +#define ADV7180_REG_EXTENDED_OUTPUT_CONTROL 0x0004 +#define ADV7180_EXTENDED_OUTPUT_CONTROL_NTSCDIS 0xC5 + +#define ADV7180_REG_AUTODETECT_ENABLE 0x0007 +#define ADV7180_AUTODETECT_DEFAULT 0x7f +/* Contrast */ +#define ADV7180_REG_CON 0x0008 /*Unsigned */ +#define ADV7180_CON_MIN 0 +#define ADV7180_CON_DEF 128 +#define ADV7180_CON_MAX 255 +/* Brightness*/ +#define ADV7180_REG_BRI 0x000a /*Signed */ +#define ADV7180_BRI_MIN -128 +#define ADV7180_BRI_DEF 0 +#define ADV7180_BRI_MAX 127 +/* Hue */ +#define ADV7180_REG_HUE 0x000b /*Signed, inverted */ +#define ADV7180_HUE_MIN -127 +#define ADV7180_HUE_DEF 0 +#define ADV7180_HUE_MAX 128 + +#define ADV7180_REG_DEF_VALUE_Y 0x000c +#define ADV7180_DEF_VAL_EN 0x1 +#define ADV7180_DEF_VAL_AUTO_EN 0x2 +#define ADV7180_REG_CTRL 0x000e +#define ADV7180_CTRL_IRQ_SPACE 0x20 + +#define ADV7180_REG_PWR_MAN 0x0f +#define ADV7180_PWR_MAN_ON 0x04 +#define ADV7180_PWR_MAN_OFF 0x24 +#define ADV7180_PWR_MAN_RES 0x80 + +#define ADV7180_REG_STATUS1 0x0010 +#define ADV7180_STATUS1_IN_LOCK 0x01 +#define ADV7180_STATUS1_AUTOD_MASK 0x70 +#define ADV7180_STATUS1_AUTOD_NTSM_M_J 0x00 +#define ADV7180_STATUS1_AUTOD_NTSC_4_43 0x10 +#define ADV7180_STATUS1_AUTOD_PAL_M 0x20 +#define ADV7180_STATUS1_AUTOD_PAL_60 0x30 +#define ADV7180_STATUS1_AUTOD_PAL_B_G 0x40 +#define ADV7180_STATUS1_AUTOD_SECAM 0x50 +#define ADV7180_STATUS1_AUTOD_PAL_COMB 0x60 +#define ADV7180_STATUS1_AUTOD_SECAM_525 0x70 + +#define ADV7180_REG_IDENT 0x0011 +#define ADV7180_ID_7180 0x18 + +#define ADV7180_REG_STATUS3 0x0013 +#define ADV7180_REG_ANALOG_CLAMP_CTL 0x0014 +#define ADV7180_REG_SHAP_FILTER_CTL_1 0x0017 +#define ADV7180_REG_CTRL_2 0x001d +#define ADV7180_REG_VSYNC_FIELD_CTL_1 0x0031 +#define ADV7180_VSYNC_FIELD_CTL_1_NEWAV 0x12 +#define ADV7180_REG_MANUAL_WIN_CTL_1 0x003d +#define ADV7180_REG_MANUAL_WIN_CTL_2 0x003e +#define ADV7180_REG_MANUAL_WIN_CTL_3 0x003f +#define ADV7180_REG_LOCK_CNT 0x0051 +#define ADV7180_REG_CVBS_TRIM 0x0052 +#define ADV7180_REG_CLAMP_ADJ 0x005a +#define ADV7180_REG_RES_CIR 0x005f +#define ADV7180_REG_DIFF_MODE 0x0060 + +#define ADV7180_REG_ICONF1 0x2040 +#define ADV7180_ICONF1_ACTIVE_LOW 0x01 +#define ADV7180_ICONF1_PSYNC_ONLY 0x10 +#define ADV7180_ICONF1_ACTIVE_TO_CLR 0xC0 +/* Saturation */ +#define ADV7180_REG_SD_SAT_CB 0x00e3 /*Unsigned */ +#define ADV7180_REG_SD_SAT_CR 0x00e4 /*Unsigned */ +#define ADV7180_SAT_MIN 0 +#define ADV7180_SAT_DEF 128 +#define ADV7180_SAT_MAX 255 + +#define ADV7180_IRQ1_LOCK 0x01 +#define ADV7180_IRQ1_UNLOCK 0x02 +#define ADV7180_REG_ISR1 0x2042 +#define ADV7180_REG_ICR1 0x2043 +#define ADV7180_REG_IMR1 0x2044 +#define ADV7180_REG_IMR2 0x2048 +#define ADV7180_IRQ3_AD_CHANGE 0x08 +#define ADV7180_REG_ISR3 0x204A +#define ADV7180_REG_ICR3 0x204B +#define ADV7180_REG_IMR3 0x204C +#define ADV7180_REG_IMR4 0x2050 + +#define ADV7180_REG_NTSC_V_BIT_END 0x00E6 +#define ADV7180_NTSC_V_BIT_END_MANUAL_NVEND 0x4F + +#define ADV7180_REG_VPP_SLAVE_ADDR 0xFD +#define ADV7180_REG_CSI_SLAVE_ADDR 0xFE + +#define ADV7180_REG_ACE_CTRL1 0x4080 +#define ADV7180_REG_ACE_CTRL5 0x4084 +#define ADV7180_REG_FLCONTROL 0x40e0 +#define ADV7180_FLCONTROL_FL_ENABLE 0x1 + +#define ADV7180_REG_RST_CLAMP 0x809c +#define ADV7180_REG_AGC_ADJ1 0x80b6 +#define ADV7180_REG_AGC_ADJ2 0x80c0 + +#define ADV7180_CSI_REG_PWRDN 0x00 +#define ADV7180_CSI_PWRDN 0x80 + +#define ADV7180_INPUT_CVBS_AIN1 0x00 +#define ADV7180_INPUT_CVBS_AIN2 0x01 +#define ADV7180_INPUT_CVBS_AIN3 0x02 +#define ADV7180_INPUT_CVBS_AIN4 0x03 +#define ADV7180_INPUT_CVBS_AIN5 0x04 +#define ADV7180_INPUT_CVBS_AIN6 0x05 +#define ADV7180_INPUT_SVIDEO_AIN1_AIN2 0x06 +#define ADV7180_INPUT_SVIDEO_AIN3_AIN4 0x07 +#define ADV7180_INPUT_SVIDEO_AIN5_AIN6 0x08 +#define ADV7180_INPUT_YPRPB_AIN1_AIN2_AIN3 0x09 +#define ADV7180_INPUT_YPRPB_AIN4_AIN5_AIN6 0x0a + +#define ADV7182_INPUT_CVBS_AIN1 0x00 +#define ADV7182_INPUT_CVBS_AIN2 0x01 +#define ADV7182_INPUT_CVBS_AIN3 0x02 +#define ADV7182_INPUT_CVBS_AIN4 0x03 +#define ADV7182_INPUT_CVBS_AIN5 0x04 +#define ADV7182_INPUT_CVBS_AIN6 0x05 +#define ADV7182_INPUT_CVBS_AIN7 0x06 +#define ADV7182_INPUT_CVBS_AIN8 0x07 +#define ADV7182_INPUT_SVIDEO_AIN1_AIN2 0x08 +#define ADV7182_INPUT_SVIDEO_AIN3_AIN4 0x09 +#define ADV7182_INPUT_SVIDEO_AIN5_AIN6 0x0a +#define ADV7182_INPUT_SVIDEO_AIN7_AIN8 0x0b +#define ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3 0x0c +#define ADV7182_INPUT_YPRPB_AIN4_AIN5_AIN6 0x0d +#define ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2 0x0e +#define ADV7182_INPUT_DIFF_CVBS_AIN3_AIN4 0x0f +#define ADV7182_INPUT_DIFF_CVBS_AIN5_AIN6 0x10 +#define ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8 0x11 + +#define ADV7180_DEFAULT_CSI_I2C_ADDR 0x44 +#define ADV7180_DEFAULT_VPP_I2C_ADDR 0x42 + +#define V4L2_CID_ADV_FAST_SWITCH (V4L2_CID_USER_ADV7180_BASE + 0x00) + +/* Initial number of frames to skip to avoid possible garbage */ +#define ADV7180_NUM_OF_SKIP_FRAMES 2 + +struct adv7180_state; + +#define ADV7180_FLAG_RESET_POWERED BIT(0) +#define ADV7180_FLAG_V2 BIT(1) +#define ADV7180_FLAG_MIPI_CSI2 BIT(2) +#define ADV7180_FLAG_I2P BIT(3) +#define ADV7180_FLAG_TEST_PATTERN BIT(4) + +struct adv7180_chip_info { + unsigned int flags; + unsigned int valid_input_mask; + int (*set_std)(struct adv7180_state *st, unsigned int std); + int (*select_input)(struct adv7180_state *st, unsigned int input); + int (*init)(struct adv7180_state *state); +}; + +struct adv7180_state { + struct v4l2_ctrl_handler ctrl_hdl; + struct v4l2_subdev sd; + struct media_pad pad; + struct mutex mutex; /* mutual excl. when accessing chip */ + int irq; + struct gpio_desc *pwdn_gpio; + struct gpio_desc *rst_gpio; + v4l2_std_id curr_norm; + bool streaming; + u8 input; + + struct i2c_client *client; + unsigned int register_page; + struct i2c_client *csi_client; + struct i2c_client *vpp_client; + const struct adv7180_chip_info *chip_info; + enum v4l2_field field; + bool force_bt656_4; +}; +#define to_adv7180_sd(_ctrl) (&container_of(_ctrl->handler, \ + struct adv7180_state, \ + ctrl_hdl)->sd) + +static int adv7180_select_page(struct adv7180_state *state, unsigned int page) +{ + if (state->register_page != page) { + i2c_smbus_write_byte_data(state->client, ADV7180_REG_CTRL, + page); + state->register_page = page; + } + + return 0; +} + +static int adv7180_write(struct adv7180_state *state, unsigned int reg, + unsigned int value) +{ + lockdep_assert_held(&state->mutex); + adv7180_select_page(state, reg >> 8); + return i2c_smbus_write_byte_data(state->client, reg & 0xff, value); +} + +static int adv7180_read(struct adv7180_state *state, unsigned int reg) +{ + lockdep_assert_held(&state->mutex); + adv7180_select_page(state, reg >> 8); + return i2c_smbus_read_byte_data(state->client, reg & 0xff); +} + +static int adv7180_csi_write(struct adv7180_state *state, unsigned int reg, + unsigned int value) +{ + return i2c_smbus_write_byte_data(state->csi_client, reg, value); +} + +static int adv7180_set_video_standard(struct adv7180_state *state, + unsigned int std) +{ + return state->chip_info->set_std(state, std); +} + +static int adv7180_vpp_write(struct adv7180_state *state, unsigned int reg, + unsigned int value) +{ + return i2c_smbus_write_byte_data(state->vpp_client, reg, value); +} + +static int adv7180_set_power(struct adv7180_state *state, bool on) +{ + u8 val; + int ret; + + if (on) + val = ADV7180_PWR_MAN_ON; + else + val = ADV7180_PWR_MAN_OFF; + + ret = adv7180_write(state, ADV7180_REG_PWR_MAN, val); + if (ret) + return ret; + + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) { + if (on) { + adv7180_csi_write(state, 0xDE, 0x02); + adv7180_csi_write(state, 0xD2, 0xF7); + adv7180_csi_write(state, 0xD8, 0x65); + adv7180_csi_write(state, 0xE0, 0x09); + adv7180_csi_write(state, 0x2C, 0x00); + if (state->field == V4L2_FIELD_NONE) + adv7180_csi_write(state, 0x1D, 0x80); + adv7180_csi_write(state, 0x00, 0x00); + } else { + adv7180_csi_write(state, 0x00, 0x80); + } + } + + return 0; +} + +static v4l2_std_id adv7180_std_to_v4l2(u8 status1) +{ + /* in case V4L2_IN_ST_NO_SIGNAL */ + if (!(status1 & ADV7180_STATUS1_IN_LOCK)) + return V4L2_STD_UNKNOWN; + + switch (status1 & ADV7180_STATUS1_AUTOD_MASK) { + case ADV7180_STATUS1_AUTOD_NTSM_M_J: + return V4L2_STD_NTSC; + case ADV7180_STATUS1_AUTOD_NTSC_4_43: + return V4L2_STD_NTSC_443; + case ADV7180_STATUS1_AUTOD_PAL_M: + return V4L2_STD_PAL_M; + case ADV7180_STATUS1_AUTOD_PAL_60: + return V4L2_STD_PAL_60; + case ADV7180_STATUS1_AUTOD_PAL_B_G: + return V4L2_STD_PAL; + case ADV7180_STATUS1_AUTOD_SECAM: + return V4L2_STD_SECAM; + case ADV7180_STATUS1_AUTOD_PAL_COMB: + return V4L2_STD_PAL_Nc | V4L2_STD_PAL_N; + case ADV7180_STATUS1_AUTOD_SECAM_525: + return V4L2_STD_SECAM; + default: + return V4L2_STD_UNKNOWN; + } +} + +static int v4l2_std_to_adv7180(v4l2_std_id std) +{ + if (std == V4L2_STD_PAL_60) + return ADV7180_STD_PAL60; + if (std == V4L2_STD_NTSC_443) + return ADV7180_STD_NTSC_443; + if (std == V4L2_STD_PAL_N) + return ADV7180_STD_PAL_N; + if (std == V4L2_STD_PAL_M) + return ADV7180_STD_PAL_M; + if (std == V4L2_STD_PAL_Nc) + return ADV7180_STD_PAL_COMB_N; + + if (std & V4L2_STD_PAL) + return ADV7180_STD_PAL_BG; + if (std & V4L2_STD_NTSC) + return ADV7180_STD_NTSC_M; + if (std & V4L2_STD_SECAM) + return ADV7180_STD_PAL_SECAM; + + return -EINVAL; +} + +static u32 adv7180_status_to_v4l2(u8 status1) +{ + if (!(status1 & ADV7180_STATUS1_IN_LOCK)) + return V4L2_IN_ST_NO_SIGNAL; + + return 0; +} + +static int __adv7180_status(struct adv7180_state *state, u32 *status, + v4l2_std_id *std) +{ + int status1 = adv7180_read(state, ADV7180_REG_STATUS1); + + if (status1 < 0) + return status1; + + if (status) + *status = adv7180_status_to_v4l2(status1); + if (std) + *std = adv7180_std_to_v4l2(status1); + + return 0; +} + +static inline struct adv7180_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7180_state, sd); +} + +static int adv7180_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct adv7180_state *state = to_state(sd); + int ret; + + guard(mutex)(&state->mutex); + + /* + * We can't sample the standard if the device is streaming as that would + * interfere with the capture session as the VID_SEL reg is touched. + */ + if (state->streaming) + return -EBUSY; + + /* Set the standard to autodetect PAL B/G/H/I/D, NTSC J or SECAM */ + ret = adv7180_set_video_standard(state, + ADV7180_STD_AD_PAL_BG_NTSC_J_SECAM); + if (ret) + return ret; + + /* Allow some time for the autodetection to run. */ + msleep(100); + + return __adv7180_status(state, NULL, std); +} + +static int adv7180_s_routing(struct v4l2_subdev *sd, u32 input, + u32 output, u32 config) +{ + struct adv7180_state *state = to_state(sd); + int ret = mutex_lock_interruptible(&state->mutex); + + if (ret) + return ret; + + if (input > 31 || !(BIT(input) & state->chip_info->valid_input_mask)) { + ret = -EINVAL; + goto out; + } + + ret = state->chip_info->select_input(state, input); + + if (ret == 0) + state->input = input; +out: + mutex_unlock(&state->mutex); + return ret; +} + +static int adv7180_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct adv7180_state *state = to_state(sd); + int ret = mutex_lock_interruptible(&state->mutex); + if (ret) + return ret; + + ret = __adv7180_status(state, status, NULL); + mutex_unlock(&state->mutex); + return ret; +} + +static int adv7180_program_std(struct adv7180_state *state) +{ + int ret; + + ret = v4l2_std_to_adv7180(state->curr_norm); + if (ret < 0) + return ret; + + ret = adv7180_set_video_standard(state, ret); + if (ret < 0) + return ret; + return 0; +} + +static int adv7180_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7180_state *state = to_state(sd); + int ret; + + guard(mutex)(&state->mutex); + + /* Make sure we can support this std */ + ret = v4l2_std_to_adv7180(std); + if (ret < 0) + return ret; + + state->curr_norm = std; + + return 0; +} + +static int adv7180_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm) +{ + struct adv7180_state *state = to_state(sd); + + *norm = state->curr_norm; + + return 0; +} + +static int adv7180_get_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval *fi) +{ + struct adv7180_state *state = to_state(sd); + + /* + * FIXME: Implement support for V4L2_SUBDEV_FORMAT_TRY, using the V4L2 + * subdev active state API. + */ + if (fi->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + if (state->curr_norm & V4L2_STD_525_60) { + fi->interval.numerator = 1001; + fi->interval.denominator = 30000; + } else { + fi->interval.numerator = 1; + fi->interval.denominator = 25; + } + + /* + * If the de-interlacer is active, the chip produces full video frames + * at the field rate. + */ + if (state->field == V4L2_FIELD_NONE) + fi->interval.denominator *= 2; + + return 0; +} + +static void adv7180_set_power_pin(struct adv7180_state *state, bool on) +{ + if (!state->pwdn_gpio) + return; + + if (on) { + gpiod_set_value_cansleep(state->pwdn_gpio, 0); + usleep_range(5000, 10000); + } else { + gpiod_set_value_cansleep(state->pwdn_gpio, 1); + } +} + +static void adv7180_set_reset_pin(struct adv7180_state *state, bool on) +{ + if (!state->rst_gpio) + return; + + if (on) { + gpiod_set_value_cansleep(state->rst_gpio, 1); + } else { + gpiod_set_value_cansleep(state->rst_gpio, 0); + usleep_range(5000, 10000); + } +} + +static const char * const test_pattern_menu[] = { + "Single color", + "Color bars", + "Luma ramp", + "Boundary box", + "Disable", +}; + +static int adv7180_test_pattern(struct adv7180_state *state, int value) +{ + unsigned int reg = 0; + + /* Map menu value into register value */ + if (value < 3) + reg = value; + if (value == 3) + reg = 5; + + adv7180_write(state, ADV7180_REG_ANALOG_CLAMP_CTL, reg); + + if (value == ARRAY_SIZE(test_pattern_menu) - 1) { + reg = adv7180_read(state, ADV7180_REG_DEF_VALUE_Y); + reg &= ~ADV7180_DEF_VAL_EN; + adv7180_write(state, ADV7180_REG_DEF_VALUE_Y, reg); + return 0; + } + + reg = adv7180_read(state, ADV7180_REG_DEF_VALUE_Y); + reg |= ADV7180_DEF_VAL_EN | ADV7180_DEF_VAL_AUTO_EN; + adv7180_write(state, ADV7180_REG_DEF_VALUE_Y, reg); + + return 0; +} + +static int adv7180_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_adv7180_sd(ctrl); + struct adv7180_state *state = to_state(sd); + int ret = 0; + int val; + + lockdep_assert_held(&state->mutex); + + val = ctrl->val; + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ret = adv7180_write(state, ADV7180_REG_BRI, val); + break; + case V4L2_CID_HUE: + /*Hue is inverted according to HSL chart */ + ret = adv7180_write(state, ADV7180_REG_HUE, -val); + break; + case V4L2_CID_CONTRAST: + ret = adv7180_write(state, ADV7180_REG_CON, val); + break; + case V4L2_CID_SATURATION: + /* + *This could be V4L2_CID_BLUE_BALANCE/V4L2_CID_RED_BALANCE + *Let's not confuse the user, everybody understands saturation + */ + ret = adv7180_write(state, ADV7180_REG_SD_SAT_CB, val); + if (ret < 0) + break; + ret = adv7180_write(state, ADV7180_REG_SD_SAT_CR, val); + break; + case V4L2_CID_ADV_FAST_SWITCH: + if (ctrl->val) { + /* ADI required write */ + adv7180_write(state, 0x80d9, 0x44); + adv7180_write(state, ADV7180_REG_FLCONTROL, + ADV7180_FLCONTROL_FL_ENABLE); + } else { + /* ADI required write */ + adv7180_write(state, 0x80d9, 0xc4); + adv7180_write(state, ADV7180_REG_FLCONTROL, 0x00); + } + break; + case V4L2_CID_TEST_PATTERN: + ret = adv7180_test_pattern(state, val); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct v4l2_ctrl_ops adv7180_ctrl_ops = { + .s_ctrl = adv7180_s_ctrl, +}; + +static const struct v4l2_ctrl_config adv7180_ctrl_fast_switch = { + .ops = &adv7180_ctrl_ops, + .id = V4L2_CID_ADV_FAST_SWITCH, + .name = "Fast Switching", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, +}; + +static int adv7180_init_controls(struct adv7180_state *state) +{ + v4l2_ctrl_handler_init(&state->ctrl_hdl, 4); + state->ctrl_hdl.lock = &state->mutex; + + v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7180_ctrl_ops, + V4L2_CID_BRIGHTNESS, ADV7180_BRI_MIN, + ADV7180_BRI_MAX, 1, ADV7180_BRI_DEF); + v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7180_ctrl_ops, + V4L2_CID_CONTRAST, ADV7180_CON_MIN, + ADV7180_CON_MAX, 1, ADV7180_CON_DEF); + v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7180_ctrl_ops, + V4L2_CID_SATURATION, ADV7180_SAT_MIN, + ADV7180_SAT_MAX, 1, ADV7180_SAT_DEF); + v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7180_ctrl_ops, + V4L2_CID_HUE, ADV7180_HUE_MIN, + ADV7180_HUE_MAX, 1, ADV7180_HUE_DEF); + v4l2_ctrl_new_custom(&state->ctrl_hdl, &adv7180_ctrl_fast_switch, NULL); + + if (state->chip_info->flags & ADV7180_FLAG_TEST_PATTERN) { + v4l2_ctrl_new_std_menu_items(&state->ctrl_hdl, + &adv7180_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(test_pattern_menu) - 1, + 0, + ARRAY_SIZE(test_pattern_menu) - 1, + test_pattern_menu); + } + + state->sd.ctrl_handler = &state->ctrl_hdl; + if (state->ctrl_hdl.error) { + int err = state->ctrl_hdl.error; + + v4l2_ctrl_handler_free(&state->ctrl_hdl); + return err; + } + + return 0; +} +static void adv7180_exit_controls(struct adv7180_state *state) +{ + v4l2_ctrl_handler_free(&state->ctrl_hdl); +} + +static int adv7180_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_UYVY8_2X8; + + return 0; +} + +static int adv7180_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt) +{ + struct adv7180_state *state = to_state(sd); + + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + fmt->width = 720; + fmt->height = state->curr_norm & V4L2_STD_525_60 ? 480 : 576; + + if (state->field == V4L2_FIELD_ALTERNATE) + fmt->height /= 2; + + return 0; +} + +static int adv7180_set_field_mode(struct adv7180_state *state) +{ + if (!(state->chip_info->flags & ADV7180_FLAG_I2P)) + return 0; + + if (state->field == V4L2_FIELD_NONE) { + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) { + adv7180_csi_write(state, 0x01, 0x20); + adv7180_csi_write(state, 0x02, 0x28); + adv7180_csi_write(state, 0x03, 0x38); + adv7180_csi_write(state, 0x04, 0x30); + adv7180_csi_write(state, 0x05, 0x30); + adv7180_csi_write(state, 0x06, 0x80); + adv7180_csi_write(state, 0x07, 0x70); + adv7180_csi_write(state, 0x08, 0x50); + } + adv7180_vpp_write(state, 0xa3, 0x00); + adv7180_vpp_write(state, 0x5b, 0x00); + adv7180_vpp_write(state, 0x55, 0x80); + } else { + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) { + adv7180_csi_write(state, 0x01, 0x18); + adv7180_csi_write(state, 0x02, 0x18); + adv7180_csi_write(state, 0x03, 0x30); + adv7180_csi_write(state, 0x04, 0x20); + adv7180_csi_write(state, 0x05, 0x28); + adv7180_csi_write(state, 0x06, 0x40); + adv7180_csi_write(state, 0x07, 0x58); + adv7180_csi_write(state, 0x08, 0x30); + } + adv7180_vpp_write(state, 0xa3, 0x70); + adv7180_vpp_write(state, 0x5b, 0x80); + adv7180_vpp_write(state, 0x55, 0x00); + } + + return 0; +} + +static int adv7180_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct adv7180_state *state = to_state(sd); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + format->format = *v4l2_subdev_state_get_format(sd_state, 0); + } else { + adv7180_mbus_fmt(sd, &format->format); + format->format.field = state->field; + } + + return 0; +} + +static int adv7180_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct adv7180_state *state = to_state(sd); + struct v4l2_mbus_framefmt *framefmt; + int ret; + + switch (format->format.field) { + case V4L2_FIELD_NONE: + if (state->chip_info->flags & ADV7180_FLAG_I2P) + break; + fallthrough; + default: + format->format.field = V4L2_FIELD_ALTERNATE; + break; + } + + ret = adv7180_mbus_fmt(sd, &format->format); + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + state->field = format->format.field; + } else { + framefmt = v4l2_subdev_state_get_format(sd_state, 0); + *framefmt = format->format; + } + + return ret; +} + +static int adv7180_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_subdev_format fmt = { + .which = sd_state ? V4L2_SUBDEV_FORMAT_TRY + : V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + return adv7180_set_pad_format(sd, sd_state, &fmt); +} + +static int adv7180_get_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct adv7180_state *state = to_state(sd); + + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) { + cfg->type = V4L2_MBUS_CSI2_DPHY; + cfg->bus.mipi_csi2.num_data_lanes = 1; + cfg->bus.mipi_csi2.flags = 0; + } else { + /* + * The ADV7180 sensor supports BT.601/656 output modes. + * The BT.656 is default and not yet configurable by s/w. + */ + cfg->bus.parallel.flags = V4L2_MBUS_MASTER | + V4L2_MBUS_PCLK_SAMPLE_RISING | + V4L2_MBUS_DATA_ACTIVE_HIGH; + cfg->type = V4L2_MBUS_BT656; + } + + return 0; +} + +static int adv7180_get_skip_frames(struct v4l2_subdev *sd, u32 *frames) +{ + *frames = ADV7180_NUM_OF_SKIP_FRAMES; + + return 0; +} + +static int adv7180_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *norm) +{ + *norm = V4L2_STD_ALL; + return 0; +} + +static int init_device(struct adv7180_state *state) +{ + int ret; + + lockdep_assert_held(&state->mutex); + + ret = adv7180_program_std(state); + if (ret) + return ret; + + adv7180_set_field_mode(state); + + __v4l2_ctrl_handler_setup(&state->ctrl_hdl); + + return ret; +} + +static int adv7180_reset_device(struct adv7180_state *state) +{ + int ret; + + lockdep_assert_held(&state->mutex); + + adv7180_set_power_pin(state, true); + adv7180_set_reset_pin(state, false); + + adv7180_write(state, ADV7180_REG_PWR_MAN, ADV7180_PWR_MAN_RES); + usleep_range(5000, 10000); + + /* + * If the devices decoder is power on after reset, power off so the + * device can be configured. + */ + if (state->chip_info->flags & ADV7180_FLAG_RESET_POWERED) + adv7180_set_power(state, false); + + ret = state->chip_info->init(state); + if (ret) + return ret; + + ret = init_device(state); + if (ret) + return ret; + + /* register for interrupts */ + if (state->irq > 0) { + /* config the Interrupt pin to be active low */ + ret = adv7180_write(state, ADV7180_REG_ICONF1, + ADV7180_ICONF1_ACTIVE_LOW | + ADV7180_ICONF1_PSYNC_ONLY); + if (ret < 0) + return ret; + + ret = adv7180_write(state, ADV7180_REG_IMR1, 0); + if (ret < 0) + return ret; + + ret = adv7180_write(state, ADV7180_REG_IMR2, 0); + if (ret < 0) + return ret; + + /* enable AD change interrupts */ + ret = adv7180_write(state, ADV7180_REG_IMR3, + ADV7180_IRQ3_AD_CHANGE); + if (ret < 0) + return ret; + + ret = adv7180_write(state, ADV7180_REG_IMR4, 0); + if (ret < 0) + return ret; + } + + /* + * If the devices decoder is power on after reset, restore the power + * after configuration. This is to preserve the behavior of the driver, + * not doing this result in the first 35+ frames captured being garbage. + */ + if (state->chip_info->flags & ADV7180_FLAG_RESET_POWERED) + adv7180_set_power(state, true); + + return 0; +} + +static int adv7180_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct adv7180_state *state = to_state(sd); + int ret; + + /* Must wait until querystd released the lock */ + guard(mutex)(&state->mutex); + + /* + * Always power off the decoder even if streaming is to be enabled, the + * decoder needs to be off for the device to be configured. + */ + ret = adv7180_set_power(state, false); + if (ret) + return ret; + + if (enable) { + ret = init_device(state); + if (ret) + return ret; + + ret = adv7180_set_power(state, true); + if (ret) + return ret; + } + + state->streaming = enable; + + return 0; +} + +static int adv7180_subscribe_event(struct v4l2_subdev *sd, + struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int adv7180_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct adv7180_state *state = to_state(sd); + int ret; + + ret = adv7180_read(state, reg->reg); + if (ret < 0) + return ret; + + reg->val = ret; + reg->size = 1; + + return 0; +} + +static int adv7180_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct adv7180_state *state = to_state(sd); + + return adv7180_write(state, reg->reg, reg->val); +} +#endif + +static const struct v4l2_subdev_video_ops adv7180_video_ops = { + .s_std = adv7180_s_std, + .g_std = adv7180_g_std, + .querystd = adv7180_querystd, + .g_input_status = adv7180_g_input_status, + .s_routing = adv7180_s_routing, + .g_tvnorms = adv7180_g_tvnorms, + .s_stream = adv7180_s_stream, +}; + +static const struct v4l2_subdev_core_ops adv7180_core_ops = { + .subscribe_event = adv7180_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = adv7180_g_register, + .s_register = adv7180_s_register, +#endif +}; + +static const struct v4l2_subdev_pad_ops adv7180_pad_ops = { + .enum_mbus_code = adv7180_enum_mbus_code, + .set_fmt = adv7180_set_pad_format, + .get_fmt = adv7180_get_pad_format, + .get_frame_interval = adv7180_get_frame_interval, + .get_mbus_config = adv7180_get_mbus_config, +}; + +static const struct v4l2_subdev_sensor_ops adv7180_sensor_ops = { + .g_skip_frames = adv7180_get_skip_frames, +}; + +static const struct v4l2_subdev_ops adv7180_ops = { + .core = &adv7180_core_ops, + .video = &adv7180_video_ops, + .pad = &adv7180_pad_ops, + .sensor = &adv7180_sensor_ops, +}; + +static const struct v4l2_subdev_internal_ops adv7180_internal_ops = { + .init_state = adv7180_init_state, +}; + +static irqreturn_t adv7180_irq(int irq, void *devid) +{ + struct adv7180_state *state = devid; + u8 isr3; + + mutex_lock(&state->mutex); + isr3 = adv7180_read(state, ADV7180_REG_ISR3); + /* clear */ + adv7180_write(state, ADV7180_REG_ICR3, isr3); + + if (isr3 & ADV7180_IRQ3_AD_CHANGE) { + static const struct v4l2_event src_ch = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + v4l2_subdev_notify_event(&state->sd, &src_ch); + } + mutex_unlock(&state->mutex); + + return IRQ_HANDLED; +} + +static int adv7180_init(struct adv7180_state *state) +{ + int ret; + + /* ITU-R BT.656-4 compatible */ + ret = adv7180_write(state, ADV7180_REG_EXTENDED_OUTPUT_CONTROL, + ADV7180_EXTENDED_OUTPUT_CONTROL_NTSCDIS); + if (ret < 0) + return ret; + + /* Manually set V bit end position in NTSC mode */ + return adv7180_write(state, ADV7180_REG_NTSC_V_BIT_END, + ADV7180_NTSC_V_BIT_END_MANUAL_NVEND); +} + +static int adv7180_set_std(struct adv7180_state *state, unsigned int std) +{ + return adv7180_write(state, ADV7180_REG_INPUT_CONTROL, + (std << 4) | state->input); +} + +static int adv7180_select_input(struct adv7180_state *state, unsigned int input) +{ + int ret; + + ret = adv7180_read(state, ADV7180_REG_INPUT_CONTROL); + if (ret < 0) + return ret; + + ret &= ~ADV7180_INPUT_CONTROL_INSEL_MASK; + ret |= input; + return adv7180_write(state, ADV7180_REG_INPUT_CONTROL, ret); +} + +static int adv7182_init(struct adv7180_state *state) +{ + if (state->csi_client) + adv7180_write(state, ADV7180_REG_CSI_SLAVE_ADDR, + state->csi_client->addr << 1); + + if (state->vpp_client) + adv7180_write(state, ADV7180_REG_VPP_SLAVE_ADDR, + state->vpp_client->addr << 1); + + if (state->chip_info->flags & ADV7180_FLAG_V2) { + /* ADI recommended writes for improved video quality */ + adv7180_write(state, 0x0080, 0x51); + adv7180_write(state, 0x0081, 0x51); + adv7180_write(state, 0x0082, 0x68); + } + + /* ADI required writes */ + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) { + adv7180_write(state, ADV7180_REG_OUTPUT_CONTROL, 0x4e); + adv7180_write(state, ADV7180_REG_EXTENDED_OUTPUT_CONTROL, 0x57); + adv7180_write(state, ADV7180_REG_CTRL_2, 0xc0); + } else { + if (state->chip_info->flags & ADV7180_FLAG_V2) { + if (state->force_bt656_4) { + /* ITU-R BT.656-4 compatible */ + adv7180_write(state, + ADV7180_REG_EXTENDED_OUTPUT_CONTROL, + ADV7180_EXTENDED_OUTPUT_CONTROL_NTSCDIS); + /* Manually set NEWAVMODE */ + adv7180_write(state, + ADV7180_REG_VSYNC_FIELD_CTL_1, + ADV7180_VSYNC_FIELD_CTL_1_NEWAV); + /* Manually set V bit end position in NTSC mode */ + adv7180_write(state, + ADV7180_REG_NTSC_V_BIT_END, + ADV7180_NTSC_V_BIT_END_MANUAL_NVEND); + } else { + adv7180_write(state, + ADV7180_REG_EXTENDED_OUTPUT_CONTROL, + 0x17); + } + } else { + adv7180_write(state, + ADV7180_REG_EXTENDED_OUTPUT_CONTROL, + 0x07); + } + adv7180_write(state, ADV7180_REG_OUTPUT_CONTROL, 0x0c); + adv7180_write(state, ADV7180_REG_CTRL_2, 0x40); + } + + adv7180_write(state, 0x0013, 0x00); + + return 0; +} + +static int adv7182_set_std(struct adv7180_state *state, unsigned int std) +{ + /* Failing to set the reserved bit can result in increased video noise */ + return adv7180_write(state, ADV7182_REG_INPUT_VIDSEL, + (std << 4) | ADV7182_REG_INPUT_RESERVED); +} + +enum adv7182_input_type { + ADV7182_INPUT_TYPE_CVBS, + ADV7182_INPUT_TYPE_DIFF_CVBS, + ADV7182_INPUT_TYPE_SVIDEO, + ADV7182_INPUT_TYPE_YPBPR, +}; + +static enum adv7182_input_type adv7182_get_input_type(unsigned int input) +{ + switch (input) { + case ADV7182_INPUT_CVBS_AIN1: + case ADV7182_INPUT_CVBS_AIN2: + case ADV7182_INPUT_CVBS_AIN3: + case ADV7182_INPUT_CVBS_AIN4: + case ADV7182_INPUT_CVBS_AIN5: + case ADV7182_INPUT_CVBS_AIN6: + case ADV7182_INPUT_CVBS_AIN7: + case ADV7182_INPUT_CVBS_AIN8: + return ADV7182_INPUT_TYPE_CVBS; + case ADV7182_INPUT_SVIDEO_AIN1_AIN2: + case ADV7182_INPUT_SVIDEO_AIN3_AIN4: + case ADV7182_INPUT_SVIDEO_AIN5_AIN6: + case ADV7182_INPUT_SVIDEO_AIN7_AIN8: + return ADV7182_INPUT_TYPE_SVIDEO; + case ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3: + case ADV7182_INPUT_YPRPB_AIN4_AIN5_AIN6: + return ADV7182_INPUT_TYPE_YPBPR; + case ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2: + case ADV7182_INPUT_DIFF_CVBS_AIN3_AIN4: + case ADV7182_INPUT_DIFF_CVBS_AIN5_AIN6: + case ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8: + return ADV7182_INPUT_TYPE_DIFF_CVBS; + default: /* Will never happen */ + return 0; + } +} + +/* ADI recommended writes to registers 0x52, 0x53, 0x54 */ +static unsigned int adv7182_lbias_settings[][3] = { + [ADV7182_INPUT_TYPE_CVBS] = { 0xCB, 0x4E, 0x80 }, + [ADV7182_INPUT_TYPE_DIFF_CVBS] = { 0xC0, 0x4E, 0x80 }, + [ADV7182_INPUT_TYPE_SVIDEO] = { 0x0B, 0xCE, 0x80 }, + [ADV7182_INPUT_TYPE_YPBPR] = { 0x0B, 0x4E, 0xC0 }, +}; + +static unsigned int adv7280_lbias_settings[][3] = { + [ADV7182_INPUT_TYPE_CVBS] = { 0xCD, 0x4E, 0x80 }, + [ADV7182_INPUT_TYPE_DIFF_CVBS] = { 0xC0, 0x4E, 0x80 }, + [ADV7182_INPUT_TYPE_SVIDEO] = { 0x0B, 0xCE, 0x80 }, + [ADV7182_INPUT_TYPE_YPBPR] = { 0x0B, 0x4E, 0xC0 }, +}; + +static int adv7182_select_input(struct adv7180_state *state, unsigned int input) +{ + enum adv7182_input_type input_type; + unsigned int *lbias; + unsigned int i; + int ret; + + ret = adv7180_write(state, ADV7180_REG_INPUT_CONTROL, input); + if (ret) + return ret; + + /* Reset clamp circuitry - ADI recommended writes */ + adv7180_write(state, ADV7180_REG_RST_CLAMP, 0x00); + adv7180_write(state, ADV7180_REG_RST_CLAMP, 0xff); + + input_type = adv7182_get_input_type(input); + + switch (input_type) { + case ADV7182_INPUT_TYPE_CVBS: + case ADV7182_INPUT_TYPE_DIFF_CVBS: + /* ADI recommends to use the SH1 filter */ + adv7180_write(state, ADV7180_REG_SHAP_FILTER_CTL_1, 0x41); + break; + default: + adv7180_write(state, ADV7180_REG_SHAP_FILTER_CTL_1, 0x01); + break; + } + + if (state->chip_info->flags & ADV7180_FLAG_V2) + lbias = adv7280_lbias_settings[input_type]; + else + lbias = adv7182_lbias_settings[input_type]; + + for (i = 0; i < ARRAY_SIZE(adv7182_lbias_settings[0]); i++) + adv7180_write(state, ADV7180_REG_CVBS_TRIM + i, lbias[i]); + + if (input_type == ADV7182_INPUT_TYPE_DIFF_CVBS) { + /* ADI required writes to make differential CVBS work */ + adv7180_write(state, ADV7180_REG_RES_CIR, 0xa8); + adv7180_write(state, ADV7180_REG_CLAMP_ADJ, 0x90); + adv7180_write(state, ADV7180_REG_DIFF_MODE, 0xb0); + adv7180_write(state, ADV7180_REG_AGC_ADJ1, 0x08); + adv7180_write(state, ADV7180_REG_AGC_ADJ2, 0xa0); + } else { + adv7180_write(state, ADV7180_REG_RES_CIR, 0xf0); + adv7180_write(state, ADV7180_REG_CLAMP_ADJ, 0xd0); + adv7180_write(state, ADV7180_REG_DIFF_MODE, 0x10); + adv7180_write(state, ADV7180_REG_AGC_ADJ1, 0x9c); + adv7180_write(state, ADV7180_REG_AGC_ADJ2, 0x00); + } + + return 0; +} + +static const struct adv7180_chip_info adv7180_info = { + .flags = ADV7180_FLAG_RESET_POWERED, + /* We cannot discriminate between LQFP and 40-pin LFCSP, so accept + * all inputs and let the card driver take care of validation + */ + .valid_input_mask = BIT(ADV7180_INPUT_CVBS_AIN1) | + BIT(ADV7180_INPUT_CVBS_AIN2) | + BIT(ADV7180_INPUT_CVBS_AIN3) | + BIT(ADV7180_INPUT_CVBS_AIN4) | + BIT(ADV7180_INPUT_CVBS_AIN5) | + BIT(ADV7180_INPUT_CVBS_AIN6) | + BIT(ADV7180_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7180_INPUT_SVIDEO_AIN3_AIN4) | + BIT(ADV7180_INPUT_SVIDEO_AIN5_AIN6) | + BIT(ADV7180_INPUT_YPRPB_AIN1_AIN2_AIN3) | + BIT(ADV7180_INPUT_YPRPB_AIN4_AIN5_AIN6), + .init = adv7180_init, + .set_std = adv7180_set_std, + .select_input = adv7180_select_input, +}; + +static const struct adv7180_chip_info adv7182_info = { + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN3) | + BIT(ADV7182_INPUT_CVBS_AIN4) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN3_AIN4) | + BIT(ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN3_AIN4), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static const struct adv7180_chip_info adv7280_info = { + .flags = ADV7180_FLAG_V2 | ADV7180_FLAG_I2P | ADV7180_FLAG_TEST_PATTERN, + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN3) | + BIT(ADV7182_INPUT_CVBS_AIN4) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN3_AIN4) | + BIT(ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static const struct adv7180_chip_info adv7280_m_info = { + .flags = ADV7180_FLAG_V2 | ADV7180_FLAG_MIPI_CSI2 | ADV7180_FLAG_I2P | + ADV7180_FLAG_TEST_PATTERN, + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN3) | + BIT(ADV7182_INPUT_CVBS_AIN4) | + BIT(ADV7182_INPUT_CVBS_AIN5) | + BIT(ADV7182_INPUT_CVBS_AIN6) | + BIT(ADV7182_INPUT_CVBS_AIN7) | + BIT(ADV7182_INPUT_CVBS_AIN8) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN3_AIN4) | + BIT(ADV7182_INPUT_SVIDEO_AIN5_AIN6) | + BIT(ADV7182_INPUT_SVIDEO_AIN7_AIN8) | + BIT(ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3) | + BIT(ADV7182_INPUT_YPRPB_AIN4_AIN5_AIN6), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static const struct adv7180_chip_info adv7281_info = { + .flags = ADV7180_FLAG_V2 | ADV7180_FLAG_MIPI_CSI2 | + ADV7180_FLAG_TEST_PATTERN, + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN7) | + BIT(ADV7182_INPUT_CVBS_AIN8) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN7_AIN8) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static const struct adv7180_chip_info adv7281_m_info = { + .flags = ADV7180_FLAG_V2 | ADV7180_FLAG_MIPI_CSI2 | + ADV7180_FLAG_TEST_PATTERN, + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN3) | + BIT(ADV7182_INPUT_CVBS_AIN4) | + BIT(ADV7182_INPUT_CVBS_AIN7) | + BIT(ADV7182_INPUT_CVBS_AIN8) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN3_AIN4) | + BIT(ADV7182_INPUT_SVIDEO_AIN7_AIN8) | + BIT(ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN3_AIN4) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static const struct adv7180_chip_info adv7281_ma_info = { + .flags = ADV7180_FLAG_V2 | ADV7180_FLAG_MIPI_CSI2 | + ADV7180_FLAG_TEST_PATTERN, + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN3) | + BIT(ADV7182_INPUT_CVBS_AIN4) | + BIT(ADV7182_INPUT_CVBS_AIN5) | + BIT(ADV7182_INPUT_CVBS_AIN6) | + BIT(ADV7182_INPUT_CVBS_AIN7) | + BIT(ADV7182_INPUT_CVBS_AIN8) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN3_AIN4) | + BIT(ADV7182_INPUT_SVIDEO_AIN5_AIN6) | + BIT(ADV7182_INPUT_SVIDEO_AIN7_AIN8) | + BIT(ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3) | + BIT(ADV7182_INPUT_YPRPB_AIN4_AIN5_AIN6) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN3_AIN4) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN5_AIN6) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static const struct adv7180_chip_info adv7282_info = { + .flags = ADV7180_FLAG_V2 | ADV7180_FLAG_I2P | ADV7180_FLAG_TEST_PATTERN, + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN7) | + BIT(ADV7182_INPUT_CVBS_AIN8) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN7_AIN8) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static const struct adv7180_chip_info adv7282_m_info = { + .flags = ADV7180_FLAG_V2 | ADV7180_FLAG_MIPI_CSI2 | ADV7180_FLAG_I2P | + ADV7180_FLAG_TEST_PATTERN, + .valid_input_mask = BIT(ADV7182_INPUT_CVBS_AIN1) | + BIT(ADV7182_INPUT_CVBS_AIN2) | + BIT(ADV7182_INPUT_CVBS_AIN3) | + BIT(ADV7182_INPUT_CVBS_AIN4) | + BIT(ADV7182_INPUT_CVBS_AIN7) | + BIT(ADV7182_INPUT_CVBS_AIN8) | + BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) | + BIT(ADV7182_INPUT_SVIDEO_AIN3_AIN4) | + BIT(ADV7182_INPUT_SVIDEO_AIN7_AIN8) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN3_AIN4) | + BIT(ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8), + .init = adv7182_init, + .set_std = adv7182_set_std, + .select_input = adv7182_select_input, +}; + +static int adv7180_probe(struct i2c_client *client) +{ + struct device_node *np = client->dev.of_node; + struct adv7180_state *state; + struct v4l2_subdev *sd; + int ret; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + + state->client = client; + state->field = V4L2_FIELD_ALTERNATE; + state->chip_info = i2c_get_match_data(client); + + state->pwdn_gpio = devm_gpiod_get_optional(&client->dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(state->pwdn_gpio)) { + ret = PTR_ERR(state->pwdn_gpio); + v4l_err(client, "request for power pin failed: %d\n", ret); + return ret; + } + + state->rst_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(state->rst_gpio)) { + ret = PTR_ERR(state->rst_gpio); + v4l_err(client, "request for reset pin failed: %d\n", ret); + return ret; + } + + if (of_property_read_bool(np, "adv,force-bt656-4") || + of_property_read_bool(np, "adi,force-bt656-4")) + state->force_bt656_4 = true; + + if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) { + state->csi_client = + i2c_new_ancillary_device(client, "csi", + ADV7180_DEFAULT_CSI_I2C_ADDR); + if (IS_ERR(state->csi_client)) + return PTR_ERR(state->csi_client); + } + + if (state->chip_info->flags & ADV7180_FLAG_I2P) { + state->vpp_client = + i2c_new_ancillary_device(client, "vpp", + ADV7180_DEFAULT_VPP_I2C_ADDR); + if (IS_ERR(state->vpp_client)) { + ret = PTR_ERR(state->vpp_client); + goto err_unregister_csi_client; + } + } + + state->irq = client->irq; + mutex_init(&state->mutex); + state->curr_norm = V4L2_STD_NTSC; + + state->input = 0; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &adv7180_ops); + sd->internal_ops = &adv7180_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + + ret = adv7180_init_controls(state); + if (ret) + goto err_unregister_vpp_client; + + state->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_ATV_DECODER; + ret = media_entity_pads_init(&sd->entity, 1, &state->pad); + if (ret) + goto err_free_ctrl; + + mutex_lock(&state->mutex); + ret = adv7180_reset_device(state); + mutex_unlock(&state->mutex); + if (ret) + goto err_media_entity_cleanup; + + if (state->irq > 0) { + ret = request_threaded_irq(client->irq, NULL, adv7180_irq, + IRQF_ONESHOT | IRQF_TRIGGER_FALLING, + KBUILD_MODNAME, state); + if (ret) + goto err_media_entity_cleanup; + } + + ret = v4l2_async_register_subdev(sd); + if (ret) + goto err_free_irq; + + mutex_lock(&state->mutex); + ret = adv7180_read(state, ADV7180_REG_IDENT); + mutex_unlock(&state->mutex); + if (ret < 0) + goto err_v4l2_async_unregister; + + v4l_info(client, "chip id 0x%x found @ 0x%02x (%s)\n", + ret, client->addr, client->adapter->name); + + return 0; + +err_v4l2_async_unregister: + v4l2_async_unregister_subdev(sd); +err_free_irq: + if (state->irq > 0) + free_irq(client->irq, state); +err_media_entity_cleanup: + media_entity_cleanup(&sd->entity); +err_free_ctrl: + adv7180_exit_controls(state); +err_unregister_vpp_client: + i2c_unregister_device(state->vpp_client); +err_unregister_csi_client: + i2c_unregister_device(state->csi_client); + mutex_destroy(&state->mutex); + return ret; +} + +static void adv7180_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7180_state *state = to_state(sd); + + v4l2_async_unregister_subdev(sd); + + if (state->irq > 0) + free_irq(client->irq, state); + + media_entity_cleanup(&sd->entity); + adv7180_exit_controls(state); + + i2c_unregister_device(state->vpp_client); + i2c_unregister_device(state->csi_client); + + adv7180_set_reset_pin(state, true); + adv7180_set_power_pin(state, false); + + mutex_destroy(&state->mutex); +} + +#ifdef CONFIG_PM_SLEEP +static int adv7180_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct adv7180_state *state = to_state(sd); + + guard(mutex)(&state->mutex); + + return adv7180_set_power(state, false); +} + +static int adv7180_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct adv7180_state *state = to_state(sd); + int ret; + + guard(mutex)(&state->mutex); + + ret = adv7180_reset_device(state); + if (ret < 0) + return ret; + + /* If we were streaming when suspending, start decoder. */ + if (state->streaming) { + ret = adv7180_set_power(state, true); + if (ret) + return ret; + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(adv7180_pm_ops, adv7180_suspend, adv7180_resume); +#define ADV7180_PM_OPS (&adv7180_pm_ops) + +#else +#define ADV7180_PM_OPS NULL +#endif + +static const struct i2c_device_id adv7180_id[] = { + { .name = "adv7180", .driver_data = (kernel_ulong_t)&adv7180_info }, + { .name = "adv7180cp", .driver_data = (kernel_ulong_t)&adv7180_info }, + { .name = "adv7180st", .driver_data = (kernel_ulong_t)&adv7180_info }, + { .name = "adv7182", .driver_data = (kernel_ulong_t)&adv7182_info }, + { .name = "adv7280", .driver_data = (kernel_ulong_t)&adv7280_info }, + { .name = "adv7280-m", .driver_data = (kernel_ulong_t)&adv7280_m_info }, + { .name = "adv7281", .driver_data = (kernel_ulong_t)&adv7281_info }, + { .name = "adv7281-m", .driver_data = (kernel_ulong_t)&adv7281_m_info }, + { .name = "adv7281-ma", .driver_data = (kernel_ulong_t)&adv7281_ma_info }, + { .name = "adv7282", .driver_data = (kernel_ulong_t)&adv7282_info }, + { .name = "adv7282-m", .driver_data = (kernel_ulong_t)&adv7282_m_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7180_id); + +static const struct of_device_id adv7180_of_id[] = { + { .compatible = "adi,adv7180", &adv7180_info }, + { .compatible = "adi,adv7180cp", &adv7180_info }, + { .compatible = "adi,adv7180st", &adv7180_info }, + { .compatible = "adi,adv7182", &adv7182_info }, + { .compatible = "adi,adv7280", &adv7280_info }, + { .compatible = "adi,adv7280-m", &adv7280_m_info }, + { .compatible = "adi,adv7281", &adv7281_info }, + { .compatible = "adi,adv7281-m", &adv7281_m_info }, + { .compatible = "adi,adv7281-ma", &adv7281_ma_info }, + { .compatible = "adi,adv7282", &adv7282_info }, + { .compatible = "adi,adv7282-m", &adv7282_m_info }, + {} +}; +MODULE_DEVICE_TABLE(of, adv7180_of_id); + +static struct i2c_driver adv7180_driver = { + .driver = { + .name = KBUILD_MODNAME, + .pm = ADV7180_PM_OPS, + .of_match_table = adv7180_of_id, + }, + .probe = adv7180_probe, + .remove = adv7180_remove, + .id_table = adv7180_id, +}; + +module_i2c_driver(adv7180_driver); + +MODULE_DESCRIPTION("Analog Devices ADV7180 video decoder driver"); +MODULE_AUTHOR("Mocean Laboratories"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/adv7183.c b/drivers/media/i2c/adv7183.c new file mode 100644 index 000000000..a04a1a205 --- /dev/null +++ b/drivers/media/i2c/adv7183.c @@ -0,0 +1,641 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * adv7183.c Analog Devices ADV7183 video decoder driver + * + * Copyright (c) 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "adv7183_regs.h" + +struct adv7183 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + + v4l2_std_id std; /* Current set standard */ + u32 input; + u32 output; + struct gpio_desc *reset_pin; + struct gpio_desc *oe_pin; + struct v4l2_mbus_framefmt fmt; +}; + +/* EXAMPLES USING 27 MHz CLOCK + * Mode 1 CVBS Input (Composite Video on AIN5) + * All standards are supported through autodetect, 8-bit, 4:2:2, ITU-R BT.656 output on P15 to P8. + */ +static const unsigned char adv7183_init_regs[] = { + ADV7183_IN_CTRL, 0x04, /* CVBS input on AIN5 */ + ADV7183_DIGI_CLAMP_CTRL_1, 0x00, /* Slow down digital clamps */ + ADV7183_SHAP_FILT_CTRL, 0x41, /* Set CSFM to SH1 */ + ADV7183_ADC_CTRL, 0x16, /* Power down ADC 1 and ADC 2 */ + ADV7183_CTI_DNR_CTRL_4, 0x04, /* Set DNR threshold to 4 for flat response */ + /* ADI recommended programming sequence */ + ADV7183_ADI_CTRL, 0x80, + ADV7183_CTI_DNR_CTRL_4, 0x20, + 0x52, 0x18, + 0x58, 0xED, + 0x77, 0xC5, + 0x7C, 0x93, + 0x7D, 0x00, + 0xD0, 0x48, + 0xD5, 0xA0, + 0xD7, 0xEA, + ADV7183_SD_SATURATION_CR, 0x3E, + ADV7183_PAL_V_END, 0x3E, + ADV7183_PAL_F_TOGGLE, 0x0F, + ADV7183_ADI_CTRL, 0x00, +}; + +static inline struct adv7183 *to_adv7183(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7183, sd); +} +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7183, hdl)->sd; +} + +static inline int adv7183_read(struct v4l2_subdev *sd, unsigned char reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static inline int adv7183_write(struct v4l2_subdev *sd, unsigned char reg, + unsigned char value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int adv7183_writeregs(struct v4l2_subdev *sd, + const unsigned char *regs, unsigned int num) +{ + unsigned char reg, data; + unsigned int cnt = 0; + + if (num & 0x1) { + v4l2_err(sd, "invalid regs array\n"); + return -1; + } + + while (cnt < num) { + reg = *regs++; + data = *regs++; + cnt += 2; + + adv7183_write(sd, reg, data); + } + return 0; +} + +static int adv7183_log_status(struct v4l2_subdev *sd) +{ + struct adv7183 *decoder = to_adv7183(sd); + + v4l2_info(sd, "adv7183: Input control = 0x%02x\n", + adv7183_read(sd, ADV7183_IN_CTRL)); + v4l2_info(sd, "adv7183: Video selection = 0x%02x\n", + adv7183_read(sd, ADV7183_VD_SEL)); + v4l2_info(sd, "adv7183: Output control = 0x%02x\n", + adv7183_read(sd, ADV7183_OUT_CTRL)); + v4l2_info(sd, "adv7183: Extended output control = 0x%02x\n", + adv7183_read(sd, ADV7183_EXT_OUT_CTRL)); + v4l2_info(sd, "adv7183: Autodetect enable = 0x%02x\n", + adv7183_read(sd, ADV7183_AUTO_DET_EN)); + v4l2_info(sd, "adv7183: Contrast = 0x%02x\n", + adv7183_read(sd, ADV7183_CONTRAST)); + v4l2_info(sd, "adv7183: Brightness = 0x%02x\n", + adv7183_read(sd, ADV7183_BRIGHTNESS)); + v4l2_info(sd, "adv7183: Hue = 0x%02x\n", + adv7183_read(sd, ADV7183_HUE)); + v4l2_info(sd, "adv7183: Default value Y = 0x%02x\n", + adv7183_read(sd, ADV7183_DEF_Y)); + v4l2_info(sd, "adv7183: Default value C = 0x%02x\n", + adv7183_read(sd, ADV7183_DEF_C)); + v4l2_info(sd, "adv7183: ADI control = 0x%02x\n", + adv7183_read(sd, ADV7183_ADI_CTRL)); + v4l2_info(sd, "adv7183: Power Management = 0x%02x\n", + adv7183_read(sd, ADV7183_POW_MANAGE)); + v4l2_info(sd, "adv7183: Status 1 2 and 3 = 0x%02x 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_STATUS_1), + adv7183_read(sd, ADV7183_STATUS_2), + adv7183_read(sd, ADV7183_STATUS_3)); + v4l2_info(sd, "adv7183: Ident = 0x%02x\n", + adv7183_read(sd, ADV7183_IDENT)); + v4l2_info(sd, "adv7183: Analog clamp control = 0x%02x\n", + adv7183_read(sd, ADV7183_ANAL_CLAMP_CTRL)); + v4l2_info(sd, "adv7183: Digital clamp control 1 = 0x%02x\n", + adv7183_read(sd, ADV7183_DIGI_CLAMP_CTRL_1)); + v4l2_info(sd, "adv7183: Shaping filter control 1 and 2 = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_SHAP_FILT_CTRL), + adv7183_read(sd, ADV7183_SHAP_FILT_CTRL_2)); + v4l2_info(sd, "adv7183: Comb filter control = 0x%02x\n", + adv7183_read(sd, ADV7183_COMB_FILT_CTRL)); + v4l2_info(sd, "adv7183: ADI control 2 = 0x%02x\n", + adv7183_read(sd, ADV7183_ADI_CTRL_2)); + v4l2_info(sd, "adv7183: Pixel delay control = 0x%02x\n", + adv7183_read(sd, ADV7183_PIX_DELAY_CTRL)); + v4l2_info(sd, "adv7183: Misc gain control = 0x%02x\n", + adv7183_read(sd, ADV7183_MISC_GAIN_CTRL)); + v4l2_info(sd, "adv7183: AGC mode control = 0x%02x\n", + adv7183_read(sd, ADV7183_AGC_MODE_CTRL)); + v4l2_info(sd, "adv7183: Chroma gain control 1 and 2 = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_CHRO_GAIN_CTRL_1), + adv7183_read(sd, ADV7183_CHRO_GAIN_CTRL_2)); + v4l2_info(sd, "adv7183: Luma gain control 1 and 2 = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_LUMA_GAIN_CTRL_1), + adv7183_read(sd, ADV7183_LUMA_GAIN_CTRL_2)); + v4l2_info(sd, "adv7183: Vsync field control 1 2 and 3 = 0x%02x 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_VS_FIELD_CTRL_1), + adv7183_read(sd, ADV7183_VS_FIELD_CTRL_2), + adv7183_read(sd, ADV7183_VS_FIELD_CTRL_3)); + v4l2_info(sd, "adv7183: Hsync position control 1 2 and 3 = 0x%02x 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_HS_POS_CTRL_1), + adv7183_read(sd, ADV7183_HS_POS_CTRL_2), + adv7183_read(sd, ADV7183_HS_POS_CTRL_3)); + v4l2_info(sd, "adv7183: Polarity = 0x%02x\n", + adv7183_read(sd, ADV7183_POLARITY)); + v4l2_info(sd, "adv7183: ADC control = 0x%02x\n", + adv7183_read(sd, ADV7183_ADC_CTRL)); + v4l2_info(sd, "adv7183: SD offset Cb and Cr = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_SD_OFFSET_CB), + adv7183_read(sd, ADV7183_SD_OFFSET_CR)); + v4l2_info(sd, "adv7183: SD saturation Cb and Cr = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_SD_SATURATION_CB), + adv7183_read(sd, ADV7183_SD_SATURATION_CR)); + v4l2_info(sd, "adv7183: Drive strength = 0x%02x\n", + adv7183_read(sd, ADV7183_DRIVE_STR)); + v4l2_ctrl_handler_log_status(&decoder->hdl, sd->name); + return 0; +} + +static int adv7183_g_std(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct adv7183 *decoder = to_adv7183(sd); + + *std = decoder->std; + return 0; +} + +static int adv7183_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7183 *decoder = to_adv7183(sd); + int reg; + + reg = adv7183_read(sd, ADV7183_IN_CTRL) & 0xF; + if (std == V4L2_STD_PAL_60) + reg |= 0x60; + else if (std == V4L2_STD_NTSC_443) + reg |= 0x70; + else if (std == V4L2_STD_PAL_N) + reg |= 0x90; + else if (std == V4L2_STD_PAL_M) + reg |= 0xA0; + else if (std == V4L2_STD_PAL_Nc) + reg |= 0xC0; + else if (std & V4L2_STD_PAL) + reg |= 0x80; + else if (std & V4L2_STD_NTSC) + reg |= 0x50; + else if (std & V4L2_STD_SECAM) + reg |= 0xE0; + else + return -EINVAL; + adv7183_write(sd, ADV7183_IN_CTRL, reg); + + decoder->std = std; + + return 0; +} + +static int adv7183_reset(struct v4l2_subdev *sd, u32 val) +{ + int reg; + + reg = adv7183_read(sd, ADV7183_POW_MANAGE) | 0x80; + adv7183_write(sd, ADV7183_POW_MANAGE, reg); + /* wait 5ms before any further i2c writes are performed */ + usleep_range(5000, 10000); + return 0; +} + +static int adv7183_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7183 *decoder = to_adv7183(sd); + int reg; + + if ((input > ADV7183_COMPONENT1) || (output > ADV7183_16BIT_OUT)) + return -EINVAL; + + if (input != decoder->input) { + decoder->input = input; + reg = adv7183_read(sd, ADV7183_IN_CTRL) & 0xF0; + switch (input) { + case ADV7183_COMPOSITE1: + reg |= 0x1; + break; + case ADV7183_COMPOSITE2: + reg |= 0x2; + break; + case ADV7183_COMPOSITE3: + reg |= 0x3; + break; + case ADV7183_COMPOSITE4: + reg |= 0x4; + break; + case ADV7183_COMPOSITE5: + reg |= 0x5; + break; + case ADV7183_COMPOSITE6: + reg |= 0xB; + break; + case ADV7183_COMPOSITE7: + reg |= 0xC; + break; + case ADV7183_COMPOSITE8: + reg |= 0xD; + break; + case ADV7183_COMPOSITE9: + reg |= 0xE; + break; + case ADV7183_COMPOSITE10: + reg |= 0xF; + break; + case ADV7183_SVIDEO0: + reg |= 0x6; + break; + case ADV7183_SVIDEO1: + reg |= 0x7; + break; + case ADV7183_SVIDEO2: + reg |= 0x8; + break; + case ADV7183_COMPONENT0: + reg |= 0x9; + break; + case ADV7183_COMPONENT1: + reg |= 0xA; + break; + default: + break; + } + adv7183_write(sd, ADV7183_IN_CTRL, reg); + } + + if (output != decoder->output) { + decoder->output = output; + reg = adv7183_read(sd, ADV7183_OUT_CTRL) & 0xC0; + switch (output) { + case ADV7183_16BIT_OUT: + reg |= 0x9; + break; + default: + reg |= 0xC; + break; + } + adv7183_write(sd, ADV7183_OUT_CTRL, reg); + } + + return 0; +} + +static int adv7183_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + int val = ctrl->val; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (val < 0) + val = 127 - val; + adv7183_write(sd, ADV7183_BRIGHTNESS, val); + break; + case V4L2_CID_CONTRAST: + adv7183_write(sd, ADV7183_CONTRAST, val); + break; + case V4L2_CID_SATURATION: + adv7183_write(sd, ADV7183_SD_SATURATION_CB, val >> 8); + adv7183_write(sd, ADV7183_SD_SATURATION_CR, (val & 0xFF)); + break; + case V4L2_CID_HUE: + adv7183_write(sd, ADV7183_SD_OFFSET_CB, val >> 8); + adv7183_write(sd, ADV7183_SD_OFFSET_CR, (val & 0xFF)); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int adv7183_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct adv7183 *decoder = to_adv7183(sd); + int reg; + + /* enable autodetection block */ + reg = adv7183_read(sd, ADV7183_IN_CTRL) & 0xF; + adv7183_write(sd, ADV7183_IN_CTRL, reg); + + /* wait autodetection switch */ + mdelay(10); + + /* get autodetection result */ + reg = adv7183_read(sd, ADV7183_STATUS_1); + switch ((reg >> 0x4) & 0x7) { + case 0: + *std &= V4L2_STD_NTSC; + break; + case 1: + *std &= V4L2_STD_NTSC_443; + break; + case 2: + *std &= V4L2_STD_PAL_M; + break; + case 3: + *std &= V4L2_STD_PAL_60; + break; + case 4: + *std &= V4L2_STD_PAL; + break; + case 5: + *std &= V4L2_STD_SECAM; + break; + case 6: + *std &= V4L2_STD_PAL_Nc; + break; + case 7: + *std &= V4L2_STD_SECAM; + break; + default: + *std = V4L2_STD_UNKNOWN; + break; + } + + /* after std detection, write back user set std */ + adv7183_s_std(sd, decoder->std); + return 0; +} + +static int adv7183_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + int reg; + + *status = V4L2_IN_ST_NO_SIGNAL; + reg = adv7183_read(sd, ADV7183_STATUS_1); + if (reg < 0) + return reg; + if (reg & 0x1) + *status = 0; + return 0; +} + +static int adv7183_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_UYVY8_2X8; + return 0; +} + +static int adv7183_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct adv7183 *decoder = to_adv7183(sd); + struct v4l2_mbus_framefmt *fmt = &format->format; + + if (format->pad) + return -EINVAL; + + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + if (decoder->std & V4L2_STD_525_60) { + fmt->field = V4L2_FIELD_SEQ_TB; + fmt->width = 720; + fmt->height = 480; + } else { + fmt->field = V4L2_FIELD_SEQ_BT; + fmt->width = 720; + fmt->height = 576; + } + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + decoder->fmt = *fmt; + return 0; +} + +static int adv7183_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct adv7183 *decoder = to_adv7183(sd); + + if (format->pad) + return -EINVAL; + + format->format = decoder->fmt; + return 0; +} + +static int adv7183_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct adv7183 *decoder = to_adv7183(sd); + + if (enable) + gpiod_set_value(decoder->oe_pin, 1); + else + gpiod_set_value(decoder->oe_pin, 0); + udelay(1); + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int adv7183_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + reg->val = adv7183_read(sd, reg->reg & 0xff); + reg->size = 1; + return 0; +} + +static int adv7183_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + adv7183_write(sd, reg->reg & 0xff, reg->val & 0xff); + return 0; +} +#endif + +static const struct v4l2_ctrl_ops adv7183_ctrl_ops = { + .s_ctrl = adv7183_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops adv7183_core_ops = { + .log_status = adv7183_log_status, + .reset = adv7183_reset, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = adv7183_g_register, + .s_register = adv7183_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops adv7183_video_ops = { + .g_std = adv7183_g_std, + .s_std = adv7183_s_std, + .s_routing = adv7183_s_routing, + .querystd = adv7183_querystd, + .g_input_status = adv7183_g_input_status, + .s_stream = adv7183_s_stream, +}; + +static const struct v4l2_subdev_pad_ops adv7183_pad_ops = { + .enum_mbus_code = adv7183_enum_mbus_code, + .get_fmt = adv7183_get_fmt, + .set_fmt = adv7183_set_fmt, +}; + +static const struct v4l2_subdev_ops adv7183_ops = { + .core = &adv7183_core_ops, + .video = &adv7183_video_ops, + .pad = &adv7183_pad_ops, +}; + +static int adv7183_probe(struct i2c_client *client) +{ + struct adv7183 *decoder; + struct v4l2_subdev *sd; + struct v4l2_ctrl_handler *hdl; + int ret; + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr << 1, client->adapter->name); + + decoder = devm_kzalloc(&client->dev, sizeof(*decoder), GFP_KERNEL); + if (decoder == NULL) + return -ENOMEM; + + /* + * Requesting high will assert reset, the line should be + * flagged as active low in descriptor table or machine description. + */ + decoder->reset_pin = devm_gpiod_get(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(decoder->reset_pin)) + return PTR_ERR(decoder->reset_pin); + gpiod_set_consumer_name(decoder->reset_pin, "ADV7183 Reset"); + /* + * Requesting low will start with output disabled, the line should be + * flagged as active low in descriptor table or machine description. + */ + decoder->oe_pin = devm_gpiod_get(&client->dev, "oe", + GPIOD_OUT_LOW); + if (IS_ERR(decoder->oe_pin)) + return PTR_ERR(decoder->oe_pin); + gpiod_set_consumer_name(decoder->reset_pin, "ADV7183 Output Enable"); + + sd = &decoder->sd; + v4l2_i2c_subdev_init(sd, client, &adv7183_ops); + + hdl = &decoder->hdl; + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &adv7183_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &adv7183_ctrl_ops, + V4L2_CID_CONTRAST, 0, 0xFF, 1, 0x80); + v4l2_ctrl_new_std(hdl, &adv7183_ctrl_ops, + V4L2_CID_SATURATION, 0, 0xFFFF, 1, 0x8080); + v4l2_ctrl_new_std(hdl, &adv7183_ctrl_ops, + V4L2_CID_HUE, 0, 0xFFFF, 1, 0x8080); + /* hook the control handler into the driver */ + sd->ctrl_handler = hdl; + if (hdl->error) { + ret = hdl->error; + + v4l2_ctrl_handler_free(hdl); + return ret; + } + + /* v4l2 doesn't support an autodetect standard, pick PAL as default */ + decoder->std = V4L2_STD_PAL; + decoder->input = ADV7183_COMPOSITE4; + decoder->output = ADV7183_8BIT_OUT; + + /* reset chip */ + /* reset pulse width at least 5ms */ + mdelay(10); + /* De-assert reset line (descriptor tagged active low) */ + gpiod_set_value(decoder->reset_pin, 0); + /* wait 5ms before any further i2c writes are performed */ + mdelay(5); + + adv7183_writeregs(sd, adv7183_init_regs, ARRAY_SIZE(adv7183_init_regs)); + adv7183_s_std(sd, decoder->std); + fmt.format.width = 720; + fmt.format.height = 576; + adv7183_set_fmt(sd, NULL, &fmt); + + /* initialize the hardware to the default control values */ + ret = v4l2_ctrl_handler_setup(hdl); + if (ret) { + v4l2_ctrl_handler_free(hdl); + return ret; + } + + return 0; +} + +static void adv7183_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); +} + +static const struct i2c_device_id adv7183_id[] = { + { .name = "adv7183" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, adv7183_id); + +static struct i2c_driver adv7183_driver = { + .driver = { + .name = "adv7183", + }, + .probe = adv7183_probe, + .remove = adv7183_remove, + .id_table = adv7183_id, +}; + +module_i2c_driver(adv7183_driver); + +MODULE_DESCRIPTION("Analog Devices ADV7183 video decoder driver"); +MODULE_AUTHOR("Scott Jiang "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/adv7183_regs.h b/drivers/media/i2c/adv7183_regs.h new file mode 100644 index 000000000..d241efe73 --- /dev/null +++ b/drivers/media/i2c/adv7183_regs.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * adv7183 - Analog Devices ADV7183 video decoder registers + * + * Copyright (c) 2011 Analog Devices Inc. + */ + +#ifndef _ADV7183_REGS_H_ +#define _ADV7183_REGS_H_ + +#define ADV7183_IN_CTRL 0x00 /* Input control */ +#define ADV7183_VD_SEL 0x01 /* Video selection */ +#define ADV7183_OUT_CTRL 0x03 /* Output control */ +#define ADV7183_EXT_OUT_CTRL 0x04 /* Extended output control */ +#define ADV7183_AUTO_DET_EN 0x07 /* Autodetect enable */ +#define ADV7183_CONTRAST 0x08 /* Contrast */ +#define ADV7183_BRIGHTNESS 0x0A /* Brightness */ +#define ADV7183_HUE 0x0B /* Hue */ +#define ADV7183_DEF_Y 0x0C /* Default value Y */ +#define ADV7183_DEF_C 0x0D /* Default value C */ +#define ADV7183_ADI_CTRL 0x0E /* ADI control */ +#define ADV7183_POW_MANAGE 0x0F /* Power Management */ +#define ADV7183_STATUS_1 0x10 /* Status 1 */ +#define ADV7183_IDENT 0x11 /* Ident */ +#define ADV7183_STATUS_2 0x12 /* Status 2 */ +#define ADV7183_STATUS_3 0x13 /* Status 3 */ +#define ADV7183_ANAL_CLAMP_CTRL 0x14 /* Analog clamp control */ +#define ADV7183_DIGI_CLAMP_CTRL_1 0x15 /* Digital clamp control 1 */ +#define ADV7183_SHAP_FILT_CTRL 0x17 /* Shaping filter control */ +#define ADV7183_SHAP_FILT_CTRL_2 0x18 /* Shaping filter control 2 */ +#define ADV7183_COMB_FILT_CTRL 0x19 /* Comb filter control */ +#define ADV7183_ADI_CTRL_2 0x1D /* ADI control 2 */ +#define ADV7183_PIX_DELAY_CTRL 0x27 /* Pixel delay control */ +#define ADV7183_MISC_GAIN_CTRL 0x2B /* Misc gain control */ +#define ADV7183_AGC_MODE_CTRL 0x2C /* AGC mode control */ +#define ADV7183_CHRO_GAIN_CTRL_1 0x2D /* Chroma gain control 1 */ +#define ADV7183_CHRO_GAIN_CTRL_2 0x2E /* Chroma gain control 2 */ +#define ADV7183_LUMA_GAIN_CTRL_1 0x2F /* Luma gain control 1 */ +#define ADV7183_LUMA_GAIN_CTRL_2 0x30 /* Luma gain control 2 */ +#define ADV7183_VS_FIELD_CTRL_1 0x31 /* Vsync field control 1 */ +#define ADV7183_VS_FIELD_CTRL_2 0x32 /* Vsync field control 2 */ +#define ADV7183_VS_FIELD_CTRL_3 0x33 /* Vsync field control 3 */ +#define ADV7183_HS_POS_CTRL_1 0x34 /* Hsync position control 1 */ +#define ADV7183_HS_POS_CTRL_2 0x35 /* Hsync position control 2 */ +#define ADV7183_HS_POS_CTRL_3 0x36 /* Hsync position control 3 */ +#define ADV7183_POLARITY 0x37 /* Polarity */ +#define ADV7183_NTSC_COMB_CTRL 0x38 /* NTSC comb control */ +#define ADV7183_PAL_COMB_CTRL 0x39 /* PAL comb control */ +#define ADV7183_ADC_CTRL 0x3A /* ADC control */ +#define ADV7183_MAN_WIN_CTRL 0x3D /* Manual window control */ +#define ADV7183_RESAMPLE_CTRL 0x41 /* Resample control */ +#define ADV7183_GEMSTAR_CTRL_1 0x48 /* Gemstar ctrl 1 */ +#define ADV7183_GEMSTAR_CTRL_2 0x49 /* Gemstar ctrl 2 */ +#define ADV7183_GEMSTAR_CTRL_3 0x4A /* Gemstar ctrl 3 */ +#define ADV7183_GEMSTAR_CTRL_4 0x4B /* Gemstar ctrl 4 */ +#define ADV7183_GEMSTAR_CTRL_5 0x4C /* Gemstar ctrl 5 */ +#define ADV7183_CTI_DNR_CTRL_1 0x4D /* CTI DNR ctrl 1 */ +#define ADV7183_CTI_DNR_CTRL_2 0x4E /* CTI DNR ctrl 2 */ +#define ADV7183_CTI_DNR_CTRL_4 0x50 /* CTI DNR ctrl 4 */ +#define ADV7183_LOCK_CNT 0x51 /* Lock count */ +#define ADV7183_FREE_LINE_LEN 0x8F /* Free-Run line length 1 */ +#define ADV7183_VBI_INFO 0x90 /* VBI info */ +#define ADV7183_WSS_1 0x91 /* WSS 1 */ +#define ADV7183_WSS_2 0x92 /* WSS 2 */ +#define ADV7183_EDTV_1 0x93 /* EDTV 1 */ +#define ADV7183_EDTV_2 0x94 /* EDTV 2 */ +#define ADV7183_EDTV_3 0x95 /* EDTV 3 */ +#define ADV7183_CGMS_1 0x96 /* CGMS 1 */ +#define ADV7183_CGMS_2 0x97 /* CGMS 2 */ +#define ADV7183_CGMS_3 0x98 /* CGMS 3 */ +#define ADV7183_CCAP_1 0x99 /* CCAP 1 */ +#define ADV7183_CCAP_2 0x9A /* CCAP 2 */ +#define ADV7183_LETTERBOX_1 0x9B /* Letterbox 1 */ +#define ADV7183_LETTERBOX_2 0x9C /* Letterbox 2 */ +#define ADV7183_LETTERBOX_3 0x9D /* Letterbox 3 */ +#define ADV7183_CRC_EN 0xB2 /* CRC enable */ +#define ADV7183_ADC_SWITCH_1 0xC3 /* ADC switch 1 */ +#define ADV7183_ADC_SWITCH_2 0xC4 /* ADC switch 2 */ +#define ADV7183_LETTERBOX_CTRL_1 0xDC /* Letterbox control 1 */ +#define ADV7183_LETTERBOX_CTRL_2 0xDD /* Letterbox control 2 */ +#define ADV7183_SD_OFFSET_CB 0xE1 /* SD offset Cb */ +#define ADV7183_SD_OFFSET_CR 0xE2 /* SD offset Cr */ +#define ADV7183_SD_SATURATION_CB 0xE3 /* SD saturation Cb */ +#define ADV7183_SD_SATURATION_CR 0xE4 /* SD saturation Cr */ +#define ADV7183_NTSC_V_BEGIN 0xE5 /* NTSC V bit begin */ +#define ADV7183_NTSC_V_END 0xE6 /* NTSC V bit end */ +#define ADV7183_NTSC_F_TOGGLE 0xE7 /* NTSC F bit toggle */ +#define ADV7183_PAL_V_BEGIN 0xE8 /* PAL V bit begin */ +#define ADV7183_PAL_V_END 0xE9 /* PAL V bit end */ +#define ADV7183_PAL_F_TOGGLE 0xEA /* PAL F bit toggle */ +#define ADV7183_DRIVE_STR 0xF4 /* Drive strength */ +#define ADV7183_IF_COMP_CTRL 0xF8 /* IF comp control */ +#define ADV7183_VS_MODE_CTRL 0xF9 /* VS mode control */ + +#endif diff --git a/drivers/media/i2c/adv7343.c b/drivers/media/i2c/adv7343.c new file mode 100644 index 000000000..9b91d7073 --- /dev/null +++ b/drivers/media/i2c/adv7343.c @@ -0,0 +1,529 @@ +/* + * adv7343 - ADV7343 Video Encoder Driver + * + * The encoder hardware does not support SECAM. + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://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 +#include +#include +#include + +#include "adv7343_regs.h" + +MODULE_DESCRIPTION("ADV7343 video encoder driver"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level 0-1"); + +struct adv7343_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + const struct adv7343_platform_data *pdata; + u8 reg00; + u8 reg01; + u8 reg02; + u8 reg35; + u8 reg80; + u8 reg82; + u32 output; + v4l2_std_id std; +}; + +static inline struct adv7343_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7343_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7343_state, hdl)->sd; +} + +static inline int adv7343_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static const u8 adv7343_init_reg_val[] = { + ADV7343_SOFT_RESET, ADV7343_SOFT_RESET_DEFAULT, + ADV7343_POWER_MODE_REG, ADV7343_POWER_MODE_REG_DEFAULT, + + ADV7343_HD_MODE_REG1, ADV7343_HD_MODE_REG1_DEFAULT, + ADV7343_HD_MODE_REG2, ADV7343_HD_MODE_REG2_DEFAULT, + ADV7343_HD_MODE_REG3, ADV7343_HD_MODE_REG3_DEFAULT, + ADV7343_HD_MODE_REG4, ADV7343_HD_MODE_REG4_DEFAULT, + ADV7343_HD_MODE_REG5, ADV7343_HD_MODE_REG5_DEFAULT, + ADV7343_HD_MODE_REG6, ADV7343_HD_MODE_REG6_DEFAULT, + ADV7343_HD_MODE_REG7, ADV7343_HD_MODE_REG7_DEFAULT, + + ADV7343_SD_MODE_REG1, ADV7343_SD_MODE_REG1_DEFAULT, + ADV7343_SD_MODE_REG2, ADV7343_SD_MODE_REG2_DEFAULT, + ADV7343_SD_MODE_REG3, ADV7343_SD_MODE_REG3_DEFAULT, + ADV7343_SD_MODE_REG4, ADV7343_SD_MODE_REG4_DEFAULT, + ADV7343_SD_MODE_REG5, ADV7343_SD_MODE_REG5_DEFAULT, + ADV7343_SD_MODE_REG6, ADV7343_SD_MODE_REG6_DEFAULT, + ADV7343_SD_MODE_REG7, ADV7343_SD_MODE_REG7_DEFAULT, + ADV7343_SD_MODE_REG8, ADV7343_SD_MODE_REG8_DEFAULT, + + ADV7343_SD_HUE_REG, ADV7343_SD_HUE_REG_DEFAULT, + ADV7343_SD_CGMS_WSS0, ADV7343_SD_CGMS_WSS0_DEFAULT, + ADV7343_SD_BRIGHTNESS_WSS, ADV7343_SD_BRIGHTNESS_WSS_DEFAULT, +}; + +/* + * 2^32 + * FSC(reg) = FSC (HZ) * -------- + * 27000000 + */ +static const struct adv7343_std_info stdinfo[] = { + { + /* FSC(Hz) = 3,579,545.45 Hz */ + SD_STD_NTSC, 569408542, V4L2_STD_NTSC, + }, { + /* FSC(Hz) = 3,575,611.00 Hz */ + SD_STD_PAL_M, 568782678, V4L2_STD_PAL_M, + }, { + /* FSC(Hz) = 3,582,056.00 */ + SD_STD_PAL_N, 569807903, V4L2_STD_PAL_Nc, + }, { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_PAL_N, 705268427, V4L2_STD_PAL_N, + }, { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_PAL_BDGHI, 705268427, V4L2_STD_PAL, + }, { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_NTSC, 705268427, V4L2_STD_NTSC_443, + }, { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_PAL_M, 705268427, V4L2_STD_PAL_60, + }, +}; + +static int adv7343_setstd(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7343_state *state = to_state(sd); + struct adv7343_std_info *std_info; + int num_std; + char *fsc_ptr; + u8 reg, val; + int err = 0; + int i = 0; + + std_info = (struct adv7343_std_info *)stdinfo; + num_std = ARRAY_SIZE(stdinfo); + + for (i = 0; i < num_std; i++) { + if (std_info[i].stdid & std) + break; + } + + if (i == num_std) { + v4l2_dbg(1, debug, sd, + "Invalid std or std is not supported: %llx\n", + (unsigned long long)std); + return -EINVAL; + } + + /* Set the standard */ + val = state->reg80 & (~(SD_STD_MASK)); + val |= std_info[i].standard_val3; + err = adv7343_write(sd, ADV7343_SD_MODE_REG1, val); + if (err < 0) + goto setstd_exit; + + state->reg80 = val; + + /* Configure the input mode register */ + val = state->reg01 & (~((u8) INPUT_MODE_MASK)); + val |= SD_INPUT_MODE; + err = adv7343_write(sd, ADV7343_MODE_SELECT_REG, val); + if (err < 0) + goto setstd_exit; + + state->reg01 = val; + + /* Program the sub carrier frequency registers */ + fsc_ptr = (unsigned char *)&std_info[i].fsc_val; + reg = ADV7343_FSC_REG0; + for (i = 0; i < 4; i++, reg++, fsc_ptr++) { + err = adv7343_write(sd, reg, *fsc_ptr); + if (err < 0) + goto setstd_exit; + } + + val = state->reg80; + + /* Filter settings */ + if (std & (V4L2_STD_NTSC | V4L2_STD_NTSC_443)) + val &= 0x03; + else if (std & ~V4L2_STD_SECAM) + val |= 0x04; + + err = adv7343_write(sd, ADV7343_SD_MODE_REG1, val); + if (err < 0) + goto setstd_exit; + + state->reg80 = val; + +setstd_exit: + if (err != 0) + v4l2_err(sd, "Error setting std, write failed\n"); + + return err; +} + +static int adv7343_setoutput(struct v4l2_subdev *sd, u32 output_type) +{ + struct adv7343_state *state = to_state(sd); + unsigned char val; + int err = 0; + + if (output_type > ADV7343_SVIDEO_ID) { + v4l2_dbg(1, debug, sd, + "Invalid output type or output type not supported:%d\n", + output_type); + return -EINVAL; + } + + /* Enable Appropriate DAC */ + val = state->reg00 & 0x03; + + /* configure default configuration */ + if (!state->pdata) + if (output_type == ADV7343_COMPOSITE_ID) + val |= ADV7343_COMPOSITE_POWER_VALUE; + else if (output_type == ADV7343_COMPONENT_ID) + val |= ADV7343_COMPONENT_POWER_VALUE; + else + val |= ADV7343_SVIDEO_POWER_VALUE; + else + val = state->pdata->mode_config.sleep_mode << 0 | + state->pdata->mode_config.pll_control << 1 | + state->pdata->mode_config.dac[2] << 2 | + state->pdata->mode_config.dac[1] << 3 | + state->pdata->mode_config.dac[0] << 4 | + state->pdata->mode_config.dac[5] << 5 | + state->pdata->mode_config.dac[4] << 6 | + state->pdata->mode_config.dac[3] << 7; + + err = adv7343_write(sd, ADV7343_POWER_MODE_REG, val); + if (err < 0) + goto setoutput_exit; + + state->reg00 = val; + + /* Enable YUV output */ + val = state->reg02 | YUV_OUTPUT_SELECT; + err = adv7343_write(sd, ADV7343_MODE_REG0, val); + if (err < 0) + goto setoutput_exit; + + state->reg02 = val; + + /* configure SD DAC Output 2 and SD DAC Output 1 bit to zero */ + val = state->reg82 & (SD_DAC_1_DI & SD_DAC_2_DI); + + if (state->pdata && state->pdata->sd_config.sd_dac_out[0]) + val = val | (state->pdata->sd_config.sd_dac_out[0] << 1); + else if (state->pdata && !state->pdata->sd_config.sd_dac_out[0]) + val = val & ~(state->pdata->sd_config.sd_dac_out[0] << 1); + + if (state->pdata && state->pdata->sd_config.sd_dac_out[1]) + val = val | (state->pdata->sd_config.sd_dac_out[1] << 2); + else if (state->pdata && !state->pdata->sd_config.sd_dac_out[1]) + val = val & ~(state->pdata->sd_config.sd_dac_out[1] << 2); + + err = adv7343_write(sd, ADV7343_SD_MODE_REG2, val); + if (err < 0) + goto setoutput_exit; + + state->reg82 = val; + + /* configure ED/HD Color DAC Swap and ED/HD RGB Input Enable bit to + * zero */ + val = state->reg35 & (HD_RGB_INPUT_DI & HD_DAC_SWAP_DI); + err = adv7343_write(sd, ADV7343_HD_MODE_REG6, val); + if (err < 0) + goto setoutput_exit; + + state->reg35 = val; + +setoutput_exit: + if (err != 0) + v4l2_err(sd, "Error setting output, write failed\n"); + + return err; +} + +static int adv7343_log_status(struct v4l2_subdev *sd) +{ + struct adv7343_state *state = to_state(sd); + + v4l2_info(sd, "Standard: %llx\n", (unsigned long long)state->std); + v4l2_info(sd, "Output: %s\n", (state->output == 0) ? "Composite" : + ((state->output == 1) ? "Component" : "S-Video")); + return 0; +} + +static int adv7343_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + return adv7343_write(sd, ADV7343_SD_BRIGHTNESS_WSS, + ctrl->val); + + case V4L2_CID_HUE: + return adv7343_write(sd, ADV7343_SD_HUE_REG, ctrl->val); + + case V4L2_CID_GAIN: + return adv7343_write(sd, ADV7343_DAC2_OUTPUT_LEVEL, ctrl->val); + } + return -EINVAL; +} + +static const struct v4l2_ctrl_ops adv7343_ctrl_ops = { + .s_ctrl = adv7343_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops adv7343_core_ops = { + .log_status = adv7343_log_status, +}; + +static int adv7343_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7343_state *state = to_state(sd); + int err = 0; + + if (state->std == std) + return 0; + + err = adv7343_setstd(sd, std); + if (!err) + state->std = std; + + return err; +} + +static int adv7343_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7343_state *state = to_state(sd); + int err = 0; + + if (state->output == output) + return 0; + + err = adv7343_setoutput(sd, output); + if (!err) + state->output = output; + + return err; +} + +static const struct v4l2_subdev_video_ops adv7343_video_ops = { + .s_std_output = adv7343_s_std_output, + .s_routing = adv7343_s_routing, +}; + +static const struct v4l2_subdev_ops adv7343_ops = { + .core = &adv7343_core_ops, + .video = &adv7343_video_ops, +}; + +static int adv7343_initialize(struct v4l2_subdev *sd) +{ + struct adv7343_state *state = to_state(sd); + int err = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(adv7343_init_reg_val); i += 2) { + + err = adv7343_write(sd, adv7343_init_reg_val[i], + adv7343_init_reg_val[i+1]); + if (err) { + v4l2_err(sd, "Error initializing\n"); + return err; + } + } + + /* Configure for default video standard */ + err = adv7343_setoutput(sd, state->output); + if (err < 0) { + v4l2_err(sd, "Error setting output during init\n"); + return -EINVAL; + } + + err = adv7343_setstd(sd, state->std); + if (err < 0) { + v4l2_err(sd, "Error setting std during init\n"); + return -EINVAL; + } + + return err; +} + +static struct adv7343_platform_data * +adv7343_get_pdata(struct i2c_client *client) +{ + struct adv7343_platform_data *pdata; + struct device_node *np; + + if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node) + return client->dev.platform_data; + + np = of_graph_get_endpoint_by_regs(client->dev.of_node, 0, -1); + if (!np) + return NULL; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto done; + + pdata->mode_config.sleep_mode = + of_property_read_bool(np, "adi,power-mode-sleep-mode"); + + pdata->mode_config.pll_control = + of_property_read_bool(np, "adi,power-mode-pll-ctrl"); + + of_property_read_u32_array(np, "adi,dac-enable", + pdata->mode_config.dac, 6); + + of_property_read_u32_array(np, "adi,sd-dac-enable", + pdata->sd_config.sd_dac_out, 2); + +done: + of_node_put(np); + return pdata; +} + +static int adv7343_probe(struct i2c_client *client) +{ + struct adv7343_state *state; + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(struct adv7343_state), + GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + + /* Copy board specific information here */ + state->pdata = adv7343_get_pdata(client); + + state->reg00 = 0x80; + state->reg01 = 0x00; + state->reg02 = 0x20; + state->reg35 = 0x00; + state->reg80 = ADV7343_SD_MODE_REG1_DEFAULT; + state->reg82 = ADV7343_SD_MODE_REG2_DEFAULT; + + state->output = ADV7343_COMPOSITE_ID; + state->std = V4L2_STD_NTSC; + + v4l2_i2c_subdev_init(&state->sd, client, &adv7343_ops); + + v4l2_ctrl_handler_init(&state->hdl, 2); + v4l2_ctrl_new_std(&state->hdl, &adv7343_ctrl_ops, + V4L2_CID_BRIGHTNESS, ADV7343_BRIGHTNESS_MIN, + ADV7343_BRIGHTNESS_MAX, 1, + ADV7343_BRIGHTNESS_DEF); + v4l2_ctrl_new_std(&state->hdl, &adv7343_ctrl_ops, + V4L2_CID_HUE, ADV7343_HUE_MIN, + ADV7343_HUE_MAX, 1, + ADV7343_HUE_DEF); + v4l2_ctrl_new_std(&state->hdl, &adv7343_ctrl_ops, + V4L2_CID_GAIN, ADV7343_GAIN_MIN, + ADV7343_GAIN_MAX, 1, + ADV7343_GAIN_DEF); + state->sd.ctrl_handler = &state->hdl; + if (state->hdl.error) { + err = state->hdl.error; + goto done; + } + v4l2_ctrl_handler_setup(&state->hdl); + + err = adv7343_initialize(&state->sd); + if (err) + goto done; + + err = v4l2_async_register_subdev(&state->sd); + +done: + if (err < 0) + v4l2_ctrl_handler_free(&state->hdl); + + return err; +} + +static void adv7343_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7343_state *state = to_state(sd); + + v4l2_async_unregister_subdev(&state->sd); + v4l2_ctrl_handler_free(&state->hdl); +} + +static const struct i2c_device_id adv7343_id[] = { + { .name = "adv7343" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, adv7343_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id adv7343_of_match[] = { + {.compatible = "adi,adv7343", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, adv7343_of_match); +#endif + +static struct i2c_driver adv7343_driver = { + .driver = { + .of_match_table = of_match_ptr(adv7343_of_match), + .name = "adv7343", + }, + .probe = adv7343_probe, + .remove = adv7343_remove, + .id_table = adv7343_id, +}; + +module_i2c_driver(adv7343_driver); diff --git a/drivers/media/i2c/adv7343_regs.h b/drivers/media/i2c/adv7343_regs.h new file mode 100644 index 000000000..e0357e627 --- /dev/null +++ b/drivers/media/i2c/adv7343_regs.h @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ADV7343 encoder related structure and register definitions + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + */ + +#ifndef ADV7343_REGS_H +#define ADV7343_REGS_H + +struct adv7343_std_info { + u32 standard_val3; + u32 fsc_val; + v4l2_std_id stdid; +}; + +/* Register offset macros */ +#define ADV7343_POWER_MODE_REG (0x00) +#define ADV7343_MODE_SELECT_REG (0x01) +#define ADV7343_MODE_REG0 (0x02) + +#define ADV7343_DAC2_OUTPUT_LEVEL (0x0b) + +#define ADV7343_SOFT_RESET (0x17) + +#define ADV7343_HD_MODE_REG1 (0x30) +#define ADV7343_HD_MODE_REG2 (0x31) +#define ADV7343_HD_MODE_REG3 (0x32) +#define ADV7343_HD_MODE_REG4 (0x33) +#define ADV7343_HD_MODE_REG5 (0x34) +#define ADV7343_HD_MODE_REG6 (0x35) + +#define ADV7343_HD_MODE_REG7 (0x39) + +#define ADV7343_SD_MODE_REG1 (0x80) +#define ADV7343_SD_MODE_REG2 (0x82) +#define ADV7343_SD_MODE_REG3 (0x83) +#define ADV7343_SD_MODE_REG4 (0x84) +#define ADV7343_SD_MODE_REG5 (0x86) +#define ADV7343_SD_MODE_REG6 (0x87) +#define ADV7343_SD_MODE_REG7 (0x88) +#define ADV7343_SD_MODE_REG8 (0x89) + +#define ADV7343_FSC_REG0 (0x8C) +#define ADV7343_FSC_REG1 (0x8D) +#define ADV7343_FSC_REG2 (0x8E) +#define ADV7343_FSC_REG3 (0x8F) + +#define ADV7343_SD_CGMS_WSS0 (0x99) + +#define ADV7343_SD_HUE_REG (0xA0) +#define ADV7343_SD_BRIGHTNESS_WSS (0xA1) + +/* Default values for the registers */ +#define ADV7343_POWER_MODE_REG_DEFAULT (0x10) +#define ADV7343_HD_MODE_REG1_DEFAULT (0x3C) /* Changed Default + 720p EAVSAV code*/ +#define ADV7343_HD_MODE_REG2_DEFAULT (0x01) /* Changed Pixel data + valid */ +#define ADV7343_HD_MODE_REG3_DEFAULT (0x00) /* Color delay 0 clks */ +#define ADV7343_HD_MODE_REG4_DEFAULT (0xE8) /* Changed */ +#define ADV7343_HD_MODE_REG5_DEFAULT (0x08) +#define ADV7343_HD_MODE_REG6_DEFAULT (0x00) +#define ADV7343_HD_MODE_REG7_DEFAULT (0x00) +#define ADV7343_SD_MODE_REG8_DEFAULT (0x00) +#define ADV7343_SOFT_RESET_DEFAULT (0x02) +#define ADV7343_COMPOSITE_POWER_VALUE (0x80) +#define ADV7343_COMPONENT_POWER_VALUE (0x1C) +#define ADV7343_SVIDEO_POWER_VALUE (0x60) +#define ADV7343_SD_HUE_REG_DEFAULT (127) +#define ADV7343_SD_BRIGHTNESS_WSS_DEFAULT (0x03) + +#define ADV7343_SD_CGMS_WSS0_DEFAULT (0x10) + +#define ADV7343_SD_MODE_REG1_DEFAULT (0x00) +#define ADV7343_SD_MODE_REG2_DEFAULT (0xC9) +#define ADV7343_SD_MODE_REG3_DEFAULT (0x10) +#define ADV7343_SD_MODE_REG4_DEFAULT (0x01) +#define ADV7343_SD_MODE_REG5_DEFAULT (0x02) +#define ADV7343_SD_MODE_REG6_DEFAULT (0x0C) +#define ADV7343_SD_MODE_REG7_DEFAULT (0x04) +#define ADV7343_SD_MODE_REG8_DEFAULT (0x00) + +/* Bit masks for Mode Select Register */ +#define INPUT_MODE_MASK (0x70) +#define SD_INPUT_MODE (0x00) +#define HD_720P_INPUT_MODE (0x10) +#define HD_1080I_INPUT_MODE (0x10) + +/* Bit masks for Mode Register 0 */ +#define TEST_PATTERN_BLACK_BAR_EN (0x04) +#define YUV_OUTPUT_SELECT (0x20) +#define RGB_OUTPUT_SELECT (0xDF) + +/* Bit masks for DAC output levels */ +#define DAC_OUTPUT_LEVEL_MASK (0xFF) + +/* Bit masks for soft reset register */ +#define SOFT_RESET (0x02) + +/* Bit masks for HD Mode Register 1 */ +#define OUTPUT_STD_MASK (0x03) +#define OUTPUT_STD_SHIFT (0) +#define OUTPUT_STD_EIA0_2 (0x00) +#define OUTPUT_STD_EIA0_1 (0x01) +#define OUTPUT_STD_FULL (0x02) +#define EMBEDDED_SYNC (0x04) +#define EXTERNAL_SYNC (0xFB) +#define STD_MODE_SHIFT (3) +#define STD_MODE_MASK (0x1F) +#define STD_MODE_720P (0x05) +#define STD_MODE_720P_25 (0x08) +#define STD_MODE_720P_30 (0x07) +#define STD_MODE_720P_50 (0x06) +#define STD_MODE_1080I (0x0D) +#define STD_MODE_1080I_25fps (0x0E) +#define STD_MODE_1080P_24 (0x12) +#define STD_MODE_1080P_25 (0x10) +#define STD_MODE_1080P_30 (0x0F) +#define STD_MODE_525P (0x00) +#define STD_MODE_625P (0x03) + +/* Bit masks for SD Mode Register 1 */ +#define SD_STD_MASK (0x03) +#define SD_STD_NTSC (0x00) +#define SD_STD_PAL_BDGHI (0x01) +#define SD_STD_PAL_M (0x02) +#define SD_STD_PAL_N (0x03) +#define SD_LUMA_FLTR_MASK (0x7) +#define SD_LUMA_FLTR_SHIFT (0x2) +#define SD_CHROMA_FLTR_MASK (0x7) +#define SD_CHROMA_FLTR_SHIFT (0x5) + +/* Bit masks for SD Mode Register 2 */ +#define SD_PBPR_SSAF_EN (0x01) +#define SD_PBPR_SSAF_DI (0xFE) +#define SD_DAC_1_DI (0xFD) +#define SD_DAC_2_DI (0xFB) +#define SD_PEDESTAL_EN (0x08) +#define SD_PEDESTAL_DI (0xF7) +#define SD_SQUARE_PIXEL_EN (0x10) +#define SD_SQUARE_PIXEL_DI (0xEF) +#define SD_PIXEL_DATA_VALID (0x40) +#define SD_ACTIVE_EDGE_EN (0x80) +#define SD_ACTIVE_EDGE_DI (0x7F) + +/* Bit masks for HD Mode Register 6 */ +#define HD_RGB_INPUT_EN (0x02) +#define HD_RGB_INPUT_DI (0xFD) +#define HD_PBPR_SYNC_EN (0x04) +#define HD_PBPR_SYNC_DI (0xFB) +#define HD_DAC_SWAP_EN (0x08) +#define HD_DAC_SWAP_DI (0xF7) +#define HD_GAMMA_CURVE_A (0xEF) +#define HD_GAMMA_CURVE_B (0x10) +#define HD_GAMMA_EN (0x20) +#define HD_GAMMA_DI (0xDF) +#define HD_ADPT_FLTR_MODEB (0x40) +#define HD_ADPT_FLTR_MODEA (0xBF) +#define HD_ADPT_FLTR_EN (0x80) +#define HD_ADPT_FLTR_DI (0x7F) + +#define ADV7343_BRIGHTNESS_MAX (127) +#define ADV7343_BRIGHTNESS_MIN (0) +#define ADV7343_BRIGHTNESS_DEF (3) +#define ADV7343_HUE_MAX (255) +#define ADV7343_HUE_MIN (0) +#define ADV7343_HUE_DEF (127) +#define ADV7343_GAIN_MAX (64) +#define ADV7343_GAIN_MIN (-64) +#define ADV7343_GAIN_DEF (0) + +#endif diff --git a/drivers/media/i2c/adv7393.c b/drivers/media/i2c/adv7393.c new file mode 100644 index 000000000..6f948ba02 --- /dev/null +++ b/drivers/media/i2c/adv7393.c @@ -0,0 +1,462 @@ +/* + * adv7393 - ADV7393 Video Encoder Driver + * + * The encoder hardware does not support SECAM. + * + * Copyright (C) 2010-2012 ADVANSEE - http://www.advansee.com/ + * Benoît Thébaudeau + * + * Based on ADV7343 driver, + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://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 + +#include "adv7393_regs.h" + +MODULE_DESCRIPTION("ADV7393 video encoder driver"); +MODULE_LICENSE("GPL"); + +static bool debug; +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Debug level 0-1"); + +struct adv7393_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + u8 reg00; + u8 reg01; + u8 reg02; + u8 reg35; + u8 reg80; + u8 reg82; + u32 output; + v4l2_std_id std; +}; + +static inline struct adv7393_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7393_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7393_state, hdl)->sd; +} + +static inline int adv7393_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static const u8 adv7393_init_reg_val[] = { + ADV7393_SOFT_RESET, ADV7393_SOFT_RESET_DEFAULT, + ADV7393_POWER_MODE_REG, ADV7393_POWER_MODE_REG_DEFAULT, + + ADV7393_HD_MODE_REG1, ADV7393_HD_MODE_REG1_DEFAULT, + ADV7393_HD_MODE_REG2, ADV7393_HD_MODE_REG2_DEFAULT, + ADV7393_HD_MODE_REG3, ADV7393_HD_MODE_REG3_DEFAULT, + ADV7393_HD_MODE_REG4, ADV7393_HD_MODE_REG4_DEFAULT, + ADV7393_HD_MODE_REG5, ADV7393_HD_MODE_REG5_DEFAULT, + ADV7393_HD_MODE_REG6, ADV7393_HD_MODE_REG6_DEFAULT, + ADV7393_HD_MODE_REG7, ADV7393_HD_MODE_REG7_DEFAULT, + + ADV7393_SD_MODE_REG1, ADV7393_SD_MODE_REG1_DEFAULT, + ADV7393_SD_MODE_REG2, ADV7393_SD_MODE_REG2_DEFAULT, + ADV7393_SD_MODE_REG3, ADV7393_SD_MODE_REG3_DEFAULT, + ADV7393_SD_MODE_REG4, ADV7393_SD_MODE_REG4_DEFAULT, + ADV7393_SD_MODE_REG5, ADV7393_SD_MODE_REG5_DEFAULT, + ADV7393_SD_MODE_REG6, ADV7393_SD_MODE_REG6_DEFAULT, + ADV7393_SD_MODE_REG7, ADV7393_SD_MODE_REG7_DEFAULT, + ADV7393_SD_MODE_REG8, ADV7393_SD_MODE_REG8_DEFAULT, + + ADV7393_SD_TIMING_REG0, ADV7393_SD_TIMING_REG0_DEFAULT, + + ADV7393_SD_HUE_ADJUST, ADV7393_SD_HUE_ADJUST_DEFAULT, + ADV7393_SD_CGMS_WSS0, ADV7393_SD_CGMS_WSS0_DEFAULT, + ADV7393_SD_BRIGHTNESS_WSS, ADV7393_SD_BRIGHTNESS_WSS_DEFAULT, +}; + +/* + * 2^32 + * FSC(reg) = FSC (HZ) * -------- + * 27000000 + */ +static const struct adv7393_std_info stdinfo[] = { + { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_NTSC, 705268427, V4L2_STD_NTSC_443, + }, { + /* FSC(Hz) = 3,579,545.45 Hz */ + SD_STD_NTSC, 569408542, V4L2_STD_NTSC, + }, { + /* FSC(Hz) = 3,575,611.00 Hz */ + SD_STD_PAL_M, 568782678, V4L2_STD_PAL_M, + }, { + /* FSC(Hz) = 3,582,056.00 Hz */ + SD_STD_PAL_N, 569807903, V4L2_STD_PAL_Nc, + }, { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_PAL_N, 705268427, V4L2_STD_PAL_N, + }, { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_PAL_M, 705268427, V4L2_STD_PAL_60, + }, { + /* FSC(Hz) = 4,433,618.75 Hz */ + SD_STD_PAL_BDGHI, 705268427, V4L2_STD_PAL, + }, +}; + +static int adv7393_setstd(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7393_state *state = to_state(sd); + const struct adv7393_std_info *std_info; + int num_std; + u8 reg; + u32 val; + int err = 0; + int i; + + num_std = ARRAY_SIZE(stdinfo); + + for (i = 0; i < num_std; i++) { + if (stdinfo[i].stdid & std) + break; + } + + if (i == num_std) { + v4l2_dbg(1, debug, sd, + "Invalid std or std is not supported: %llx\n", + (unsigned long long)std); + return -EINVAL; + } + + std_info = &stdinfo[i]; + + /* Set the standard */ + val = state->reg80 & ~SD_STD_MASK; + val |= std_info->standard_val3; + err = adv7393_write(sd, ADV7393_SD_MODE_REG1, val); + if (err < 0) + goto setstd_exit; + + state->reg80 = val; + + /* Configure the input mode register */ + val = state->reg01 & ~INPUT_MODE_MASK; + val |= SD_INPUT_MODE; + err = adv7393_write(sd, ADV7393_MODE_SELECT_REG, val); + if (err < 0) + goto setstd_exit; + + state->reg01 = val; + + /* Program the sub carrier frequency registers */ + val = std_info->fsc_val; + for (reg = ADV7393_FSC_REG0; reg <= ADV7393_FSC_REG3; reg++) { + err = adv7393_write(sd, reg, val); + if (err < 0) + goto setstd_exit; + val >>= 8; + } + + val = state->reg82; + + /* Pedestal settings */ + if (std & (V4L2_STD_NTSC | V4L2_STD_NTSC_443)) + val |= SD_PEDESTAL_EN; + else + val &= SD_PEDESTAL_DI; + + err = adv7393_write(sd, ADV7393_SD_MODE_REG2, val); + if (err < 0) + goto setstd_exit; + + state->reg82 = val; + +setstd_exit: + if (err != 0) + v4l2_err(sd, "Error setting std, write failed\n"); + + return err; +} + +static int adv7393_setoutput(struct v4l2_subdev *sd, u32 output_type) +{ + struct adv7393_state *state = to_state(sd); + u8 val; + int err = 0; + + if (output_type > ADV7393_SVIDEO_ID) { + v4l2_dbg(1, debug, sd, + "Invalid output type or output type not supported:%d\n", + output_type); + return -EINVAL; + } + + /* Enable Appropriate DAC */ + val = state->reg00 & 0x03; + + if (output_type == ADV7393_COMPOSITE_ID) + val |= ADV7393_COMPOSITE_POWER_VALUE; + else if (output_type == ADV7393_COMPONENT_ID) + val |= ADV7393_COMPONENT_POWER_VALUE; + else + val |= ADV7393_SVIDEO_POWER_VALUE; + + err = adv7393_write(sd, ADV7393_POWER_MODE_REG, val); + if (err < 0) + goto setoutput_exit; + + state->reg00 = val; + + /* Enable YUV output */ + val = state->reg02 | YUV_OUTPUT_SELECT; + err = adv7393_write(sd, ADV7393_MODE_REG0, val); + if (err < 0) + goto setoutput_exit; + + state->reg02 = val; + + /* configure SD DAC Output 1 bit */ + val = state->reg82; + if (output_type == ADV7393_COMPONENT_ID) + val &= SD_DAC_OUT1_DI; + else + val |= SD_DAC_OUT1_EN; + err = adv7393_write(sd, ADV7393_SD_MODE_REG2, val); + if (err < 0) + goto setoutput_exit; + + state->reg82 = val; + + /* configure ED/HD Color DAC Swap bit to zero */ + val = state->reg35 & HD_DAC_SWAP_DI; + err = adv7393_write(sd, ADV7393_HD_MODE_REG6, val); + if (err < 0) + goto setoutput_exit; + + state->reg35 = val; + +setoutput_exit: + if (err != 0) + v4l2_err(sd, "Error setting output, write failed\n"); + + return err; +} + +static int adv7393_log_status(struct v4l2_subdev *sd) +{ + struct adv7393_state *state = to_state(sd); + + v4l2_info(sd, "Standard: %llx\n", (unsigned long long)state->std); + v4l2_info(sd, "Output: %s\n", (state->output == 0) ? "Composite" : + ((state->output == 1) ? "Component" : "S-Video")); + return 0; +} + +static int adv7393_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + return adv7393_write(sd, ADV7393_SD_BRIGHTNESS_WSS, + ctrl->val & SD_BRIGHTNESS_VALUE_MASK); + + case V4L2_CID_HUE: + return adv7393_write(sd, ADV7393_SD_HUE_ADJUST, + ctrl->val - ADV7393_HUE_MIN); + + case V4L2_CID_GAIN: + return adv7393_write(sd, ADV7393_DAC123_OUTPUT_LEVEL, + ctrl->val); + } + return -EINVAL; +} + +static const struct v4l2_ctrl_ops adv7393_ctrl_ops = { + .s_ctrl = adv7393_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops adv7393_core_ops = { + .log_status = adv7393_log_status, +}; + +static int adv7393_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7393_state *state = to_state(sd); + int err = 0; + + if (state->std == std) + return 0; + + err = adv7393_setstd(sd, std); + if (!err) + state->std = std; + + return err; +} + +static int adv7393_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7393_state *state = to_state(sd); + int err = 0; + + if (state->output == output) + return 0; + + err = adv7393_setoutput(sd, output); + if (!err) + state->output = output; + + return err; +} + +static const struct v4l2_subdev_video_ops adv7393_video_ops = { + .s_std_output = adv7393_s_std_output, + .s_routing = adv7393_s_routing, +}; + +static const struct v4l2_subdev_ops adv7393_ops = { + .core = &adv7393_core_ops, + .video = &adv7393_video_ops, +}; + +static int adv7393_initialize(struct v4l2_subdev *sd) +{ + struct adv7393_state *state = to_state(sd); + int err = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(adv7393_init_reg_val); i += 2) { + + err = adv7393_write(sd, adv7393_init_reg_val[i], + adv7393_init_reg_val[i+1]); + if (err) { + v4l2_err(sd, "Error initializing\n"); + return err; + } + } + + /* Configure for default video standard */ + err = adv7393_setoutput(sd, state->output); + if (err < 0) { + v4l2_err(sd, "Error setting output during init\n"); + return -EINVAL; + } + + err = adv7393_setstd(sd, state->std); + if (err < 0) { + v4l2_err(sd, "Error setting std during init\n"); + return -EINVAL; + } + + return err; +} + +static int adv7393_probe(struct i2c_client *client) +{ + struct adv7393_state *state; + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + + state->reg00 = ADV7393_POWER_MODE_REG_DEFAULT; + state->reg01 = 0x00; + state->reg02 = 0x20; + state->reg35 = ADV7393_HD_MODE_REG6_DEFAULT; + state->reg80 = ADV7393_SD_MODE_REG1_DEFAULT; + state->reg82 = ADV7393_SD_MODE_REG2_DEFAULT; + + state->output = ADV7393_COMPOSITE_ID; + state->std = V4L2_STD_NTSC; + + v4l2_i2c_subdev_init(&state->sd, client, &adv7393_ops); + + v4l2_ctrl_handler_init(&state->hdl, 3); + v4l2_ctrl_new_std(&state->hdl, &adv7393_ctrl_ops, + V4L2_CID_BRIGHTNESS, ADV7393_BRIGHTNESS_MIN, + ADV7393_BRIGHTNESS_MAX, 1, + ADV7393_BRIGHTNESS_DEF); + v4l2_ctrl_new_std(&state->hdl, &adv7393_ctrl_ops, + V4L2_CID_HUE, ADV7393_HUE_MIN, + ADV7393_HUE_MAX, 1, + ADV7393_HUE_DEF); + v4l2_ctrl_new_std(&state->hdl, &adv7393_ctrl_ops, + V4L2_CID_GAIN, ADV7393_GAIN_MIN, + ADV7393_GAIN_MAX, 1, + ADV7393_GAIN_DEF); + state->sd.ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + return err; + } + v4l2_ctrl_handler_setup(&state->hdl); + + err = adv7393_initialize(&state->sd); + if (err) + v4l2_ctrl_handler_free(&state->hdl); + return err; +} + +static void adv7393_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7393_state *state = to_state(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&state->hdl); +} + +static const struct i2c_device_id adv7393_id[] = { + { .name = "adv7393" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7393_id); + +static struct i2c_driver adv7393_driver = { + .driver = { + .name = "adv7393", + }, + .probe = adv7393_probe, + .remove = adv7393_remove, + .id_table = adv7393_id, +}; +module_i2c_driver(adv7393_driver); diff --git a/drivers/media/i2c/adv7393_regs.h b/drivers/media/i2c/adv7393_regs.h new file mode 100644 index 000000000..6eb8732b5 --- /dev/null +++ b/drivers/media/i2c/adv7393_regs.h @@ -0,0 +1,180 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ADV7393 encoder related structure and register definitions + * + * Copyright (C) 2010-2012 ADVANSEE - http://www.advansee.com/ + * Benoît Thébaudeau + * + * Based on ADV7343 driver, + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + */ + +#ifndef ADV7393_REGS_H +#define ADV7393_REGS_H + +struct adv7393_std_info { + u32 standard_val3; + u32 fsc_val; + v4l2_std_id stdid; +}; + +/* Register offset macros */ +#define ADV7393_POWER_MODE_REG (0x00) +#define ADV7393_MODE_SELECT_REG (0x01) +#define ADV7393_MODE_REG0 (0x02) + +#define ADV7393_DAC123_OUTPUT_LEVEL (0x0B) + +#define ADV7393_SOFT_RESET (0x17) + +#define ADV7393_HD_MODE_REG1 (0x30) +#define ADV7393_HD_MODE_REG2 (0x31) +#define ADV7393_HD_MODE_REG3 (0x32) +#define ADV7393_HD_MODE_REG4 (0x33) +#define ADV7393_HD_MODE_REG5 (0x34) +#define ADV7393_HD_MODE_REG6 (0x35) + +#define ADV7393_HD_MODE_REG7 (0x39) + +#define ADV7393_SD_MODE_REG1 (0x80) +#define ADV7393_SD_MODE_REG2 (0x82) +#define ADV7393_SD_MODE_REG3 (0x83) +#define ADV7393_SD_MODE_REG4 (0x84) +#define ADV7393_SD_MODE_REG5 (0x86) +#define ADV7393_SD_MODE_REG6 (0x87) +#define ADV7393_SD_MODE_REG7 (0x88) +#define ADV7393_SD_MODE_REG8 (0x89) + +#define ADV7393_SD_TIMING_REG0 (0x8A) + +#define ADV7393_FSC_REG0 (0x8C) +#define ADV7393_FSC_REG1 (0x8D) +#define ADV7393_FSC_REG2 (0x8E) +#define ADV7393_FSC_REG3 (0x8F) + +#define ADV7393_SD_CGMS_WSS0 (0x99) + +#define ADV7393_SD_HUE_ADJUST (0xA0) +#define ADV7393_SD_BRIGHTNESS_WSS (0xA1) + +/* Default values for the registers */ +#define ADV7393_POWER_MODE_REG_DEFAULT (0x10) +#define ADV7393_HD_MODE_REG1_DEFAULT (0x3C) /* Changed Default + 720p EAV/SAV code*/ +#define ADV7393_HD_MODE_REG2_DEFAULT (0x01) /* Changed Pixel data + valid */ +#define ADV7393_HD_MODE_REG3_DEFAULT (0x00) /* Color delay 0 clks */ +#define ADV7393_HD_MODE_REG4_DEFAULT (0xEC) /* Changed */ +#define ADV7393_HD_MODE_REG5_DEFAULT (0x08) +#define ADV7393_HD_MODE_REG6_DEFAULT (0x00) +#define ADV7393_HD_MODE_REG7_DEFAULT (0x00) +#define ADV7393_SOFT_RESET_DEFAULT (0x02) +#define ADV7393_COMPOSITE_POWER_VALUE (0x10) +#define ADV7393_COMPONENT_POWER_VALUE (0x1C) +#define ADV7393_SVIDEO_POWER_VALUE (0x0C) +#define ADV7393_SD_HUE_ADJUST_DEFAULT (0x80) +#define ADV7393_SD_BRIGHTNESS_WSS_DEFAULT (0x00) + +#define ADV7393_SD_CGMS_WSS0_DEFAULT (0x10) + +#define ADV7393_SD_MODE_REG1_DEFAULT (0x10) +#define ADV7393_SD_MODE_REG2_DEFAULT (0xC9) +#define ADV7393_SD_MODE_REG3_DEFAULT (0x00) +#define ADV7393_SD_MODE_REG4_DEFAULT (0x00) +#define ADV7393_SD_MODE_REG5_DEFAULT (0x02) +#define ADV7393_SD_MODE_REG6_DEFAULT (0x8C) +#define ADV7393_SD_MODE_REG7_DEFAULT (0x14) +#define ADV7393_SD_MODE_REG8_DEFAULT (0x00) + +#define ADV7393_SD_TIMING_REG0_DEFAULT (0x0C) + +/* Bit masks for Mode Select Register */ +#define INPUT_MODE_MASK (0x70) +#define SD_INPUT_MODE (0x00) +#define HD_720P_INPUT_MODE (0x10) +#define HD_1080I_INPUT_MODE (0x10) + +/* Bit masks for Mode Register 0 */ +#define TEST_PATTERN_BLACK_BAR_EN (0x04) +#define YUV_OUTPUT_SELECT (0x20) +#define RGB_OUTPUT_SELECT (0xDF) + +/* Bit masks for SD brightness/WSS */ +#define SD_BRIGHTNESS_VALUE_MASK (0x7F) +#define SD_BLANK_WSS_DATA_MASK (0x80) + +/* Bit masks for soft reset register */ +#define SOFT_RESET (0x02) + +/* Bit masks for HD Mode Register 1 */ +#define OUTPUT_STD_MASK (0x03) +#define OUTPUT_STD_SHIFT (0) +#define OUTPUT_STD_EIA0_2 (0x00) +#define OUTPUT_STD_EIA0_1 (0x01) +#define OUTPUT_STD_FULL (0x02) +#define EMBEDDED_SYNC (0x04) +#define EXTERNAL_SYNC (0xFB) +#define STD_MODE_MASK (0x1F) +#define STD_MODE_SHIFT (3) +#define STD_MODE_720P (0x05) +#define STD_MODE_720P_25 (0x08) +#define STD_MODE_720P_30 (0x07) +#define STD_MODE_720P_50 (0x06) +#define STD_MODE_1080I (0x0D) +#define STD_MODE_1080I_25 (0x0E) +#define STD_MODE_1080P_24 (0x11) +#define STD_MODE_1080P_25 (0x10) +#define STD_MODE_1080P_30 (0x0F) +#define STD_MODE_525P (0x00) +#define STD_MODE_625P (0x03) + +/* Bit masks for SD Mode Register 1 */ +#define SD_STD_MASK (0x03) +#define SD_STD_NTSC (0x00) +#define SD_STD_PAL_BDGHI (0x01) +#define SD_STD_PAL_M (0x02) +#define SD_STD_PAL_N (0x03) +#define SD_LUMA_FLTR_MASK (0x07) +#define SD_LUMA_FLTR_SHIFT (2) +#define SD_CHROMA_FLTR_MASK (0x07) +#define SD_CHROMA_FLTR_SHIFT (5) + +/* Bit masks for SD Mode Register 2 */ +#define SD_PRPB_SSAF_EN (0x01) +#define SD_PRPB_SSAF_DI (0xFE) +#define SD_DAC_OUT1_EN (0x02) +#define SD_DAC_OUT1_DI (0xFD) +#define SD_PEDESTAL_EN (0x08) +#define SD_PEDESTAL_DI (0xF7) +#define SD_SQUARE_PIXEL_EN (0x10) +#define SD_SQUARE_PIXEL_DI (0xEF) +#define SD_PIXEL_DATA_VALID (0x40) +#define SD_ACTIVE_EDGE_EN (0x80) +#define SD_ACTIVE_EDGE_DI (0x7F) + +/* Bit masks for HD Mode Register 6 */ +#define HD_PRPB_SYNC_EN (0x04) +#define HD_PRPB_SYNC_DI (0xFB) +#define HD_DAC_SWAP_EN (0x08) +#define HD_DAC_SWAP_DI (0xF7) +#define HD_GAMMA_CURVE_A (0xEF) +#define HD_GAMMA_CURVE_B (0x10) +#define HD_GAMMA_EN (0x20) +#define HD_GAMMA_DI (0xDF) +#define HD_ADPT_FLTR_MODEA (0xBF) +#define HD_ADPT_FLTR_MODEB (0x40) +#define HD_ADPT_FLTR_EN (0x80) +#define HD_ADPT_FLTR_DI (0x7F) + +#define ADV7393_BRIGHTNESS_MAX (63) +#define ADV7393_BRIGHTNESS_MIN (-64) +#define ADV7393_BRIGHTNESS_DEF (0) +#define ADV7393_HUE_MAX (127) +#define ADV7393_HUE_MIN (-128) +#define ADV7393_HUE_DEF (0) +#define ADV7393_GAIN_MAX (64) +#define ADV7393_GAIN_MIN (-64) +#define ADV7393_GAIN_DEF (0) + +#endif diff --git a/drivers/media/i2c/adv748x/Makefile b/drivers/media/i2c/adv748x/Makefile new file mode 100644 index 000000000..93844f14c --- /dev/null +++ b/drivers/media/i2c/adv748x/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +adv748x-objs := \ + adv748x-afe.o \ + adv748x-core.o \ + adv748x-csi2.o \ + adv748x-hdmi.o + +obj-$(CONFIG_VIDEO_ADV748X) += adv748x.o diff --git a/drivers/media/i2c/adv748x/adv748x-afe.c b/drivers/media/i2c/adv748x/adv748x-afe.c new file mode 100644 index 000000000..678199196 --- /dev/null +++ b/drivers/media/i2c/adv748x/adv748x-afe.c @@ -0,0 +1,539 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices ADV748X 8 channel analog front end (AFE) receiver + * with standard definition processor (SDP) + * + * Copyright (C) 2017 Renesas Electronics Corp. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "adv748x.h" + +/* ----------------------------------------------------------------------------- + * SDP + */ + +#define ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM 0x0 +#define ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM_PED 0x1 +#define ADV748X_AFE_STD_AD_PAL_N_NTSC_J_SECAM 0x2 +#define ADV748X_AFE_STD_AD_PAL_N_NTSC_M_SECAM 0x3 +#define ADV748X_AFE_STD_NTSC_J 0x4 +#define ADV748X_AFE_STD_NTSC_M 0x5 +#define ADV748X_AFE_STD_PAL60 0x6 +#define ADV748X_AFE_STD_NTSC_443 0x7 +#define ADV748X_AFE_STD_PAL_BG 0x8 +#define ADV748X_AFE_STD_PAL_N 0x9 +#define ADV748X_AFE_STD_PAL_M 0xa +#define ADV748X_AFE_STD_PAL_M_PED 0xb +#define ADV748X_AFE_STD_PAL_COMB_N 0xc +#define ADV748X_AFE_STD_PAL_COMB_N_PED 0xd +#define ADV748X_AFE_STD_PAL_SECAM 0xe +#define ADV748X_AFE_STD_PAL_SECAM_PED 0xf + +static int adv748x_afe_read_ro_map(struct adv748x_state *state, u8 reg) +{ + int ret; + + /* Select SDP Read-Only Main Map */ + ret = sdp_write(state, ADV748X_SDP_MAP_SEL, + ADV748X_SDP_MAP_SEL_RO_MAIN); + if (ret < 0) + return ret; + + return sdp_read(state, reg); +} + +static int adv748x_afe_status(struct adv748x_afe *afe, u32 *signal, + v4l2_std_id *std) +{ + struct adv748x_state *state = adv748x_afe_to_state(afe); + int info; + + /* Read status from reg 0x10 of SDP RO Map */ + info = adv748x_afe_read_ro_map(state, ADV748X_SDP_RO_10); + if (info < 0) + return info; + + if (signal) + *signal = info & ADV748X_SDP_RO_10_IN_LOCK ? + 0 : V4L2_IN_ST_NO_SIGNAL; + + if (!std) + return 0; + + /* Standard not valid if there is no signal */ + if (!(info & ADV748X_SDP_RO_10_IN_LOCK)) { + *std = V4L2_STD_UNKNOWN; + return 0; + } + + switch (info & 0x70) { + case 0x00: + *std = V4L2_STD_NTSC; + break; + case 0x10: + *std = V4L2_STD_NTSC_443; + break; + case 0x20: + *std = V4L2_STD_PAL_M; + break; + case 0x30: + *std = V4L2_STD_PAL_60; + break; + case 0x40: + *std = V4L2_STD_PAL; + break; + case 0x50: + *std = V4L2_STD_SECAM; + break; + case 0x60: + *std = V4L2_STD_PAL_Nc | V4L2_STD_PAL_N; + break; + case 0x70: + *std = V4L2_STD_SECAM; + break; + default: + *std = V4L2_STD_UNKNOWN; + break; + } + + return 0; +} + +static void adv748x_afe_fill_format(struct adv748x_afe *afe, + struct v4l2_mbus_framefmt *fmt) +{ + memset(fmt, 0, sizeof(*fmt)); + + fmt->code = MEDIA_BUS_FMT_UYVY8_1X16; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + fmt->field = V4L2_FIELD_ALTERNATE; + + fmt->width = 720; + fmt->height = afe->curr_norm & V4L2_STD_525_60 ? 480 : 576; + + /* Field height */ + fmt->height /= 2; +} + +static int adv748x_afe_std(v4l2_std_id std) +{ + if (std == V4L2_STD_PAL_60) + return ADV748X_AFE_STD_PAL60; + if (std == V4L2_STD_NTSC_443) + return ADV748X_AFE_STD_NTSC_443; + if (std == V4L2_STD_PAL_N) + return ADV748X_AFE_STD_PAL_N; + if (std == V4L2_STD_PAL_M) + return ADV748X_AFE_STD_PAL_M; + if (std == V4L2_STD_PAL_Nc) + return ADV748X_AFE_STD_PAL_COMB_N; + if (std & V4L2_STD_NTSC) + return ADV748X_AFE_STD_NTSC_M; + if (std & V4L2_STD_PAL) + return ADV748X_AFE_STD_PAL_BG; + if (std & V4L2_STD_SECAM) + return ADV748X_AFE_STD_PAL_SECAM; + + return -EINVAL; +} + +static void adv748x_afe_set_video_standard(struct adv748x_state *state, + int sdpstd) +{ + sdp_clrset(state, ADV748X_SDP_VID_SEL, ADV748X_SDP_VID_SEL_MASK, + (sdpstd & 0xf) << ADV748X_SDP_VID_SEL_SHIFT); +} + +int adv748x_afe_s_input(struct adv748x_afe *afe, unsigned int input) +{ + struct adv748x_state *state = adv748x_afe_to_state(afe); + + return sdp_write(state, ADV748X_SDP_INSEL, input); +} + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_video_ops + */ + +static int adv748x_afe_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm) +{ + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); + + *norm = afe->curr_norm; + + return 0; +} + +static int adv748x_afe_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); + struct adv748x_state *state = adv748x_afe_to_state(afe); + int afe_std = adv748x_afe_std(std); + + if (afe_std < 0) + return afe_std; + + mutex_lock(&state->mutex); + + adv748x_afe_set_video_standard(state, afe_std); + afe->curr_norm = std; + + mutex_unlock(&state->mutex); + + return 0; +} + +static int adv748x_afe_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); + struct adv748x_state *state = adv748x_afe_to_state(afe); + int afe_std; + int ret; + + mutex_lock(&state->mutex); + + if (afe->streaming) { + ret = -EBUSY; + goto unlock; + } + + /* Set auto detect mode */ + adv748x_afe_set_video_standard(state, + ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM); + + msleep(100); + + /* Read detected standard */ + ret = adv748x_afe_status(afe, NULL, std); + + afe_std = adv748x_afe_std(afe->curr_norm); + if (afe_std < 0) + goto unlock; + + /* Restore original state */ + adv748x_afe_set_video_standard(state, afe_std); + +unlock: + mutex_unlock(&state->mutex); + + return ret; +} + +static int adv748x_afe_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *norm) +{ + *norm = V4L2_STD_ALL; + + return 0; +} + +static int adv748x_afe_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); + struct adv748x_state *state = adv748x_afe_to_state(afe); + int ret; + + mutex_lock(&state->mutex); + + ret = adv748x_afe_status(afe, status, NULL); + + mutex_unlock(&state->mutex); + + return ret; +} + +static int adv748x_afe_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); + struct adv748x_state *state = adv748x_afe_to_state(afe); + u32 signal = V4L2_IN_ST_NO_SIGNAL; + int ret; + + mutex_lock(&state->mutex); + + if (enable) { + ret = adv748x_afe_s_input(afe, afe->input); + if (ret) + goto unlock; + } + + ret = adv748x_tx_power(afe->tx, enable); + if (ret) + goto unlock; + + afe->streaming = enable; + + adv748x_afe_status(afe, &signal, NULL); + if (signal != V4L2_IN_ST_NO_SIGNAL) + adv_dbg(state, "Detected SDP signal\n"); + else + adv_dbg(state, "Couldn't detect SDP video signal\n"); + +unlock: + mutex_unlock(&state->mutex); + + return ret; +} + +static const struct v4l2_subdev_video_ops adv748x_afe_video_ops = { + .g_std = adv748x_afe_g_std, + .s_std = adv748x_afe_s_std, + .querystd = adv748x_afe_querystd, + .g_tvnorms = adv748x_afe_g_tvnorms, + .g_input_status = adv748x_afe_g_input_status, + .s_stream = adv748x_afe_s_stream, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_pad_ops + */ + +static int adv748x_afe_propagate_pixelrate(struct adv748x_afe *afe) +{ + struct v4l2_subdev *tx; + + tx = adv748x_get_remote_sd(&afe->pads[ADV748X_AFE_SOURCE]); + if (!tx) + return -ENOLINK; + + /* + * The ADV748x ADC sampling frequency is twice the externally supplied + * clock whose frequency is required to be 28.63636 MHz. It oversamples + * with a factor of 4 resulting in a pixel rate of 14.3180180 MHz. + */ + return adv748x_csi2_set_pixelrate(tx, 14318180); +} + +static int adv748x_afe_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_UYVY8_1X16; + + return 0; +} + +static int adv748x_afe_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat) +{ + struct adv748x_afe *afe = adv748x_sd_to_afe(sd); + struct v4l2_mbus_framefmt *mbusformat; + + /* It makes no sense to get the format of the analog sink pads */ + if (sdformat->pad != ADV748X_AFE_SOURCE) + return -EINVAL; + + if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) { + mbusformat = v4l2_subdev_state_get_format(sd_state, + sdformat->pad); + sdformat->format = *mbusformat; + } else { + adv748x_afe_fill_format(afe, &sdformat->format); + adv748x_afe_propagate_pixelrate(afe); + } + + return 0; +} + +static int adv748x_afe_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat) +{ + struct v4l2_mbus_framefmt *mbusformat; + + /* It makes no sense to get the format of the analog sink pads */ + if (sdformat->pad != ADV748X_AFE_SOURCE) + return -EINVAL; + + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return adv748x_afe_get_format(sd, sd_state, sdformat); + + mbusformat = v4l2_subdev_state_get_format(sd_state, sdformat->pad); + *mbusformat = sdformat->format; + + return 0; +} + +static const struct v4l2_subdev_pad_ops adv748x_afe_pad_ops = { + .enum_mbus_code = adv748x_afe_enum_mbus_code, + .set_fmt = adv748x_afe_set_format, + .get_fmt = adv748x_afe_get_format, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_ops + */ + +static const struct v4l2_subdev_ops adv748x_afe_ops = { + .video = &adv748x_afe_video_ops, + .pad = &adv748x_afe_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Controls + */ + +static const char * const afe_ctrl_frp_menu[] = { + "Disabled", + "Solid Blue", + "Color Bars", + "Grey Ramp", + "Cb Ramp", + "Cr Ramp", + "Boundary" +}; + +static int adv748x_afe_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct adv748x_afe *afe = adv748x_ctrl_to_afe(ctrl); + struct adv748x_state *state = adv748x_afe_to_state(afe); + bool enable; + int ret; + + ret = sdp_write(state, 0x0e, 0x00); + if (ret < 0) + return ret; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ret = sdp_write(state, ADV748X_SDP_BRI, ctrl->val); + break; + case V4L2_CID_HUE: + /* Hue is inverted according to HSL chart */ + ret = sdp_write(state, ADV748X_SDP_HUE, -ctrl->val); + break; + case V4L2_CID_CONTRAST: + ret = sdp_write(state, ADV748X_SDP_CON, ctrl->val); + break; + case V4L2_CID_SATURATION: + ret = sdp_write(state, ADV748X_SDP_SD_SAT_U, ctrl->val); + if (ret) + break; + ret = sdp_write(state, ADV748X_SDP_SD_SAT_V, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + enable = !!ctrl->val; + + /* Enable/Disable Color bar test patterns */ + ret = sdp_clrset(state, ADV748X_SDP_DEF, ADV748X_SDP_DEF_VAL_EN, + enable); + if (ret) + break; + ret = sdp_clrset(state, ADV748X_SDP_FRP, ADV748X_SDP_FRP_MASK, + enable ? ctrl->val - 1 : 0); + break; + default: + return -EINVAL; + } + + return ret; +} + +static const struct v4l2_ctrl_ops adv748x_afe_ctrl_ops = { + .s_ctrl = adv748x_afe_s_ctrl, +}; + +static int adv748x_afe_init_controls(struct adv748x_afe *afe) +{ + struct adv748x_state *state = adv748x_afe_to_state(afe); + + v4l2_ctrl_handler_init(&afe->ctrl_hdl, 5); + + /* Use our mutex for the controls */ + afe->ctrl_hdl.lock = &state->mutex; + + v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, + V4L2_CID_BRIGHTNESS, ADV748X_SDP_BRI_MIN, + ADV748X_SDP_BRI_MAX, 1, ADV748X_SDP_BRI_DEF); + v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, + V4L2_CID_CONTRAST, ADV748X_SDP_CON_MIN, + ADV748X_SDP_CON_MAX, 1, ADV748X_SDP_CON_DEF); + v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, + V4L2_CID_SATURATION, ADV748X_SDP_SAT_MIN, + ADV748X_SDP_SAT_MAX, 1, ADV748X_SDP_SAT_DEF); + v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, + V4L2_CID_HUE, ADV748X_SDP_HUE_MIN, + ADV748X_SDP_HUE_MAX, 1, ADV748X_SDP_HUE_DEF); + + v4l2_ctrl_new_std_menu_items(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(afe_ctrl_frp_menu) - 1, + 0, 0, afe_ctrl_frp_menu); + + afe->sd.ctrl_handler = &afe->ctrl_hdl; + if (afe->ctrl_hdl.error) { + v4l2_ctrl_handler_free(&afe->ctrl_hdl); + return afe->ctrl_hdl.error; + } + + return v4l2_ctrl_handler_setup(&afe->ctrl_hdl); +} + +int adv748x_afe_init(struct adv748x_afe *afe) +{ + struct adv748x_state *state = adv748x_afe_to_state(afe); + int ret; + unsigned int i; + + afe->input = 0; + afe->streaming = false; + afe->curr_norm = V4L2_STD_NTSC_M; + + adv748x_subdev_init(&afe->sd, state, &adv748x_afe_ops, + MEDIA_ENT_F_ATV_DECODER, "afe"); + + /* Identify the first connector found as a default input if set */ + for (i = ADV748X_PORT_AIN0; i <= ADV748X_PORT_AIN7; i++) { + /* Inputs and ports are 1-indexed to match the data sheet */ + if (state->endpoints[i]) { + afe->input = i; + break; + } + } + + adv748x_afe_s_input(afe, afe->input); + + adv_dbg(state, "AFE Default input set to %d\n", afe->input); + + /* Entity pads and sinks are 0-indexed to match the pads */ + for (i = ADV748X_AFE_SINK_AIN0; i <= ADV748X_AFE_SINK_AIN7; i++) + afe->pads[i].flags = MEDIA_PAD_FL_SINK; + + afe->pads[ADV748X_AFE_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&afe->sd.entity, ADV748X_AFE_NR_PADS, + afe->pads); + if (ret) + return ret; + + ret = adv748x_afe_init_controls(afe); + if (ret) + goto error; + + return 0; + +error: + media_entity_cleanup(&afe->sd.entity); + + return ret; +} + +void adv748x_afe_cleanup(struct adv748x_afe *afe) +{ + v4l2_device_unregister_subdev(&afe->sd); + media_entity_cleanup(&afe->sd.entity); + v4l2_ctrl_handler_free(&afe->ctrl_hdl); +} diff --git a/drivers/media/i2c/adv748x/adv748x-core.c b/drivers/media/i2c/adv748x/adv748x-core.c new file mode 100644 index 000000000..3eb6d5e8f --- /dev/null +++ b/drivers/media/i2c/adv748x/adv748x-core.c @@ -0,0 +1,858 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices ADV748X HDMI receiver with AFE + * + * Copyright (C) 2017 Renesas Electronics Corp. + * + * Authors: + * Koji Matsuoka + * Niklas Söderlund + * Kieran Bingham + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "adv748x.h" + +/* ----------------------------------------------------------------------------- + * Register manipulation + */ + +#define ADV748X_REGMAP_CONF(n) \ +{ \ + .name = n, \ + .reg_bits = 8, \ + .val_bits = 8, \ + .max_register = 0xff, \ + .cache_type = REGCACHE_NONE, \ +} + +static const struct regmap_config adv748x_regmap_cnf[] = { + ADV748X_REGMAP_CONF("io"), + ADV748X_REGMAP_CONF("dpll"), + ADV748X_REGMAP_CONF("cp"), + ADV748X_REGMAP_CONF("hdmi"), + ADV748X_REGMAP_CONF("edid"), + ADV748X_REGMAP_CONF("repeater"), + ADV748X_REGMAP_CONF("infoframe"), + ADV748X_REGMAP_CONF("cbus"), + ADV748X_REGMAP_CONF("cec"), + ADV748X_REGMAP_CONF("sdp"), + ADV748X_REGMAP_CONF("txa"), + ADV748X_REGMAP_CONF("txb"), +}; + +static int adv748x_configure_regmap(struct adv748x_state *state, int region) +{ + int err; + + if (!state->i2c_clients[region]) + return -ENODEV; + + state->regmap[region] = + devm_regmap_init_i2c(state->i2c_clients[region], + &adv748x_regmap_cnf[region]); + + if (IS_ERR(state->regmap[region])) { + err = PTR_ERR(state->regmap[region]); + adv_err(state, + "Error initializing regmap %d with error %d\n", + region, err); + return -EINVAL; + } + + return 0; +} +struct adv748x_register_map { + const char *name; + u8 default_addr; +}; + +static const struct adv748x_register_map adv748x_default_addresses[] = { + [ADV748X_PAGE_IO] = { "main", 0x70 }, + [ADV748X_PAGE_DPLL] = { "dpll", 0x26 }, + [ADV748X_PAGE_CP] = { "cp", 0x22 }, + [ADV748X_PAGE_HDMI] = { "hdmi", 0x34 }, + [ADV748X_PAGE_EDID] = { "edid", 0x36 }, + [ADV748X_PAGE_REPEATER] = { "repeater", 0x32 }, + [ADV748X_PAGE_INFOFRAME] = { "infoframe", 0x31 }, + [ADV748X_PAGE_CBUS] = { "cbus", 0x30 }, + [ADV748X_PAGE_CEC] = { "cec", 0x41 }, + [ADV748X_PAGE_SDP] = { "sdp", 0x79 }, + [ADV748X_PAGE_TXB] = { "txb", 0x48 }, + [ADV748X_PAGE_TXA] = { "txa", 0x4a }, +}; + +static int adv748x_read_check(struct adv748x_state *state, + int client_page, u8 reg) +{ + struct i2c_client *client = state->i2c_clients[client_page]; + int err; + unsigned int val; + + err = regmap_read(state->regmap[client_page], reg, &val); + + if (err) { + adv_err(state, "error reading %02x, %02x\n", + client->addr, reg); + return err; + } + + return val; +} + +int adv748x_read(struct adv748x_state *state, u8 page, u8 reg) +{ + return adv748x_read_check(state, page, reg); +} + +int adv748x_write(struct adv748x_state *state, u8 page, u8 reg, u8 value) +{ + return regmap_write(state->regmap[page], reg, value); +} + +static int adv748x_write_check(struct adv748x_state *state, u8 page, u8 reg, + u8 value, int *error) +{ + if (*error) + return *error; + + *error = adv748x_write(state, page, reg, value); + return *error; +} + +/* adv748x_write_block(): Write raw data with a maximum of I2C_SMBUS_BLOCK_MAX + * size to one or more registers. + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int adv748x_write_block(struct adv748x_state *state, int client_page, + unsigned int init_reg, const void *val, + size_t val_len) +{ + struct regmap *regmap = state->regmap[client_page]; + + if (val_len > I2C_SMBUS_BLOCK_MAX) + val_len = I2C_SMBUS_BLOCK_MAX; + + return regmap_raw_write(regmap, init_reg, val, val_len); +} + +static int adv748x_set_slave_addresses(struct adv748x_state *state) +{ + struct i2c_client *client; + unsigned int i; + u8 io_reg; + + for (i = ADV748X_PAGE_DPLL; i < ADV748X_PAGE_MAX; ++i) { + io_reg = ADV748X_IO_SLAVE_ADDR_BASE + i; + client = state->i2c_clients[i]; + + io_write(state, io_reg, client->addr << 1); + } + + return 0; +} + +static void adv748x_unregister_clients(struct adv748x_state *state) +{ + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(state->i2c_clients); ++i) + i2c_unregister_device(state->i2c_clients[i]); +} + +static int adv748x_initialise_clients(struct adv748x_state *state) +{ + unsigned int i; + int ret; + + for (i = ADV748X_PAGE_DPLL; i < ADV748X_PAGE_MAX; ++i) { + state->i2c_clients[i] = i2c_new_ancillary_device( + state->client, + adv748x_default_addresses[i].name, + adv748x_default_addresses[i].default_addr); + + if (IS_ERR(state->i2c_clients[i])) { + adv_err(state, "failed to create i2c client %u\n", i); + return PTR_ERR(state->i2c_clients[i]); + } + + ret = adv748x_configure_regmap(state, i); + if (ret) + return ret; + } + + return 0; +} + +/** + * struct adv748x_reg_value - Register write instruction + * @page: Regmap page identifier + * @reg: I2C register + * @value: value to write to @page at @reg + */ +struct adv748x_reg_value { + u8 page; + u8 reg; + u8 value; +}; + +static int adv748x_write_regs(struct adv748x_state *state, + const struct adv748x_reg_value *regs) +{ + int ret; + + for (; regs->page != ADV748X_PAGE_EOR; regs++) { + ret = adv748x_write(state, regs->page, regs->reg, regs->value); + if (ret < 0) { + adv_err(state, "Error regs page: 0x%02x reg: 0x%02x\n", + regs->page, regs->reg); + return ret; + } + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * TXA and TXB + */ + +static int adv748x_power_up_tx(struct adv748x_csi2 *tx) +{ + struct adv748x_state *state = tx->state; + u8 page = is_txa(tx) ? ADV748X_PAGE_TXA : ADV748X_PAGE_TXB; + int ret = 0; + + /* Enable n-lane MIPI */ + adv748x_write_check(state, page, 0x00, 0x80 | tx->active_lanes, &ret); + + /* Set Auto DPHY Timing */ + adv748x_write_check(state, page, 0x00, 0xa0 | tx->active_lanes, &ret); + + /* ADI Required Write */ + if (tx->src == &state->hdmi.sd) { + adv748x_write_check(state, page, 0xdb, 0x10, &ret); + adv748x_write_check(state, page, 0xd6, 0x07, &ret); + } else { + adv748x_write_check(state, page, 0xd2, 0x40, &ret); + } + + adv748x_write_check(state, page, 0xc4, 0x0a, &ret); + adv748x_write_check(state, page, 0x71, 0x33, &ret); + adv748x_write_check(state, page, 0x72, 0x11, &ret); + + /* i2c_dphy_pwdn - 1'b0 */ + adv748x_write_check(state, page, 0xf0, 0x00, &ret); + + /* ADI Required Writes*/ + adv748x_write_check(state, page, 0x31, 0x82, &ret); + adv748x_write_check(state, page, 0x1e, 0x40, &ret); + + /* i2c_mipi_pll_en - 1'b1 */ + adv748x_write_check(state, page, 0xda, 0x01, &ret); + usleep_range(2000, 2500); + + /* Power-up CSI-TX */ + adv748x_write_check(state, page, 0x00, 0x20 | tx->active_lanes, &ret); + usleep_range(1000, 1500); + + /* ADI Required Writes */ + adv748x_write_check(state, page, 0xc1, 0x2b, &ret); + usleep_range(1000, 1500); + adv748x_write_check(state, page, 0x31, 0x80, &ret); + + return ret; +} + +static int adv748x_power_down_tx(struct adv748x_csi2 *tx) +{ + struct adv748x_state *state = tx->state; + u8 page = is_txa(tx) ? ADV748X_PAGE_TXA : ADV748X_PAGE_TXB; + int ret = 0; + + /* ADI Required Writes */ + adv748x_write_check(state, page, 0x31, 0x82, &ret); + adv748x_write_check(state, page, 0x1e, 0x00, &ret); + + /* Enable n-lane MIPI */ + adv748x_write_check(state, page, 0x00, 0x80 | tx->active_lanes, &ret); + + /* i2c_mipi_pll_en - 1'b1 */ + adv748x_write_check(state, page, 0xda, 0x01, &ret); + + /* ADI Required Write */ + adv748x_write_check(state, page, 0xc1, 0x3b, &ret); + + return ret; +} + +int adv748x_tx_power(struct adv748x_csi2 *tx, bool on) +{ + int val; + + if (!is_tx_enabled(tx)) + return 0; + + val = tx_read(tx, ADV748X_CSI_FS_AS_LS); + if (val < 0) + return val; + + /* + * This test against BIT(6) is not documented by the datasheet, but was + * specified in the downstream driver. + * Track with a WARN_ONCE to determine if it is ever set by HW. + */ + WARN_ONCE((on && val & ADV748X_CSI_FS_AS_LS_UNKNOWN), + "Enabling with unknown bit set"); + + return on ? adv748x_power_up_tx(tx) : adv748x_power_down_tx(tx); +} + +/* ----------------------------------------------------------------------------- + * Media Operations + */ +static int adv748x_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *rsd = media_entity_to_v4l2_subdev(remote->entity); + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct adv748x_state *state = v4l2_get_subdevdata(sd); + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + bool enable = flags & MEDIA_LNK_FL_ENABLED; + u8 io10_mask = ADV748X_IO_10_CSI1_EN | + ADV748X_IO_10_CSI4_EN | + ADV748X_IO_10_CSI4_IN_SEL_AFE; + u8 io10 = 0; + + /* Refuse to enable multiple links to the same TX at the same time. */ + if (enable && tx->src) + return -EINVAL; + + /* Set or clear the source (HDMI or AFE) and the current TX. */ + if (rsd == &state->afe.sd) + state->afe.tx = enable ? tx : NULL; + else + state->hdmi.tx = enable ? tx : NULL; + + tx->src = enable ? rsd : NULL; + + if (state->afe.tx) { + /* AFE Requires TXA enabled, even when output to TXB */ + io10 |= ADV748X_IO_10_CSI4_EN; + if (is_txa(tx)) { + /* + * Output from the SD-core (480i and 576i) from the TXA + * interface requires reducing the number of enabled + * data lanes in order to guarantee a valid link + * frequency. + */ + tx->active_lanes = min(tx->num_lanes, 2U); + io10 |= ADV748X_IO_10_CSI4_IN_SEL_AFE; + } else { + /* TXB has a single data lane, no need to adjust. */ + io10 |= ADV748X_IO_10_CSI1_EN; + } + } + + if (state->hdmi.tx) { + /* + * Restore the number of active lanes, in case we have gone + * through an AFE->TXA streaming sessions. + */ + tx->active_lanes = tx->num_lanes; + io10 |= ADV748X_IO_10_CSI4_EN; + } + + return io_clrset(state, ADV748X_IO_10, io10_mask, io10); +} + +static const struct media_entity_operations adv748x_tx_media_ops = { + .link_setup = adv748x_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct media_entity_operations adv748x_media_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* ----------------------------------------------------------------------------- + * HW setup + */ + +/* Initialize CP Core with RGB888 format. */ +static const struct adv748x_reg_value adv748x_init_hdmi[] = { + /* Disable chip powerdown & Enable HDMI Rx block */ + {ADV748X_PAGE_IO, 0x00, 0x40}, + + {ADV748X_PAGE_REPEATER, 0x40, 0x83}, /* Enable HDCP 1.1 */ + + {ADV748X_PAGE_HDMI, 0x00, 0x08},/* Foreground Channel = A */ + {ADV748X_PAGE_HDMI, 0x98, 0xff},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x99, 0xa3},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x9a, 0x00},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x9b, 0x0a},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x9d, 0x40},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0xcb, 0x09},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x3d, 0x10},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x3e, 0x7b},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x3f, 0x5e},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x4e, 0xfe},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x4f, 0x18},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x57, 0xa3},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x58, 0x04},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0x85, 0x10},/* ADI Required Write */ + + {ADV748X_PAGE_HDMI, 0x83, 0x00},/* Enable All Terminations */ + {ADV748X_PAGE_HDMI, 0xa3, 0x01},/* ADI Required Write */ + {ADV748X_PAGE_HDMI, 0xbe, 0x00},/* ADI Required Write */ + + {ADV748X_PAGE_HDMI, 0x6c, 0x01},/* HPA Manual Enable */ + {ADV748X_PAGE_HDMI, 0xf8, 0x01},/* HPA Asserted */ + {ADV748X_PAGE_HDMI, 0x0f, 0x00},/* Audio Mute Speed Set to Fastest */ + /* (Smallest Step Size) */ + + {ADV748X_PAGE_IO, 0x04, 0x02}, /* RGB Out of CP */ + {ADV748X_PAGE_IO, 0x12, 0xf0}, /* CSC Depends on ip Packets, SDR 444 */ + {ADV748X_PAGE_IO, 0x17, 0x80}, /* Luma & Chroma can reach 254d */ + {ADV748X_PAGE_IO, 0x03, 0x86}, /* CP-Insert_AV_Code */ + + {ADV748X_PAGE_CP, 0x7c, 0x00}, /* ADI Required Write */ + + {ADV748X_PAGE_IO, 0x0c, 0xe0}, /* Enable LLC_DLL & Double LLC Timing */ + {ADV748X_PAGE_IO, 0x0e, 0xdd}, /* LLC/PIX/SPI PINS TRISTATED AUD */ + + {ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */ +}; + +/* Initialize AFE core with YUV8 format. */ +static const struct adv748x_reg_value adv748x_init_afe[] = { + {ADV748X_PAGE_IO, 0x00, 0x30}, /* Disable chip powerdown Rx */ + {ADV748X_PAGE_IO, 0xf2, 0x01}, /* Enable I2C Read Auto-Increment */ + + {ADV748X_PAGE_IO, 0x0e, 0xff}, /* LLC/PIX/AUD/SPI PINS TRISTATED */ + + {ADV748X_PAGE_SDP, 0x0f, 0x00}, /* Exit Power Down Mode */ + {ADV748X_PAGE_SDP, 0x52, 0xcd}, /* ADI Required Write */ + + {ADV748X_PAGE_SDP, 0x0e, 0x80}, /* ADI Required Write */ + {ADV748X_PAGE_SDP, 0x9c, 0x00}, /* ADI Required Write */ + {ADV748X_PAGE_SDP, 0x9c, 0xff}, /* ADI Required Write */ + {ADV748X_PAGE_SDP, 0x0e, 0x00}, /* ADI Required Write */ + + /* ADI recommended writes for improved video quality */ + {ADV748X_PAGE_SDP, 0x80, 0x51}, /* ADI Required Write */ + {ADV748X_PAGE_SDP, 0x81, 0x51}, /* ADI Required Write */ + {ADV748X_PAGE_SDP, 0x82, 0x68}, /* ADI Required Write */ + + {ADV748X_PAGE_SDP, 0x03, 0x42}, /* Tri-S Output , PwrDwn 656 pads */ + {ADV748X_PAGE_SDP, 0x04, 0xb5}, /* ITU-R BT.656-4 compatible */ + {ADV748X_PAGE_SDP, 0x13, 0x00}, /* ADI Required Write */ + + {ADV748X_PAGE_SDP, 0x17, 0x41}, /* Select SH1 */ + {ADV748X_PAGE_SDP, 0x31, 0x12}, /* ADI Required Write */ + {ADV748X_PAGE_SDP, 0xe6, 0x4f}, /* V bit end pos manually in NTSC */ + + {ADV748X_PAGE_EOR, 0xff, 0xff} /* End of register table */ +}; + +static int adv748x_sw_reset(struct adv748x_state *state) +{ + int ret; + + ret = io_write(state, ADV748X_IO_REG_FF, ADV748X_IO_REG_FF_MAIN_RESET); + if (ret) + return ret; + + usleep_range(5000, 6000); + + /* Disable CEC Wakeup from power-down mode */ + ret = io_clrset(state, ADV748X_IO_REG_01, ADV748X_IO_REG_01_PWRDN_MASK, + ADV748X_IO_REG_01_PWRDNB); + if (ret) + return ret; + + /* Enable I2C Read Auto-Increment for consecutive reads */ + return io_write(state, ADV748X_IO_REG_F2, + ADV748X_IO_REG_F2_READ_AUTO_INC); +} + +static int adv748x_reset(struct adv748x_state *state) +{ + int ret; + u8 regval = 0; + + ret = adv748x_sw_reset(state); + if (ret < 0) + return ret; + + ret = adv748x_set_slave_addresses(state); + if (ret < 0) + return ret; + + /* Initialize CP and AFE cores. */ + ret = adv748x_write_regs(state, adv748x_init_hdmi); + if (ret) + return ret; + + ret = adv748x_write_regs(state, adv748x_init_afe); + if (ret) + return ret; + + adv748x_afe_s_input(&state->afe, state->afe.input); + + adv_dbg(state, "AFE Default input set to %d\n", state->afe.input); + + /* Reset TXA and TXB */ + adv748x_tx_power(&state->txa, 1); + adv748x_tx_power(&state->txa, 0); + adv748x_tx_power(&state->txb, 1); + adv748x_tx_power(&state->txb, 0); + + /* Disable chip powerdown & Enable HDMI Rx block */ + io_write(state, ADV748X_IO_PD, ADV748X_IO_PD_RX_EN); + + /* Conditionally enable TXa and TXb. */ + if (is_tx_enabled(&state->txa)) { + regval |= ADV748X_IO_10_CSI4_EN; + adv748x_csi2_set_virtual_channel(&state->txa, 0); + } + if (is_tx_enabled(&state->txb)) { + regval |= ADV748X_IO_10_CSI1_EN; + adv748x_csi2_set_virtual_channel(&state->txb, 0); + } + io_write(state, ADV748X_IO_10, regval); + + /* Use vid_std and v_freq as freerun resolution for CP */ + cp_clrset(state, ADV748X_CP_CLMP_POS, ADV748X_CP_CLMP_POS_DIS_AUTO, + ADV748X_CP_CLMP_POS_DIS_AUTO); + + return 0; +} + +static int adv748x_identify_chip(struct adv748x_state *state) +{ + int msb, lsb; + + lsb = io_read(state, ADV748X_IO_CHIP_REV_ID_1); + msb = io_read(state, ADV748X_IO_CHIP_REV_ID_2); + + if (lsb < 0 || msb < 0) { + adv_err(state, "Failed to read chip revision\n"); + return -EIO; + } + + adv_info(state, "chip found @ 0x%02x revision %02x%02x\n", + state->client->addr << 1, lsb, msb); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Suspend / Resume + */ + +static int __maybe_unused adv748x_resume_early(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct adv748x_state *state = i2c_get_clientdata(client); + + return adv748x_reset(state); +} + +/* ----------------------------------------------------------------------------- + * i2c driver + */ + +void adv748x_subdev_init(struct v4l2_subdev *sd, struct adv748x_state *state, + const struct v4l2_subdev_ops *ops, u32 function, + const char *ident) +{ + v4l2_subdev_init(sd, ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + /* the owner is the same as the i2c_client's driver owner */ + sd->owner = state->dev->driver->owner; + sd->dev = state->dev; + + v4l2_set_subdevdata(sd, state); + + /* initialize name */ + snprintf(sd->name, sizeof(sd->name), "%s %d-%04x %s", + state->dev->driver->name, + i2c_adapter_id(state->client->adapter), + state->client->addr, ident); + + sd->entity.function = function; + sd->entity.ops = is_tx(adv748x_sd_to_csi2(sd)) ? + &adv748x_tx_media_ops : &adv748x_media_ops; +} + +static int adv748x_parse_csi2_lanes(struct adv748x_state *state, + unsigned int port, + struct device_node *ep) +{ + struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY }; + unsigned int num_lanes; + int ret; + + if (port != ADV748X_PORT_TXA && port != ADV748X_PORT_TXB) + return 0; + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &vep); + if (ret) + return ret; + + num_lanes = vep.bus.mipi_csi2.num_data_lanes; + + if (vep.base.port == ADV748X_PORT_TXA) { + if (num_lanes != 1 && num_lanes != 2 && num_lanes != 4) { + adv_err(state, "TXA: Invalid number (%u) of lanes\n", + num_lanes); + return -EINVAL; + } + + state->txa.num_lanes = num_lanes; + state->txa.active_lanes = num_lanes; + adv_dbg(state, "TXA: using %u lanes\n", state->txa.num_lanes); + } + + if (vep.base.port == ADV748X_PORT_TXB) { + if (num_lanes != 1) { + adv_err(state, "TXB: Invalid number (%u) of lanes\n", + num_lanes); + return -EINVAL; + } + + state->txb.num_lanes = num_lanes; + state->txb.active_lanes = num_lanes; + adv_dbg(state, "TXB: using %u lanes\n", state->txb.num_lanes); + } + + return 0; +} + +static int adv748x_parse_dt(struct adv748x_state *state) +{ + struct device_node *ep_np = NULL; + struct of_endpoint ep; + bool out_found = false; + bool in_found = false; + int ret; + + for_each_endpoint_of_node(state->dev->of_node, ep_np) { + of_graph_parse_endpoint(ep_np, &ep); + adv_info(state, "Endpoint %pOF on port %d", ep.local_node, + ep.port); + + if (ep.port >= ADV748X_PORT_MAX) { + adv_err(state, "Invalid endpoint %pOF on port %d", + ep.local_node, ep.port); + + continue; + } + + if (state->endpoints[ep.port]) { + adv_err(state, + "Multiple port endpoints are not supported"); + continue; + } + + of_node_get(ep_np); + state->endpoints[ep.port] = ep_np; + + /* + * At least one input endpoint and one output endpoint shall + * be defined. + */ + if (ep.port < ADV748X_PORT_TXA) + in_found = true; + else + out_found = true; + + /* Store number of CSI-2 lanes used for TXA and TXB. */ + ret = adv748x_parse_csi2_lanes(state, ep.port, ep_np); + if (ret) + return ret; + } + + return in_found && out_found ? 0 : -ENODEV; +} + +static void adv748x_dt_cleanup(struct adv748x_state *state) +{ + unsigned int i; + + for (i = 0; i < ADV748X_PORT_MAX; i++) + of_node_put(state->endpoints[i]); +} + +static int adv748x_probe(struct i2c_client *client) +{ + struct adv748x_state *state; + int ret; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + mutex_init(&state->mutex); + + state->dev = &client->dev; + state->client = client; + state->i2c_clients[ADV748X_PAGE_IO] = client; + i2c_set_clientdata(client, state); + + /* + * We can not use container_of to get back to the state with two TXs; + * Initialize the TXs's fields unconditionally on the endpoint + * presence to access them later. + */ + state->txa.state = state->txb.state = state; + state->txa.page = ADV748X_PAGE_TXA; + state->txb.page = ADV748X_PAGE_TXB; + state->txa.port = ADV748X_PORT_TXA; + state->txb.port = ADV748X_PORT_TXB; + + /* Discover and process ports declared by the Device tree endpoints */ + ret = adv748x_parse_dt(state); + if (ret) { + adv_err(state, "Failed to parse device tree"); + goto err_free_mutex; + } + + /* Configure IO Regmap region */ + ret = adv748x_configure_regmap(state, ADV748X_PAGE_IO); + if (ret) { + adv_err(state, "Error configuring IO regmap region"); + goto err_cleanup_dt; + } + + ret = adv748x_identify_chip(state); + if (ret) { + adv_err(state, "Failed to identify chip"); + goto err_cleanup_dt; + } + + /* Configure remaining pages as I2C clients with regmap access */ + ret = adv748x_initialise_clients(state); + if (ret) { + adv_err(state, "Failed to setup client regmap pages"); + goto err_cleanup_clients; + } + + /* SW reset ADV748X to its default values */ + ret = adv748x_reset(state); + if (ret) { + adv_err(state, "Failed to reset hardware"); + goto err_cleanup_clients; + } + + /* Initialise HDMI */ + ret = adv748x_hdmi_init(&state->hdmi); + if (ret) { + adv_err(state, "Failed to probe HDMI"); + goto err_cleanup_clients; + } + + /* Initialise AFE */ + ret = adv748x_afe_init(&state->afe); + if (ret) { + adv_err(state, "Failed to probe AFE"); + goto err_cleanup_hdmi; + } + + /* Initialise TXA */ + ret = adv748x_csi2_init(state, &state->txa); + if (ret) { + adv_err(state, "Failed to probe TXA"); + goto err_cleanup_afe; + } + + /* Initialise TXB */ + ret = adv748x_csi2_init(state, &state->txb); + if (ret) { + adv_err(state, "Failed to probe TXB"); + goto err_cleanup_txa; + } + + return 0; + +err_cleanup_txa: + adv748x_csi2_cleanup(&state->txa); +err_cleanup_afe: + adv748x_afe_cleanup(&state->afe); +err_cleanup_hdmi: + adv748x_hdmi_cleanup(&state->hdmi); +err_cleanup_clients: + adv748x_unregister_clients(state); +err_cleanup_dt: + adv748x_dt_cleanup(state); +err_free_mutex: + mutex_destroy(&state->mutex); + + return ret; +} + +static void adv748x_remove(struct i2c_client *client) +{ + struct adv748x_state *state = i2c_get_clientdata(client); + + adv748x_afe_cleanup(&state->afe); + adv748x_hdmi_cleanup(&state->hdmi); + + adv748x_csi2_cleanup(&state->txa); + adv748x_csi2_cleanup(&state->txb); + + adv748x_unregister_clients(state); + adv748x_dt_cleanup(state); + mutex_destroy(&state->mutex); +} + +static const struct of_device_id adv748x_of_table[] = { + { .compatible = "adi,adv7481", }, + { .compatible = "adi,adv7482", }, + { } +}; +MODULE_DEVICE_TABLE(of, adv748x_of_table); + +static const struct dev_pm_ops adv748x_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL, adv748x_resume_early) +}; + +static struct i2c_driver adv748x_driver = { + .driver = { + .name = "adv748x", + .of_match_table = adv748x_of_table, + .pm = &adv748x_pm_ops, + }, + .probe = adv748x_probe, + .remove = adv748x_remove, +}; + +module_i2c_driver(adv748x_driver); + +MODULE_AUTHOR("Kieran Bingham "); +MODULE_DESCRIPTION("ADV748X video decoder"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/adv748x/adv748x-csi2.c b/drivers/media/i2c/adv748x/adv748x-csi2.c new file mode 100644 index 000000000..ebe7da8eb --- /dev/null +++ b/drivers/media/i2c/adv748x/adv748x-csi2.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices ADV748X CSI-2 Transmitter + * + * Copyright (C) 2017 Renesas Electronics Corp. + */ + +#include + +#include +#include +#include + +#include "adv748x.h" + +static const unsigned int adv748x_csi2_txa_fmts[] = { + MEDIA_BUS_FMT_UYVY8_1X16, + MEDIA_BUS_FMT_RGB888_1X24, +}; + +static const unsigned int adv748x_csi2_txb_fmts[] = { + MEDIA_BUS_FMT_UYVY8_1X16, +}; + +int adv748x_csi2_set_virtual_channel(struct adv748x_csi2 *tx, unsigned int vc) +{ + return tx_write(tx, ADV748X_CSI_VC_REF, vc << ADV748X_CSI_VC_REF_SHIFT); +} + +/** + * adv748x_csi2_register_link : Register and link internal entities + * + * @tx: CSI2 private entity + * @v4l2_dev: Video registration device + * @src: Source subdevice to establish link + * @src_pad: Pad number of source to link to this @tx + * @enable: Link enabled flag + * + * Ensure that the subdevice is registered against the v4l2_device, and link the + * source pad to the sink pad of the CSI2 bus entity. + */ +static int adv748x_csi2_register_link(struct adv748x_csi2 *tx, + struct v4l2_device *v4l2_dev, + struct v4l2_subdev *src, + unsigned int src_pad, + bool enable) +{ + int ret; + + if (!src->v4l2_dev) { + ret = v4l2_device_register_subdev(v4l2_dev, src); + if (ret) + return ret; + } + + ret = media_create_pad_link(&src->entity, src_pad, + &tx->sd.entity, ADV748X_CSI2_SINK, + enable ? MEDIA_LNK_FL_ENABLED : 0); + if (ret) + return ret; + + if (enable) + tx->src = src; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_internal_ops + */ + +static int adv748x_csi2_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + static const struct v4l2_mbus_framefmt adv748x_csi2_default_fmt = { + .width = 1280, + .height = 720, + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .colorspace = V4L2_COLORSPACE_SRGB, + .field = V4L2_FIELD_NONE, + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, + .quantization = V4L2_QUANTIZATION_DEFAULT, + .xfer_func = V4L2_XFER_FUNC_DEFAULT, + }; + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(state, ADV748X_CSI2_SINK); + *fmt = adv748x_csi2_default_fmt; + + fmt = v4l2_subdev_state_get_format(state, ADV748X_CSI2_SOURCE); + *fmt = adv748x_csi2_default_fmt; + + return 0; +} + +/* + * We use the internal registered operation to be able to ensure that our + * incremental subdevices (not connected in the forward path) can be registered + * against the resulting video path and media device. + */ + +static int adv748x_csi2_registered(struct v4l2_subdev *sd) +{ + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + struct adv748x_state *state = tx->state; + int ret; + + adv_dbg(state, "Registered %s (%s)", is_txa(tx) ? "TXA":"TXB", + sd->name); + + /* + * Link TXA to AFE and HDMI, and TXB to AFE only as TXB cannot output + * HDMI. + * + * The HDMI->TXA link is enabled by default, as is the AFE->TXB one. + */ + if (is_afe_enabled(state)) { + ret = adv748x_csi2_register_link(tx, sd->v4l2_dev, + &state->afe.sd, + ADV748X_AFE_SOURCE, + is_txb(tx)); + if (ret) + return ret; + + /* TXB can output AFE signals only. */ + if (is_txb(tx)) + state->afe.tx = tx; + } + + /* Register link to HDMI for TXA only. */ + if (is_txb(tx) || !is_hdmi_enabled(state)) + return 0; + + ret = adv748x_csi2_register_link(tx, sd->v4l2_dev, &state->hdmi.sd, + ADV748X_HDMI_SOURCE, true); + if (ret) + return ret; + + /* The default HDMI output is TXA. */ + state->hdmi.tx = tx; + + return 0; +} + +static const struct v4l2_subdev_internal_ops adv748x_csi2_internal_ops = { + .init_state = adv748x_csi2_init_state, + .registered = adv748x_csi2_registered, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_video_ops + */ + +static int adv748x_csi2_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + struct v4l2_subdev *src; + + src = adv748x_get_remote_sd(&tx->pads[ADV748X_CSI2_SINK]); + if (!src) + return -EPIPE; + + return v4l2_subdev_call(src, video, s_stream, enable); +} + +static const struct v4l2_subdev_video_ops adv748x_csi2_video_ops = { + .s_stream = adv748x_csi2_s_stream, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_pad_ops + * + * The CSI2 bus pads are ignorant to the data sizes or formats. + * But we must support setting the pad formats for format propagation. + */ + +static int adv748x_csi2_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + const unsigned int *codes = is_txa(tx) ? + adv748x_csi2_txa_fmts : + adv748x_csi2_txb_fmts; + size_t num_fmts = is_txa(tx) ? ARRAY_SIZE(adv748x_csi2_txa_fmts) + : ARRAY_SIZE(adv748x_csi2_txb_fmts); + + /* + * The format available on the source pad is the one applied on the sink + * pad. + */ + if (code->pad == ADV748X_CSI2_SOURCE) { + struct v4l2_mbus_framefmt *fmt; + + if (code->index) + return -EINVAL; + + fmt = v4l2_subdev_state_get_format(sd_state, ADV748X_CSI2_SINK); + code->code = fmt->code; + + return 0; + } + + if (code->index >= num_fmts) + return -EINVAL; + + code->code = codes[code->index]; + + return 0; +} + +static bool adv748x_csi2_is_fmt_supported(struct adv748x_csi2 *tx, u32 code) +{ + const unsigned int *codes = is_txa(tx) ? + adv748x_csi2_txa_fmts : + adv748x_csi2_txb_fmts; + size_t num_fmts = is_txa(tx) ? ARRAY_SIZE(adv748x_csi2_txa_fmts) + : ARRAY_SIZE(adv748x_csi2_txb_fmts); + + for (unsigned int i = 0; i < num_fmts; i++) { + if (codes[i] == code) + return true; + } + + return false; +} + +static int adv748x_csi2_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat) +{ + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + struct v4l2_mbus_framefmt *mbusformat; + + if (sdformat->pad == ADV748X_CSI2_SOURCE) + return v4l2_subdev_get_fmt(sd, sd_state, sdformat); + + /* + * Make sure the format is supported, if not default it to + * UYVY8 as it's supported by both TXes. + */ + if (!adv748x_csi2_is_fmt_supported(tx, sdformat->format.code)) + sdformat->format.code = MEDIA_BUS_FMT_UYVY8_1X16; + + mbusformat = v4l2_subdev_state_get_format(sd_state, sdformat->pad); + *mbusformat = sdformat->format; + + /* Propagate format to the source pad. */ + mbusformat = v4l2_subdev_state_get_format(sd_state, ADV748X_CSI2_SOURCE); + *mbusformat = sdformat->format; + + return 0; +} + +static int adv748x_csi2_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_config *config) +{ + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + + if (pad != ADV748X_CSI2_SOURCE) + return -EINVAL; + + config->type = V4L2_MBUS_CSI2_DPHY; + config->bus.mipi_csi2.num_data_lanes = tx->active_lanes; + + return 0; +} + +static const struct v4l2_subdev_pad_ops adv748x_csi2_pad_ops = { + .enum_mbus_code = adv748x_csi2_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = adv748x_csi2_set_format, + .get_mbus_config = adv748x_csi2_get_mbus_config, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_ops + */ + +static const struct v4l2_subdev_ops adv748x_csi2_ops = { + .video = &adv748x_csi2_video_ops, + .pad = &adv748x_csi2_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Subdev module and controls + */ + +int adv748x_csi2_set_pixelrate(struct v4l2_subdev *sd, s64 rate) +{ + struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); + + if (!tx->pixel_rate) + return -EINVAL; + + return v4l2_ctrl_s_ctrl_int64(tx->pixel_rate, rate); +} + +static int adv748x_csi2_s_ctrl(struct v4l2_ctrl *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_PIXEL_RATE: + return 0; + default: + return -EINVAL; + } +} + +static const struct v4l2_ctrl_ops adv748x_csi2_ctrl_ops = { + .s_ctrl = adv748x_csi2_s_ctrl, +}; + +static int adv748x_csi2_init_controls(struct adv748x_csi2 *tx) +{ + + v4l2_ctrl_handler_init(&tx->ctrl_hdl, 1); + + tx->pixel_rate = v4l2_ctrl_new_std(&tx->ctrl_hdl, + &adv748x_csi2_ctrl_ops, + V4L2_CID_PIXEL_RATE, 1, INT_MAX, + 1, 1); + + tx->sd.ctrl_handler = &tx->ctrl_hdl; + if (tx->ctrl_hdl.error) { + v4l2_ctrl_handler_free(&tx->ctrl_hdl); + return tx->ctrl_hdl.error; + } + + return v4l2_ctrl_handler_setup(&tx->ctrl_hdl); +} + +int adv748x_csi2_init(struct adv748x_state *state, struct adv748x_csi2 *tx) +{ + int ret; + + if (!is_tx_enabled(tx)) + return 0; + + adv748x_subdev_init(&tx->sd, state, &adv748x_csi2_ops, + MEDIA_ENT_F_VID_IF_BRIDGE, + is_txa(tx) ? "txa" : "txb"); + + /* Register internal ops for incremental subdev registration */ + tx->sd.internal_ops = &adv748x_csi2_internal_ops; + + tx->pads[ADV748X_CSI2_SINK].flags = MEDIA_PAD_FL_SINK; + tx->pads[ADV748X_CSI2_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&tx->sd.entity, ADV748X_CSI2_NR_PADS, + tx->pads); + if (ret) + return ret; + + ret = v4l2_async_subdev_endpoint_add(&tx->sd, + of_fwnode_handle(state->endpoints[tx->port])); + if (ret) + goto err_free_media; + + ret = adv748x_csi2_init_controls(tx); + if (ret) + goto err_cleanup_subdev; + + tx->sd.state_lock = &state->mutex; + ret = v4l2_subdev_init_finalize(&tx->sd); + if (ret) + goto err_free_ctrl; + + ret = v4l2_async_register_subdev(&tx->sd); + if (ret) + goto err_free_ctrl; + + return 0; + +err_free_ctrl: + v4l2_ctrl_handler_free(&tx->ctrl_hdl); +err_cleanup_subdev: + v4l2_subdev_cleanup(&tx->sd); +err_free_media: + media_entity_cleanup(&tx->sd.entity); + + return ret; +} + +void adv748x_csi2_cleanup(struct adv748x_csi2 *tx) +{ + if (!is_tx_enabled(tx)) + return; + + v4l2_async_unregister_subdev(&tx->sd); + media_entity_cleanup(&tx->sd.entity); + v4l2_ctrl_handler_free(&tx->ctrl_hdl); + v4l2_subdev_cleanup(&tx->sd); +} diff --git a/drivers/media/i2c/adv748x/adv748x-hdmi.c b/drivers/media/i2c/adv748x/adv748x-hdmi.c new file mode 100644 index 000000000..b154dea29 --- /dev/null +++ b/drivers/media/i2c/adv748x/adv748x-hdmi.c @@ -0,0 +1,761 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices ADV748X HDMI receiver and Component Processor (CP) + * + * Copyright (C) 2017 Renesas Electronics Corp. + */ + +#include +#include + +#include +#include +#include +#include + +#include + +#include "adv748x.h" + +/* ----------------------------------------------------------------------------- + * HDMI and CP + */ + +#define ADV748X_HDMI_MIN_WIDTH 640 +#define ADV748X_HDMI_MAX_WIDTH 1920 +#define ADV748X_HDMI_MIN_HEIGHT 480 +#define ADV748X_HDMI_MAX_HEIGHT 1200 + +/* V4L2_DV_BT_CEA_720X480I59_94 - 0.5 MHz */ +#define ADV748X_HDMI_MIN_PIXELCLOCK 13000000 +/* V4L2_DV_BT_DMT_1600X1200P60 */ +#define ADV748X_HDMI_MAX_PIXELCLOCK 162000000 + +static const struct v4l2_dv_timings_cap adv748x_hdmi_timings_cap = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + + V4L2_INIT_BT_TIMINGS(ADV748X_HDMI_MIN_WIDTH, ADV748X_HDMI_MAX_WIDTH, + ADV748X_HDMI_MIN_HEIGHT, ADV748X_HDMI_MAX_HEIGHT, + ADV748X_HDMI_MIN_PIXELCLOCK, + ADV748X_HDMI_MAX_PIXELCLOCK, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT, + V4L2_DV_BT_CAP_PROGRESSIVE) +}; + +struct adv748x_hdmi_video_standards { + struct v4l2_dv_timings timings; + u8 vid_std; + u8 v_freq; +}; + +static const struct adv748x_hdmi_video_standards +adv748x_hdmi_video_standards[] = { + { V4L2_DV_BT_CEA_720X480P59_94, 0x4a, 0x00 }, + { V4L2_DV_BT_CEA_720X576P50, 0x4b, 0x00 }, + { V4L2_DV_BT_CEA_1280X720P60, 0x53, 0x00 }, + { V4L2_DV_BT_CEA_1280X720P50, 0x53, 0x01 }, + { V4L2_DV_BT_CEA_1280X720P30, 0x53, 0x02 }, + { V4L2_DV_BT_CEA_1280X720P25, 0x53, 0x03 }, + { V4L2_DV_BT_CEA_1280X720P24, 0x53, 0x04 }, + { V4L2_DV_BT_CEA_1920X1080P60, 0x5e, 0x00 }, + { V4L2_DV_BT_CEA_1920X1080P50, 0x5e, 0x01 }, + { V4L2_DV_BT_CEA_1920X1080P30, 0x5e, 0x02 }, + { V4L2_DV_BT_CEA_1920X1080P25, 0x5e, 0x03 }, + { V4L2_DV_BT_CEA_1920X1080P24, 0x5e, 0x04 }, + /* SVGA */ + { V4L2_DV_BT_DMT_800X600P56, 0x80, 0x00 }, + { V4L2_DV_BT_DMT_800X600P60, 0x81, 0x00 }, + { V4L2_DV_BT_DMT_800X600P72, 0x82, 0x00 }, + { V4L2_DV_BT_DMT_800X600P75, 0x83, 0x00 }, + { V4L2_DV_BT_DMT_800X600P85, 0x84, 0x00 }, + /* SXGA */ + { V4L2_DV_BT_DMT_1280X1024P60, 0x85, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P75, 0x86, 0x00 }, + /* VGA */ + { V4L2_DV_BT_DMT_640X480P60, 0x88, 0x00 }, + { V4L2_DV_BT_DMT_640X480P72, 0x89, 0x00 }, + { V4L2_DV_BT_DMT_640X480P75, 0x8a, 0x00 }, + { V4L2_DV_BT_DMT_640X480P85, 0x8b, 0x00 }, + /* XGA */ + { V4L2_DV_BT_DMT_1024X768P60, 0x8c, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P70, 0x8d, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P75, 0x8e, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P85, 0x8f, 0x00 }, + /* UXGA */ + { V4L2_DV_BT_DMT_1600X1200P60, 0x96, 0x00 }, +}; + +static void adv748x_hdmi_fill_format(struct adv748x_hdmi *hdmi, + struct v4l2_mbus_framefmt *fmt) +{ + memset(fmt, 0, sizeof(*fmt)); + + fmt->code = MEDIA_BUS_FMT_RGB888_1X24; + fmt->field = hdmi->timings.bt.interlaced ? + V4L2_FIELD_ALTERNATE : V4L2_FIELD_NONE; + + /* TODO: The colorspace depends on the AVI InfoFrame contents */ + fmt->colorspace = V4L2_COLORSPACE_SRGB; + + fmt->width = hdmi->timings.bt.width; + fmt->height = hdmi->timings.bt.height; + + if (fmt->field == V4L2_FIELD_ALTERNATE) + fmt->height /= 2; +} + +static void adv748x_fill_optional_dv_timings(struct v4l2_dv_timings *timings) +{ + v4l2_find_dv_timings_cap(timings, &adv748x_hdmi_timings_cap, + 250000, NULL, NULL); +} + +static bool adv748x_hdmi_has_signal(struct adv748x_state *state) +{ + int val; + + /* Check that VERT_FILTER and DE_REGEN is locked */ + val = hdmi_read(state, ADV748X_HDMI_LW1); + return (val & ADV748X_HDMI_LW1_VERT_FILTER) && + (val & ADV748X_HDMI_LW1_DE_REGEN); +} + +static int adv748x_hdmi_read_pixelclock(struct adv748x_state *state) +{ + int a, b; + + a = hdmi_read(state, ADV748X_HDMI_TMDS_1); + b = hdmi_read(state, ADV748X_HDMI_TMDS_2); + if (a < 0 || b < 0) + return -ENODATA; + + /* + * The high 9 bits store TMDS frequency measurement in MHz + * The low 7 bits of TMDS_2 store the 7-bit TMDS fractional frequency + * measurement in 1/128 MHz + */ + return ((a << 1) | (b >> 7)) * 1000000 + (b & 0x7f) * 1000000 / 128; +} + +/* + * adv748x_hdmi_set_de_timings: Adjust horizontal picture offset through DE + * + * HDMI CP uses a Data Enable synchronisation timing reference + * + * Vary the leading and trailing edge position of the DE signal output by the CP + * core. Values are stored as signed-twos-complement in one-pixel-clock units + * + * The start and end are shifted equally by the 10-bit shift value. + */ +static void adv748x_hdmi_set_de_timings(struct adv748x_state *state, int shift) +{ + u8 high, low; + + /* POS_HIGH stores bits 8 and 9 of both the start and end */ + high = ADV748X_CP_DE_POS_HIGH_SET; + high |= (shift & 0x300) >> 8; + low = shift & 0xff; + + /* The sequence of the writes is important and must be followed */ + cp_write(state, ADV748X_CP_DE_POS_HIGH, high); + cp_write(state, ADV748X_CP_DE_POS_END_LOW, low); + + high |= (shift & 0x300) >> 6; + + cp_write(state, ADV748X_CP_DE_POS_HIGH, high); + cp_write(state, ADV748X_CP_DE_POS_START_LOW, low); +} + +static int adv748x_hdmi_set_video_timings(struct adv748x_state *state, + const struct v4l2_dv_timings *timings) +{ + const struct adv748x_hdmi_video_standards *stds = + adv748x_hdmi_video_standards; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(adv748x_hdmi_video_standards); i++) { + if (v4l2_match_dv_timings(timings, &stds[i].timings, 250000, + false)) + break; + } + + if (i >= ARRAY_SIZE(adv748x_hdmi_video_standards)) + return -EINVAL; + + /* + * When setting cp_vid_std to either 720p, 1080i, or 1080p, the video + * will get shifted horizontally to the left in active video mode. + * The de_h_start and de_h_end controls are used to centre the picture + * correctly + */ + switch (stds[i].vid_std) { + case 0x53: /* 720p */ + adv748x_hdmi_set_de_timings(state, -40); + break; + case 0x54: /* 1080i */ + case 0x5e: /* 1080p */ + adv748x_hdmi_set_de_timings(state, -44); + break; + default: + adv748x_hdmi_set_de_timings(state, 0); + break; + } + + io_write(state, ADV748X_IO_VID_STD, stds[i].vid_std); + io_clrset(state, ADV748X_IO_DATAPATH, ADV748X_IO_DATAPATH_VFREQ_M, + stds[i].v_freq << ADV748X_IO_DATAPATH_VFREQ_SHIFT); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_video_ops + */ + +static int adv748x_hdmi_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + int ret; + + if (!timings) + return -EINVAL; + + if (v4l2_match_dv_timings(&hdmi->timings, timings, 0, false)) + return 0; + + if (!v4l2_valid_dv_timings(timings, &adv748x_hdmi_timings_cap, + NULL, NULL)) + return -ERANGE; + + adv748x_fill_optional_dv_timings(timings); + + mutex_lock(&state->mutex); + + ret = adv748x_hdmi_set_video_timings(state, timings); + if (ret) + goto error; + + hdmi->timings = *timings; + + cp_clrset(state, ADV748X_CP_VID_ADJ_2, ADV748X_CP_VID_ADJ_2_INTERLACED, + timings->bt.interlaced ? + ADV748X_CP_VID_ADJ_2_INTERLACED : 0); + + mutex_unlock(&state->mutex); + + return 0; + +error: + mutex_unlock(&state->mutex); + return ret; +} + +static int adv748x_hdmi_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + + mutex_lock(&state->mutex); + + *timings = hdmi->timings; + + mutex_unlock(&state->mutex); + + return 0; +} + +static int adv748x_hdmi_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + struct v4l2_bt_timings *bt = &timings->bt; + int pixelclock; + int polarity; + + if (!timings) + return -EINVAL; + + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + + /* + * If the pattern generator is enabled the device shall not be queried + * for timings. Instead the timings programmed shall be reported as they + * are the ones being used to generate the pattern. + */ + if (cp_read(state, ADV748X_CP_PAT_GEN) & ADV748X_CP_PAT_GEN_EN) { + *timings = hdmi->timings; + return 0; + } + + if (!adv748x_hdmi_has_signal(state)) + return -ENOLINK; + + pixelclock = adv748x_hdmi_read_pixelclock(state); + if (pixelclock < 0) + return -ENODATA; + + timings->type = V4L2_DV_BT_656_1120; + + bt->pixelclock = pixelclock; + bt->interlaced = hdmi_read(state, ADV748X_HDMI_F1H1) & + ADV748X_HDMI_F1H1_INTERLACED ? + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; + bt->width = hdmi_read16(state, ADV748X_HDMI_LW1, + ADV748X_HDMI_LW1_WIDTH_MASK); + bt->height = hdmi_read16(state, ADV748X_HDMI_F0H1, + ADV748X_HDMI_F0H1_HEIGHT_MASK); + bt->hfrontporch = hdmi_read16(state, ADV748X_HDMI_HFRONT_PORCH, + ADV748X_HDMI_HFRONT_PORCH_MASK); + bt->hsync = hdmi_read16(state, ADV748X_HDMI_HSYNC_WIDTH, + ADV748X_HDMI_HSYNC_WIDTH_MASK); + bt->hbackporch = hdmi_read16(state, ADV748X_HDMI_HBACK_PORCH, + ADV748X_HDMI_HBACK_PORCH_MASK); + bt->vfrontporch = hdmi_read16(state, ADV748X_HDMI_VFRONT_PORCH, + ADV748X_HDMI_VFRONT_PORCH_MASK) / 2; + bt->vsync = hdmi_read16(state, ADV748X_HDMI_VSYNC_WIDTH, + ADV748X_HDMI_VSYNC_WIDTH_MASK) / 2; + bt->vbackporch = hdmi_read16(state, ADV748X_HDMI_VBACK_PORCH, + ADV748X_HDMI_VBACK_PORCH_MASK) / 2; + + polarity = hdmi_read(state, 0x05); + bt->polarities = (polarity & BIT(4) ? V4L2_DV_VSYNC_POS_POL : 0) | + (polarity & BIT(5) ? V4L2_DV_HSYNC_POS_POL : 0); + + if (bt->interlaced == V4L2_DV_INTERLACED) { + bt->height += hdmi_read16(state, 0x0b, 0x1fff); + bt->il_vfrontporch = hdmi_read16(state, 0x2c, 0x3fff) / 2; + bt->il_vsync = hdmi_read16(state, 0x30, 0x3fff) / 2; + bt->il_vbackporch = hdmi_read16(state, 0x34, 0x3fff) / 2; + } + + adv748x_fill_optional_dv_timings(timings); + + /* + * No interrupt handling is implemented yet. + * There should be an IRQ when a cable is plugged and the new timings + * should be figured out and stored to state. + */ + hdmi->timings = *timings; + + return 0; +} + +static int adv748x_hdmi_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + + mutex_lock(&state->mutex); + + *status = adv748x_hdmi_has_signal(state) ? 0 : V4L2_IN_ST_NO_SIGNAL; + + mutex_unlock(&state->mutex); + + return 0; +} + +static int adv748x_hdmi_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + int ret; + + mutex_lock(&state->mutex); + + ret = adv748x_tx_power(hdmi->tx, enable); + if (ret) + goto done; + + if (adv748x_hdmi_has_signal(state)) + adv_dbg(state, "Detected HDMI signal\n"); + else + adv_dbg(state, "Couldn't detect HDMI video signal\n"); + +done: + mutex_unlock(&state->mutex); + return ret; +} + +static const struct v4l2_subdev_video_ops adv748x_video_ops_hdmi = { + .g_input_status = adv748x_hdmi_g_input_status, + .s_stream = adv748x_hdmi_s_stream, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_pad_ops + */ + +static int adv748x_hdmi_propagate_pixelrate(struct adv748x_hdmi *hdmi) +{ + struct v4l2_subdev *tx; + struct v4l2_dv_timings timings; + + tx = adv748x_get_remote_sd(&hdmi->pads[ADV748X_HDMI_SOURCE]); + if (!tx) + return -ENOLINK; + + adv748x_hdmi_query_dv_timings(&hdmi->sd, 0, &timings); + + return adv748x_csi2_set_pixelrate(tx, timings.bt.pixelclock); +} + +static int adv748x_hdmi_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_RGB888_1X24; + + return 0; +} + +static int adv748x_hdmi_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + struct v4l2_mbus_framefmt *mbusformat; + + if (sdformat->pad != ADV748X_HDMI_SOURCE) + return -EINVAL; + + if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) { + mbusformat = v4l2_subdev_state_get_format(sd_state, + sdformat->pad); + sdformat->format = *mbusformat; + } else { + adv748x_hdmi_fill_format(hdmi, &sdformat->format); + adv748x_hdmi_propagate_pixelrate(hdmi); + } + + return 0; +} + +static int adv748x_hdmi_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat) +{ + struct v4l2_mbus_framefmt *mbusformat; + + if (sdformat->pad != ADV748X_HDMI_SOURCE) + return -EINVAL; + + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return adv748x_hdmi_get_format(sd, sd_state, sdformat); + + mbusformat = v4l2_subdev_state_get_format(sd_state, sdformat->pad); + *mbusformat = sdformat->format; + + return 0; +} + +static int adv748x_hdmi_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (!hdmi->edid.present) + return -ENODATA; + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = hdmi->edid.blocks; + return 0; + } + + if (edid->start_block >= hdmi->edid.blocks) + return -EINVAL; + + if (edid->start_block + edid->blocks > hdmi->edid.blocks) + edid->blocks = hdmi->edid.blocks - edid->start_block; + + memcpy(edid->edid, hdmi->edid.edid + edid->start_block * 128, + edid->blocks * 128); + + return 0; +} + +static inline int adv748x_hdmi_edid_write_block(struct adv748x_hdmi *hdmi, + unsigned int total_len, const u8 *val) +{ + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + int err = 0; + int i = 0; + int len = 0; + + adv_dbg(state, "%s: write EDID block (%d byte)\n", + __func__, total_len); + + while (!err && i < total_len) { + len = (total_len - i) > I2C_SMBUS_BLOCK_MAX ? + I2C_SMBUS_BLOCK_MAX : + (total_len - i); + + err = adv748x_write_block(state, ADV748X_PAGE_EDID, + i, val + i, len); + i += len; + } + + return err; +} + +static int adv748x_hdmi_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct adv748x_hdmi *hdmi = adv748x_sd_to_hdmi(sd); + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + int err; + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->start_block != 0) + return -EINVAL; + + if (edid->blocks == 0) { + hdmi->edid.blocks = 0; + hdmi->edid.present = 0; + + /* Fall back to a 16:9 aspect ratio */ + hdmi->aspect_ratio.numerator = 16; + hdmi->aspect_ratio.denominator = 9; + + /* Disable the EDID */ + repeater_write(state, ADV748X_REPEATER_EDID_SZ, + edid->blocks << ADV748X_REPEATER_EDID_SZ_SHIFT); + + repeater_write(state, ADV748X_REPEATER_EDID_CTL, 0); + + return 0; + } + + if (edid->blocks > 4) { + edid->blocks = 4; + return -E2BIG; + } + + memcpy(hdmi->edid.edid, edid->edid, 128 * edid->blocks); + hdmi->edid.blocks = edid->blocks; + hdmi->edid.present = true; + + hdmi->aspect_ratio = v4l2_calc_aspect_ratio(edid->edid[0x15], + edid->edid[0x16]); + + err = adv748x_hdmi_edid_write_block(hdmi, 128 * edid->blocks, + hdmi->edid.edid); + if (err < 0) { + v4l2_err(sd, "error %d writing edid pad %d\n", err, edid->pad); + return err; + } + + repeater_write(state, ADV748X_REPEATER_EDID_SZ, + edid->blocks << ADV748X_REPEATER_EDID_SZ_SHIFT); + + repeater_write(state, ADV748X_REPEATER_EDID_CTL, + ADV748X_REPEATER_EDID_CTL_EN); + + return 0; +} + +static bool adv748x_hdmi_check_dv_timings(const struct v4l2_dv_timings *timings, + void *hdl) +{ + const struct adv748x_hdmi_video_standards *stds = + adv748x_hdmi_video_standards; + unsigned int i; + + for (i = 0; stds[i].timings.bt.width; i++) + if (v4l2_match_dv_timings(timings, &stds[i].timings, 0, false)) + return true; + + return false; +} + +static int adv748x_hdmi_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + return v4l2_enum_dv_timings_cap(timings, &adv748x_hdmi_timings_cap, + adv748x_hdmi_check_dv_timings, NULL); +} + +static int adv748x_hdmi_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + *cap = adv748x_hdmi_timings_cap; + return 0; +} + +static const struct v4l2_subdev_pad_ops adv748x_pad_ops_hdmi = { + .enum_mbus_code = adv748x_hdmi_enum_mbus_code, + .set_fmt = adv748x_hdmi_set_format, + .get_fmt = adv748x_hdmi_get_format, + .get_edid = adv748x_hdmi_get_edid, + .set_edid = adv748x_hdmi_set_edid, + .s_dv_timings = adv748x_hdmi_s_dv_timings, + .g_dv_timings = adv748x_hdmi_g_dv_timings, + .query_dv_timings = adv748x_hdmi_query_dv_timings, + .dv_timings_cap = adv748x_hdmi_dv_timings_cap, + .enum_dv_timings = adv748x_hdmi_enum_dv_timings, +}; + +/* ----------------------------------------------------------------------------- + * v4l2_subdev_ops + */ + +static const struct v4l2_subdev_ops adv748x_ops_hdmi = { + .video = &adv748x_video_ops_hdmi, + .pad = &adv748x_pad_ops_hdmi, +}; + +/* ----------------------------------------------------------------------------- + * Controls + */ + +static const char * const hdmi_ctrl_patgen_menu[] = { + "Disabled", + "Solid Color", + "Color Bars", + "Ramp Grey", + "Ramp Blue", + "Ramp Red", + "Checkered" +}; + +static int adv748x_hdmi_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct adv748x_hdmi *hdmi = adv748x_ctrl_to_hdmi(ctrl); + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + int ret; + u8 pattern; + + /* Enable video adjustment first */ + ret = cp_clrset(state, ADV748X_CP_VID_ADJ, + ADV748X_CP_VID_ADJ_ENABLE, + ADV748X_CP_VID_ADJ_ENABLE); + if (ret < 0) + return ret; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ret = cp_write(state, ADV748X_CP_BRI, ctrl->val); + break; + case V4L2_CID_HUE: + ret = cp_write(state, ADV748X_CP_HUE, ctrl->val); + break; + case V4L2_CID_CONTRAST: + ret = cp_write(state, ADV748X_CP_CON, ctrl->val); + break; + case V4L2_CID_SATURATION: + ret = cp_write(state, ADV748X_CP_SAT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + pattern = ctrl->val; + + /* Pattern is 0-indexed. Ctrl Menu is 1-indexed */ + if (pattern) { + pattern--; + pattern |= ADV748X_CP_PAT_GEN_EN; + } + + ret = cp_write(state, ADV748X_CP_PAT_GEN, pattern); + + break; + default: + return -EINVAL; + } + + return ret; +} + +static const struct v4l2_ctrl_ops adv748x_hdmi_ctrl_ops = { + .s_ctrl = adv748x_hdmi_s_ctrl, +}; + +static int adv748x_hdmi_init_controls(struct adv748x_hdmi *hdmi) +{ + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + + v4l2_ctrl_handler_init(&hdmi->ctrl_hdl, 5); + + /* Use our mutex for the controls */ + hdmi->ctrl_hdl.lock = &state->mutex; + + v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, + V4L2_CID_BRIGHTNESS, ADV748X_CP_BRI_MIN, + ADV748X_CP_BRI_MAX, 1, ADV748X_CP_BRI_DEF); + v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, + V4L2_CID_CONTRAST, ADV748X_CP_CON_MIN, + ADV748X_CP_CON_MAX, 1, ADV748X_CP_CON_DEF); + v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, + V4L2_CID_SATURATION, ADV748X_CP_SAT_MIN, + ADV748X_CP_SAT_MAX, 1, ADV748X_CP_SAT_DEF); + v4l2_ctrl_new_std(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, + V4L2_CID_HUE, ADV748X_CP_HUE_MIN, + ADV748X_CP_HUE_MAX, 1, ADV748X_CP_HUE_DEF); + + /* + * Todo: V4L2_CID_DV_RX_POWER_PRESENT should also be supported when + * interrupts are handled correctly + */ + + v4l2_ctrl_new_std_menu_items(&hdmi->ctrl_hdl, &adv748x_hdmi_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(hdmi_ctrl_patgen_menu) - 1, + 0, 0, hdmi_ctrl_patgen_menu); + + hdmi->sd.ctrl_handler = &hdmi->ctrl_hdl; + if (hdmi->ctrl_hdl.error) { + v4l2_ctrl_handler_free(&hdmi->ctrl_hdl); + return hdmi->ctrl_hdl.error; + } + + return v4l2_ctrl_handler_setup(&hdmi->ctrl_hdl); +} + +int adv748x_hdmi_init(struct adv748x_hdmi *hdmi) +{ + struct adv748x_state *state = adv748x_hdmi_to_state(hdmi); + struct v4l2_dv_timings cea1280x720 = V4L2_DV_BT_CEA_1280X720P30; + int ret; + + adv748x_hdmi_s_dv_timings(&hdmi->sd, 0, &cea1280x720); + + /* Initialise a default 16:9 aspect ratio */ + hdmi->aspect_ratio.numerator = 16; + hdmi->aspect_ratio.denominator = 9; + + adv748x_subdev_init(&hdmi->sd, state, &adv748x_ops_hdmi, + MEDIA_ENT_F_IO_DTV, "hdmi"); + + hdmi->pads[ADV748X_HDMI_SINK].flags = MEDIA_PAD_FL_SINK; + hdmi->pads[ADV748X_HDMI_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&hdmi->sd.entity, + ADV748X_HDMI_NR_PADS, hdmi->pads); + if (ret) + return ret; + + ret = adv748x_hdmi_init_controls(hdmi); + if (ret) + goto err_free_media; + + return 0; + +err_free_media: + media_entity_cleanup(&hdmi->sd.entity); + + return ret; +} + +void adv748x_hdmi_cleanup(struct adv748x_hdmi *hdmi) +{ + v4l2_device_unregister_subdev(&hdmi->sd); + media_entity_cleanup(&hdmi->sd.entity); + v4l2_ctrl_handler_free(&hdmi->ctrl_hdl); +} diff --git a/drivers/media/i2c/adv748x/adv748x.h b/drivers/media/i2c/adv748x/adv748x.h new file mode 100644 index 000000000..2c1db5968 --- /dev/null +++ b/drivers/media/i2c/adv748x/adv748x.h @@ -0,0 +1,443 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Driver for Analog Devices ADV748X video decoder and HDMI receiver + * + * Copyright (C) 2017 Renesas Electronics Corp. + * + * Authors: + * Koji Matsuoka + * Niklas Söderlund + * Kieran Bingham + * + * The ADV748x range of receivers have the following configurations: + * + * Analog HDMI MHL 4-Lane 1-Lane + * In In CSI CSI + * ADV7480 X X X + * ADV7481 X X X X X + * ADV7482 X X X X + */ + +#include + +#ifndef _ADV748X_H_ +#define _ADV748X_H_ + +enum adv748x_page { + ADV748X_PAGE_IO, + ADV748X_PAGE_DPLL, + ADV748X_PAGE_CP, + ADV748X_PAGE_HDMI, + ADV748X_PAGE_EDID, + ADV748X_PAGE_REPEATER, + ADV748X_PAGE_INFOFRAME, + ADV748X_PAGE_CBUS, + ADV748X_PAGE_CEC, + ADV748X_PAGE_SDP, + ADV748X_PAGE_TXB, + ADV748X_PAGE_TXA, + ADV748X_PAGE_MAX, + + /* Fake pages for register sequences */ + ADV748X_PAGE_EOR, /* End Mark */ +}; + +/* + * Device tree port number definitions + * + * The ADV748X ports define the mapping between subdevices + * and the device tree specification + */ +enum adv748x_ports { + ADV748X_PORT_AIN0 = 0, + ADV748X_PORT_AIN1 = 1, + ADV748X_PORT_AIN2 = 2, + ADV748X_PORT_AIN3 = 3, + ADV748X_PORT_AIN4 = 4, + ADV748X_PORT_AIN5 = 5, + ADV748X_PORT_AIN6 = 6, + ADV748X_PORT_AIN7 = 7, + ADV748X_PORT_HDMI = 8, + ADV748X_PORT_TTL = 9, + ADV748X_PORT_TXA = 10, + ADV748X_PORT_TXB = 11, + ADV748X_PORT_MAX = 12, +}; + +enum adv748x_csi2_pads { + ADV748X_CSI2_SINK, + ADV748X_CSI2_SOURCE, + ADV748X_CSI2_NR_PADS, +}; + +/* CSI2 transmitters can have 2 internal connections, HDMI/AFE */ +#define ADV748X_CSI2_MAX_SUBDEVS 2 + +struct adv748x_csi2 { + struct adv748x_state *state; + unsigned int page; + unsigned int port; + unsigned int num_lanes; + unsigned int active_lanes; + + struct media_pad pads[ADV748X_CSI2_NR_PADS]; + struct v4l2_ctrl_handler ctrl_hdl; + struct v4l2_ctrl *pixel_rate; + struct v4l2_subdev *src; + struct v4l2_subdev sd; +}; + +#define notifier_to_csi2(n) container_of(n, struct adv748x_csi2, notifier) +#define adv748x_sd_to_csi2(sd) container_of(sd, struct adv748x_csi2, sd) + +#define is_tx_enabled(_tx) ((_tx)->state->endpoints[(_tx)->port] != NULL) +#define is_txa(_tx) ((_tx) == &(_tx)->state->txa) +#define is_txb(_tx) ((_tx) == &(_tx)->state->txb) +#define is_tx(_tx) (is_txa(_tx) || is_txb(_tx)) + +#define is_afe_enabled(_state) \ + ((_state)->endpoints[ADV748X_PORT_AIN0] != NULL || \ + (_state)->endpoints[ADV748X_PORT_AIN1] != NULL || \ + (_state)->endpoints[ADV748X_PORT_AIN2] != NULL || \ + (_state)->endpoints[ADV748X_PORT_AIN3] != NULL || \ + (_state)->endpoints[ADV748X_PORT_AIN4] != NULL || \ + (_state)->endpoints[ADV748X_PORT_AIN5] != NULL || \ + (_state)->endpoints[ADV748X_PORT_AIN6] != NULL || \ + (_state)->endpoints[ADV748X_PORT_AIN7] != NULL) +#define is_hdmi_enabled(_state) ((_state)->endpoints[ADV748X_PORT_HDMI] != NULL) + +enum adv748x_hdmi_pads { + ADV748X_HDMI_SINK, + ADV748X_HDMI_SOURCE, + ADV748X_HDMI_NR_PADS, +}; + +struct adv748x_hdmi { + struct media_pad pads[ADV748X_HDMI_NR_PADS]; + struct v4l2_ctrl_handler ctrl_hdl; + struct v4l2_subdev sd; + struct v4l2_mbus_framefmt format; + + struct v4l2_dv_timings timings; + struct v4l2_fract aspect_ratio; + + struct adv748x_csi2 *tx; + + struct { + u8 edid[512]; + u32 present; + unsigned int blocks; + } edid; +}; + +#define adv748x_ctrl_to_hdmi(ctrl) \ + container_of(ctrl->handler, struct adv748x_hdmi, ctrl_hdl) +#define adv748x_sd_to_hdmi(sd) container_of(sd, struct adv748x_hdmi, sd) + +enum adv748x_afe_pads { + ADV748X_AFE_SINK_AIN0, + ADV748X_AFE_SINK_AIN1, + ADV748X_AFE_SINK_AIN2, + ADV748X_AFE_SINK_AIN3, + ADV748X_AFE_SINK_AIN4, + ADV748X_AFE_SINK_AIN5, + ADV748X_AFE_SINK_AIN6, + ADV748X_AFE_SINK_AIN7, + ADV748X_AFE_SOURCE, + ADV748X_AFE_NR_PADS, +}; + +struct adv748x_afe { + struct media_pad pads[ADV748X_AFE_NR_PADS]; + struct v4l2_ctrl_handler ctrl_hdl; + struct v4l2_subdev sd; + struct v4l2_mbus_framefmt format; + + struct adv748x_csi2 *tx; + + bool streaming; + v4l2_std_id curr_norm; + unsigned int input; +}; + +#define adv748x_ctrl_to_afe(ctrl) \ + container_of(ctrl->handler, struct adv748x_afe, ctrl_hdl) +#define adv748x_sd_to_afe(sd) container_of(sd, struct adv748x_afe, sd) + +/** + * struct adv748x_state - State of ADV748X + * @dev: (OF) device + * @client: I2C client + * @mutex: protect global state + * + * @endpoints: parsed device node endpoints for each port + * + * @i2c_clients: I2C clients for the page accesses + * @regmap: regmap configuration pages. + * + * @hdmi: state of HDMI receiver context + * @afe: state of AFE receiver context + * @txa: state of TXA transmitter context + * @txb: state of TXB transmitter context + */ +struct adv748x_state { + struct device *dev; + struct i2c_client *client; + struct mutex mutex; + + struct device_node *endpoints[ADV748X_PORT_MAX]; + + struct i2c_client *i2c_clients[ADV748X_PAGE_MAX]; + struct regmap *regmap[ADV748X_PAGE_MAX]; + + struct adv748x_hdmi hdmi; + struct adv748x_afe afe; + struct adv748x_csi2 txa; + struct adv748x_csi2 txb; +}; + +#define adv748x_hdmi_to_state(h) container_of(h, struct adv748x_state, hdmi) +#define adv748x_afe_to_state(a) container_of(a, struct adv748x_state, afe) + +#define adv_err(a, fmt, arg...) dev_err(a->dev, fmt, ##arg) +#define adv_info(a, fmt, arg...) dev_info(a->dev, fmt, ##arg) +#define adv_dbg(a, fmt, arg...) dev_dbg(a->dev, fmt, ##arg) + +/* Register Mappings */ + +/* IO Map */ +#define ADV748X_IO_PD 0x00 /* power down controls */ +#define ADV748X_IO_PD_RX_EN BIT(6) + +#define ADV748X_IO_REG_01 0x01 /* pwrdn{2}b, prog_xtal_freq */ +#define ADV748X_IO_REG_01_PWRDN_MASK (BIT(7) | BIT(6)) +#define ADV748X_IO_REG_01_PWRDN2B BIT(7) /* CEC Wakeup Support */ +#define ADV748X_IO_REG_01_PWRDNB BIT(6) /* CEC Wakeup Support */ + +#define ADV748X_IO_REG_04 0x04 +#define ADV748X_IO_REG_04_FORCE_FR BIT(0) /* Force CP free-run */ + +#define ADV748X_IO_DATAPATH 0x03 /* datapath cntrl */ +#define ADV748X_IO_DATAPATH_VFREQ_M 0x70 +#define ADV748X_IO_DATAPATH_VFREQ_SHIFT 4 + +#define ADV748X_IO_VID_STD 0x05 + +#define ADV748X_IO_10 0x10 /* io_reg_10 */ +#define ADV748X_IO_10_CSI4_EN BIT(7) +#define ADV748X_IO_10_CSI1_EN BIT(6) +#define ADV748X_IO_10_PIX_OUT_EN BIT(5) +#define ADV748X_IO_10_CSI4_IN_SEL_AFE BIT(3) + +#define ADV748X_IO_CHIP_REV_ID_1 0xdf +#define ADV748X_IO_CHIP_REV_ID_2 0xe0 + +#define ADV748X_IO_REG_F2 0xf2 +#define ADV748X_IO_REG_F2_READ_AUTO_INC BIT(0) + +/* For PAGE slave address offsets */ +#define ADV748X_IO_SLAVE_ADDR_BASE 0xf2 + +/* + * The ADV748x_Recommended_Settings_PrA_2014-08-20.pdf details both 0x80 and + * 0xff as examples for performing a software reset. + */ +#define ADV748X_IO_REG_FF 0xff +#define ADV748X_IO_REG_FF_MAIN_RESET 0xff + +/* HDMI RX Map */ +#define ADV748X_HDMI_LW1 0x07 /* line width_1 */ +#define ADV748X_HDMI_LW1_VERT_FILTER BIT(7) +#define ADV748X_HDMI_LW1_DE_REGEN BIT(5) +#define ADV748X_HDMI_LW1_WIDTH_MASK 0x1fff + +#define ADV748X_HDMI_F0H1 0x09 /* field0 height_1 */ +#define ADV748X_HDMI_F0H1_HEIGHT_MASK 0x1fff + +#define ADV748X_HDMI_F1H1 0x0b /* field1 height_1 */ +#define ADV748X_HDMI_F1H1_INTERLACED BIT(5) + +#define ADV748X_HDMI_HFRONT_PORCH 0x20 /* hsync_front_porch_1 */ +#define ADV748X_HDMI_HFRONT_PORCH_MASK 0x1fff + +#define ADV748X_HDMI_HSYNC_WIDTH 0x22 /* hsync_pulse_width_1 */ +#define ADV748X_HDMI_HSYNC_WIDTH_MASK 0x1fff + +#define ADV748X_HDMI_HBACK_PORCH 0x24 /* hsync_back_porch_1 */ +#define ADV748X_HDMI_HBACK_PORCH_MASK 0x1fff + +#define ADV748X_HDMI_VFRONT_PORCH 0x2a /* field0_vs_front_porch_1 */ +#define ADV748X_HDMI_VFRONT_PORCH_MASK 0x3fff + +#define ADV748X_HDMI_VSYNC_WIDTH 0x2e /* field0_vs_pulse_width_1 */ +#define ADV748X_HDMI_VSYNC_WIDTH_MASK 0x3fff + +#define ADV748X_HDMI_VBACK_PORCH 0x32 /* field0_vs_back_porch_1 */ +#define ADV748X_HDMI_VBACK_PORCH_MASK 0x3fff + +#define ADV748X_HDMI_TMDS_1 0x51 /* hdmi_reg_51 */ +#define ADV748X_HDMI_TMDS_2 0x52 /* hdmi_reg_52 */ + +/* HDMI RX Repeater Map */ +#define ADV748X_REPEATER_EDID_SZ 0x70 /* primary_edid_size */ +#define ADV748X_REPEATER_EDID_SZ_SHIFT 4 + +#define ADV748X_REPEATER_EDID_CTL 0x74 /* hdcp edid controls */ +#define ADV748X_REPEATER_EDID_CTL_EN BIT(0) /* man_edid_a_enable */ + +/* SDP Main Map */ +#define ADV748X_SDP_INSEL 0x00 /* user_map_rw_reg_00 */ + +#define ADV748X_SDP_VID_SEL 0x02 /* user_map_rw_reg_02 */ +#define ADV748X_SDP_VID_SEL_MASK 0xf0 +#define ADV748X_SDP_VID_SEL_SHIFT 4 + +/* Contrast - Unsigned*/ +#define ADV748X_SDP_CON 0x08 /* user_map_rw_reg_08 */ +#define ADV748X_SDP_CON_MIN 0 +#define ADV748X_SDP_CON_DEF 128 +#define ADV748X_SDP_CON_MAX 255 + +/* Brightness - Signed */ +#define ADV748X_SDP_BRI 0x0a /* user_map_rw_reg_0a */ +#define ADV748X_SDP_BRI_MIN -128 +#define ADV748X_SDP_BRI_DEF 0 +#define ADV748X_SDP_BRI_MAX 127 + +/* Hue - Signed, inverted*/ +#define ADV748X_SDP_HUE 0x0b /* user_map_rw_reg_0b */ +#define ADV748X_SDP_HUE_MIN -127 +#define ADV748X_SDP_HUE_DEF 0 +#define ADV748X_SDP_HUE_MAX 128 + +/* Test Patterns / Default Values */ +#define ADV748X_SDP_DEF 0x0c /* user_map_rw_reg_0c */ +#define ADV748X_SDP_DEF_VAL_EN BIT(0) /* Force free run mode */ +#define ADV748X_SDP_DEF_VAL_AUTO_EN BIT(1) /* Free run when no signal */ + +#define ADV748X_SDP_MAP_SEL 0x0e /* user_map_rw_reg_0e */ +#define ADV748X_SDP_MAP_SEL_RO_MAIN 1 + +/* Free run pattern select */ +#define ADV748X_SDP_FRP 0x14 +#define ADV748X_SDP_FRP_MASK GENMASK(2, 0) + +/* Saturation */ +#define ADV748X_SDP_SD_SAT_U 0xe3 /* user_map_rw_reg_e3 */ +#define ADV748X_SDP_SD_SAT_V 0xe4 /* user_map_rw_reg_e4 */ +#define ADV748X_SDP_SAT_MIN 0 +#define ADV748X_SDP_SAT_DEF 128 +#define ADV748X_SDP_SAT_MAX 255 + +/* SDP RO Main Map */ +#define ADV748X_SDP_RO_10 0x10 +#define ADV748X_SDP_RO_10_IN_LOCK BIT(0) + +/* CP Map */ +#define ADV748X_CP_PAT_GEN 0x37 /* int_pat_gen_1 */ +#define ADV748X_CP_PAT_GEN_EN BIT(7) + +/* Contrast Control - Unsigned */ +#define ADV748X_CP_CON 0x3a /* contrast_cntrl */ +#define ADV748X_CP_CON_MIN 0 /* Minimum contrast */ +#define ADV748X_CP_CON_DEF 128 /* Default */ +#define ADV748X_CP_CON_MAX 255 /* Maximum contrast */ + +/* Saturation Control - Unsigned */ +#define ADV748X_CP_SAT 0x3b /* saturation_cntrl */ +#define ADV748X_CP_SAT_MIN 0 /* Minimum saturation */ +#define ADV748X_CP_SAT_DEF 128 /* Default */ +#define ADV748X_CP_SAT_MAX 255 /* Maximum saturation */ + +/* Brightness Control - Signed */ +#define ADV748X_CP_BRI 0x3c /* brightness_cntrl */ +#define ADV748X_CP_BRI_MIN -128 /* Luma is -512d */ +#define ADV748X_CP_BRI_DEF 0 /* Luma is 0 */ +#define ADV748X_CP_BRI_MAX 127 /* Luma is 508d */ + +/* Hue Control */ +#define ADV748X_CP_HUE 0x3d /* hue_cntrl */ +#define ADV748X_CP_HUE_MIN 0 /* -90 degree */ +#define ADV748X_CP_HUE_DEF 0 /* -90 degree */ +#define ADV748X_CP_HUE_MAX 255 /* +90 degree */ + +#define ADV748X_CP_VID_ADJ 0x3e /* vid_adj_0 */ +#define ADV748X_CP_VID_ADJ_ENABLE BIT(7) /* Enable colour controls */ + +#define ADV748X_CP_DE_POS_HIGH 0x8b /* de_pos_adj_6 */ +#define ADV748X_CP_DE_POS_HIGH_SET BIT(6) +#define ADV748X_CP_DE_POS_END_LOW 0x8c /* de_pos_adj_7 */ +#define ADV748X_CP_DE_POS_START_LOW 0x8d /* de_pos_adj_8 */ + +#define ADV748X_CP_VID_ADJ_2 0x91 +#define ADV748X_CP_VID_ADJ_2_INTERLACED BIT(6) +#define ADV748X_CP_VID_ADJ_2_INTERLACED_3D BIT(4) + +#define ADV748X_CP_CLMP_POS 0xc9 /* clmp_pos_cntrl_4 */ +#define ADV748X_CP_CLMP_POS_DIS_AUTO BIT(0) /* dis_auto_param_buff */ + +/* CSI : TXA/TXB Maps */ +#define ADV748X_CSI_VC_REF 0x0d /* csi_tx_top_reg_0d */ +#define ADV748X_CSI_VC_REF_SHIFT 6 + +#define ADV748X_CSI_FS_AS_LS 0x1e /* csi_tx_top_reg_1e */ +#define ADV748X_CSI_FS_AS_LS_UNKNOWN BIT(6) /* Undocumented bit */ + +/* Register handling */ + +int adv748x_read(struct adv748x_state *state, u8 addr, u8 reg); +int adv748x_write(struct adv748x_state *state, u8 page, u8 reg, u8 value); +int adv748x_write_block(struct adv748x_state *state, int client_page, + unsigned int init_reg, const void *val, + size_t val_len); + +#define io_read(s, r) adv748x_read(s, ADV748X_PAGE_IO, r) +#define io_write(s, r, v) adv748x_write(s, ADV748X_PAGE_IO, r, v) +#define io_clrset(s, r, m, v) io_write(s, r, (io_read(s, r) & ~(m)) | (v)) + +#define hdmi_read(s, r) adv748x_read(s, ADV748X_PAGE_HDMI, r) +#define hdmi_read16(s, r, m) (((hdmi_read(s, r) << 8) | hdmi_read(s, (r)+1)) & (m)) +#define hdmi_write(s, r, v) adv748x_write(s, ADV748X_PAGE_HDMI, r, v) + +#define repeater_read(s, r) adv748x_read(s, ADV748X_PAGE_REPEATER, r) +#define repeater_write(s, r, v) adv748x_write(s, ADV748X_PAGE_REPEATER, r, v) + +#define sdp_read(s, r) adv748x_read(s, ADV748X_PAGE_SDP, r) +#define sdp_write(s, r, v) adv748x_write(s, ADV748X_PAGE_SDP, r, v) +#define sdp_clrset(s, r, m, v) sdp_write(s, r, (sdp_read(s, r) & ~(m)) | (v)) + +#define cp_read(s, r) adv748x_read(s, ADV748X_PAGE_CP, r) +#define cp_write(s, r, v) adv748x_write(s, ADV748X_PAGE_CP, r, v) +#define cp_clrset(s, r, m, v) cp_write(s, r, (cp_read(s, r) & ~(m)) | (v)) + +#define tx_read(t, r) adv748x_read(t->state, t->page, r) +#define tx_write(t, r, v) adv748x_write(t->state, t->page, r, v) + +static inline struct v4l2_subdev *adv748x_get_remote_sd(struct media_pad *pad) +{ + pad = media_pad_remote_pad_first(pad); + if (!pad) + return NULL; + + return media_entity_to_v4l2_subdev(pad->entity); +} + +void adv748x_subdev_init(struct v4l2_subdev *sd, struct adv748x_state *state, + const struct v4l2_subdev_ops *ops, u32 function, + const char *ident); + +int adv748x_tx_power(struct adv748x_csi2 *tx, bool on); + +int adv748x_afe_init(struct adv748x_afe *afe); +void adv748x_afe_cleanup(struct adv748x_afe *afe); +int adv748x_afe_s_input(struct adv748x_afe *afe, unsigned int input); + +int adv748x_csi2_init(struct adv748x_state *state, struct adv748x_csi2 *tx); +void adv748x_csi2_cleanup(struct adv748x_csi2 *tx); +int adv748x_csi2_set_virtual_channel(struct adv748x_csi2 *tx, unsigned int vc); +int adv748x_csi2_set_pixelrate(struct v4l2_subdev *sd, s64 rate); + +int adv748x_hdmi_init(struct adv748x_hdmi *hdmi); +void adv748x_hdmi_cleanup(struct adv748x_hdmi *hdmi); + +#endif /* _ADV748X_H_ */ diff --git a/drivers/media/i2c/adv7511-v4l2.c b/drivers/media/i2c/adv7511-v4l2.c new file mode 100644 index 000000000..860cff50c --- /dev/null +++ b/drivers/media/i2c/adv7511-v4l2.c @@ -0,0 +1,2025 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices ADV7511 HDMI Transmitter Device Driver + * + * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + */ + +/* + * This file is named adv7511-v4l2.c so it doesn't conflict with the Analog + * Device ADV7511 (config fragment CONFIG_DRM_I2C_ADV7511). + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +MODULE_DESCRIPTION("Analog Devices ADV7511 HDMI Transmitter Device Driver"); +MODULE_AUTHOR("Hans Verkuil"); +MODULE_LICENSE("GPL v2"); + +#define MASK_ADV7511_EDID_RDY_INT 0x04 +#define MASK_ADV7511_MSEN_INT 0x40 +#define MASK_ADV7511_HPD_INT 0x80 + +#define MASK_ADV7511_HPD_DETECT 0x40 +#define MASK_ADV7511_MSEN_DETECT 0x20 +#define MASK_ADV7511_EDID_RDY 0x10 + +#define EDID_MAX_RETRIES (8) +#define EDID_DELAY 250 +#define EDID_MAX_SEGM 8 + +#define ADV7511_MAX_WIDTH 1920 +#define ADV7511_MAX_HEIGHT 1200 +#define ADV7511_MIN_PIXELCLOCK 20000000 +#define ADV7511_MAX_PIXELCLOCK 225000000 + +#define ADV7511_MAX_ADDRS (3) + +/* +********************************************************************** +* +* Arrays with configuration parameters for the ADV7511 +* +********************************************************************** +*/ + +struct adv7511_state_edid { + /* total number of blocks */ + u32 blocks; + /* Number of segments read */ + u32 segments; + u8 data[EDID_MAX_SEGM * 256]; + /* Number of EDID read retries left */ + unsigned read_retries; + bool complete; +}; + +struct adv7511_state { + struct adv7511_platform_data pdata; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + int chip_revision; + u8 i2c_edid_addr; + u8 i2c_pktmem_addr; + u8 i2c_cec_addr; + + struct i2c_client *i2c_cec; + struct cec_adapter *cec_adap; + u8 cec_addr[ADV7511_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; + + /* Is the adv7511 powered on? */ + bool power_on; + /* Did we receive hotplug and rx-sense signals? */ + bool have_monitor; + bool enabled_irq; + /* timings from s_dv_timings */ + struct v4l2_dv_timings dv_timings; + u32 fmt_code; + u32 colorspace; + u32 ycbcr_enc; + u32 quantization; + u32 xfer_func; + u32 content_type; + /* controls */ + struct v4l2_ctrl *hdmi_mode_ctrl; + struct v4l2_ctrl *hotplug_ctrl; + struct v4l2_ctrl *rx_sense_ctrl; + struct v4l2_ctrl *have_edid0_ctrl; + struct v4l2_ctrl *rgb_quantization_range_ctrl; + struct v4l2_ctrl *content_type_ctrl; + struct i2c_client *i2c_edid; + struct i2c_client *i2c_pktmem; + struct adv7511_state_edid edid; + /* Running counter of the number of detected EDIDs (for debugging) */ + unsigned edid_detect_counter; + struct workqueue_struct *work_queue; + struct delayed_work edid_handler; /* work entry */ + + struct dentry *debugfs_dir; + struct v4l2_debugfs_if *infoframes; +}; + +static void adv7511_check_monitor_present_status(struct v4l2_subdev *sd); +static bool adv7511_check_edid_status(struct v4l2_subdev *sd); +static void adv7511_setup(struct v4l2_subdev *sd); +static int adv7511_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq); +static int adv7511_s_clock_freq(struct v4l2_subdev *sd, u32 freq); + + +static const struct v4l2_dv_timings_cap adv7511_timings_cap = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + V4L2_INIT_BT_TIMINGS(640, ADV7511_MAX_WIDTH, 350, ADV7511_MAX_HEIGHT, + ADV7511_MIN_PIXELCLOCK, ADV7511_MAX_PIXELCLOCK, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM) +}; + +static inline struct adv7511_state *get_adv7511_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7511_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7511_state, hdl)->sd; +} + +/* ------------------------ I2C ----------------------------------------------- */ + +static s32 adv_smbus_read_byte_data_check(struct i2c_client *client, + u8 command, bool check) +{ + union i2c_smbus_data data; + + if (!i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, command, + I2C_SMBUS_BYTE_DATA, &data)) + return data.byte; + if (check) + v4l_err(client, "error reading %02x, %02x\n", + client->addr, command); + return -1; +} + +static s32 adv_smbus_read_byte_data(struct i2c_client *client, u8 command) +{ + int i; + for (i = 0; i < 3; i++) { + int ret = adv_smbus_read_byte_data_check(client, command, true); + if (ret >= 0) { + if (i) + v4l_err(client, "read ok after %d retries\n", i); + return ret; + } + } + v4l_err(client, "read failed\n"); + return -1; +} + +static int adv7511_rd(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return adv_smbus_read_byte_data(client, reg); +} + +static int adv7511_wr(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + int i; + + for (i = 0; i < 3; i++) { + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret == 0) + return 0; + } + v4l2_err(sd, "%s: i2c write error\n", __func__); + return ret; +} + +/* To set specific bits in the register, a clear-mask is given (to be AND-ed), + and then the value-mask (to be OR-ed). */ +static inline void adv7511_wr_and_or(struct v4l2_subdev *sd, u8 reg, u8 clr_mask, u8 val_mask) +{ + adv7511_wr(sd, reg, (adv7511_rd(sd, reg) & clr_mask) | val_mask); +} + +static int adv7511_edid_rd(struct v4l2_subdev *sd, uint16_t len, uint8_t *buf) +{ + struct adv7511_state *state = get_adv7511_state(sd); + int i; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + for (i = 0; i < len; i += I2C_SMBUS_BLOCK_MAX) { + s32 ret; + + ret = i2c_smbus_read_i2c_block_data(state->i2c_edid, i, + I2C_SMBUS_BLOCK_MAX, buf + i); + if (ret < 0) { + v4l2_err(sd, "%s: i2c read error\n", __func__); + return ret; + } + } + + return 0; +} + +static inline int adv7511_cec_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + return i2c_smbus_read_byte_data(state->i2c_cec, reg); +} + +static int adv7511_cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7511_state *state = get_adv7511_state(sd); + int ret; + int i; + + for (i = 0; i < 3; i++) { + ret = i2c_smbus_write_byte_data(state->i2c_cec, reg, val); + if (ret == 0) + return 0; + } + v4l2_err(sd, "%s: I2C Write Problem\n", __func__); + return ret; +} + +static inline int adv7511_cec_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) +{ + return adv7511_cec_write(sd, reg, (adv7511_cec_read(sd, reg) & mask) | val); +} + +static int adv7511_pktmem_rd(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + return adv_smbus_read_byte_data(state->i2c_pktmem, reg); +} + +static inline bool adv7511_have_hotplug(struct v4l2_subdev *sd) +{ + return adv7511_rd(sd, 0x42) & MASK_ADV7511_HPD_DETECT; +} + +static inline bool adv7511_have_rx_sense(struct v4l2_subdev *sd) +{ + return adv7511_rd(sd, 0x42) & MASK_ADV7511_MSEN_DETECT; +} + +static void adv7511_csc_conversion_mode(struct v4l2_subdev *sd, u8 mode) +{ + adv7511_wr_and_or(sd, 0x18, 0x9f, (mode & 0x3)<<5); +} + +static void adv7511_csc_coeff(struct v4l2_subdev *sd, + u16 A1, u16 A2, u16 A3, u16 A4, + u16 B1, u16 B2, u16 B3, u16 B4, + u16 C1, u16 C2, u16 C3, u16 C4) +{ + /* A */ + adv7511_wr_and_or(sd, 0x18, 0xe0, A1>>8); + adv7511_wr(sd, 0x19, A1); + adv7511_wr_and_or(sd, 0x1A, 0xe0, A2>>8); + adv7511_wr(sd, 0x1B, A2); + adv7511_wr_and_or(sd, 0x1c, 0xe0, A3>>8); + adv7511_wr(sd, 0x1d, A3); + adv7511_wr_and_or(sd, 0x1e, 0xe0, A4>>8); + adv7511_wr(sd, 0x1f, A4); + + /* B */ + adv7511_wr_and_or(sd, 0x20, 0xe0, B1>>8); + adv7511_wr(sd, 0x21, B1); + adv7511_wr_and_or(sd, 0x22, 0xe0, B2>>8); + adv7511_wr(sd, 0x23, B2); + adv7511_wr_and_or(sd, 0x24, 0xe0, B3>>8); + adv7511_wr(sd, 0x25, B3); + adv7511_wr_and_or(sd, 0x26, 0xe0, B4>>8); + adv7511_wr(sd, 0x27, B4); + + /* C */ + adv7511_wr_and_or(sd, 0x28, 0xe0, C1>>8); + adv7511_wr(sd, 0x29, C1); + adv7511_wr_and_or(sd, 0x2A, 0xe0, C2>>8); + adv7511_wr(sd, 0x2B, C2); + adv7511_wr_and_or(sd, 0x2C, 0xe0, C3>>8); + adv7511_wr(sd, 0x2D, C3); + adv7511_wr_and_or(sd, 0x2E, 0xe0, C4>>8); + adv7511_wr(sd, 0x2F, C4); +} + +static void adv7511_csc_rgb_full2limit(struct v4l2_subdev *sd, bool enable) +{ + if (enable) { + u8 csc_mode = 0; + adv7511_csc_conversion_mode(sd, csc_mode); + adv7511_csc_coeff(sd, + 4096-564, 0, 0, 256, + 0, 4096-564, 0, 256, + 0, 0, 4096-564, 256); + /* enable CSC */ + adv7511_wr_and_or(sd, 0x18, 0x7f, 0x80); + /* AVI infoframe: Limited range RGB (16-235) */ + adv7511_wr_and_or(sd, 0x57, 0xf3, 0x04); + } else { + /* disable CSC */ + adv7511_wr_and_or(sd, 0x18, 0x7f, 0x0); + /* AVI infoframe: Full range RGB (0-255) */ + adv7511_wr_and_or(sd, 0x57, 0xf3, 0x08); + } +} + +static void adv7511_set_rgb_quantization_mode(struct v4l2_subdev *sd, struct v4l2_ctrl *ctrl) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + /* Only makes sense for RGB formats */ + if (state->fmt_code != MEDIA_BUS_FMT_RGB888_1X24) { + /* so just keep quantization */ + adv7511_csc_rgb_full2limit(sd, false); + return; + } + + switch (ctrl->val) { + case V4L2_DV_RGB_RANGE_AUTO: + /* automatic */ + if (state->dv_timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) { + /* CE format, RGB limited range (16-235) */ + adv7511_csc_rgb_full2limit(sd, true); + } else { + /* not CE format, RGB full range (0-255) */ + adv7511_csc_rgb_full2limit(sd, false); + } + break; + case V4L2_DV_RGB_RANGE_LIMITED: + /* RGB limited range (16-235) */ + adv7511_csc_rgb_full2limit(sd, true); + break; + case V4L2_DV_RGB_RANGE_FULL: + /* RGB full range (0-255) */ + adv7511_csc_rgb_full2limit(sd, false); + break; + } +} + +/* ------------------------------ CTRL OPS ------------------------------ */ + +static int adv7511_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct adv7511_state *state = get_adv7511_state(sd); + + v4l2_dbg(1, debug, sd, "%s: ctrl id: %d, ctrl->val %d\n", __func__, ctrl->id, ctrl->val); + + if (state->hdmi_mode_ctrl == ctrl) { + /* Set HDMI or DVI-D */ + adv7511_wr_and_or(sd, 0xaf, 0xfd, ctrl->val == V4L2_DV_TX_MODE_HDMI ? 0x02 : 0x00); + return 0; + } + if (state->rgb_quantization_range_ctrl == ctrl) { + adv7511_set_rgb_quantization_mode(sd, ctrl); + return 0; + } + if (state->content_type_ctrl == ctrl) { + u8 itc, cn; + + state->content_type = ctrl->val; + itc = state->content_type != V4L2_DV_IT_CONTENT_TYPE_NO_ITC; + cn = itc ? state->content_type : V4L2_DV_IT_CONTENT_TYPE_GRAPHICS; + adv7511_wr_and_or(sd, 0x57, 0x7f, itc << 7); + adv7511_wr_and_or(sd, 0x59, 0xcf, cn << 4); + return 0; + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops adv7511_ctrl_ops = { + .s_ctrl = adv7511_s_ctrl, +}; + +/* ---------------------------- CORE OPS ------------------------------------------- */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static void adv7511_inv_register(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + v4l2_info(sd, "0x000-0x0ff: Main Map\n"); + if (state->i2c_cec) + v4l2_info(sd, "0x100-0x1ff: CEC Map\n"); +} + +static int adv7511_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + reg->size = 1; + switch (reg->reg >> 8) { + case 0: + reg->val = adv7511_rd(sd, reg->reg & 0xff); + break; + case 1: + if (state->i2c_cec) { + reg->val = adv7511_cec_read(sd, reg->reg & 0xff); + break; + } + fallthrough; + default: + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv7511_inv_register(sd); + break; + } + return 0; +} + +static int adv7511_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + switch (reg->reg >> 8) { + case 0: + adv7511_wr(sd, reg->reg & 0xff, reg->val & 0xff); + break; + case 1: + if (state->i2c_cec) { + adv7511_cec_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + } + fallthrough; + default: + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv7511_inv_register(sd); + break; + } + return 0; +} +#endif + +struct adv7511_cfg_read_infoframe { + const char *desc; + u8 present_reg; + u8 present_mask; + u8 header[3]; + u16 payload_addr; +}; + +static u8 hdmi_infoframe_checksum(u8 *ptr, size_t size) +{ + u8 csum = 0; + size_t i; + + /* compute checksum */ + for (i = 0; i < size; i++) + csum += ptr[i]; + + return 256 - csum; +} + +static int read_infoframe(struct v4l2_subdev *sd, + const struct adv7511_cfg_read_infoframe *cri, + u8 *buffer) +{ + u8 len; + int i; + + if (!(adv7511_rd(sd, cri->present_reg) & cri->present_mask)) { + v4l2_info(sd, "%s infoframe not transmitted\n", cri->desc); + return 0; + } + + memcpy(buffer, cri->header, sizeof(cri->header)); + + len = buffer[2]; + + if (len + 4 > V4L2_DEBUGFS_IF_MAX_LEN) { + v4l2_err(sd, "%s: invalid %s infoframe length %d\n", __func__, cri->desc, len); + return 0; + } + + if (cri->payload_addr >= 0x100) { + for (i = 0; i < len; i++) + buffer[i + 4] = adv7511_pktmem_rd(sd, cri->payload_addr + i - 0x100); + } else { + for (i = 0; i < len; i++) + buffer[i + 4] = adv7511_rd(sd, cri->payload_addr + i); + } + buffer[3] = 0; + buffer[3] = hdmi_infoframe_checksum(buffer, len + 4); + + return len + 4; +} + +static void log_infoframe(struct v4l2_subdev *sd, + const struct adv7511_cfg_read_infoframe *cri) +{ + union hdmi_infoframe frame; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct device *dev = &client->dev; + u8 buffer[V4L2_DEBUGFS_IF_MAX_LEN] = {}; + int len = read_infoframe(sd, cri, buffer); + + if (len <= 0) + return; + + if (hdmi_infoframe_unpack(&frame, buffer, len) < 0) { + v4l2_err(sd, "%s: unpack of %s infoframe failed\n", + __func__, cri->desc); + return; + } + + hdmi_infoframe_log(KERN_INFO, dev, &frame); +} + +static const struct adv7511_cfg_read_infoframe cri[] = { + { "AVI", 0x44, 0x10, { 0x82, 2, 13 }, 0x55 }, + { "Audio", 0x44, 0x08, { 0x84, 1, 10 }, 0x73 }, + { "SDP", 0x40, 0x40, { 0x83, 1, 25 }, 0x103 }, +}; + +static void adv7511_log_infoframes(struct v4l2_subdev *sd) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cri); i++) + log_infoframe(sd, &cri[i]); +} + +static int adv7511_log_status(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + struct adv7511_state_edid *edid = &state->edid; + int i; + + static const char * const states[] = { + "in reset", + "reading EDID", + "idle", + "initializing HDCP", + "HDCP enabled", + "initializing HDCP repeater", + "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" + }; + static const char * const errors[] = { + "no error", + "bad receiver BKSV", + "Ri mismatch", + "Pj mismatch", + "i2c error", + "timed out", + "max repeater cascade exceeded", + "hash check failed", + "too many devices", + "9", "A", "B", "C", "D", "E", "F" + }; + + v4l2_info(sd, "power %s\n", state->power_on ? "on" : "off"); + v4l2_info(sd, "%s hotplug, %s Rx Sense, %s EDID (%d block(s))\n", + (adv7511_rd(sd, 0x42) & MASK_ADV7511_HPD_DETECT) ? "detected" : "no", + (adv7511_rd(sd, 0x42) & MASK_ADV7511_MSEN_DETECT) ? "detected" : "no", + edid->segments ? "found" : "no", + edid->blocks); + v4l2_info(sd, "%s output %s\n", + (adv7511_rd(sd, 0xaf) & 0x02) ? + "HDMI" : "DVI-D", + (adv7511_rd(sd, 0xa1) & 0x3c) ? + "disabled" : "enabled"); + v4l2_info(sd, "state: %s, error: %s, detect count: %u, msk/irq: %02x/%02x\n", + states[adv7511_rd(sd, 0xc8) & 0xf], + errors[adv7511_rd(sd, 0xc8) >> 4], state->edid_detect_counter, + adv7511_rd(sd, 0x94), adv7511_rd(sd, 0x96)); + v4l2_info(sd, "RGB quantization: %s range\n", adv7511_rd(sd, 0x18) & 0x80 ? "limited" : "full"); + if (adv7511_rd(sd, 0xaf) & 0x02) { + /* HDMI only */ + u8 manual_cts = adv7511_rd(sd, 0x0a) & 0x80; + u32 N = (adv7511_rd(sd, 0x01) & 0xf) << 16 | + adv7511_rd(sd, 0x02) << 8 | + adv7511_rd(sd, 0x03); + u8 vic_detect = adv7511_rd(sd, 0x3e) >> 2; + u8 vic_sent = adv7511_rd(sd, 0x3d) & 0x3f; + u32 CTS; + + if (manual_cts) + CTS = (adv7511_rd(sd, 0x07) & 0xf) << 16 | + adv7511_rd(sd, 0x08) << 8 | + adv7511_rd(sd, 0x09); + else + CTS = (adv7511_rd(sd, 0x04) & 0xf) << 16 | + adv7511_rd(sd, 0x05) << 8 | + adv7511_rd(sd, 0x06); + v4l2_info(sd, "CTS %s mode: N %d, CTS %d\n", + manual_cts ? "manual" : "automatic", N, CTS); + v4l2_info(sd, "VIC: detected %d, sent %d\n", + vic_detect, vic_sent); + adv7511_log_infoframes(sd); + } + if (state->dv_timings.type == V4L2_DV_BT_656_1120) + v4l2_print_dv_timings(sd->name, "timings: ", + &state->dv_timings, false); + else + v4l2_info(sd, "no timings set\n"); + v4l2_info(sd, "i2c edid addr: 0x%x\n", state->i2c_edid_addr); + + if (state->i2c_cec == NULL) + return 0; + + v4l2_info(sd, "i2c cec addr: 0x%x\n", state->i2c_cec_addr); + + v4l2_info(sd, "CEC: %s\n", state->cec_enabled_adap ? + "enabled" : "disabled"); + if (state->cec_enabled_adap) { + for (i = 0; i < ADV7511_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (is_valid) + v4l2_info(sd, "CEC Logical Address: 0x%x\n", + state->cec_addr[i]); + } + } + v4l2_info(sd, "i2c pktmem addr: 0x%x\n", state->i2c_pktmem_addr); + return 0; +} + +/* Power up/down adv7511 */ +static int adv7511_s_power(struct v4l2_subdev *sd, int on) +{ + struct adv7511_state *state = get_adv7511_state(sd); + const int retries = 20; + int i; + + v4l2_dbg(1, debug, sd, "%s: power %s\n", __func__, on ? "on" : "off"); + + state->power_on = on; + + if (!on) { + /* Power down */ + adv7511_wr_and_or(sd, 0x41, 0xbf, 0x40); + return true; + } + + /* Power up */ + /* The adv7511 does not always come up immediately. + Retry multiple times. */ + for (i = 0; i < retries; i++) { + adv7511_wr_and_or(sd, 0x41, 0xbf, 0x0); + if ((adv7511_rd(sd, 0x41) & 0x40) == 0) + break; + adv7511_wr_and_or(sd, 0x41, 0xbf, 0x40); + msleep(10); + } + if (i == retries) { + v4l2_dbg(1, debug, sd, "%s: failed to powerup the adv7511!\n", __func__); + adv7511_s_power(sd, 0); + return false; + } + if (i > 1) + v4l2_dbg(1, debug, sd, "%s: needed %d retries to powerup the adv7511\n", __func__, i); + + /* Reserved registers that must be set */ + adv7511_wr(sd, 0x98, 0x03); + adv7511_wr_and_or(sd, 0x9a, 0xfe, 0x70); + adv7511_wr(sd, 0x9c, 0x30); + adv7511_wr_and_or(sd, 0x9d, 0xfc, 0x01); + adv7511_wr(sd, 0xa2, 0xa4); + adv7511_wr(sd, 0xa3, 0xa4); + adv7511_wr(sd, 0xe0, 0xd0); + adv7511_wr(sd, 0xf9, 0x00); + + adv7511_wr(sd, 0x43, state->i2c_edid_addr); + adv7511_wr(sd, 0x45, state->i2c_pktmem_addr); + + /* Set number of attempts to read the EDID */ + adv7511_wr(sd, 0xc9, 0xf); + return true; +} + +#if IS_ENABLED(CONFIG_VIDEO_ADV7511_CEC) +static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct adv7511_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + + if (state->i2c_cec == NULL) + return -EIO; + + if (!state->cec_enabled_adap && enable) { + /* power up cec section */ + adv7511_cec_write_and_or(sd, 0x4e, 0xfc, 0x01); + /* legacy mode and clear all rx buffers */ + adv7511_cec_write(sd, 0x4a, 0x00); + adv7511_cec_write(sd, 0x4a, 0x07); + adv7511_cec_write_and_or(sd, 0x11, 0xfe, 0); /* initially disable tx */ + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready 1 */ + if (state->enabled_irq) + adv7511_wr_and_or(sd, 0x95, 0xc0, 0x39); + } else if (state->cec_enabled_adap && !enable) { + if (state->enabled_irq) + adv7511_wr_and_or(sd, 0x95, 0xc0, 0x00); + /* disable address mask 1-3 */ + adv7511_cec_write_and_or(sd, 0x4b, 0x8f, 0x00); + /* power down cec section */ + adv7511_cec_write_and_or(sd, 0x4e, 0xfc, 0x00); + state->cec_valid_addrs = 0; + } + state->cec_enabled_adap = enable; + return 0; +} + +static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct adv7511_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + unsigned int i, free_idx = ADV7511_MAX_ADDRS; + + if (!state->cec_enabled_adap) + return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + adv7511_cec_write_and_or(sd, 0x4b, 0x8f, 0); + state->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV7511_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (free_idx == ADV7511_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && state->cec_addr[i] == addr) + return 0; + } + if (i == ADV7511_MAX_ADDRS) { + i = free_idx; + if (i == ADV7511_MAX_ADDRS) + return -ENXIO; + } + state->cec_addr[i] = addr; + state->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + adv7511_cec_write_and_or(sd, 0x4b, 0xef, 0x10); + /* set address for mask 0 */ + adv7511_cec_write_and_or(sd, 0x4c, 0xf0, addr); + break; + case 1: + /* enable address mask 1 */ + adv7511_cec_write_and_or(sd, 0x4b, 0xdf, 0x20); + /* set address for mask 1 */ + adv7511_cec_write_and_or(sd, 0x4c, 0x0f, addr << 4); + break; + case 2: + /* enable address mask 2 */ + adv7511_cec_write_and_or(sd, 0x4b, 0xbf, 0x40); + /* set address for mask 1 */ + adv7511_cec_write_and_or(sd, 0x4d, 0xf0, addr); + break; + } + return 0; +} + +static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct adv7511_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + u8 len = msg->len; + unsigned int i; + + v4l2_dbg(1, debug, sd, "%s: len %d\n", __func__, len); + + if (len > 16) { + v4l2_err(sd, "%s: len exceeded 16 (%d)\n", __func__, len); + return -EINVAL; + } + + /* + * The number of retries is the number of attempts - 1, but retry + * at least once. It's not clear if a value of 0 is allowed, so + * let's do at least one retry. + */ + adv7511_cec_write_and_or(sd, 0x12, ~0x70, max(1, attempts - 1) << 4); + + /* clear cec tx irq status */ + adv7511_wr(sd, 0x97, 0x38); + + /* write data */ + for (i = 0; i < len; i++) + adv7511_cec_write(sd, i, msg->msg[i]); + + /* set length (data + header) */ + adv7511_cec_write(sd, 0x10, len); + /* start transmit, enable tx */ + adv7511_cec_write(sd, 0x11, 0x01); + return 0; +} + +static void adv_cec_tx_raw_status(struct v4l2_subdev *sd, u8 tx_raw_status) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + if ((adv7511_cec_read(sd, 0x11) & 0x01) == 0) { + v4l2_dbg(1, debug, sd, "%s: tx raw: tx disabled\n", __func__); + return; + } + + if (tx_raw_status & 0x10) { + v4l2_dbg(1, debug, sd, + "%s: tx raw: arbitration lost\n", __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_ARB_LOST, + 1, 0, 0, 0); + return; + } + if (tx_raw_status & 0x08) { + u8 status; + u8 nack_cnt; + u8 low_drive_cnt; + + v4l2_dbg(1, debug, sd, "%s: tx raw: retry failed\n", __func__); + /* + * We set this status bit since this hardware performs + * retransmissions. + */ + status = CEC_TX_STATUS_MAX_RETRIES; + nack_cnt = adv7511_cec_read(sd, 0x14) & 0xf; + if (nack_cnt) + status |= CEC_TX_STATUS_NACK; + low_drive_cnt = adv7511_cec_read(sd, 0x14) >> 4; + if (low_drive_cnt) + status |= CEC_TX_STATUS_LOW_DRIVE; + cec_transmit_done(state->cec_adap, status, + 0, nack_cnt, low_drive_cnt, 0); + return; + } + if (tx_raw_status & 0x20) { + v4l2_dbg(1, debug, sd, "%s: tx raw: ready ok\n", __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); + return; + } +} + +static const struct cec_adap_ops adv7511_cec_adap_ops = { + .adap_enable = adv7511_cec_adap_enable, + .adap_log_addr = adv7511_cec_adap_log_addr, + .adap_transmit = adv7511_cec_adap_transmit, +}; +#endif + +/* Enable interrupts */ +static void adv7511_set_isr(struct v4l2_subdev *sd, bool enable) +{ + struct adv7511_state *state = get_adv7511_state(sd); + u8 irqs = MASK_ADV7511_HPD_INT | MASK_ADV7511_MSEN_INT; + u8 irqs_rd; + int retries = 100; + + v4l2_dbg(2, debug, sd, "%s: %s\n", __func__, enable ? "enable" : "disable"); + + if (state->enabled_irq == enable) + return; + state->enabled_irq = enable; + + /* The datasheet says that the EDID ready interrupt should be + disabled if there is no hotplug. */ + if (!enable) + irqs = 0; + else if (adv7511_have_hotplug(sd)) + irqs |= MASK_ADV7511_EDID_RDY_INT; + + /* + * This i2c write can fail (approx. 1 in 1000 writes). But it + * is essential that this register is correct, so retry it + * multiple times. + * + * Note that the i2c write does not report an error, but the readback + * clearly shows the wrong value. + */ + do { + adv7511_wr(sd, 0x94, irqs); + irqs_rd = adv7511_rd(sd, 0x94); + } while (retries-- && irqs_rd != irqs); + + if (irqs_rd != irqs) + v4l2_err(sd, "Could not set interrupts: hw failure?\n"); + + adv7511_wr_and_or(sd, 0x95, 0xc0, + (state->cec_enabled_adap && enable) ? 0x39 : 0x00); +} + +/* Interrupt handler */ +static int adv7511_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + u8 irq_status; + u8 cec_irq; + + /* disable interrupts to prevent a race condition */ + adv7511_set_isr(sd, false); + irq_status = adv7511_rd(sd, 0x96); + cec_irq = adv7511_rd(sd, 0x97); + /* clear detected interrupts */ + adv7511_wr(sd, 0x96, irq_status); + adv7511_wr(sd, 0x97, cec_irq); + + v4l2_dbg(1, debug, sd, "%s: irq 0x%x, cec-irq 0x%x\n", __func__, + irq_status, cec_irq); + + if (irq_status & (MASK_ADV7511_HPD_INT | MASK_ADV7511_MSEN_INT)) + adv7511_check_monitor_present_status(sd); + if (irq_status & MASK_ADV7511_EDID_RDY_INT) + adv7511_check_edid_status(sd); + +#if IS_ENABLED(CONFIG_VIDEO_ADV7511_CEC) + if (cec_irq & 0x38) + adv_cec_tx_raw_status(sd, cec_irq); + + if (cec_irq & 1) { + struct adv7511_state *state = get_adv7511_state(sd); + struct cec_msg msg; + + msg.len = adv7511_cec_read(sd, 0x25) & 0x1f; + + v4l2_dbg(1, debug, sd, "%s: cec msg len %d\n", __func__, + msg.len); + + if (msg.len > CEC_MAX_MSG_SIZE) + msg.len = CEC_MAX_MSG_SIZE; + + if (msg.len) { + u8 i; + + for (i = 0; i < msg.len; i++) + msg.msg[i] = adv7511_cec_read(sd, i + 0x15); + + adv7511_cec_write(sd, 0x4a, 0); /* toggle to re-enable rx 1 */ + adv7511_cec_write(sd, 0x4a, 1); + cec_received_msg(state->cec_adap, &msg); + } + } +#endif + + /* enable interrupts */ + adv7511_set_isr(sd, true); + + if (handled) + *handled = true; + return 0; +} + +static const struct v4l2_subdev_core_ops adv7511_core_ops = { + .log_status = adv7511_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = adv7511_g_register, + .s_register = adv7511_s_register, +#endif + .s_power = adv7511_s_power, + .interrupt_service_routine = adv7511_isr, +}; + +/* ------------------------------ VIDEO OPS ------------------------------ */ + +/* Enable/disable adv7511 output */ +static int adv7511_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__, (enable ? "en" : "dis")); + adv7511_wr_and_or(sd, 0xa1, ~0x3c, (enable ? 0 : 0x3c)); + if (enable) { + adv7511_check_monitor_present_status(sd); + } else { + adv7511_s_power(sd, 0); + state->have_monitor = false; + } + return 0; +} + +static int adv7511_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct adv7511_state *state = get_adv7511_state(sd); + struct v4l2_bt_timings *bt = &timings->bt; + u32 fps; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (pad != 0) + return -EINVAL; + + /* quick sanity check */ + if (!v4l2_valid_dv_timings(timings, &adv7511_timings_cap, NULL, NULL)) + return -EINVAL; + + /* Fill the optional fields .standards and .flags in struct v4l2_dv_timings + if the format is one of the CEA or DMT timings. */ + v4l2_find_dv_timings_cap(timings, &adv7511_timings_cap, 0, NULL, NULL); + + /* save timings */ + state->dv_timings = *timings; + + /* set h/vsync polarities */ + adv7511_wr_and_or(sd, 0x17, 0x9f, + ((bt->polarities & V4L2_DV_VSYNC_POS_POL) ? 0 : 0x40) | + ((bt->polarities & V4L2_DV_HSYNC_POS_POL) ? 0 : 0x20)); + + fps = (u32)bt->pixelclock / (V4L2_DV_BT_FRAME_WIDTH(bt) * V4L2_DV_BT_FRAME_HEIGHT(bt)); + switch (fps) { + case 24: + adv7511_wr_and_or(sd, 0xfb, 0xf9, 1 << 1); + break; + case 25: + adv7511_wr_and_or(sd, 0xfb, 0xf9, 2 << 1); + break; + case 30: + adv7511_wr_and_or(sd, 0xfb, 0xf9, 3 << 1); + break; + default: + adv7511_wr_and_or(sd, 0xfb, 0xf9, 0); + break; + } + + /* update quantization range based on new dv_timings */ + adv7511_set_rgb_quantization_mode(sd, state->rgb_quantization_range_ctrl); + + return 0; +} + +static int adv7511_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (pad != 0) + return -EINVAL; + + if (!timings) + return -EINVAL; + + *timings = state->dv_timings; + + return 0; +} + +static int adv7511_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + if (timings->pad != 0) + return -EINVAL; + + return v4l2_enum_dv_timings_cap(timings, &adv7511_timings_cap, NULL, NULL); +} + +static int adv7511_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + if (cap->pad != 0) + return -EINVAL; + + *cap = adv7511_timings_cap; + return 0; +} + +static const struct v4l2_subdev_video_ops adv7511_video_ops = { + .s_stream = adv7511_s_stream, +}; + +/* ------------------------------ AUDIO OPS ------------------------------ */ +static int adv7511_s_audio_stream(struct v4l2_subdev *sd, int enable) +{ + v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__, (enable ? "en" : "dis")); + + if (enable) + adv7511_wr_and_or(sd, 0x4b, 0x3f, 0x80); + else + adv7511_wr_and_or(sd, 0x4b, 0x3f, 0x40); + + return 0; +} + +static int adv7511_s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + u32 N; + + switch (freq) { + case 32000: N = 4096; break; + case 44100: N = 6272; break; + case 48000: N = 6144; break; + case 88200: N = 12544; break; + case 96000: N = 12288; break; + case 176400: N = 25088; break; + case 192000: N = 24576; break; + default: + return -EINVAL; + } + + /* Set N (used with CTS to regenerate the audio clock) */ + adv7511_wr(sd, 0x01, (N >> 16) & 0xf); + adv7511_wr(sd, 0x02, (N >> 8) & 0xff); + adv7511_wr(sd, 0x03, N & 0xff); + + return 0; +} + +static int adv7511_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + u32 i2s_sf; + + switch (freq) { + case 32000: i2s_sf = 0x30; break; + case 44100: i2s_sf = 0x00; break; + case 48000: i2s_sf = 0x20; break; + case 88200: i2s_sf = 0x80; break; + case 96000: i2s_sf = 0xa0; break; + case 176400: i2s_sf = 0xc0; break; + case 192000: i2s_sf = 0xe0; break; + default: + return -EINVAL; + } + + /* Set sampling frequency for I2S audio to 48 kHz */ + adv7511_wr_and_or(sd, 0x15, 0xf, i2s_sf); + + return 0; +} + +static int adv7511_s_routing(struct v4l2_subdev *sd, u32 input, u32 output, u32 config) +{ + /* Only 2 channels in use for application */ + adv7511_wr_and_or(sd, 0x73, 0xf8, 0x1); + /* Speaker mapping */ + adv7511_wr(sd, 0x76, 0x00); + + /* 16 bit audio word length */ + adv7511_wr_and_or(sd, 0x14, 0xf0, 0x02); + + return 0; +} + +static const struct v4l2_subdev_audio_ops adv7511_audio_ops = { + .s_stream = adv7511_s_audio_stream, + .s_clock_freq = adv7511_s_clock_freq, + .s_i2s_clock_freq = adv7511_s_i2s_clock_freq, + .s_routing = adv7511_s_routing, +}; + +/* ---------------------------- PAD OPS ------------------------------------- */ + +static int adv7511_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->pad != 0) + return -EINVAL; + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = state->edid.blocks; + return 0; + } + + if (state->edid.blocks == 0) + return -ENODATA; + + if (edid->start_block >= state->edid.blocks) + return -EINVAL; + + if (edid->start_block + edid->blocks > state->edid.blocks) + edid->blocks = state->edid.blocks - edid->start_block; + + memcpy(edid->edid, &state->edid.data[edid->start_block * 128], + 128 * edid->blocks); + + return 0; +} + +static int adv7511_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad != 0) + return -EINVAL; + + switch (code->index) { + case 0: + code->code = MEDIA_BUS_FMT_RGB888_1X24; + break; + case 1: + code->code = MEDIA_BUS_FMT_YUYV8_1X16; + break; + case 2: + code->code = MEDIA_BUS_FMT_UYVY8_1X16; + break; + default: + return -EINVAL; + } + return 0; +} + +static void adv7511_fill_format(struct adv7511_state *state, + struct v4l2_mbus_framefmt *format) +{ + format->width = state->dv_timings.bt.width; + format->height = state->dv_timings.bt.height; + format->field = V4L2_FIELD_NONE; +} + +static int adv7511_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + if (format->pad != 0) + return -EINVAL; + + memset(&format->format, 0, sizeof(format->format)); + adv7511_fill_format(state, &format->format); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + format->format.code = fmt->code; + format->format.colorspace = fmt->colorspace; + format->format.ycbcr_enc = fmt->ycbcr_enc; + format->format.quantization = fmt->quantization; + format->format.xfer_func = fmt->xfer_func; + } else { + format->format.code = state->fmt_code; + format->format.colorspace = state->colorspace; + format->format.ycbcr_enc = state->ycbcr_enc; + format->format.quantization = state->quantization; + format->format.xfer_func = state->xfer_func; + } + + return 0; +} + +static int adv7511_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct adv7511_state *state = get_adv7511_state(sd); + /* + * Bitfield namings come the CEA-861-F standard, table 8 "Auxiliary + * Video Information (AVI) InfoFrame Format" + * + * c = Colorimetry + * ec = Extended Colorimetry + * y = RGB or YCbCr + * q = RGB Quantization Range + * yq = YCC Quantization Range + */ + u8 c = HDMI_COLORIMETRY_NONE; + u8 ec = HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + u8 y = HDMI_COLORSPACE_RGB; + u8 q = HDMI_QUANTIZATION_RANGE_DEFAULT; + u8 yq = HDMI_YCC_QUANTIZATION_RANGE_LIMITED; + u8 itc = state->content_type != V4L2_DV_IT_CONTENT_TYPE_NO_ITC; + u8 cn = itc ? state->content_type : V4L2_DV_IT_CONTENT_TYPE_GRAPHICS; + + if (format->pad != 0) + return -EINVAL; + switch (format->format.code) { + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_RGB888_1X24: + break; + default: + return -EINVAL; + } + + adv7511_fill_format(state, &format->format); + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + fmt->code = format->format.code; + fmt->colorspace = format->format.colorspace; + fmt->ycbcr_enc = format->format.ycbcr_enc; + fmt->quantization = format->format.quantization; + fmt->xfer_func = format->format.xfer_func; + return 0; + } + + switch (format->format.code) { + case MEDIA_BUS_FMT_UYVY8_1X16: + adv7511_wr_and_or(sd, 0x15, 0xf0, 0x01); + adv7511_wr_and_or(sd, 0x16, 0x03, 0xb8); + y = HDMI_COLORSPACE_YUV422; + break; + case MEDIA_BUS_FMT_YUYV8_1X16: + adv7511_wr_and_or(sd, 0x15, 0xf0, 0x01); + adv7511_wr_and_or(sd, 0x16, 0x03, 0xbc); + y = HDMI_COLORSPACE_YUV422; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + default: + adv7511_wr_and_or(sd, 0x15, 0xf0, 0x00); + adv7511_wr_and_or(sd, 0x16, 0x03, 0x00); + break; + } + state->fmt_code = format->format.code; + state->colorspace = format->format.colorspace; + state->ycbcr_enc = format->format.ycbcr_enc; + state->quantization = format->format.quantization; + state->xfer_func = format->format.xfer_func; + + switch (format->format.colorspace) { + case V4L2_COLORSPACE_OPRGB: + c = HDMI_COLORIMETRY_EXTENDED; + ec = y ? HDMI_EXTENDED_COLORIMETRY_OPYCC_601 : + HDMI_EXTENDED_COLORIMETRY_OPRGB; + break; + case V4L2_COLORSPACE_SMPTE170M: + c = y ? HDMI_COLORIMETRY_ITU_601 : HDMI_COLORIMETRY_NONE; + if (y && format->format.ycbcr_enc == V4L2_YCBCR_ENC_XV601) { + c = HDMI_COLORIMETRY_EXTENDED; + ec = HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + } + break; + case V4L2_COLORSPACE_REC709: + c = y ? HDMI_COLORIMETRY_ITU_709 : HDMI_COLORIMETRY_NONE; + if (y && format->format.ycbcr_enc == V4L2_YCBCR_ENC_XV709) { + c = HDMI_COLORIMETRY_EXTENDED; + ec = HDMI_EXTENDED_COLORIMETRY_XV_YCC_709; + } + break; + case V4L2_COLORSPACE_SRGB: + c = y ? HDMI_COLORIMETRY_EXTENDED : HDMI_COLORIMETRY_NONE; + ec = y ? HDMI_EXTENDED_COLORIMETRY_S_YCC_601 : + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + break; + case V4L2_COLORSPACE_BT2020: + c = HDMI_COLORIMETRY_EXTENDED; + if (y && format->format.ycbcr_enc == V4L2_YCBCR_ENC_BT2020_CONST_LUM) + ec = HDMI_EXTENDED_COLORIMETRY_BT2020_CONST_LUM; + else + ec = HDMI_EXTENDED_COLORIMETRY_BT2020; + break; + default: + break; + } + + /* + * CEA-861-F says that for RGB formats the YCC range must match the + * RGB range, although sources should ignore the YCC range. + * + * The RGB quantization range shouldn't be non-zero if the EDID doesn't + * have the Q bit set in the Video Capabilities Data Block, however this + * isn't checked at the moment. The assumption is that the application + * knows the EDID and can detect this. + * + * The same is true for the YCC quantization range: non-standard YCC + * quantization ranges should only be sent if the EDID has the YQ bit + * set in the Video Capabilities Data Block. + */ + switch (format->format.quantization) { + case V4L2_QUANTIZATION_FULL_RANGE: + q = y ? HDMI_QUANTIZATION_RANGE_DEFAULT : + HDMI_QUANTIZATION_RANGE_FULL; + yq = q ? q - 1 : HDMI_YCC_QUANTIZATION_RANGE_FULL; + break; + case V4L2_QUANTIZATION_LIM_RANGE: + q = y ? HDMI_QUANTIZATION_RANGE_DEFAULT : + HDMI_QUANTIZATION_RANGE_LIMITED; + yq = q ? q - 1 : HDMI_YCC_QUANTIZATION_RANGE_LIMITED; + break; + } + + adv7511_wr_and_or(sd, 0x4a, 0xbf, 0); + adv7511_wr_and_or(sd, 0x55, 0x9f, y << 5); + adv7511_wr_and_or(sd, 0x56, 0x3f, c << 6); + adv7511_wr_and_or(sd, 0x57, 0x83, (ec << 4) | (q << 2) | (itc << 7)); + adv7511_wr_and_or(sd, 0x59, 0x0f, (yq << 6) | (cn << 4)); + adv7511_wr_and_or(sd, 0x4a, 0xff, 1); + adv7511_set_rgb_quantization_mode(sd, state->rgb_quantization_range_ctrl); + + return 0; +} + +static const struct v4l2_subdev_pad_ops adv7511_pad_ops = { + .get_edid = adv7511_get_edid, + .enum_mbus_code = adv7511_enum_mbus_code, + .get_fmt = adv7511_get_fmt, + .set_fmt = adv7511_set_fmt, + .s_dv_timings = adv7511_s_dv_timings, + .g_dv_timings = adv7511_g_dv_timings, + .enum_dv_timings = adv7511_enum_dv_timings, + .dv_timings_cap = adv7511_dv_timings_cap, +}; + +/* --------------------- SUBDEV OPS --------------------------------------- */ + +static const struct v4l2_subdev_ops adv7511_ops = { + .core = &adv7511_core_ops, + .pad = &adv7511_pad_ops, + .video = &adv7511_video_ops, + .audio = &adv7511_audio_ops, +}; + +/* ----------------------------------------------------------------------- */ +static void adv7511_dbg_dump_edid(int lvl, int debug, struct v4l2_subdev *sd, int segment, u8 *buf) +{ + if (debug >= lvl) { + int i, j; + v4l2_dbg(lvl, debug, sd, "edid segment %d\n", segment); + for (i = 0; i < 256; i += 16) { + u8 b[128]; + u8 *bp = b; + if (i == 128) + v4l2_dbg(lvl, debug, sd, "\n"); + for (j = i; j < i + 16; j++) { + sprintf(bp, "0x%02x, ", buf[j]); + bp += 6; + } + bp[0] = '\0'; + v4l2_dbg(lvl, debug, sd, "%s\n", b); + } + } +} + +static void adv7511_notify_no_edid(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + struct adv7511_edid_detect ed; + + /* We failed to read the EDID, so send an event for this. */ + ed.present = false; + ed.segment = adv7511_rd(sd, 0xc4); + ed.phys_addr = CEC_PHYS_ADDR_INVALID; + cec_s_phys_addr(state->cec_adap, ed.phys_addr, false); + v4l2_subdev_notify(sd, ADV7511_EDID_DETECT, (void *)&ed); + v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, 0x0); +} + +static void adv7511_edid_handler(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct adv7511_state *state = container_of(dwork, struct adv7511_state, edid_handler); + struct v4l2_subdev *sd = &state->sd; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (adv7511_check_edid_status(sd)) { + /* Return if we received the EDID. */ + return; + } + + if (adv7511_have_hotplug(sd)) { + /* We must retry reading the EDID several times, it is possible + * that initially the EDID couldn't be read due to i2c errors + * (DVI connectors are particularly prone to this problem). */ + if (state->edid.read_retries) { + state->edid.read_retries--; + v4l2_dbg(1, debug, sd, "%s: edid read failed\n", __func__); + state->have_monitor = false; + adv7511_s_power(sd, false); + adv7511_s_power(sd, true); + queue_delayed_work(state->work_queue, &state->edid_handler, EDID_DELAY); + return; + } + } + + /* We failed to read the EDID, so send an event for this. */ + adv7511_notify_no_edid(sd); + v4l2_dbg(1, debug, sd, "%s: no edid found\n", __func__); +} + +static void adv7511_audio_setup(struct v4l2_subdev *sd) +{ + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + adv7511_s_i2s_clock_freq(sd, 48000); + adv7511_s_clock_freq(sd, 48000); + adv7511_s_routing(sd, 0, 0, 0); +} + +/* Configure hdmi transmitter. */ +static void adv7511_setup(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + /* Input format: RGB 4:4:4 */ + adv7511_wr_and_or(sd, 0x15, 0xf0, 0x0); + /* Output format: RGB 4:4:4 */ + adv7511_wr_and_or(sd, 0x16, 0x7f, 0x0); + /* 1st order interpolation 4:2:2 -> 4:4:4 up conversion, Aspect ratio: 16:9 */ + adv7511_wr_and_or(sd, 0x17, 0xf9, 0x06); + /* Disable pixel repetition */ + adv7511_wr_and_or(sd, 0x3b, 0x9f, 0x0); + /* Disable CSC */ + adv7511_wr_and_or(sd, 0x18, 0x7f, 0x0); + /* Output format: RGB 4:4:4, Active Format Information is valid, + * underscanned */ + adv7511_wr_and_or(sd, 0x55, 0x9c, 0x12); + /* AVI Info frame packet enable, Audio Info frame disable */ + adv7511_wr_and_or(sd, 0x44, 0xe7, 0x10); + /* Colorimetry, Active format aspect ratio: same as picure. */ + adv7511_wr(sd, 0x56, 0xa8); + /* No encryption */ + adv7511_wr_and_or(sd, 0xaf, 0xed, 0x0); + + /* Positive clk edge capture for input video clock */ + adv7511_wr_and_or(sd, 0xba, 0x1f, 0x60); + + adv7511_audio_setup(sd); + + v4l2_ctrl_handler_setup(&state->hdl); +} + +static void adv7511_notify_monitor_detect(struct v4l2_subdev *sd) +{ + struct adv7511_monitor_detect mdt; + struct adv7511_state *state = get_adv7511_state(sd); + + mdt.present = state->have_monitor; + v4l2_subdev_notify(sd, ADV7511_MONITOR_DETECT, (void *)&mdt); +} + +static void adv7511_check_monitor_present_status(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + /* read hotplug and rx-sense state */ + u8 status = adv7511_rd(sd, 0x42); + + v4l2_dbg(1, debug, sd, "%s: status: 0x%x%s%s\n", + __func__, + status, + status & MASK_ADV7511_HPD_DETECT ? ", hotplug" : "", + status & MASK_ADV7511_MSEN_DETECT ? ", rx-sense" : ""); + + /* update read only ctrls */ + v4l2_ctrl_s_ctrl(state->hotplug_ctrl, adv7511_have_hotplug(sd) ? 0x1 : 0x0); + v4l2_ctrl_s_ctrl(state->rx_sense_ctrl, adv7511_have_rx_sense(sd) ? 0x1 : 0x0); + + if ((status & MASK_ADV7511_HPD_DETECT) && ((status & MASK_ADV7511_MSEN_DETECT) || state->edid.segments)) { + v4l2_dbg(1, debug, sd, "%s: hotplug and (rx-sense or edid)\n", __func__); + if (!state->have_monitor) { + v4l2_dbg(1, debug, sd, "%s: monitor detected\n", __func__); + state->have_monitor = true; + adv7511_set_isr(sd, true); + if (!adv7511_s_power(sd, true)) { + v4l2_dbg(1, debug, sd, "%s: monitor detected, powerup failed\n", __func__); + return; + } + adv7511_setup(sd); + adv7511_notify_monitor_detect(sd); + state->edid.read_retries = EDID_MAX_RETRIES; + queue_delayed_work(state->work_queue, &state->edid_handler, EDID_DELAY); + } + } else if (status & MASK_ADV7511_HPD_DETECT) { + v4l2_dbg(1, debug, sd, "%s: hotplug detected\n", __func__); + state->edid.read_retries = EDID_MAX_RETRIES; + queue_delayed_work(state->work_queue, &state->edid_handler, EDID_DELAY); + } else if (!(status & MASK_ADV7511_HPD_DETECT)) { + v4l2_dbg(1, debug, sd, "%s: hotplug not detected\n", __func__); + if (state->have_monitor) { + v4l2_dbg(1, debug, sd, "%s: monitor not detected\n", __func__); + state->have_monitor = false; + adv7511_notify_monitor_detect(sd); + } + adv7511_s_power(sd, false); + memset(&state->edid, 0, sizeof(struct adv7511_state_edid)); + adv7511_notify_no_edid(sd); + } +} + +static bool edid_block_verify_crc(u8 *edid_block) +{ + u8 sum = 0; + int i; + + for (i = 0; i < 128; i++) + sum += edid_block[i]; + return sum == 0; +} + +static bool edid_verify_crc(struct v4l2_subdev *sd, u32 segment) +{ + struct adv7511_state *state = get_adv7511_state(sd); + u32 blocks = state->edid.blocks; + u8 *data = state->edid.data; + + if (!edid_block_verify_crc(&data[segment * 256])) + return false; + if ((segment + 1) * 2 <= blocks) + return edid_block_verify_crc(&data[segment * 256 + 128]); + return true; +} + +static bool edid_verify_header(struct v4l2_subdev *sd, u32 segment) +{ + static const u8 hdmi_header[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 + }; + struct adv7511_state *state = get_adv7511_state(sd); + u8 *data = state->edid.data; + + if (segment != 0) + return true; + return !memcmp(data, hdmi_header, sizeof(hdmi_header)); +} + +static bool adv7511_check_edid_status(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + u8 edidRdy = adv7511_rd(sd, 0xc5); + + v4l2_dbg(1, debug, sd, "%s: edid ready (retries: %d)\n", + __func__, EDID_MAX_RETRIES - state->edid.read_retries); + + if (state->edid.complete) + return true; + + if (edidRdy & MASK_ADV7511_EDID_RDY) { + int segment = adv7511_rd(sd, 0xc4); + struct adv7511_edid_detect ed; + int err; + + if (segment >= EDID_MAX_SEGM) { + v4l2_err(sd, "edid segment number too big\n"); + return false; + } + v4l2_dbg(1, debug, sd, "%s: got segment %d\n", __func__, segment); + err = adv7511_edid_rd(sd, 256, &state->edid.data[segment * 256]); + if (!err) { + adv7511_dbg_dump_edid(2, debug, sd, segment, &state->edid.data[segment * 256]); + if (segment == 0) { + state->edid.blocks = + v4l2_num_edid_blocks(state->edid.data, + EDID_MAX_SEGM * 2); + v4l2_dbg(1, debug, sd, "%s: %d blocks in total\n", + __func__, state->edid.blocks); + } + } + + if (err || !edid_verify_crc(sd, segment) || !edid_verify_header(sd, segment)) { + /* Couldn't read EDID or EDID is invalid. Force retry! */ + if (!err) + v4l2_err(sd, "%s: edid crc or header error\n", __func__); + state->have_monitor = false; + adv7511_s_power(sd, false); + adv7511_s_power(sd, true); + return false; + } + /* one more segment read ok */ + state->edid.segments = segment + 1; + v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, 0x1); + if (state->edid.blocks > state->edid.segments * 2) { + /* Request next EDID segment */ + v4l2_dbg(1, debug, sd, "%s: request segment %d\n", __func__, state->edid.segments); + adv7511_wr(sd, 0xc9, 0xf); + adv7511_wr(sd, 0xc4, state->edid.segments); + state->edid.read_retries = EDID_MAX_RETRIES; + queue_delayed_work(state->work_queue, &state->edid_handler, EDID_DELAY); + return false; + } + + v4l2_dbg(1, debug, sd, "%s: edid complete with %d segment(s)\n", __func__, state->edid.segments); + state->edid.complete = true; + ed.phys_addr = cec_get_edid_phys_addr(state->edid.data, + state->edid.segments * 256, + NULL); + /* report when we have all segments + but report only for segment 0 + */ + ed.present = true; + ed.segment = 0; + state->edid_detect_counter++; + cec_s_phys_addr(state->cec_adap, ed.phys_addr, false); + v4l2_subdev_notify(sd, ADV7511_EDID_DETECT, (void *)&ed); + return ed.present; + } + + return false; +} + +static ssize_t +adv7511_debugfs_if_read(u32 type, void *priv, + struct file *filp, char __user *ubuf, size_t count, loff_t *ppos) +{ + u8 buf[V4L2_DEBUGFS_IF_MAX_LEN] = {}; + struct v4l2_subdev *sd = priv; + int index; + int len; + + switch (type) { + case V4L2_DEBUGFS_IF_AVI: + index = 0; + break; + case V4L2_DEBUGFS_IF_AUDIO: + index = 1; + break; + case V4L2_DEBUGFS_IF_SPD: + index = 2; + break; + default: + return 0; + } + len = read_infoframe(sd, &cri[index], buf); + if (len > 0) + len = simple_read_from_buffer(ubuf, count, ppos, buf, len); + return len < 0 ? 0 : len; +} + +static int adv7511_registered(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err; + + err = cec_register_adapter(state->cec_adap, &client->dev); + if (err) { + cec_delete_adapter(state->cec_adap); + return err; + } + + state->debugfs_dir = debugfs_create_dir(sd->name, v4l2_debugfs_root()); + state->infoframes = v4l2_debugfs_if_alloc(state->debugfs_dir, + V4L2_DEBUGFS_IF_AVI | V4L2_DEBUGFS_IF_AUDIO | + V4L2_DEBUGFS_IF_SPD, sd, adv7511_debugfs_if_read); + return 0; +} + +static void adv7511_unregistered(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + cec_unregister_adapter(state->cec_adap); + v4l2_debugfs_if_free(state->infoframes); + state->infoframes = NULL; + debugfs_remove_recursive(state->debugfs_dir); + state->debugfs_dir = NULL; +} + +static const struct v4l2_subdev_internal_ops adv7511_int_ops = { + .registered = adv7511_registered, + .unregistered = adv7511_unregistered, +}; + +/* ----------------------------------------------------------------------- */ +/* Setup ADV7511 */ +static void adv7511_init_setup(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + struct adv7511_state_edid *edid = &state->edid; + u32 cec_clk = state->pdata.cec_clk; + u8 ratio; + + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + /* clear all interrupts */ + adv7511_wr(sd, 0x96, 0xff); + adv7511_wr(sd, 0x97, 0xff); + /* + * Stop HPD from resetting a lot of registers. + * It might leave the chip in a partly un-initialized state, + * in particular with regards to hotplug bounces. + */ + adv7511_wr_and_or(sd, 0xd6, 0x3f, 0xc0); + memset(edid, 0, sizeof(struct adv7511_state_edid)); + state->have_monitor = false; + adv7511_set_isr(sd, false); + adv7511_s_stream(sd, false); + adv7511_s_audio_stream(sd, false); + + if (state->i2c_cec == NULL) + return; + + v4l2_dbg(1, debug, sd, "%s: cec_clk %d\n", __func__, cec_clk); + + /* cec soft reset */ + adv7511_cec_write(sd, 0x50, 0x01); + adv7511_cec_write(sd, 0x50, 0x00); + + /* legacy mode */ + adv7511_cec_write(sd, 0x4a, 0x00); + adv7511_cec_write(sd, 0x4a, 0x07); + + if (cec_clk % 750000 != 0) + v4l2_err(sd, "%s: cec_clk %d, not multiple of 750 Khz\n", + __func__, cec_clk); + + ratio = (cec_clk / 750000) - 1; + adv7511_cec_write(sd, 0x4e, ratio << 2); +} + +static int adv7511_probe(struct i2c_client *client) +{ + struct adv7511_state *state; + struct adv7511_platform_data *pdata = client->dev.platform_data; + struct v4l2_ctrl_handler *hdl; + struct v4l2_subdev *sd; + u8 chip_id[2]; + int err = -EIO; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + state = devm_kzalloc(&client->dev, sizeof(struct adv7511_state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + /* Platform data */ + if (!pdata) { + v4l_err(client, "No platform data!\n"); + return -ENODEV; + } + memcpy(&state->pdata, pdata, sizeof(state->pdata)); + state->fmt_code = MEDIA_BUS_FMT_RGB888_1X24; + state->colorspace = V4L2_COLORSPACE_SRGB; + + sd = &state->sd; + + v4l2_dbg(1, debug, sd, "detecting adv7511 client on address 0x%x\n", + client->addr << 1); + + v4l2_i2c_subdev_init(sd, client, &adv7511_ops); + sd->internal_ops = &adv7511_int_ops; + + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 10); + /* add in ascending ID order */ + state->hdmi_mode_ctrl = v4l2_ctrl_new_std_menu(hdl, &adv7511_ctrl_ops, + V4L2_CID_DV_TX_MODE, V4L2_DV_TX_MODE_HDMI, + 0, V4L2_DV_TX_MODE_DVI_D); + state->hotplug_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_HOTPLUG, 0, 1, 0, 0); + state->rx_sense_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_RXSENSE, 0, 1, 0, 0); + state->have_edid0_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_TX_EDID_PRESENT, 0, 1, 0, 0); + state->rgb_quantization_range_ctrl = + v4l2_ctrl_new_std_menu(hdl, &adv7511_ctrl_ops, + V4L2_CID_DV_TX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, + 0, V4L2_DV_RGB_RANGE_AUTO); + state->content_type_ctrl = + v4l2_ctrl_new_std_menu(hdl, &adv7511_ctrl_ops, + V4L2_CID_DV_TX_IT_CONTENT_TYPE, V4L2_DV_IT_CONTENT_TYPE_NO_ITC, + 0, V4L2_DV_IT_CONTENT_TYPE_NO_ITC); + sd->ctrl_handler = hdl; + if (hdl->error) { + err = hdl->error; + goto err_hdl; + } + state->pad.flags = MEDIA_PAD_FL_SINK; + sd->entity.function = MEDIA_ENT_F_DV_ENCODER; + err = media_entity_pads_init(&sd->entity, 1, &state->pad); + if (err) + goto err_hdl; + + /* EDID and CEC i2c addr */ + state->i2c_edid_addr = state->pdata.i2c_edid << 1; + state->i2c_cec_addr = state->pdata.i2c_cec << 1; + state->i2c_pktmem_addr = state->pdata.i2c_pktmem << 1; + + state->chip_revision = adv7511_rd(sd, 0x0); + chip_id[0] = adv7511_rd(sd, 0xf5); + chip_id[1] = adv7511_rd(sd, 0xf6); + if (chip_id[0] != 0x75 || chip_id[1] != 0x11) { + v4l2_err(sd, "chip_id != 0x7511, read 0x%02x%02x\n", chip_id[0], + chip_id[1]); + err = -EIO; + goto err_entity; + } + + state->i2c_edid = i2c_new_dummy_device(client->adapter, + state->i2c_edid_addr >> 1); + if (IS_ERR(state->i2c_edid)) { + v4l2_err(sd, "failed to register edid i2c client\n"); + err = PTR_ERR(state->i2c_edid); + goto err_entity; + } + + adv7511_wr(sd, 0xe1, state->i2c_cec_addr); + if (state->pdata.cec_clk < 3000000 || + state->pdata.cec_clk > 100000000) { + v4l2_err(sd, "%s: cec_clk %u outside range, disabling cec\n", + __func__, state->pdata.cec_clk); + state->pdata.cec_clk = 0; + } + + if (state->pdata.cec_clk) { + state->i2c_cec = i2c_new_dummy_device(client->adapter, + state->i2c_cec_addr >> 1); + if (IS_ERR(state->i2c_cec)) { + v4l2_err(sd, "failed to register cec i2c client\n"); + err = PTR_ERR(state->i2c_cec); + goto err_unreg_edid; + } + adv7511_wr(sd, 0xe2, 0x00); /* power up cec section */ + } else { + adv7511_wr(sd, 0xe2, 0x01); /* power down cec section */ + } + + state->i2c_pktmem = i2c_new_dummy_device(client->adapter, state->i2c_pktmem_addr >> 1); + if (IS_ERR(state->i2c_pktmem)) { + v4l2_err(sd, "failed to register pktmem i2c client\n"); + err = PTR_ERR(state->i2c_pktmem); + goto err_unreg_cec; + } + + state->work_queue = create_singlethread_workqueue(sd->name); + if (state->work_queue == NULL) { + v4l2_err(sd, "could not create workqueue\n"); + err = -ENOMEM; + goto err_unreg_pktmem; + } + + INIT_DELAYED_WORK(&state->edid_handler, adv7511_edid_handler); + + adv7511_init_setup(sd); + +#if IS_ENABLED(CONFIG_VIDEO_ADV7511_CEC) + state->cec_adap = cec_allocate_adapter(&adv7511_cec_adap_ops, + state, dev_name(&client->dev), CEC_CAP_DEFAULTS, + ADV7511_MAX_ADDRS); + err = PTR_ERR_OR_ZERO(state->cec_adap); + if (err) { + destroy_workqueue(state->work_queue); + goto err_unreg_pktmem; + } +#endif + + adv7511_set_isr(sd, true); + adv7511_check_monitor_present_status(sd); + + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + return 0; + +err_unreg_pktmem: + i2c_unregister_device(state->i2c_pktmem); +err_unreg_cec: + i2c_unregister_device(state->i2c_cec); +err_unreg_edid: + i2c_unregister_device(state->i2c_edid); +err_entity: + media_entity_cleanup(&sd->entity); +err_hdl: + v4l2_ctrl_handler_free(&state->hdl); + return err; +} + +/* ----------------------------------------------------------------------- */ + +static void adv7511_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7511_state *state = get_adv7511_state(sd); + + state->chip_revision = -1; + + v4l2_dbg(1, debug, sd, "%s removed @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + + adv7511_set_isr(sd, false); + adv7511_init_setup(sd); + cancel_delayed_work_sync(&state->edid_handler); + i2c_unregister_device(state->i2c_edid); + i2c_unregister_device(state->i2c_cec); + i2c_unregister_device(state->i2c_pktmem); + destroy_workqueue(state->work_queue); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id adv7511_id[] = { + { .name = "adv7511-v4l2" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7511_id); + +static struct i2c_driver adv7511_driver = { + .driver = { + .name = "adv7511-v4l2", + }, + .probe = adv7511_probe, + .remove = adv7511_remove, + .id_table = adv7511_id, +}; + +module_i2c_driver(adv7511_driver); diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c new file mode 100644 index 000000000..ac9c69ce4 --- /dev/null +++ b/drivers/media/i2c/adv7604.c @@ -0,0 +1,3766 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * adv7604 - Analog Devices ADV7604 video decoder driver + * + * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + */ + +/* + * References (c = chapter, p = page): + * REF_01 - Analog devices, ADV7604, Register Settings Recommendations, + * Revision 2.5, June 2010 + * REF_02 - Analog devices, Register map documentation, Documentation of + * the register maps, Software manual, Rev. F, June 2010 + * REF_03 - Analog devices, ADV7604, Hardware Manual, Rev. F, August 2010 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +MODULE_DESCRIPTION("Analog Devices ADV7604/10/11/12 video decoder driver"); +MODULE_AUTHOR("Hans Verkuil "); +MODULE_AUTHOR("Mats Randgaard "); +MODULE_LICENSE("GPL"); + +/* ADV7604 system clock frequency */ +#define ADV76XX_FSC (28636360) + +#define ADV76XX_RGB_OUT (1 << 1) + +#define ADV76XX_OP_FORMAT_SEL_8BIT (0 << 0) +#define ADV7604_OP_FORMAT_SEL_10BIT (1 << 0) +#define ADV76XX_OP_FORMAT_SEL_12BIT (2 << 0) + +#define ADV76XX_OP_MODE_SEL_SDR_422 (0 << 5) +#define ADV7604_OP_MODE_SEL_DDR_422 (1 << 5) +#define ADV76XX_OP_MODE_SEL_SDR_444 (2 << 5) +#define ADV7604_OP_MODE_SEL_DDR_444 (3 << 5) +#define ADV76XX_OP_MODE_SEL_SDR_422_2X (4 << 5) +#define ADV7604_OP_MODE_SEL_ADI_CM (5 << 5) + +#define ADV76XX_OP_CH_SEL_GBR (0 << 5) +#define ADV76XX_OP_CH_SEL_GRB (1 << 5) +#define ADV76XX_OP_CH_SEL_BGR (2 << 5) +#define ADV76XX_OP_CH_SEL_RGB (3 << 5) +#define ADV76XX_OP_CH_SEL_BRG (4 << 5) +#define ADV76XX_OP_CH_SEL_RBG (5 << 5) + +#define ADV76XX_OP_SWAP_CB_CR (1 << 0) + +#define ADV76XX_MAX_ADDRS (3) + +#define ADV76XX_MAX_EDID_BLOCKS 4 + +enum adv76xx_type { + ADV7604, + ADV7611, // including ADV7610 + ADV7612, +}; + +struct adv76xx_reg_seq { + unsigned int reg; + u8 val; +}; + +struct adv76xx_format_info { + u32 code; + u8 op_ch_sel; + bool rgb_out; + bool swap_cb_cr; + u8 op_format_sel; +}; + +struct adv76xx_cfg_read_infoframe { + const char *desc; + u8 present_mask; + u8 head_addr; + u8 payload_addr; +}; + +struct adv76xx_chip_info { + enum adv76xx_type type; + + bool has_afe; + unsigned int max_port; + unsigned int num_dv_ports; + + unsigned int edid_enable_reg; + unsigned int edid_status_reg; + unsigned int edid_segment_reg; + unsigned int edid_segment_mask; + unsigned int edid_spa_loc_reg; + unsigned int edid_spa_loc_msb_mask; + unsigned int edid_spa_port_b_reg; + unsigned int lcf_reg; + + unsigned int cable_det_mask; + unsigned int tdms_lock_mask; + unsigned int fmt_change_digital_mask; + unsigned int cp_csc; + + unsigned int cec_irq_status; + unsigned int cec_rx_enable; + unsigned int cec_rx_enable_mask; + bool cec_irq_swap; + + const struct adv76xx_format_info *formats; + unsigned int nformats; + + void (*set_termination)(struct v4l2_subdev *sd, bool enable); + void (*setup_irqs)(struct v4l2_subdev *sd); + unsigned int (*read_hdmi_pixelclock)(struct v4l2_subdev *sd); + unsigned int (*read_cable_det)(struct v4l2_subdev *sd); + + /* 0 = AFE, 1 = HDMI */ + const struct adv76xx_reg_seq *recommended_settings[2]; + unsigned int num_recommended_settings[2]; + + unsigned long page_mask; + + /* Masks for timings */ + unsigned int linewidth_mask; + unsigned int field0_height_mask; + unsigned int field1_height_mask; + unsigned int hfrontporch_mask; + unsigned int hsync_mask; + unsigned int hbackporch_mask; + unsigned int field0_vfrontporch_mask; + unsigned int field1_vfrontporch_mask; + unsigned int field0_vsync_mask; + unsigned int field1_vsync_mask; + unsigned int field0_vbackporch_mask; + unsigned int field1_vbackporch_mask; +}; + +/* + ********************************************************************** + * + * Arrays with configuration parameters for the ADV7604 + * + ********************************************************************** + */ + +struct adv76xx_state { + const struct adv76xx_chip_info *info; + struct adv76xx_platform_data pdata; + + struct gpio_desc *hpd_gpio[4]; + struct gpio_desc *reset_gpio; + + struct v4l2_subdev sd; + struct media_pad pads[ADV76XX_PAD_MAX]; + unsigned int source_pad; + + struct v4l2_ctrl_handler hdl; + + enum adv76xx_pad selected_input; + + struct v4l2_dv_timings timings; + const struct adv76xx_format_info *format; + + struct { + u8 edid[ADV76XX_MAX_EDID_BLOCKS * 128]; + u32 present; + unsigned blocks; + } edid; + u16 spa_port_a[2]; + struct v4l2_fract aspect_ratio; + u32 rgb_quantization_range; + struct delayed_work delayed_work_enable_hotplug; + bool restart_stdi_once; + + struct dentry *debugfs_dir; + struct v4l2_debugfs_if *infoframes; + + /* CEC */ + struct cec_adapter *cec_adap; + u8 cec_addr[ADV76XX_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; + + /* i2c clients */ + struct i2c_client *i2c_clients[ADV76XX_PAGE_MAX]; + + /* Regmaps */ + struct regmap *regmap[ADV76XX_PAGE_MAX]; + + /* controls */ + struct v4l2_ctrl *detect_tx_5v_ctrl; + struct v4l2_ctrl *analog_sampling_phase_ctrl; + struct v4l2_ctrl *free_run_color_manual_ctrl; + struct v4l2_ctrl *free_run_color_ctrl; + struct v4l2_ctrl *rgb_quantization_range_ctrl; +}; + +static bool adv76xx_has_afe(struct adv76xx_state *state) +{ + return state->info->has_afe; +} + +/* Unsupported timings. This device cannot support 720p30. */ +static const struct v4l2_dv_timings adv76xx_timings_exceptions[] = { + V4L2_DV_BT_CEA_1280X720P30, + { } +}; + +static bool adv76xx_check_dv_timings(const struct v4l2_dv_timings *t, void *hdl) +{ + int i; + + for (i = 0; adv76xx_timings_exceptions[i].bt.width; i++) + if (v4l2_match_dv_timings(t, adv76xx_timings_exceptions + i, 0, false)) + return false; + return true; +} + +struct adv76xx_video_standards { + struct v4l2_dv_timings timings; + u8 vid_std; + u8 v_freq; +}; + +/* sorted by number of lines */ +static const struct adv76xx_video_standards adv7604_prim_mode_comp[] = { + /* { V4L2_DV_BT_CEA_720X480P59_94, 0x0a, 0x00 }, TODO flickering */ + { V4L2_DV_BT_CEA_720X576P50, 0x0b, 0x00 }, + { V4L2_DV_BT_CEA_1280X720P50, 0x19, 0x01 }, + { V4L2_DV_BT_CEA_1280X720P60, 0x19, 0x00 }, + { V4L2_DV_BT_CEA_1920X1080P24, 0x1e, 0x04 }, + { V4L2_DV_BT_CEA_1920X1080P25, 0x1e, 0x03 }, + { V4L2_DV_BT_CEA_1920X1080P30, 0x1e, 0x02 }, + { V4L2_DV_BT_CEA_1920X1080P50, 0x1e, 0x01 }, + { V4L2_DV_BT_CEA_1920X1080P60, 0x1e, 0x00 }, + /* TODO add 1920x1080P60_RB (CVT timing) */ + { }, +}; + +/* sorted by number of lines */ +static const struct adv76xx_video_standards adv7604_prim_mode_gr[] = { + { V4L2_DV_BT_DMT_640X480P60, 0x08, 0x00 }, + { V4L2_DV_BT_DMT_640X480P72, 0x09, 0x00 }, + { V4L2_DV_BT_DMT_640X480P75, 0x0a, 0x00 }, + { V4L2_DV_BT_DMT_640X480P85, 0x0b, 0x00 }, + { V4L2_DV_BT_DMT_800X600P56, 0x00, 0x00 }, + { V4L2_DV_BT_DMT_800X600P60, 0x01, 0x00 }, + { V4L2_DV_BT_DMT_800X600P72, 0x02, 0x00 }, + { V4L2_DV_BT_DMT_800X600P75, 0x03, 0x00 }, + { V4L2_DV_BT_DMT_800X600P85, 0x04, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P60, 0x0c, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P70, 0x0d, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P75, 0x0e, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P85, 0x0f, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P60, 0x05, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P75, 0x06, 0x00 }, + { V4L2_DV_BT_DMT_1360X768P60, 0x12, 0x00 }, + { V4L2_DV_BT_DMT_1366X768P60, 0x13, 0x00 }, + { V4L2_DV_BT_DMT_1400X1050P60, 0x14, 0x00 }, + { V4L2_DV_BT_DMT_1400X1050P75, 0x15, 0x00 }, + { V4L2_DV_BT_DMT_1600X1200P60, 0x16, 0x00 }, /* TODO not tested */ + /* TODO add 1600X1200P60_RB (not a DMT timing) */ + { V4L2_DV_BT_DMT_1680X1050P60, 0x18, 0x00 }, + { V4L2_DV_BT_DMT_1920X1200P60_RB, 0x19, 0x00 }, /* TODO not tested */ + { }, +}; + +/* sorted by number of lines */ +static const struct adv76xx_video_standards adv76xx_prim_mode_hdmi_comp[] = { + { V4L2_DV_BT_CEA_720X480P59_94, 0x0a, 0x00 }, + { V4L2_DV_BT_CEA_720X576P50, 0x0b, 0x00 }, + { V4L2_DV_BT_CEA_1280X720P50, 0x13, 0x01 }, + { V4L2_DV_BT_CEA_1280X720P60, 0x13, 0x00 }, + { V4L2_DV_BT_CEA_1920X1080P24, 0x1e, 0x04 }, + { V4L2_DV_BT_CEA_1920X1080P25, 0x1e, 0x03 }, + { V4L2_DV_BT_CEA_1920X1080P30, 0x1e, 0x02 }, + { V4L2_DV_BT_CEA_1920X1080P50, 0x1e, 0x01 }, + { V4L2_DV_BT_CEA_1920X1080P60, 0x1e, 0x00 }, + { }, +}; + +/* sorted by number of lines */ +static const struct adv76xx_video_standards adv76xx_prim_mode_hdmi_gr[] = { + { V4L2_DV_BT_DMT_640X480P60, 0x08, 0x00 }, + { V4L2_DV_BT_DMT_640X480P72, 0x09, 0x00 }, + { V4L2_DV_BT_DMT_640X480P75, 0x0a, 0x00 }, + { V4L2_DV_BT_DMT_640X480P85, 0x0b, 0x00 }, + { V4L2_DV_BT_DMT_800X600P56, 0x00, 0x00 }, + { V4L2_DV_BT_DMT_800X600P60, 0x01, 0x00 }, + { V4L2_DV_BT_DMT_800X600P72, 0x02, 0x00 }, + { V4L2_DV_BT_DMT_800X600P75, 0x03, 0x00 }, + { V4L2_DV_BT_DMT_800X600P85, 0x04, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P60, 0x0c, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P70, 0x0d, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P75, 0x0e, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P85, 0x0f, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P60, 0x05, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P75, 0x06, 0x00 }, + { }, +}; + +static const struct v4l2_event adv76xx_ev_fmt = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, +}; + +/* ----------------------------------------------------------------------- */ + +static inline struct adv76xx_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv76xx_state, sd); +} + +static inline unsigned htotal(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_FRAME_WIDTH(t); +} + +static inline unsigned vtotal(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_FRAME_HEIGHT(t); +} + +/* ----------------------------------------------------------------------- */ + +static int adv76xx_read_check(struct adv76xx_state *state, + int client_page, u8 reg) +{ + struct i2c_client *client = state->i2c_clients[client_page]; + int err; + unsigned int val; + + err = regmap_read(state->regmap[client_page], reg, &val); + + if (err) { + v4l_err(client, "error reading %02x, %02x\n", + client->addr, reg); + return err; + } + return val; +} + +/* adv76xx_write_block(): Write raw data with a maximum of I2C_SMBUS_BLOCK_MAX + * size to one or more registers. + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +static int adv76xx_write_block(struct adv76xx_state *state, int client_page, + unsigned int init_reg, const void *val, + size_t val_len) +{ + struct regmap *regmap = state->regmap[client_page]; + + if (val_len > I2C_SMBUS_BLOCK_MAX) + val_len = I2C_SMBUS_BLOCK_MAX; + + return regmap_raw_write(regmap, init_reg, val, val_len); +} + +/* ----------------------------------------------------------------------- */ + +static inline int io_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_IO, reg); +} + +static inline int io_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_IO], reg, val); +} + +static inline int io_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) +{ + return io_write(sd, reg, (io_read(sd, reg) & ~mask) | val); +} + +static inline int __always_unused avlink_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV7604_PAGE_AVLINK, reg); +} + +static inline int __always_unused avlink_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV7604_PAGE_AVLINK], reg, val); +} + +static inline int cec_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_CEC, reg); +} + +static inline int cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_CEC], reg, val); +} + +static inline int cec_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) +{ + return cec_write(sd, reg, (cec_read(sd, reg) & ~mask) | val); +} + +static inline int infoframe_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_INFOFRAME, reg); +} + +static inline int __always_unused infoframe_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_INFOFRAME], reg, val); +} + +static inline int __always_unused afe_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_AFE, reg); +} + +static inline int afe_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_AFE], reg, val); +} + +static inline int rep_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_REP, reg); +} + +static inline int rep_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_REP], reg, val); +} + +static inline int rep_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return rep_write(sd, reg, (rep_read(sd, reg) & ~mask) | val); +} + +static inline int __always_unused edid_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_EDID, reg); +} + +static inline int __always_unused edid_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_EDID], reg, val); +} + +static inline int edid_write_block(struct v4l2_subdev *sd, + unsigned int total_len, const u8 *val) +{ + struct adv76xx_state *state = to_state(sd); + int err = 0; + int i = 0; + int len = 0; + + v4l2_dbg(2, debug, sd, "%s: write EDID block (%d byte)\n", + __func__, total_len); + + while (!err && i < total_len) { + len = (total_len - i) > I2C_SMBUS_BLOCK_MAX ? + I2C_SMBUS_BLOCK_MAX : + (total_len - i); + + err = adv76xx_write_block(state, ADV76XX_PAGE_EDID, + i, val + i, len); + i += len; + } + + return err; +} + +static void adv76xx_set_hpd(struct adv76xx_state *state, unsigned int hpd) +{ + const struct adv76xx_chip_info *info = state->info; + unsigned int i; + + if (info->type == ADV7604) { + for (i = 0; i < state->info->num_dv_ports; ++i) + gpiod_set_value_cansleep(state->hpd_gpio[i], hpd & BIT(i)); + } else { + for (i = 0; i < state->info->num_dv_ports; ++i) + io_write_clr_set(&state->sd, 0x20, 0x80 >> i, + (!!(hpd & BIT(i))) << (7 - i)); + } + + v4l2_subdev_notify(&state->sd, ADV76XX_HOTPLUG, &hpd); +} + +static void adv76xx_delayed_work_enable_hotplug(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct adv76xx_state *state = container_of(dwork, struct adv76xx_state, + delayed_work_enable_hotplug); + struct v4l2_subdev *sd = &state->sd; + + v4l2_dbg(2, debug, sd, "%s: enable hotplug\n", __func__); + + adv76xx_set_hpd(state, state->edid.present); +} + +static inline int hdmi_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_HDMI, reg); +} + +static u16 hdmi_read16(struct v4l2_subdev *sd, u8 reg, u16 mask) +{ + return ((hdmi_read(sd, reg) << 8) | hdmi_read(sd, reg + 1)) & mask; +} + +static inline int hdmi_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_HDMI], reg, val); +} + +static inline int hdmi_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return hdmi_write(sd, reg, (hdmi_read(sd, reg) & ~mask) | val); +} + +static inline int __always_unused test_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_TEST], reg, val); +} + +static inline int cp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV76XX_PAGE_CP, reg); +} + +static u16 cp_read16(struct v4l2_subdev *sd, u8 reg, u16 mask) +{ + return ((cp_read(sd, reg) << 8) | cp_read(sd, reg + 1)) & mask; +} + +static inline int cp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV76XX_PAGE_CP], reg, val); +} + +static inline int cp_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return cp_write(sd, reg, (cp_read(sd, reg) & ~mask) | val); +} + +static inline int __always_unused vdp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv76xx_state *state = to_state(sd); + + return adv76xx_read_check(state, ADV7604_PAGE_VDP, reg); +} + +static inline int __always_unused vdp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + + return regmap_write(state->regmap[ADV7604_PAGE_VDP], reg, val); +} + +#define ADV76XX_REG(page, offset) (((page) << 8) | (offset)) +#define ADV76XX_REG_SEQ_TERM 0xffff + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int adv76xx_read_reg(struct v4l2_subdev *sd, unsigned int reg) +{ + struct adv76xx_state *state = to_state(sd); + unsigned int page = reg >> 8; + unsigned int val; + int err; + + if (page >= ADV76XX_PAGE_MAX || !(BIT(page) & state->info->page_mask)) + return -EINVAL; + + reg &= 0xff; + err = regmap_read(state->regmap[page], reg, &val); + + return err ? err : val; +} +#endif + +static int adv76xx_write_reg(struct v4l2_subdev *sd, unsigned int reg, u8 val) +{ + struct adv76xx_state *state = to_state(sd); + unsigned int page = reg >> 8; + + if (page >= ADV76XX_PAGE_MAX || !(BIT(page) & state->info->page_mask)) + return -EINVAL; + + reg &= 0xff; + + return regmap_write(state->regmap[page], reg, val); +} + +static void adv76xx_write_reg_seq(struct v4l2_subdev *sd, + const struct adv76xx_reg_seq *reg_seq) +{ + unsigned int i; + + for (i = 0; reg_seq[i].reg != ADV76XX_REG_SEQ_TERM; i++) + adv76xx_write_reg(sd, reg_seq[i].reg, reg_seq[i].val); +} + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +static const struct adv76xx_format_info adv7604_formats[] = { + { MEDIA_BUS_FMT_RGB888_1X24, ADV76XX_OP_CH_SEL_RGB, true, false, + ADV76XX_OP_MODE_SEL_SDR_444 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_2X8, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_2X8, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV10_2X10, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV7604_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YVYU10_2X10, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV7604_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YUYV12_2X12, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YVYU12_2X12, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_UYVY8_1X16, ADV76XX_OP_CH_SEL_RBG, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_VYUY8_1X16, ADV76XX_OP_CH_SEL_RBG, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_1X16, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_1X16, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_UYVY10_1X20, ADV76XX_OP_CH_SEL_RBG, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV7604_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_VYUY10_1X20, ADV76XX_OP_CH_SEL_RBG, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV7604_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YUYV10_1X20, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV7604_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YVYU10_1X20, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV7604_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_UYVY12_1X24, ADV76XX_OP_CH_SEL_RBG, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_VYUY12_1X24, ADV76XX_OP_CH_SEL_RBG, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YUYV12_1X24, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YVYU12_1X24, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, +}; + +static const struct adv76xx_format_info adv7611_formats[] = { + { MEDIA_BUS_FMT_RGB888_1X24, ADV76XX_OP_CH_SEL_RGB, true, false, + ADV76XX_OP_MODE_SEL_SDR_444 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_2X8, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_2X8, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV12_2X12, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YVYU12_2X12, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_UYVY8_1X16, ADV76XX_OP_CH_SEL_RBG, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_VYUY8_1X16, ADV76XX_OP_CH_SEL_RBG, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_1X16, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_1X16, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_UYVY12_1X24, ADV76XX_OP_CH_SEL_RBG, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_VYUY12_1X24, ADV76XX_OP_CH_SEL_RBG, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YUYV12_1X24, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YVYU12_1X24, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_12BIT }, +}; + +static const struct adv76xx_format_info adv7612_formats[] = { + { MEDIA_BUS_FMT_RGB888_1X24, ADV76XX_OP_CH_SEL_RGB, true, false, + ADV76XX_OP_MODE_SEL_SDR_444 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_2X8, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_2X8, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422 | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_UYVY8_1X16, ADV76XX_OP_CH_SEL_RBG, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_VYUY8_1X16, ADV76XX_OP_CH_SEL_RBG, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_1X16, ADV76XX_OP_CH_SEL_RGB, false, false, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_1X16, ADV76XX_OP_CH_SEL_RGB, false, true, + ADV76XX_OP_MODE_SEL_SDR_422_2X | ADV76XX_OP_FORMAT_SEL_8BIT }, +}; + +static const struct adv76xx_format_info * +adv76xx_format_info(struct adv76xx_state *state, u32 code) +{ + unsigned int i; + + for (i = 0; i < state->info->nformats; ++i) { + if (state->info->formats[i].code == code) + return &state->info->formats[i]; + } + + return NULL; +} + +/* ----------------------------------------------------------------------- */ + +static inline bool is_analog_input(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + return state->selected_input == ADV7604_PAD_VGA_RGB || + state->selected_input == ADV7604_PAD_VGA_COMP; +} + +static inline bool is_digital_input(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + return state->selected_input == ADV76XX_PAD_HDMI_PORT_A || + state->selected_input == ADV7604_PAD_HDMI_PORT_B || + state->selected_input == ADV7604_PAD_HDMI_PORT_C || + state->selected_input == ADV7604_PAD_HDMI_PORT_D; +} + +static const struct v4l2_dv_timings_cap adv7604_timings_cap_analog = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + V4L2_INIT_BT_TIMINGS(640, 1920, 350, 1200, 25000000, 170000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM) +}; + +static const struct v4l2_dv_timings_cap adv76xx_timings_cap_digital = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + V4L2_INIT_BT_TIMINGS(640, 1920, 350, 1200, 25000000, 225000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM) +}; + +/* + * Return the DV timings capabilities for the requested sink pad. As a special + * case, pad value -1 returns the capabilities for the currently selected input. + */ +static const struct v4l2_dv_timings_cap * +adv76xx_get_dv_timings_cap(struct v4l2_subdev *sd, int pad) +{ + if (pad == -1) { + struct adv76xx_state *state = to_state(sd); + + pad = state->selected_input; + } + + switch (pad) { + case ADV76XX_PAD_HDMI_PORT_A: + case ADV7604_PAD_HDMI_PORT_B: + case ADV7604_PAD_HDMI_PORT_C: + case ADV7604_PAD_HDMI_PORT_D: + return &adv76xx_timings_cap_digital; + + case ADV7604_PAD_VGA_RGB: + case ADV7604_PAD_VGA_COMP: + default: + return &adv7604_timings_cap_analog; + } +} + + +/* ----------------------------------------------------------------------- */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static void adv76xx_inv_register(struct v4l2_subdev *sd) +{ + v4l2_info(sd, "0x000-0x0ff: IO Map\n"); + v4l2_info(sd, "0x100-0x1ff: AVLink Map\n"); + v4l2_info(sd, "0x200-0x2ff: CEC Map\n"); + v4l2_info(sd, "0x300-0x3ff: InfoFrame Map\n"); + v4l2_info(sd, "0x400-0x4ff: ESDP Map\n"); + v4l2_info(sd, "0x500-0x5ff: DPP Map\n"); + v4l2_info(sd, "0x600-0x6ff: AFE Map\n"); + v4l2_info(sd, "0x700-0x7ff: Repeater Map\n"); + v4l2_info(sd, "0x800-0x8ff: EDID Map\n"); + v4l2_info(sd, "0x900-0x9ff: HDMI Map\n"); + v4l2_info(sd, "0xa00-0xaff: Test Map\n"); + v4l2_info(sd, "0xb00-0xbff: CP Map\n"); + v4l2_info(sd, "0xc00-0xcff: VDP Map\n"); +} + +static int adv76xx_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + int ret; + + ret = adv76xx_read_reg(sd, reg->reg); + if (ret < 0) { + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv76xx_inv_register(sd); + return ret; + } + + reg->size = 1; + reg->val = ret; + + return 0; +} + +static int adv76xx_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + int ret; + + ret = adv76xx_write_reg(sd, reg->reg, reg->val); + if (ret < 0) { + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv76xx_inv_register(sd); + return ret; + } + + return 0; +} +#endif + +static unsigned int adv7604_read_cable_det(struct v4l2_subdev *sd) +{ + u8 value = io_read(sd, 0x6f); + + return ((value & 0x10) >> 4) + | ((value & 0x08) >> 2) + | ((value & 0x04) << 0) + | ((value & 0x02) << 2); +} + +static unsigned int adv7611_read_cable_det(struct v4l2_subdev *sd) +{ + u8 value = io_read(sd, 0x6f); + + return value & 1; +} + +static unsigned int adv7612_read_cable_det(struct v4l2_subdev *sd) +{ + /* Reads CABLE_DET_A_RAW. For input B support, need to + * account for bit 7 [MSB] of 0x6a (ie. CABLE_DET_B_RAW) + */ + u8 value = io_read(sd, 0x6f); + + return value & 1; +} + +static int adv76xx_s_detect_tx_5v_ctrl(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + u16 cable_det = info->read_cable_det(sd); + + return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, cable_det); +} + +static int find_and_set_predefined_video_timings(struct v4l2_subdev *sd, + u8 prim_mode, + const struct adv76xx_video_standards *predef_vid_timings, + const struct v4l2_dv_timings *timings) +{ + int i; + + for (i = 0; predef_vid_timings[i].timings.bt.width; i++) { + if (!v4l2_match_dv_timings(timings, &predef_vid_timings[i].timings, + is_digital_input(sd) ? 250000 : 1000000, false)) + continue; + io_write(sd, 0x00, predef_vid_timings[i].vid_std); /* video std */ + io_write(sd, 0x01, (predef_vid_timings[i].v_freq << 4) + + prim_mode); /* v_freq and prim mode */ + return 0; + } + + return -1; +} + +static int configure_predefined_video_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv76xx_state *state = to_state(sd); + int err; + + v4l2_dbg(1, debug, sd, "%s", __func__); + + if (adv76xx_has_afe(state)) { + /* reset to default values */ + io_write(sd, 0x16, 0x43); + io_write(sd, 0x17, 0x5a); + } + /* disable embedded syncs for auto graphics mode */ + cp_write_clr_set(sd, 0x81, 0x10, 0x00); + cp_write(sd, 0x8f, 0x00); + cp_write(sd, 0x90, 0x00); + cp_write(sd, 0xa2, 0x00); + cp_write(sd, 0xa3, 0x00); + cp_write(sd, 0xa4, 0x00); + cp_write(sd, 0xa5, 0x00); + cp_write(sd, 0xa6, 0x00); + cp_write(sd, 0xa7, 0x00); + cp_write(sd, 0xab, 0x00); + cp_write(sd, 0xac, 0x00); + + if (is_analog_input(sd)) { + err = find_and_set_predefined_video_timings(sd, + 0x01, adv7604_prim_mode_comp, timings); + if (err) + err = find_and_set_predefined_video_timings(sd, + 0x02, adv7604_prim_mode_gr, timings); + } else if (is_digital_input(sd)) { + err = find_and_set_predefined_video_timings(sd, + 0x05, adv76xx_prim_mode_hdmi_comp, timings); + if (err) + err = find_and_set_predefined_video_timings(sd, + 0x06, adv76xx_prim_mode_hdmi_gr, timings); + } else { + v4l2_dbg(2, debug, sd, "%s: Unknown port %d selected\n", + __func__, state->selected_input); + err = -1; + } + + + return err; +} + +static void configure_custom_video_timings(struct v4l2_subdev *sd, + const struct v4l2_bt_timings *bt) +{ + struct adv76xx_state *state = to_state(sd); + u32 width = htotal(bt); + u32 height = vtotal(bt); + u16 cp_start_sav = bt->hsync + bt->hbackporch - 4; + u16 cp_start_eav = width - bt->hfrontporch; + u16 cp_start_vbi = height - bt->vfrontporch; + u16 cp_end_vbi = bt->vsync + bt->vbackporch; + u16 ch1_fr_ll = (((u32)bt->pixelclock / 100) > 0) ? + ((width * (ADV76XX_FSC / 100)) / ((u32)bt->pixelclock / 100)) : 0; + const u8 pll[2] = { + 0xc0 | ((width >> 8) & 0x1f), + width & 0xff + }; + + v4l2_dbg(2, debug, sd, "%s\n", __func__); + + if (is_analog_input(sd)) { + /* auto graphics */ + io_write(sd, 0x00, 0x07); /* video std */ + io_write(sd, 0x01, 0x02); /* prim mode */ + /* enable embedded syncs for auto graphics mode */ + cp_write_clr_set(sd, 0x81, 0x10, 0x10); + + /* Should only be set in auto-graphics mode [REF_02, p. 91-92] */ + /* setup PLL_DIV_MAN_EN and PLL_DIV_RATIO */ + /* IO-map reg. 0x16 and 0x17 should be written in sequence */ + if (regmap_raw_write(state->regmap[ADV76XX_PAGE_IO], + 0x16, pll, 2)) + v4l2_err(sd, "writing to reg 0x16 and 0x17 failed\n"); + + /* active video - horizontal timing */ + cp_write(sd, 0xa2, (cp_start_sav >> 4) & 0xff); + cp_write(sd, 0xa3, ((cp_start_sav & 0x0f) << 4) | + ((cp_start_eav >> 8) & 0x0f)); + cp_write(sd, 0xa4, cp_start_eav & 0xff); + + /* active video - vertical timing */ + cp_write(sd, 0xa5, (cp_start_vbi >> 4) & 0xff); + cp_write(sd, 0xa6, ((cp_start_vbi & 0xf) << 4) | + ((cp_end_vbi >> 8) & 0xf)); + cp_write(sd, 0xa7, cp_end_vbi & 0xff); + } else if (is_digital_input(sd)) { + /* set default prim_mode/vid_std for HDMI + according to [REF_03, c. 4.2] */ + io_write(sd, 0x00, 0x02); /* video std */ + io_write(sd, 0x01, 0x06); /* prim mode */ + } else { + v4l2_dbg(2, debug, sd, "%s: Unknown port %d selected\n", + __func__, state->selected_input); + } + + cp_write(sd, 0x8f, (ch1_fr_ll >> 8) & 0x7); + cp_write(sd, 0x90, ch1_fr_ll & 0xff); + cp_write(sd, 0xab, (height >> 4) & 0xff); + cp_write(sd, 0xac, (height & 0x0f) << 4); +} + +static void adv76xx_set_offset(struct v4l2_subdev *sd, bool auto_offset, u16 offset_a, u16 offset_b, u16 offset_c) +{ + struct adv76xx_state *state = to_state(sd); + u8 offset_buf[4]; + + if (auto_offset) { + offset_a = 0x3ff; + offset_b = 0x3ff; + offset_c = 0x3ff; + } + + v4l2_dbg(2, debug, sd, "%s: %s offset: a = 0x%x, b = 0x%x, c = 0x%x\n", + __func__, auto_offset ? "Auto" : "Manual", + offset_a, offset_b, offset_c); + + offset_buf[0] = (cp_read(sd, 0x77) & 0xc0) | ((offset_a & 0x3f0) >> 4); + offset_buf[1] = ((offset_a & 0x00f) << 4) | ((offset_b & 0x3c0) >> 6); + offset_buf[2] = ((offset_b & 0x03f) << 2) | ((offset_c & 0x300) >> 8); + offset_buf[3] = offset_c & 0x0ff; + + /* Registers must be written in this order with no i2c access in between */ + if (regmap_raw_write(state->regmap[ADV76XX_PAGE_CP], + 0x77, offset_buf, 4)) + v4l2_err(sd, "%s: i2c error writing to CP reg 0x77, 0x78, 0x79, 0x7a\n", __func__); +} + +static void adv76xx_set_gain(struct v4l2_subdev *sd, bool auto_gain, u16 gain_a, u16 gain_b, u16 gain_c) +{ + struct adv76xx_state *state = to_state(sd); + u8 gain_buf[4]; + u8 gain_man = 1; + u8 agc_mode_man = 1; + + if (auto_gain) { + gain_man = 0; + agc_mode_man = 0; + gain_a = 0x100; + gain_b = 0x100; + gain_c = 0x100; + } + + v4l2_dbg(2, debug, sd, "%s: %s gain: a = 0x%x, b = 0x%x, c = 0x%x\n", + __func__, auto_gain ? "Auto" : "Manual", + gain_a, gain_b, gain_c); + + gain_buf[0] = ((gain_man << 7) | (agc_mode_man << 6) | ((gain_a & 0x3f0) >> 4)); + gain_buf[1] = (((gain_a & 0x00f) << 4) | ((gain_b & 0x3c0) >> 6)); + gain_buf[2] = (((gain_b & 0x03f) << 2) | ((gain_c & 0x300) >> 8)); + gain_buf[3] = ((gain_c & 0x0ff)); + + /* Registers must be written in this order with no i2c access in between */ + if (regmap_raw_write(state->regmap[ADV76XX_PAGE_CP], + 0x73, gain_buf, 4)) + v4l2_err(sd, "%s: i2c error writing to CP reg 0x73, 0x74, 0x75, 0x76\n", __func__); +} + +static void set_rgb_quantization_range(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + bool rgb_output = io_read(sd, 0x02) & 0x02; + bool hdmi_signal = hdmi_read(sd, 0x05) & 0x80; + u8 y = HDMI_COLORSPACE_RGB; + + if (hdmi_signal && (io_read(sd, 0x60) & 1)) + y = infoframe_read(sd, 0x01) >> 5; + + v4l2_dbg(2, debug, sd, "%s: RGB quantization range: %d, RGB out: %d, HDMI: %d\n", + __func__, state->rgb_quantization_range, + rgb_output, hdmi_signal); + + adv76xx_set_gain(sd, true, 0x0, 0x0, 0x0); + adv76xx_set_offset(sd, true, 0x0, 0x0, 0x0); + io_write_clr_set(sd, 0x02, 0x04, rgb_output ? 0 : 4); + + switch (state->rgb_quantization_range) { + case V4L2_DV_RGB_RANGE_AUTO: + if (state->selected_input == ADV7604_PAD_VGA_RGB) { + /* Receiving analog RGB signal + * Set RGB full range (0-255) */ + io_write_clr_set(sd, 0x02, 0xf0, 0x10); + break; + } + + if (state->selected_input == ADV7604_PAD_VGA_COMP) { + /* Receiving analog YPbPr signal + * Set automode */ + io_write_clr_set(sd, 0x02, 0xf0, 0xf0); + break; + } + + if (hdmi_signal) { + /* Receiving HDMI signal + * Set automode */ + io_write_clr_set(sd, 0x02, 0xf0, 0xf0); + break; + } + + /* Receiving DVI-D signal + * ADV7604 selects RGB limited range regardless of + * input format (CE/IT) in automatic mode */ + if (state->timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) { + /* RGB limited range (16-235) */ + io_write_clr_set(sd, 0x02, 0xf0, 0x00); + } else { + /* RGB full range (0-255) */ + io_write_clr_set(sd, 0x02, 0xf0, 0x10); + + if (is_digital_input(sd) && rgb_output) { + adv76xx_set_offset(sd, false, 0x40, 0x40, 0x40); + } else { + adv76xx_set_gain(sd, false, 0xe0, 0xe0, 0xe0); + adv76xx_set_offset(sd, false, 0x70, 0x70, 0x70); + } + } + break; + case V4L2_DV_RGB_RANGE_LIMITED: + if (state->selected_input == ADV7604_PAD_VGA_COMP) { + /* YCrCb limited range (16-235) */ + io_write_clr_set(sd, 0x02, 0xf0, 0x20); + break; + } + + if (y != HDMI_COLORSPACE_RGB) + break; + + /* RGB limited range (16-235) */ + io_write_clr_set(sd, 0x02, 0xf0, 0x00); + + break; + case V4L2_DV_RGB_RANGE_FULL: + if (state->selected_input == ADV7604_PAD_VGA_COMP) { + /* YCrCb full range (0-255) */ + io_write_clr_set(sd, 0x02, 0xf0, 0x60); + break; + } + + if (y != HDMI_COLORSPACE_RGB) + break; + + /* RGB full range (0-255) */ + io_write_clr_set(sd, 0x02, 0xf0, 0x10); + + if (is_analog_input(sd) || hdmi_signal) + break; + + /* Adjust gain/offset for DVI-D signals only */ + if (rgb_output) { + adv76xx_set_offset(sd, false, 0x40, 0x40, 0x40); + } else { + adv76xx_set_gain(sd, false, 0xe0, 0xe0, 0xe0); + adv76xx_set_offset(sd, false, 0x70, 0x70, 0x70); + } + break; + } +} + +static int adv76xx_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = + &container_of(ctrl->handler, struct adv76xx_state, hdl)->sd; + + struct adv76xx_state *state = to_state(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + cp_write(sd, 0x3c, ctrl->val); + return 0; + case V4L2_CID_CONTRAST: + cp_write(sd, 0x3a, ctrl->val); + return 0; + case V4L2_CID_SATURATION: + cp_write(sd, 0x3b, ctrl->val); + return 0; + case V4L2_CID_HUE: + cp_write(sd, 0x3d, ctrl->val); + return 0; + case V4L2_CID_DV_RX_RGB_RANGE: + state->rgb_quantization_range = ctrl->val; + set_rgb_quantization_range(sd); + return 0; + case V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE: + if (!adv76xx_has_afe(state)) + return -EINVAL; + /* Set the analog sampling phase. This is needed to find the + best sampling phase for analog video: an application or + driver has to try a number of phases and analyze the picture + quality before settling on the best performing phase. */ + afe_write(sd, 0xc8, ctrl->val); + return 0; + case V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL: + /* Use the default blue color for free running mode, + or supply your own. */ + cp_write_clr_set(sd, 0xbf, 0x04, ctrl->val << 2); + return 0; + case V4L2_CID_ADV_RX_FREE_RUN_COLOR: + cp_write(sd, 0xc0, (ctrl->val & 0xff0000) >> 16); + cp_write(sd, 0xc1, (ctrl->val & 0x00ff00) >> 8); + cp_write(sd, 0xc2, (u8)(ctrl->val & 0x0000ff)); + return 0; + } + return -EINVAL; +} + +static int adv76xx_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = + &container_of(ctrl->handler, struct adv76xx_state, hdl)->sd; + + if (ctrl->id == V4L2_CID_DV_RX_IT_CONTENT_TYPE) { + ctrl->val = V4L2_DV_IT_CONTENT_TYPE_NO_ITC; + if ((io_read(sd, 0x60) & 1) && (infoframe_read(sd, 0x03) & 0x80)) + ctrl->val = (infoframe_read(sd, 0x05) >> 4) & 3; + return 0; + } + return -EINVAL; +} + +/* ----------------------------------------------------------------------- */ + +static inline bool no_power(struct v4l2_subdev *sd) +{ + /* Entire chip or CP powered off */ + return io_read(sd, 0x0c) & 0x24; +} + +static inline bool no_signal_tmds(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + return !(io_read(sd, 0x6a) & (0x10 >> state->selected_input)); +} + +static inline bool no_lock_tmds(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + + return (io_read(sd, 0x6a) & info->tdms_lock_mask) != info->tdms_lock_mask; +} + +static inline bool is_hdmi(struct v4l2_subdev *sd) +{ + return hdmi_read(sd, 0x05) & 0x80; +} + +static inline bool no_lock_sspd(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + /* + * Chips without a AFE don't expose registers for the SSPD, so just assume + * that we have a lock. + */ + if (adv76xx_has_afe(state)) + return false; + + /* TODO channel 2 */ + return ((cp_read(sd, 0xb5) & 0xd0) != 0xd0); +} + +static inline bool no_lock_stdi(struct v4l2_subdev *sd) +{ + /* TODO channel 2 */ + return !(cp_read(sd, 0xb1) & 0x80); +} + +static inline bool no_signal(struct v4l2_subdev *sd) +{ + bool ret; + + ret = no_power(sd); + + ret |= no_lock_stdi(sd); + ret |= no_lock_sspd(sd); + + if (is_digital_input(sd)) { + ret |= no_lock_tmds(sd); + ret |= no_signal_tmds(sd); + } + + return ret; +} + +static inline bool no_lock_cp(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + if (!adv76xx_has_afe(state)) + return false; + + /* CP has detected a non standard number of lines on the incoming + video compared to what it is configured to receive by s_dv_timings */ + return io_read(sd, 0x12) & 0x01; +} + +static inline bool in_free_run(struct v4l2_subdev *sd) +{ + return cp_read(sd, 0xff) & 0x10; +} + +static int adv76xx_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + *status = 0; + *status |= no_power(sd) ? V4L2_IN_ST_NO_POWER : 0; + *status |= no_signal(sd) ? V4L2_IN_ST_NO_SIGNAL : 0; + if (!in_free_run(sd) && no_lock_cp(sd)) + *status |= is_digital_input(sd) ? + V4L2_IN_ST_NO_SYNC : V4L2_IN_ST_NO_H_LOCK; + + v4l2_dbg(1, debug, sd, "%s: status = 0x%x\n", __func__, *status); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +struct stdi_readback { + u16 bl, lcf, lcvs; + u8 hs_pol, vs_pol; + bool interlaced; +}; + +static int stdi2dv_timings(struct v4l2_subdev *sd, + struct stdi_readback *stdi, + struct v4l2_dv_timings *timings) +{ + struct adv76xx_state *state = to_state(sd); + u32 hfreq = (ADV76XX_FSC * 8) / stdi->bl; + u32 pix_clk; + int i; + + for (i = 0; v4l2_dv_timings_presets[i].bt.width; i++) { + const struct v4l2_bt_timings *bt = &v4l2_dv_timings_presets[i].bt; + + if (!v4l2_valid_dv_timings(&v4l2_dv_timings_presets[i], + adv76xx_get_dv_timings_cap(sd, -1), + adv76xx_check_dv_timings, NULL)) + continue; + if (vtotal(bt) != stdi->lcf + 1) + continue; + if (bt->vsync != stdi->lcvs) + continue; + + pix_clk = hfreq * htotal(bt); + + if ((pix_clk < bt->pixelclock + 1000000) && + (pix_clk > bt->pixelclock - 1000000)) { + *timings = v4l2_dv_timings_presets[i]; + return 0; + } + } + + if (v4l2_detect_cvt(stdi->lcf + 1, hfreq, stdi->lcvs, 0, + (stdi->hs_pol == '+' ? V4L2_DV_HSYNC_POS_POL : 0) | + (stdi->vs_pol == '+' ? V4L2_DV_VSYNC_POS_POL : 0), + false, adv76xx_get_dv_timings_cap(sd, -1), timings)) + return 0; + if (v4l2_detect_gtf(stdi->lcf + 1, hfreq, stdi->lcvs, + (stdi->hs_pol == '+' ? V4L2_DV_HSYNC_POS_POL : 0) | + (stdi->vs_pol == '+' ? V4L2_DV_VSYNC_POS_POL : 0), + false, state->aspect_ratio, + adv76xx_get_dv_timings_cap(sd, -1), timings)) + return 0; + + v4l2_dbg(2, debug, sd, + "%s: No format candidate found for lcvs = %d, lcf=%d, bl = %d, %chsync, %cvsync\n", + __func__, stdi->lcvs, stdi->lcf, stdi->bl, + stdi->hs_pol, stdi->vs_pol); + return -1; +} + + +static int read_stdi(struct v4l2_subdev *sd, struct stdi_readback *stdi) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + u8 polarity; + + if (no_lock_stdi(sd) || no_lock_sspd(sd)) { + v4l2_dbg(2, debug, sd, "%s: STDI and/or SSPD not locked\n", __func__); + return -1; + } + + /* read STDI */ + stdi->bl = cp_read16(sd, 0xb1, 0x3fff); + stdi->lcf = cp_read16(sd, info->lcf_reg, 0x7ff); + stdi->lcvs = cp_read(sd, 0xb3) >> 3; + stdi->interlaced = io_read(sd, 0x12) & 0x10; + + if (adv76xx_has_afe(state)) { + /* read SSPD */ + polarity = cp_read(sd, 0xb5); + if ((polarity & 0x03) == 0x01) { + stdi->hs_pol = polarity & 0x10 + ? (polarity & 0x08 ? '+' : '-') : 'x'; + stdi->vs_pol = polarity & 0x40 + ? (polarity & 0x20 ? '+' : '-') : 'x'; + } else { + stdi->hs_pol = 'x'; + stdi->vs_pol = 'x'; + } + } else { + polarity = hdmi_read(sd, 0x05); + stdi->hs_pol = polarity & 0x20 ? '+' : '-'; + stdi->vs_pol = polarity & 0x10 ? '+' : '-'; + } + + if (no_lock_stdi(sd) || no_lock_sspd(sd)) { + v4l2_dbg(2, debug, sd, + "%s: signal lost during readout of STDI/SSPD\n", __func__); + return -1; + } + + if (stdi->lcf < 239 || stdi->bl < 8 || stdi->bl == 0x3fff) { + v4l2_dbg(2, debug, sd, "%s: invalid signal\n", __func__); + memset(stdi, 0, sizeof(struct stdi_readback)); + return -1; + } + + v4l2_dbg(2, debug, sd, + "%s: lcf (frame height - 1) = %d, bl = %d, lcvs (vsync) = %d, %chsync, %cvsync, %s\n", + __func__, stdi->lcf, stdi->bl, stdi->lcvs, + stdi->hs_pol, stdi->vs_pol, + stdi->interlaced ? "interlaced" : "progressive"); + + return 0; +} + +static int adv76xx_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + struct adv76xx_state *state = to_state(sd); + + if (timings->pad >= state->source_pad) + return -EINVAL; + + return v4l2_enum_dv_timings_cap(timings, + adv76xx_get_dv_timings_cap(sd, timings->pad), + adv76xx_check_dv_timings, NULL); +} + +static int adv76xx_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + struct adv76xx_state *state = to_state(sd); + unsigned int pad = cap->pad; + + if (cap->pad >= state->source_pad) + return -EINVAL; + + *cap = *adv76xx_get_dv_timings_cap(sd, pad); + cap->pad = pad; + + return 0; +} + +/* Fill the optional fields .standards and .flags in struct v4l2_dv_timings + if the format is listed in adv76xx_timings[] */ +static void adv76xx_fill_optional_dv_timings_fields(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + v4l2_find_dv_timings_cap(timings, adv76xx_get_dv_timings_cap(sd, -1), + is_digital_input(sd) ? 250000 : 1000000, + adv76xx_check_dv_timings, NULL); +} + +static unsigned int adv7604_read_hdmi_pixelclock(struct v4l2_subdev *sd) +{ + int a, b; + + a = hdmi_read(sd, 0x06); + b = hdmi_read(sd, 0x3b); + if (a < 0 || b < 0) + return 0; + + return a * 1000000 + ((b & 0x30) >> 4) * 250000; +} + +static unsigned int adv7611_read_hdmi_pixelclock(struct v4l2_subdev *sd) +{ + int a, b; + + a = hdmi_read(sd, 0x51); + b = hdmi_read(sd, 0x52); + if (a < 0 || b < 0) + return 0; + + return ((a << 1) | (b >> 7)) * 1000000 + (b & 0x7f) * 1000000 / 128; +} + +static unsigned int adv76xx_read_hdmi_pixelclock(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + unsigned int freq, bits_per_channel, pixelrepetition; + + freq = info->read_hdmi_pixelclock(sd); + if (is_hdmi(sd)) { + /* adjust for deep color mode and pixel repetition */ + bits_per_channel = ((hdmi_read(sd, 0x0b) & 0x60) >> 4) + 8; + pixelrepetition = (hdmi_read(sd, 0x05) & 0x0f) + 1; + + freq = freq * 8 / bits_per_channel / pixelrepetition; + } + + return freq; +} + +static int adv76xx_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + struct v4l2_bt_timings *bt = &timings->bt; + struct stdi_readback stdi; + + if (!timings) + return -EINVAL; + + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + + if (no_signal(sd)) { + state->restart_stdi_once = true; + v4l2_dbg(1, debug, sd, "%s: no valid signal\n", __func__); + return -ENOLINK; + } + + /* read STDI */ + if (read_stdi(sd, &stdi)) { + v4l2_dbg(1, debug, sd, "%s: STDI/SSPD not locked\n", __func__); + return -ENOLINK; + } + bt->interlaced = stdi.interlaced ? + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; + + if (is_digital_input(sd)) { + bool hdmi_signal = hdmi_read(sd, 0x05) & 0x80; + u8 vic = 0; + u32 w, h; + + w = hdmi_read16(sd, 0x07, info->linewidth_mask); + h = hdmi_read16(sd, 0x09, info->field0_height_mask); + + if (hdmi_signal && (io_read(sd, 0x60) & 1)) + vic = infoframe_read(sd, 0x04); + + if (vic && v4l2_find_dv_timings_cea861_vic(timings, vic) && + bt->width == w && bt->height == h) + goto found; + + timings->type = V4L2_DV_BT_656_1120; + + bt->width = w; + bt->height = h; + bt->pixelclock = adv76xx_read_hdmi_pixelclock(sd); + bt->hfrontporch = hdmi_read16(sd, 0x20, info->hfrontporch_mask); + bt->hsync = hdmi_read16(sd, 0x22, info->hsync_mask); + bt->hbackporch = hdmi_read16(sd, 0x24, info->hbackporch_mask); + bt->vfrontporch = hdmi_read16(sd, 0x2a, + info->field0_vfrontporch_mask) / 2; + bt->vsync = hdmi_read16(sd, 0x2e, info->field0_vsync_mask) / 2; + bt->vbackporch = hdmi_read16(sd, 0x32, + info->field0_vbackporch_mask) / 2; + bt->polarities = ((hdmi_read(sd, 0x05) & 0x10) ? V4L2_DV_VSYNC_POS_POL : 0) | + ((hdmi_read(sd, 0x05) & 0x20) ? V4L2_DV_HSYNC_POS_POL : 0); + if (bt->interlaced == V4L2_DV_INTERLACED) { + bt->height += hdmi_read16(sd, 0x0b, + info->field1_height_mask); + bt->il_vfrontporch = hdmi_read16(sd, 0x2c, + info->field1_vfrontporch_mask) / 2; + bt->il_vsync = hdmi_read16(sd, 0x30, + info->field1_vsync_mask) / 2; + bt->il_vbackporch = hdmi_read16(sd, 0x34, + info->field1_vbackporch_mask) / 2; + } + adv76xx_fill_optional_dv_timings_fields(sd, timings); + } else { + /* find format + * Since LCVS values are inaccurate [REF_03, p. 275-276], + * stdi2dv_timings() is called with lcvs +-1 if the first attempt fails. + */ + if (!stdi2dv_timings(sd, &stdi, timings)) + goto found; + stdi.lcvs += 1; + v4l2_dbg(1, debug, sd, "%s: lcvs + 1 = %d\n", __func__, stdi.lcvs); + if (!stdi2dv_timings(sd, &stdi, timings)) + goto found; + stdi.lcvs -= 2; + v4l2_dbg(1, debug, sd, "%s: lcvs - 1 = %d\n", __func__, stdi.lcvs); + if (stdi2dv_timings(sd, &stdi, timings)) { + /* + * The STDI block may measure wrong values, especially + * for lcvs and lcf. If the driver can not find any + * valid timing, the STDI block is restarted to measure + * the video timings again. The function will return an + * error, but the restart of STDI will generate a new + * STDI interrupt and the format detection process will + * restart. + */ + if (state->restart_stdi_once) { + v4l2_dbg(1, debug, sd, "%s: restart STDI\n", __func__); + /* TODO restart STDI for Sync Channel 2 */ + /* enter one-shot mode */ + cp_write_clr_set(sd, 0x86, 0x06, 0x00); + /* trigger STDI restart */ + cp_write_clr_set(sd, 0x86, 0x06, 0x04); + /* reset to continuous mode */ + cp_write_clr_set(sd, 0x86, 0x06, 0x02); + state->restart_stdi_once = false; + return -ENOLINK; + } + v4l2_dbg(1, debug, sd, "%s: format not supported\n", __func__); + return -ERANGE; + } + state->restart_stdi_once = true; + } +found: + + if (no_signal(sd)) { + v4l2_dbg(1, debug, sd, "%s: signal lost during readout\n", __func__); + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + return -ENOLINK; + } + + if ((is_analog_input(sd) && bt->pixelclock > 170000000) || + (is_digital_input(sd) && bt->pixelclock > 225000000)) { + v4l2_dbg(1, debug, sd, "%s: pixelclock out of range %d\n", + __func__, (u32)bt->pixelclock); + return -ERANGE; + } + + if (debug > 1) + v4l2_print_dv_timings(sd->name, "adv76xx_query_dv_timings: ", + timings, true); + + return 0; +} + +static int adv76xx_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct adv76xx_state *state = to_state(sd); + struct v4l2_bt_timings *bt; + int err; + + if (!timings) + return -EINVAL; + + if (v4l2_match_dv_timings(&state->timings, timings, 0, false)) { + v4l2_dbg(1, debug, sd, "%s: no change\n", __func__); + return 0; + } + + bt = &timings->bt; + + if (!v4l2_valid_dv_timings(timings, adv76xx_get_dv_timings_cap(sd, -1), + adv76xx_check_dv_timings, NULL)) + return -ERANGE; + + adv76xx_fill_optional_dv_timings_fields(sd, timings); + + state->timings = *timings; + + cp_write_clr_set(sd, 0x91, 0x40, bt->interlaced ? 0x40 : 0x00); + + /* Use prim_mode and vid_std when available */ + err = configure_predefined_video_timings(sd, timings); + if (err) { + /* custom settings when the video format + does not have prim_mode/vid_std */ + configure_custom_video_timings(sd, bt); + } + + set_rgb_quantization_range(sd); + + if (debug > 1) + v4l2_print_dv_timings(sd->name, "adv76xx_s_dv_timings: ", + timings, true); + return 0; +} + +static int adv76xx_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct adv76xx_state *state = to_state(sd); + + *timings = state->timings; + return 0; +} + +static void adv7604_set_termination(struct v4l2_subdev *sd, bool enable) +{ + hdmi_write(sd, 0x01, enable ? 0x00 : 0x78); +} + +static void adv7611_set_termination(struct v4l2_subdev *sd, bool enable) +{ + hdmi_write(sd, 0x83, enable ? 0xfe : 0xff); +} + +static void enable_input(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + if (is_analog_input(sd)) { + io_write(sd, 0x15, 0xb0); /* Disable Tristate of Pins (no audio) */ + } else if (is_digital_input(sd)) { + hdmi_write_clr_set(sd, 0x00, 0x03, state->selected_input); + state->info->set_termination(sd, true); + io_write(sd, 0x15, 0xa0); /* Disable Tristate of Pins */ + hdmi_write_clr_set(sd, 0x1a, 0x10, 0x00); /* Unmute audio */ + } else { + v4l2_dbg(2, debug, sd, "%s: Unknown port %d selected\n", + __func__, state->selected_input); + } +} + +static void disable_input(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + hdmi_write_clr_set(sd, 0x1a, 0x10, 0x10); /* Mute audio */ + msleep(16); /* 512 samples with >= 32 kHz sample rate [REF_03, c. 7.16.10] */ + io_write(sd, 0x15, 0xbe); /* Tristate all outputs from video core */ + state->info->set_termination(sd, false); +} + +static void select_input(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + + if (is_analog_input(sd)) { + adv76xx_write_reg_seq(sd, info->recommended_settings[0]); + + afe_write(sd, 0x00, 0x08); /* power up ADC */ + afe_write(sd, 0x01, 0x06); /* power up Analog Front End */ + afe_write(sd, 0xc8, 0x00); /* phase control */ + } else if (is_digital_input(sd)) { + hdmi_write(sd, 0x00, state->selected_input & 0x03); + + adv76xx_write_reg_seq(sd, info->recommended_settings[1]); + + if (adv76xx_has_afe(state)) { + afe_write(sd, 0x00, 0xff); /* power down ADC */ + afe_write(sd, 0x01, 0xfe); /* power down Analog Front End */ + afe_write(sd, 0xc8, 0x40); /* phase control */ + } + + cp_write(sd, 0x3e, 0x00); /* CP core pre-gain control */ + cp_write(sd, 0xc3, 0x39); /* CP coast control. Graphics mode */ + cp_write(sd, 0x40, 0x80); /* CP core pre-gain control. Graphics mode */ + } else { + v4l2_dbg(2, debug, sd, "%s: Unknown port %d selected\n", + __func__, state->selected_input); + } + + /* Enable video adjustment (contrast, saturation, brightness and hue) */ + cp_write_clr_set(sd, 0x3e, 0x80, 0x80); +} + +static int adv76xx_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv76xx_state *state = to_state(sd); + + v4l2_dbg(2, debug, sd, "%s: input %d, selected input %d", + __func__, input, state->selected_input); + + if (input == state->selected_input) + return 0; + + if (input > state->info->max_port) + return -EINVAL; + + state->selected_input = input; + + disable_input(sd); + select_input(sd); + enable_input(sd); + + v4l2_subdev_notify_event(sd, &adv76xx_ev_fmt); + + return 0; +} + +static int adv76xx_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct adv76xx_state *state = to_state(sd); + + if (code->index >= state->info->nformats) + return -EINVAL; + + code->code = state->info->formats[code->index].code; + + return 0; +} + +static void adv76xx_fill_format(struct adv76xx_state *state, + struct v4l2_mbus_framefmt *format) +{ + memset(format, 0, sizeof(*format)); + + format->width = state->timings.bt.width; + format->height = state->timings.bt.height; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + if (state->timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) + format->colorspace = (state->timings.bt.height <= 576) ? + V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709; +} + +/* + * Compute the op_ch_sel value required to obtain on the bus the component order + * corresponding to the selected format taking into account bus reordering + * applied by the board at the output of the device. + * + * The following table gives the op_ch_value from the format component order + * (expressed as op_ch_sel value in column) and the bus reordering (expressed as + * adv76xx_bus_order value in row). + * + * | GBR(0) GRB(1) BGR(2) RGB(3) BRG(4) RBG(5) + * ----------+------------------------------------------------- + * RGB (NOP) | GBR GRB BGR RGB BRG RBG + * GRB (1-2) | BGR RGB GBR GRB RBG BRG + * RBG (2-3) | GRB GBR BRG RBG BGR RGB + * BGR (1-3) | RBG BRG RGB BGR GRB GBR + * BRG (ROR) | BRG RBG GRB GBR RGB BGR + * GBR (ROL) | RGB BGR RBG BRG GBR GRB + */ +static unsigned int adv76xx_op_ch_sel(struct adv76xx_state *state) +{ +#define _SEL(a,b,c,d,e,f) { \ + ADV76XX_OP_CH_SEL_##a, ADV76XX_OP_CH_SEL_##b, ADV76XX_OP_CH_SEL_##c, \ + ADV76XX_OP_CH_SEL_##d, ADV76XX_OP_CH_SEL_##e, ADV76XX_OP_CH_SEL_##f } +#define _BUS(x) [ADV7604_BUS_ORDER_##x] + + static const unsigned int op_ch_sel[6][6] = { + _BUS(RGB) /* NOP */ = _SEL(GBR, GRB, BGR, RGB, BRG, RBG), + _BUS(GRB) /* 1-2 */ = _SEL(BGR, RGB, GBR, GRB, RBG, BRG), + _BUS(RBG) /* 2-3 */ = _SEL(GRB, GBR, BRG, RBG, BGR, RGB), + _BUS(BGR) /* 1-3 */ = _SEL(RBG, BRG, RGB, BGR, GRB, GBR), + _BUS(BRG) /* ROR */ = _SEL(BRG, RBG, GRB, GBR, RGB, BGR), + _BUS(GBR) /* ROL */ = _SEL(RGB, BGR, RBG, BRG, GBR, GRB), + }; + + return op_ch_sel[state->pdata.bus_order][state->format->op_ch_sel >> 5]; +} + +static void adv76xx_setup_format(struct adv76xx_state *state) +{ + struct v4l2_subdev *sd = &state->sd; + + io_write_clr_set(sd, 0x02, 0x02, + state->format->rgb_out ? ADV76XX_RGB_OUT : 0); + io_write(sd, 0x03, state->format->op_format_sel | + state->pdata.op_format_mode_sel); + io_write_clr_set(sd, 0x04, 0xe0, adv76xx_op_ch_sel(state)); + io_write_clr_set(sd, 0x05, 0x01, + state->format->swap_cb_cr ? ADV76XX_OP_SWAP_CB_CR : 0); + set_rgb_quantization_range(sd); +} + +static int adv76xx_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct adv76xx_state *state = to_state(sd); + + if (format->pad != state->source_pad) + return -EINVAL; + + adv76xx_fill_format(state, &format->format); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + format->format.code = fmt->code; + } else { + format->format.code = state->format->code; + } + + return 0; +} + +static int adv76xx_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct adv76xx_state *state = to_state(sd); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + /* Only CROP, CROP_DEFAULT and CROP_BOUNDS are supported */ + if (sel->target > V4L2_SEL_TGT_CROP_BOUNDS) + return -EINVAL; + + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = state->timings.bt.width; + sel->r.height = state->timings.bt.height; + + return 0; +} + +static int adv76xx_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_format_info *info; + + if (format->pad != state->source_pad) + return -EINVAL; + + info = adv76xx_format_info(state, format->format.code); + if (!info) + info = adv76xx_format_info(state, MEDIA_BUS_FMT_YUYV8_2X8); + + adv76xx_fill_format(state, &format->format); + format->format.code = info->code; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + fmt->code = format->format.code; + } else { + state->format = info; + adv76xx_setup_format(state); + } + + return 0; +} + +#if IS_ENABLED(CONFIG_VIDEO_ADV7604_CEC) +static void adv76xx_cec_tx_raw_status(struct v4l2_subdev *sd, u8 tx_raw_status) +{ + struct adv76xx_state *state = to_state(sd); + + if ((cec_read(sd, 0x11) & 0x01) == 0) { + v4l2_dbg(1, debug, sd, "%s: tx raw: tx disabled\n", __func__); + return; + } + + if (tx_raw_status & 0x02) { + v4l2_dbg(1, debug, sd, "%s: tx raw: arbitration lost\n", + __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_ARB_LOST, + 1, 0, 0, 0); + return; + } + if (tx_raw_status & 0x04) { + u8 status; + u8 nack_cnt; + u8 low_drive_cnt; + + v4l2_dbg(1, debug, sd, "%s: tx raw: retry failed\n", __func__); + /* + * We set this status bit since this hardware performs + * retransmissions. + */ + status = CEC_TX_STATUS_MAX_RETRIES; + nack_cnt = cec_read(sd, 0x14) & 0xf; + if (nack_cnt) + status |= CEC_TX_STATUS_NACK; + low_drive_cnt = cec_read(sd, 0x14) >> 4; + if (low_drive_cnt) + status |= CEC_TX_STATUS_LOW_DRIVE; + cec_transmit_done(state->cec_adap, status, + 0, nack_cnt, low_drive_cnt, 0); + return; + } + if (tx_raw_status & 0x01) { + v4l2_dbg(1, debug, sd, "%s: tx raw: ready ok\n", __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); + return; + } +} + +static void adv76xx_cec_isr(struct v4l2_subdev *sd, bool *handled) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + u8 cec_irq; + + /* cec controller */ + cec_irq = io_read(sd, info->cec_irq_status) & 0x0f; + if (!cec_irq) + return; + + v4l2_dbg(1, debug, sd, "%s: cec: irq 0x%x\n", __func__, cec_irq); + adv76xx_cec_tx_raw_status(sd, cec_irq); + if (cec_irq & 0x08) { + struct cec_msg msg; + + msg.len = cec_read(sd, 0x25) & 0x1f; + if (msg.len > CEC_MAX_MSG_SIZE) + msg.len = CEC_MAX_MSG_SIZE; + + if (msg.len) { + u8 i; + + for (i = 0; i < msg.len; i++) + msg.msg[i] = cec_read(sd, i + 0x15); + cec_write(sd, info->cec_rx_enable, + info->cec_rx_enable_mask); /* re-enable rx */ + cec_received_msg(state->cec_adap, &msg); + } + } + + if (info->cec_irq_swap) { + /* + * Note: the bit order is swapped between 0x4d and 0x4e + * on adv7604 + */ + cec_irq = ((cec_irq & 0x08) >> 3) | ((cec_irq & 0x04) >> 1) | + ((cec_irq & 0x02) << 1) | ((cec_irq & 0x01) << 3); + } + io_write(sd, info->cec_irq_status + 1, cec_irq); + + if (handled) + *handled = true; +} + +static int adv76xx_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct adv76xx_state *state = cec_get_drvdata(adap); + const struct adv76xx_chip_info *info = state->info; + struct v4l2_subdev *sd = &state->sd; + + if (!state->cec_enabled_adap && enable) { + cec_write_clr_set(sd, 0x2a, 0x01, 0x01); /* power up cec */ + cec_write(sd, 0x2c, 0x01); /* cec soft reset */ + cec_write_clr_set(sd, 0x11, 0x01, 0); /* initially disable tx */ + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready */ + io_write_clr_set(sd, info->cec_irq_status + 3, 0x0f, 0x0f); + cec_write(sd, info->cec_rx_enable, info->cec_rx_enable_mask); + } else if (state->cec_enabled_adap && !enable) { + /* disable cec interrupts */ + io_write_clr_set(sd, info->cec_irq_status + 3, 0x0f, 0x00); + /* disable address mask 1-3 */ + cec_write_clr_set(sd, 0x27, 0x70, 0x00); + /* power down cec section */ + cec_write_clr_set(sd, 0x2a, 0x01, 0x00); + state->cec_valid_addrs = 0; + } + state->cec_enabled_adap = enable; + adv76xx_s_detect_tx_5v_ctrl(sd); + return 0; +} + +static int adv76xx_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct adv76xx_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + unsigned int i, free_idx = ADV76XX_MAX_ADDRS; + + if (!state->cec_enabled_adap) + return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + cec_write_clr_set(sd, 0x27, 0x70, 0); + state->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV76XX_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (free_idx == ADV76XX_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && state->cec_addr[i] == addr) + return 0; + } + if (i == ADV76XX_MAX_ADDRS) { + i = free_idx; + if (i == ADV76XX_MAX_ADDRS) + return -ENXIO; + } + state->cec_addr[i] = addr; + state->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + cec_write_clr_set(sd, 0x27, 0x10, 0x10); + /* set address for mask 0 */ + cec_write_clr_set(sd, 0x28, 0x0f, addr); + break; + case 1: + /* enable address mask 1 */ + cec_write_clr_set(sd, 0x27, 0x20, 0x20); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x28, 0xf0, addr << 4); + break; + case 2: + /* enable address mask 2 */ + cec_write_clr_set(sd, 0x27, 0x40, 0x40); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x29, 0x0f, addr); + break; + } + return 0; +} + +static int adv76xx_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct adv76xx_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + u8 len = msg->len; + unsigned int i; + + /* + * The number of retries is the number of attempts - 1, but retry + * at least once. It's not clear if a value of 0 is allowed, so + * let's do at least one retry. + */ + cec_write_clr_set(sd, 0x12, 0x70, max(1, attempts - 1) << 4); + + if (len > 16) { + v4l2_err(sd, "%s: len exceeded 16 (%d)\n", __func__, len); + return -EINVAL; + } + + /* write data */ + for (i = 0; i < len; i++) + cec_write(sd, i, msg->msg[i]); + + /* set length (data + header) */ + cec_write(sd, 0x10, len); + /* start transmit, enable tx */ + cec_write(sd, 0x11, 0x01); + return 0; +} + +static const struct cec_adap_ops adv76xx_cec_adap_ops = { + .adap_enable = adv76xx_cec_adap_enable, + .adap_log_addr = adv76xx_cec_adap_log_addr, + .adap_transmit = adv76xx_cec_adap_transmit, +}; +#endif + +static int adv76xx_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + const u8 irq_reg_0x43 = io_read(sd, 0x43); + const u8 irq_reg_0x6b = io_read(sd, 0x6b); + const u8 irq_reg_0x70 = io_read(sd, 0x70); + u8 fmt_change_digital; + u8 fmt_change; + u8 tx_5v; + + if (irq_reg_0x43) + io_write(sd, 0x44, irq_reg_0x43); + if (irq_reg_0x70) + io_write(sd, 0x71, irq_reg_0x70); + if (irq_reg_0x6b) + io_write(sd, 0x6c, irq_reg_0x6b); + + v4l2_dbg(2, debug, sd, "%s: ", __func__); + + /* format change */ + fmt_change = irq_reg_0x43 & 0x98; + fmt_change_digital = is_digital_input(sd) + ? irq_reg_0x6b & info->fmt_change_digital_mask + : 0; + + if (fmt_change || fmt_change_digital) { + v4l2_dbg(1, debug, sd, + "%s: fmt_change = 0x%x, fmt_change_digital = 0x%x\n", + __func__, fmt_change, fmt_change_digital); + + v4l2_subdev_notify_event(sd, &adv76xx_ev_fmt); + + if (handled) + *handled = true; + } + /* HDMI/DVI mode */ + if (irq_reg_0x6b & 0x01) { + v4l2_dbg(1, debug, sd, "%s: irq %s mode\n", __func__, + (io_read(sd, 0x6a) & 0x01) ? "HDMI" : "DVI"); + set_rgb_quantization_range(sd); + if (handled) + *handled = true; + } + +#if IS_ENABLED(CONFIG_VIDEO_ADV7604_CEC) + /* cec */ + adv76xx_cec_isr(sd, handled); +#endif + + /* tx 5v detect */ + tx_5v = irq_reg_0x70 & info->cable_det_mask; + if (tx_5v) { + v4l2_dbg(1, debug, sd, "%s: tx_5v: 0x%x\n", __func__, tx_5v); + adv76xx_s_detect_tx_5v_ctrl(sd); + if (handled) + *handled = true; + } + return 0; +} + +static irqreturn_t adv76xx_irq_handler(int irq, void *dev_id) +{ + struct adv76xx_state *state = dev_id; + bool handled = false; + + adv76xx_isr(&state->sd, 0, &handled); + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static int adv76xx_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct adv76xx_state *state = to_state(sd); + u8 *data = NULL; + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + switch (edid->pad) { + case ADV76XX_PAD_HDMI_PORT_A: + case ADV7604_PAD_HDMI_PORT_B: + case ADV7604_PAD_HDMI_PORT_C: + case ADV7604_PAD_HDMI_PORT_D: + if (state->edid.present & (1 << edid->pad)) + data = state->edid.edid; + break; + default: + return -EINVAL; + } + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = data ? state->edid.blocks : 0; + return 0; + } + + if (!data) + return -ENODATA; + + if (edid->start_block >= state->edid.blocks) + return -EINVAL; + + if (edid->start_block + edid->blocks > state->edid.blocks) + edid->blocks = state->edid.blocks - edid->start_block; + + memcpy(edid->edid, data + edid->start_block * 128, edid->blocks * 128); + + return 0; +} + +static int adv76xx_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + unsigned int spa_loc; + u16 pa, parent_pa; + int err; + int i; + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->pad > ADV7604_PAD_HDMI_PORT_D) + return -EINVAL; + if (edid->start_block != 0) + return -EINVAL; + if (edid->blocks == 0) { + /* Disable hotplug and I2C access to EDID RAM from DDC port */ + state->edid.present &= ~(1 << edid->pad); + adv76xx_set_hpd(state, state->edid.present); + rep_write_clr_set(sd, info->edid_enable_reg, 0x0f, state->edid.present); + + /* Fall back to a 16:9 aspect ratio */ + state->aspect_ratio.numerator = 16; + state->aspect_ratio.denominator = 9; + + if (!state->edid.present) { + state->edid.blocks = 0; + cec_phys_addr_invalidate(state->cec_adap); + } + + v4l2_dbg(2, debug, sd, "%s: clear EDID pad %d, edid.present = 0x%x\n", + __func__, edid->pad, state->edid.present); + return 0; + } + if (edid->blocks > ADV76XX_MAX_EDID_BLOCKS) { + edid->blocks = ADV76XX_MAX_EDID_BLOCKS; + return -E2BIG; + } + + pa = v4l2_get_edid_phys_addr(edid->edid, edid->blocks * 128, &spa_loc); + err = v4l2_phys_addr_validate(pa, &parent_pa, NULL); + if (err) + return err; + + if (!spa_loc) { + /* + * There is no SPA, so just set spa_loc to 128 and pa to whatever + * data is there. + */ + spa_loc = 128; + pa = (edid->edid[spa_loc] << 8) | edid->edid[spa_loc + 1]; + } + + v4l2_dbg(2, debug, sd, "%s: write EDID pad %d, edid.present = 0x%x\n", + __func__, edid->pad, state->edid.present); + + /* Disable hotplug and I2C access to EDID RAM from DDC port */ + cancel_delayed_work_sync(&state->delayed_work_enable_hotplug); + adv76xx_set_hpd(state, 0); + rep_write_clr_set(sd, info->edid_enable_reg, 0x0f, 0x00); + + switch (edid->pad) { + case ADV76XX_PAD_HDMI_PORT_A: + state->spa_port_a[0] = pa >> 8; + state->spa_port_a[1] = pa & 0xff; + break; + case ADV7604_PAD_HDMI_PORT_B: + rep_write(sd, info->edid_spa_port_b_reg, pa >> 8); + rep_write(sd, info->edid_spa_port_b_reg + 1, pa & 0xff); + break; + case ADV7604_PAD_HDMI_PORT_C: + rep_write(sd, info->edid_spa_port_b_reg + 2, pa >> 8); + rep_write(sd, info->edid_spa_port_b_reg + 3, pa & 0xff); + break; + case ADV7604_PAD_HDMI_PORT_D: + rep_write(sd, info->edid_spa_port_b_reg + 4, pa >> 8); + rep_write(sd, info->edid_spa_port_b_reg + 5, pa & 0xff); + break; + default: + return -EINVAL; + } + + if (info->edid_spa_loc_reg) { + u8 mask = info->edid_spa_loc_msb_mask; + + rep_write(sd, info->edid_spa_loc_reg, spa_loc & 0xff); + rep_write_clr_set(sd, info->edid_spa_loc_reg + 1, + mask, (spa_loc & 0x100) ? mask : 0); + } + + edid->edid[spa_loc] = state->spa_port_a[0]; + edid->edid[spa_loc + 1] = state->spa_port_a[1]; + + memcpy(state->edid.edid, edid->edid, 128 * edid->blocks); + state->edid.blocks = edid->blocks; + state->aspect_ratio = v4l2_calc_aspect_ratio(edid->edid[0x15], + edid->edid[0x16]); + state->edid.present |= 1 << edid->pad; + + rep_write_clr_set(sd, info->edid_segment_reg, + info->edid_segment_mask, 0); + err = edid_write_block(sd, 128 * min(edid->blocks, 2U), state->edid.edid); + if (err < 0) { + v4l2_err(sd, "error %d writing edid pad %d\n", err, edid->pad); + return err; + } + if (edid->blocks > 2) { + rep_write_clr_set(sd, info->edid_segment_reg, + info->edid_segment_mask, + info->edid_segment_mask); + err = edid_write_block(sd, 128 * (edid->blocks - 2), + state->edid.edid + 256); + if (err < 0) { + v4l2_err(sd, "error %d writing edid pad %d\n", + err, edid->pad); + return err; + } + } + + /* adv76xx calculates the checksums and enables I2C access to internal + EDID RAM from DDC port. */ + rep_write_clr_set(sd, info->edid_enable_reg, 0x0f, state->edid.present); + + for (i = 0; i < 1000; i++) { + if (rep_read(sd, info->edid_status_reg) & state->edid.present) + break; + mdelay(1); + } + if (i == 1000) { + v4l2_err(sd, "error enabling edid (0x%x)\n", state->edid.present); + return -EIO; + } + cec_s_phys_addr(state->cec_adap, parent_pa, false); + + /* enable hotplug after 143 ms */ + schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 7); + return 0; +} + +/*********** avi info frame CEA-861-E **************/ + +static const struct adv76xx_cfg_read_infoframe adv76xx_cri[] = { + { "AVI", 0x01, 0xe0, 0x00 }, + { "Audio", 0x02, 0xe3, 0x1c }, + { "SDP", 0x04, 0xe6, 0x2a }, + { "Vendor", 0x10, 0xec, 0x54 } +}; + +static int adv76xx_read_infoframe_buf(struct v4l2_subdev *sd, int index, + u8 buf[V4L2_DEBUGFS_IF_MAX_LEN]) +{ + u8 len; + int i; + + if (!(io_read(sd, 0x60) & adv76xx_cri[index].present_mask)) { + v4l2_info(sd, "%s infoframe not received\n", + adv76xx_cri[index].desc); + return -ENOENT; + } + + for (i = 0; i < 3; i++) + buf[i] = infoframe_read(sd, adv76xx_cri[index].head_addr + i); + + len = buf[2] + 1; + + if (len + 3 > V4L2_DEBUGFS_IF_MAX_LEN) { + v4l2_err(sd, "%s: invalid %s infoframe length %d\n", __func__, + adv76xx_cri[index].desc, len); + return -ENOENT; + } + + for (i = 0; i < len; i++) + buf[i + 3] = infoframe_read(sd, + adv76xx_cri[index].payload_addr + i); + return len + 3; +} + +static void adv76xx_log_infoframes(struct v4l2_subdev *sd) +{ + int i; + + if (!is_hdmi(sd)) { + v4l2_info(sd, "receive DVI-D signal, no infoframes\n"); + return; + } + + for (i = 0; i < ARRAY_SIZE(adv76xx_cri); i++) { + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 buffer[V4L2_DEBUGFS_IF_MAX_LEN] = {}; + union hdmi_infoframe frame; + int len; + + len = adv76xx_read_infoframe_buf(sd, i, buffer); + if (len < 0) + continue; + + if (hdmi_infoframe_unpack(&frame, buffer, len) < 0) + v4l2_err(sd, "%s: unpack of %s infoframe failed\n", + __func__, adv76xx_cri[i].desc); + else + hdmi_infoframe_log(KERN_INFO, &client->dev, &frame); + } +} + +static int adv76xx_log_status(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + struct v4l2_dv_timings timings; + struct stdi_readback stdi; + int ret; + u8 reg_io_0x02; + u8 edid_enabled; + u8 cable_det; + static const char * const csc_coeff_sel_rb[16] = { + "bypassed", "YPbPr601 -> RGB", "reserved", "YPbPr709 -> RGB", + "reserved", "RGB -> YPbPr601", "reserved", "RGB -> YPbPr709", + "reserved", "YPbPr709 -> YPbPr601", "YPbPr601 -> YPbPr709", + "reserved", "reserved", "reserved", "reserved", "manual" + }; + static const char * const input_color_space_txt[16] = { + "RGB limited range (16-235)", "RGB full range (0-255)", + "YCbCr Bt.601 (16-235)", "YCbCr Bt.709 (16-235)", + "xvYCC Bt.601", "xvYCC Bt.709", + "YCbCr Bt.601 (0-255)", "YCbCr Bt.709 (0-255)", + "invalid", "invalid", "invalid", "invalid", "invalid", + "invalid", "invalid", "automatic" + }; + static const char * const hdmi_color_space_txt[16] = { + "RGB limited range (16-235)", "RGB full range (0-255)", + "YCbCr Bt.601 (16-235)", "YCbCr Bt.709 (16-235)", + "xvYCC Bt.601", "xvYCC Bt.709", + "YCbCr Bt.601 (0-255)", "YCbCr Bt.709 (0-255)", + "sYCC", "opYCC 601", "opRGB", "invalid", "invalid", + "invalid", "invalid", "invalid" + }; + static const char * const rgb_quantization_range_txt[] = { + "Automatic", + "RGB limited range (16-235)", + "RGB full range (0-255)", + }; + static const char * const deep_color_mode_txt[4] = { + "8-bits per channel", + "10-bits per channel", + "12-bits per channel", + "16-bits per channel (not supported)" + }; + + v4l2_info(sd, "-----Chip status-----\n"); + v4l2_info(sd, "Chip power: %s\n", no_power(sd) ? "off" : "on"); + edid_enabled = rep_read(sd, info->edid_status_reg); + v4l2_info(sd, "EDID enabled port A: %s, B: %s, C: %s, D: %s\n", + ((edid_enabled & 0x01) ? "Yes" : "No"), + ((edid_enabled & 0x02) ? "Yes" : "No"), + ((edid_enabled & 0x04) ? "Yes" : "No"), + ((edid_enabled & 0x08) ? "Yes" : "No")); + v4l2_info(sd, "CEC: %s\n", state->cec_enabled_adap ? + "enabled" : "disabled"); + if (state->cec_enabled_adap) { + int i; + + for (i = 0; i < ADV76XX_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (is_valid) + v4l2_info(sd, "CEC Logical Address: 0x%x\n", + state->cec_addr[i]); + } + } + + v4l2_info(sd, "-----Signal status-----\n"); + cable_det = info->read_cable_det(sd); + v4l2_info(sd, "Cable detected (+5V power) port A: %s, B: %s, C: %s, D: %s\n", + ((cable_det & 0x01) ? "Yes" : "No"), + ((cable_det & 0x02) ? "Yes" : "No"), + ((cable_det & 0x04) ? "Yes" : "No"), + ((cable_det & 0x08) ? "Yes" : "No")); + v4l2_info(sd, "TMDS signal detected: %s\n", + no_signal_tmds(sd) ? "false" : "true"); + v4l2_info(sd, "TMDS signal locked: %s\n", + no_lock_tmds(sd) ? "false" : "true"); + v4l2_info(sd, "SSPD locked: %s\n", no_lock_sspd(sd) ? "false" : "true"); + v4l2_info(sd, "STDI locked: %s\n", no_lock_stdi(sd) ? "false" : "true"); + v4l2_info(sd, "CP locked: %s\n", no_lock_cp(sd) ? "false" : "true"); + v4l2_info(sd, "CP free run: %s\n", + (in_free_run(sd)) ? "on" : "off"); + v4l2_info(sd, "Prim-mode = 0x%x, video std = 0x%x, v_freq = 0x%x\n", + io_read(sd, 0x01) & 0x0f, io_read(sd, 0x00) & 0x3f, + (io_read(sd, 0x01) & 0x70) >> 4); + + v4l2_info(sd, "-----Video Timings-----\n"); + if (read_stdi(sd, &stdi)) + v4l2_info(sd, "STDI: not locked\n"); + else + v4l2_info(sd, "STDI: lcf (frame height - 1) = %d, bl = %d, lcvs (vsync) = %d, %s, %chsync, %cvsync\n", + stdi.lcf, stdi.bl, stdi.lcvs, + stdi.interlaced ? "interlaced" : "progressive", + stdi.hs_pol, stdi.vs_pol); + if (adv76xx_query_dv_timings(sd, 0, &timings)) + v4l2_info(sd, "No video detected\n"); + else + v4l2_print_dv_timings(sd->name, "Detected format: ", + &timings, true); + v4l2_print_dv_timings(sd->name, "Configured format: ", + &state->timings, true); + + if (no_signal(sd)) + return 0; + + v4l2_info(sd, "-----Color space-----\n"); + v4l2_info(sd, "RGB quantization range ctrl: %s\n", + rgb_quantization_range_txt[state->rgb_quantization_range]); + + ret = io_read(sd, 0x02); + if (ret < 0) { + v4l2_info(sd, "Can't read Input/Output color space\n"); + } else { + reg_io_0x02 = ret; + + v4l2_info(sd, "Input color space: %s\n", + input_color_space_txt[reg_io_0x02 >> 4]); + v4l2_info(sd, "Output color space: %s %s, alt-gamma %s\n", + (reg_io_0x02 & 0x02) ? "RGB" : "YCbCr", + (((reg_io_0x02 >> 2) & 0x01) ^ (reg_io_0x02 & 0x01)) ? + "(16-235)" : "(0-255)", + (reg_io_0x02 & 0x08) ? "enabled" : "disabled"); + } + v4l2_info(sd, "Color space conversion: %s\n", + csc_coeff_sel_rb[cp_read(sd, info->cp_csc) >> 4]); + + if (!is_digital_input(sd)) + return 0; + + v4l2_info(sd, "-----%s status-----\n", is_hdmi(sd) ? "HDMI" : "DVI-D"); + v4l2_info(sd, "Digital video port selected: %c\n", + (hdmi_read(sd, 0x00) & 0x03) + 'A'); + v4l2_info(sd, "HDCP encrypted content: %s\n", + (hdmi_read(sd, 0x05) & 0x40) ? "true" : "false"); + v4l2_info(sd, "HDCP keys read: %s%s\n", + (hdmi_read(sd, 0x04) & 0x20) ? "yes" : "no", + (hdmi_read(sd, 0x04) & 0x10) ? "ERROR" : ""); + if (is_hdmi(sd)) { + bool audio_pll_locked = hdmi_read(sd, 0x04) & 0x01; + bool audio_sample_packet_detect = hdmi_read(sd, 0x18) & 0x01; + bool audio_mute = io_read(sd, 0x65) & 0x40; + + v4l2_info(sd, "Audio: pll %s, samples %s, %s\n", + audio_pll_locked ? "locked" : "not locked", + audio_sample_packet_detect ? "detected" : "not detected", + audio_mute ? "muted" : "enabled"); + if (audio_pll_locked && audio_sample_packet_detect) { + v4l2_info(sd, "Audio format: %s\n", + (hdmi_read(sd, 0x07) & 0x20) ? "multi-channel" : "stereo"); + } + v4l2_info(sd, "Audio CTS: %u\n", (hdmi_read(sd, 0x5b) << 12) + + (hdmi_read(sd, 0x5c) << 8) + + (hdmi_read(sd, 0x5d) & 0xf0)); + v4l2_info(sd, "Audio N: %u\n", ((hdmi_read(sd, 0x5d) & 0x0f) << 16) + + (hdmi_read(sd, 0x5e) << 8) + + hdmi_read(sd, 0x5f)); + v4l2_info(sd, "AV Mute: %s\n", (hdmi_read(sd, 0x04) & 0x40) ? "on" : "off"); + + v4l2_info(sd, "Deep color mode: %s\n", deep_color_mode_txt[(hdmi_read(sd, 0x0b) & 0x60) >> 5]); + v4l2_info(sd, "HDMI colorspace: %s\n", hdmi_color_space_txt[hdmi_read(sd, 0x53) & 0xf]); + + adv76xx_log_infoframes(sd); + } + + return 0; +} + +static int adv76xx_subscribe_event(struct v4l2_subdev *sd, + struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +static ssize_t +adv76xx_debugfs_if_read(u32 type, void *priv, struct file *filp, + char __user *ubuf, size_t count, loff_t *ppos) +{ + u8 buf[V4L2_DEBUGFS_IF_MAX_LEN] = {}; + struct v4l2_subdev *sd = priv; + int index; + int len; + + if (!is_hdmi(sd)) + return 0; + + switch (type) { + case V4L2_DEBUGFS_IF_AVI: + index = 0; + break; + case V4L2_DEBUGFS_IF_AUDIO: + index = 1; + break; + case V4L2_DEBUGFS_IF_SPD: + index = 2; + break; + case V4L2_DEBUGFS_IF_HDMI: + index = 3; + break; + default: + return 0; + } + + len = adv76xx_read_infoframe_buf(sd, index, buf); + if (len > 0) + len = simple_read_from_buffer(ubuf, count, ppos, buf, len); + return len < 0 ? 0 : len; +} + +static int adv76xx_registered(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err; + + err = cec_register_adapter(state->cec_adap, &client->dev); + if (err) { + cec_delete_adapter(state->cec_adap); + return err; + } + state->debugfs_dir = debugfs_create_dir(sd->name, v4l2_debugfs_root()); + state->infoframes = v4l2_debugfs_if_alloc(state->debugfs_dir, + V4L2_DEBUGFS_IF_AVI | V4L2_DEBUGFS_IF_AUDIO | + V4L2_DEBUGFS_IF_SPD | V4L2_DEBUGFS_IF_HDMI, sd, + adv76xx_debugfs_if_read); + return 0; +} + +static void adv76xx_unregistered(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + cec_unregister_adapter(state->cec_adap); + v4l2_debugfs_if_free(state->infoframes); + state->infoframes = NULL; + debugfs_remove_recursive(state->debugfs_dir); + state->debugfs_dir = NULL; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops adv76xx_ctrl_ops = { + .s_ctrl = adv76xx_s_ctrl, + .g_volatile_ctrl = adv76xx_g_volatile_ctrl, +}; + +static const struct v4l2_subdev_core_ops adv76xx_core_ops = { + .log_status = adv76xx_log_status, + .interrupt_service_routine = adv76xx_isr, + .subscribe_event = adv76xx_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = adv76xx_g_register, + .s_register = adv76xx_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops adv76xx_video_ops = { + .s_routing = adv76xx_s_routing, + .g_input_status = adv76xx_g_input_status, +}; + +static const struct v4l2_subdev_pad_ops adv76xx_pad_ops = { + .enum_mbus_code = adv76xx_enum_mbus_code, + .get_selection = adv76xx_get_selection, + .get_fmt = adv76xx_get_format, + .set_fmt = adv76xx_set_format, + .get_edid = adv76xx_get_edid, + .set_edid = adv76xx_set_edid, + .s_dv_timings = adv76xx_s_dv_timings, + .g_dv_timings = adv76xx_g_dv_timings, + .query_dv_timings = adv76xx_query_dv_timings, + .dv_timings_cap = adv76xx_dv_timings_cap, + .enum_dv_timings = adv76xx_enum_dv_timings, +}; + +static const struct v4l2_subdev_ops adv76xx_ops = { + .core = &adv76xx_core_ops, + .video = &adv76xx_video_ops, + .pad = &adv76xx_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops adv76xx_int_ops = { + .registered = adv76xx_registered, + .unregistered = adv76xx_unregistered, +}; + +/* -------------------------- custom ctrls ---------------------------------- */ + +static const struct v4l2_ctrl_config adv7604_ctrl_analog_sampling_phase = { + .ops = &adv76xx_ctrl_ops, + .id = V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE, + .name = "Analog Sampling Phase", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 0x1f, + .step = 1, + .def = 0, +}; + +static const struct v4l2_ctrl_config adv76xx_ctrl_free_run_color_manual = { + .ops = &adv76xx_ctrl_ops, + .id = V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL, + .name = "Free Running Color, Manual", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = false, + .max = true, + .step = 1, + .def = false, +}; + +static const struct v4l2_ctrl_config adv76xx_ctrl_free_run_color = { + .ops = &adv76xx_ctrl_ops, + .id = V4L2_CID_ADV_RX_FREE_RUN_COLOR, + .name = "Free Running Color", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0x0, + .max = 0xffffff, + .step = 0x1, + .def = 0x0, +}; + +/* ----------------------------------------------------------------------- */ + +struct adv76xx_register_map { + const char *name; + u8 default_addr; +}; + +static const struct adv76xx_register_map adv76xx_default_addresses[] = { + [ADV76XX_PAGE_IO] = { "main", 0x4c }, + [ADV7604_PAGE_AVLINK] = { "avlink", 0x42 }, + [ADV76XX_PAGE_CEC] = { "cec", 0x40 }, + [ADV76XX_PAGE_INFOFRAME] = { "infoframe", 0x3e }, + [ADV7604_PAGE_ESDP] = { "esdp", 0x38 }, + [ADV7604_PAGE_DPP] = { "dpp", 0x3c }, + [ADV76XX_PAGE_AFE] = { "afe", 0x26 }, + [ADV76XX_PAGE_REP] = { "rep", 0x32 }, + [ADV76XX_PAGE_EDID] = { "edid", 0x36 }, + [ADV76XX_PAGE_HDMI] = { "hdmi", 0x34 }, + [ADV76XX_PAGE_TEST] = { "test", 0x30 }, + [ADV76XX_PAGE_CP] = { "cp", 0x22 }, + [ADV7604_PAGE_VDP] = { "vdp", 0x24 }, +}; + +static int adv76xx_core_init(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + const struct adv76xx_chip_info *info = state->info; + struct adv76xx_platform_data *pdata = &state->pdata; + + hdmi_write(sd, 0x48, + (pdata->disable_pwrdnb ? 0x80 : 0) | + (pdata->disable_cable_det_rst ? 0x40 : 0)); + + disable_input(sd); + + if (pdata->default_input >= 0 && + pdata->default_input < state->source_pad) { + state->selected_input = pdata->default_input; + select_input(sd); + enable_input(sd); + } + + /* power */ + io_write(sd, 0x0c, 0x42); /* Power up part and power down VDP */ + io_write(sd, 0x0b, 0x44); /* Power down ESDP block */ + cp_write(sd, 0xcf, 0x01); /* Power down macrovision */ + + /* HPD */ + if (info->type != ADV7604) { + /* Set manual HPD values to 0 */ + io_write_clr_set(sd, 0x20, 0xc0, 0); + /* + * Set HPA_DELAY to 200 ms and set automatic HPD control + * to: internal EDID is active AND a cable is detected + * AND the manual HPD control is set to 1. + */ + hdmi_write_clr_set(sd, 0x6c, 0xf6, 0x26); + } + + /* video format */ + io_write_clr_set(sd, 0x02, 0x0f, pdata->alt_gamma << 3); + io_write_clr_set(sd, 0x05, 0x0e, pdata->blank_data << 3 | + pdata->insert_av_codes << 2 | + pdata->replicate_av_codes << 1); + adv76xx_setup_format(state); + + cp_write(sd, 0x69, 0x30); /* Enable CP CSC */ + + /* VS, HS polarities */ + io_write(sd, 0x06, 0xa0 | pdata->inv_vs_pol << 2 | + pdata->inv_hs_pol << 1 | pdata->inv_llc_pol); + + /* Adjust drive strength */ + io_write(sd, 0x14, 0x40 | pdata->dr_str_data << 4 | + pdata->dr_str_clk << 2 | + pdata->dr_str_sync); + + cp_write(sd, 0xba, (pdata->hdmi_free_run_mode << 1) | 0x01); /* HDMI free run */ + cp_write(sd, 0xf3, 0xdc); /* Low threshold to enter/exit free run mode */ + cp_write(sd, 0xf9, 0x23); /* STDI ch. 1 - LCVS change threshold - + ADI recommended setting [REF_01, c. 2.3.3] */ + cp_write(sd, 0x45, 0x23); /* STDI ch. 2 - LCVS change threshold - + ADI recommended setting [REF_01, c. 2.3.3] */ + cp_write(sd, 0xc9, 0x2d); /* use prim_mode and vid_std as free run resolution + for digital formats */ + + /* HDMI audio */ + hdmi_write_clr_set(sd, 0x15, 0x03, 0x03); /* Mute on FIFO over-/underflow [REF_01, c. 1.2.18] */ + hdmi_write_clr_set(sd, 0x1a, 0x0e, 0x08); /* Wait 1 s before unmute */ + hdmi_write_clr_set(sd, 0x68, 0x06, 0x06); /* FIFO reset on over-/underflow [REF_01, c. 1.2.19] */ + + /* TODO from platform data */ + afe_write(sd, 0xb5, 0x01); /* Setting MCLK to 256Fs */ + + if (adv76xx_has_afe(state)) { + afe_write(sd, 0x02, pdata->ain_sel); /* Select analog input muxing mode */ + io_write_clr_set(sd, 0x30, 1 << 4, pdata->output_bus_lsb_to_msb << 4); + } + + /* interrupts */ + io_write(sd, 0x40, 0xc0 | pdata->int1_config); /* Configure INT1 */ + io_write(sd, 0x46, 0x98); /* Enable SSPD, STDI and CP unlocked interrupts */ + io_write(sd, 0x6e, info->fmt_change_digital_mask); /* Enable V_LOCKED and DE_REGEN_LCK interrupts */ + io_write(sd, 0x73, info->cable_det_mask); /* Enable cable detection (+5v) interrupts */ + info->setup_irqs(sd); + + return v4l2_ctrl_handler_setup(sd->ctrl_handler); +} + +static void adv7604_setup_irqs(struct v4l2_subdev *sd) +{ + io_write(sd, 0x41, 0xd7); /* STDI irq for any change, disable INT2 */ +} + +static void adv7611_setup_irqs(struct v4l2_subdev *sd) +{ + io_write(sd, 0x41, 0xd0); /* STDI irq for any change, disable INT2 */ +} + +static void adv7612_setup_irqs(struct v4l2_subdev *sd) +{ + io_write(sd, 0x41, 0xd0); /* disable INT2 */ +} + +static void adv76xx_unregister_clients(struct adv76xx_state *state) +{ + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(state->i2c_clients); ++i) + i2c_unregister_device(state->i2c_clients[i]); +} + +static struct i2c_client *adv76xx_dummy_client(struct v4l2_subdev *sd, + unsigned int page) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv76xx_state *state = to_state(sd); + struct adv76xx_platform_data *pdata = &state->pdata; + unsigned int io_reg = 0xf2 + page; + struct i2c_client *new_client; + + if (pdata && pdata->i2c_addresses[page]) + new_client = i2c_new_dummy_device(client->adapter, + pdata->i2c_addresses[page]); + else + new_client = i2c_new_ancillary_device(client, + adv76xx_default_addresses[page].name, + adv76xx_default_addresses[page].default_addr); + + if (!IS_ERR(new_client)) + io_write(sd, io_reg, new_client->addr << 1); + + return new_client; +} + +static const struct adv76xx_reg_seq adv7604_recommended_settings_afe[] = { + /* reset ADI recommended settings for HDMI: */ + /* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 4. */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x0d), 0x04 }, /* HDMI filter optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x0d), 0x04 }, /* HDMI filter optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x3d), 0x00 }, /* DDC bus active pull-up control */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x3e), 0x74 }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x4e), 0x3b }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x57), 0x74 }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x58), 0x63 }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x8d), 0x18 }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x8e), 0x34 }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x93), 0x88 }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x94), 0x2e }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x96), 0x00 }, /* enable automatic EQ changing */ + + /* set ADI recommended settings for digitizer */ + /* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 17. */ + { ADV76XX_REG(ADV76XX_PAGE_AFE, 0x12), 0x7b }, /* ADC noise shaping filter controls */ + { ADV76XX_REG(ADV76XX_PAGE_AFE, 0x0c), 0x1f }, /* CP core gain controls */ + { ADV76XX_REG(ADV76XX_PAGE_CP, 0x3e), 0x04 }, /* CP core pre-gain control */ + { ADV76XX_REG(ADV76XX_PAGE_CP, 0xc3), 0x39 }, /* CP coast control. Graphics mode */ + { ADV76XX_REG(ADV76XX_PAGE_CP, 0x40), 0x5c }, /* CP core pre-gain control. Graphics mode */ + + { ADV76XX_REG_SEQ_TERM, 0 }, +}; + +static const struct adv76xx_reg_seq adv7604_recommended_settings_hdmi[] = { + /* set ADI recommended settings for HDMI: */ + /* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 4. */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x0d), 0x84 }, /* HDMI filter optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x3d), 0x10 }, /* DDC bus active pull-up control */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x3e), 0x39 }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x4e), 0x3b }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x57), 0xb6 }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x58), 0x03 }, /* TMDS PLL optimization */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x8d), 0x18 }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x8e), 0x34 }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x93), 0x8b }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x94), 0x2d }, /* equaliser */ + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x96), 0x01 }, /* enable automatic EQ changing */ + + /* reset ADI recommended settings for digitizer */ + /* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 17. */ + { ADV76XX_REG(ADV76XX_PAGE_AFE, 0x12), 0xfb }, /* ADC noise shaping filter controls */ + { ADV76XX_REG(ADV76XX_PAGE_AFE, 0x0c), 0x0d }, /* CP core gain controls */ + + { ADV76XX_REG_SEQ_TERM, 0 }, +}; + +static const struct adv76xx_reg_seq adv7611_recommended_settings_hdmi[] = { + /* ADV7611 Register Settings Recommendations Rev 1.5, May 2014 */ + { ADV76XX_REG(ADV76XX_PAGE_CP, 0x6c), 0x00 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x9b), 0x03 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x6f), 0x08 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x85), 0x1f }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x87), 0x70 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x57), 0xda }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x58), 0x01 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x03), 0x98 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x4c), 0x44 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x8d), 0x04 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x8e), 0x1e }, + + { ADV76XX_REG_SEQ_TERM, 0 }, +}; + +static const struct adv76xx_reg_seq adv7612_recommended_settings_hdmi[] = { + { ADV76XX_REG(ADV76XX_PAGE_CP, 0x6c), 0x00 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x9b), 0x03 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x6f), 0x08 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x85), 0x1f }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x87), 0x70 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x57), 0xda }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x58), 0x01 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x03), 0x98 }, + { ADV76XX_REG(ADV76XX_PAGE_HDMI, 0x4c), 0x44 }, + { ADV76XX_REG_SEQ_TERM, 0 }, +}; + +static const struct adv76xx_chip_info adv76xx_chip_info[] = { + [ADV7604] = { + .type = ADV7604, + .has_afe = true, + .max_port = ADV7604_PAD_VGA_COMP, + .num_dv_ports = 4, + .edid_enable_reg = 0x77, + .edid_status_reg = 0x7d, + .edid_segment_reg = 0x77, + .edid_segment_mask = 0x10, + .edid_spa_loc_reg = 0x76, + .edid_spa_loc_msb_mask = 0x40, + .edid_spa_port_b_reg = 0x70, + .lcf_reg = 0xb3, + .tdms_lock_mask = 0xe0, + .cable_det_mask = 0x1e, + .fmt_change_digital_mask = 0xc1, + .cp_csc = 0xfc, + .cec_irq_status = 0x4d, + .cec_rx_enable = 0x26, + .cec_rx_enable_mask = 0x01, + .cec_irq_swap = true, + .formats = adv7604_formats, + .nformats = ARRAY_SIZE(adv7604_formats), + .set_termination = adv7604_set_termination, + .setup_irqs = adv7604_setup_irqs, + .read_hdmi_pixelclock = adv7604_read_hdmi_pixelclock, + .read_cable_det = adv7604_read_cable_det, + .recommended_settings = { + [0] = adv7604_recommended_settings_afe, + [1] = adv7604_recommended_settings_hdmi, + }, + .num_recommended_settings = { + [0] = ARRAY_SIZE(adv7604_recommended_settings_afe), + [1] = ARRAY_SIZE(adv7604_recommended_settings_hdmi), + }, + .page_mask = BIT(ADV76XX_PAGE_IO) | BIT(ADV7604_PAGE_AVLINK) | + BIT(ADV76XX_PAGE_CEC) | BIT(ADV76XX_PAGE_INFOFRAME) | + BIT(ADV7604_PAGE_ESDP) | BIT(ADV7604_PAGE_DPP) | + BIT(ADV76XX_PAGE_AFE) | BIT(ADV76XX_PAGE_REP) | + BIT(ADV76XX_PAGE_EDID) | BIT(ADV76XX_PAGE_HDMI) | + BIT(ADV76XX_PAGE_TEST) | BIT(ADV76XX_PAGE_CP) | + BIT(ADV7604_PAGE_VDP), + .linewidth_mask = 0xfff, + .field0_height_mask = 0xfff, + .field1_height_mask = 0xfff, + .hfrontporch_mask = 0x3ff, + .hsync_mask = 0x3ff, + .hbackporch_mask = 0x3ff, + .field0_vfrontporch_mask = 0x1fff, + .field0_vsync_mask = 0x1fff, + .field0_vbackporch_mask = 0x1fff, + .field1_vfrontporch_mask = 0x1fff, + .field1_vsync_mask = 0x1fff, + .field1_vbackporch_mask = 0x1fff, + }, + [ADV7611] = { + .type = ADV7611, + .has_afe = false, + .max_port = ADV76XX_PAD_HDMI_PORT_A, + .num_dv_ports = 1, + .edid_enable_reg = 0x74, + .edid_status_reg = 0x76, + .edid_segment_reg = 0x7a, + .edid_segment_mask = 0x01, + .lcf_reg = 0xa3, + .tdms_lock_mask = 0x43, + .cable_det_mask = 0x01, + .fmt_change_digital_mask = 0x03, + .cp_csc = 0xf4, + .cec_irq_status = 0x93, + .cec_rx_enable = 0x2c, + .cec_rx_enable_mask = 0x02, + .formats = adv7611_formats, + .nformats = ARRAY_SIZE(adv7611_formats), + .set_termination = adv7611_set_termination, + .setup_irqs = adv7611_setup_irqs, + .read_hdmi_pixelclock = adv7611_read_hdmi_pixelclock, + .read_cable_det = adv7611_read_cable_det, + .recommended_settings = { + [1] = adv7611_recommended_settings_hdmi, + }, + .num_recommended_settings = { + [1] = ARRAY_SIZE(adv7611_recommended_settings_hdmi), + }, + .page_mask = BIT(ADV76XX_PAGE_IO) | BIT(ADV76XX_PAGE_CEC) | + BIT(ADV76XX_PAGE_INFOFRAME) | BIT(ADV76XX_PAGE_AFE) | + BIT(ADV76XX_PAGE_REP) | BIT(ADV76XX_PAGE_EDID) | + BIT(ADV76XX_PAGE_HDMI) | BIT(ADV76XX_PAGE_CP), + .linewidth_mask = 0x1fff, + .field0_height_mask = 0x1fff, + .field1_height_mask = 0x1fff, + .hfrontporch_mask = 0x1fff, + .hsync_mask = 0x1fff, + .hbackporch_mask = 0x1fff, + .field0_vfrontporch_mask = 0x3fff, + .field0_vsync_mask = 0x3fff, + .field0_vbackporch_mask = 0x3fff, + .field1_vfrontporch_mask = 0x3fff, + .field1_vsync_mask = 0x3fff, + .field1_vbackporch_mask = 0x3fff, + }, + [ADV7612] = { + .type = ADV7612, + .has_afe = false, + .max_port = ADV76XX_PAD_HDMI_PORT_A, /* B not supported */ + .num_dv_ports = 1, /* normally 2 */ + .edid_enable_reg = 0x74, + .edid_status_reg = 0x76, + .edid_segment_reg = 0x7a, + .edid_segment_mask = 0x01, + .edid_spa_loc_reg = 0x70, + .edid_spa_loc_msb_mask = 0x01, + .edid_spa_port_b_reg = 0x52, + .lcf_reg = 0xa3, + .tdms_lock_mask = 0x43, + .cable_det_mask = 0x01, + .fmt_change_digital_mask = 0x03, + .cp_csc = 0xf4, + .cec_irq_status = 0x93, + .cec_rx_enable = 0x2c, + .cec_rx_enable_mask = 0x02, + .formats = adv7612_formats, + .nformats = ARRAY_SIZE(adv7612_formats), + .set_termination = adv7611_set_termination, + .setup_irqs = adv7612_setup_irqs, + .read_hdmi_pixelclock = adv7611_read_hdmi_pixelclock, + .read_cable_det = adv7612_read_cable_det, + .recommended_settings = { + [1] = adv7612_recommended_settings_hdmi, + }, + .num_recommended_settings = { + [1] = ARRAY_SIZE(adv7612_recommended_settings_hdmi), + }, + .page_mask = BIT(ADV76XX_PAGE_IO) | BIT(ADV76XX_PAGE_CEC) | + BIT(ADV76XX_PAGE_INFOFRAME) | BIT(ADV76XX_PAGE_AFE) | + BIT(ADV76XX_PAGE_REP) | BIT(ADV76XX_PAGE_EDID) | + BIT(ADV76XX_PAGE_HDMI) | BIT(ADV76XX_PAGE_CP), + .linewidth_mask = 0x1fff, + .field0_height_mask = 0x1fff, + .field1_height_mask = 0x1fff, + .hfrontporch_mask = 0x1fff, + .hsync_mask = 0x1fff, + .hbackporch_mask = 0x1fff, + .field0_vfrontporch_mask = 0x3fff, + .field0_vsync_mask = 0x3fff, + .field0_vbackporch_mask = 0x3fff, + .field1_vfrontporch_mask = 0x3fff, + .field1_vsync_mask = 0x3fff, + .field1_vbackporch_mask = 0x3fff, + }, +}; + +static const struct i2c_device_id adv76xx_i2c_id[] = { + { .name = "adv7604", .driver_data = (kernel_ulong_t)&adv76xx_chip_info[ADV7604] }, + { .name = "adv7610", .driver_data = (kernel_ulong_t)&adv76xx_chip_info[ADV7611] }, + { .name = "adv7611", .driver_data = (kernel_ulong_t)&adv76xx_chip_info[ADV7611] }, + { .name = "adv7612", .driver_data = (kernel_ulong_t)&adv76xx_chip_info[ADV7612] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv76xx_i2c_id); + +static const struct of_device_id adv76xx_of_id[] __maybe_unused = { + { .compatible = "adi,adv7610", .data = &adv76xx_chip_info[ADV7611] }, + { .compatible = "adi,adv7611", .data = &adv76xx_chip_info[ADV7611] }, + { .compatible = "adi,adv7612", .data = &adv76xx_chip_info[ADV7612] }, + { } +}; +MODULE_DEVICE_TABLE(of, adv76xx_of_id); + +static int adv76xx_parse_dt(struct adv76xx_state *state) +{ + struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 }; + struct device_node *endpoint; + struct device_node *np; + unsigned int flags; + int ret; + u32 v; + + np = state->i2c_clients[ADV76XX_PAGE_IO]->dev.of_node; + + /* FIXME: Parse the endpoint. */ + endpoint = of_graph_get_endpoint_by_regs(np, -1, -1); + if (!endpoint) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), &bus_cfg); + of_node_put(endpoint); + if (ret) + return ret; + + if (!of_property_read_u32(np, "default-input", &v)) + state->pdata.default_input = v; + else + state->pdata.default_input = -1; + + flags = bus_cfg.bus.parallel.flags; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + state->pdata.inv_hs_pol = 1; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + state->pdata.inv_vs_pol = 1; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + state->pdata.inv_llc_pol = 1; + + if (bus_cfg.bus_type == V4L2_MBUS_BT656) + state->pdata.insert_av_codes = 1; + + /* Disable the interrupt for now as no DT-based board uses it. */ + state->pdata.int1_config = ADV76XX_INT1_CONFIG_ACTIVE_HIGH; + + /* Hardcode the remaining platform data fields. */ + state->pdata.disable_pwrdnb = 0; + state->pdata.disable_cable_det_rst = 0; + state->pdata.blank_data = 1; + state->pdata.op_format_mode_sel = ADV7604_OP_FORMAT_MODE0; + state->pdata.bus_order = ADV7604_BUS_ORDER_RGB; + state->pdata.dr_str_data = ADV76XX_DR_STR_MEDIUM_HIGH; + state->pdata.dr_str_clk = ADV76XX_DR_STR_MEDIUM_HIGH; + state->pdata.dr_str_sync = ADV76XX_DR_STR_MEDIUM_HIGH; + + return 0; +} + +static const struct regmap_config adv76xx_regmap_cnf[] = { + { + .name = "io", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "avlink", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "cec", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "infoframe", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "esdp", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "epp", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "afe", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "rep", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "edid", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + + { + .name = "hdmi", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "test", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "cp", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, + { + .name = "vdp", + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + }, +}; + +static int configure_regmap(struct adv76xx_state *state, int region) +{ + int err; + + if (!state->i2c_clients[region]) + return -ENODEV; + + state->regmap[region] = + devm_regmap_init_i2c(state->i2c_clients[region], + &adv76xx_regmap_cnf[region]); + + if (IS_ERR(state->regmap[region])) { + err = PTR_ERR(state->regmap[region]); + v4l_err(state->i2c_clients[region], + "Error initializing regmap %d with error %d\n", + region, err); + return -EINVAL; + } + + return 0; +} + +static int configure_regmaps(struct adv76xx_state *state) +{ + int i, err; + + for (i = ADV7604_PAGE_AVLINK ; i < ADV76XX_PAGE_MAX; i++) { + err = configure_regmap(state, i); + if (err && (err != -ENODEV)) + return err; + } + return 0; +} + +static void adv76xx_reset(struct adv76xx_state *state) +{ + if (state->reset_gpio) { + /* + * Note: Misinterpretation of reset assertion - do not re-use + * this code. The reset pin is using incorrect (for a reset + * signal) logical level. + * + * ADV76XX can be reset by a low reset pulse of minimum 5 ms. + */ + gpiod_set_value_cansleep(state->reset_gpio, 0); + usleep_range(5000, 10000); + gpiod_set_value_cansleep(state->reset_gpio, 1); + /* It is recommended to wait 5 ms after the low pulse before */ + /* an I2C write is performed to the ADV76XX. */ + usleep_range(5000, 10000); + } +} + +static int adv76xx_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + static const struct v4l2_dv_timings cea640x480 = + V4L2_DV_BT_CEA_640X480P59_94; + struct adv76xx_state *state; + struct v4l2_ctrl_handler *hdl; + struct v4l2_ctrl *ctrl; + struct v4l2_subdev *sd; + unsigned int i; + unsigned int val, val2; + int err; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + v4l_dbg(1, debug, client, "detecting adv76xx client on address 0x%x\n", + client->addr << 1); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->i2c_clients[ADV76XX_PAGE_IO] = client; + + /* initialize variables */ + state->restart_stdi_once = true; + state->selected_input = ~0; + + if (IS_ENABLED(CONFIG_OF) && client->dev.of_node) { + const struct of_device_id *oid; + + oid = of_match_node(adv76xx_of_id, client->dev.of_node); + state->info = oid->data; + + err = adv76xx_parse_dt(state); + if (err < 0) { + v4l_err(client, "DT parsing error\n"); + return err; + } + } else if (client->dev.platform_data) { + struct adv76xx_platform_data *pdata = client->dev.platform_data; + + state->info = (const struct adv76xx_chip_info *)id->driver_data; + state->pdata = *pdata; + } else { + v4l_err(client, "No platform data!\n"); + return -ENODEV; + } + + /* Request GPIOs. */ + for (i = 0; i < state->info->num_dv_ports; ++i) { + state->hpd_gpio[i] = + devm_gpiod_get_index_optional(&client->dev, "hpd", i, + GPIOD_OUT_LOW); + if (IS_ERR(state->hpd_gpio[i])) + return PTR_ERR(state->hpd_gpio[i]); + + if (state->hpd_gpio[i]) + v4l_info(client, "Handling HPD %u GPIO\n", i); + } + state->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(state->reset_gpio)) + return PTR_ERR(state->reset_gpio); + + adv76xx_reset(state); + + state->timings = cea640x480; + state->format = adv76xx_format_info(state, MEDIA_BUS_FMT_YUYV8_2X8); + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &adv76xx_ops); + snprintf(sd->name, sizeof(sd->name), "%s %d-%04x", + id->name, i2c_adapter_id(client->adapter), + client->addr); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + sd->internal_ops = &adv76xx_int_ops; + + /* Configure IO Regmap region */ + err = configure_regmap(state, ADV76XX_PAGE_IO); + + if (err) { + v4l2_err(sd, "Error configuring IO regmap region\n"); + return -ENODEV; + } + + /* + * Verify that the chip is present. On ADV7604 the RD_INFO register only + * identifies the revision, while on ADV7611 it identifies the model as + * well. Use the HDMI slave address on ADV7604 and RD_INFO on ADV7611. + */ + switch (state->info->type) { + case ADV7604: + err = regmap_read(state->regmap[ADV76XX_PAGE_IO], 0xfb, &val); + if (err) { + v4l2_err(sd, "Error %d reading IO Regmap\n", err); + return -ENODEV; + } + if (val != 0x68) { + v4l2_err(sd, "not an ADV7604 on address 0x%x\n", + client->addr << 1); + return -ENODEV; + } + break; + case ADV7611: + case ADV7612: + err = regmap_read(state->regmap[ADV76XX_PAGE_IO], + 0xea, + &val); + if (err) { + v4l2_err(sd, "Error %d reading IO Regmap\n", err); + return -ENODEV; + } + val2 = val << 8; + err = regmap_read(state->regmap[ADV76XX_PAGE_IO], + 0xeb, + &val); + if (err) { + v4l2_err(sd, "Error %d reading IO Regmap\n", err); + return -ENODEV; + } + val |= val2; + if ((state->info->type == ADV7611 && val != 0x2051) || + (state->info->type == ADV7612 && val != 0x2041)) { + v4l2_err(sd, "not an %s on address 0x%x\n", + state->info->type == ADV7611 ? "ADV7610/11" : "ADV7612", + client->addr << 1); + return -ENODEV; + } + break; + } + + /* control handlers */ + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, adv76xx_has_afe(state) ? 9 : 8); + + v4l2_ctrl_new_std(hdl, &adv76xx_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &adv76xx_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &adv76xx_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &adv76xx_ctrl_ops, + V4L2_CID_HUE, 0, 255, 1, 0); + ctrl = v4l2_ctrl_new_std_menu(hdl, &adv76xx_ctrl_ops, + V4L2_CID_DV_RX_IT_CONTENT_TYPE, V4L2_DV_IT_CONTENT_TYPE_NO_ITC, + 0, V4L2_DV_IT_CONTENT_TYPE_NO_ITC); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + state->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_RX_POWER_PRESENT, 0, + (1 << state->info->num_dv_ports) - 1, 0, 0); + state->rgb_quantization_range_ctrl = + v4l2_ctrl_new_std_menu(hdl, &adv76xx_ctrl_ops, + V4L2_CID_DV_RX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, + 0, V4L2_DV_RGB_RANGE_AUTO); + + /* custom controls */ + if (adv76xx_has_afe(state)) + state->analog_sampling_phase_ctrl = + v4l2_ctrl_new_custom(hdl, &adv7604_ctrl_analog_sampling_phase, NULL); + state->free_run_color_manual_ctrl = + v4l2_ctrl_new_custom(hdl, &adv76xx_ctrl_free_run_color_manual, NULL); + state->free_run_color_ctrl = + v4l2_ctrl_new_custom(hdl, &adv76xx_ctrl_free_run_color, NULL); + + sd->ctrl_handler = hdl; + if (hdl->error) { + err = hdl->error; + goto err_hdl; + } + if (adv76xx_s_detect_tx_5v_ctrl(sd)) { + err = -ENODEV; + goto err_hdl; + } + + for (i = 1; i < ADV76XX_PAGE_MAX; ++i) { + struct i2c_client *dummy_client; + + if (!(BIT(i) & state->info->page_mask)) + continue; + + dummy_client = adv76xx_dummy_client(sd, i); + if (IS_ERR(dummy_client)) { + err = PTR_ERR(dummy_client); + v4l2_err(sd, "failed to create i2c client %u\n", i); + goto err_i2c; + } + + state->i2c_clients[i] = dummy_client; + } + + INIT_DELAYED_WORK(&state->delayed_work_enable_hotplug, + adv76xx_delayed_work_enable_hotplug); + + state->source_pad = state->info->num_dv_ports + + (state->info->has_afe ? 2 : 0); + if (WARN_ON(state->source_pad >= ADV76XX_PAD_MAX)) { + err = -EINVAL; + v4l2_err(sd, "invalid chip info\n"); + goto err_i2c; + } + + for (i = 0; i < state->source_pad; ++i) + state->pads[i].flags = MEDIA_PAD_FL_SINK; + state->pads[state->source_pad].flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_DV_DECODER; + + err = media_entity_pads_init(&sd->entity, state->source_pad + 1, + state->pads); + if (err) + goto err_i2c; + + /* Configure regmaps */ + err = configure_regmaps(state); + if (err) + goto err_entity; + + err = adv76xx_core_init(sd); + if (err) + goto err_entity; + + if (client->irq) { + err = devm_request_threaded_irq(&client->dev, + client->irq, + NULL, adv76xx_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + client->name, state); + if (err) + goto err_entity; + } + +#if IS_ENABLED(CONFIG_VIDEO_ADV7604_CEC) + state->cec_adap = cec_allocate_adapter(&adv76xx_cec_adap_ops, + state, dev_name(&client->dev), + CEC_CAP_DEFAULTS, ADV76XX_MAX_ADDRS); + err = PTR_ERR_OR_ZERO(state->cec_adap); + if (err) + goto err_entity; +#endif + + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + + err = v4l2_async_register_subdev(sd); + if (err) + goto err_entity; + + return 0; + +err_entity: + media_entity_cleanup(&sd->entity); +err_i2c: + adv76xx_unregister_clients(state); +err_hdl: + v4l2_ctrl_handler_free(hdl); + return err; +} + +/* ----------------------------------------------------------------------- */ + +static void adv76xx_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv76xx_state *state = to_state(sd); + + /* disable interrupts */ + io_write(sd, 0x40, 0); + io_write(sd, 0x41, 0); + io_write(sd, 0x46, 0); + io_write(sd, 0x6e, 0); + io_write(sd, 0x73, 0); + + cancel_delayed_work_sync(&state->delayed_work_enable_hotplug); + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + adv76xx_unregister_clients(to_state(sd)); + v4l2_ctrl_handler_free(sd->ctrl_handler); +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver adv76xx_driver = { + .driver = { + .name = "adv7604", + .of_match_table = of_match_ptr(adv76xx_of_id), + }, + .probe = adv76xx_probe, + .remove = adv76xx_remove, + .id_table = adv76xx_i2c_id, +}; + +module_i2c_driver(adv76xx_driver); diff --git a/drivers/media/i2c/adv7842.c b/drivers/media/i2c/adv7842.c new file mode 100644 index 000000000..3cfae89ce --- /dev/null +++ b/drivers/media/i2c/adv7842.c @@ -0,0 +1,3694 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * adv7842 - Analog Devices ADV7842 video decoder driver + * + * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + */ + +/* + * References (c = chapter, p = page): + * REF_01 - Analog devices, ADV7842, + * Register Settings Recommendations, Rev. 1.9, April 2011 + * REF_02 - Analog devices, Software User Guide, UG-206, + * ADV7842 I2C Register Maps, Rev. 0, November 2010 + * REF_03 - Analog devices, Hardware User Guide, UG-214, + * ADV7842 Fast Switching 2:1 HDMI 1.4 Receiver with 3D-Comb + * Decoder and Digitizer , Rev. 0, January 2011 + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +MODULE_DESCRIPTION("Analog Devices ADV7842 video decoder driver"); +MODULE_AUTHOR("Hans Verkuil "); +MODULE_AUTHOR("Martin Bugge "); +MODULE_LICENSE("GPL"); + +/* ADV7842 system clock frequency */ +#define ADV7842_fsc (28636360) + +#define ADV7842_RGB_OUT (1 << 1) + +#define ADV7842_OP_FORMAT_SEL_8BIT (0 << 0) +#define ADV7842_OP_FORMAT_SEL_10BIT (1 << 0) +#define ADV7842_OP_FORMAT_SEL_12BIT (2 << 0) + +#define ADV7842_OP_MODE_SEL_SDR_422 (0 << 5) +#define ADV7842_OP_MODE_SEL_DDR_422 (1 << 5) +#define ADV7842_OP_MODE_SEL_SDR_444 (2 << 5) +#define ADV7842_OP_MODE_SEL_DDR_444 (3 << 5) +#define ADV7842_OP_MODE_SEL_SDR_422_2X (4 << 5) +#define ADV7842_OP_MODE_SEL_ADI_CM (5 << 5) + +#define ADV7842_OP_CH_SEL_GBR (0 << 5) +#define ADV7842_OP_CH_SEL_GRB (1 << 5) +#define ADV7842_OP_CH_SEL_BGR (2 << 5) +#define ADV7842_OP_CH_SEL_RGB (3 << 5) +#define ADV7842_OP_CH_SEL_BRG (4 << 5) +#define ADV7842_OP_CH_SEL_RBG (5 << 5) + +#define ADV7842_OP_SWAP_CB_CR (1 << 0) + +#define ADV7842_MAX_ADDRS (3) + +/* +********************************************************************** +* +* Arrays with configuration parameters for the ADV7842 +* +********************************************************************** +*/ + +struct adv7842_format_info { + u32 code; + u8 op_ch_sel; + bool rgb_out; + bool swap_cb_cr; + u8 op_format_sel; +}; + +struct adv7842_state { + struct adv7842_platform_data pdata; + struct v4l2_subdev sd; + struct media_pad pads[ADV7842_PAD_SOURCE + 1]; + struct v4l2_ctrl_handler hdl; + enum adv7842_mode mode; + struct v4l2_dv_timings timings; + enum adv7842_vid_std_select vid_std_select; + + const struct adv7842_format_info *format; + + v4l2_std_id norm; + struct { + u8 edid[512]; + u32 blocks; + u32 present; + } hdmi_edid; + struct { + u8 edid[128]; + u32 blocks; + u32 present; + } vga_edid; + struct v4l2_fract aspect_ratio; + u32 rgb_quantization_range; + bool is_cea_format; + struct delayed_work delayed_work_enable_hotplug; + bool restart_stdi_once; + bool hdmi_port_a; + + struct dentry *debugfs_dir; + struct v4l2_debugfs_if *infoframes; + + /* i2c clients */ + struct i2c_client *i2c_sdp_io; + struct i2c_client *i2c_sdp; + struct i2c_client *i2c_cp; + struct i2c_client *i2c_vdp; + struct i2c_client *i2c_afe; + struct i2c_client *i2c_hdmi; + struct i2c_client *i2c_repeater; + struct i2c_client *i2c_edid; + struct i2c_client *i2c_infoframe; + struct i2c_client *i2c_cec; + struct i2c_client *i2c_avlink; + + /* controls */ + struct v4l2_ctrl *detect_tx_5v_ctrl; + struct v4l2_ctrl *analog_sampling_phase_ctrl; + struct v4l2_ctrl *free_run_color_ctrl_manual; + struct v4l2_ctrl *free_run_color_ctrl; + struct v4l2_ctrl *rgb_quantization_range_ctrl; + + struct cec_adapter *cec_adap; + u8 cec_addr[ADV7842_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; +}; + +/* Unsupported timings. This device cannot support 720p30. */ +static const struct v4l2_dv_timings adv7842_timings_exceptions[] = { + V4L2_DV_BT_CEA_1280X720P30, + { } +}; + +static bool adv7842_check_dv_timings(const struct v4l2_dv_timings *t, void *hdl) +{ + int i; + + for (i = 0; adv7842_timings_exceptions[i].bt.width; i++) + if (v4l2_match_dv_timings(t, adv7842_timings_exceptions + i, 0, false)) + return false; + return true; +} + +struct adv7842_video_standards { + struct v4l2_dv_timings timings; + u8 vid_std; + u8 v_freq; +}; + +/* sorted by number of lines */ +static const struct adv7842_video_standards adv7842_prim_mode_comp[] = { + /* { V4L2_DV_BT_CEA_720X480P59_94, 0x0a, 0x00 }, TODO flickering */ + { V4L2_DV_BT_CEA_720X576P50, 0x0b, 0x00 }, + { V4L2_DV_BT_CEA_1280X720P50, 0x19, 0x01 }, + { V4L2_DV_BT_CEA_1280X720P60, 0x19, 0x00 }, + { V4L2_DV_BT_CEA_1920X1080P24, 0x1e, 0x04 }, + { V4L2_DV_BT_CEA_1920X1080P25, 0x1e, 0x03 }, + { V4L2_DV_BT_CEA_1920X1080P30, 0x1e, 0x02 }, + { V4L2_DV_BT_CEA_1920X1080P50, 0x1e, 0x01 }, + { V4L2_DV_BT_CEA_1920X1080P60, 0x1e, 0x00 }, + /* TODO add 1920x1080P60_RB (CVT timing) */ + { }, +}; + +/* sorted by number of lines */ +static const struct adv7842_video_standards adv7842_prim_mode_gr[] = { + { V4L2_DV_BT_DMT_640X480P60, 0x08, 0x00 }, + { V4L2_DV_BT_DMT_640X480P72, 0x09, 0x00 }, + { V4L2_DV_BT_DMT_640X480P75, 0x0a, 0x00 }, + { V4L2_DV_BT_DMT_640X480P85, 0x0b, 0x00 }, + { V4L2_DV_BT_DMT_800X600P56, 0x00, 0x00 }, + { V4L2_DV_BT_DMT_800X600P60, 0x01, 0x00 }, + { V4L2_DV_BT_DMT_800X600P72, 0x02, 0x00 }, + { V4L2_DV_BT_DMT_800X600P75, 0x03, 0x00 }, + { V4L2_DV_BT_DMT_800X600P85, 0x04, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P60, 0x0c, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P70, 0x0d, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P75, 0x0e, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P85, 0x0f, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P60, 0x05, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P75, 0x06, 0x00 }, + { V4L2_DV_BT_DMT_1360X768P60, 0x12, 0x00 }, + { V4L2_DV_BT_DMT_1366X768P60, 0x13, 0x00 }, + { V4L2_DV_BT_DMT_1400X1050P60, 0x14, 0x00 }, + { V4L2_DV_BT_DMT_1400X1050P75, 0x15, 0x00 }, + { V4L2_DV_BT_DMT_1600X1200P60, 0x16, 0x00 }, /* TODO not tested */ + /* TODO add 1600X1200P60_RB (not a DMT timing) */ + { V4L2_DV_BT_DMT_1680X1050P60, 0x18, 0x00 }, + { V4L2_DV_BT_DMT_1920X1200P60_RB, 0x19, 0x00 }, /* TODO not tested */ + { }, +}; + +/* sorted by number of lines */ +static const struct adv7842_video_standards adv7842_prim_mode_hdmi_comp[] = { + { V4L2_DV_BT_CEA_720X480P59_94, 0x0a, 0x00 }, + { V4L2_DV_BT_CEA_720X576P50, 0x0b, 0x00 }, + { V4L2_DV_BT_CEA_1280X720P50, 0x13, 0x01 }, + { V4L2_DV_BT_CEA_1280X720P60, 0x13, 0x00 }, + { V4L2_DV_BT_CEA_1920X1080P24, 0x1e, 0x04 }, + { V4L2_DV_BT_CEA_1920X1080P25, 0x1e, 0x03 }, + { V4L2_DV_BT_CEA_1920X1080P30, 0x1e, 0x02 }, + { V4L2_DV_BT_CEA_1920X1080P50, 0x1e, 0x01 }, + { V4L2_DV_BT_CEA_1920X1080P60, 0x1e, 0x00 }, + { }, +}; + +/* sorted by number of lines */ +static const struct adv7842_video_standards adv7842_prim_mode_hdmi_gr[] = { + { V4L2_DV_BT_DMT_640X480P60, 0x08, 0x00 }, + { V4L2_DV_BT_DMT_640X480P72, 0x09, 0x00 }, + { V4L2_DV_BT_DMT_640X480P75, 0x0a, 0x00 }, + { V4L2_DV_BT_DMT_640X480P85, 0x0b, 0x00 }, + { V4L2_DV_BT_DMT_800X600P56, 0x00, 0x00 }, + { V4L2_DV_BT_DMT_800X600P60, 0x01, 0x00 }, + { V4L2_DV_BT_DMT_800X600P72, 0x02, 0x00 }, + { V4L2_DV_BT_DMT_800X600P75, 0x03, 0x00 }, + { V4L2_DV_BT_DMT_800X600P85, 0x04, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P60, 0x0c, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P70, 0x0d, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P75, 0x0e, 0x00 }, + { V4L2_DV_BT_DMT_1024X768P85, 0x0f, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P60, 0x05, 0x00 }, + { V4L2_DV_BT_DMT_1280X1024P75, 0x06, 0x00 }, + { }, +}; + +static const struct v4l2_event adv7842_ev_fmt = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, +}; + +/* ----------------------------------------------------------------------- */ + +static inline struct adv7842_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7842_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7842_state, hdl)->sd; +} + +static inline unsigned htotal(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_FRAME_WIDTH(t); +} + +static inline unsigned vtotal(const struct v4l2_bt_timings *t) +{ + return V4L2_DV_BT_FRAME_HEIGHT(t); +} + + +/* ----------------------------------------------------------------------- */ + +static s32 adv_smbus_read_byte_data_check(struct i2c_client *client, + u8 command, bool check) +{ + union i2c_smbus_data data; + + if (!i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, command, + I2C_SMBUS_BYTE_DATA, &data)) + return data.byte; + if (check) + v4l_err(client, "error reading %02x, %02x\n", + client->addr, command); + return -EIO; +} + +static s32 adv_smbus_read_byte_data(struct i2c_client *client, u8 command) +{ + int i; + + for (i = 0; i < 3; i++) { + int ret = adv_smbus_read_byte_data_check(client, command, true); + + if (ret >= 0) { + if (i) + v4l_err(client, "read ok after %d retries\n", i); + return ret; + } + } + v4l_err(client, "read failed\n"); + return -EIO; +} + +static s32 adv_smbus_write_byte_data(struct i2c_client *client, + u8 command, u8 value) +{ + union i2c_smbus_data data; + int err; + int i; + + data.byte = value; + for (i = 0; i < 3; i++) { + err = i2c_smbus_xfer(client->adapter, client->addr, + client->flags, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_BYTE_DATA, &data); + if (!err) + break; + } + if (err < 0) + v4l_err(client, "error writing %02x, %02x, %02x\n", + client->addr, command, value); + return err; +} + +static void adv_smbus_write_byte_no_check(struct i2c_client *client, + u8 command, u8 value) +{ + union i2c_smbus_data data; + data.byte = value; + + i2c_smbus_xfer(client->adapter, client->addr, + client->flags, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_BYTE_DATA, &data); +} + +/* ----------------------------------------------------------------------- */ + +static inline int io_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return adv_smbus_read_byte_data(client, reg); +} + +static inline int io_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return adv_smbus_write_byte_data(client, reg, val); +} + +static inline int io_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return io_write(sd, reg, (io_read(sd, reg) & mask) | val); +} + +static inline int io_write_clr_set(struct v4l2_subdev *sd, + u8 reg, u8 mask, u8 val) +{ + return io_write(sd, reg, (io_read(sd, reg) & ~mask) | val); +} + +static inline int avlink_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_avlink, reg); +} + +static inline int avlink_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_avlink, reg, val); +} + +static inline int cec_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_cec, reg); +} + +static inline int cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_cec, reg, val); +} + +static inline int cec_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return cec_write(sd, reg, (cec_read(sd, reg) & ~mask) | val); +} + +static inline int infoframe_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_infoframe, reg); +} + +static inline int infoframe_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_infoframe, reg, val); +} + +static inline int sdp_io_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_sdp_io, reg); +} + +static inline int sdp_io_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_sdp_io, reg, val); +} + +static inline int sdp_io_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return sdp_io_write(sd, reg, (sdp_io_read(sd, reg) & mask) | val); +} + +static inline int sdp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_sdp, reg); +} + +static inline int sdp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_sdp, reg, val); +} + +static inline int sdp_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return sdp_write(sd, reg, (sdp_read(sd, reg) & mask) | val); +} + +static inline int afe_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_afe, reg); +} + +static inline int afe_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_afe, reg, val); +} + +static inline int afe_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return afe_write(sd, reg, (afe_read(sd, reg) & mask) | val); +} + +static inline int rep_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_repeater, reg); +} + +static inline int rep_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_repeater, reg, val); +} + +static inline int rep_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return rep_write(sd, reg, (rep_read(sd, reg) & mask) | val); +} + +static inline int edid_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_edid, reg); +} + +static inline int edid_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_edid, reg, val); +} + +static inline int hdmi_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_hdmi, reg); +} + +static inline int hdmi_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_hdmi, reg, val); +} + +static inline int hdmi_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return hdmi_write(sd, reg, (hdmi_read(sd, reg) & mask) | val); +} + +static inline int cp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_cp, reg); +} + +static inline int cp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_cp, reg, val); +} + +static inline int cp_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +{ + return cp_write(sd, reg, (cp_read(sd, reg) & mask) | val); +} + +static inline int vdp_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_read_byte_data(state->i2c_vdp, reg); +} + +static inline int vdp_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7842_state *state = to_state(sd); + + return adv_smbus_write_byte_data(state->i2c_vdp, reg, val); +} + +static void main_reset(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + adv_smbus_write_byte_no_check(client, 0xff, 0x80); + + mdelay(5); +} + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +static const struct adv7842_format_info adv7842_formats[] = { + { MEDIA_BUS_FMT_RGB888_1X24, ADV7842_OP_CH_SEL_RGB, true, false, + ADV7842_OP_MODE_SEL_SDR_444 | ADV7842_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_2X8, ADV7842_OP_CH_SEL_RGB, false, false, + ADV7842_OP_MODE_SEL_SDR_422 | ADV7842_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_2X8, ADV7842_OP_CH_SEL_RGB, false, true, + ADV7842_OP_MODE_SEL_SDR_422 | ADV7842_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV10_2X10, ADV7842_OP_CH_SEL_RGB, false, false, + ADV7842_OP_MODE_SEL_SDR_422 | ADV7842_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YVYU10_2X10, ADV7842_OP_CH_SEL_RGB, false, true, + ADV7842_OP_MODE_SEL_SDR_422 | ADV7842_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YUYV12_2X12, ADV7842_OP_CH_SEL_RGB, false, false, + ADV7842_OP_MODE_SEL_SDR_422 | ADV7842_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YVYU12_2X12, ADV7842_OP_CH_SEL_RGB, false, true, + ADV7842_OP_MODE_SEL_SDR_422 | ADV7842_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_UYVY8_1X16, ADV7842_OP_CH_SEL_RBG, false, false, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_VYUY8_1X16, ADV7842_OP_CH_SEL_RBG, false, true, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YUYV8_1X16, ADV7842_OP_CH_SEL_RGB, false, false, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_YVYU8_1X16, ADV7842_OP_CH_SEL_RGB, false, true, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_8BIT }, + { MEDIA_BUS_FMT_UYVY10_1X20, ADV7842_OP_CH_SEL_RBG, false, false, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_VYUY10_1X20, ADV7842_OP_CH_SEL_RBG, false, true, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YUYV10_1X20, ADV7842_OP_CH_SEL_RGB, false, false, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_YVYU10_1X20, ADV7842_OP_CH_SEL_RGB, false, true, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_10BIT }, + { MEDIA_BUS_FMT_UYVY12_1X24, ADV7842_OP_CH_SEL_RBG, false, false, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_VYUY12_1X24, ADV7842_OP_CH_SEL_RBG, false, true, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YUYV12_1X24, ADV7842_OP_CH_SEL_RGB, false, false, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_12BIT }, + { MEDIA_BUS_FMT_YVYU12_1X24, ADV7842_OP_CH_SEL_RGB, false, true, + ADV7842_OP_MODE_SEL_SDR_422_2X | ADV7842_OP_FORMAT_SEL_12BIT }, +}; + +static const struct adv7842_format_info * +adv7842_format_info(struct adv7842_state *state, u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(adv7842_formats); ++i) { + if (adv7842_formats[i].code == code) + return &adv7842_formats[i]; + } + + return NULL; +} + +/* ----------------------------------------------------------------------- */ + +static inline bool is_analog_input(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + + return ((state->mode == ADV7842_MODE_RGB) || + (state->mode == ADV7842_MODE_COMP)); +} + +static inline bool is_digital_input(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + + return state->mode == ADV7842_MODE_HDMI; +} + +static const struct v4l2_dv_timings_cap adv7842_timings_cap_analog = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + V4L2_INIT_BT_TIMINGS(640, 1920, 350, 1200, 25000000, 170000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM) +}; + +static const struct v4l2_dv_timings_cap adv7842_timings_cap_digital = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + V4L2_INIT_BT_TIMINGS(640, 1920, 350, 1200, 25000000, 225000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM) +}; + +static inline const struct v4l2_dv_timings_cap * +adv7842_get_dv_timings_cap(struct v4l2_subdev *sd) +{ + return is_digital_input(sd) ? &adv7842_timings_cap_digital : + &adv7842_timings_cap_analog; +} + +/* ----------------------------------------------------------------------- */ + +static u16 adv7842_read_cable_det(struct v4l2_subdev *sd) +{ + u8 reg = io_read(sd, 0x6f); + u16 val = 0; + + if (reg & 0x02) + val |= 1; /* port A */ + if (reg & 0x01) + val |= 2; /* port B */ + return val; +} + +static void adv7842_delayed_work_enable_hotplug(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct adv7842_state *state = container_of(dwork, + struct adv7842_state, delayed_work_enable_hotplug); + struct v4l2_subdev *sd = &state->sd; + int present = state->hdmi_edid.present; + u8 mask = 0; + + v4l2_dbg(2, debug, sd, "%s: enable hotplug on ports: 0x%x\n", + __func__, present); + + if (present & (0x04 << ADV7842_EDID_PORT_A)) + mask |= 0x20; + if (present & (0x04 << ADV7842_EDID_PORT_B)) + mask |= 0x10; + io_write_and_or(sd, 0x20, 0xcf, mask); +} + +static int edid_write_vga_segment(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv7842_state *state = to_state(sd); + const u8 *edid = state->vga_edid.edid; + u32 blocks = state->vga_edid.blocks; + int err = 0; + int i; + + v4l2_dbg(2, debug, sd, "%s: write EDID on VGA port\n", __func__); + + if (!state->vga_edid.present) + return 0; + + /* HPA disable on port A and B */ + io_write_and_or(sd, 0x20, 0xcf, 0x00); + + /* Disable I2C access to internal EDID ram from VGA DDC port */ + rep_write_and_or(sd, 0x7f, 0x7f, 0x00); + + /* edid segment pointer '1' for VGA port */ + rep_write_and_or(sd, 0x77, 0xef, 0x10); + + for (i = 0; !err && i < blocks * 128; i += I2C_SMBUS_BLOCK_MAX) + err = i2c_smbus_write_i2c_block_data(state->i2c_edid, i, + I2C_SMBUS_BLOCK_MAX, + edid + i); + if (err) + return err; + + /* Calculates the checksums and enables I2C access + * to internal EDID ram from VGA DDC port. + */ + rep_write_and_or(sd, 0x7f, 0x7f, 0x80); + + for (i = 0; i < 1000; i++) { + if (rep_read(sd, 0x79) & 0x20) + break; + mdelay(1); + } + if (i == 1000) { + v4l_err(client, "error enabling edid on VGA port\n"); + return -EIO; + } + + /* enable hotplug after 200 ms */ + schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 5); + + return 0; +} + +static int edid_write_hdmi_segment(struct v4l2_subdev *sd, u8 port) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv7842_state *state = to_state(sd); + const u8 *edid = state->hdmi_edid.edid; + u32 blocks = state->hdmi_edid.blocks; + unsigned int spa_loc; + u16 pa, parent_pa; + int err = 0; + int i; + + v4l2_dbg(2, debug, sd, "%s: write EDID on port %c\n", + __func__, (port == ADV7842_EDID_PORT_A) ? 'A' : 'B'); + + /* HPA disable on port A and B */ + io_write_and_or(sd, 0x20, 0xcf, 0x00); + + /* Disable I2C access to internal EDID ram from HDMI DDC ports */ + rep_write_and_or(sd, 0x77, 0xf3, 0x00); + + if (!state->hdmi_edid.present) { + cec_phys_addr_invalidate(state->cec_adap); + return 0; + } + + pa = v4l2_get_edid_phys_addr(edid, blocks * 128, &spa_loc); + err = v4l2_phys_addr_validate(pa, &parent_pa, NULL); + if (err) + return err; + + if (!spa_loc) { + /* + * There is no SPA, so just set spa_loc to 128 and pa to whatever + * data is there. + */ + spa_loc = 128; + pa = (edid[spa_loc] << 8) | edid[spa_loc + 1]; + } + + + for (i = 0; !err && i < blocks * 128; i += I2C_SMBUS_BLOCK_MAX) { + /* set edid segment pointer for HDMI ports */ + if (i % 256 == 0) + rep_write_and_or(sd, 0x77, 0xef, i >= 256 ? 0x10 : 0x00); + err = i2c_smbus_write_i2c_block_data(state->i2c_edid, i, + I2C_SMBUS_BLOCK_MAX, edid + i); + } + if (err) + return err; + + if (port == ADV7842_EDID_PORT_A) { + rep_write(sd, 0x72, pa >> 8); + rep_write(sd, 0x73, pa & 0xff); + } else { + rep_write(sd, 0x74, pa >> 8); + rep_write(sd, 0x75, pa & 0xff); + } + rep_write(sd, 0x76, spa_loc & 0xff); + rep_write_and_or(sd, 0x77, 0xbf, (spa_loc >> 2) & 0x40); + + /* Calculates the checksums and enables I2C access to internal + * EDID ram from HDMI DDC ports + */ + rep_write_and_or(sd, 0x77, 0xf3, state->hdmi_edid.present); + + for (i = 0; i < 1000; i++) { + if (rep_read(sd, 0x7d) & state->hdmi_edid.present) + break; + mdelay(1); + } + if (i == 1000) { + v4l_err(client, "error enabling edid on port %c\n", + (port == ADV7842_EDID_PORT_A) ? 'A' : 'B'); + return -EIO; + } + cec_s_phys_addr(state->cec_adap, parent_pa, false); + + /* enable hotplug after 200 ms */ + schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 5); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static void adv7842_inv_register(struct v4l2_subdev *sd) +{ + v4l2_info(sd, "0x000-0x0ff: IO Map\n"); + v4l2_info(sd, "0x100-0x1ff: AVLink Map\n"); + v4l2_info(sd, "0x200-0x2ff: CEC Map\n"); + v4l2_info(sd, "0x300-0x3ff: InfoFrame Map\n"); + v4l2_info(sd, "0x400-0x4ff: SDP_IO Map\n"); + v4l2_info(sd, "0x500-0x5ff: SDP Map\n"); + v4l2_info(sd, "0x600-0x6ff: AFE Map\n"); + v4l2_info(sd, "0x700-0x7ff: Repeater Map\n"); + v4l2_info(sd, "0x800-0x8ff: EDID Map\n"); + v4l2_info(sd, "0x900-0x9ff: HDMI Map\n"); + v4l2_info(sd, "0xa00-0xaff: CP Map\n"); + v4l2_info(sd, "0xb00-0xbff: VDP Map\n"); +} + +static int adv7842_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + reg->size = 1; + switch (reg->reg >> 8) { + case 0: + reg->val = io_read(sd, reg->reg & 0xff); + break; + case 1: + reg->val = avlink_read(sd, reg->reg & 0xff); + break; + case 2: + reg->val = cec_read(sd, reg->reg & 0xff); + break; + case 3: + reg->val = infoframe_read(sd, reg->reg & 0xff); + break; + case 4: + reg->val = sdp_io_read(sd, reg->reg & 0xff); + break; + case 5: + reg->val = sdp_read(sd, reg->reg & 0xff); + break; + case 6: + reg->val = afe_read(sd, reg->reg & 0xff); + break; + case 7: + reg->val = rep_read(sd, reg->reg & 0xff); + break; + case 8: + reg->val = edid_read(sd, reg->reg & 0xff); + break; + case 9: + reg->val = hdmi_read(sd, reg->reg & 0xff); + break; + case 0xa: + reg->val = cp_read(sd, reg->reg & 0xff); + break; + case 0xb: + reg->val = vdp_read(sd, reg->reg & 0xff); + break; + default: + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv7842_inv_register(sd); + break; + } + return 0; +} + +static int adv7842_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + u8 val = reg->val & 0xff; + + switch (reg->reg >> 8) { + case 0: + io_write(sd, reg->reg & 0xff, val); + break; + case 1: + avlink_write(sd, reg->reg & 0xff, val); + break; + case 2: + cec_write(sd, reg->reg & 0xff, val); + break; + case 3: + infoframe_write(sd, reg->reg & 0xff, val); + break; + case 4: + sdp_io_write(sd, reg->reg & 0xff, val); + break; + case 5: + sdp_write(sd, reg->reg & 0xff, val); + break; + case 6: + afe_write(sd, reg->reg & 0xff, val); + break; + case 7: + rep_write(sd, reg->reg & 0xff, val); + break; + case 8: + edid_write(sd, reg->reg & 0xff, val); + break; + case 9: + hdmi_write(sd, reg->reg & 0xff, val); + break; + case 0xa: + cp_write(sd, reg->reg & 0xff, val); + break; + case 0xb: + vdp_write(sd, reg->reg & 0xff, val); + break; + default: + v4l2_info(sd, "Register %03llx not supported\n", reg->reg); + adv7842_inv_register(sd); + break; + } + return 0; +} +#endif + +static int adv7842_s_detect_tx_5v_ctrl(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + u16 cable_det = adv7842_read_cable_det(sd); + + v4l2_dbg(1, debug, sd, "%s: 0x%x\n", __func__, cable_det); + + return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, cable_det); +} + +static int find_and_set_predefined_video_timings(struct v4l2_subdev *sd, + u8 prim_mode, + const struct adv7842_video_standards *predef_vid_timings, + const struct v4l2_dv_timings *timings) +{ + int i; + + for (i = 0; predef_vid_timings[i].timings.bt.width; i++) { + if (!v4l2_match_dv_timings(timings, &predef_vid_timings[i].timings, + is_digital_input(sd) ? 250000 : 1000000, false)) + continue; + /* video std */ + io_write(sd, 0x00, predef_vid_timings[i].vid_std); + /* v_freq and prim mode */ + io_write(sd, 0x01, (predef_vid_timings[i].v_freq << 4) + prim_mode); + return 0; + } + + return -1; +} + +static int configure_predefined_video_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + int err; + + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + /* reset to default values */ + io_write(sd, 0x16, 0x43); + io_write(sd, 0x17, 0x5a); + /* disable embedded syncs for auto graphics mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x00); + cp_write(sd, 0x26, 0x00); + cp_write(sd, 0x27, 0x00); + cp_write(sd, 0x28, 0x00); + cp_write(sd, 0x29, 0x00); + cp_write(sd, 0x8f, 0x40); + cp_write(sd, 0x90, 0x00); + cp_write(sd, 0xa5, 0x00); + cp_write(sd, 0xa6, 0x00); + cp_write(sd, 0xa7, 0x00); + cp_write(sd, 0xab, 0x00); + cp_write(sd, 0xac, 0x00); + + switch (state->mode) { + case ADV7842_MODE_COMP: + case ADV7842_MODE_RGB: + err = find_and_set_predefined_video_timings(sd, + 0x01, adv7842_prim_mode_comp, timings); + if (err) + err = find_and_set_predefined_video_timings(sd, + 0x02, adv7842_prim_mode_gr, timings); + break; + case ADV7842_MODE_HDMI: + err = find_and_set_predefined_video_timings(sd, + 0x05, adv7842_prim_mode_hdmi_comp, timings); + if (err) + err = find_and_set_predefined_video_timings(sd, + 0x06, adv7842_prim_mode_hdmi_gr, timings); + break; + default: + v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", + __func__, state->mode); + err = -1; + break; + } + + + return err; +} + +static void configure_custom_video_timings(struct v4l2_subdev *sd, + const struct v4l2_bt_timings *bt) +{ + struct adv7842_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u32 width = htotal(bt); + u32 height = vtotal(bt); + u16 cp_start_sav = bt->hsync + bt->hbackporch - 4; + u16 cp_start_eav = width - bt->hfrontporch; + u16 cp_start_vbi = height - bt->vfrontporch + 1; + u16 cp_end_vbi = bt->vsync + bt->vbackporch + 1; + u16 ch1_fr_ll = (((u32)bt->pixelclock / 100) > 0) ? + ((width * (ADV7842_fsc / 100)) / ((u32)bt->pixelclock / 100)) : 0; + const u8 pll[2] = { + 0xc0 | ((width >> 8) & 0x1f), + width & 0xff + }; + + v4l2_dbg(2, debug, sd, "%s\n", __func__); + + switch (state->mode) { + case ADV7842_MODE_COMP: + case ADV7842_MODE_RGB: + /* auto graphics */ + io_write(sd, 0x00, 0x07); /* video std */ + io_write(sd, 0x01, 0x02); /* prim mode */ + /* enable embedded syncs for auto graphics mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x10); + + /* Should only be set in auto-graphics mode [REF_02, p. 91-92] */ + /* setup PLL_DIV_MAN_EN and PLL_DIV_RATIO */ + /* IO-map reg. 0x16 and 0x17 should be written in sequence */ + if (i2c_smbus_write_i2c_block_data(client, 0x16, 2, pll)) { + v4l2_err(sd, "writing to reg 0x16 and 0x17 failed\n"); + break; + } + + /* active video - horizontal timing */ + cp_write(sd, 0x26, (cp_start_sav >> 8) & 0xf); + cp_write(sd, 0x27, (cp_start_sav & 0xff)); + cp_write(sd, 0x28, (cp_start_eav >> 8) & 0xf); + cp_write(sd, 0x29, (cp_start_eav & 0xff)); + + /* active video - vertical timing */ + cp_write(sd, 0xa5, (cp_start_vbi >> 4) & 0xff); + cp_write(sd, 0xa6, ((cp_start_vbi & 0xf) << 4) | + ((cp_end_vbi >> 8) & 0xf)); + cp_write(sd, 0xa7, cp_end_vbi & 0xff); + break; + case ADV7842_MODE_HDMI: + /* set default prim_mode/vid_std for HDMI + according to [REF_03, c. 4.2] */ + io_write(sd, 0x00, 0x02); /* video std */ + io_write(sd, 0x01, 0x06); /* prim mode */ + break; + default: + v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", + __func__, state->mode); + break; + } + + cp_write(sd, 0x8f, (ch1_fr_ll >> 8) & 0x7); + cp_write(sd, 0x90, ch1_fr_ll & 0xff); + cp_write(sd, 0xab, (height >> 4) & 0xff); + cp_write(sd, 0xac, (height & 0x0f) << 4); +} + +static void adv7842_set_offset(struct v4l2_subdev *sd, bool auto_offset, u16 offset_a, u16 offset_b, u16 offset_c) +{ + struct adv7842_state *state = to_state(sd); + u8 offset_buf[4]; + + if (auto_offset) { + offset_a = 0x3ff; + offset_b = 0x3ff; + offset_c = 0x3ff; + } + + v4l2_dbg(2, debug, sd, "%s: %s offset: a = 0x%x, b = 0x%x, c = 0x%x\n", + __func__, auto_offset ? "Auto" : "Manual", + offset_a, offset_b, offset_c); + + offset_buf[0]= (cp_read(sd, 0x77) & 0xc0) | ((offset_a & 0x3f0) >> 4); + offset_buf[1] = ((offset_a & 0x00f) << 4) | ((offset_b & 0x3c0) >> 6); + offset_buf[2] = ((offset_b & 0x03f) << 2) | ((offset_c & 0x300) >> 8); + offset_buf[3] = offset_c & 0x0ff; + + /* Registers must be written in this order with no i2c access in between */ + if (i2c_smbus_write_i2c_block_data(state->i2c_cp, 0x77, 4, offset_buf)) + v4l2_err(sd, "%s: i2c error writing to CP reg 0x77, 0x78, 0x79, 0x7a\n", __func__); +} + +static void adv7842_set_gain(struct v4l2_subdev *sd, bool auto_gain, u16 gain_a, u16 gain_b, u16 gain_c) +{ + struct adv7842_state *state = to_state(sd); + u8 gain_buf[4]; + u8 gain_man = 1; + u8 agc_mode_man = 1; + + if (auto_gain) { + gain_man = 0; + agc_mode_man = 0; + gain_a = 0x100; + gain_b = 0x100; + gain_c = 0x100; + } + + v4l2_dbg(2, debug, sd, "%s: %s gain: a = 0x%x, b = 0x%x, c = 0x%x\n", + __func__, auto_gain ? "Auto" : "Manual", + gain_a, gain_b, gain_c); + + gain_buf[0] = ((gain_man << 7) | (agc_mode_man << 6) | ((gain_a & 0x3f0) >> 4)); + gain_buf[1] = (((gain_a & 0x00f) << 4) | ((gain_b & 0x3c0) >> 6)); + gain_buf[2] = (((gain_b & 0x03f) << 2) | ((gain_c & 0x300) >> 8)); + gain_buf[3] = ((gain_c & 0x0ff)); + + /* Registers must be written in this order with no i2c access in between */ + if (i2c_smbus_write_i2c_block_data(state->i2c_cp, 0x73, 4, gain_buf)) + v4l2_err(sd, "%s: i2c error writing to CP reg 0x73, 0x74, 0x75, 0x76\n", __func__); +} + +static void set_rgb_quantization_range(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + bool rgb_output = io_read(sd, 0x02) & 0x02; + bool hdmi_signal = hdmi_read(sd, 0x05) & 0x80; + u8 y = HDMI_COLORSPACE_RGB; + + if (hdmi_signal && (io_read(sd, 0x60) & 1)) + y = infoframe_read(sd, 0x01) >> 5; + + v4l2_dbg(2, debug, sd, "%s: RGB quantization range: %d, RGB out: %d, HDMI: %d\n", + __func__, state->rgb_quantization_range, + rgb_output, hdmi_signal); + + adv7842_set_gain(sd, true, 0x0, 0x0, 0x0); + adv7842_set_offset(sd, true, 0x0, 0x0, 0x0); + io_write_clr_set(sd, 0x02, 0x04, rgb_output ? 0 : 4); + + switch (state->rgb_quantization_range) { + case V4L2_DV_RGB_RANGE_AUTO: + if (state->mode == ADV7842_MODE_RGB) { + /* Receiving analog RGB signal + * Set RGB full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + break; + } + + if (state->mode == ADV7842_MODE_COMP) { + /* Receiving analog YPbPr signal + * Set automode */ + io_write_and_or(sd, 0x02, 0x0f, 0xf0); + break; + } + + if (hdmi_signal) { + /* Receiving HDMI signal + * Set automode */ + io_write_and_or(sd, 0x02, 0x0f, 0xf0); + break; + } + + /* Receiving DVI-D signal + * ADV7842 selects RGB limited range regardless of + * input format (CE/IT) in automatic mode */ + if (state->timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) { + /* RGB limited range (16-235) */ + io_write_and_or(sd, 0x02, 0x0f, 0x00); + } else { + /* RGB full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + + if (is_digital_input(sd) && rgb_output) { + adv7842_set_offset(sd, false, 0x40, 0x40, 0x40); + } else { + adv7842_set_gain(sd, false, 0xe0, 0xe0, 0xe0); + adv7842_set_offset(sd, false, 0x70, 0x70, 0x70); + } + } + break; + case V4L2_DV_RGB_RANGE_LIMITED: + if (state->mode == ADV7842_MODE_COMP) { + /* YCrCb limited range (16-235) */ + io_write_and_or(sd, 0x02, 0x0f, 0x20); + break; + } + + if (y != HDMI_COLORSPACE_RGB) + break; + + /* RGB limited range (16-235) */ + io_write_and_or(sd, 0x02, 0x0f, 0x00); + + break; + case V4L2_DV_RGB_RANGE_FULL: + if (state->mode == ADV7842_MODE_COMP) { + /* YCrCb full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x60); + break; + } + + if (y != HDMI_COLORSPACE_RGB) + break; + + /* RGB full range (0-255) */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + + if (is_analog_input(sd) || hdmi_signal) + break; + + /* Adjust gain/offset for DVI-D signals only */ + if (rgb_output) { + adv7842_set_offset(sd, false, 0x40, 0x40, 0x40); + } else { + adv7842_set_gain(sd, false, 0xe0, 0xe0, 0xe0); + adv7842_set_offset(sd, false, 0x70, 0x70, 0x70); + } + break; + } +} + +static int adv7842_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct adv7842_state *state = to_state(sd); + + /* TODO SDP ctrls + contrast/brightness/hue/free run is acting a bit strange, + not sure if sdp csc is correct. + */ + switch (ctrl->id) { + /* standard ctrls */ + case V4L2_CID_BRIGHTNESS: + cp_write(sd, 0x3c, ctrl->val); + sdp_write(sd, 0x14, ctrl->val); + /* ignore lsb sdp 0x17[3:2] */ + return 0; + case V4L2_CID_CONTRAST: + cp_write(sd, 0x3a, ctrl->val); + sdp_write(sd, 0x13, ctrl->val); + /* ignore lsb sdp 0x17[1:0] */ + return 0; + case V4L2_CID_SATURATION: + cp_write(sd, 0x3b, ctrl->val); + sdp_write(sd, 0x15, ctrl->val); + /* ignore lsb sdp 0x17[5:4] */ + return 0; + case V4L2_CID_HUE: + cp_write(sd, 0x3d, ctrl->val); + sdp_write(sd, 0x16, ctrl->val); + /* ignore lsb sdp 0x17[7:6] */ + return 0; + /* custom ctrls */ + case V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE: + afe_write(sd, 0xc8, ctrl->val); + return 0; + case V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL: + cp_write_and_or(sd, 0xbf, ~0x04, (ctrl->val << 2)); + sdp_write_and_or(sd, 0xdd, ~0x04, (ctrl->val << 2)); + return 0; + case V4L2_CID_ADV_RX_FREE_RUN_COLOR: { + u8 R = (ctrl->val & 0xff0000) >> 16; + u8 G = (ctrl->val & 0x00ff00) >> 8; + u8 B = (ctrl->val & 0x0000ff); + /* RGB -> YUV, numerical approximation */ + int Y = 66 * R + 129 * G + 25 * B; + int U = -38 * R - 74 * G + 112 * B; + int V = 112 * R - 94 * G - 18 * B; + + /* Scale down to 8 bits with rounding */ + Y = (Y + 128) >> 8; + U = (U + 128) >> 8; + V = (V + 128) >> 8; + /* make U,V positive */ + Y += 16; + U += 128; + V += 128; + + v4l2_dbg(1, debug, sd, "R %x, G %x, B %x\n", R, G, B); + v4l2_dbg(1, debug, sd, "Y %x, U %x, V %x\n", Y, U, V); + + /* CP */ + cp_write(sd, 0xc1, R); + cp_write(sd, 0xc0, G); + cp_write(sd, 0xc2, B); + /* SDP */ + sdp_write(sd, 0xde, Y); + sdp_write(sd, 0xdf, (V & 0xf0) | ((U >> 4) & 0x0f)); + return 0; + } + case V4L2_CID_DV_RX_RGB_RANGE: + state->rgb_quantization_range = ctrl->val; + set_rgb_quantization_range(sd); + return 0; + } + return -EINVAL; +} + +static int adv7842_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + if (ctrl->id == V4L2_CID_DV_RX_IT_CONTENT_TYPE) { + ctrl->val = V4L2_DV_IT_CONTENT_TYPE_NO_ITC; + if ((io_read(sd, 0x60) & 1) && (infoframe_read(sd, 0x03) & 0x80)) + ctrl->val = (infoframe_read(sd, 0x05) >> 4) & 3; + return 0; + } + return -EINVAL; +} + +static inline bool no_power(struct v4l2_subdev *sd) +{ + return io_read(sd, 0x0c) & 0x24; +} + +static inline bool no_cp_signal(struct v4l2_subdev *sd) +{ + return ((cp_read(sd, 0xb5) & 0xd0) != 0xd0) || !(cp_read(sd, 0xb1) & 0x80); +} + +static inline bool is_hdmi(struct v4l2_subdev *sd) +{ + return hdmi_read(sd, 0x05) & 0x80; +} + +static int adv7842_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct adv7842_state *state = to_state(sd); + + *status = 0; + + if (io_read(sd, 0x0c) & 0x24) + *status |= V4L2_IN_ST_NO_POWER; + + if (state->mode == ADV7842_MODE_SDP) { + /* status from SDP block */ + if (!(sdp_read(sd, 0x5A) & 0x01)) + *status |= V4L2_IN_ST_NO_SIGNAL; + + v4l2_dbg(1, debug, sd, "%s: SDP status = 0x%x\n", + __func__, *status); + return 0; + } + /* status from CP block */ + if ((cp_read(sd, 0xb5) & 0xd0) != 0xd0 || + !(cp_read(sd, 0xb1) & 0x80)) + /* TODO channel 2 */ + *status |= V4L2_IN_ST_NO_SIGNAL; + + if (is_digital_input(sd) && ((io_read(sd, 0x74) & 0x03) != 0x03)) + *status |= V4L2_IN_ST_NO_SIGNAL; + + v4l2_dbg(1, debug, sd, "%s: CP status = 0x%x\n", + __func__, *status); + + return 0; +} + +struct stdi_readback { + u16 bl, lcf, lcvs; + u8 hs_pol, vs_pol; + bool interlaced; +}; + +static int stdi2dv_timings(struct v4l2_subdev *sd, + struct stdi_readback *stdi, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + u32 hfreq = (ADV7842_fsc * 8) / stdi->bl; + u32 pix_clk; + int i; + + for (i = 0; v4l2_dv_timings_presets[i].bt.width; i++) { + const struct v4l2_bt_timings *bt = &v4l2_dv_timings_presets[i].bt; + + if (!v4l2_valid_dv_timings(&v4l2_dv_timings_presets[i], + adv7842_get_dv_timings_cap(sd), + adv7842_check_dv_timings, NULL)) + continue; + if (vtotal(bt) != stdi->lcf + 1) + continue; + if (bt->vsync != stdi->lcvs) + continue; + + pix_clk = hfreq * htotal(bt); + + if ((pix_clk < bt->pixelclock + 1000000) && + (pix_clk > bt->pixelclock - 1000000)) { + *timings = v4l2_dv_timings_presets[i]; + return 0; + } + } + + if (v4l2_detect_cvt(stdi->lcf + 1, hfreq, stdi->lcvs, 0, + (stdi->hs_pol == '+' ? V4L2_DV_HSYNC_POS_POL : 0) | + (stdi->vs_pol == '+' ? V4L2_DV_VSYNC_POS_POL : 0), + false, adv7842_get_dv_timings_cap(sd), timings)) + return 0; + if (v4l2_detect_gtf(stdi->lcf + 1, hfreq, stdi->lcvs, + (stdi->hs_pol == '+' ? V4L2_DV_HSYNC_POS_POL : 0) | + (stdi->vs_pol == '+' ? V4L2_DV_VSYNC_POS_POL : 0), + false, state->aspect_ratio, + adv7842_get_dv_timings_cap(sd), timings)) + return 0; + + v4l2_dbg(2, debug, sd, + "%s: No format candidate found for lcvs = %d, lcf=%d, bl = %d, %chsync, %cvsync\n", + __func__, stdi->lcvs, stdi->lcf, stdi->bl, + stdi->hs_pol, stdi->vs_pol); + return -1; +} + +static int read_stdi(struct v4l2_subdev *sd, struct stdi_readback *stdi) +{ + u32 status; + + adv7842_g_input_status(sd, &status); + if (status & V4L2_IN_ST_NO_SIGNAL) { + v4l2_dbg(2, debug, sd, "%s: no signal\n", __func__); + return -ENOLINK; + } + + stdi->bl = ((cp_read(sd, 0xb1) & 0x3f) << 8) | cp_read(sd, 0xb2); + stdi->lcf = ((cp_read(sd, 0xb3) & 0x7) << 8) | cp_read(sd, 0xb4); + stdi->lcvs = cp_read(sd, 0xb3) >> 3; + + if ((cp_read(sd, 0xb5) & 0x80) && ((cp_read(sd, 0xb5) & 0x03) == 0x01)) { + stdi->hs_pol = ((cp_read(sd, 0xb5) & 0x10) ? + ((cp_read(sd, 0xb5) & 0x08) ? '+' : '-') : 'x'); + stdi->vs_pol = ((cp_read(sd, 0xb5) & 0x40) ? + ((cp_read(sd, 0xb5) & 0x20) ? '+' : '-') : 'x'); + } else { + stdi->hs_pol = 'x'; + stdi->vs_pol = 'x'; + } + stdi->interlaced = (cp_read(sd, 0xb1) & 0x40) ? true : false; + + if (stdi->lcf < 239 || stdi->bl < 8 || stdi->bl == 0x3fff) { + v4l2_dbg(2, debug, sd, "%s: invalid signal\n", __func__); + return -ENOLINK; + } + + v4l2_dbg(2, debug, sd, + "%s: lcf (frame height - 1) = %d, bl = %d, lcvs (vsync) = %d, %chsync, %cvsync, %s\n", + __func__, stdi->lcf, stdi->bl, stdi->lcvs, + stdi->hs_pol, stdi->vs_pol, + stdi->interlaced ? "interlaced" : "progressive"); + + return 0; +} + +static int adv7842_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + if (timings->pad != 0) + return -EINVAL; + + return v4l2_enum_dv_timings_cap(timings, + adv7842_get_dv_timings_cap(sd), adv7842_check_dv_timings, NULL); +} + +static int adv7842_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + if (cap->pad != 0) + return -EINVAL; + + *cap = *adv7842_get_dv_timings_cap(sd); + return 0; +} + +/* Fill the optional fields .standards and .flags in struct v4l2_dv_timings + if the format is listed in adv7842_timings[] */ +static void adv7842_fill_optional_dv_timings_fields(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + v4l2_find_dv_timings_cap(timings, adv7842_get_dv_timings_cap(sd), + is_digital_input(sd) ? 250000 : 1000000, + adv7842_check_dv_timings, NULL); + timings->bt.flags |= V4L2_DV_FL_CAN_DETECT_REDUCED_FPS; +} + +static int adv7842_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + struct v4l2_bt_timings *bt = &timings->bt; + struct stdi_readback stdi = { 0 }; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (pad != 0) + return -EINVAL; + + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + + /* SDP block */ + if (state->mode == ADV7842_MODE_SDP) + return -ENODATA; + + /* read STDI */ + if (read_stdi(sd, &stdi)) { + state->restart_stdi_once = true; + v4l2_dbg(1, debug, sd, "%s: no valid signal\n", __func__); + return -ENOLINK; + } + bt->interlaced = stdi.interlaced ? + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; + bt->standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT; + + if (is_digital_input(sd)) { + u32 freq; + + timings->type = V4L2_DV_BT_656_1120; + + bt->width = (hdmi_read(sd, 0x07) & 0x0f) * 256 + hdmi_read(sd, 0x08); + bt->height = (hdmi_read(sd, 0x09) & 0x0f) * 256 + hdmi_read(sd, 0x0a); + freq = ((hdmi_read(sd, 0x51) << 1) + (hdmi_read(sd, 0x52) >> 7)) * 1000000; + freq += ((hdmi_read(sd, 0x52) & 0x7f) * 7813); + if (is_hdmi(sd)) { + /* adjust for deep color mode */ + freq = freq * 8 / (((hdmi_read(sd, 0x0b) & 0xc0) >> 6) * 2 + 8); + } + bt->pixelclock = freq; + bt->hfrontporch = (hdmi_read(sd, 0x20) & 0x03) * 256 + + hdmi_read(sd, 0x21); + bt->hsync = (hdmi_read(sd, 0x22) & 0x03) * 256 + + hdmi_read(sd, 0x23); + bt->hbackporch = (hdmi_read(sd, 0x24) & 0x03) * 256 + + hdmi_read(sd, 0x25); + bt->vfrontporch = ((hdmi_read(sd, 0x2a) & 0x1f) * 256 + + hdmi_read(sd, 0x2b)) / 2; + bt->vsync = ((hdmi_read(sd, 0x2e) & 0x1f) * 256 + + hdmi_read(sd, 0x2f)) / 2; + bt->vbackporch = ((hdmi_read(sd, 0x32) & 0x1f) * 256 + + hdmi_read(sd, 0x33)) / 2; + bt->polarities = ((hdmi_read(sd, 0x05) & 0x10) ? V4L2_DV_VSYNC_POS_POL : 0) | + ((hdmi_read(sd, 0x05) & 0x20) ? V4L2_DV_HSYNC_POS_POL : 0); + if (bt->interlaced == V4L2_DV_INTERLACED) { + bt->height += (hdmi_read(sd, 0x0b) & 0x0f) * 256 + + hdmi_read(sd, 0x0c); + bt->il_vfrontporch = ((hdmi_read(sd, 0x2c) & 0x1f) * 256 + + hdmi_read(sd, 0x2d)) / 2; + bt->il_vsync = ((hdmi_read(sd, 0x30) & 0x1f) * 256 + + hdmi_read(sd, 0x31)) / 2; + bt->il_vbackporch = ((hdmi_read(sd, 0x34) & 0x1f) * 256 + + hdmi_read(sd, 0x35)) / 2; + } else { + bt->il_vfrontporch = 0; + bt->il_vsync = 0; + bt->il_vbackporch = 0; + } + adv7842_fill_optional_dv_timings_fields(sd, timings); + if ((timings->bt.flags & V4L2_DV_FL_CAN_REDUCE_FPS) && + freq < bt->pixelclock) { + u32 reduced_freq = ((u32)bt->pixelclock / 1001) * 1000; + u32 delta_freq = abs(freq - reduced_freq); + + if (delta_freq < ((u32)bt->pixelclock - reduced_freq) / 2) + timings->bt.flags |= V4L2_DV_FL_REDUCED_FPS; + } + } else { + /* find format + * Since LCVS values are inaccurate [REF_03, p. 339-340], + * stdi2dv_timings() is called with lcvs +-1 if the first attempt fails. + */ + if (!stdi2dv_timings(sd, &stdi, timings)) + goto found; + stdi.lcvs += 1; + v4l2_dbg(1, debug, sd, "%s: lcvs + 1 = %d\n", __func__, stdi.lcvs); + if (!stdi2dv_timings(sd, &stdi, timings)) + goto found; + stdi.lcvs -= 2; + v4l2_dbg(1, debug, sd, "%s: lcvs - 1 = %d\n", __func__, stdi.lcvs); + if (stdi2dv_timings(sd, &stdi, timings)) { + /* + * The STDI block may measure wrong values, especially + * for lcvs and lcf. If the driver can not find any + * valid timing, the STDI block is restarted to measure + * the video timings again. The function will return an + * error, but the restart of STDI will generate a new + * STDI interrupt and the format detection process will + * restart. + */ + if (state->restart_stdi_once) { + v4l2_dbg(1, debug, sd, "%s: restart STDI\n", __func__); + /* TODO restart STDI for Sync Channel 2 */ + /* enter one-shot mode */ + cp_write_and_or(sd, 0x86, 0xf9, 0x00); + /* trigger STDI restart */ + cp_write_and_or(sd, 0x86, 0xf9, 0x04); + /* reset to continuous mode */ + cp_write_and_or(sd, 0x86, 0xf9, 0x02); + state->restart_stdi_once = false; + return -ENOLINK; + } + v4l2_dbg(1, debug, sd, "%s: format not supported\n", __func__); + return -ERANGE; + } + state->restart_stdi_once = true; + } +found: + + if (debug > 1) + v4l2_print_dv_timings(sd->name, "adv7842_query_dv_timings:", + timings, true); + return 0; +} + +static int adv7842_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + struct v4l2_bt_timings *bt; + int err; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (pad != 0) + return -EINVAL; + + if (state->mode == ADV7842_MODE_SDP) + return -ENODATA; + + if (v4l2_match_dv_timings(&state->timings, timings, 0, false)) { + v4l2_dbg(1, debug, sd, "%s: no change\n", __func__); + return 0; + } + + bt = &timings->bt; + + if (!v4l2_valid_dv_timings(timings, adv7842_get_dv_timings_cap(sd), + adv7842_check_dv_timings, NULL)) + return -ERANGE; + + adv7842_fill_optional_dv_timings_fields(sd, timings); + + state->timings = *timings; + + cp_write(sd, 0x91, bt->interlaced ? 0x40 : 0x00); + + /* Use prim_mode and vid_std when available */ + err = configure_predefined_video_timings(sd, timings); + if (err) { + /* custom settings when the video format + does not have prim_mode/vid_std */ + configure_custom_video_timings(sd, bt); + } + + set_rgb_quantization_range(sd); + + + if (debug > 1) + v4l2_print_dv_timings(sd->name, "adv7842_s_dv_timings: ", + timings, true); + return 0; +} + +static int adv7842_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct adv7842_state *state = to_state(sd); + + if (pad != 0) + return -EINVAL; + + if (state->mode == ADV7842_MODE_SDP) + return -ENODATA; + *timings = state->timings; + return 0; +} + +static void enable_input(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + + set_rgb_quantization_range(sd); + switch (state->mode) { + case ADV7842_MODE_SDP: + case ADV7842_MODE_COMP: + case ADV7842_MODE_RGB: + io_write(sd, 0x15, 0xb0); /* Disable Tristate of Pins (no audio) */ + break; + case ADV7842_MODE_HDMI: + hdmi_write(sd, 0x01, 0x00); /* Enable HDMI clock terminators */ + io_write(sd, 0x15, 0xa0); /* Disable Tristate of Pins */ + hdmi_write_and_or(sd, 0x1a, 0xef, 0x00); /* Unmute audio */ + break; + default: + v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", + __func__, state->mode); + break; + } +} + +static void disable_input(struct v4l2_subdev *sd) +{ + hdmi_write_and_or(sd, 0x1a, 0xef, 0x10); /* Mute audio [REF_01, c. 2.2.2] */ + msleep(16); /* 512 samples with >= 32 kHz sample rate [REF_03, c. 8.29] */ + io_write(sd, 0x15, 0xbe); /* Tristate all outputs from video core */ + hdmi_write(sd, 0x01, 0x78); /* Disable HDMI clock terminators */ +} + +static void sdp_csc_coeff(struct v4l2_subdev *sd, + const struct adv7842_sdp_csc_coeff *c) +{ + /* csc auto/manual */ + sdp_io_write_and_or(sd, 0xe0, 0xbf, c->manual ? 0x00 : 0x40); + + if (!c->manual) + return; + + /* csc scaling */ + sdp_io_write_and_or(sd, 0xe0, 0x7f, c->scaling == 2 ? 0x80 : 0x00); + + /* A coeff */ + sdp_io_write_and_or(sd, 0xe0, 0xe0, c->A1 >> 8); + sdp_io_write(sd, 0xe1, c->A1); + sdp_io_write_and_or(sd, 0xe2, 0xe0, c->A2 >> 8); + sdp_io_write(sd, 0xe3, c->A2); + sdp_io_write_and_or(sd, 0xe4, 0xe0, c->A3 >> 8); + sdp_io_write(sd, 0xe5, c->A3); + + /* A scale */ + sdp_io_write_and_or(sd, 0xe6, 0x80, c->A4 >> 8); + sdp_io_write(sd, 0xe7, c->A4); + + /* B coeff */ + sdp_io_write_and_or(sd, 0xe8, 0xe0, c->B1 >> 8); + sdp_io_write(sd, 0xe9, c->B1); + sdp_io_write_and_or(sd, 0xea, 0xe0, c->B2 >> 8); + sdp_io_write(sd, 0xeb, c->B2); + sdp_io_write_and_or(sd, 0xec, 0xe0, c->B3 >> 8); + sdp_io_write(sd, 0xed, c->B3); + + /* B scale */ + sdp_io_write_and_or(sd, 0xee, 0x80, c->B4 >> 8); + sdp_io_write(sd, 0xef, c->B4); + + /* C coeff */ + sdp_io_write_and_or(sd, 0xf0, 0xe0, c->C1 >> 8); + sdp_io_write(sd, 0xf1, c->C1); + sdp_io_write_and_or(sd, 0xf2, 0xe0, c->C2 >> 8); + sdp_io_write(sd, 0xf3, c->C2); + sdp_io_write_and_or(sd, 0xf4, 0xe0, c->C3 >> 8); + sdp_io_write(sd, 0xf5, c->C3); + + /* C scale */ + sdp_io_write_and_or(sd, 0xf6, 0x80, c->C4 >> 8); + sdp_io_write(sd, 0xf7, c->C4); +} + +static void select_input(struct v4l2_subdev *sd, + enum adv7842_vid_std_select vid_std_select) +{ + struct adv7842_state *state = to_state(sd); + + switch (state->mode) { + case ADV7842_MODE_SDP: + io_write(sd, 0x00, vid_std_select); /* video std: CVBS or YC mode */ + io_write(sd, 0x01, 0); /* prim mode */ + /* enable embedded syncs for auto graphics mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x10); + + afe_write(sd, 0x00, 0x00); /* power up ADC */ + afe_write(sd, 0xc8, 0x00); /* phase control */ + + io_write(sd, 0xdd, 0x90); /* Manual 2x output clock */ + /* script says register 0xde, which don't exist in manual */ + + /* Manual analog input muxing mode, CVBS (6.4)*/ + afe_write_and_or(sd, 0x02, 0x7f, 0x80); + if (vid_std_select == ADV7842_SDP_VID_STD_CVBS_SD_4x1) { + afe_write(sd, 0x03, 0xa0); /* ADC0 to AIN10 (CVBS), ADC1 N/C*/ + afe_write(sd, 0x04, 0x00); /* ADC2 N/C,ADC3 N/C*/ + } else { + afe_write(sd, 0x03, 0xa0); /* ADC0 to AIN10 (CVBS), ADC1 N/C*/ + afe_write(sd, 0x04, 0xc0); /* ADC2 to AIN12, ADC3 N/C*/ + } + afe_write(sd, 0x0c, 0x1f); /* ADI recommend write */ + afe_write(sd, 0x12, 0x63); /* ADI recommend write */ + + sdp_io_write(sd, 0xb2, 0x60); /* Disable AV codes */ + sdp_io_write(sd, 0xc8, 0xe3); /* Disable Ancillary data */ + + /* SDP recommended settings */ + sdp_write(sd, 0x00, 0x3F); /* Autodetect PAL NTSC (not SECAM) */ + sdp_write(sd, 0x01, 0x00); /* Pedestal Off */ + + sdp_write(sd, 0x03, 0xE4); /* Manual VCR Gain Luma 0x40B */ + sdp_write(sd, 0x04, 0x0B); /* Manual Luma setting */ + sdp_write(sd, 0x05, 0xC3); /* Manual Chroma setting 0x3FE */ + sdp_write(sd, 0x06, 0xFE); /* Manual Chroma setting */ + sdp_write(sd, 0x12, 0x0D); /* Frame TBC,I_P, 3D comb enabled */ + sdp_write(sd, 0xA7, 0x00); /* ADI Recommended Write */ + sdp_io_write(sd, 0xB0, 0x00); /* Disable H and v blanking */ + + /* deinterlacer enabled and 3D comb */ + sdp_write_and_or(sd, 0x12, 0xf6, 0x09); + + break; + + case ADV7842_MODE_COMP: + case ADV7842_MODE_RGB: + /* Automatic analog input muxing mode */ + afe_write_and_or(sd, 0x02, 0x7f, 0x00); + /* set mode and select free run resolution */ + io_write(sd, 0x00, vid_std_select); /* video std */ + io_write(sd, 0x01, 0x02); /* prim mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x10); /* enable embedded syncs + for auto graphics mode */ + + afe_write(sd, 0x00, 0x00); /* power up ADC */ + afe_write(sd, 0xc8, 0x00); /* phase control */ + if (state->mode == ADV7842_MODE_COMP) { + /* force to YCrCb */ + io_write_and_or(sd, 0x02, 0x0f, 0x60); + } else { + /* force to RGB */ + io_write_and_or(sd, 0x02, 0x0f, 0x10); + } + + /* set ADI recommended settings for digitizer */ + /* "ADV7842 Register Settings Recommendations + * (rev. 1.8, November 2010)" p. 9. */ + afe_write(sd, 0x0c, 0x1f); /* ADC Range improvement */ + afe_write(sd, 0x12, 0x63); /* ADC Range improvement */ + + /* set to default gain for RGB */ + cp_write(sd, 0x73, 0x10); + cp_write(sd, 0x74, 0x04); + cp_write(sd, 0x75, 0x01); + cp_write(sd, 0x76, 0x00); + + cp_write(sd, 0x3e, 0x04); /* CP core pre-gain control */ + cp_write(sd, 0xc3, 0x39); /* CP coast control. Graphics mode */ + cp_write(sd, 0x40, 0x5c); /* CP core pre-gain control. Graphics mode */ + break; + + case ADV7842_MODE_HDMI: + /* Automatic analog input muxing mode */ + afe_write_and_or(sd, 0x02, 0x7f, 0x00); + /* set mode and select free run resolution */ + if (state->hdmi_port_a) + hdmi_write(sd, 0x00, 0x02); /* select port A */ + else + hdmi_write(sd, 0x00, 0x03); /* select port B */ + io_write(sd, 0x00, vid_std_select); /* video std */ + io_write(sd, 0x01, 5); /* prim mode */ + cp_write_and_or(sd, 0x81, 0xef, 0x00); /* disable embedded syncs + for auto graphics mode */ + + /* set ADI recommended settings for HDMI: */ + /* "ADV7842 Register Settings Recommendations + * (rev. 1.8, November 2010)" p. 3. */ + hdmi_write(sd, 0xc0, 0x00); + hdmi_write(sd, 0x0d, 0x34); /* ADI recommended write */ + hdmi_write(sd, 0x3d, 0x10); /* ADI recommended write */ + hdmi_write(sd, 0x44, 0x85); /* TMDS PLL optimization */ + hdmi_write(sd, 0x46, 0x1f); /* ADI recommended write */ + hdmi_write(sd, 0x57, 0xb6); /* TMDS PLL optimization */ + hdmi_write(sd, 0x58, 0x03); /* TMDS PLL optimization */ + hdmi_write(sd, 0x60, 0x88); /* TMDS PLL optimization */ + hdmi_write(sd, 0x61, 0x88); /* TMDS PLL optimization */ + hdmi_write(sd, 0x6c, 0x18); /* Disable ISRC clearing bit, + Improve robustness */ + hdmi_write(sd, 0x75, 0x10); /* DDC drive strength */ + hdmi_write(sd, 0x85, 0x1f); /* equaliser */ + hdmi_write(sd, 0x87, 0x70); /* ADI recommended write */ + hdmi_write(sd, 0x89, 0x04); /* equaliser */ + hdmi_write(sd, 0x8a, 0x1e); /* equaliser */ + hdmi_write(sd, 0x93, 0x04); /* equaliser */ + hdmi_write(sd, 0x94, 0x1e); /* equaliser */ + hdmi_write(sd, 0x99, 0xa1); /* ADI recommended write */ + hdmi_write(sd, 0x9b, 0x09); /* ADI recommended write */ + hdmi_write(sd, 0x9d, 0x02); /* equaliser */ + + afe_write(sd, 0x00, 0xff); /* power down ADC */ + afe_write(sd, 0xc8, 0x40); /* phase control */ + + /* set to default gain for HDMI */ + cp_write(sd, 0x73, 0x10); + cp_write(sd, 0x74, 0x04); + cp_write(sd, 0x75, 0x01); + cp_write(sd, 0x76, 0x00); + + /* reset ADI recommended settings for digitizer */ + /* "ADV7842 Register Settings Recommendations + * (rev. 2.5, June 2010)" p. 17. */ + afe_write(sd, 0x12, 0xfb); /* ADC noise shaping filter controls */ + afe_write(sd, 0x0c, 0x0d); /* CP core gain controls */ + cp_write(sd, 0x3e, 0x00); /* CP core pre-gain control */ + + /* CP coast control */ + cp_write(sd, 0xc3, 0x33); /* Component mode */ + + /* color space conversion, autodetect color space */ + io_write_and_or(sd, 0x02, 0x0f, 0xf0); + break; + + default: + v4l2_dbg(2, debug, sd, "%s: Unknown mode %d\n", + __func__, state->mode); + break; + } +} + +static int adv7842_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7842_state *state = to_state(sd); + + v4l2_dbg(2, debug, sd, "%s: input %d\n", __func__, input); + + switch (input) { + case ADV7842_SELECT_HDMI_PORT_A: + state->mode = ADV7842_MODE_HDMI; + state->vid_std_select = ADV7842_HDMI_COMP_VID_STD_HD_1250P; + state->hdmi_port_a = true; + break; + case ADV7842_SELECT_HDMI_PORT_B: + state->mode = ADV7842_MODE_HDMI; + state->vid_std_select = ADV7842_HDMI_COMP_VID_STD_HD_1250P; + state->hdmi_port_a = false; + break; + case ADV7842_SELECT_VGA_COMP: + state->mode = ADV7842_MODE_COMP; + state->vid_std_select = ADV7842_RGB_VID_STD_AUTO_GRAPH_MODE; + break; + case ADV7842_SELECT_VGA_RGB: + state->mode = ADV7842_MODE_RGB; + state->vid_std_select = ADV7842_RGB_VID_STD_AUTO_GRAPH_MODE; + break; + case ADV7842_SELECT_SDP_CVBS: + state->mode = ADV7842_MODE_SDP; + state->vid_std_select = ADV7842_SDP_VID_STD_CVBS_SD_4x1; + break; + case ADV7842_SELECT_SDP_YC: + state->mode = ADV7842_MODE_SDP; + state->vid_std_select = ADV7842_SDP_VID_STD_YC_SD4_x1; + break; + default: + return -EINVAL; + } + + disable_input(sd); + select_input(sd, state->vid_std_select); + enable_input(sd); + + v4l2_subdev_notify_event(sd, &adv7842_ev_fmt); + + return 0; +} + +static int adv7842_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(adv7842_formats)) + return -EINVAL; + code->code = adv7842_formats[code->index].code; + return 0; +} + +static void adv7842_fill_format(struct adv7842_state *state, + struct v4l2_mbus_framefmt *format) +{ + memset(format, 0, sizeof(*format)); + + format->width = state->timings.bt.width; + format->height = state->timings.bt.height; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + if (state->timings.bt.flags & V4L2_DV_FL_IS_CE_VIDEO) + format->colorspace = (state->timings.bt.height <= 576) ? + V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709; +} + +/* + * Compute the op_ch_sel value required to obtain on the bus the component order + * corresponding to the selected format taking into account bus reordering + * applied by the board at the output of the device. + * + * The following table gives the op_ch_value from the format component order + * (expressed as op_ch_sel value in column) and the bus reordering (expressed as + * adv7842_bus_order value in row). + * + * | GBR(0) GRB(1) BGR(2) RGB(3) BRG(4) RBG(5) + * ----------+------------------------------------------------- + * RGB (NOP) | GBR GRB BGR RGB BRG RBG + * GRB (1-2) | BGR RGB GBR GRB RBG BRG + * RBG (2-3) | GRB GBR BRG RBG BGR RGB + * BGR (1-3) | RBG BRG RGB BGR GRB GBR + * BRG (ROR) | BRG RBG GRB GBR RGB BGR + * GBR (ROL) | RGB BGR RBG BRG GBR GRB + */ +static unsigned int adv7842_op_ch_sel(struct adv7842_state *state) +{ +#define _SEL(a, b, c, d, e, f) { \ + ADV7842_OP_CH_SEL_##a, ADV7842_OP_CH_SEL_##b, ADV7842_OP_CH_SEL_##c, \ + ADV7842_OP_CH_SEL_##d, ADV7842_OP_CH_SEL_##e, ADV7842_OP_CH_SEL_##f } +#define _BUS(x) [ADV7842_BUS_ORDER_##x] + + static const unsigned int op_ch_sel[6][6] = { + _BUS(RGB) /* NOP */ = _SEL(GBR, GRB, BGR, RGB, BRG, RBG), + _BUS(GRB) /* 1-2 */ = _SEL(BGR, RGB, GBR, GRB, RBG, BRG), + _BUS(RBG) /* 2-3 */ = _SEL(GRB, GBR, BRG, RBG, BGR, RGB), + _BUS(BGR) /* 1-3 */ = _SEL(RBG, BRG, RGB, BGR, GRB, GBR), + _BUS(BRG) /* ROR */ = _SEL(BRG, RBG, GRB, GBR, RGB, BGR), + _BUS(GBR) /* ROL */ = _SEL(RGB, BGR, RBG, BRG, GBR, GRB), + }; + + return op_ch_sel[state->pdata.bus_order][state->format->op_ch_sel >> 5]; +} + +static void adv7842_setup_format(struct adv7842_state *state) +{ + struct v4l2_subdev *sd = &state->sd; + + io_write_clr_set(sd, 0x02, 0x02, + state->format->rgb_out ? ADV7842_RGB_OUT : 0); + io_write(sd, 0x03, state->format->op_format_sel | + state->pdata.op_format_mode_sel); + io_write_clr_set(sd, 0x04, 0xe0, adv7842_op_ch_sel(state)); + io_write_clr_set(sd, 0x05, 0x01, + state->format->swap_cb_cr ? ADV7842_OP_SWAP_CB_CR : 0); + set_rgb_quantization_range(sd); +} + +static int adv7842_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct adv7842_state *state = to_state(sd); + + if (format->pad != ADV7842_PAD_SOURCE) + return -EINVAL; + + if (state->mode == ADV7842_MODE_SDP) { + /* SPD block */ + if (!(sdp_read(sd, 0x5a) & 0x01)) + return -EINVAL; + format->format.code = MEDIA_BUS_FMT_YUYV8_2X8; + format->format.width = 720; + /* valid signal */ + if (state->norm & V4L2_STD_525_60) + format->format.height = 480; + else + format->format.height = 576; + format->format.colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; + } + + adv7842_fill_format(state, &format->format); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + format->format.code = fmt->code; + } else { + format->format.code = state->format->code; + } + + return 0; +} + +static int adv7842_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct adv7842_state *state = to_state(sd); + const struct adv7842_format_info *info; + + if (format->pad != ADV7842_PAD_SOURCE) + return -EINVAL; + + if (state->mode == ADV7842_MODE_SDP) + return adv7842_get_format(sd, sd_state, format); + + info = adv7842_format_info(state, format->format.code); + if (info == NULL) + info = adv7842_format_info(state, MEDIA_BUS_FMT_YUYV8_2X8); + + adv7842_fill_format(state, &format->format); + format->format.code = info->code; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + fmt->code = format->format.code; + } else { + state->format = info; + adv7842_setup_format(state); + } + + return 0; +} + +static void adv7842_irq_enable(struct v4l2_subdev *sd, bool enable) +{ + if (enable) { + /* Enable SSPD, STDI and CP locked/unlocked interrupts */ + io_write(sd, 0x46, 0x9c); + /* ESDP_50HZ_DET interrupt */ + io_write(sd, 0x5a, 0x10); + /* Enable CABLE_DET_A/B_ST (+5v) interrupt */ + io_write(sd, 0x73, 0x03); + /* Enable V_LOCKED and DE_REGEN_LCK interrupts */ + io_write(sd, 0x78, 0x03); + /* Enable SDP Standard Detection Change and SDP Video Detected */ + io_write(sd, 0xa0, 0x09); + /* Enable HDMI_MODE interrupt */ + io_write(sd, 0x69, 0x08); + } else { + io_write(sd, 0x46, 0x0); + io_write(sd, 0x5a, 0x0); + io_write(sd, 0x73, 0x0); + io_write(sd, 0x78, 0x0); + io_write(sd, 0xa0, 0x0); + io_write(sd, 0x69, 0x0); + } +} + +#if IS_ENABLED(CONFIG_VIDEO_ADV7842_CEC) +static void adv7842_cec_tx_raw_status(struct v4l2_subdev *sd, u8 tx_raw_status) +{ + struct adv7842_state *state = to_state(sd); + + if ((cec_read(sd, 0x11) & 0x01) == 0) { + v4l2_dbg(1, debug, sd, "%s: tx raw: tx disabled\n", __func__); + return; + } + + if (tx_raw_status & 0x02) { + v4l2_dbg(1, debug, sd, "%s: tx raw: arbitration lost\n", + __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_ARB_LOST, + 1, 0, 0, 0); + return; + } + if (tx_raw_status & 0x04) { + u8 status; + u8 nack_cnt; + u8 low_drive_cnt; + + v4l2_dbg(1, debug, sd, "%s: tx raw: retry failed\n", __func__); + /* + * We set this status bit since this hardware performs + * retransmissions. + */ + status = CEC_TX_STATUS_MAX_RETRIES; + nack_cnt = cec_read(sd, 0x14) & 0xf; + if (nack_cnt) + status |= CEC_TX_STATUS_NACK; + low_drive_cnt = cec_read(sd, 0x14) >> 4; + if (low_drive_cnt) + status |= CEC_TX_STATUS_LOW_DRIVE; + cec_transmit_done(state->cec_adap, status, + 0, nack_cnt, low_drive_cnt, 0); + return; + } + if (tx_raw_status & 0x01) { + v4l2_dbg(1, debug, sd, "%s: tx raw: ready ok\n", __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); + return; + } +} + +static void adv7842_cec_isr(struct v4l2_subdev *sd, bool *handled) +{ + u8 cec_irq; + + /* cec controller */ + cec_irq = io_read(sd, 0x93) & 0x0f; + if (!cec_irq) + return; + + v4l2_dbg(1, debug, sd, "%s: cec: irq 0x%x\n", __func__, cec_irq); + adv7842_cec_tx_raw_status(sd, cec_irq); + if (cec_irq & 0x08) { + struct adv7842_state *state = to_state(sd); + struct cec_msg msg; + + msg.len = cec_read(sd, 0x25) & 0x1f; + if (msg.len > CEC_MAX_MSG_SIZE) + msg.len = CEC_MAX_MSG_SIZE; + + if (msg.len) { + u8 i; + + for (i = 0; i < msg.len; i++) + msg.msg[i] = cec_read(sd, i + 0x15); + cec_write(sd, 0x26, 0x01); /* re-enable rx */ + cec_received_msg(state->cec_adap, &msg); + } + } + + io_write(sd, 0x94, cec_irq); + + if (handled) + *handled = true; +} + +static int adv7842_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct adv7842_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + + if (!state->cec_enabled_adap && enable) { + cec_write_clr_set(sd, 0x2a, 0x01, 0x01); /* power up cec */ + cec_write(sd, 0x2c, 0x01); /* cec soft reset */ + cec_write_clr_set(sd, 0x11, 0x01, 0); /* initially disable tx */ + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready */ + io_write_clr_set(sd, 0x96, 0x0f, 0x0f); + cec_write(sd, 0x26, 0x01); /* enable rx */ + } else if (state->cec_enabled_adap && !enable) { + /* disable cec interrupts */ + io_write_clr_set(sd, 0x96, 0x0f, 0x00); + /* disable address mask 1-3 */ + cec_write_clr_set(sd, 0x27, 0x70, 0x00); + /* power down cec section */ + cec_write_clr_set(sd, 0x2a, 0x01, 0x00); + state->cec_valid_addrs = 0; + } + state->cec_enabled_adap = enable; + return 0; +} + +static int adv7842_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct adv7842_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + unsigned int i, free_idx = ADV7842_MAX_ADDRS; + + if (!state->cec_enabled_adap) + return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + cec_write_clr_set(sd, 0x27, 0x70, 0); + state->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV7842_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (free_idx == ADV7842_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && state->cec_addr[i] == addr) + return 0; + } + if (i == ADV7842_MAX_ADDRS) { + i = free_idx; + if (i == ADV7842_MAX_ADDRS) + return -ENXIO; + } + state->cec_addr[i] = addr; + state->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + cec_write_clr_set(sd, 0x27, 0x10, 0x10); + /* set address for mask 0 */ + cec_write_clr_set(sd, 0x28, 0x0f, addr); + break; + case 1: + /* enable address mask 1 */ + cec_write_clr_set(sd, 0x27, 0x20, 0x20); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x28, 0xf0, addr << 4); + break; + case 2: + /* enable address mask 2 */ + cec_write_clr_set(sd, 0x27, 0x40, 0x40); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x29, 0x0f, addr); + break; + } + return 0; +} + +static int adv7842_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct adv7842_state *state = cec_get_drvdata(adap); + struct v4l2_subdev *sd = &state->sd; + u8 len = msg->len; + unsigned int i; + + /* + * The number of retries is the number of attempts - 1, but retry + * at least once. It's not clear if a value of 0 is allowed, so + * let's do at least one retry. + */ + cec_write_clr_set(sd, 0x12, 0x70, max(1, attempts - 1) << 4); + + if (len > 16) { + v4l2_err(sd, "%s: len exceeded 16 (%d)\n", __func__, len); + return -EINVAL; + } + + /* write data */ + for (i = 0; i < len; i++) + cec_write(sd, i, msg->msg[i]); + + /* set length (data + header) */ + cec_write(sd, 0x10, len); + /* start transmit, enable tx */ + cec_write(sd, 0x11, 0x01); + return 0; +} + +static const struct cec_adap_ops adv7842_cec_adap_ops = { + .adap_enable = adv7842_cec_adap_enable, + .adap_log_addr = adv7842_cec_adap_log_addr, + .adap_transmit = adv7842_cec_adap_transmit, +}; +#endif + +static int adv7842_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + struct adv7842_state *state = to_state(sd); + u8 fmt_change_cp, fmt_change_digital, fmt_change_sdp; + u8 irq_status[6]; + + adv7842_irq_enable(sd, false); + + /* read status */ + irq_status[0] = io_read(sd, 0x43); + irq_status[1] = io_read(sd, 0x57); + irq_status[2] = io_read(sd, 0x70); + irq_status[3] = io_read(sd, 0x75); + irq_status[4] = io_read(sd, 0x9d); + irq_status[5] = io_read(sd, 0x66); + + /* and clear */ + if (irq_status[0]) + io_write(sd, 0x44, irq_status[0]); + if (irq_status[1]) + io_write(sd, 0x58, irq_status[1]); + if (irq_status[2]) + io_write(sd, 0x71, irq_status[2]); + if (irq_status[3]) + io_write(sd, 0x76, irq_status[3]); + if (irq_status[4]) + io_write(sd, 0x9e, irq_status[4]); + if (irq_status[5]) + io_write(sd, 0x67, irq_status[5]); + + adv7842_irq_enable(sd, true); + + v4l2_dbg(1, debug, sd, "%s: irq %x, %x, %x, %x, %x, %x\n", __func__, + irq_status[0], irq_status[1], irq_status[2], + irq_status[3], irq_status[4], irq_status[5]); + + /* format change CP */ + fmt_change_cp = irq_status[0] & 0x9c; + + /* format change SDP */ + if (state->mode == ADV7842_MODE_SDP) + fmt_change_sdp = (irq_status[1] & 0x30) | (irq_status[4] & 0x09); + else + fmt_change_sdp = 0; + + /* digital format CP */ + if (is_digital_input(sd)) + fmt_change_digital = irq_status[3] & 0x03; + else + fmt_change_digital = 0; + + /* format change */ + if (fmt_change_cp || fmt_change_digital || fmt_change_sdp) { + v4l2_dbg(1, debug, sd, + "%s: fmt_change_cp = 0x%x, fmt_change_digital = 0x%x, fmt_change_sdp = 0x%x\n", + __func__, fmt_change_cp, fmt_change_digital, + fmt_change_sdp); + v4l2_subdev_notify_event(sd, &adv7842_ev_fmt); + if (handled) + *handled = true; + } + + /* HDMI/DVI mode */ + if (irq_status[5] & 0x08) { + v4l2_dbg(1, debug, sd, "%s: irq %s mode\n", __func__, + (io_read(sd, 0x65) & 0x08) ? "HDMI" : "DVI"); + set_rgb_quantization_range(sd); + if (handled) + *handled = true; + } + +#if IS_ENABLED(CONFIG_VIDEO_ADV7842_CEC) + /* cec */ + adv7842_cec_isr(sd, handled); +#endif + + /* tx 5v detect */ + if (irq_status[2] & 0x3) { + v4l2_dbg(1, debug, sd, "%s: irq tx_5v\n", __func__); + adv7842_s_detect_tx_5v_ctrl(sd); + if (handled) + *handled = true; + } + return 0; +} + +static int adv7842_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct adv7842_state *state = to_state(sd); + u32 blocks = 0; + u8 *data = NULL; + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + switch (edid->pad) { + case ADV7842_EDID_PORT_A: + case ADV7842_EDID_PORT_B: + if (state->hdmi_edid.present & (0x04 << edid->pad)) { + data = state->hdmi_edid.edid; + blocks = state->hdmi_edid.blocks; + } + break; + case ADV7842_EDID_PORT_VGA: + if (state->vga_edid.present) { + data = state->vga_edid.edid; + blocks = state->vga_edid.blocks; + } + break; + default: + return -EINVAL; + } + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = blocks; + return 0; + } + + if (!data) + return -ENODATA; + + if (edid->start_block >= blocks) + return -EINVAL; + + if (edid->start_block + edid->blocks > blocks) + edid->blocks = blocks - edid->start_block; + + memcpy(edid->edid, data + edid->start_block * 128, edid->blocks * 128); + + return 0; +} + +/* + * If the VGA_EDID_ENABLE bit is set (Repeater Map 0x7f, bit 7), then + * the first two blocks of the EDID are for the HDMI, and the first block + * of segment 1 (i.e. the third block of the EDID) is for VGA. + * So if a VGA EDID is installed, then the maximum size of the HDMI EDID + * is 2 blocks. + */ +static int adv7842_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *e) +{ + struct adv7842_state *state = to_state(sd); + unsigned int max_blocks = e->pad == ADV7842_EDID_PORT_VGA ? 1 : 4; + int err = 0; + + memset(e->reserved, 0, sizeof(e->reserved)); + + if (e->pad > ADV7842_EDID_PORT_VGA) + return -EINVAL; + if (e->start_block != 0) + return -EINVAL; + if (e->pad < ADV7842_EDID_PORT_VGA && state->vga_edid.blocks) + max_blocks = 2; + if (e->pad == ADV7842_EDID_PORT_VGA && state->hdmi_edid.blocks > 2) + return -EBUSY; + if (e->blocks > max_blocks) { + e->blocks = max_blocks; + return -E2BIG; + } + + /* todo, per edid */ + if (e->blocks) + state->aspect_ratio = v4l2_calc_aspect_ratio(e->edid[0x15], + e->edid[0x16]); + + switch (e->pad) { + case ADV7842_EDID_PORT_VGA: + memset(state->vga_edid.edid, 0, sizeof(state->vga_edid.edid)); + state->vga_edid.blocks = e->blocks; + state->vga_edid.present = e->blocks ? 0x1 : 0x0; + if (e->blocks) + memcpy(state->vga_edid.edid, e->edid, 128); + err = edid_write_vga_segment(sd); + break; + case ADV7842_EDID_PORT_A: + case ADV7842_EDID_PORT_B: + memset(state->hdmi_edid.edid, 0, sizeof(state->hdmi_edid.edid)); + state->hdmi_edid.blocks = e->blocks; + if (e->blocks) { + state->hdmi_edid.present |= 0x04 << e->pad; + memcpy(state->hdmi_edid.edid, e->edid, 128 * e->blocks); + } else { + state->hdmi_edid.present &= ~(0x04 << e->pad); + adv7842_s_detect_tx_5v_ctrl(sd); + } + err = edid_write_hdmi_segment(sd, e->pad); + break; + default: + return -EINVAL; + } + if (err < 0) + v4l2_err(sd, "error %d writing edid on port %d\n", err, e->pad); + return err; +} + +struct adv7842_cfg_read_infoframe { + const char *desc; + u8 present_mask; + u8 head_addr; + u8 payload_addr; +}; + +static const struct adv7842_cfg_read_infoframe adv7842_cri[] = { + { "AVI", 0x01, 0xe0, 0x00 }, + { "Audio", 0x02, 0xe3, 0x1c }, + { "SDP", 0x04, 0xe6, 0x2a }, + { "Vendor", 0x10, 0xec, 0x54 } +}; + +static int adv7842_read_infoframe_buf(struct v4l2_subdev *sd, int index, + u8 buf[V4L2_DEBUGFS_IF_MAX_LEN]) +{ + const struct adv7842_cfg_read_infoframe *cri = &adv7842_cri[index]; + int len, i; + + if (!(io_read(sd, 0x60) & cri->present_mask)) { + v4l2_dbg(1, debug, sd, + "%s infoframe not received\n", cri->desc); + return -ENOENT; + } + + for (i = 0; i < 3; i++) + buf[i] = infoframe_read(sd, cri->head_addr + i); + + len = buf[2] + 1; + + if (len + 3 > V4L2_DEBUGFS_IF_MAX_LEN) { + v4l2_err(sd, "%s: invalid %s infoframe length %d\n", + __func__, cri->desc, len); + return -ENOENT; + } + + for (i = 0; i < len; i++) + buf[i + 3] = infoframe_read(sd, cri->payload_addr + i); + return len + 3; +} + +static void adv7842_log_infoframes(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct device *dev = &client->dev; + union hdmi_infoframe frame; + u8 buffer[V4L2_DEBUGFS_IF_MAX_LEN] = {}; + int len, i; + + if (!(hdmi_read(sd, 0x05) & 0x80)) { + v4l2_info(sd, "receive DVI-D signal, no infoframes\n"); + return; + } + + for (i = 0; i < ARRAY_SIZE(adv7842_cri); i++) { + len = adv7842_read_infoframe_buf(sd, i, buffer); + if (len < 0) + continue; + + if (hdmi_infoframe_unpack(&frame, buffer, len) < 0) + v4l2_err(sd, "%s: unpack of %s infoframe failed\n", + __func__, adv7842_cri[i].desc); + else + hdmi_infoframe_log(KERN_INFO, dev, &frame); + } +} + +#if 0 +/* Let's keep it here for now, as it could be useful for debug */ +static const char * const prim_mode_txt[] = { + "SDP", + "Component", + "Graphics", + "Reserved", + "CVBS & HDMI AUDIO", + "HDMI-Comp", + "HDMI-GR", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", +}; +#endif + +static int adv7842_sdp_log_status(struct v4l2_subdev *sd) +{ + /* SDP (Standard definition processor) block */ + u8 sdp_signal_detected = sdp_read(sd, 0x5A) & 0x01; + + v4l2_info(sd, "Chip powered %s\n", no_power(sd) ? "off" : "on"); + v4l2_info(sd, "Prim-mode = 0x%x, video std = 0x%x\n", + io_read(sd, 0x01) & 0x0f, io_read(sd, 0x00) & 0x3f); + + v4l2_info(sd, "SDP: free run: %s\n", + (sdp_read(sd, 0x56) & 0x01) ? "on" : "off"); + v4l2_info(sd, "SDP: %s\n", sdp_signal_detected ? + "valid SD/PR signal detected" : "invalid/no signal"); + if (sdp_signal_detected) { + static const char * const sdp_std_txt[] = { + "NTSC-M/J", + "1?", + "NTSC-443", + "60HzSECAM", + "PAL-M", + "5?", + "PAL-60", + "7?", "8?", "9?", "a?", "b?", + "PAL-CombN", + "d?", + "PAL-BGHID", + "SECAM" + }; + v4l2_info(sd, "SDP: standard %s\n", + sdp_std_txt[sdp_read(sd, 0x52) & 0x0f]); + v4l2_info(sd, "SDP: %s\n", + (sdp_read(sd, 0x59) & 0x08) ? "50Hz" : "60Hz"); + v4l2_info(sd, "SDP: %s\n", + (sdp_read(sd, 0x57) & 0x08) ? "Interlaced" : "Progressive"); + v4l2_info(sd, "SDP: deinterlacer %s\n", + (sdp_read(sd, 0x12) & 0x08) ? "enabled" : "disabled"); + v4l2_info(sd, "SDP: csc %s mode\n", + (sdp_io_read(sd, 0xe0) & 0x40) ? "auto" : "manual"); + } + return 0; +} + +static int adv7842_cp_log_status(struct v4l2_subdev *sd) +{ + /* CP block */ + struct adv7842_state *state = to_state(sd); + struct v4l2_dv_timings timings; + int temp; + u8 reg_io_0x02 = io_read(sd, 0x02); + u8 reg_io_0x21 = io_read(sd, 0x21); + u8 reg_rep_0x77 = rep_read(sd, 0x77); + u8 reg_rep_0x7d = rep_read(sd, 0x7d); + bool audio_pll_locked = hdmi_read(sd, 0x04) & 0x01; + bool audio_sample_packet_detect = hdmi_read(sd, 0x18) & 0x01; + bool audio_mute = io_read(sd, 0x65) & 0x40; + + static const char * const csc_coeff_sel_rb[16] = { + "bypassed", "YPbPr601 -> RGB", "reserved", "YPbPr709 -> RGB", + "reserved", "RGB -> YPbPr601", "reserved", "RGB -> YPbPr709", + "reserved", "YPbPr709 -> YPbPr601", "YPbPr601 -> YPbPr709", + "reserved", "reserved", "reserved", "reserved", "manual" + }; + static const char * const input_color_space_txt[16] = { + "RGB limited range (16-235)", "RGB full range (0-255)", + "YCbCr Bt.601 (16-235)", "YCbCr Bt.709 (16-235)", + "xvYCC Bt.601", "xvYCC Bt.709", + "YCbCr Bt.601 (0-255)", "YCbCr Bt.709 (0-255)", + "invalid", "invalid", "invalid", "invalid", "invalid", + "invalid", "invalid", "automatic" + }; + static const char * const rgb_quantization_range_txt[] = { + "Automatic", + "RGB limited range (16-235)", + "RGB full range (0-255)", + }; + static const char * const deep_color_mode_txt[4] = { + "8-bits per channel", + "10-bits per channel", + "12-bits per channel", + "16-bits per channel (not supported)" + }; + + v4l2_info(sd, "-----Chip status-----\n"); + v4l2_info(sd, "Chip power: %s\n", no_power(sd) ? "off" : "on"); + v4l2_info(sd, "HDMI/DVI-D port selected: %s\n", + state->hdmi_port_a ? "A" : "B"); + v4l2_info(sd, "EDID A %s, B %s\n", + ((reg_rep_0x7d & 0x04) && (reg_rep_0x77 & 0x04)) ? + "enabled" : "disabled", + ((reg_rep_0x7d & 0x08) && (reg_rep_0x77 & 0x08)) ? + "enabled" : "disabled"); + v4l2_info(sd, "HPD A %s, B %s\n", + reg_io_0x21 & 0x02 ? "enabled" : "disabled", + reg_io_0x21 & 0x01 ? "enabled" : "disabled"); + v4l2_info(sd, "CEC: %s\n", state->cec_enabled_adap ? + "enabled" : "disabled"); + if (state->cec_enabled_adap) { + int i; + + for (i = 0; i < ADV7842_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (is_valid) + v4l2_info(sd, "CEC Logical Address: 0x%x\n", + state->cec_addr[i]); + } + } + + v4l2_info(sd, "-----Signal status-----\n"); + if (state->hdmi_port_a) { + v4l2_info(sd, "Cable detected (+5V power): %s\n", + io_read(sd, 0x6f) & 0x02 ? "true" : "false"); + v4l2_info(sd, "TMDS signal detected: %s\n", + (io_read(sd, 0x6a) & 0x02) ? "true" : "false"); + v4l2_info(sd, "TMDS signal locked: %s\n", + (io_read(sd, 0x6a) & 0x20) ? "true" : "false"); + } else { + v4l2_info(sd, "Cable detected (+5V power):%s\n", + io_read(sd, 0x6f) & 0x01 ? "true" : "false"); + v4l2_info(sd, "TMDS signal detected: %s\n", + (io_read(sd, 0x6a) & 0x01) ? "true" : "false"); + v4l2_info(sd, "TMDS signal locked: %s\n", + (io_read(sd, 0x6a) & 0x10) ? "true" : "false"); + } + v4l2_info(sd, "CP free run: %s\n", + (!!(cp_read(sd, 0xff) & 0x10) ? "on" : "off")); + v4l2_info(sd, "Prim-mode = 0x%x, video std = 0x%x, v_freq = 0x%x\n", + io_read(sd, 0x01) & 0x0f, io_read(sd, 0x00) & 0x3f, + (io_read(sd, 0x01) & 0x70) >> 4); + + v4l2_info(sd, "-----Video Timings-----\n"); + if (no_cp_signal(sd)) { + v4l2_info(sd, "STDI: not locked\n"); + } else { + u32 bl = ((cp_read(sd, 0xb1) & 0x3f) << 8) | cp_read(sd, 0xb2); + u32 lcf = ((cp_read(sd, 0xb3) & 0x7) << 8) | cp_read(sd, 0xb4); + u32 lcvs = cp_read(sd, 0xb3) >> 3; + u32 fcl = ((cp_read(sd, 0xb8) & 0x1f) << 8) | cp_read(sd, 0xb9); + char hs_pol = ((cp_read(sd, 0xb5) & 0x10) ? + ((cp_read(sd, 0xb5) & 0x08) ? '+' : '-') : 'x'); + char vs_pol = ((cp_read(sd, 0xb5) & 0x40) ? + ((cp_read(sd, 0xb5) & 0x20) ? '+' : '-') : 'x'); + v4l2_info(sd, + "STDI: lcf (frame height - 1) = %d, bl = %d, lcvs (vsync) = %d, fcl = %d, %s, %chsync, %cvsync\n", + lcf, bl, lcvs, fcl, + (cp_read(sd, 0xb1) & 0x40) ? + "interlaced" : "progressive", + hs_pol, vs_pol); + } + if (adv7842_query_dv_timings(sd, 0, &timings)) + v4l2_info(sd, "No video detected\n"); + else + v4l2_print_dv_timings(sd->name, "Detected format: ", + &timings, true); + v4l2_print_dv_timings(sd->name, "Configured format: ", + &state->timings, true); + + if (no_cp_signal(sd)) + return 0; + + v4l2_info(sd, "-----Color space-----\n"); + v4l2_info(sd, "RGB quantization range ctrl: %s\n", + rgb_quantization_range_txt[state->rgb_quantization_range]); + v4l2_info(sd, "Input color space: %s\n", + input_color_space_txt[reg_io_0x02 >> 4]); + v4l2_info(sd, "Output color space: %s %s, alt-gamma %s\n", + (reg_io_0x02 & 0x02) ? "RGB" : "YCbCr", + (((reg_io_0x02 >> 2) & 0x01) ^ (reg_io_0x02 & 0x01)) ? + "(16-235)" : "(0-255)", + (reg_io_0x02 & 0x08) ? "enabled" : "disabled"); + temp = cp_read(sd, 0xf4) >> 4; + v4l2_info(sd, "Color space conversion: %s\n", + temp < 0 ? "" : csc_coeff_sel_rb[temp]); + + if (!is_digital_input(sd)) + return 0; + + v4l2_info(sd, "-----%s status-----\n", is_hdmi(sd) ? "HDMI" : "DVI-D"); + v4l2_info(sd, "HDCP encrypted content: %s\n", + (hdmi_read(sd, 0x05) & 0x40) ? "true" : "false"); + v4l2_info(sd, "HDCP keys read: %s%s\n", + (hdmi_read(sd, 0x04) & 0x20) ? "yes" : "no", + (hdmi_read(sd, 0x04) & 0x10) ? "ERROR" : ""); + if (!is_hdmi(sd)) + return 0; + + v4l2_info(sd, "Audio: pll %s, samples %s, %s\n", + audio_pll_locked ? "locked" : "not locked", + audio_sample_packet_detect ? "detected" : "not detected", + audio_mute ? "muted" : "enabled"); + if (audio_pll_locked && audio_sample_packet_detect) { + v4l2_info(sd, "Audio format: %s\n", + (hdmi_read(sd, 0x07) & 0x40) ? "multi-channel" : "stereo"); + } + v4l2_info(sd, "Audio CTS: %u\n", (hdmi_read(sd, 0x5b) << 12) + + (hdmi_read(sd, 0x5c) << 8) + + (hdmi_read(sd, 0x5d) & 0xf0)); + v4l2_info(sd, "Audio N: %u\n", ((hdmi_read(sd, 0x5d) & 0x0f) << 16) + + (hdmi_read(sd, 0x5e) << 8) + + hdmi_read(sd, 0x5f)); + v4l2_info(sd, "AV Mute: %s\n", + (hdmi_read(sd, 0x04) & 0x40) ? "on" : "off"); + temp = hdmi_read(sd, 0x0b) >> 6; + v4l2_info(sd, "Deep color mode: %s\n", + temp < 0 ? "" : deep_color_mode_txt[temp]); + + adv7842_log_infoframes(sd); + + return 0; +} + +static int adv7842_log_status(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + + if (state->mode == ADV7842_MODE_SDP) + return adv7842_sdp_log_status(sd); + return adv7842_cp_log_status(sd); +} + +static int adv7842_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct adv7842_state *state = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (state->mode != ADV7842_MODE_SDP) + return -ENODATA; + + if (!(sdp_read(sd, 0x5A) & 0x01)) { + *std = 0; + v4l2_dbg(1, debug, sd, "%s: no valid signal\n", __func__); + return 0; + } + + switch (sdp_read(sd, 0x52) & 0x0f) { + case 0: + /* NTSC-M/J */ + *std &= V4L2_STD_NTSC; + break; + case 2: + /* NTSC-443 */ + *std &= V4L2_STD_NTSC_443; + break; + case 3: + /* 60HzSECAM */ + *std &= V4L2_STD_SECAM; + break; + case 4: + /* PAL-M */ + *std &= V4L2_STD_PAL_M; + break; + case 6: + /* PAL-60 */ + *std &= V4L2_STD_PAL_60; + break; + case 0xc: + /* PAL-CombN */ + *std &= V4L2_STD_PAL_Nc; + break; + case 0xe: + /* PAL-BGHID */ + *std &= V4L2_STD_PAL; + break; + case 0xf: + /* SECAM */ + *std &= V4L2_STD_SECAM; + break; + default: + *std &= V4L2_STD_ALL; + break; + } + return 0; +} + +static void adv7842_s_sdp_io(struct v4l2_subdev *sd, struct adv7842_sdp_io_sync_adjustment *s) +{ + if (s && s->adjust) { + sdp_io_write(sd, 0x94, (s->hs_beg >> 8) & 0xf); + sdp_io_write(sd, 0x95, s->hs_beg & 0xff); + sdp_io_write(sd, 0x96, (s->hs_width >> 8) & 0xf); + sdp_io_write(sd, 0x97, s->hs_width & 0xff); + sdp_io_write(sd, 0x98, (s->de_beg >> 8) & 0xf); + sdp_io_write(sd, 0x99, s->de_beg & 0xff); + sdp_io_write(sd, 0x9a, (s->de_end >> 8) & 0xf); + sdp_io_write(sd, 0x9b, s->de_end & 0xff); + sdp_io_write(sd, 0xa8, s->vs_beg_o); + sdp_io_write(sd, 0xa9, s->vs_beg_e); + sdp_io_write(sd, 0xaa, s->vs_end_o); + sdp_io_write(sd, 0xab, s->vs_end_e); + sdp_io_write(sd, 0xac, s->de_v_beg_o); + sdp_io_write(sd, 0xad, s->de_v_beg_e); + sdp_io_write(sd, 0xae, s->de_v_end_o); + sdp_io_write(sd, 0xaf, s->de_v_end_e); + } else { + /* set to default */ + sdp_io_write(sd, 0x94, 0x00); + sdp_io_write(sd, 0x95, 0x00); + sdp_io_write(sd, 0x96, 0x00); + sdp_io_write(sd, 0x97, 0x20); + sdp_io_write(sd, 0x98, 0x00); + sdp_io_write(sd, 0x99, 0x00); + sdp_io_write(sd, 0x9a, 0x00); + sdp_io_write(sd, 0x9b, 0x00); + sdp_io_write(sd, 0xa8, 0x04); + sdp_io_write(sd, 0xa9, 0x04); + sdp_io_write(sd, 0xaa, 0x04); + sdp_io_write(sd, 0xab, 0x04); + sdp_io_write(sd, 0xac, 0x04); + sdp_io_write(sd, 0xad, 0x04); + sdp_io_write(sd, 0xae, 0x04); + sdp_io_write(sd, 0xaf, 0x04); + } +} + +static int adv7842_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct adv7842_state *state = to_state(sd); + struct adv7842_platform_data *pdata = &state->pdata; + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (state->mode != ADV7842_MODE_SDP) + return -ENODATA; + + if (norm & V4L2_STD_625_50) + adv7842_s_sdp_io(sd, &pdata->sdp_io_sync_625); + else if (norm & V4L2_STD_525_60) + adv7842_s_sdp_io(sd, &pdata->sdp_io_sync_525); + else + adv7842_s_sdp_io(sd, NULL); + + if (norm & V4L2_STD_ALL) { + state->norm = norm; + return 0; + } + return -EINVAL; +} + +static int adv7842_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm) +{ + struct adv7842_state *state = to_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (state->mode != ADV7842_MODE_SDP) + return -ENODATA; + + *norm = state->norm; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int adv7842_core_init(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + struct adv7842_platform_data *pdata = &state->pdata; + hdmi_write(sd, 0x48, + (pdata->disable_pwrdnb ? 0x80 : 0) | + (pdata->disable_cable_det_rst ? 0x40 : 0)); + + disable_input(sd); + + /* + * Disable I2C access to internal EDID ram from HDMI DDC ports + * Disable auto edid enable when leaving powerdown mode + */ + rep_write_and_or(sd, 0x77, 0xd3, 0x20); + + /* power */ + io_write(sd, 0x0c, 0x42); /* Power up part and power down VDP */ + io_write(sd, 0x15, 0x80); /* Power up pads */ + + /* video format */ + io_write(sd, 0x02, 0xf0 | pdata->alt_gamma << 3); + io_write_and_or(sd, 0x05, 0xf0, pdata->blank_data << 3 | + pdata->insert_av_codes << 2 | + pdata->replicate_av_codes << 1); + adv7842_setup_format(state); + + /* HDMI audio */ + hdmi_write_and_or(sd, 0x1a, 0xf1, 0x08); /* Wait 1 s before unmute */ + + /* Drive strength */ + io_write_and_or(sd, 0x14, 0xc0, + pdata->dr_str_data << 4 | + pdata->dr_str_clk << 2 | + pdata->dr_str_sync); + + /* HDMI free run */ + cp_write_and_or(sd, 0xba, 0xfc, pdata->hdmi_free_run_enable | + (pdata->hdmi_free_run_mode << 1)); + + /* SPD free run */ + sdp_write_and_or(sd, 0xdd, 0xf0, pdata->sdp_free_run_force | + (pdata->sdp_free_run_cbar_en << 1) | + (pdata->sdp_free_run_man_col_en << 2) | + (pdata->sdp_free_run_auto << 3)); + + /* TODO from platform data */ + cp_write(sd, 0x69, 0x14); /* Enable CP CSC */ + io_write(sd, 0x06, 0xa6); /* positive VS and HS and DE */ + cp_write(sd, 0xf3, 0xdc); /* Low threshold to enter/exit free run mode */ + afe_write(sd, 0xb5, 0x01); /* Setting MCLK to 256Fs */ + + afe_write(sd, 0x02, pdata->ain_sel); /* Select analog input muxing mode */ + io_write_and_or(sd, 0x30, ~(1 << 4), pdata->output_bus_lsb_to_msb << 4); + + sdp_csc_coeff(sd, &pdata->sdp_csc_coeff); + + /* todo, improve settings for sdram */ + if (pdata->sd_ram_size >= 128) { + sdp_write(sd, 0x12, 0x0d); /* Frame TBC,3D comb enabled */ + if (pdata->sd_ram_ddr) { + /* SDP setup for the AD eval board */ + sdp_io_write(sd, 0x6f, 0x00); /* DDR mode */ + sdp_io_write(sd, 0x75, 0x0a); /* 128 MB memory size */ + sdp_io_write(sd, 0x7a, 0xa5); /* Timing Adjustment */ + sdp_io_write(sd, 0x7b, 0x8f); /* Timing Adjustment */ + sdp_io_write(sd, 0x60, 0x01); /* SDRAM reset */ + } else { + sdp_io_write(sd, 0x75, 0x0a); /* 64 MB memory size ?*/ + sdp_io_write(sd, 0x74, 0x00); /* must be zero for sdr sdram */ + sdp_io_write(sd, 0x79, 0x33); /* CAS latency to 3, + depends on memory */ + sdp_io_write(sd, 0x6f, 0x01); /* SDR mode */ + sdp_io_write(sd, 0x7a, 0xa5); /* Timing Adjustment */ + sdp_io_write(sd, 0x7b, 0x8f); /* Timing Adjustment */ + sdp_io_write(sd, 0x60, 0x01); /* SDRAM reset */ + } + } else { + /* + * Manual UG-214, rev 0 is bit confusing on this bit + * but a '1' disables any signal if the Ram is active. + */ + sdp_io_write(sd, 0x29, 0x10); /* Tristate memory interface */ + } + + select_input(sd, pdata->vid_std_select); + + enable_input(sd); + + if (pdata->hpa_auto) { + /* HPA auto, HPA 0.5s after Edid set and Cable detect */ + hdmi_write(sd, 0x69, 0x5c); + } else { + /* HPA manual */ + hdmi_write(sd, 0x69, 0xa3); + /* HPA disable on port A and B */ + io_write_and_or(sd, 0x20, 0xcf, 0x00); + } + + /* LLC */ + io_write(sd, 0x19, 0x80 | pdata->llc_dll_phase); + io_write(sd, 0x33, 0x40); + + /* interrupts */ + io_write(sd, 0x40, 0xf2); /* Configure INT1 */ + + adv7842_irq_enable(sd, true); + + return v4l2_ctrl_handler_setup(sd->ctrl_handler); +} + +/* ----------------------------------------------------------------------- */ + +static int adv7842_ddr_ram_test(struct v4l2_subdev *sd) +{ + /* + * From ADV784x external Memory test.pdf + * + * Reset must just been performed before running test. + * Recommended to reset after test. + */ + int i; + int pass = 0; + int fail = 0; + int complete = 0; + + io_write(sd, 0x00, 0x01); /* Program SDP 4x1 */ + io_write(sd, 0x01, 0x00); /* Program SDP mode */ + afe_write(sd, 0x80, 0x92); /* SDP Recommended Write */ + afe_write(sd, 0x9B, 0x01); /* SDP Recommended Write ADV7844ES1 */ + afe_write(sd, 0x9C, 0x60); /* SDP Recommended Write ADV7844ES1 */ + afe_write(sd, 0x9E, 0x02); /* SDP Recommended Write ADV7844ES1 */ + afe_write(sd, 0xA0, 0x0B); /* SDP Recommended Write ADV7844ES1 */ + afe_write(sd, 0xC3, 0x02); /* Memory BIST Initialisation */ + io_write(sd, 0x0C, 0x40); /* Power up ADV7844 */ + io_write(sd, 0x15, 0xBA); /* Enable outputs */ + sdp_write(sd, 0x12, 0x00); /* Disable 3D comb, Frame TBC & 3DNR */ + io_write(sd, 0xFF, 0x04); /* Reset memory controller */ + + usleep_range(5000, 6000); + + sdp_write(sd, 0x12, 0x00); /* Disable 3D Comb, Frame TBC & 3DNR */ + sdp_io_write(sd, 0x2A, 0x01); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x7c, 0x19); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x80, 0x87); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x81, 0x4a); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x82, 0x2c); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x83, 0x0e); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x84, 0x94); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x85, 0x62); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x7d, 0x00); /* Memory BIST Initialisation */ + sdp_io_write(sd, 0x7e, 0x1a); /* Memory BIST Initialisation */ + + usleep_range(5000, 6000); + + sdp_io_write(sd, 0xd9, 0xd5); /* Enable BIST Test */ + sdp_write(sd, 0x12, 0x05); /* Enable FRAME TBC & 3D COMB */ + + msleep(20); + + for (i = 0; i < 10; i++) { + u8 result = sdp_io_read(sd, 0xdb); + if (result & 0x10) { + complete++; + if (result & 0x20) + fail++; + else + pass++; + } + msleep(20); + } + + v4l2_dbg(1, debug, sd, + "Ram Test: completed %d of %d: pass %d, fail %d\n", + complete, i, pass, fail); + + if (!complete || fail) + return -EIO; + return 0; +} + +static void adv7842_rewrite_i2c_addresses(struct v4l2_subdev *sd, + struct adv7842_platform_data *pdata) +{ + io_write(sd, 0xf1, pdata->i2c_sdp << 1); + io_write(sd, 0xf2, pdata->i2c_sdp_io << 1); + io_write(sd, 0xf3, pdata->i2c_avlink << 1); + io_write(sd, 0xf4, pdata->i2c_cec << 1); + io_write(sd, 0xf5, pdata->i2c_infoframe << 1); + + io_write(sd, 0xf8, pdata->i2c_afe << 1); + io_write(sd, 0xf9, pdata->i2c_repeater << 1); + io_write(sd, 0xfa, pdata->i2c_edid << 1); + io_write(sd, 0xfb, pdata->i2c_hdmi << 1); + + io_write(sd, 0xfd, pdata->i2c_cp << 1); + io_write(sd, 0xfe, pdata->i2c_vdp << 1); +} + +static int adv7842_command_ram_test(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct adv7842_state *state = to_state(sd); + struct adv7842_platform_data *pdata = client->dev.platform_data; + struct v4l2_dv_timings timings; + int ret = 0; + + if (!pdata) + return -ENODEV; + + if (!pdata->sd_ram_size || !pdata->sd_ram_ddr) { + v4l2_info(sd, "no sdram or no ddr sdram\n"); + return -EINVAL; + } + + main_reset(sd); + + adv7842_rewrite_i2c_addresses(sd, pdata); + + /* run ram test */ + ret = adv7842_ddr_ram_test(sd); + + main_reset(sd); + + adv7842_rewrite_i2c_addresses(sd, pdata); + + /* and re-init chip and state */ + adv7842_core_init(sd); + + disable_input(sd); + + select_input(sd, state->vid_std_select); + + enable_input(sd); + + edid_write_vga_segment(sd); + edid_write_hdmi_segment(sd, ADV7842_EDID_PORT_A); + edid_write_hdmi_segment(sd, ADV7842_EDID_PORT_B); + + timings = state->timings; + + memset(&state->timings, 0, sizeof(struct v4l2_dv_timings)); + + adv7842_s_dv_timings(sd, 0, &timings); + + return ret; +} + +static long adv7842_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + switch (cmd) { + case ADV7842_CMD_RAM_TEST: + return adv7842_command_ram_test(sd); + } + return -ENOTTY; +} + +static int adv7842_subscribe_event(struct v4l2_subdev *sd, + struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +static ssize_t +adv7842_debugfs_if_read(u32 type, void *priv, struct file *filp, + char __user *ubuf, size_t count, loff_t *ppos) +{ + u8 buf[V4L2_DEBUGFS_IF_MAX_LEN] = {}; + struct v4l2_subdev *sd = priv; + int index; + int len; + + if (!is_hdmi(sd)) + return 0; + + switch (type) { + case V4L2_DEBUGFS_IF_AVI: + index = 0; + break; + case V4L2_DEBUGFS_IF_AUDIO: + index = 1; + break; + case V4L2_DEBUGFS_IF_SPD: + index = 2; + break; + case V4L2_DEBUGFS_IF_HDMI: + index = 3; + break; + default: + return 0; + } + + len = adv7842_read_infoframe_buf(sd, index, buf); + if (len > 0) + len = simple_read_from_buffer(ubuf, count, ppos, buf, len); + return len < 0 ? 0 : len; +} + +static int adv7842_registered(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err; + + err = cec_register_adapter(state->cec_adap, &client->dev); + if (err) { + cec_delete_adapter(state->cec_adap); + } else { + state->debugfs_dir = debugfs_create_dir(sd->name, v4l2_debugfs_root()); + state->infoframes = v4l2_debugfs_if_alloc(state->debugfs_dir, + V4L2_DEBUGFS_IF_AVI | V4L2_DEBUGFS_IF_AUDIO | + V4L2_DEBUGFS_IF_SPD | V4L2_DEBUGFS_IF_HDMI, sd, + adv7842_debugfs_if_read); + } + return err; +} + +static void adv7842_unregistered(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + + cec_unregister_adapter(state->cec_adap); + v4l2_debugfs_if_free(state->infoframes); + state->infoframes = NULL; + debugfs_remove_recursive(state->debugfs_dir); + state->debugfs_dir = NULL; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops adv7842_ctrl_ops = { + .s_ctrl = adv7842_s_ctrl, + .g_volatile_ctrl = adv7842_g_volatile_ctrl, +}; + +static const struct v4l2_subdev_core_ops adv7842_core_ops = { + .log_status = adv7842_log_status, + .ioctl = adv7842_ioctl, + .interrupt_service_routine = adv7842_isr, + .subscribe_event = adv7842_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = adv7842_g_register, + .s_register = adv7842_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops adv7842_video_ops = { + .g_std = adv7842_g_std, + .s_std = adv7842_s_std, + .s_routing = adv7842_s_routing, + .querystd = adv7842_querystd, + .g_input_status = adv7842_g_input_status, +}; + +static const struct v4l2_subdev_pad_ops adv7842_pad_ops = { + .enum_mbus_code = adv7842_enum_mbus_code, + .get_fmt = adv7842_get_format, + .set_fmt = adv7842_set_format, + .get_edid = adv7842_get_edid, + .set_edid = adv7842_set_edid, + .s_dv_timings = adv7842_s_dv_timings, + .g_dv_timings = adv7842_g_dv_timings, + .query_dv_timings = adv7842_query_dv_timings, + .enum_dv_timings = adv7842_enum_dv_timings, + .dv_timings_cap = adv7842_dv_timings_cap, +}; + +static const struct v4l2_subdev_ops adv7842_ops = { + .core = &adv7842_core_ops, + .video = &adv7842_video_ops, + .pad = &adv7842_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops adv7842_int_ops = { + .registered = adv7842_registered, + .unregistered = adv7842_unregistered, +}; + +/* -------------------------- custom ctrls ---------------------------------- */ + +static const struct v4l2_ctrl_config adv7842_ctrl_analog_sampling_phase = { + .ops = &adv7842_ctrl_ops, + .id = V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE, + .name = "Analog Sampling Phase", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 0x1f, + .step = 1, + .def = 0, +}; + +static const struct v4l2_ctrl_config adv7842_ctrl_free_run_color_manual = { + .ops = &adv7842_ctrl_ops, + .id = V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL, + .name = "Free Running Color, Manual", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .max = 1, + .step = 1, + .def = 1, +}; + +static const struct v4l2_ctrl_config adv7842_ctrl_free_run_color = { + .ops = &adv7842_ctrl_ops, + .id = V4L2_CID_ADV_RX_FREE_RUN_COLOR, + .name = "Free Running Color", + .type = V4L2_CTRL_TYPE_INTEGER, + .max = 0xffffff, + .step = 0x1, +}; + + +static void adv7842_unregister_clients(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + i2c_unregister_device(state->i2c_avlink); + i2c_unregister_device(state->i2c_cec); + i2c_unregister_device(state->i2c_infoframe); + i2c_unregister_device(state->i2c_sdp_io); + i2c_unregister_device(state->i2c_sdp); + i2c_unregister_device(state->i2c_afe); + i2c_unregister_device(state->i2c_repeater); + i2c_unregister_device(state->i2c_edid); + i2c_unregister_device(state->i2c_hdmi); + i2c_unregister_device(state->i2c_cp); + i2c_unregister_device(state->i2c_vdp); + + state->i2c_avlink = NULL; + state->i2c_cec = NULL; + state->i2c_infoframe = NULL; + state->i2c_sdp_io = NULL; + state->i2c_sdp = NULL; + state->i2c_afe = NULL; + state->i2c_repeater = NULL; + state->i2c_edid = NULL; + state->i2c_hdmi = NULL; + state->i2c_cp = NULL; + state->i2c_vdp = NULL; +} + +static struct i2c_client *adv7842_dummy_client(struct v4l2_subdev *sd, const char *desc, + u8 addr, u8 io_reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct i2c_client *cp; + + io_write(sd, io_reg, addr << 1); + + if (addr == 0) { + v4l2_err(sd, "no %s i2c addr configured\n", desc); + return NULL; + } + + cp = i2c_new_dummy_device(client->adapter, io_read(sd, io_reg) >> 1); + if (IS_ERR(cp)) { + v4l2_err(sd, "register %s on i2c addr 0x%x failed with %pe\n", + desc, addr, cp); + cp = NULL; + } + + return cp; +} + +static int adv7842_register_clients(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + struct adv7842_platform_data *pdata = &state->pdata; + + state->i2c_avlink = adv7842_dummy_client(sd, "avlink", pdata->i2c_avlink, 0xf3); + state->i2c_cec = adv7842_dummy_client(sd, "cec", pdata->i2c_cec, 0xf4); + state->i2c_infoframe = adv7842_dummy_client(sd, "infoframe", pdata->i2c_infoframe, 0xf5); + state->i2c_sdp_io = adv7842_dummy_client(sd, "sdp_io", pdata->i2c_sdp_io, 0xf2); + state->i2c_sdp = adv7842_dummy_client(sd, "sdp", pdata->i2c_sdp, 0xf1); + state->i2c_afe = adv7842_dummy_client(sd, "afe", pdata->i2c_afe, 0xf8); + state->i2c_repeater = adv7842_dummy_client(sd, "repeater", pdata->i2c_repeater, 0xf9); + state->i2c_edid = adv7842_dummy_client(sd, "edid", pdata->i2c_edid, 0xfa); + state->i2c_hdmi = adv7842_dummy_client(sd, "hdmi", pdata->i2c_hdmi, 0xfb); + state->i2c_cp = adv7842_dummy_client(sd, "cp", pdata->i2c_cp, 0xfd); + state->i2c_vdp = adv7842_dummy_client(sd, "vdp", pdata->i2c_vdp, 0xfe); + + if (!state->i2c_avlink || + !state->i2c_cec || + !state->i2c_infoframe || + !state->i2c_sdp_io || + !state->i2c_sdp || + !state->i2c_afe || + !state->i2c_repeater || + !state->i2c_edid || + !state->i2c_hdmi || + !state->i2c_cp || + !state->i2c_vdp) + return -1; + + return 0; +} + +static int adv7842_probe(struct i2c_client *client) +{ + struct adv7842_state *state; + static const struct v4l2_dv_timings cea640x480 = + V4L2_DV_BT_CEA_640X480P59_94; + struct adv7842_platform_data *pdata = client->dev.platform_data; + struct v4l2_ctrl_handler *hdl; + struct v4l2_ctrl *ctrl; + struct v4l2_subdev *sd; + unsigned int i; + u16 rev; + int err; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_dbg(1, debug, client, "detecting adv7842 client on address 0x%x\n", + client->addr << 1); + + if (!pdata) { + v4l_err(client, "No platform data!\n"); + return -ENODEV; + } + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + /* platform data */ + state->pdata = *pdata; + state->timings = cea640x480; + state->format = adv7842_format_info(state, MEDIA_BUS_FMT_YUYV8_2X8); + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &adv7842_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + sd->internal_ops = &adv7842_int_ops; + state->mode = pdata->mode; + + state->hdmi_port_a = pdata->input == ADV7842_SELECT_HDMI_PORT_A; + state->restart_stdi_once = true; + + /* i2c access to adv7842? */ + rev = adv_smbus_read_byte_data_check(client, 0xea, false) << 8 | + adv_smbus_read_byte_data_check(client, 0xeb, false); + if (rev != 0x2012) { + v4l2_info(sd, "got rev=0x%04x on first read attempt\n", rev); + rev = adv_smbus_read_byte_data_check(client, 0xea, false) << 8 | + adv_smbus_read_byte_data_check(client, 0xeb, false); + } + if (rev != 0x2012) { + v4l2_info(sd, "not an adv7842 on address 0x%x (rev=0x%04x)\n", + client->addr << 1, rev); + return -ENODEV; + } + + if (pdata->chip_reset) + main_reset(sd); + + /* control handlers */ + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 6); + + /* add in ascending ID order */ + v4l2_ctrl_new_std(hdl, &adv7842_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &adv7842_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &adv7842_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &adv7842_ctrl_ops, + V4L2_CID_HUE, 0, 128, 1, 0); + ctrl = v4l2_ctrl_new_std_menu(hdl, &adv7842_ctrl_ops, + V4L2_CID_DV_RX_IT_CONTENT_TYPE, V4L2_DV_IT_CONTENT_TYPE_NO_ITC, + 0, V4L2_DV_IT_CONTENT_TYPE_NO_ITC); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + /* custom controls */ + state->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_DV_RX_POWER_PRESENT, 0, 3, 0, 0); + state->analog_sampling_phase_ctrl = v4l2_ctrl_new_custom(hdl, + &adv7842_ctrl_analog_sampling_phase, NULL); + state->free_run_color_ctrl_manual = v4l2_ctrl_new_custom(hdl, + &adv7842_ctrl_free_run_color_manual, NULL); + state->free_run_color_ctrl = v4l2_ctrl_new_custom(hdl, + &adv7842_ctrl_free_run_color, NULL); + state->rgb_quantization_range_ctrl = + v4l2_ctrl_new_std_menu(hdl, &adv7842_ctrl_ops, + V4L2_CID_DV_RX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, + 0, V4L2_DV_RGB_RANGE_AUTO); + sd->ctrl_handler = hdl; + if (hdl->error) { + err = hdl->error; + goto err_hdl; + } + if (adv7842_s_detect_tx_5v_ctrl(sd)) { + err = -ENODEV; + goto err_hdl; + } + + if (adv7842_register_clients(sd) < 0) { + err = -ENOMEM; + v4l2_err(sd, "failed to create all i2c clients\n"); + goto err_i2c; + } + + + INIT_DELAYED_WORK(&state->delayed_work_enable_hotplug, + adv7842_delayed_work_enable_hotplug); + + sd->entity.function = MEDIA_ENT_F_DV_DECODER; + for (i = 0; i < ADV7842_PAD_SOURCE; ++i) + state->pads[i].flags = MEDIA_PAD_FL_SINK; + state->pads[ADV7842_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + err = media_entity_pads_init(&sd->entity, ADV7842_PAD_SOURCE + 1, + state->pads); + if (err) + goto err_i2c; + + err = adv7842_core_init(sd); + if (err) + goto err_entity; + +#if IS_ENABLED(CONFIG_VIDEO_ADV7842_CEC) + state->cec_adap = cec_allocate_adapter(&adv7842_cec_adap_ops, + state, dev_name(&client->dev), + CEC_CAP_DEFAULTS, ADV7842_MAX_ADDRS); + err = PTR_ERR_OR_ZERO(state->cec_adap); + if (err) + goto err_entity; +#endif + + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + return 0; + +err_entity: + media_entity_cleanup(&sd->entity); +err_i2c: + adv7842_unregister_clients(sd); +err_hdl: + v4l2_ctrl_handler_free(hdl); + return err; +} + +/* ----------------------------------------------------------------------- */ + +static void adv7842_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7842_state *state = to_state(sd); + + adv7842_irq_enable(sd, false); + cancel_delayed_work_sync(&state->delayed_work_enable_hotplug); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + adv7842_unregister_clients(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id adv7842_id[] = { + { .name = "adv7842" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7842_id); + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver adv7842_driver = { + .driver = { + .name = "adv7842", + }, + .probe = adv7842_probe, + .remove = adv7842_remove, + .id_table = adv7842_id, +}; + +module_i2c_driver(adv7842_driver); diff --git a/drivers/media/i2c/ak7375.c b/drivers/media/i2c/ak7375.c new file mode 100644 index 000000000..9a2432cea --- /dev/null +++ b/drivers/media/i2c/ak7375.c @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 Intel Corporation + +#include +#include +#include +#include +#include +#include +#include +#include + +struct ak73xx_chipdef { + u8 reg_position; + u8 reg_cont; + u8 shift_pos; + u8 mode_active; + u8 mode_standby; + bool has_standby; /* Some chips may not have standby mode */ + u16 focus_pos_max; + /* + * This sets the minimum granularity for the focus positions. + * A value of 1 gives maximum accuracy for a desired focus position + */ + u16 focus_steps; + /* + * This acts as the minimum granularity of lens movement. + * Keep this value power of 2, so the control steps can be + * uniformly adjusted for gradual lens movement, with desired + * number of control steps. + */ + u16 ctrl_steps; + u16 ctrl_delay_us; + /* + * The vcm may take time (tDELAY) to power on and start taking + * I2C messages. + */ + u16 power_delay_us; +}; + +static const struct ak73xx_chipdef ak7345_cdef = { + .reg_position = 0x0, + .reg_cont = 0x2, + .shift_pos = 7, /* 9 bits position values, need to << 7 */ + .mode_active = 0x0, + .has_standby = false, + .focus_pos_max = 511, + .focus_steps = 1, + .ctrl_steps = 16, + .ctrl_delay_us = 1000, + .power_delay_us = 20000, +}; + +static const struct ak73xx_chipdef ak7375_cdef = { + .reg_position = 0x0, + .reg_cont = 0x2, + .shift_pos = 4, /* 12 bits position values, need to << 4 */ + .mode_active = 0x0, + .mode_standby = 0x40, + .has_standby = true, + .focus_pos_max = 4095, + .focus_steps = 1, + .ctrl_steps = 64, + .ctrl_delay_us = 1000, + .power_delay_us = 10000, +}; + +static const char * const ak7375_supply_names[] = { + "vdd", + "vio", +}; + +/* ak7375 device structure */ +struct ak7375_device { + const struct ak73xx_chipdef *cdef; + struct v4l2_ctrl_handler ctrls_vcm; + struct v4l2_subdev sd; + struct v4l2_ctrl *focus; + struct regulator_bulk_data supplies[ARRAY_SIZE(ak7375_supply_names)]; + + /* active or standby mode */ + bool active; +}; + +static inline struct ak7375_device *to_ak7375_vcm(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct ak7375_device, ctrls_vcm); +} + +static inline struct ak7375_device *sd_to_ak7375_vcm(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct ak7375_device, sd); +} + +static int ak7375_i2c_write(struct ak7375_device *ak7375, + u8 addr, u16 data, u8 size) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ak7375->sd); + u8 buf[3]; + int ret; + + if (size != 1 && size != 2) + return -EINVAL; + buf[0] = addr; + buf[size] = data & 0xff; + if (size == 2) + buf[1] = (data >> 8) & 0xff; + ret = i2c_master_send(client, (const char *)buf, size + 1); + if (ret < 0) + return ret; + if (ret != size + 1) + return -EIO; + + return 0; +} + +static int ak7375_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ak7375_device *dev_vcm = to_ak7375_vcm(ctrl); + const struct ak73xx_chipdef *cdef = dev_vcm->cdef; + + if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) + return ak7375_i2c_write(dev_vcm, cdef->reg_position, + ctrl->val << cdef->shift_pos, 2); + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops ak7375_vcm_ctrl_ops = { + .s_ctrl = ak7375_set_ctrl, +}; + +static int ak7375_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return pm_runtime_resume_and_get(sd->dev); +} + +static int ak7375_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops ak7375_int_ops = { + .open = ak7375_open, + .close = ak7375_close, +}; + +static const struct v4l2_subdev_ops ak7375_ops = { }; + +static void ak7375_subdev_cleanup(struct ak7375_device *ak7375_dev) +{ + v4l2_async_unregister_subdev(&ak7375_dev->sd); + v4l2_ctrl_handler_free(&ak7375_dev->ctrls_vcm); + media_entity_cleanup(&ak7375_dev->sd.entity); +} + +static int ak7375_init_controls(struct ak7375_device *dev_vcm) +{ + struct v4l2_ctrl_handler *hdl = &dev_vcm->ctrls_vcm; + const struct v4l2_ctrl_ops *ops = &ak7375_vcm_ctrl_ops; + const struct ak73xx_chipdef *cdef = dev_vcm->cdef; + + v4l2_ctrl_handler_init(hdl, 1); + + dev_vcm->focus = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, + 0, cdef->focus_pos_max, cdef->focus_steps, 0); + + if (hdl->error) + dev_err(dev_vcm->sd.dev, "%s fail error: 0x%x\n", + __func__, hdl->error); + dev_vcm->sd.ctrl_handler = hdl; + + return hdl->error; +} + +static int ak7375_probe(struct i2c_client *client) +{ + struct ak7375_device *ak7375_dev; + int ret; + unsigned int i; + + ak7375_dev = devm_kzalloc(&client->dev, sizeof(*ak7375_dev), + GFP_KERNEL); + if (!ak7375_dev) + return -ENOMEM; + + ak7375_dev->cdef = device_get_match_data(&client->dev); + + for (i = 0; i < ARRAY_SIZE(ak7375_supply_names); i++) + ak7375_dev->supplies[i].supply = ak7375_supply_names[i]; + + ret = devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(ak7375_supply_names), + ak7375_dev->supplies); + if (ret) { + dev_err_probe(&client->dev, ret, "Failed to get regulators\n"); + return ret; + } + + v4l2_i2c_subdev_init(&ak7375_dev->sd, client, &ak7375_ops); + ak7375_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ak7375_dev->sd.internal_ops = &ak7375_int_ops; + ak7375_dev->sd.entity.function = MEDIA_ENT_F_LENS; + + ret = ak7375_init_controls(ak7375_dev); + if (ret) + goto err_cleanup; + + ret = media_entity_pads_init(&ak7375_dev->sd.entity, 0, NULL); + if (ret < 0) + goto err_cleanup; + + ret = v4l2_async_register_subdev(&ak7375_dev->sd); + if (ret < 0) + goto err_cleanup; + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +err_cleanup: + v4l2_ctrl_handler_free(&ak7375_dev->ctrls_vcm); + media_entity_cleanup(&ak7375_dev->sd.entity); + + return ret; +} + +static void ak7375_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ak7375_device *ak7375_dev = sd_to_ak7375_vcm(sd); + + ak7375_subdev_cleanup(ak7375_dev); + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +/* + * This function sets the vcm position, so it consumes least current + * The lens position is gradually moved in units of ctrl_steps, + * to make the movements smoothly. + */ +static int __maybe_unused ak7375_vcm_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ak7375_device *ak7375_dev = sd_to_ak7375_vcm(sd); + const struct ak73xx_chipdef *cdef = ak7375_dev->cdef; + int ret, val; + + if (!ak7375_dev->active) + return 0; + + for (val = ak7375_dev->focus->val & ~(cdef->ctrl_steps - 1); + val >= 0; val -= cdef->ctrl_steps) { + ret = ak7375_i2c_write(ak7375_dev, cdef->reg_position, + val << cdef->shift_pos, 2); + if (ret) + dev_err_once(dev, "%s I2C failure: %d\n", + __func__, ret); + usleep_range(cdef->ctrl_delay_us, cdef->ctrl_delay_us + 10); + } + + if (cdef->has_standby) { + ret = ak7375_i2c_write(ak7375_dev, cdef->reg_cont, + cdef->mode_standby, 1); + if (ret) + dev_err(dev, "%s I2C failure: %d\n", __func__, ret); + } + + ret = regulator_bulk_disable(ARRAY_SIZE(ak7375_supply_names), + ak7375_dev->supplies); + if (ret) + return ret; + + ak7375_dev->active = false; + + return 0; +} + +/* + * This function sets the vcm position to the value set by the user + * through v4l2_ctrl_ops s_ctrl handler + * The lens position is gradually moved in units of ctrl_steps, + * to make the movements smoothly. + */ +static int __maybe_unused ak7375_vcm_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ak7375_device *ak7375_dev = sd_to_ak7375_vcm(sd); + const struct ak73xx_chipdef *cdef = ak7375_dev->cdef; + int ret, val; + + if (ak7375_dev->active) + return 0; + + ret = regulator_bulk_enable(ARRAY_SIZE(ak7375_supply_names), + ak7375_dev->supplies); + if (ret) + return ret; + + /* Wait for vcm to become ready */ + usleep_range(cdef->power_delay_us, cdef->power_delay_us + 500); + + ret = ak7375_i2c_write(ak7375_dev, cdef->reg_cont, + cdef->mode_active, 1); + if (ret) { + dev_err(dev, "%s I2C failure: %d\n", __func__, ret); + return ret; + } + + for (val = ak7375_dev->focus->val % cdef->ctrl_steps; + val <= ak7375_dev->focus->val; + val += cdef->ctrl_steps) { + ret = ak7375_i2c_write(ak7375_dev, cdef->reg_position, + val << cdef->shift_pos, 2); + if (ret) + dev_err_ratelimited(dev, "%s I2C failure: %d\n", + __func__, ret); + usleep_range(cdef->ctrl_delay_us, cdef->ctrl_delay_us + 10); + } + + ak7375_dev->active = true; + + return 0; +} + +static const struct of_device_id ak7375_of_table[] = { + { .compatible = "asahi-kasei,ak7345", .data = &ak7345_cdef, }, + { .compatible = "asahi-kasei,ak7375", .data = &ak7375_cdef, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ak7375_of_table); + +static const struct dev_pm_ops ak7375_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ak7375_vcm_suspend, ak7375_vcm_resume) + SET_RUNTIME_PM_OPS(ak7375_vcm_suspend, ak7375_vcm_resume, NULL) +}; + +static struct i2c_driver ak7375_i2c_driver = { + .driver = { + .name = "ak7375", + .pm = &ak7375_pm_ops, + .of_match_table = ak7375_of_table, + }, + .probe = ak7375_probe, + .remove = ak7375_remove, +}; +module_i2c_driver(ak7375_i2c_driver); + +MODULE_AUTHOR("Tianshu Qiu "); +MODULE_AUTHOR("Bingbu Cao "); +MODULE_DESCRIPTION("AK7375 VCM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ak881x.c b/drivers/media/i2c/ak881x.c new file mode 100644 index 000000000..cea46f019 --- /dev/null +++ b/drivers/media/i2c/ak881x.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for AK8813 / AK8814 TV-ecoders from Asahi Kasei Microsystems Co., Ltd. (AKM) + * + * Copyright (C) 2010, Guennadi Liakhovetski + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define AK881X_INTERFACE_MODE 0 +#define AK881X_VIDEO_PROCESS1 1 +#define AK881X_VIDEO_PROCESS2 2 +#define AK881X_VIDEO_PROCESS3 3 +#define AK881X_DAC_MODE 5 +#define AK881X_STATUS 0x24 +#define AK881X_DEVICE_ID 0x25 +#define AK881X_DEVICE_REVISION 0x26 + +struct ak881x { + struct v4l2_subdev subdev; + struct ak881x_pdata *pdata; + unsigned int lines; + char revision; /* DEVICE_REVISION content */ +}; + +static int reg_read(struct i2c_client *client, const u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int reg_write(struct i2c_client *client, const u8 reg, + const u8 data) +{ + return i2c_smbus_write_byte_data(client, reg, data); +} + +static int reg_set(struct i2c_client *client, const u8 reg, + const u8 data, u8 mask) +{ + int ret = reg_read(client, reg); + if (ret < 0) + return ret; + return reg_write(client, reg, (ret & ~mask) | (data & mask)); +} + +static struct ak881x *to_ak881x(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct ak881x, subdev); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ak881x_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg > 0x26) + return -EINVAL; + + reg->size = 1; + reg->val = reg_read(client, reg->reg); + + if (reg->val > 0xffff) + return -EIO; + + return 0; +} + +static int ak881x_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg > 0x26) + return -EINVAL; + + if (reg_write(client, reg->reg, reg->val) < 0) + return -EIO; + + return 0; +} +#endif + +static int ak881x_fill_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ak881x *ak881x = to_ak881x(client); + + if (format->pad) + return -EINVAL; + + v4l_bound_align_image(&mf->width, 0, 720, 2, + &mf->height, 0, ak881x->lines, 1, 0); + mf->field = V4L2_FIELD_INTERLACED; + mf->code = MEDIA_BUS_FMT_YUYV8_2X8; + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int ak881x_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_YUYV8_2X8; + return 0; +} + +static int ak881x_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ak881x *ak881x = to_ak881x(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = 720; + sel->r.height = ak881x->lines; + return 0; + default: + return -EINVAL; + } +} + +static int ak881x_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ak881x *ak881x = to_ak881x(client); + u8 vp1; + + if (std == V4L2_STD_NTSC_443) { + vp1 = 3; + ak881x->lines = 480; + } else if (std == V4L2_STD_PAL_M) { + vp1 = 5; + ak881x->lines = 480; + } else if (std == V4L2_STD_PAL_60) { + vp1 = 7; + ak881x->lines = 480; + } else if (std & V4L2_STD_NTSC) { + vp1 = 0; + ak881x->lines = 480; + } else if (std & V4L2_STD_PAL) { + vp1 = 0xf; + ak881x->lines = 576; + } else { + /* No SECAM or PAL_N/Nc supported */ + return -EINVAL; + } + + reg_set(client, AK881X_VIDEO_PROCESS1, vp1, 0xf); + + return 0; +} + +static int ak881x_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ak881x *ak881x = to_ak881x(client); + + if (enable) { + u8 dac; + /* For colour-bar testing set bit 6 of AK881X_VIDEO_PROCESS1 */ + /* Default: composite output */ + if (ak881x->pdata->flags & AK881X_COMPONENT) + dac = 3; + else + dac = 4; + /* Turn on the DAC(s) */ + reg_write(client, AK881X_DAC_MODE, dac); + dev_dbg(&client->dev, "chip status 0x%x\n", + reg_read(client, AK881X_STATUS)); + } else { + /* ...and clear bit 6 of AK881X_VIDEO_PROCESS1 here */ + reg_write(client, AK881X_DAC_MODE, 0); + dev_dbg(&client->dev, "chip status 0x%x\n", + reg_read(client, AK881X_STATUS)); + } + + return 0; +} + +static const struct v4l2_subdev_core_ops ak881x_subdev_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ak881x_g_register, + .s_register = ak881x_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops ak881x_subdev_video_ops = { + .s_std_output = ak881x_s_std_output, + .s_stream = ak881x_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ak881x_subdev_pad_ops = { + .enum_mbus_code = ak881x_enum_mbus_code, + .get_selection = ak881x_get_selection, + .set_fmt = ak881x_fill_fmt, + .get_fmt = ak881x_fill_fmt, +}; + +static const struct v4l2_subdev_ops ak881x_subdev_ops = { + .core = &ak881x_subdev_core_ops, + .video = &ak881x_subdev_video_ops, + .pad = &ak881x_subdev_pad_ops, +}; + +static int ak881x_probe(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + struct ak881x *ak881x; + u8 ifmode, data; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_warn(&adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + ak881x = devm_kzalloc(&client->dev, sizeof(*ak881x), GFP_KERNEL); + if (!ak881x) + return -ENOMEM; + + v4l2_i2c_subdev_init(&ak881x->subdev, client, &ak881x_subdev_ops); + + data = reg_read(client, AK881X_DEVICE_ID); + + switch (data) { + case 0x13: + case 0x14: + break; + default: + dev_err(&client->dev, + "No ak881x chip detected, register read %x\n", data); + return -ENODEV; + } + + ak881x->revision = reg_read(client, AK881X_DEVICE_REVISION); + ak881x->pdata = client->dev.platform_data; + + if (ak881x->pdata) { + if (ak881x->pdata->flags & AK881X_FIELD) + ifmode = 4; + else + ifmode = 0; + + switch (ak881x->pdata->flags & AK881X_IF_MODE_MASK) { + case AK881X_IF_MODE_BT656: + ifmode |= 1; + break; + case AK881X_IF_MODE_MASTER: + ifmode |= 2; + break; + case AK881X_IF_MODE_SLAVE: + default: + break; + } + + dev_dbg(&client->dev, "IF mode %x\n", ifmode); + + /* + * "Line Blanking No." seems to be the same as the number of + * "black" lines on, e.g., SuperH VOU, whose default value of 20 + * "incidentally" matches ak881x' default + */ + reg_write(client, AK881X_INTERFACE_MODE, ifmode | (20 << 3)); + } + + /* Hardware default: NTSC-M */ + ak881x->lines = 480; + + dev_info(&client->dev, "Detected an ak881x chip ID %x, revision %x\n", + data, ak881x->revision); + + return 0; +} + +static void ak881x_remove(struct i2c_client *client) +{ + struct ak881x *ak881x = to_ak881x(client); + + v4l2_device_unregister_subdev(&ak881x->subdev); +} + +static const struct i2c_device_id ak881x_id[] = { + { .name = "ak8813" }, + { .name = "ak8814" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak881x_id); + +static struct i2c_driver ak881x_i2c_driver = { + .driver = { + .name = "ak881x", + }, + .probe = ak881x_probe, + .remove = ak881x_remove, + .id_table = ak881x_id, +}; + +module_i2c_driver(ak881x_i2c_driver); + +MODULE_DESCRIPTION("TV-output driver for ak8813/ak8814"); +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/alvium-csi2.c b/drivers/media/i2c/alvium-csi2.c new file mode 100644 index 000000000..f51f9b987 --- /dev/null +++ b/drivers/media/i2c/alvium-csi2.c @@ -0,0 +1,2553 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Allied Vision Technologies GmbH Alvium camera driver + * + * Copyright (C) 2023 Tommaso Merciai + * Copyright (C) 2023 Martin Hecht + * Copyright (C) 2023 Avnet EMG GmbH + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alvium-csi2.h" + +static const struct v4l2_mbus_framefmt alvium_csi2_default_fmt = { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .width = 640, + .height = 480, + .colorspace = V4L2_COLORSPACE_SRGB, + .ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SRGB), + .quantization = V4L2_QUANTIZATION_FULL_RANGE, + .xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SRGB), + .field = V4L2_FIELD_NONE, +}; + +static const struct alvium_pixfmt alvium_csi2_fmts[] = { + { + /* UYVY8_2X8 */ + .id = ALVIUM_FMT_UYVY8_2X8, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt_av_bit = ALVIUM_BIT_YUV422_8, + .bay_av_bit = ALVIUM_BIT_BAY_NONE, + .mipi_fmt_regval = MIPI_CSI2_DT_YUV422_8B, + .bay_fmt_regval = -1, + .is_raw = 0, + }, { + /* UYVY8_1X16 */ + .id = ALVIUM_FMT_UYVY8_1X16, + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt_av_bit = ALVIUM_BIT_YUV422_8, + .bay_av_bit = ALVIUM_BIT_BAY_NONE, + .mipi_fmt_regval = MIPI_CSI2_DT_YUV422_8B, + .bay_fmt_regval = -1, + .is_raw = 0, + }, { + /* YUYV8_1X16 */ + .id = ALVIUM_FMT_YUYV8_1X16, + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt_av_bit = ALVIUM_BIT_YUV422_8, + .bay_av_bit = ALVIUM_BIT_BAY_NONE, + .mipi_fmt_regval = MIPI_CSI2_DT_YUV422_8B, + .bay_fmt_regval = -1, + .is_raw = 0, + }, { + /* YUYV8_2X8 */ + .id = ALVIUM_FMT_YUYV8_2X8, + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt_av_bit = ALVIUM_BIT_YUV422_8, + .bay_av_bit = ALVIUM_BIT_BAY_NONE, + .mipi_fmt_regval = MIPI_CSI2_DT_YUV422_8B, + .bay_fmt_regval = -1, + .is_raw = 0, + }, { + /* YUYV10_1X20 */ + .id = ALVIUM_FMT_YUYV10_1X20, + .code = MEDIA_BUS_FMT_YUYV10_1X20, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt_av_bit = ALVIUM_BIT_YUV422_10, + .bay_av_bit = ALVIUM_BIT_BAY_NONE, + .mipi_fmt_regval = MIPI_CSI2_DT_YUV422_10B, + .bay_fmt_regval = -1, + .is_raw = 0, + }, { + /* RGB888_1X24 */ + .id = ALVIUM_FMT_RGB888_1X24, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt_av_bit = ALVIUM_BIT_RGB888, + .bay_av_bit = ALVIUM_BIT_BAY_NONE, + .mipi_fmt_regval = MIPI_CSI2_DT_RGB888, + .bay_fmt_regval = -1, + .is_raw = 0, + }, { + /* RBG888_1X24 */ + .id = ALVIUM_FMT_RBG888_1X24, + .code = MEDIA_BUS_FMT_RBG888_1X24, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt_av_bit = ALVIUM_BIT_RGB888, + .bay_av_bit = ALVIUM_BIT_BAY_NONE, + .mipi_fmt_regval = MIPI_CSI2_DT_RGB888, + .bay_fmt_regval = -1, + .is_raw = 0, + }, { + /* BGR888_1X24 */ + .id = ALVIUM_FMT_BGR888_1X24, + .code = MEDIA_BUS_FMT_BGR888_1X24, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt_av_bit = ALVIUM_BIT_RGB888, + .bay_av_bit = ALVIUM_BIT_BAY_NONE, + .mipi_fmt_regval = MIPI_CSI2_DT_RGB888, + .bay_fmt_regval = -1, + .is_raw = 0, + }, { + /* RGB888_3X8 */ + .id = ALVIUM_FMT_RGB888_3X8, + .code = MEDIA_BUS_FMT_RGB888_3X8, + .colorspace = V4L2_COLORSPACE_SRGB, + .fmt_av_bit = ALVIUM_BIT_RGB888, + .bay_av_bit = ALVIUM_BIT_BAY_NONE, + .mipi_fmt_regval = MIPI_CSI2_DT_RGB888, + .bay_fmt_regval = -1, + .is_raw = 0, + }, { + /* Y8_1X8 */ + .id = ALVIUM_FMT_Y8_1X8, + .code = MEDIA_BUS_FMT_Y8_1X8, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW8, + .bay_av_bit = ALVIUM_BIT_BAY_MONO, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW8, + .bay_fmt_regval = 0x00, + .is_raw = 1, + }, { + /* SGRBG8_1X8 */ + .id = ALVIUM_FMT_SGRBG8_1X8, + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW8, + .bay_av_bit = ALVIUM_BIT_BAY_GR, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW8, + .bay_fmt_regval = 0x01, + .is_raw = 1, + }, { + /* SRGGB8_1X8 */ + .id = ALVIUM_FMT_SRGGB8_1X8, + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW8, + .bay_av_bit = ALVIUM_BIT_BAY_RG, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW8, + .bay_fmt_regval = 0x02, + .is_raw = 1, + }, { + /* SGBRG8_1X8 */ + .id = ALVIUM_FMT_SGBRG8_1X8, + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW8, + .bay_av_bit = ALVIUM_BIT_BAY_GB, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW8, + .bay_fmt_regval = 0x03, + .is_raw = 1, + }, { + /* SBGGR8_1X8 */ + .id = ALVIUM_FMT_SBGGR8_1X8, + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW8, + .bay_av_bit = ALVIUM_BIT_BAY_BG, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW8, + .bay_fmt_regval = 0x04, + .is_raw = 1, + }, { + /* Y10_1X10 */ + .id = ALVIUM_FMT_Y10_1X10, + .code = MEDIA_BUS_FMT_Y10_1X10, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW10, + .bay_av_bit = ALVIUM_BIT_BAY_MONO, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW10, + .bay_fmt_regval = 0x00, + .is_raw = 1, + }, { + /* SGRBG10_1X10 */ + .id = ALVIUM_FMT_SGRBG10_1X10, + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW10, + .bay_av_bit = ALVIUM_BIT_BAY_GR, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW10, + .bay_fmt_regval = 0x01, + .is_raw = 1, + }, { + /* SRGGB10_1X10 */ + .id = ALVIUM_FMT_SRGGB10_1X10, + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW10, + .bay_av_bit = ALVIUM_BIT_BAY_RG, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW10, + .bay_fmt_regval = 0x02, + .is_raw = 1, + }, { + /* SGBRG10_1X10 */ + .id = ALVIUM_FMT_SGBRG10_1X10, + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW10, + .bay_av_bit = ALVIUM_BIT_BAY_GB, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW10, + .bay_fmt_regval = 0x03, + .is_raw = 1, + }, { + /* SBGGR10_1X10 */ + .id = ALVIUM_FMT_SBGGR10_1X10, + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW10, + .bay_av_bit = ALVIUM_BIT_BAY_BG, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW10, + .bay_fmt_regval = 0x04, + .is_raw = 1, + }, { + /* Y12_1X12 */ + .id = ALVIUM_FMT_Y12_1X12, + .code = MEDIA_BUS_FMT_Y12_1X12, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW12, + .bay_av_bit = ALVIUM_BIT_BAY_MONO, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW12, + .bay_fmt_regval = 0x00, + .is_raw = 1, + }, { + /* SGRBG12_1X12 */ + .id = ALVIUM_FMT_SGRBG12_1X12, + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW12, + .bay_av_bit = ALVIUM_BIT_BAY_GR, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW12, + .bay_fmt_regval = 0x01, + .is_raw = 1, + }, { + /* SRGGB12_1X12 */ + .id = ALVIUM_FMT_SRGGB12_1X12, + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW12, + .bay_av_bit = ALVIUM_BIT_BAY_RG, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW12, + .bay_fmt_regval = 0x02, + .is_raw = 1, + }, { + /* SGBRG12_1X12 */ + .id = ALVIUM_FMT_SGBRG12_1X12, + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW12, + .bay_av_bit = ALVIUM_BIT_BAY_GB, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW12, + .bay_fmt_regval = 0x03, + .is_raw = 1, + }, { + /* SBGGR12_1X12 */ + .id = ALVIUM_FMT_SBGGR12_1X12, + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW12, + .bay_av_bit = ALVIUM_BIT_BAY_BG, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW12, + .bay_fmt_regval = 0x04, + .is_raw = 1, + }, { + /* SBGGR14_1X14 */ + .id = ALVIUM_FMT_SBGGR14_1X14, + .code = MEDIA_BUS_FMT_SBGGR14_1X14, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW14, + .bay_av_bit = ALVIUM_BIT_BAY_GR, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW14, + .bay_fmt_regval = 0x01, + .is_raw = 1, + }, { + /* SGBRG14_1X14 */ + .id = ALVIUM_FMT_SGBRG14_1X14, + .code = MEDIA_BUS_FMT_SGBRG14_1X14, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW14, + .bay_av_bit = ALVIUM_BIT_BAY_RG, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW14, + .bay_fmt_regval = 0x02, + .is_raw = 1, + }, { + /* SRGGB14_1X14 */ + .id = ALVIUM_FMT_SRGGB14_1X14, + .code = MEDIA_BUS_FMT_SRGGB14_1X14, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW14, + .bay_av_bit = ALVIUM_BIT_BAY_GB, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW14, + .bay_fmt_regval = 0x03, + .is_raw = 1, + }, { + /* SGRBG14_1X14 */ + .id = ALVIUM_FMT_SGRBG14_1X14, + .code = MEDIA_BUS_FMT_SGRBG14_1X14, + .colorspace = V4L2_COLORSPACE_RAW, + .fmt_av_bit = ALVIUM_BIT_RAW14, + .bay_av_bit = ALVIUM_BIT_BAY_BG, + .mipi_fmt_regval = MIPI_CSI2_DT_RAW14, + .bay_fmt_regval = 0x04, + .is_raw = 1, + }, + { /* sentinel */ } +}; + +static int alvium_read(struct alvium_dev *alvium, u32 reg, u64 *val, int *err) +{ + if (reg & REG_BCRM_V4L2) { + reg &= ~REG_BCRM_V4L2; + reg += alvium->bcrm_addr; + } + + return cci_read(alvium->regmap, reg, val, err); +} + +static int alvium_write(struct alvium_dev *alvium, u32 reg, u64 val, int *err) +{ + if (reg & REG_BCRM_V4L2) { + reg &= ~REG_BCRM_V4L2; + reg += alvium->bcrm_addr; + } + + return cci_write(alvium->regmap, reg, val, err); +} + +static int alvium_write_hshake(struct alvium_dev *alvium, u32 reg, u64 val) +{ + struct device *dev = &alvium->i2c_client->dev; + u64 hshake_bit; + int ret = 0; + + /* reset handshake bit and write alvium reg */ + alvium_write(alvium, REG_BCRM_WRITE_HANDSHAKE_RW, 0, &ret); + alvium_write(alvium, reg, val, &ret); + if (ret) { + dev_err(dev, "Fail to write reg\n"); + return ret; + } + + /* poll handshake bit since bit0 = 1 */ + read_poll_timeout(alvium_read, hshake_bit, + ((hshake_bit & BCRM_HANDSHAKE_W_DONE_EN_BIT) == 1), + 15000, 45000, true, + alvium, REG_BCRM_WRITE_HANDSHAKE_RW, + &hshake_bit, &ret); + if (ret) { + dev_err(dev, "poll bit[0] = 1, hshake reg fail\n"); + return ret; + } + + /* reset handshake bit, write 0 to bit0 */ + alvium_write(alvium, REG_BCRM_WRITE_HANDSHAKE_RW, 0, &ret); + if (ret) { + dev_err(dev, "Fail to reset hshake reg\n"); + return ret; + } + + /* poll handshake bit since bit0 = 0 */ + read_poll_timeout(alvium_read, hshake_bit, + ((hshake_bit & BCRM_HANDSHAKE_W_DONE_EN_BIT) == 0), + 15000, 45000, true, + alvium, REG_BCRM_WRITE_HANDSHAKE_RW, + &hshake_bit, &ret); + if (ret) { + dev_err(dev, "poll bit[0] = 0, hshake reg fail\n"); + return ret; + } + + return 0; +} + +static int alvium_get_bcrm_vers(struct alvium_dev *alvium) +{ + struct device *dev = &alvium->i2c_client->dev; + u64 min, maj; + int ret = 0; + + ret = alvium_read(alvium, REG_BCRM_MINOR_VERSION_R, &min, &ret); + ret = alvium_read(alvium, REG_BCRM_MAJOR_VERSION_R, &maj, &ret); + if (ret) + return ret; + + dev_info(dev, "bcrm version: %llu.%llu\n", min, maj); + + return 0; +} + +static int alvium_get_fw_version(struct alvium_dev *alvium) +{ + struct device *dev = &alvium->i2c_client->dev; + u64 val; + int ret; + + ret = alvium_read(alvium, REG_BCRM_DEVICE_FW, &val, NULL); + if (ret) + return ret; + + dev_info(dev, "fw version: %02u.%02u.%04u.%08x\n", + (u8)((val & BCRM_DEVICE_FW_SPEC_MASK) >> + BCRM_DEVICE_FW_SPEC_SHIFT), + (u8)((val & BCRM_DEVICE_FW_MAJOR_MASK) >> + BCRM_DEVICE_FW_MAJOR_SHIFT), + (u16)((val & BCRM_DEVICE_FW_MINOR_MASK) >> + BCRM_DEVICE_FW_MINOR_SHIFT), + (u32)((val & BCRM_DEVICE_FW_PATCH_MASK) >> + BCRM_DEVICE_FW_PATCH_SHIFT)); + + return 0; +} + +static int alvium_get_bcrm_addr(struct alvium_dev *alvium) +{ + u64 val; + int ret; + + ret = alvium_read(alvium, REG_BCRM_REG_ADDR_R, &val, NULL); + if (ret) + return ret; + + alvium->bcrm_addr = val; + + return 0; +} + +static int alvium_is_alive(struct alvium_dev *alvium) +{ + u64 bcrm, hbeat; + int ret = 0; + + alvium_read(alvium, REG_BCRM_MINOR_VERSION_R, &bcrm, &ret); + alvium_read(alvium, REG_BCRM_HEARTBEAT_RW, &hbeat, &ret); + if (ret) + return ret; + + return hbeat; +} + +static void alvium_print_avail_mipi_fmt(struct alvium_dev *alvium) +{ + struct device *dev = &alvium->i2c_client->dev; + + dev_dbg(dev, "avail mipi_fmt yuv420_8_leg: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_YUV420_8_LEG]); + dev_dbg(dev, "avail mipi_fmt yuv420_8: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_YUV420_8]); + dev_dbg(dev, "avail mipi_fmt yuv420_10: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_YUV420_10]); + dev_dbg(dev, "avail mipi_fmt yuv420_8_csps: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_YUV420_8_CSPS]); + dev_dbg(dev, "avail mipi_fmt yuv420_10_csps: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_YUV420_10_CSPS]); + dev_dbg(dev, "avail mipi_fmt yuv422_8: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_YUV422_8]); + dev_dbg(dev, "avail mipi_fmt yuv422_10: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_YUV422_10]); + dev_dbg(dev, "avail mipi_fmt rgb888: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RGB888]); + dev_dbg(dev, "avail mipi_fmt rgb666: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RGB666]); + dev_dbg(dev, "avail mipi_fmt rgb565: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RGB565]); + dev_dbg(dev, "avail mipi_fmt rgb555: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RGB555]); + dev_dbg(dev, "avail mipi_fmt rgb444: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RGB444]); + dev_dbg(dev, "avail mipi_fmt raw6: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RAW6]); + dev_dbg(dev, "avail mipi_fmt raw7: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RAW7]); + dev_dbg(dev, "avail mipi_fmt raw8: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RAW8]); + dev_dbg(dev, "avail mipi_fmt raw10: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RAW10]); + dev_dbg(dev, "avail mipi_fmt raw12: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RAW12]); + dev_dbg(dev, "avail mipi_fmt raw14: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RAW14]); + dev_dbg(dev, "avail mipi_fmt jpeg: %u\n", + alvium->is_mipi_fmt_avail[ALVIUM_BIT_JPEG]); +} + +static void alvium_print_avail_feat(struct alvium_dev *alvium) +{ + struct device *dev = &alvium->i2c_client->dev; + + dev_dbg(dev, "feature rev_x: %u\n", alvium->avail_ft.rev_x); + dev_dbg(dev, "feature rev_y: %u\n", alvium->avail_ft.rev_y); + dev_dbg(dev, "feature int_autop: %u\n", alvium->avail_ft.int_autop); + dev_dbg(dev, "feature black_lvl: %u\n", alvium->avail_ft.black_lvl); + dev_dbg(dev, "feature gain: %u\n", alvium->avail_ft.gain); + dev_dbg(dev, "feature gamma: %u\n", alvium->avail_ft.gamma); + dev_dbg(dev, "feature contrast: %u\n", alvium->avail_ft.contrast); + dev_dbg(dev, "feature sat: %u\n", alvium->avail_ft.sat); + dev_dbg(dev, "feature hue: %u\n", alvium->avail_ft.hue); + dev_dbg(dev, "feature whiteb: %u\n", alvium->avail_ft.whiteb); + dev_dbg(dev, "feature sharp: %u\n", alvium->avail_ft.sharp); + dev_dbg(dev, "feature auto_exp: %u\n", alvium->avail_ft.auto_exp); + dev_dbg(dev, "feature auto_gain: %u\n", alvium->avail_ft.auto_gain); + dev_dbg(dev, "feature auto_whiteb: %u\n", alvium->avail_ft.auto_whiteb); + dev_dbg(dev, "feature dev_temp: %u\n", alvium->avail_ft.dev_temp); + dev_dbg(dev, "feature acq_abort: %u\n", alvium->avail_ft.acq_abort); + dev_dbg(dev, "feature acq_fr: %u\n", alvium->avail_ft.acq_fr); + dev_dbg(dev, "feature fr_trigger: %u\n", alvium->avail_ft.fr_trigger); + dev_dbg(dev, "feature exp_acq_line: %u\n", + alvium->avail_ft.exp_acq_line); +} + +static void alvium_print_avail_bayer(struct alvium_dev *alvium) +{ + struct device *dev = &alvium->i2c_client->dev; + + dev_dbg(dev, "avail bayer mono: %u\n", + alvium->is_bay_avail[ALVIUM_BIT_BAY_MONO]); + dev_dbg(dev, "avail bayer gr: %u\n", + alvium->is_bay_avail[ALVIUM_BIT_BAY_GR]); + dev_dbg(dev, "avail bayer rg: %u\n", + alvium->is_bay_avail[ALVIUM_BIT_BAY_RG]); + dev_dbg(dev, "avail bayer gb: %u\n", + alvium->is_bay_avail[ALVIUM_BIT_BAY_GB]); + dev_dbg(dev, "avail bayer bg: %u\n", + alvium->is_bay_avail[ALVIUM_BIT_BAY_BG]); +} + +static int alvium_get_feat_inq(struct alvium_dev *alvium) +{ + struct alvium_avail_feat *f; + u64 val; + int ret; + + ret = alvium_read(alvium, REG_BCRM_FEATURE_INQUIRY_R, &val, NULL); + if (ret) + return ret; + + f = (struct alvium_avail_feat *)&val; + alvium->avail_ft = *f; + alvium_print_avail_feat(alvium); + + return 0; +} + +static int alvium_get_host_supp_csi_lanes(struct alvium_dev *alvium) +{ + u64 val; + int ret; + + ret = alvium_read(alvium, REG_BCRM_CSI2_LANE_COUNT_RW, &val, NULL); + if (ret) + return ret; + + alvium->h_sup_csi_lanes = val; + + return 0; +} + +static int alvium_set_csi_lanes(struct alvium_dev *alvium) +{ + struct device *dev = &alvium->i2c_client->dev; + u64 num_lanes; + int ret; + + num_lanes = alvium->ep.bus.mipi_csi2.num_data_lanes; + + if (num_lanes > alvium->h_sup_csi_lanes) + return -EINVAL; + + ret = alvium_write_hshake(alvium, REG_BCRM_CSI2_LANE_COUNT_RW, + num_lanes); + if (ret) { + dev_err(dev, "Fail to set csi lanes reg\n"); + return ret; + } + + return 0; +} + +static int alvium_set_lp2hs_delay(struct alvium_dev *alvium) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret = 0; + + /* + * The purpose of this reg is force a DPhy reset + * for the period described by the millisecond on + * the reg, before it starts streaming. + * + * To be clear, with that value bigger than 0 the + * Alvium forces a dphy-reset on all lanes for that period. + * That means all lanes go up into low power state. + * + */ + alvium_write(alvium, REG_BCRM_LP2HS_DELAY_RW, + ALVIUM_LP2HS_DELAY_MS, &ret); + if (ret) { + dev_err(dev, "Fail to set lp2hs delay reg\n"); + return ret; + } + + return 0; +} + +static int alvium_get_csi_clk_params(struct alvium_dev *alvium) +{ + u64 min_csi_clk, max_csi_clk; + int ret = 0; + + alvium_read(alvium, REG_BCRM_CSI2_CLOCK_MIN_R, &min_csi_clk, &ret); + alvium_read(alvium, REG_BCRM_CSI2_CLOCK_MAX_R, &max_csi_clk, &ret); + if (ret) + return ret; + + alvium->min_csi_clk = min_csi_clk; + alvium->max_csi_clk = max_csi_clk; + + return 0; +} + +static int alvium_set_csi_clk(struct alvium_dev *alvium) +{ + struct device *dev = &alvium->i2c_client->dev; + u64 csi_clk; + int ret; + + csi_clk = clamp(alvium->ep.link_frequencies[0], + (u64)alvium->min_csi_clk, (u64)alvium->max_csi_clk); + + if (alvium->ep.link_frequencies[0] != (u64)csi_clk) { + dev_warn(dev, + "requested csi clock (%llu MHz) out of range [%u, %u] Adjusted to %llu\n", + alvium->ep.link_frequencies[0], + alvium->min_csi_clk, alvium->max_csi_clk, csi_clk); + } + + ret = alvium_write_hshake(alvium, REG_BCRM_CSI2_CLOCK_RW, csi_clk); + if (ret) { + dev_err(dev, "Fail to set csi clock reg\n"); + return ret; + } + + alvium->link_freq = csi_clk; + + return 0; +} + +static int alvium_get_img_width_params(struct alvium_dev *alvium) +{ + u64 imgw, imgw_min, imgw_max, imgw_inc; + int ret = 0; + + alvium_read(alvium, REG_BCRM_IMG_WIDTH_RW, &imgw, &ret); + alvium_read(alvium, REG_BCRM_IMG_WIDTH_MIN_R, &imgw_min, &ret); + alvium_read(alvium, REG_BCRM_IMG_WIDTH_MAX_R, &imgw_max, &ret); + alvium_read(alvium, REG_BCRM_IMG_WIDTH_INC_R, &imgw_inc, &ret); + if (ret) + return ret; + + alvium->dft_img_width = imgw; + alvium->img_min_width = imgw_min; + alvium->img_max_width = imgw_max; + alvium->img_inc_width = imgw_inc; + + return 0; +} + +static int alvium_get_img_height_params(struct alvium_dev *alvium) +{ + u64 imgh, imgh_min, imgh_max, imgh_inc; + int ret = 0; + + alvium_read(alvium, REG_BCRM_IMG_HEIGHT_RW, &imgh, &ret); + alvium_read(alvium, REG_BCRM_IMG_HEIGHT_MIN_R, &imgh_min, &ret); + alvium_read(alvium, REG_BCRM_IMG_HEIGHT_MAX_R, &imgh_max, &ret); + alvium_read(alvium, REG_BCRM_IMG_HEIGHT_INC_R, &imgh_inc, &ret); + if (ret) + return ret; + + alvium->dft_img_height = imgh; + alvium->img_min_height = imgh_min; + alvium->img_max_height = imgh_max; + alvium->img_inc_height = imgh_inc; + + return 0; +} + +static int alvium_set_img_width(struct alvium_dev *alvium, u32 width) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_IMG_WIDTH_RW, width); + if (ret) { + dev_err(dev, "Fail to set img width\n"); + return ret; + } + + return 0; +} + +static int alvium_set_img_height(struct alvium_dev *alvium, u32 height) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_IMG_HEIGHT_RW, height); + if (ret) { + dev_err(dev, "Fail to set img height\n"); + return ret; + } + + return 0; +} + +static int alvium_set_img_offx(struct alvium_dev *alvium, u32 offx) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_IMG_OFFSET_X_RW, offx); + if (ret) { + dev_err(dev, "Fail to set img offx\n"); + return ret; + } + + return 0; +} + +static int alvium_set_img_offy(struct alvium_dev *alvium, u32 offy) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_IMG_OFFSET_Y_RW, offy); + if (ret) { + dev_err(dev, "Fail to set img offy\n"); + return ret; + } + + return 0; +} + +static int alvium_get_offx_params(struct alvium_dev *alvium) +{ + u64 min_offx, max_offx, inc_offx; + int ret = 0; + + alvium_read(alvium, REG_BCRM_IMG_OFFSET_X_MIN_R, &min_offx, &ret); + alvium_read(alvium, REG_BCRM_IMG_OFFSET_X_MAX_R, &max_offx, &ret); + alvium_read(alvium, REG_BCRM_IMG_OFFSET_X_INC_R, &inc_offx, &ret); + if (ret) + return ret; + + alvium->min_offx = min_offx; + alvium->max_offx = max_offx; + alvium->inc_offx = inc_offx; + + return 0; +} + +static int alvium_get_offy_params(struct alvium_dev *alvium) +{ + u64 min_offy, max_offy, inc_offy; + int ret = 0; + + alvium_read(alvium, REG_BCRM_IMG_OFFSET_Y_MIN_R, &min_offy, &ret); + alvium_read(alvium, REG_BCRM_IMG_OFFSET_Y_MAX_R, &max_offy, &ret); + alvium_read(alvium, REG_BCRM_IMG_OFFSET_Y_INC_R, &inc_offy, &ret); + if (ret) + return ret; + + alvium->min_offy = min_offy; + alvium->max_offy = max_offy; + alvium->inc_offy = inc_offy; + + return 0; +} + +static int alvium_get_gain_params(struct alvium_dev *alvium) +{ + u64 dft_gain, min_gain, max_gain, inc_gain; + int ret = 0; + + alvium_read(alvium, REG_BCRM_GAIN_RW, &dft_gain, &ret); + alvium_read(alvium, REG_BCRM_GAIN_MIN_R, &min_gain, &ret); + alvium_read(alvium, REG_BCRM_GAIN_MAX_R, &max_gain, &ret); + alvium_read(alvium, REG_BCRM_GAIN_INC_R, &inc_gain, &ret); + if (ret) + return ret; + + alvium->dft_gain = dft_gain; + alvium->min_gain = min_gain; + alvium->max_gain = max_gain; + alvium->inc_gain = inc_gain; + + return 0; +} + +static int alvium_get_exposure_params(struct alvium_dev *alvium) +{ + u64 dft_exp, min_exp, max_exp, inc_exp; + int ret = 0; + + alvium_read(alvium, REG_BCRM_EXPOSURE_TIME_RW, &dft_exp, &ret); + alvium_read(alvium, REG_BCRM_EXPOSURE_TIME_MIN_R, &min_exp, &ret); + alvium_read(alvium, REG_BCRM_EXPOSURE_TIME_MAX_R, &max_exp, &ret); + alvium_read(alvium, REG_BCRM_EXPOSURE_TIME_INC_R, &inc_exp, &ret); + if (ret) + return ret; + + alvium->dft_exp = dft_exp; + alvium->min_exp = min_exp; + alvium->max_exp = max_exp; + alvium->inc_exp = inc_exp; + + return 0; +} + +static int alvium_get_red_balance_ratio_params(struct alvium_dev *alvium) +{ + u64 dft_rb, min_rb, max_rb, inc_rb; + int ret = 0; + + alvium_read(alvium, REG_BCRM_RED_BALANCE_RATIO_RW, &dft_rb, &ret); + alvium_read(alvium, REG_BCRM_RED_BALANCE_RATIO_MIN_R, &min_rb, &ret); + alvium_read(alvium, REG_BCRM_RED_BALANCE_RATIO_MAX_R, &max_rb, &ret); + alvium_read(alvium, REG_BCRM_RED_BALANCE_RATIO_INC_R, &inc_rb, &ret); + if (ret) + return ret; + + alvium->dft_rbalance = dft_rb; + alvium->min_rbalance = min_rb; + alvium->max_rbalance = max_rb; + alvium->inc_rbalance = inc_rb; + + return 0; +} + +static int alvium_get_blue_balance_ratio_params(struct alvium_dev *alvium) +{ + u64 dft_bb, min_bb, max_bb, inc_bb; + int ret = 0; + + alvium_read(alvium, REG_BCRM_BLUE_BALANCE_RATIO_RW, &dft_bb, &ret); + alvium_read(alvium, REG_BCRM_BLUE_BALANCE_RATIO_MIN_R, &min_bb, &ret); + alvium_read(alvium, REG_BCRM_BLUE_BALANCE_RATIO_MAX_R, &max_bb, &ret); + alvium_read(alvium, REG_BCRM_BLUE_BALANCE_RATIO_INC_R, &inc_bb, &ret); + if (ret) + return ret; + + alvium->dft_bbalance = dft_bb; + alvium->min_bbalance = min_bb; + alvium->max_bbalance = max_bb; + alvium->inc_bbalance = inc_bb; + + return 0; +} + +static int alvium_get_hue_params(struct alvium_dev *alvium) +{ + u64 dft_hue, min_hue, max_hue, inc_hue; + int ret = 0; + + alvium_read(alvium, REG_BCRM_HUE_RW, &dft_hue, &ret); + alvium_read(alvium, REG_BCRM_HUE_MIN_R, &min_hue, &ret); + alvium_read(alvium, REG_BCRM_HUE_MAX_R, &max_hue, &ret); + alvium_read(alvium, REG_BCRM_HUE_INC_R, &inc_hue, &ret); + if (ret) + return ret; + + alvium->dft_hue = (s32)dft_hue; + alvium->min_hue = (s32)min_hue; + alvium->max_hue = (s32)max_hue; + alvium->inc_hue = (s32)inc_hue; + + return 0; +} + +static int alvium_get_black_lvl_params(struct alvium_dev *alvium) +{ + u64 dft_blvl, min_blvl, max_blvl, inc_blvl; + int ret = 0; + + alvium_read(alvium, REG_BCRM_BLACK_LEVEL_RW, &dft_blvl, &ret); + alvium_read(alvium, REG_BCRM_BLACK_LEVEL_MIN_R, &min_blvl, &ret); + alvium_read(alvium, REG_BCRM_BLACK_LEVEL_MAX_R, &max_blvl, &ret); + alvium_read(alvium, REG_BCRM_BLACK_LEVEL_INC_R, &inc_blvl, &ret); + if (ret) + return ret; + + alvium->dft_black_lvl = (s32)dft_blvl; + alvium->min_black_lvl = (s32)min_blvl; + alvium->max_black_lvl = (s32)max_blvl; + alvium->inc_black_lvl = (s32)inc_blvl; + + return 0; +} + +static int alvium_get_gamma_params(struct alvium_dev *alvium) +{ + u64 dft_g, min_g, max_g, inc_g; + int ret = 0; + + alvium_read(alvium, REG_BCRM_GAMMA_RW, &dft_g, &ret); + alvium_read(alvium, REG_BCRM_GAMMA_MIN_R, &min_g, &ret); + alvium_read(alvium, REG_BCRM_GAMMA_MAX_R, &max_g, &ret); + alvium_read(alvium, REG_BCRM_GAMMA_INC_R, &inc_g, &ret); + if (ret) + return ret; + + alvium->dft_gamma = dft_g; + alvium->min_gamma = min_g; + alvium->max_gamma = max_g; + alvium->inc_gamma = inc_g; + + return 0; +} + +static int alvium_get_sharpness_params(struct alvium_dev *alvium) +{ + u64 dft_sh, min_sh, max_sh, inc_sh; + int ret = 0; + + alvium_read(alvium, REG_BCRM_SHARPNESS_RW, &dft_sh, &ret); + alvium_read(alvium, REG_BCRM_SHARPNESS_MIN_R, &min_sh, &ret); + alvium_read(alvium, REG_BCRM_BLACK_LEVEL_MAX_R, &max_sh, &ret); + alvium_read(alvium, REG_BCRM_SHARPNESS_INC_R, &inc_sh, &ret); + if (ret) + return ret; + + alvium->dft_sharp = (s32)dft_sh; + alvium->min_sharp = (s32)min_sh; + alvium->max_sharp = (s32)max_sh; + alvium->inc_sharp = (s32)inc_sh; + + return 0; +} + +static int alvium_get_contrast_params(struct alvium_dev *alvium) +{ + u64 dft_c, min_c, max_c, inc_c; + int ret = 0; + + alvium_read(alvium, REG_BCRM_CONTRAST_VALUE_RW, &dft_c, &ret); + alvium_read(alvium, REG_BCRM_CONTRAST_VALUE_MIN_R, &min_c, &ret); + alvium_read(alvium, REG_BCRM_CONTRAST_VALUE_MAX_R, &max_c, &ret); + alvium_read(alvium, REG_BCRM_CONTRAST_VALUE_INC_R, &inc_c, &ret); + if (ret) + return ret; + + alvium->dft_contrast = dft_c; + alvium->min_contrast = min_c; + alvium->max_contrast = max_c; + alvium->inc_contrast = inc_c; + + return 0; +} + +static int alvium_get_saturation_params(struct alvium_dev *alvium) +{ + u64 dft_sat, min_sat, max_sat, inc_sat; + int ret = 0; + + alvium_read(alvium, REG_BCRM_SATURATION_RW, &dft_sat, &ret); + alvium_read(alvium, REG_BCRM_SATURATION_MIN_R, &min_sat, &ret); + alvium_read(alvium, REG_BCRM_SATURATION_MAX_R, &max_sat, &ret); + alvium_read(alvium, REG_BCRM_SATURATION_INC_R, &inc_sat, &ret); + if (ret) + return ret; + + alvium->dft_sat = dft_sat; + alvium->min_sat = min_sat; + alvium->max_sat = max_sat; + alvium->inc_sat = inc_sat; + + return 0; +} + +static int alvium_set_bcm_mode(struct alvium_dev *alvium) +{ + int ret = 0; + + alvium_write(alvium, REG_GENCP_CHANGEMODE_W, ALVIUM_BCM_MODE, &ret); + alvium->bcrm_mode = ALVIUM_BCM_MODE; + + return ret; +} + +static int alvium_get_mode(struct alvium_dev *alvium) +{ + u64 bcrm_mode; + int ret; + + ret = alvium_read(alvium, REG_GENCP_CURRENTMODE_R, &bcrm_mode, NULL); + if (ret) + return ret; + + switch (bcrm_mode) { + case ALVIUM_BCM_MODE: + alvium->bcrm_mode = ALVIUM_BCM_MODE; + break; + case ALVIUM_GENCP_MODE: + alvium->bcrm_mode = ALVIUM_GENCP_MODE; + break; + } + + return 0; +} + +static int alvium_get_avail_mipi_data_format(struct alvium_dev *alvium) +{ + struct alvium_avail_mipi_fmt *avail_fmt; + u64 val; + int ret; + + ret = alvium_read(alvium, REG_BCRM_IMG_AVAILABLE_MIPI_DATA_FORMATS_R, + &val, NULL); + if (ret) + return ret; + + avail_fmt = (struct alvium_avail_mipi_fmt *)&val; + + alvium->is_mipi_fmt_avail[ALVIUM_BIT_YUV420_8_LEG] = + avail_fmt->yuv420_8_leg; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_YUV420_8] = + avail_fmt->yuv420_8; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_YUV420_10] = + avail_fmt->yuv420_10; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_YUV420_8_CSPS] = + avail_fmt->yuv420_8_csps; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_YUV420_10_CSPS] = + avail_fmt->yuv420_10_csps; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_YUV422_8] = + avail_fmt->yuv422_8; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_YUV422_10] = + avail_fmt->yuv422_10; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RGB888] = + avail_fmt->rgb888; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RGB666] = + avail_fmt->rgb666; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RGB565] = + avail_fmt->rgb565; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RGB555] = + avail_fmt->rgb555; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RGB444] = + avail_fmt->rgb444; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RAW6] = + avail_fmt->raw6; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RAW7] = + avail_fmt->raw7; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RAW8] = + avail_fmt->raw8; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RAW10] = + avail_fmt->raw10; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RAW12] = + avail_fmt->raw12; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_RAW14] = + avail_fmt->raw14; + alvium->is_mipi_fmt_avail[ALVIUM_BIT_JPEG] = + avail_fmt->jpeg; + + alvium_print_avail_mipi_fmt(alvium); + + return 0; +} + +static int alvium_setup_mipi_fmt(struct alvium_dev *alvium) +{ + unsigned int avail_fmt_cnt = 0; + unsigned int fmt = 0; + size_t sz = 0; + + /* calculate fmt array size */ + for (fmt = 0; fmt < ALVIUM_NUM_SUPP_MIPI_DATA_FMT; fmt++) { + if (!alvium->is_mipi_fmt_avail[alvium_csi2_fmts[fmt].fmt_av_bit]) + continue; + + if (!alvium_csi2_fmts[fmt].is_raw || + alvium->is_bay_avail[alvium_csi2_fmts[fmt].bay_av_bit]) + sz++; + } + + /* init alvium_csi2_fmt array */ + alvium->alvium_csi2_fmt_n = sz; + alvium->alvium_csi2_fmt = + kmalloc_objs(struct alvium_pixfmt, sz); + if (!alvium->alvium_csi2_fmt) + return -ENOMEM; + + /* Create the alvium_csi2 fmt array from formats available */ + for (fmt = 0; fmt < ALVIUM_NUM_SUPP_MIPI_DATA_FMT; fmt++) { + if (!alvium->is_mipi_fmt_avail[alvium_csi2_fmts[fmt].fmt_av_bit]) + continue; + + if (!alvium_csi2_fmts[fmt].is_raw || + alvium->is_bay_avail[alvium_csi2_fmts[fmt].bay_av_bit]) { + alvium->alvium_csi2_fmt[avail_fmt_cnt] = + alvium_csi2_fmts[fmt]; + avail_fmt_cnt++; + } + } + + return 0; +} + +static int alvium_set_mipi_fmt(struct alvium_dev *alvium, + const struct alvium_pixfmt *pixfmt) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_IMG_MIPI_DATA_FORMAT_RW, + pixfmt->mipi_fmt_regval); + if (ret) { + dev_err(dev, "Fail to set mipi fmt\n"); + return ret; + } + + return 0; +} + +static int alvium_get_avail_bayer(struct alvium_dev *alvium) +{ + struct alvium_avail_bayer *avail_bay; + u64 val; + int ret; + + ret = alvium_read(alvium, REG_BCRM_IMG_BAYER_PATTERN_INQUIRY_R, + &val, NULL); + if (ret) + return ret; + + avail_bay = (struct alvium_avail_bayer *)&val; + + alvium->is_bay_avail[ALVIUM_BIT_BAY_MONO] = avail_bay->mono; + alvium->is_bay_avail[ALVIUM_BIT_BAY_GR] = avail_bay->gr; + alvium->is_bay_avail[ALVIUM_BIT_BAY_RG] = avail_bay->rg; + alvium->is_bay_avail[ALVIUM_BIT_BAY_GB] = avail_bay->gb; + alvium->is_bay_avail[ALVIUM_BIT_BAY_BG] = avail_bay->bg; + + alvium_print_avail_bayer(alvium); + + return 0; +} + +static int alvium_set_bayer_pattern(struct alvium_dev *alvium, + const struct alvium_pixfmt *pixfmt) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_IMG_BAYER_PATTERN_RW, + pixfmt->bay_fmt_regval); + if (ret) { + dev_err(dev, "Fail to set bayer pattern\n"); + return ret; + } + + return 0; +} + +static int alvium_get_frame_interval(struct alvium_dev *alvium, + u64 *min_fr, u64 *max_fr) +{ + int ret = 0; + + alvium_read(alvium, REG_BCRM_ACQUISITION_FRAME_RATE_MIN_R, + min_fr, &ret); + alvium_read(alvium, REG_BCRM_ACQUISITION_FRAME_RATE_MAX_R, + max_fr, &ret); + + return ret; +} + +static int alvium_set_frame_rate(struct alvium_dev *alvium, u64 fr) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_ACQUISITION_FRAME_RATE_EN_RW, + 1); + if (ret) { + dev_err(dev, "Fail to set acquisition frame rate enable reg\n"); + return ret; + } + + ret = alvium_write_hshake(alvium, REG_BCRM_FRAME_START_TRIGGER_MODE_RW, + 0); + if (ret) { + dev_err(dev, "Fail to set frame start trigger mode reg\n"); + return ret; + } + + ret = alvium_write_hshake(alvium, REG_BCRM_ACQUISITION_FRAME_RATE_RW, + fr); + if (ret) { + dev_err(dev, "Fail to set frame rate lanes reg\n"); + return ret; + } + + dev_dbg(dev, "set frame rate: %llu us\n", fr); + + return 0; +} + +static int alvium_set_stream_mipi(struct alvium_dev *alvium, bool on) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, on ? REG_BCRM_ACQUISITION_START_RW : + REG_BCRM_ACQUISITION_STOP_RW, 0x01); + if (ret) { + dev_err(dev, "Fail set_stream_mipi\n"); + return ret; + } + + return 0; +} + +static int alvium_get_gain(struct alvium_dev *alvium) +{ + u64 gain; + int ret; + + /* The unit is millibel (1 mB = 0.01 dB) */ + ret = alvium_read(alvium, REG_BCRM_GAIN_RW, &gain, NULL); + if (ret) + return ret; + + return gain; +} + +static int alvium_set_ctrl_gain(struct alvium_dev *alvium, int gain) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + /* The unit is millibel (1 mB = 0.01 dB) */ + ret = alvium_write_hshake(alvium, REG_BCRM_GAIN_RW, (u64)gain); + if (ret) { + dev_err(dev, "Fail to set gain value reg\n"); + return ret; + } + + return 0; +} + +static int alvium_set_ctrl_auto_gain(struct alvium_dev *alvium, bool on) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_GAIN_AUTO_RW, + on ? 0x02 : 0x00); + if (ret) { + dev_err(dev, "Fail to set autogain reg\n"); + return ret; + } + + return 0; +} + +static int alvium_get_exposure(struct alvium_dev *alvium) +{ + u64 exp; + int ret; + + /* Exposure time in ns */ + ret = alvium_read(alvium, REG_BCRM_EXPOSURE_TIME_RW, &exp, NULL); + if (ret) + return ret; + + return exp; +} + +static int alvium_set_ctrl_auto_exposure(struct alvium_dev *alvium, bool on) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_EXPOSURE_AUTO_RW, + on ? 0x02 : 0x00); + if (ret) { + dev_err(dev, "Fail to set autoexposure reg\n"); + return ret; + } + + return 0; +} + +static int alvium_set_ctrl_exposure(struct alvium_dev *alvium, int exposure_ns) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_EXPOSURE_TIME_RW, + (u64)exposure_ns); + if (ret) { + dev_err(dev, "Fail to set exposure value reg\n"); + return ret; + } + + return 0; +} + +static int alvium_set_ctrl_blue_balance_ratio(struct alvium_dev *alvium, + int blue) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_BLUE_BALANCE_RATIO_RW, + (u64)blue); + if (ret) { + dev_err(dev, "Fail to set blue ratio value reg\n"); + return ret; + } + + return 0; +} + +static int alvium_set_ctrl_red_balance_ratio(struct alvium_dev *alvium, int red) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_RED_BALANCE_RATIO_RW, + (u64)red); + if (ret) { + dev_err(dev, "Fail to set red ratio value reg\n"); + return ret; + } + + return 0; +} + +static int alvium_set_ctrl_awb(struct alvium_dev *alvium, bool on) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_WHITE_BALANCE_AUTO_RW, + on ? 0x02 : 0x00); + if (ret) { + dev_err(dev, "Fail to set awb reg\n"); + return ret; + } + + return 0; +} + +static int alvium_set_ctrl_hue(struct alvium_dev *alvium, int val) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_HUE_RW, (u64)val); + if (ret) { + dev_err(dev, "Fail to set hue value reg\n"); + return ret; + } + + return 0; +} + +static int alvium_set_ctrl_contrast(struct alvium_dev *alvium, int val) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_CONTRAST_VALUE_RW, (u64)val); + if (ret) { + dev_err(dev, "Fail to set contrast value reg\n"); + return ret; + } + + return 0; +} + +static int alvium_set_ctrl_saturation(struct alvium_dev *alvium, int val) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_SATURATION_RW, (u64)val); + if (ret) { + dev_err(dev, "Fail to set contrast value reg\n"); + return ret; + } + + return 0; +} + +static int alvium_set_ctrl_gamma(struct alvium_dev *alvium, int val) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_GAMMA_RW, (u64)val); + if (ret) { + dev_err(dev, "Fail to set gamma value reg\n"); + return ret; + } + + return 0; +} + +static int alvium_set_ctrl_sharpness(struct alvium_dev *alvium, int val) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_SHARPNESS_RW, (u64)val); + if (ret) { + dev_err(dev, "Fail to set sharpness value reg\n"); + return ret; + } + + return 0; +} + +static int alvium_set_ctrl_hflip(struct alvium_dev *alvium, int val) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_IMG_REVERSE_X_RW, (u64)val); + if (ret) { + dev_err(dev, "Fail to set reverse_x value reg\n"); + return ret; + } + + return 0; +} + +static int alvium_set_ctrl_vflip(struct alvium_dev *alvium, int val) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_write_hshake(alvium, REG_BCRM_IMG_REVERSE_Y_RW, (u64)val); + if (ret) { + dev_err(dev, "Fail to set reverse_y value reg\n"); + return ret; + } + + return 0; +} + +static int alvium_get_hw_features_params(struct alvium_dev *alvium) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_get_csi_clk_params(alvium); + if (ret) { + dev_err(dev, "Fail to read min/max csi clock regs\n"); + return ret; + } + + ret = alvium_get_img_width_params(alvium); + if (ret) { + dev_err(dev, "Fail to read img width regs\n"); + return ret; + } + + ret = alvium_get_img_height_params(alvium); + if (ret) { + dev_err(dev, "Fail to read img height regs\n"); + return ret; + } + + ret = alvium_get_offx_params(alvium); + if (ret) { + dev_err(dev, "Fail to read offx regs\n"); + return ret; + } + + ret = alvium_get_offy_params(alvium); + if (ret) { + dev_err(dev, "Fail to read offy regs\n"); + return ret; + } + + ret = alvium_get_gain_params(alvium); + if (ret) { + dev_err(dev, "Fail to read gain regs\n"); + return ret; + } + + ret = alvium_get_exposure_params(alvium); + if (ret) { + dev_err(dev, "Fail to read min/max exp regs\n"); + return ret; + } + + ret = alvium_get_red_balance_ratio_params(alvium); + if (ret) { + dev_err(dev, "Fail to read red balance ratio regs\n"); + return ret; + } + + ret = alvium_get_blue_balance_ratio_params(alvium); + if (ret) { + dev_err(dev, "Fail to read blue balance ratio regs\n"); + return ret; + } + + ret = alvium_get_hue_params(alvium); + if (ret) { + dev_err(dev, "Fail to read hue regs\n"); + return ret; + } + + ret = alvium_get_contrast_params(alvium); + if (ret) { + dev_err(dev, "Fail to read contrast regs\n"); + return ret; + } + + ret = alvium_get_saturation_params(alvium); + if (ret) { + dev_err(dev, "Fail to read saturation regs\n"); + return ret; + } + + ret = alvium_get_black_lvl_params(alvium); + if (ret) { + dev_err(dev, "Fail to read black lvl regs\n"); + return ret; + } + + ret = alvium_get_gamma_params(alvium); + if (ret) { + dev_err(dev, "Fail to read gamma regs\n"); + return ret; + } + + ret = alvium_get_sharpness_params(alvium); + if (ret) { + dev_err(dev, "Fail to read sharpness regs\n"); + return ret; + } + + return 0; +} + +static int alvium_get_hw_info(struct alvium_dev *alvium) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + ret = alvium_get_bcrm_vers(alvium); + if (ret) { + dev_err(dev, "Fail to read bcrm version reg\n"); + return ret; + } + + ret = alvium_get_bcrm_addr(alvium); + if (ret) { + dev_err(dev, "Fail to bcrm address reg\n"); + return ret; + } + + ret = alvium_get_fw_version(alvium); + if (ret) { + dev_err(dev, "Fail to read fw version reg\n"); + return ret; + } + + ret = alvium_get_host_supp_csi_lanes(alvium); + if (ret) { + dev_err(dev, "Fail to read host supported csi lanes reg\n"); + return ret; + } + + ret = alvium_get_feat_inq(alvium); + if (ret) { + dev_err(dev, "Fail to read bcrm feature inquiry reg\n"); + return ret; + } + + ret = alvium_get_hw_features_params(alvium); + if (ret) { + dev_err(dev, "Fail to read features params regs\n"); + return ret; + } + + ret = alvium_get_avail_mipi_data_format(alvium); + if (ret) { + dev_err(dev, "Fail to read available mipi data formats reg\n"); + return ret; + } + + ret = alvium_get_avail_bayer(alvium); + if (ret) { + dev_err(dev, "Fail to read available Bayer patterns reg\n"); + return ret; + } + + ret = alvium_get_mode(alvium); + if (ret) { + dev_err(dev, "Fail to get current mode reg\n"); + return ret; + } + + return 0; +} + +static int alvium_hw_init(struct alvium_dev *alvium) +{ + struct device *dev = &alvium->i2c_client->dev; + int ret; + + /* Set Alvium BCM mode*/ + ret = alvium_set_bcm_mode(alvium); + if (ret) { + dev_err(dev, "Fail to set BCM mode\n"); + return ret; + } + + ret = alvium_set_csi_lanes(alvium); + if (ret) { + dev_err(dev, "Fail to set csi lanes\n"); + return ret; + } + + ret = alvium_set_csi_clk(alvium); + if (ret) { + dev_err(dev, "Fail to set csi clk\n"); + return ret; + } + + ret = alvium_set_lp2hs_delay(alvium); + if (ret) { + dev_err(dev, "Fail to set lp2hs reg\n"); + return ret; + } + + return 0; +} + +/* --------------- Subdev Operations --------------- */ +static int alvium_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval *fi) +{ + struct alvium_dev *alvium = sd_to_alvium(sd); + struct device *dev = &alvium->i2c_client->dev; + u64 req_fr, min_fr, max_fr; + struct v4l2_fract *interval; + int ret; + + if (alvium->streaming) + return -EBUSY; + + if (fi->interval.denominator == 0) + return -EINVAL; + + ret = alvium_get_frame_interval(alvium, &min_fr, &max_fr); + if (ret) { + dev_err(dev, "Fail to get frame interval\n"); + return ret; + } + + dev_dbg(dev, "fi->interval.numerator = %d\n", + fi->interval.numerator); + dev_dbg(dev, "fi->interval.denominator = %d\n", + fi->interval.denominator); + + req_fr = (u64)((fi->interval.denominator * USEC_PER_SEC) / + fi->interval.numerator); + req_fr = clamp(req_fr, min_fr, max_fr); + + interval = v4l2_subdev_state_get_interval(sd_state, 0); + + interval->numerator = fi->interval.numerator; + interval->denominator = fi->interval.denominator; + + if (fi->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return 0; + + return alvium_set_frame_rate(alvium, req_fr); +} + +static int alvium_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct alvium_dev *alvium = sd_to_alvium(sd); + + if (code->index >= alvium->alvium_csi2_fmt_n) + return -EINVAL; + + code->code = alvium->alvium_csi2_fmt[code->index].code; + + return 0; +} + +static const struct alvium_pixfmt * +alvium_code_to_pixfmt(struct alvium_dev *alvium, u32 code) +{ + unsigned int i; + + for (i = 0; alvium->alvium_csi2_fmt[i].code; ++i) + if (alvium->alvium_csi2_fmt[i].code == code) + return &alvium->alvium_csi2_fmt[i]; + + return &alvium->alvium_csi2_fmt[0]; +} + +static int alvium_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct alvium_dev *alvium = sd_to_alvium(sd); + const struct alvium_pixfmt *alvium_csi2_fmt; + + if (fse->index) + return -EINVAL; + + alvium_csi2_fmt = alvium_code_to_pixfmt(alvium, fse->code); + if (fse->code != alvium_csi2_fmt->code) + return -EINVAL; + + fse->min_width = alvium->img_min_width; + fse->max_width = alvium->img_max_width; + fse->min_height = alvium->img_min_height; + fse->max_height = alvium->img_max_height; + return 0; +} + +static int alvium_set_mode(struct alvium_dev *alvium, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt; + struct v4l2_rect *crop; + int ret; + + crop = v4l2_subdev_state_get_crop(state, 0); + fmt = v4l2_subdev_state_get_format(state, 0); + + v4l_bound_align_image(&fmt->width, alvium->img_min_width, + alvium->img_max_width, 0, + &fmt->height, alvium->img_min_height, + alvium->img_max_height, 0, 0); + + /* alvium don't accept negative crop left/top */ + crop->left = clamp((u32)max(0, crop->left), alvium->min_offx, + (u32)(alvium->img_max_width - fmt->width)); + crop->top = clamp((u32)max(0, crop->top), alvium->min_offy, + (u32)(alvium->img_max_height - fmt->height)); + + ret = alvium_set_img_width(alvium, fmt->width); + if (ret) + return ret; + + ret = alvium_set_img_height(alvium, fmt->height); + if (ret) + return ret; + + ret = alvium_set_img_offx(alvium, crop->left); + if (ret) + return ret; + + ret = alvium_set_img_offy(alvium, crop->top); + if (ret) + return ret; + + return 0; +} + +static int alvium_set_framefmt(struct alvium_dev *alvium, + struct v4l2_mbus_framefmt *format) +{ + struct device *dev = &alvium->i2c_client->dev; + const struct alvium_pixfmt *alvium_csi2_fmt; + int ret = 0; + + alvium_csi2_fmt = alvium_code_to_pixfmt(alvium, format->code); + + ret = alvium_set_mipi_fmt(alvium, alvium_csi2_fmt); + if (ret) + return ret; + + if (alvium_csi2_fmt->is_raw) { + ret = alvium_set_bayer_pattern(alvium, alvium_csi2_fmt); + if (ret) + return ret; + } + + dev_dbg(dev, "start: %s, mipi_fmt_regval regval = 0x%llx", + __func__, alvium_csi2_fmt->mipi_fmt_regval); + + return ret; +} + +static int alvium_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct alvium_dev *alvium = sd_to_alvium(sd); + struct i2c_client *client = v4l2_get_subdevdata(&alvium->sd); + struct v4l2_mbus_framefmt *fmt; + struct v4l2_subdev_state *state; + int ret = 0; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + if (enable) { + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + goto out; + + ret = __v4l2_ctrl_handler_setup(&alvium->ctrls.handler); + if (ret) + goto out; + + ret = alvium_set_mode(alvium, state); + if (ret) + goto out; + + fmt = v4l2_subdev_state_get_format(state, 0); + ret = alvium_set_framefmt(alvium, fmt); + if (ret) + goto out; + + ret = alvium_set_stream_mipi(alvium, enable); + if (ret) + goto out; + + } else { + alvium_set_stream_mipi(alvium, enable); + pm_runtime_put_autosuspend(&client->dev); + } + + alvium->streaming = !!enable; + v4l2_subdev_unlock_state(state); + + return 0; + +out: + pm_runtime_put(&client->dev); + v4l2_subdev_unlock_state(state); + return ret; +} + +static int alvium_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct alvium_dev *alvium = sd_to_alvium(sd); + struct alvium_mode *mode = &alvium->mode; + struct v4l2_fract *interval; + struct v4l2_subdev_format sd_fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .format = alvium_csi2_default_fmt, + }; + struct v4l2_subdev_crop sd_crop = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .rect = { + .left = mode->crop.left, + .top = mode->crop.top, + .width = mode->crop.width, + .height = mode->crop.height, + }, + }; + + *v4l2_subdev_state_get_crop(state, 0) = sd_crop.rect; + *v4l2_subdev_state_get_format(state, 0) = sd_fmt.format; + + /* Setup initial frame interval*/ + interval = v4l2_subdev_state_get_interval(state, 0); + interval->numerator = 1; + interval->denominator = ALVIUM_DEFAULT_FR_HZ; + + return 0; +} + +static int alvium_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct alvium_dev *alvium = sd_to_alvium(sd); + const struct alvium_pixfmt *alvium_csi2_fmt; + struct v4l2_mbus_framefmt *fmt; + struct v4l2_rect *crop; + + fmt = v4l2_subdev_state_get_format(sd_state, 0); + crop = v4l2_subdev_state_get_crop(sd_state, 0); + + v4l_bound_align_image(&format->format.width, alvium->img_min_width, + alvium->img_max_width, 0, + &format->format.height, alvium->img_min_height, + alvium->img_max_height, 0, 0); + + /* Adjust left and top to prevent roll over sensor area */ + crop->left = clamp((u32)crop->left, (u32)0, + (alvium->img_max_width - fmt->width)); + crop->top = clamp((u32)crop->top, (u32)0, + (alvium->img_max_height - fmt->height)); + + /* Set also the crop width and height when set a new fmt */ + crop->width = fmt->width; + crop->height = fmt->height; + + alvium_csi2_fmt = alvium_code_to_pixfmt(alvium, format->format.code); + fmt->code = alvium_csi2_fmt->code; + + *fmt = format->format; + + return 0; +} + +static int alvium_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct alvium_dev *alvium = sd_to_alvium(sd); + struct v4l2_mbus_framefmt *fmt; + struct v4l2_rect *crop; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + crop = v4l2_subdev_state_get_crop(sd_state, 0); + fmt = v4l2_subdev_state_get_format(sd_state, 0); + + /* + * Alvium can only shift the origin of the img + * then we accept only value with the same value of the actual fmt + */ + if (sel->r.width != fmt->width) + sel->r.width = fmt->width; + + if (sel->r.height != fmt->height) + sel->r.height = fmt->height; + + /* alvium don't accept negative crop left/top */ + crop->left = clamp((u32)max(0, sel->r.left), alvium->min_offx, + alvium->img_max_width - sel->r.width); + crop->top = clamp((u32)max(0, sel->r.top), alvium->min_offy, + alvium->img_max_height - sel->r.height); + + sel->r = *crop; + + return 0; +} + +static int alvium_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct alvium_dev *alvium = sd_to_alvium(sd); + + switch (sel->target) { + /* Current cropping area */ + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_state_get_crop(sd_state, 0); + break; + /* Cropping bounds */ + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = alvium->img_max_width; + sel->r.height = alvium->img_max_height; + break; + /* Default cropping area */ + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.top = alvium->min_offy; + sel->r.left = alvium->min_offx; + sel->r.width = alvium->img_max_width; + sel->r.height = alvium->img_max_height; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int alvium_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct alvium_dev *alvium = sd_to_alvium(sd); + int val; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + val = alvium_get_gain(alvium); + if (val < 0) + return val; + alvium->ctrls.gain->val = val; + break; + case V4L2_CID_EXPOSURE: + val = alvium_get_exposure(alvium); + if (val < 0) + return val; + alvium->ctrls.exposure->val = val; + break; + } + + return 0; +} + +static int alvium_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct alvium_dev *alvium = sd_to_alvium(sd); + struct i2c_client *client = v4l2_get_subdevdata(&alvium->sd); + int ret; + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = alvium_set_ctrl_gain(alvium, ctrl->val); + break; + case V4L2_CID_AUTOGAIN: + ret = alvium_set_ctrl_auto_gain(alvium, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = alvium_set_ctrl_exposure(alvium, ctrl->val); + break; + case V4L2_CID_EXPOSURE_AUTO: + ret = alvium_set_ctrl_auto_exposure(alvium, ctrl->val); + break; + case V4L2_CID_RED_BALANCE: + ret = alvium_set_ctrl_red_balance_ratio(alvium, ctrl->val); + break; + case V4L2_CID_BLUE_BALANCE: + ret = alvium_set_ctrl_blue_balance_ratio(alvium, ctrl->val); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = alvium_set_ctrl_awb(alvium, ctrl->val); + break; + case V4L2_CID_HUE: + ret = alvium_set_ctrl_hue(alvium, ctrl->val); + break; + case V4L2_CID_CONTRAST: + ret = alvium_set_ctrl_contrast(alvium, ctrl->val); + break; + case V4L2_CID_SATURATION: + ret = alvium_set_ctrl_saturation(alvium, ctrl->val); + break; + case V4L2_CID_GAMMA: + ret = alvium_set_ctrl_gamma(alvium, ctrl->val); + break; + case V4L2_CID_SHARPNESS: + ret = alvium_set_ctrl_sharpness(alvium, ctrl->val); + break; + case V4L2_CID_HFLIP: + ret = alvium_set_ctrl_hflip(alvium, ctrl->val); + break; + case V4L2_CID_VFLIP: + ret = alvium_set_ctrl_vflip(alvium, ctrl->val); + break; + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops alvium_ctrl_ops = { + .g_volatile_ctrl = alvium_g_volatile_ctrl, + .s_ctrl = alvium_s_ctrl, +}; + +static int alvium_ctrl_init(struct alvium_dev *alvium) +{ + const struct v4l2_ctrl_ops *ops = &alvium_ctrl_ops; + struct alvium_ctrls *ctrls = &alvium->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + struct v4l2_fwnode_device_properties props; + int ret; + + v4l2_ctrl_handler_init(hdl, 32); + + /* Pixel rate is fixed */ + ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_PIXEL_RATE, 0, + ALVIUM_DEFAULT_PIXEL_RATE_MHZ, 1, + ALVIUM_DEFAULT_PIXEL_RATE_MHZ); + + /* Link freq is fixed */ + ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, + V4L2_CID_LINK_FREQ, + 0, 0, &alvium->link_freq); + if (ctrls->link_freq) + ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* Auto/manual white balance */ + if (alvium->avail_ft.auto_whiteb) { + ctrls->auto_wb = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, 1); + if (ctrls->auto_wb) + v4l2_ctrl_auto_cluster(3, &ctrls->auto_wb, 0, false); + } + + ctrls->blue_balance = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_BLUE_BALANCE, + alvium->min_bbalance, + alvium->max_bbalance, + alvium->inc_bbalance, + alvium->dft_bbalance); + + ctrls->red_balance = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_RED_BALANCE, + alvium->min_rbalance, + alvium->max_rbalance, + alvium->inc_rbalance, + alvium->dft_rbalance); + + /* Auto/manual exposure */ + if (alvium->avail_ft.auto_exp) { + ctrls->auto_exp = + v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, 0, + V4L2_EXPOSURE_AUTO); + if (ctrls->auto_exp) + v4l2_ctrl_auto_cluster(2, &ctrls->auto_exp, + V4L2_EXPOSURE_MANUAL, true); + } + + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_EXPOSURE, + alvium->min_exp, + alvium->max_exp, + alvium->inc_exp, + alvium->dft_exp); + if (ctrls->exposure) + ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; + + /* Auto/manual gain */ + if (alvium->avail_ft.auto_gain) { + ctrls->auto_gain = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_AUTOGAIN, + 0, 1, 1, 1); + if (ctrls->auto_gain) + v4l2_ctrl_auto_cluster(2, &ctrls->auto_gain, 0, true); + } + + if (alvium->avail_ft.gain) { + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_ANALOGUE_GAIN, + alvium->min_gain, + alvium->max_gain, + alvium->inc_gain, + alvium->dft_gain); + if (ctrls->gain) + ctrls->gain->flags |= V4L2_CTRL_FLAG_VOLATILE; + } + + if (alvium->avail_ft.sat) + ctrls->saturation = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_SATURATION, + alvium->min_sat, + alvium->max_sat, + alvium->inc_sat, + alvium->dft_sat); + + if (alvium->avail_ft.hue) + ctrls->hue = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_HUE, + alvium->min_hue, + alvium->max_hue, + alvium->inc_hue, + alvium->dft_hue); + + if (alvium->avail_ft.contrast) + ctrls->contrast = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_CONTRAST, + alvium->min_contrast, + alvium->max_contrast, + alvium->inc_contrast, + alvium->dft_contrast); + + if (alvium->avail_ft.gamma) + ctrls->gamma = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_GAMMA, + alvium->min_gamma, + alvium->max_gamma, + alvium->inc_gamma, + alvium->dft_gamma); + + if (alvium->avail_ft.sharp) + ctrls->sharpness = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_SHARPNESS, + alvium->min_sharp, + alvium->max_sharp, + alvium->inc_sharp, + alvium->dft_sharp); + + if (alvium->avail_ft.rev_x) + ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_HFLIP, + 0, 1, 1, 0); + + if (alvium->avail_ft.rev_y) + ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_VFLIP, + 0, 1, 1, 0); + + if (hdl->error) { + ret = hdl->error; + goto free_ctrls; + } + + ret = v4l2_fwnode_device_parse(&alvium->i2c_client->dev, &props); + if (ret) + goto free_ctrls; + + ret = v4l2_ctrl_new_fwnode_properties(hdl, ops, &props); + if (ret) + goto free_ctrls; + + alvium->sd.ctrl_handler = hdl; + return 0; + +free_ctrls: + v4l2_ctrl_handler_free(hdl); + return ret; +} + +static const struct v4l2_subdev_core_ops alvium_core_ops = { + .log_status = v4l2_ctrl_subdev_log_status, +}; + +static const struct v4l2_subdev_video_ops alvium_video_ops = { + .s_stream = alvium_s_stream, +}; + +static const struct v4l2_subdev_pad_ops alvium_pad_ops = { + .enum_mbus_code = alvium_enum_mbus_code, + .enum_frame_size = alvium_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = alvium_set_fmt, + .get_selection = alvium_get_selection, + .set_selection = alvium_set_selection, + .get_frame_interval = v4l2_subdev_get_frame_interval, + .set_frame_interval = alvium_s_frame_interval, +}; + +static const struct v4l2_subdev_internal_ops alvium_internal_ops = { + .init_state = alvium_init_state, +}; + +static const struct v4l2_subdev_ops alvium_subdev_ops = { + .core = &alvium_core_ops, + .pad = &alvium_pad_ops, + .video = &alvium_video_ops, +}; + +static int alvium_subdev_init(struct alvium_dev *alvium) +{ + struct i2c_client *client = alvium->i2c_client; + struct device *dev = &alvium->i2c_client->dev; + struct v4l2_subdev *sd = &alvium->sd; + int ret; + + /* Setup the initial mode */ + alvium->mode.fmt = alvium_csi2_default_fmt; + alvium->mode.width = alvium_csi2_default_fmt.width; + alvium->mode.height = alvium_csi2_default_fmt.height; + alvium->mode.crop.left = alvium->min_offx; + alvium->mode.crop.top = alvium->min_offy; + alvium->mode.crop.width = alvium_csi2_default_fmt.width; + alvium->mode.crop.height = alvium_csi2_default_fmt.height; + + /* init alvium sd */ + v4l2_i2c_subdev_init(sd, client, &alvium_subdev_ops); + + sd->internal_ops = &alvium_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + alvium->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + + ret = media_entity_pads_init(&sd->entity, 1, &alvium->pad); + if (ret) { + dev_err(dev, "Could not register media entity\n"); + return ret; + } + + ret = alvium_ctrl_init(alvium); + if (ret) { + dev_err(dev, "Control initialization error %d\n", ret); + goto entity_cleanup; + } + + alvium->sd.state_lock = alvium->ctrls.handler.lock; + + ret = v4l2_subdev_init_finalize(sd); + if (ret < 0) { + dev_err(dev, "subdev initialization error %d\n", ret); + goto err_ctrls; + } + + return 0; + +err_ctrls: + v4l2_ctrl_handler_free(&alvium->ctrls.handler); +entity_cleanup: + media_entity_cleanup(&alvium->sd.entity); + return ret; +} + +static void alvium_subdev_cleanup(struct alvium_dev *alvium) +{ + v4l2_fwnode_endpoint_free(&alvium->ep); + v4l2_subdev_cleanup(&alvium->sd); + media_entity_cleanup(&alvium->sd.entity); + v4l2_ctrl_handler_free(&alvium->ctrls.handler); +} + +static int alvium_get_dt_data(struct alvium_dev *alvium) +{ + struct device *dev = &alvium->i2c_client->dev; + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct fwnode_handle *endpoint; + + if (!fwnode) + return -EINVAL; + + /* Only CSI2 is supported for now: */ + alvium->ep.bus_type = V4L2_MBUS_CSI2_DPHY; + + endpoint = fwnode_graph_get_endpoint_by_id(fwnode, 0, 0, 0); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &alvium->ep)) { + dev_err(dev, "could not parse endpoint\n"); + goto error_out; + } + + if (!alvium->ep.nr_of_link_frequencies) { + dev_err(dev, "no link frequencies defined"); + goto error_out; + } + + return 0; + +error_out: + v4l2_fwnode_endpoint_free(&alvium->ep); + fwnode_handle_put(endpoint); + + return -EINVAL; +} + +static int alvium_set_power(struct alvium_dev *alvium, bool on) +{ + int ret; + + if (!on) + return regulator_disable(alvium->reg_vcc); + + ret = regulator_enable(alvium->reg_vcc); + if (ret) + return ret; + + /* alvium boot time 7s */ + msleep(7000); + return 0; +} + +static int alvium_runtime_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct alvium_dev *alvium = sd_to_alvium(sd); + int ret; + + ret = alvium_set_power(alvium, true); + if (ret) + return ret; + + ret = alvium_hw_init(alvium); + if (ret) { + alvium_set_power(alvium, false); + return ret; + } + + return 0; +} + +static int alvium_runtime_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct alvium_dev *alvium = sd_to_alvium(sd); + + alvium_set_power(alvium, false); + + return 0; +} + +static const struct dev_pm_ops alvium_pm_ops = { + RUNTIME_PM_OPS(alvium_runtime_suspend, alvium_runtime_resume, NULL) +}; + +static int alvium_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct alvium_dev *alvium; + int ret; + + alvium = devm_kzalloc(dev, sizeof(*alvium), GFP_KERNEL); + if (!alvium) + return -ENOMEM; + + alvium->i2c_client = client; + + alvium->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(alvium->regmap)) + return PTR_ERR(alvium->regmap); + + ret = alvium_get_dt_data(alvium); + if (ret) + return ret; + + alvium->reg_vcc = devm_regulator_get_optional(dev, "vcc-ext-in"); + if (IS_ERR(alvium->reg_vcc)) + return dev_err_probe(dev, PTR_ERR(alvium->reg_vcc), + "no vcc-ext-in regulator provided\n"); + + ret = alvium_set_power(alvium, true); + if (ret) + goto err_powerdown; + + if (!alvium_is_alive(alvium)) { + ret = -ENODEV; + dev_err_probe(dev, ret, "Device detection failed\n"); + goto err_powerdown; + } + + ret = alvium_get_hw_info(alvium); + if (ret) { + dev_err_probe(dev, ret, "get_hw_info fail\n"); + goto err_powerdown; + } + + ret = alvium_hw_init(alvium); + if (ret) { + dev_err_probe(dev, ret, "hw_init fail\n"); + goto err_powerdown; + } + + ret = alvium_setup_mipi_fmt(alvium); + if (ret) { + dev_err_probe(dev, ret, "setup_mipi_fmt fail\n"); + goto err_powerdown; + } + + /* + * Enable runtime PM without autosuspend: + * + * Don't use pm autosuspend (alvium have ~7s boot time). + * Alvium has been powered manually: + * - mark it as active + * - increase the usage count without resuming the device. + */ + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + + /* Initialize the V4L2 subdev. */ + ret = alvium_subdev_init(alvium); + if (ret) + goto err_pm; + + ret = v4l2_async_register_subdev(&alvium->sd); + if (ret < 0) { + dev_err_probe(dev, ret, "Could not register v4l2 device\n"); + goto err_subdev; + } + + return 0; + +err_subdev: + alvium_subdev_cleanup(alvium); +err_pm: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + kfree(alvium->alvium_csi2_fmt); +err_powerdown: + alvium_set_power(alvium, false); + + return ret; +} + +static void alvium_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct alvium_dev *alvium = sd_to_alvium(sd); + struct device *dev = &alvium->i2c_client->dev; + + v4l2_async_unregister_subdev(sd); + alvium_subdev_cleanup(alvium); + kfree(alvium->alvium_csi2_fmt); + /* + * Disable runtime PM. In case runtime PM is disabled in the kernel, + * make sure to turn power off manually. + */ + pm_runtime_disable(dev); + if (!pm_runtime_status_suspended(dev)) + alvium_set_power(alvium, false); + pm_runtime_set_suspended(dev); +} + +static const struct of_device_id alvium_of_ids[] = { + { .compatible = "alliedvision,alvium-csi2", }, + { } +}; +MODULE_DEVICE_TABLE(of, alvium_of_ids); + +static struct i2c_driver alvium_i2c_driver = { + .driver = { + .name = "alvium-csi2", + .pm = pm_ptr(&alvium_pm_ops), + .of_match_table = alvium_of_ids, + }, + .probe = alvium_probe, + .remove = alvium_remove, +}; + +module_i2c_driver(alvium_i2c_driver); + +MODULE_DESCRIPTION("Allied Vision's Alvium Camera Driver"); +MODULE_AUTHOR("Tommaso Merciai "); +MODULE_AUTHOR("Martin Hecht "); +MODULE_AUTHOR("Avnet Silica Software & Services EMEA"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/alvium-csi2.h b/drivers/media/i2c/alvium-csi2.h new file mode 100644 index 000000000..978af44f7 --- /dev/null +++ b/drivers/media/i2c/alvium-csi2.h @@ -0,0 +1,476 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Allied Vision Technologies GmbH Alvium camera driver + * + * Copyright (C) 2023 Tommaso Merciai + * Copyright (C) 2023 Martin Hecht + * Copyright (C) 2023 Avnet EMG GmbH + */ + +#ifndef ALVIUM_CSI2_H_ +#define ALVIUM_CSI2_H_ + +#include +#include +#include +#include +#include +#include +#include + +#define REG_BCRM_V4L2 BIT(31) + +#define REG_BCRM_V4L2_8BIT(n) (REG_BCRM_V4L2 | CCI_REG8(n)) +#define REG_BCRM_V4L2_16BIT(n) (REG_BCRM_V4L2 | CCI_REG16(n)) +#define REG_BCRM_V4L2_32BIT(n) (REG_BCRM_V4L2 | CCI_REG32(n)) +#define REG_BCRM_V4L2_64BIT(n) (REG_BCRM_V4L2 | CCI_REG64(n)) + +/* Basic Control Register Map register offsets (BCRM) */ +#define REG_BCRM_MINOR_VERSION_R CCI_REG16(0x0000) +#define REG_BCRM_MAJOR_VERSION_R CCI_REG16(0x0002) +#define REG_BCRM_REG_ADDR_R CCI_REG16(0x0014) + +#define REG_BCRM_FEATURE_INQUIRY_R REG_BCRM_V4L2_64BIT(0x0008) +#define REG_BCRM_DEVICE_FW REG_BCRM_V4L2_64BIT(0x0010) +#define REG_BCRM_WRITE_HANDSHAKE_RW REG_BCRM_V4L2_8BIT(0x0018) + +/* Streaming Control Registers */ +#define REG_BCRM_SUPPORTED_CSI2_LANE_COUNTS_R REG_BCRM_V4L2_8BIT(0x0040) +#define REG_BCRM_CSI2_LANE_COUNT_RW REG_BCRM_V4L2_8BIT(0x0044) +#define REG_BCRM_CSI2_CLOCK_MIN_R REG_BCRM_V4L2_32BIT(0x0048) +#define REG_BCRM_CSI2_CLOCK_MAX_R REG_BCRM_V4L2_32BIT(0x004c) +#define REG_BCRM_CSI2_CLOCK_RW REG_BCRM_V4L2_32BIT(0x0050) +#define REG_BCRM_BUFFER_SIZE_R REG_BCRM_V4L2_32BIT(0x0054) + +#define REG_BCRM_IPU_X_MIN_W REG_BCRM_V4L2_32BIT(0x0058) +#define REG_BCRM_IPU_X_MAX_W REG_BCRM_V4L2_32BIT(0x005c) +#define REG_BCRM_IPU_X_INC_W REG_BCRM_V4L2_32BIT(0x0060) +#define REG_BCRM_IPU_Y_MIN_W REG_BCRM_V4L2_32BIT(0x0064) +#define REG_BCRM_IPU_Y_MAX_W REG_BCRM_V4L2_32BIT(0x0068) +#define REG_BCRM_IPU_Y_INC_W REG_BCRM_V4L2_32BIT(0x006c) +#define REG_BCRM_IPU_X_R REG_BCRM_V4L2_32BIT(0x0070) +#define REG_BCRM_IPU_Y_R REG_BCRM_V4L2_32BIT(0x0074) + +#define REG_BCRM_PHY_RESET_RW REG_BCRM_V4L2_8BIT(0x0078) +#define REG_BCRM_LP2HS_DELAY_RW REG_BCRM_V4L2_32BIT(0x007c) + +/* Acquisition Control Registers */ +#define REG_BCRM_ACQUISITION_START_RW REG_BCRM_V4L2_8BIT(0x0080) +#define REG_BCRM_ACQUISITION_STOP_RW REG_BCRM_V4L2_8BIT(0x0084) +#define REG_BCRM_ACQUISITION_ABORT_RW REG_BCRM_V4L2_8BIT(0x0088) +#define REG_BCRM_ACQUISITION_STATUS_R REG_BCRM_V4L2_8BIT(0x008c) +#define REG_BCRM_ACQUISITION_FRAME_RATE_RW REG_BCRM_V4L2_64BIT(0x0090) +#define REG_BCRM_ACQUISITION_FRAME_RATE_MIN_R REG_BCRM_V4L2_64BIT(0x0098) +#define REG_BCRM_ACQUISITION_FRAME_RATE_MAX_R REG_BCRM_V4L2_64BIT(0x00a0) +#define REG_BCRM_ACQUISITION_FRAME_RATE_INC_R REG_BCRM_V4L2_64BIT(0x00a8) +#define REG_BCRM_ACQUISITION_FRAME_RATE_EN_RW REG_BCRM_V4L2_8BIT(0x00b0) + +#define REG_BCRM_FRAME_START_TRIGGER_MODE_RW REG_BCRM_V4L2_8BIT(0x00b4) +#define REG_BCRM_FRAME_START_TRIGGER_SOURCE_RW REG_BCRM_V4L2_8BIT(0x00b8) +#define REG_BCRM_FRAME_START_TRIGGER_ACTIVATION_RW REG_BCRM_V4L2_8BIT(0x00bc) +#define REG_BCRM_FRAME_START_TRIGGER_SOFTWARE_W REG_BCRM_V4L2_8BIT(0x00c0) +#define REG_BCRM_FRAME_START_TRIGGER_DELAY_RW REG_BCRM_V4L2_32BIT(0x00c4) +#define REG_BCRM_EXPOSURE_ACTIVE_LINE_MODE_RW REG_BCRM_V4L2_8BIT(0x00c8) +#define REG_BCRM_EXPOSURE_ACTIVE_LINE_SELECTOR_RW REG_BCRM_V4L2_8BIT(0x00cc) +#define REG_BCRM_LINE_CONFIGURATION_RW REG_BCRM_V4L2_32BIT(0x00d0) + +#define REG_BCRM_IMG_WIDTH_RW REG_BCRM_V4L2_32BIT(0x0100) +#define REG_BCRM_IMG_WIDTH_MIN_R REG_BCRM_V4L2_32BIT(0x0104) +#define REG_BCRM_IMG_WIDTH_MAX_R REG_BCRM_V4L2_32BIT(0x0108) +#define REG_BCRM_IMG_WIDTH_INC_R REG_BCRM_V4L2_32BIT(0x010c) + +#define REG_BCRM_IMG_HEIGHT_RW REG_BCRM_V4L2_32BIT(0x0110) +#define REG_BCRM_IMG_HEIGHT_MIN_R REG_BCRM_V4L2_32BIT(0x0114) +#define REG_BCRM_IMG_HEIGHT_MAX_R REG_BCRM_V4L2_32BIT(0x0118) +#define REG_BCRM_IMG_HEIGHT_INC_R REG_BCRM_V4L2_32BIT(0x011c) + +#define REG_BCRM_IMG_OFFSET_X_RW REG_BCRM_V4L2_32BIT(0x0120) +#define REG_BCRM_IMG_OFFSET_X_MIN_R REG_BCRM_V4L2_32BIT(0x0124) +#define REG_BCRM_IMG_OFFSET_X_MAX_R REG_BCRM_V4L2_32BIT(0x0128) +#define REG_BCRM_IMG_OFFSET_X_INC_R REG_BCRM_V4L2_32BIT(0x012c) + +#define REG_BCRM_IMG_OFFSET_Y_RW REG_BCRM_V4L2_32BIT(0x0130) +#define REG_BCRM_IMG_OFFSET_Y_MIN_R REG_BCRM_V4L2_32BIT(0x0134) +#define REG_BCRM_IMG_OFFSET_Y_MAX_R REG_BCRM_V4L2_32BIT(0x0138) +#define REG_BCRM_IMG_OFFSET_Y_INC_R REG_BCRM_V4L2_32BIT(0x013c) + +#define REG_BCRM_IMG_MIPI_DATA_FORMAT_RW REG_BCRM_V4L2_32BIT(0x0140) +#define REG_BCRM_IMG_AVAILABLE_MIPI_DATA_FORMATS_R REG_BCRM_V4L2_64BIT(0x0148) +#define REG_BCRM_IMG_BAYER_PATTERN_INQUIRY_R REG_BCRM_V4L2_8BIT(0x0150) +#define REG_BCRM_IMG_BAYER_PATTERN_RW REG_BCRM_V4L2_8BIT(0x0154) +#define REG_BCRM_IMG_REVERSE_X_RW REG_BCRM_V4L2_8BIT(0x0158) +#define REG_BCRM_IMG_REVERSE_Y_RW REG_BCRM_V4L2_8BIT(0x015c) + +#define REG_BCRM_SENSOR_WIDTH_R REG_BCRM_V4L2_32BIT(0x0160) +#define REG_BCRM_SENSOR_HEIGHT_R REG_BCRM_V4L2_32BIT(0x0164) +#define REG_BCRM_WIDTH_MAX_R REG_BCRM_V4L2_32BIT(0x0168) +#define REG_BCRM_HEIGHT_MAX_R REG_BCRM_V4L2_32BIT(0x016c) + +#define REG_BCRM_EXPOSURE_TIME_RW REG_BCRM_V4L2_64BIT(0x0180) +#define REG_BCRM_EXPOSURE_TIME_MIN_R REG_BCRM_V4L2_64BIT(0x0188) +#define REG_BCRM_EXPOSURE_TIME_MAX_R REG_BCRM_V4L2_64BIT(0x0190) +#define REG_BCRM_EXPOSURE_TIME_INC_R REG_BCRM_V4L2_64BIT(0x0198) +#define REG_BCRM_EXPOSURE_AUTO_RW REG_BCRM_V4L2_8BIT(0x01a0) + +#define REG_BCRM_INTENSITY_AUTO_PRECEDENCE_RW REG_BCRM_V4L2_8BIT(0x01a4) +#define REG_BCRM_INTENSITY_AUTO_PRECEDENCE_VALUE_RW REG_BCRM_V4L2_32BIT(0x01a8) +#define REG_BCRM_INTENSITY_AUTO_PRECEDENCE_MIN_R REG_BCRM_V4L2_32BIT(0x01ac) +#define REG_BCRM_INTENSITY_AUTO_PRECEDENCE_MAX_R REG_BCRM_V4L2_32BIT(0x01b0) +#define REG_BCRM_INTENSITY_AUTO_PRECEDENCE_INC_R REG_BCRM_V4L2_32BIT(0x01b4) + +#define REG_BCRM_BLACK_LEVEL_RW REG_BCRM_V4L2_32BIT(0x01b8) +#define REG_BCRM_BLACK_LEVEL_MIN_R REG_BCRM_V4L2_32BIT(0x01bc) +#define REG_BCRM_BLACK_LEVEL_MAX_R REG_BCRM_V4L2_32BIT(0x01c0) +#define REG_BCRM_BLACK_LEVEL_INC_R REG_BCRM_V4L2_32BIT(0x01c4) + +#define REG_BCRM_GAIN_RW REG_BCRM_V4L2_64BIT(0x01c8) +#define REG_BCRM_GAIN_MIN_R REG_BCRM_V4L2_64BIT(0x01d0) +#define REG_BCRM_GAIN_MAX_R REG_BCRM_V4L2_64BIT(0x01d8) +#define REG_BCRM_GAIN_INC_R REG_BCRM_V4L2_64BIT(0x01e0) +#define REG_BCRM_GAIN_AUTO_RW REG_BCRM_V4L2_8BIT(0x01e8) + +#define REG_BCRM_GAMMA_RW REG_BCRM_V4L2_64BIT(0x01f0) +#define REG_BCRM_GAMMA_MIN_R REG_BCRM_V4L2_64BIT(0x01f8) +#define REG_BCRM_GAMMA_MAX_R REG_BCRM_V4L2_64BIT(0x0200) +#define REG_BCRM_GAMMA_INC_R REG_BCRM_V4L2_64BIT(0x0208) + +#define REG_BCRM_CONTRAST_VALUE_RW REG_BCRM_V4L2_32BIT(0x0214) +#define REG_BCRM_CONTRAST_VALUE_MIN_R REG_BCRM_V4L2_32BIT(0x0218) +#define REG_BCRM_CONTRAST_VALUE_MAX_R REG_BCRM_V4L2_32BIT(0x021c) +#define REG_BCRM_CONTRAST_VALUE_INC_R REG_BCRM_V4L2_32BIT(0x0220) + +#define REG_BCRM_SATURATION_RW REG_BCRM_V4L2_32BIT(0x0240) +#define REG_BCRM_SATURATION_MIN_R REG_BCRM_V4L2_32BIT(0x0244) +#define REG_BCRM_SATURATION_MAX_R REG_BCRM_V4L2_32BIT(0x0248) +#define REG_BCRM_SATURATION_INC_R REG_BCRM_V4L2_32BIT(0x024c) + +#define REG_BCRM_HUE_RW REG_BCRM_V4L2_32BIT(0x0250) +#define REG_BCRM_HUE_MIN_R REG_BCRM_V4L2_32BIT(0x0254) +#define REG_BCRM_HUE_MAX_R REG_BCRM_V4L2_32BIT(0x0258) +#define REG_BCRM_HUE_INC_R REG_BCRM_V4L2_32BIT(0x025c) + +#define REG_BCRM_ALL_BALANCE_RATIO_RW REG_BCRM_V4L2_64BIT(0x0260) +#define REG_BCRM_ALL_BALANCE_RATIO_MIN_R REG_BCRM_V4L2_64BIT(0x0268) +#define REG_BCRM_ALL_BALANCE_RATIO_MAX_R REG_BCRM_V4L2_64BIT(0x0270) +#define REG_BCRM_ALL_BALANCE_RATIO_INC_R REG_BCRM_V4L2_64BIT(0x0278) + +#define REG_BCRM_RED_BALANCE_RATIO_RW REG_BCRM_V4L2_64BIT(0x0280) +#define REG_BCRM_RED_BALANCE_RATIO_MIN_R REG_BCRM_V4L2_64BIT(0x0288) +#define REG_BCRM_RED_BALANCE_RATIO_MAX_R REG_BCRM_V4L2_64BIT(0x0290) +#define REG_BCRM_RED_BALANCE_RATIO_INC_R REG_BCRM_V4L2_64BIT(0x0298) + +#define REG_BCRM_GREEN_BALANCE_RATIO_RW REG_BCRM_V4L2_64BIT(0x02a0) +#define REG_BCRM_GREEN_BALANCE_RATIO_MIN_R REG_BCRM_V4L2_64BIT(0x02a8) +#define REG_BCRM_GREEN_BALANCE_RATIO_MAX_R REG_BCRM_V4L2_64BIT(0x02b0) +#define REG_BCRM_GREEN_BALANCE_RATIO_INC_R REG_BCRM_V4L2_64BIT(0x02b8) + +#define REG_BCRM_BLUE_BALANCE_RATIO_RW REG_BCRM_V4L2_64BIT(0x02c0) +#define REG_BCRM_BLUE_BALANCE_RATIO_MIN_R REG_BCRM_V4L2_64BIT(0x02c8) +#define REG_BCRM_BLUE_BALANCE_RATIO_MAX_R REG_BCRM_V4L2_64BIT(0x02d0) +#define REG_BCRM_BLUE_BALANCE_RATIO_INC_R REG_BCRM_V4L2_64BIT(0x02d8) + +#define REG_BCRM_WHITE_BALANCE_AUTO_RW REG_BCRM_V4L2_8BIT(0x02e0) +#define REG_BCRM_SHARPNESS_RW REG_BCRM_V4L2_32BIT(0x0300) +#define REG_BCRM_SHARPNESS_MIN_R REG_BCRM_V4L2_32BIT(0x0304) +#define REG_BCRM_SHARPNESS_MAX_R REG_BCRM_V4L2_32BIT(0x0308) +#define REG_BCRM_SHARPNESS_INC_R REG_BCRM_V4L2_32BIT(0x030c) + +#define REG_BCRM_DEVICE_TEMPERATURE_R REG_BCRM_V4L2_32BIT(0x0310) +#define REG_BCRM_EXPOSURE_AUTO_MIN_RW REG_BCRM_V4L2_64BIT(0x0330) +#define REG_BCRM_EXPOSURE_AUTO_MAX_RW REG_BCRM_V4L2_64BIT(0x0338) +#define REG_BCRM_GAIN_AUTO_MIN_RW REG_BCRM_V4L2_64BIT(0x0340) +#define REG_BCRM_GAIN_AUTO_MAX_RW REG_BCRM_V4L2_64BIT(0x0348) + +/* Heartbeat reg*/ +#define REG_BCRM_HEARTBEAT_RW CCI_REG8(0x021f) + +/* GenCP Registers */ +#define REG_GENCP_CHANGEMODE_W CCI_REG8(0x021c) +#define REG_GENCP_CURRENTMODE_R CCI_REG8(0x021d) +#define REG_GENCP_IN_HANDSHAKE_RW CCI_REG8(0x001c) +#define REG_GENCP_OUT_SIZE_W CCI_REG16(0x0020) +#define REG_GENCP_IN_SIZE_R CCI_REG16(0x0024) + +/* defines */ +#define REG_BCRM_HANDSHAKE_STATUS_MASK 0x01 +#define REG_BCRM_HANDSHAKE_AVAILABLE_MASK 0x80 + +#define BCRM_HANDSHAKE_W_DONE_EN_BIT BIT(0) + +#define ALVIUM_DEFAULT_FR_HZ 10 +#define ALVIUM_DEFAULT_PIXEL_RATE_MHZ 148000000 + +#define ALVIUM_LP2HS_DELAY_MS 100 + +#define BCRM_DEVICE_FW_MAJOR_MASK GENMASK_ULL(15, 8) +#define BCRM_DEVICE_FW_MAJOR_SHIFT 8 +#define BCRM_DEVICE_FW_MINOR_MASK GENMASK_ULL(31, 16) +#define BCRM_DEVICE_FW_MINOR_SHIFT 16 +#define BCRM_DEVICE_FW_PATCH_MASK GENMASK_ULL(63, 32) +#define BCRM_DEVICE_FW_PATCH_SHIFT 32 +#define BCRM_DEVICE_FW_SPEC_MASK GENMASK_ULL(7, 0) +#define BCRM_DEVICE_FW_SPEC_SHIFT 0 + +enum alvium_bcrm_mode { + ALVIUM_BCM_MODE, + ALVIUM_GENCP_MODE, + ALVIUM_NUM_MODE +}; + +enum alvium_mipi_fmt { + ALVIUM_FMT_UYVY8_2X8 = 0, + ALVIUM_FMT_UYVY8_1X16, + ALVIUM_FMT_YUYV8_1X16, + ALVIUM_FMT_YUYV8_2X8, + ALVIUM_FMT_YUYV10_1X20, + ALVIUM_FMT_RGB888_1X24, + ALVIUM_FMT_RBG888_1X24, + ALVIUM_FMT_BGR888_1X24, + ALVIUM_FMT_RGB888_3X8, + ALVIUM_FMT_Y8_1X8, + ALVIUM_FMT_SGRBG8_1X8, + ALVIUM_FMT_SRGGB8_1X8, + ALVIUM_FMT_SGBRG8_1X8, + ALVIUM_FMT_SBGGR8_1X8, + ALVIUM_FMT_Y10_1X10, + ALVIUM_FMT_SGRBG10_1X10, + ALVIUM_FMT_SRGGB10_1X10, + ALVIUM_FMT_SGBRG10_1X10, + ALVIUM_FMT_SBGGR10_1X10, + ALVIUM_FMT_Y12_1X12, + ALVIUM_FMT_SGRBG12_1X12, + ALVIUM_FMT_SRGGB12_1X12, + ALVIUM_FMT_SGBRG12_1X12, + ALVIUM_FMT_SBGGR12_1X12, + ALVIUM_FMT_SBGGR14_1X14, + ALVIUM_FMT_SGBRG14_1X14, + ALVIUM_FMT_SRGGB14_1X14, + ALVIUM_FMT_SGRBG14_1X14, + ALVIUM_NUM_SUPP_MIPI_DATA_FMT +}; + +enum alvium_av_bayer_bit { + ALVIUM_BIT_BAY_NONE = -1, + ALVIUM_BIT_BAY_MONO = 0, + ALVIUM_BIT_BAY_GR, + ALVIUM_BIT_BAY_RG, + ALVIUM_BIT_BAY_GB, + ALVIUM_BIT_BAY_BG, + ALVIUM_NUM_BAY_AV_BIT +}; + +enum alvium_av_mipi_bit { + ALVIUM_BIT_YUV420_8_LEG = 0, + ALVIUM_BIT_YUV420_8, + ALVIUM_BIT_YUV420_10, + ALVIUM_BIT_YUV420_8_CSPS, + ALVIUM_BIT_YUV420_10_CSPS, + ALVIUM_BIT_YUV422_8, + ALVIUM_BIT_YUV422_10, + ALVIUM_BIT_RGB888, + ALVIUM_BIT_RGB666, + ALVIUM_BIT_RGB565, + ALVIUM_BIT_RGB555, + ALVIUM_BIT_RGB444, + ALVIUM_BIT_RAW6, + ALVIUM_BIT_RAW7, + ALVIUM_BIT_RAW8, + ALVIUM_BIT_RAW10, + ALVIUM_BIT_RAW12, + ALVIUM_BIT_RAW14, + ALVIUM_BIT_JPEG, + ALVIUM_NUM_SUPP_MIPI_DATA_BIT +}; + +struct alvium_avail_feat { + u64 rev_x:1; + u64 rev_y:1; + u64 int_autop:1; + u64 black_lvl:1; + u64 gain:1; + u64 gamma:1; + u64 contrast:1; + u64 sat:1; + u64 hue:1; + u64 whiteb:1; + u64 sharp:1; + u64 auto_exp:1; + u64 auto_gain:1; + u64 auto_whiteb:1; + u64 dev_temp:1; + u64 acq_abort:1; + u64 acq_fr:1; + u64 fr_trigger:1; + u64 exp_acq_line:1; + u64 reserved:45; +}; + +struct alvium_avail_mipi_fmt { + u64 yuv420_8_leg:1; + u64 yuv420_8:1; + u64 yuv420_10:1; + u64 yuv420_8_csps:1; + u64 yuv420_10_csps:1; + u64 yuv422_8:1; + u64 yuv422_10:1; + u64 rgb888:1; + u64 rgb666:1; + u64 rgb565:1; + u64 rgb555:1; + u64 rgb444:1; + u64 raw6:1; + u64 raw7:1; + u64 raw8:1; + u64 raw10:1; + u64 raw12:1; + u64 raw14:1; + u64 jpeg:1; + u64 reserved:45; +}; + +struct alvium_avail_bayer { + u8 mono:1; + u8 gr:1; + u8 rg:1; + u8 gb:1; + u8 bg:1; + u8 reserved:3; +}; + +struct alvium_mode { + struct v4l2_rect crop; + struct v4l2_mbus_framefmt fmt; + u32 width; + u32 height; +}; + +struct alvium_pixfmt { + u32 code; + u32 colorspace; + u64 mipi_fmt_regval; + u64 bay_fmt_regval; + u8 id; + u8 is_raw; + u8 fmt_av_bit; + u8 bay_av_bit; +}; + +struct alvium_ctrls { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *auto_exp; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *auto_wb; + struct v4l2_ctrl *blue_balance; + struct v4l2_ctrl *red_balance; + struct v4l2_ctrl *auto_gain; + struct v4l2_ctrl *gain; + struct v4l2_ctrl *saturation; + struct v4l2_ctrl *hue; + struct v4l2_ctrl *contrast; + struct v4l2_ctrl *gamma; + struct v4l2_ctrl *sharpness; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; +}; + +struct alvium_dev { + struct i2c_client *i2c_client; + struct v4l2_subdev sd; + struct v4l2_fwnode_endpoint ep; + struct media_pad pad; + struct regmap *regmap; + + struct regulator *reg_vcc; + + u16 bcrm_addr; + + struct alvium_avail_feat avail_ft; + u8 is_mipi_fmt_avail[ALVIUM_NUM_SUPP_MIPI_DATA_BIT]; + u8 is_bay_avail[ALVIUM_NUM_BAY_AV_BIT]; + + u32 min_csi_clk; + u32 max_csi_clk; + u32 dft_img_width; + u32 img_min_width; + u32 img_max_width; + u32 img_inc_width; + u32 dft_img_height; + u32 img_min_height; + u32 img_max_height; + u32 img_inc_height; + u32 min_offx; + u32 max_offx; + u32 inc_offx; + u32 min_offy; + u32 max_offy; + u32 inc_offy; + u64 dft_gain; + u64 min_gain; + u64 max_gain; + u64 inc_gain; + u64 dft_exp; + u64 min_exp; + u64 max_exp; + u64 inc_exp; + u64 dft_rbalance; + u64 min_rbalance; + u64 max_rbalance; + u64 inc_rbalance; + u64 dft_bbalance; + u64 min_bbalance; + u64 max_bbalance; + u64 inc_bbalance; + s32 dft_hue; + s32 min_hue; + s32 max_hue; + s32 inc_hue; + u32 dft_contrast; + u32 min_contrast; + u32 max_contrast; + u32 inc_contrast; + u32 dft_sat; + u32 min_sat; + u32 max_sat; + u32 inc_sat; + s32 dft_black_lvl; + s32 min_black_lvl; + s32 max_black_lvl; + s32 inc_black_lvl; + u64 dft_gamma; + u64 min_gamma; + u64 max_gamma; + u64 inc_gamma; + s32 dft_sharp; + s32 min_sharp; + s32 max_sharp; + s32 inc_sharp; + + struct alvium_mode mode; + + u8 h_sup_csi_lanes; + u64 link_freq; + + struct alvium_ctrls ctrls; + + u8 bcrm_mode; + + struct alvium_pixfmt *alvium_csi2_fmt; + u8 alvium_csi2_fmt_n; + + u8 streaming; + u8 apply_fiv; +}; + +static inline struct alvium_dev *sd_to_alvium(struct v4l2_subdev *sd) +{ + return container_of_const(sd, struct alvium_dev, sd); +} + +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of_const(ctrl->handler, struct alvium_dev, + ctrls.handler)->sd; +} +#endif /* ALVIUM_CSI2_H_ */ diff --git a/drivers/media/i2c/aptina-pll.c b/drivers/media/i2c/aptina-pll.c new file mode 100644 index 000000000..cd2ed4583 --- /dev/null +++ b/drivers/media/i2c/aptina-pll.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Aptina Sensor PLL Configuration + * + * Copyright (C) 2012 Laurent Pinchart + */ + +#include +#include +#include +#include + +#include "aptina-pll.h" + +int aptina_pll_calculate(struct device *dev, + const struct aptina_pll_limits *limits, + struct aptina_pll *pll) +{ + unsigned int mf_min; + unsigned int mf_max; + unsigned int p1_min; + unsigned int p1_max; + unsigned int p1; + unsigned int div; + + dev_dbg(dev, "PLL: ext clock %u pix clock %u\n", + pll->ext_clock, pll->pix_clock); + + if (pll->ext_clock < limits->ext_clock_min || + pll->ext_clock > limits->ext_clock_max) { + dev_err(dev, "pll: invalid external clock frequency.\n"); + return -EINVAL; + } + + if (pll->pix_clock == 0 || pll->pix_clock > limits->pix_clock_max) { + dev_err(dev, "pll: invalid pixel clock frequency.\n"); + return -EINVAL; + } + + /* Compute the multiplier M and combined N*P1 divisor. */ + div = gcd(pll->pix_clock, pll->ext_clock); + pll->m = pll->pix_clock / div; + div = pll->ext_clock / div; + + /* We now have the smallest M and N*P1 values that will result in the + * desired pixel clock frequency, but they might be out of the valid + * range. Compute the factor by which we should multiply them given the + * following constraints: + * + * - minimum/maximum multiplier + * - minimum/maximum multiplier output clock frequency assuming the + * minimum/maximum N value + * - minimum/maximum combined N*P1 divisor + */ + mf_min = DIV_ROUND_UP(limits->m_min, pll->m); + mf_min = max(mf_min, limits->out_clock_min / + (pll->ext_clock / limits->n_min * pll->m)); + mf_min = max(mf_min, limits->n_min * limits->p1_min / div); + mf_max = limits->m_max / pll->m; + mf_max = min(mf_max, limits->out_clock_max / + (pll->ext_clock / limits->n_max * pll->m)); + mf_max = min(mf_max, DIV_ROUND_UP(limits->n_max * limits->p1_max, div)); + + dev_dbg(dev, "pll: mf min %u max %u\n", mf_min, mf_max); + if (mf_min > mf_max) { + dev_err(dev, "pll: no valid combined N*P1 divisor.\n"); + return -EINVAL; + } + + /* + * We're looking for the highest acceptable P1 value for which a + * multiplier factor MF exists that fulfills the following conditions: + * + * 1. p1 is in the [p1_min, p1_max] range given by the limits and is + * even + * 2. mf is in the [mf_min, mf_max] range computed above + * 3. div * mf is a multiple of p1, in order to compute + * n = div * mf / p1 + * m = pll->m * mf + * 4. the internal clock frequency, given by ext_clock / n, is in the + * [int_clock_min, int_clock_max] range given by the limits + * 5. the output clock frequency, given by ext_clock / n * m, is in the + * [out_clock_min, out_clock_max] range given by the limits + * + * The first naive approach is to iterate over all p1 values acceptable + * according to (1) and all mf values acceptable according to (2), and + * stop at the first combination that fulfills (3), (4) and (5). This + * has a O(n^2) complexity. + * + * Instead of iterating over all mf values in the [mf_min, mf_max] range + * we can compute the mf increment between two acceptable values + * according to (3) with + * + * mf_inc = p1 / gcd(div, p1) (6) + * + * and round the minimum up to the nearest multiple of mf_inc. This will + * restrict the number of mf values to be checked. + * + * Furthermore, conditions (4) and (5) only restrict the range of + * acceptable p1 and mf values by modifying the minimum and maximum + * limits. (5) can be expressed as + * + * ext_clock / (div * mf / p1) * m * mf >= out_clock_min + * ext_clock / (div * mf / p1) * m * mf <= out_clock_max + * + * or + * + * p1 >= out_clock_min * div / (ext_clock * m) (7) + * p1 <= out_clock_max * div / (ext_clock * m) + * + * Similarly, (4) can be expressed as + * + * mf >= ext_clock * p1 / (int_clock_max * div) (8) + * mf <= ext_clock * p1 / (int_clock_min * div) + * + * We can thus iterate over the restricted p1 range defined by the + * combination of (1) and (7), and then compute the restricted mf range + * defined by the combination of (2), (6) and (8). If the resulting mf + * range is not empty, any value in the mf range is acceptable. We thus + * select the mf lwoer bound and the corresponding p1 value. + */ + if (limits->p1_min == 0) { + dev_err(dev, "pll: P1 minimum value must be >0.\n"); + return -EINVAL; + } + + p1_min = max(limits->p1_min, DIV_ROUND_UP(limits->out_clock_min * div, + pll->ext_clock * pll->m)); + p1_max = min(limits->p1_max, limits->out_clock_max * div / + (pll->ext_clock * pll->m)); + + dev_dbg(dev, "pll: p1 min %u max %u\n", p1_min, p1_max); + + for (p1 = p1_max & ~1; p1 >= p1_min; p1 -= 2) { + unsigned int mf_inc = p1 / gcd(div, p1); + unsigned int mf_high; + unsigned int mf_low; + + mf_low = roundup(max(mf_min, DIV_ROUND_UP(pll->ext_clock * p1, + limits->int_clock_max * div)), mf_inc); + mf_high = min(mf_max, pll->ext_clock * p1 / + (limits->int_clock_min * div)); + + if (mf_low > mf_high) + continue; + + pll->n = div * mf_low / p1; + pll->m *= mf_low; + pll->p1 = p1; + dev_dbg(dev, "PLL: N %u M %u P1 %u\n", pll->n, pll->m, pll->p1); + return 0; + } + + dev_err(dev, "pll: no valid N and P1 divisors found.\n"); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(aptina_pll_calculate); + +MODULE_DESCRIPTION("Aptina PLL Helpers"); +MODULE_AUTHOR("Laurent Pinchart "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/aptina-pll.h b/drivers/media/i2c/aptina-pll.h new file mode 100644 index 000000000..54c0e185a --- /dev/null +++ b/drivers/media/i2c/aptina-pll.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Aptina Sensor PLL Configuration + * + * Copyright (C) 2012 Laurent Pinchart + */ + +#ifndef __APTINA_PLL_H +#define __APTINA_PLL_H + +struct aptina_pll { + unsigned int ext_clock; + unsigned int pix_clock; + + unsigned int n; + unsigned int m; + unsigned int p1; +}; + +struct aptina_pll_limits { + unsigned int ext_clock_min; + unsigned int ext_clock_max; + unsigned int int_clock_min; + unsigned int int_clock_max; + unsigned int out_clock_min; + unsigned int out_clock_max; + unsigned int pix_clock_max; + + unsigned int n_min; + unsigned int n_max; + unsigned int m_min; + unsigned int m_max; + unsigned int p1_min; + unsigned int p1_max; +}; + +struct device; + +int aptina_pll_calculate(struct device *dev, + const struct aptina_pll_limits *limits, + struct aptina_pll *pll); + +#endif /* __APTINA_PLL_H */ diff --git a/drivers/media/i2c/ar0521.c b/drivers/media/i2c/ar0521.c new file mode 100644 index 000000000..ed324c2d8 --- /dev/null +++ b/drivers/media/i2c/ar0521.c @@ -0,0 +1,1192 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Sieć Badawcza Łukasiewicz + * - Przemysłowy Instytut Automatyki i Pomiarów PIAP + * Written by Krzysztof Hałasa + */ + +#include +#include +#include + +#include +#include +#include + +/* External clock (extclk) frequencies */ +#define AR0521_EXTCLK_MIN (10 * 1000 * 1000) +#define AR0521_EXTCLK_MAX (48 * 1000 * 1000) + +/* PLL and PLL2 */ +#define AR0521_PLL_MIN (320 * 1000 * 1000) +#define AR0521_PLL_MAX (1280 * 1000 * 1000) + +/* Effective pixel sample rate on the pixel array. */ +#define AR0521_PIXEL_CLOCK_RATE (184 * 1000 * 1000) +#define AR0521_PIXEL_CLOCK_MIN (168 * 1000 * 1000) +#define AR0521_PIXEL_CLOCK_MAX (414 * 1000 * 1000) + +#define AR0521_NATIVE_WIDTH 2604u +#define AR0521_NATIVE_HEIGHT 1964u +#define AR0521_MIN_X_ADDR_START 0u +#define AR0521_MIN_Y_ADDR_START 0u +#define AR0521_MAX_X_ADDR_END 2603u +#define AR0521_MAX_Y_ADDR_END 1955u + +#define AR0521_WIDTH_MIN 8u +#define AR0521_WIDTH_MAX 2592u +#define AR0521_HEIGHT_MIN 8u +#define AR0521_HEIGHT_MAX 1944u + +#define AR0521_WIDTH_BLANKING_MIN 572u +#define AR0521_HEIGHT_BLANKING_MIN 38u /* must be even */ +#define AR0521_TOTAL_HEIGHT_MAX 65535u /* max_frame_length_lines */ +#define AR0521_TOTAL_WIDTH_MAX 65532u /* max_line_length_pck */ + +#define AR0521_ANA_GAIN_MIN 0x00 +#define AR0521_ANA_GAIN_MAX 0x3f +#define AR0521_ANA_GAIN_STEP 0x01 +#define AR0521_ANA_GAIN_DEFAULT 0x00 + +/* AR0521 registers */ +#define AR0521_REG_VT_PIX_CLK_DIV 0x0300 +#define AR0521_REG_FRAME_LENGTH_LINES 0x0340 + +#define AR0521_REG_CHIP_ID 0x3000 +#define AR0521_REG_COARSE_INTEGRATION_TIME 0x3012 +#define AR0521_REG_ROW_SPEED 0x3016 +#define AR0521_REG_EXTRA_DELAY 0x3018 +#define AR0521_REG_RESET 0x301A +#define AR0521_REG_RESET_DEFAULTS 0x0238 +#define AR0521_REG_RESET_GROUP_PARAM_HOLD 0x8000 +#define AR0521_REG_RESET_STREAM BIT(2) +#define AR0521_REG_RESET_RESTART BIT(1) +#define AR0521_REG_RESET_INIT BIT(0) + +#define AR0521_REG_ANA_GAIN_CODE_GLOBAL 0x3028 + +#define AR0521_REG_GREEN1_GAIN 0x3056 +#define AR0521_REG_BLUE_GAIN 0x3058 +#define AR0521_REG_RED_GAIN 0x305A +#define AR0521_REG_GREEN2_GAIN 0x305C +#define AR0521_REG_GLOBAL_GAIN 0x305E + +#define AR0521_REG_HISPI_TEST_MODE 0x3066 +#define AR0521_REG_HISPI_TEST_MODE_LP11 0x0004 + +#define AR0521_REG_TEST_PATTERN_MODE 0x3070 + +#define AR0521_REG_SERIAL_FORMAT 0x31AE +#define AR0521_REG_SERIAL_FORMAT_MIPI 0x0200 + +#define AR0521_REG_HISPI_CONTROL_STATUS 0x31C6 +#define AR0521_REG_HISPI_CONTROL_STATUS_FRAMER_TEST_MODE_ENABLE 0x80 + +#define be cpu_to_be16 + +static const char * const ar0521_supply_names[] = { + "vdd_io", /* I/O (1.8V) supply */ + "vdd", /* Core, PLL and MIPI (1.2V) supply */ + "vaa", /* Analog (2.7V) supply */ +}; + +static const s64 ar0521_link_frequencies[] = { + 184000000, +}; + +struct ar0521_ctrls { + struct v4l2_ctrl_handler handler; + struct { + struct v4l2_ctrl *gain; + struct v4l2_ctrl *red_balance; + struct v4l2_ctrl *blue_balance; + }; + struct { + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + }; + struct v4l2_ctrl *pixrate; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *test_pattern; +}; + +struct ar0521_dev { + struct i2c_client *i2c_client; + struct v4l2_subdev sd; + struct media_pad pad; + struct clk *extclk; + u32 extclk_freq; + + struct regulator *supplies[ARRAY_SIZE(ar0521_supply_names)]; + struct gpio_desc *reset_gpio; + + /* lock to protect all members below */ + struct mutex lock; + + struct v4l2_mbus_framefmt fmt; + struct ar0521_ctrls ctrls; + unsigned int lane_count; + struct { + u16 pre; + u16 mult; + u16 pre2; + u16 mult2; + u16 vt_pix; + } pll; +}; + +static inline struct ar0521_dev *to_ar0521_dev(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ar0521_dev, sd); +} + +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct ar0521_dev, + ctrls.handler)->sd; +} + +static u32 div64_round(u64 v, u32 d) +{ + return div_u64(v + (d >> 1), d); +} + +static u32 div64_round_up(u64 v, u32 d) +{ + return div_u64(v + d - 1, d); +} + +static int ar0521_code_to_bpp(struct ar0521_dev *sensor) +{ + switch (sensor->fmt.code) { + case MEDIA_BUS_FMT_SGRBG8_1X8: + return 8; + } + + return -EINVAL; +} + +/* Data must be BE16, the first value is the register address */ +static int ar0521_write_regs(struct ar0521_dev *sensor, const __be16 *data, + unsigned int count) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg; + int ret; + + msg.addr = client->addr; + msg.flags = client->flags; + msg.buf = (u8 *)data; + msg.len = count * sizeof(*data); + + ret = i2c_transfer(client->adapter, &msg, 1); + + if (ret < 0) { + v4l2_err(&sensor->sd, "%s: I2C write error\n", __func__); + return ret; + } + + return 0; +} + +static int ar0521_write_reg(struct ar0521_dev *sensor, u16 reg, u16 val) +{ + __be16 buf[2] = {be(reg), be(val)}; + + return ar0521_write_regs(sensor, buf, 2); +} + +static int ar0521_set_geometry(struct ar0521_dev *sensor) +{ + /* Center the image in the visible output window. */ + u16 x = clamp((AR0521_WIDTH_MAX - sensor->fmt.width) / 2, + AR0521_MIN_X_ADDR_START, AR0521_MAX_X_ADDR_END); + u16 y = clamp(((AR0521_HEIGHT_MAX - sensor->fmt.height) / 2) & ~1, + AR0521_MIN_Y_ADDR_START, AR0521_MAX_Y_ADDR_END); + + /* All dimensions are unsigned 12-bit integers */ + __be16 regs[] = { + be(AR0521_REG_FRAME_LENGTH_LINES), + be(sensor->fmt.height + sensor->ctrls.vblank->val), + be(sensor->fmt.width + sensor->ctrls.hblank->val), + be(x), + be(y), + be(x + sensor->fmt.width - 1), + be(y + sensor->fmt.height - 1), + be(sensor->fmt.width), + be(sensor->fmt.height) + }; + + return ar0521_write_regs(sensor, regs, ARRAY_SIZE(regs)); +} + +static int ar0521_set_gains(struct ar0521_dev *sensor) +{ + int green = sensor->ctrls.gain->val; + int red = max(green + sensor->ctrls.red_balance->val, 0); + int blue = max(green + sensor->ctrls.blue_balance->val, 0); + unsigned int gain = min(red, min(green, blue)); + unsigned int analog = min(gain, 64u); /* range is 0 - 127 */ + __be16 regs[5]; + + red = min(red - analog + 64, 511u); + green = min(green - analog + 64, 511u); + blue = min(blue - analog + 64, 511u); + regs[0] = be(AR0521_REG_GREEN1_GAIN); + regs[1] = be(green << 7 | analog); + regs[2] = be(blue << 7 | analog); + regs[3] = be(red << 7 | analog); + regs[4] = be(green << 7 | analog); + + return ar0521_write_regs(sensor, regs, ARRAY_SIZE(regs)); +} + +static u32 calc_pll(struct ar0521_dev *sensor, u32 freq, u16 *pre_ptr, u16 *mult_ptr) +{ + u16 pre = 1, mult = 1, new_pre; + u32 pll = AR0521_PLL_MAX + 1; + + for (new_pre = 1; new_pre < 64; new_pre++) { + u32 new_pll; + u32 new_mult = div64_round_up((u64)freq * new_pre, + sensor->extclk_freq); + + if (new_mult < 32) + continue; /* Minimum value */ + if (new_mult > 254) + break; /* Maximum, larger pre won't work either */ + if (sensor->extclk_freq * (u64)new_mult < (u64)AR0521_PLL_MIN * + new_pre) + continue; + if (sensor->extclk_freq * (u64)new_mult > (u64)AR0521_PLL_MAX * + new_pre) + break; /* Larger pre won't work either */ + new_pll = div64_round_up(sensor->extclk_freq * (u64)new_mult, + new_pre); + if (new_pll < pll) { + pll = new_pll; + pre = new_pre; + mult = new_mult; + } + } + + pll = div64_round(sensor->extclk_freq * (u64)mult, pre); + *pre_ptr = pre; + *mult_ptr = mult; + return pll; +} + +static void ar0521_calc_pll(struct ar0521_dev *sensor) +{ + unsigned int pixel_clock; + u16 pre, mult; + u32 vco; + int bpp; + + /* + * PLL1 and PLL2 are computed equally even if the application note + * suggests a slower PLL1 clock. Maintain pll1 and pll2 divider and + * multiplier separated to later specialize the calculation procedure. + * + * PLL1: + * - mclk -> / pre_div1 * pre_mul1 = VCO1 = COUNTER_CLOCK + * + * PLL2: + * - mclk -> / pre_div * pre_mul = VCO + * + * VCO -> / vt_pix = PIXEL_CLOCK + * VCO -> / vt_pix / 2 = WORD_CLOCK + * VCO -> / op_sys = SERIAL_CLOCK + * + * With: + * - vt_pix = bpp / 2 + * - WORD_CLOCK = PIXEL_CLOCK / 2 + * - SERIAL_CLOCK = MIPI data rate (Mbps / lane) = WORD_CLOCK * bpp + * NOTE: this implies the MIPI clock is divided internally by 2 + * to account for DDR. + * + * As op_sys_div is fixed to 1: + * + * SERIAL_CLOCK = VCO + * VCO = 2 * MIPI_CLK + * VCO = PIXEL_CLOCK * bpp / 2 + * + * In the clock tree: + * MIPI_CLK = PIXEL_CLOCK * bpp / 2 / 2 + * + * Generic pixel_rate to bus clock frequency equation: + * MIPI_CLK = V4L2_CID_PIXEL_RATE * bpp / lanes / 2 + * + * From which we derive the PIXEL_CLOCK to use in the clock tree: + * PIXEL_CLOCK = V4L2_CID_PIXEL_RATE * 2 / lanes + * + * Documented clock ranges: + * WORD_CLOCK = (35MHz - 120 MHz) + * PIXEL_CLOCK = (84MHz - 207MHz) + * VCO = (320MHz - 1280MHz) + * + * TODO: in case we have less data lanes we have to reduce the desired + * VCO not to exceed the limits specified by the datasheet and + * consequently reduce the obtained pixel clock. + */ + pixel_clock = AR0521_PIXEL_CLOCK_RATE * 2 / sensor->lane_count; + bpp = ar0521_code_to_bpp(sensor); + sensor->pll.vt_pix = bpp / 2; + vco = pixel_clock * sensor->pll.vt_pix; + + calc_pll(sensor, vco, &pre, &mult); + + sensor->pll.pre = sensor->pll.pre2 = pre; + sensor->pll.mult = sensor->pll.mult2 = mult; +} + +static int ar0521_pll_config(struct ar0521_dev *sensor) +{ + __be16 pll_regs[] = { + be(AR0521_REG_VT_PIX_CLK_DIV), + /* 0x300 */ be(sensor->pll.vt_pix), /* vt_pix_clk_div = bpp / 2 */ + /* 0x302 */ be(1), /* vt_sys_clk_div */ + /* 0x304 */ be((sensor->pll.pre2 << 8) | sensor->pll.pre), + /* 0x306 */ be((sensor->pll.mult2 << 8) | sensor->pll.mult), + /* 0x308 */ be(sensor->pll.vt_pix * 2), /* op_pix_clk_div = 2 * vt_pix_clk_div */ + /* 0x30A */ be(1) /* op_sys_clk_div */ + }; + + ar0521_calc_pll(sensor); + return ar0521_write_regs(sensor, pll_regs, ARRAY_SIZE(pll_regs)); +} + +static int ar0521_set_stream(struct ar0521_dev *sensor, bool on) +{ + int ret; + + if (on) { + ret = pm_runtime_resume_and_get(&sensor->i2c_client->dev); + if (ret < 0) + return ret; + + /* Stop streaming for just a moment */ + ret = ar0521_write_reg(sensor, AR0521_REG_RESET, + AR0521_REG_RESET_DEFAULTS); + if (ret) + return ret; + + ret = ar0521_set_geometry(sensor); + if (ret) + return ret; + + ret = ar0521_pll_config(sensor); + if (ret) + goto err; + + ret = __v4l2_ctrl_handler_setup(&sensor->ctrls.handler); + if (ret) + goto err; + + /* Exit LP-11 mode on clock and data lanes */ + ret = ar0521_write_reg(sensor, AR0521_REG_HISPI_CONTROL_STATUS, + 0); + if (ret) + goto err; + + /* Start streaming */ + ret = ar0521_write_reg(sensor, AR0521_REG_RESET, + AR0521_REG_RESET_DEFAULTS | + AR0521_REG_RESET_STREAM); + if (ret) + goto err; + + return 0; + +err: + pm_runtime_put(&sensor->i2c_client->dev); + return ret; + + } else { + /* + * Reset gain, the sensor may produce all white pixels without + * this + */ + ret = ar0521_write_reg(sensor, AR0521_REG_GLOBAL_GAIN, 0x2000); + if (ret) + return ret; + + /* Stop streaming */ + ret = ar0521_write_reg(sensor, AR0521_REG_RESET, + AR0521_REG_RESET_DEFAULTS); + if (ret) + return ret; + + pm_runtime_put(&sensor->i2c_client->dev); + return 0; + } +} + +static void ar0521_adj_fmt(struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = clamp(ALIGN(fmt->width, 4), AR0521_WIDTH_MIN, + AR0521_WIDTH_MAX); + fmt->height = clamp(ALIGN(fmt->height, 4), AR0521_HEIGHT_MIN, + AR0521_HEIGHT_MAX); + fmt->code = MEDIA_BUS_FMT_SGRBG8_1X8; + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int ar0521_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct ar0521_dev *sensor = to_ar0521_dev(sd); + struct v4l2_mbus_framefmt *fmt; + + mutex_lock(&sensor->lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + fmt = v4l2_subdev_state_get_format(sd_state, 0); + else + fmt = &sensor->fmt; + + format->format = *fmt; + + mutex_unlock(&sensor->lock); + return 0; +} + +static int ar0521_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct ar0521_dev *sensor = to_ar0521_dev(sd); + int max_vblank, max_hblank, exposure_max; + int ret; + + ar0521_adj_fmt(&format->format); + + mutex_lock(&sensor->lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, 0); + *fmt = format->format; + + mutex_unlock(&sensor->lock); + + return 0; + } + + sensor->fmt = format->format; + ar0521_calc_pll(sensor); + + /* + * Update the exposure and blankings limits. Blankings are also reset + * to the minimum. + */ + max_hblank = AR0521_TOTAL_WIDTH_MAX - sensor->fmt.width; + ret = __v4l2_ctrl_modify_range(sensor->ctrls.hblank, + sensor->ctrls.hblank->minimum, + max_hblank, sensor->ctrls.hblank->step, + sensor->ctrls.hblank->minimum); + if (ret) + goto unlock; + + ret = __v4l2_ctrl_s_ctrl(sensor->ctrls.hblank, + sensor->ctrls.hblank->minimum); + if (ret) + goto unlock; + + max_vblank = AR0521_TOTAL_HEIGHT_MAX - sensor->fmt.height; + ret = __v4l2_ctrl_modify_range(sensor->ctrls.vblank, + sensor->ctrls.vblank->minimum, + max_vblank, sensor->ctrls.vblank->step, + sensor->ctrls.vblank->minimum); + if (ret) + goto unlock; + + ret = __v4l2_ctrl_s_ctrl(sensor->ctrls.vblank, + sensor->ctrls.vblank->minimum); + if (ret) + goto unlock; + + exposure_max = sensor->fmt.height + AR0521_HEIGHT_BLANKING_MIN - 4; + ret = __v4l2_ctrl_modify_range(sensor->ctrls.exposure, + sensor->ctrls.exposure->minimum, + exposure_max, + sensor->ctrls.exposure->step, + sensor->ctrls.exposure->default_value); +unlock: + mutex_unlock(&sensor->lock); + + return ret; +} + +static int ar0521_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct ar0521_dev *sensor = to_ar0521_dev(sd); + int exp_max; + int ret; + + /* v4l2_ctrl_lock() locks our own mutex */ + + switch (ctrl->id) { + case V4L2_CID_VBLANK: + exp_max = sensor->fmt.height + ctrl->val - 4; + __v4l2_ctrl_modify_range(sensor->ctrls.exposure, + sensor->ctrls.exposure->minimum, + exp_max, sensor->ctrls.exposure->step, + sensor->ctrls.exposure->default_value); + break; + } + + /* access the sensor only if it's powered up */ + if (!pm_runtime_get_if_in_use(&sensor->i2c_client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_HBLANK: + case V4L2_CID_VBLANK: + ret = ar0521_set_geometry(sensor); + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = ar0521_write_reg(sensor, AR0521_REG_ANA_GAIN_CODE_GLOBAL, + ctrl->val); + break; + case V4L2_CID_GAIN: + case V4L2_CID_RED_BALANCE: + case V4L2_CID_BLUE_BALANCE: + ret = ar0521_set_gains(sensor); + break; + case V4L2_CID_EXPOSURE: + ret = ar0521_write_reg(sensor, + AR0521_REG_COARSE_INTEGRATION_TIME, + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = ar0521_write_reg(sensor, AR0521_REG_TEST_PATTERN_MODE, + ctrl->val); + break; + default: + dev_err(&sensor->i2c_client->dev, + "Unsupported control %x\n", ctrl->id); + ret = -EINVAL; + break; + } + + pm_runtime_put(&sensor->i2c_client->dev); + return ret; +} + +static const struct v4l2_ctrl_ops ar0521_ctrl_ops = { + .s_ctrl = ar0521_s_ctrl, +}; + +static const char * const test_pattern_menu[] = { + "Disabled", + "Solid color", + "Color bars", + "Faded color bars" +}; + +static int ar0521_init_controls(struct ar0521_dev *sensor) +{ + const struct v4l2_ctrl_ops *ops = &ar0521_ctrl_ops; + struct ar0521_ctrls *ctrls = &sensor->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + int max_vblank, max_hblank, exposure_max; + struct v4l2_ctrl *link_freq; + int ret; + + v4l2_ctrl_handler_init(hdl, 32); + + /* We can use our own mutex for the ctrl lock */ + hdl->lock = &sensor->lock; + + /* Analog gain */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, + AR0521_ANA_GAIN_MIN, AR0521_ANA_GAIN_MAX, + AR0521_ANA_GAIN_STEP, AR0521_ANA_GAIN_DEFAULT); + + /* Manual gain */ + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, 0, 511, 1, 0); + ctrls->red_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_RED_BALANCE, + -512, 511, 1, 0); + ctrls->blue_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BLUE_BALANCE, + -512, 511, 1, 0); + v4l2_ctrl_cluster(3, &ctrls->gain); + + /* Initialize blanking limits using the default 2592x1944 format. */ + max_hblank = AR0521_TOTAL_WIDTH_MAX - AR0521_WIDTH_MAX; + ctrls->hblank = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, + AR0521_WIDTH_BLANKING_MIN, + max_hblank, 1, + AR0521_WIDTH_BLANKING_MIN); + + max_vblank = AR0521_TOTAL_HEIGHT_MAX - AR0521_HEIGHT_MAX; + ctrls->vblank = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, + AR0521_HEIGHT_BLANKING_MIN, + max_vblank, 2, + AR0521_HEIGHT_BLANKING_MIN); + v4l2_ctrl_cluster(2, &ctrls->hblank); + + /* Read-only */ + ctrls->pixrate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, + AR0521_PIXEL_CLOCK_MIN, + AR0521_PIXEL_CLOCK_MAX, 1, + AR0521_PIXEL_CLOCK_RATE); + + /* Manual exposure time: max exposure time = visible + blank - 4 */ + exposure_max = AR0521_HEIGHT_MAX + AR0521_HEIGHT_BLANKING_MIN - 4; + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 0, + exposure_max, 1, 0x70); + + link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(ar0521_link_frequencies) - 1, + 0, ar0521_link_frequencies); + if (link_freq) + link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ctrls->test_pattern = v4l2_ctrl_new_std_menu_items(hdl, ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(test_pattern_menu) - 1, + 0, 0, test_pattern_menu); + + if (hdl->error) { + ret = hdl->error; + goto free_ctrls; + } + + sensor->sd.ctrl_handler = hdl; + return 0; + +free_ctrls: + v4l2_ctrl_handler_free(hdl); + return ret; +} + +#define REGS_ENTRY(a) {(a), ARRAY_SIZE(a)} +#define REGS(...) REGS_ENTRY(((const __be16[]){__VA_ARGS__})) + +static const struct initial_reg { + const __be16 *data; /* data[0] is register address */ + unsigned int count; +} initial_regs[] = { + REGS(be(0x0112), be(0x0808)), /* 8-bit/8-bit mode */ + + /* PEDESTAL+2 :+2 is a workaround for 10bit mode +0.5 rounding */ + REGS(be(0x301E), be(0x00AA)), + + /* corrections_recommended_bayer */ + REGS(be(0x3042), + be(0x0004), /* 3042: RNC: enable b/w rnc mode */ + be(0x4580)), /* 3044: RNC: enable row noise correction */ + + REGS(be(0x30D2), + be(0x0000), /* 30D2: CRM/CC: enable crm on Visible and CC rows */ + be(0x0000), /* 30D4: CC: CC enabled with 16 samples per column */ + /* 30D6: CC: bw mode enabled/12 bit data resolution/bw mode */ + be(0x2FFF)), + + REGS(be(0x30DA), + be(0x0FFF), /* 30DA: CC: column correction clip level 2 is 0 */ + be(0x0FFF), /* 30DC: CC: column correction clip level 3 is 0 */ + be(0x0000)), /* 30DE: CC: Group FPN correction */ + + /* RNC: rnc scaling factor = * 54 / 64 (32 / 38 * 64 = 53.9) */ + REGS(be(0x30EE), be(0x1136)), + REGS(be(0x30FA), be(0xFD00)), /* GPIO0 = flash, GPIO1 = shutter */ + REGS(be(0x3120), be(0x0005)), /* p1 dither enabled for 10bit mode */ + REGS(be(0x3172), be(0x0206)), /* txlo clk divider options */ + /* FDOC:fdoc settings with fdoc every frame turned of */ + REGS(be(0x3180), be(0x9434)), + + REGS(be(0x31B0), + be(0x008B), /* 31B0: frame_preamble - FIXME check WRT lanes# */ + be(0x0050)), /* 31B2: line_preamble - FIXME check WRT lanes# */ + + /* don't use continuous clock mode while shut down */ + REGS(be(0x31BC), be(0x068C)), + REGS(be(0x31E0), be(0x0781)), /* Fuse/2DDC: enable 2ddc */ + + /* analog_setup_recommended_10bit */ + REGS(be(0x341A), be(0x4735)), /* Samp&Hold pulse in ADC */ + REGS(be(0x3420), be(0x4735)), /* Samp&Hold pulse in ADC */ + REGS(be(0x3426), be(0x8A1A)), /* ADC offset distribution pulse */ + REGS(be(0x342A), be(0x0018)), /* pulse_config */ + + /* pixel_timing_recommended */ + REGS(be(0x3D00), + /* 3D00 */ be(0x043E), be(0x4760), be(0xFFFF), be(0xFFFF), + /* 3D08 */ be(0x8000), be(0x0510), be(0xAF08), be(0x0252), + /* 3D10 */ be(0x486F), be(0x5D5D), be(0x8056), be(0x8313), + /* 3D18 */ be(0x0087), be(0x6A48), be(0x6982), be(0x0280), + /* 3D20 */ be(0x8359), be(0x8D02), be(0x8020), be(0x4882), + /* 3D28 */ be(0x4269), be(0x6A95), be(0x5988), be(0x5A83), + /* 3D30 */ be(0x5885), be(0x6280), be(0x6289), be(0x6097), + /* 3D38 */ be(0x5782), be(0x605C), be(0xBF18), be(0x0961), + /* 3D40 */ be(0x5080), be(0x2090), be(0x4390), be(0x4382), + /* 3D48 */ be(0x5F8A), be(0x5D5D), be(0x9C63), be(0x8063), + /* 3D50 */ be(0xA960), be(0x9757), be(0x8260), be(0x5CFF), + /* 3D58 */ be(0xBF10), be(0x1681), be(0x0802), be(0x8000), + /* 3D60 */ be(0x141C), be(0x6000), be(0x6022), be(0x4D80), + /* 3D68 */ be(0x5C97), be(0x6A69), be(0xAC6F), be(0x4645), + /* 3D70 */ be(0x4400), be(0x0513), be(0x8069), be(0x6AC6), + /* 3D78 */ be(0x5F95), be(0x5F70), be(0x8040), be(0x4A81), + /* 3D80 */ be(0x0300), be(0xE703), be(0x0088), be(0x4A83), + /* 3D88 */ be(0x40FF), be(0xFFFF), be(0xFD70), be(0x8040), + /* 3D90 */ be(0x4A85), be(0x4FA8), be(0x4F8C), be(0x0070), + /* 3D98 */ be(0xBE47), be(0x8847), be(0xBC78), be(0x6B89), + /* 3DA0 */ be(0x6A80), be(0x6986), be(0x6B8E), be(0x6B80), + /* 3DA8 */ be(0x6980), be(0x6A88), be(0x7C9F), be(0x866B), + /* 3DB0 */ be(0x8765), be(0x46FF), be(0xE365), be(0xA679), + /* 3DB8 */ be(0x4A40), be(0x4580), be(0x44BC), be(0x7000), + /* 3DC0 */ be(0x8040), be(0x0802), be(0x10EF), be(0x0104), + /* 3DC8 */ be(0x3860), be(0x5D5D), be(0x5682), be(0x1300), + /* 3DD0 */ be(0x8648), be(0x8202), be(0x8082), be(0x598A), + /* 3DD8 */ be(0x0280), be(0x2048), be(0x3060), be(0x8042), + /* 3DE0 */ be(0x9259), be(0x865A), be(0x8258), be(0x8562), + /* 3DE8 */ be(0x8062), be(0x8560), be(0x9257), be(0x8221), + /* 3DF0 */ be(0x10FF), be(0xB757), be(0x9361), be(0x1019), + /* 3DF8 */ be(0x8020), be(0x9043), be(0x8E43), be(0x845F), + /* 3E00 */ be(0x835D), be(0x805D), be(0x8163), be(0x8063), + /* 3E08 */ be(0xA060), be(0x9157), be(0x8260), be(0x5CFF), + /* 3E10 */ be(0xFFFF), be(0xFFE5), be(0x1016), be(0x2048), + /* 3E18 */ be(0x0802), be(0x1C60), be(0x0014), be(0x0060), + /* 3E20 */ be(0x2205), be(0x8120), be(0x908F), be(0x6A80), + /* 3E28 */ be(0x6982), be(0x5F9F), be(0x6F46), be(0x4544), + /* 3E30 */ be(0x0005), be(0x8013), be(0x8069), be(0x6A80), + /* 3E38 */ be(0x7000), be(0x0000), be(0x0000), be(0x0000), + /* 3E40 */ be(0x0000), be(0x0000), be(0x0000), be(0x0000), + /* 3E48 */ be(0x0000), be(0x0000), be(0x0000), be(0x0000), + /* 3E50 */ be(0x0000), be(0x0000), be(0x0000), be(0x0000), + /* 3E58 */ be(0x0000), be(0x0000), be(0x0000), be(0x0000), + /* 3E60 */ be(0x0000), be(0x0000), be(0x0000), be(0x0000), + /* 3E68 */ be(0x0000), be(0x0000), be(0x0000), be(0x0000), + /* 3E70 */ be(0x0000), be(0x0000), be(0x0000), be(0x0000), + /* 3E78 */ be(0x0000), be(0x0000), be(0x0000), be(0x0000), + /* 3E80 */ be(0x0000), be(0x0000), be(0x0000), be(0x0000), + /* 3E88 */ be(0x0000), be(0x0000), be(0x0000), be(0x0000), + /* 3E90 */ be(0x0000), be(0x0000), be(0x0000), be(0x0000), + /* 3E98 */ be(0x0000), be(0x0000), be(0x0000), be(0x0000), + /* 3EA0 */ be(0x0000), be(0x0000), be(0x0000), be(0x0000), + /* 3EA8 */ be(0x0000), be(0x0000), be(0x0000), be(0x0000), + /* 3EB0 */ be(0x0000), be(0x0000), be(0x0000)), + + REGS(be(0x3EB6), be(0x004C)), /* ECL */ + + REGS(be(0x3EBA), + be(0xAAAD), /* 3EBA */ + be(0x0086)), /* 3EBC: Bias currents for FSC/ECL */ + + REGS(be(0x3EC0), + be(0x1E00), /* 3EC0: SFbin/SH mode settings */ + be(0x100A), /* 3EC2: CLK divider for ramp for 10 bit 400MH */ + /* 3EC4: FSC clamps for HDR mode and adc comp power down co */ + be(0x3300), + be(0xEA44), /* 3EC6: VLN and clk gating controls */ + be(0x6F6F), /* 3EC8: Txl0 and Txlo1 settings for normal mode */ + be(0x2F4A), /* 3ECA: CDAC/Txlo2/RSTGHI/RSTGLO settings */ + be(0x0506), /* 3ECC: RSTDHI/RSTDLO/CDAC/TXHI settings */ + /* 3ECE: Ramp buffer settings and Booster enable (bits 0-5) */ + be(0x203B), + be(0x13F0), /* 3ED0: TXLO from atest/sf bin settings */ + be(0xA53D), /* 3ED2: Ramp offset */ + be(0x862F), /* 3ED4: TXLO open loop/row driver settings */ + be(0x4081), /* 3ED6: Txlatch fr cfpn rows/vln bias */ + be(0x8003), /* 3ED8: Ramp step setting for 10 bit 400 Mhz */ + be(0xA580), /* 3EDA: Ramp Offset */ + be(0xC000), /* 3EDC: over range for rst and under range for sig */ + be(0xC103)), /* 3EDE: over range for sig and col dec clk settings */ + + /* corrections_recommended_bayer */ + REGS(be(0x3F00), + be(0x0017), /* 3F00: BM_T0 */ + be(0x02DD), /* 3F02: BM_T1 */ + /* 3F04: if Ana_gain less than 2, use noise_floor0, multiply */ + be(0x0020), + /* 3F06: if Ana_gain between 4 and 7, use noise_floor2 and */ + be(0x0040), + /* 3F08: if Ana_gain between 4 and 7, use noise_floor2 and */ + be(0x0070), + /* 3F0A: Define noise_floor0(low address) and noise_floor1 */ + be(0x0101), + be(0x0302)), /* 3F0C: Define noise_floor2 and noise_floor3 */ + + REGS(be(0x3F10), + be(0x0505), /* 3F10: single k factor 0 */ + be(0x0505), /* 3F12: single k factor 1 */ + be(0x0505), /* 3F14: single k factor 2 */ + be(0x01FF), /* 3F16: cross factor 0 */ + be(0x01FF), /* 3F18: cross factor 1 */ + be(0x01FF), /* 3F1A: cross factor 2 */ + be(0x0022)), /* 3F1E */ + + /* GTH_THRES_RTN: 4max,4min filtered out of every 46 samples and */ + REGS(be(0x3F2C), be(0x442E)), + + REGS(be(0x3F3E), + be(0x0000), /* 3F3E: Switch ADC from 12 bit to 10 bit mode */ + be(0x1511), /* 3F40: couple k factor 0 */ + be(0x1511), /* 3F42: couple k factor 1 */ + be(0x0707)), /* 3F44: couple k factor 2 */ +}; + +static void __ar0521_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ar0521_dev *sensor = to_ar0521_dev(sd); + int i; + + if (sensor->reset_gpio) + /* assert RESET signal */ + gpiod_set_value_cansleep(sensor->reset_gpio, 1); + + for (i = ARRAY_SIZE(ar0521_supply_names) - 1; i >= 0; i--) { + if (sensor->supplies[i]) + regulator_disable(sensor->supplies[i]); + } +} + +static int ar0521_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ar0521_dev *sensor = to_ar0521_dev(sd); + + clk_disable_unprepare(sensor->extclk); + __ar0521_power_off(dev); + + return 0; +} + +static int ar0521_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ar0521_dev *sensor = to_ar0521_dev(sd); + unsigned int cnt; + int ret; + + for (cnt = 0; cnt < ARRAY_SIZE(ar0521_supply_names); cnt++) + if (sensor->supplies[cnt]) { + ret = regulator_enable(sensor->supplies[cnt]); + if (ret < 0) + goto off; + + usleep_range(1000, 1500); /* min 1 ms */ + } + + ret = clk_prepare_enable(sensor->extclk); + if (ret < 0) { + v4l2_err(&sensor->sd, "error enabling sensor clock\n"); + goto off; + } + usleep_range(1000, 1500); /* min 1 ms */ + + if (sensor->reset_gpio) + /* deassert RESET signal */ + gpiod_set_value_cansleep(sensor->reset_gpio, 0); + usleep_range(4500, 5000); /* min 45000 clocks */ + + for (cnt = 0; cnt < ARRAY_SIZE(initial_regs); cnt++) { + ret = ar0521_write_regs(sensor, initial_regs[cnt].data, + initial_regs[cnt].count); + if (ret) + goto off; + } + + ret = ar0521_write_reg(sensor, AR0521_REG_SERIAL_FORMAT, + AR0521_REG_SERIAL_FORMAT_MIPI | + sensor->lane_count); + if (ret) + goto off; + + /* set MIPI test mode - disabled for now */ + ret = ar0521_write_reg(sensor, AR0521_REG_HISPI_TEST_MODE, + ((0x40 << sensor->lane_count) - 0x40) | + AR0521_REG_HISPI_TEST_MODE_LP11); + if (ret) + goto off; + + ret = ar0521_write_reg(sensor, AR0521_REG_ROW_SPEED, 0x110 | + 4 / sensor->lane_count); + if (ret) + goto off; + + return 0; +off: + clk_disable_unprepare(sensor->extclk); + __ar0521_power_off(dev); + return ret; +} + +static int ar0521_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct ar0521_dev *sensor = to_ar0521_dev(sd); + + if (code->index) + return -EINVAL; + + code->code = sensor->fmt.code; + return 0; +} + +static int ar0521_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG8_1X8) + return -EINVAL; + + fse->min_width = AR0521_WIDTH_MIN; + fse->max_width = AR0521_WIDTH_MAX; + fse->min_height = AR0521_HEIGHT_MIN; + fse->max_height = AR0521_HEIGHT_MAX; + + return 0; +} + +static int ar0521_pre_streamon(struct v4l2_subdev *sd, u32 flags) +{ + struct ar0521_dev *sensor = to_ar0521_dev(sd); + int ret; + + if (!(flags & V4L2_SUBDEV_PRE_STREAMON_FL_MANUAL_LP)) + return -EACCES; + + ret = pm_runtime_resume_and_get(&sensor->i2c_client->dev); + if (ret < 0) + return ret; + + /* Set LP-11 on clock and data lanes */ + ret = ar0521_write_reg(sensor, AR0521_REG_HISPI_CONTROL_STATUS, + AR0521_REG_HISPI_CONTROL_STATUS_FRAMER_TEST_MODE_ENABLE); + if (ret) + goto err; + + /* Start streaming LP-11 */ + ret = ar0521_write_reg(sensor, AR0521_REG_RESET, + AR0521_REG_RESET_DEFAULTS | + AR0521_REG_RESET_STREAM); + if (ret) + goto err; + return 0; + +err: + pm_runtime_put(&sensor->i2c_client->dev); + return ret; +} + +static int ar0521_post_streamoff(struct v4l2_subdev *sd) +{ + struct ar0521_dev *sensor = to_ar0521_dev(sd); + + pm_runtime_put(&sensor->i2c_client->dev); + return 0; +} + +static int ar0521_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct ar0521_dev *sensor = to_ar0521_dev(sd); + int ret; + + mutex_lock(&sensor->lock); + ret = ar0521_set_stream(sensor, enable); + mutex_unlock(&sensor->lock); + + return ret; +} + +static const struct v4l2_subdev_core_ops ar0521_core_ops = { + .log_status = v4l2_ctrl_subdev_log_status, +}; + +static const struct v4l2_subdev_video_ops ar0521_video_ops = { + .s_stream = ar0521_s_stream, + .pre_streamon = ar0521_pre_streamon, + .post_streamoff = ar0521_post_streamoff, +}; + +static const struct v4l2_subdev_pad_ops ar0521_pad_ops = { + .enum_mbus_code = ar0521_enum_mbus_code, + .enum_frame_size = ar0521_enum_frame_size, + .get_fmt = ar0521_get_fmt, + .set_fmt = ar0521_set_fmt, +}; + +static const struct v4l2_subdev_ops ar0521_subdev_ops = { + .core = &ar0521_core_ops, + .video = &ar0521_video_ops, + .pad = &ar0521_pad_ops, +}; + +static int ar0521_probe(struct i2c_client *client) +{ + struct v4l2_fwnode_endpoint ep = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct device *dev = &client->dev; + struct fwnode_handle *endpoint; + struct ar0521_dev *sensor; + unsigned int cnt; + int ret; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->i2c_client = client; + sensor->fmt.width = AR0521_WIDTH_MAX; + sensor->fmt.height = AR0521_HEIGHT_MAX; + + endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(endpoint, &ep); + fwnode_handle_put(endpoint); + if (ret) { + dev_err(dev, "could not parse endpoint\n"); + return ret; + } + + if (ep.bus_type != V4L2_MBUS_CSI2_DPHY) { + dev_err(dev, "invalid bus type, must be MIPI CSI2\n"); + return -EINVAL; + } + + sensor->lane_count = ep.bus.mipi_csi2.num_data_lanes; + switch (sensor->lane_count) { + case 1: + case 2: + case 4: + break; + default: + dev_err(dev, "invalid number of MIPI data lanes\n"); + return -EINVAL; + } + + /* Get master clock (extclk) */ + sensor->extclk = devm_v4l2_sensor_clk_get(dev, "extclk"); + if (IS_ERR(sensor->extclk)) + return dev_err_probe(dev, PTR_ERR(sensor->extclk), + "failed to get extclk\n"); + + sensor->extclk_freq = clk_get_rate(sensor->extclk); + + if (sensor->extclk_freq < AR0521_EXTCLK_MIN || + sensor->extclk_freq > AR0521_EXTCLK_MAX) { + dev_err(dev, "extclk frequency out of range: %u Hz\n", + sensor->extclk_freq); + return -EINVAL; + } + + /* Request optional reset pin (usually active low) and assert it */ + sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(sensor->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(sensor->reset_gpio), + "failed to get reset gpio\n"); + + v4l2_i2c_subdev_init(&sensor->sd, client, &ar0521_subdev_ops); + + sensor->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); + if (ret) + return ret; + + for (cnt = 0; cnt < ARRAY_SIZE(ar0521_supply_names); cnt++) { + struct regulator *supply = devm_regulator_get(dev, + ar0521_supply_names[cnt]); + + if (IS_ERR(supply)) { + dev_info(dev, "no %s regulator found: %pe\n", + ar0521_supply_names[cnt], supply); + return PTR_ERR(supply); + } + sensor->supplies[cnt] = supply; + } + + mutex_init(&sensor->lock); + + ret = ar0521_init_controls(sensor); + if (ret) + goto entity_cleanup; + + ar0521_adj_fmt(&sensor->fmt); + + ret = v4l2_async_register_subdev(&sensor->sd); + if (ret) + goto free_ctrls; + + /* Turn on the device and enable runtime PM */ + ret = ar0521_power_on(&client->dev); + if (ret) + goto disable; + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + return 0; + +disable: + v4l2_async_unregister_subdev(&sensor->sd); + media_entity_cleanup(&sensor->sd.entity); +free_ctrls: + v4l2_ctrl_handler_free(&sensor->ctrls.handler); +entity_cleanup: + media_entity_cleanup(&sensor->sd.entity); + mutex_destroy(&sensor->lock); + return ret; +} + +static void ar0521_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ar0521_dev *sensor = to_ar0521_dev(sd); + + v4l2_async_unregister_subdev(&sensor->sd); + media_entity_cleanup(&sensor->sd.entity); + v4l2_ctrl_handler_free(&sensor->ctrls.handler); + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + ar0521_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + mutex_destroy(&sensor->lock); +} + +static const struct dev_pm_ops ar0521_pm_ops = { + SET_RUNTIME_PM_OPS(ar0521_power_off, ar0521_power_on, NULL) +}; +static const struct of_device_id ar0521_dt_ids[] = { + {.compatible = "onnn,ar0521"}, + {} +}; +MODULE_DEVICE_TABLE(of, ar0521_dt_ids); + +static struct i2c_driver ar0521_i2c_driver = { + .driver = { + .name = "ar0521", + .pm = &ar0521_pm_ops, + .of_match_table = ar0521_dt_ids, + }, + .probe = ar0521_probe, + .remove = ar0521_remove, +}; + +module_i2c_driver(ar0521_i2c_driver); + +MODULE_DESCRIPTION("AR0521 MIPI Camera subdev driver"); +MODULE_AUTHOR("Krzysztof Hałasa "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/bt819.c b/drivers/media/i2c/bt819.c new file mode 100644 index 000000000..da4410006 --- /dev/null +++ b/drivers/media/i2c/bt819.c @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * bt819 - BT819A VideoStream Decoder (Rockwell Part) + * + * Copyright (C) 1999 Mike Bernson + * Copyright (C) 1998 Dave Perks + * + * Modifications for LML33/DC10plus unified driver + * Copyright (C) 2000 Serguei Miridonov + * + * Changes by Ronald Bultje + * - moved over to linux>=2.4.x i2c protocol (9/9/2002) + * + * This code was modify/ported from the saa7111 driver written + * by Dave Perks. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("Brooktree-819 video decoder driver"); +MODULE_AUTHOR("Mike Bernson & Dave Perks"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +/* ----------------------------------------------------------------------- */ + +struct bt819 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + unsigned char reg[32]; + + v4l2_std_id norm; + int input; + int enable; +}; + +static inline struct bt819 *to_bt819(struct v4l2_subdev *sd) +{ + return container_of(sd, struct bt819, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct bt819, hdl)->sd; +} + +struct timing { + int hactive; + int hdelay; + int vactive; + int vdelay; + int hscale; + int vscale; +}; + +/* for values, see the bt819 datasheet */ +static struct timing timing_data[] = { + {864 - 24, 20, 625 - 2, 1, 0x0504, 0x0000}, + {858 - 24, 20, 525 - 2, 1, 0x00f8, 0x0000}, +}; + +/* ----------------------------------------------------------------------- */ + +static inline int bt819_write(struct bt819 *decoder, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(&decoder->sd); + + decoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int bt819_setbit(struct bt819 *decoder, u8 reg, u8 bit, u8 value) +{ + return bt819_write(decoder, reg, + (decoder->reg[reg] & ~(1 << bit)) | (value ? (1 << bit) : 0)); +} + +static int bt819_write_block(struct bt819 *decoder, const u8 *data, unsigned int len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&decoder->sd); + int ret = -1; + u8 reg; + + /* the bt819 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + u8 block_data[32]; + int block_len; + + while (len >= 2) { + block_len = 0; + block_data[block_len++] = reg = data[0]; + do { + block_data[block_len++] = + decoder->reg[reg++] = data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && block_len < 32); + ret = i2c_master_send(client, block_data, block_len); + if (ret < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + ret = bt819_write(decoder, reg, *data++); + if (ret < 0) + break; + len -= 2; + } + } + + return ret; +} + +static inline int bt819_read(struct bt819 *decoder, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(&decoder->sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int bt819_init(struct v4l2_subdev *sd) +{ + static unsigned char init[] = { + /*0x1f, 0x00,*/ /* Reset */ + 0x01, 0x59, /* 0x01 input format */ + 0x02, 0x00, /* 0x02 temporal decimation */ + 0x03, 0x12, /* 0x03 Cropping msb */ + 0x04, 0x16, /* 0x04 Vertical Delay, lsb */ + 0x05, 0xe0, /* 0x05 Vertical Active lsb */ + 0x06, 0x80, /* 0x06 Horizontal Delay lsb */ + 0x07, 0xd0, /* 0x07 Horizontal Active lsb */ + 0x08, 0x00, /* 0x08 Horizontal Scaling msb */ + 0x09, 0xf8, /* 0x09 Horizontal Scaling lsb */ + 0x0a, 0x00, /* 0x0a Brightness control */ + 0x0b, 0x30, /* 0x0b Miscellaneous control */ + 0x0c, 0xd8, /* 0x0c Luma Gain lsb */ + 0x0d, 0xfe, /* 0x0d Chroma Gain (U) lsb */ + 0x0e, 0xb4, /* 0x0e Chroma Gain (V) msb */ + 0x0f, 0x00, /* 0x0f Hue control */ + 0x12, 0x04, /* 0x12 Output Format */ + 0x13, 0x20, /* 0x13 Vertical Scaling msb 0x00 + chroma comb OFF, line drop scaling, interlace scaling + BUG? Why does turning the chroma comb on screw up color? + Bug in the bt819 stepping on my board? + */ + 0x14, 0x00, /* 0x14 Vertical Scaling lsb */ + 0x16, 0x07, /* 0x16 Video Timing Polarity + ACTIVE=active low + FIELD: high=odd, + vreset=active high, + hreset=active high */ + 0x18, 0x68, /* 0x18 AGC Delay */ + 0x19, 0x5d, /* 0x19 Burst Gate Delay */ + 0x1a, 0x80, /* 0x1a ADC Interface */ + }; + + struct bt819 *decoder = to_bt819(sd); + struct timing *timing = &timing_data[(decoder->norm & V4L2_STD_525_60) ? 1 : 0]; + + init[0x03 * 2 - 1] = + (((timing->vdelay >> 8) & 0x03) << 6) | + (((timing->vactive >> 8) & 0x03) << 4) | + (((timing->hdelay >> 8) & 0x03) << 2) | + ((timing->hactive >> 8) & 0x03); + init[0x04 * 2 - 1] = timing->vdelay & 0xff; + init[0x05 * 2 - 1] = timing->vactive & 0xff; + init[0x06 * 2 - 1] = timing->hdelay & 0xff; + init[0x07 * 2 - 1] = timing->hactive & 0xff; + init[0x08 * 2 - 1] = timing->hscale >> 8; + init[0x09 * 2 - 1] = timing->hscale & 0xff; + /* 0x15 in array is address 0x19 */ + init[0x15 * 2 - 1] = (decoder->norm & V4L2_STD_625_50) ? 115 : 93; /* Chroma burst delay */ + /* reset */ + bt819_write(decoder, 0x1f, 0x00); + mdelay(1); + + /* init */ + return bt819_write_block(decoder, init, sizeof(init)); +} + +/* ----------------------------------------------------------------------- */ + +static int bt819_status(struct v4l2_subdev *sd, u32 *pstatus, v4l2_std_id *pstd) +{ + struct bt819 *decoder = to_bt819(sd); + int status = bt819_read(decoder, 0x00); + int res = V4L2_IN_ST_NO_SIGNAL; + v4l2_std_id std = pstd ? *pstd : V4L2_STD_ALL; + + if ((status & 0x80)) + res = 0; + else + std = V4L2_STD_UNKNOWN; + + if ((status & 0x10)) + std &= V4L2_STD_PAL; + else + std &= V4L2_STD_NTSC; + if (pstd) + *pstd = std; + if (pstatus) + *pstatus = res; + + v4l2_dbg(1, debug, sd, "get status %x\n", status); + return 0; +} + +static int bt819_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + return bt819_status(sd, NULL, std); +} + +static int bt819_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + return bt819_status(sd, status, NULL); +} + +static int bt819_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct bt819 *decoder = to_bt819(sd); + struct timing *timing = NULL; + + v4l2_dbg(1, debug, sd, "set norm %llx\n", (unsigned long long)std); + + if (sd->v4l2_dev == NULL || sd->v4l2_dev->notify == NULL) + v4l2_err(sd, "no notify found!\n"); + + if (std & V4L2_STD_NTSC) { + v4l2_subdev_notify(sd, BT819_FIFO_RESET_LOW, NULL); + bt819_setbit(decoder, 0x01, 0, 1); + bt819_setbit(decoder, 0x01, 1, 0); + bt819_setbit(decoder, 0x01, 5, 0); + bt819_write(decoder, 0x18, 0x68); + bt819_write(decoder, 0x19, 0x5d); + /* bt819_setbit(decoder, 0x1a, 5, 1); */ + timing = &timing_data[1]; + } else if (std & V4L2_STD_PAL) { + v4l2_subdev_notify(sd, BT819_FIFO_RESET_LOW, NULL); + bt819_setbit(decoder, 0x01, 0, 1); + bt819_setbit(decoder, 0x01, 1, 1); + bt819_setbit(decoder, 0x01, 5, 1); + bt819_write(decoder, 0x18, 0x7f); + bt819_write(decoder, 0x19, 0x72); + /* bt819_setbit(decoder, 0x1a, 5, 0); */ + timing = &timing_data[0]; + } else { + v4l2_dbg(1, debug, sd, "unsupported norm %llx\n", + (unsigned long long)std); + return -EINVAL; + } + bt819_write(decoder, 0x03, + (((timing->vdelay >> 8) & 0x03) << 6) | + (((timing->vactive >> 8) & 0x03) << 4) | + (((timing->hdelay >> 8) & 0x03) << 2) | + ((timing->hactive >> 8) & 0x03)); + bt819_write(decoder, 0x04, timing->vdelay & 0xff); + bt819_write(decoder, 0x05, timing->vactive & 0xff); + bt819_write(decoder, 0x06, timing->hdelay & 0xff); + bt819_write(decoder, 0x07, timing->hactive & 0xff); + bt819_write(decoder, 0x08, (timing->hscale >> 8) & 0xff); + bt819_write(decoder, 0x09, timing->hscale & 0xff); + decoder->norm = std; + v4l2_subdev_notify(sd, BT819_FIFO_RESET_HIGH, NULL); + return 0; +} + +static int bt819_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct bt819 *decoder = to_bt819(sd); + + v4l2_dbg(1, debug, sd, "set input %x\n", input); + + if (input > 7) + return -EINVAL; + + if (sd->v4l2_dev == NULL || sd->v4l2_dev->notify == NULL) + v4l2_err(sd, "no notify found!\n"); + + if (decoder->input != input) { + v4l2_subdev_notify(sd, BT819_FIFO_RESET_LOW, NULL); + decoder->input = input; + /* select mode */ + if (decoder->input == 0) { + bt819_setbit(decoder, 0x0b, 6, 0); + bt819_setbit(decoder, 0x1a, 1, 1); + } else { + bt819_setbit(decoder, 0x0b, 6, 1); + bt819_setbit(decoder, 0x1a, 1, 0); + } + v4l2_subdev_notify(sd, BT819_FIFO_RESET_HIGH, NULL); + } + return 0; +} + +static int bt819_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct bt819 *decoder = to_bt819(sd); + + v4l2_dbg(1, debug, sd, "enable output %x\n", enable); + + if (decoder->enable != enable) { + decoder->enable = enable; + bt819_setbit(decoder, 0x16, 7, !enable); + } + return 0; +} + +static int bt819_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct bt819 *decoder = to_bt819(sd); + int temp; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + bt819_write(decoder, 0x0a, ctrl->val); + break; + + case V4L2_CID_CONTRAST: + bt819_write(decoder, 0x0c, ctrl->val & 0xff); + bt819_setbit(decoder, 0x0b, 2, ((ctrl->val >> 8) & 0x01)); + break; + + case V4L2_CID_SATURATION: + bt819_write(decoder, 0x0d, (ctrl->val >> 7) & 0xff); + bt819_setbit(decoder, 0x0b, 1, ((ctrl->val >> 15) & 0x01)); + + /* Ratio between U gain and V gain must stay the same as + the ratio between the default U and V gain values. */ + temp = (ctrl->val * 180) / 254; + bt819_write(decoder, 0x0e, (temp >> 7) & 0xff); + bt819_setbit(decoder, 0x0b, 0, (temp >> 15) & 0x01); + break; + + case V4L2_CID_HUE: + bt819_write(decoder, 0x0f, ctrl->val); + break; + + default: + return -EINVAL; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops bt819_ctrl_ops = { + .s_ctrl = bt819_s_ctrl, +}; + +static const struct v4l2_subdev_video_ops bt819_video_ops = { + .s_std = bt819_s_std, + .s_routing = bt819_s_routing, + .s_stream = bt819_s_stream, + .querystd = bt819_querystd, + .g_input_status = bt819_g_input_status, +}; + +static const struct v4l2_subdev_ops bt819_ops = { + .video = &bt819_video_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int bt819_probe(struct i2c_client *client) +{ + int i, ver; + struct bt819 *decoder; + struct v4l2_subdev *sd; + const char *name; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + decoder = devm_kzalloc(&client->dev, sizeof(*decoder), GFP_KERNEL); + if (decoder == NULL) + return -ENOMEM; + sd = &decoder->sd; + v4l2_i2c_subdev_init(sd, client, &bt819_ops); + + ver = bt819_read(decoder, 0x17); + switch (ver & 0xf0) { + case 0x70: + name = "bt819a"; + break; + case 0x60: + name = "bt817a"; + break; + case 0x20: + name = "bt815a"; + break; + default: + v4l2_dbg(1, debug, sd, + "unknown chip version 0x%02x\n", ver); + return -ENODEV; + } + + v4l_info(client, "%s found @ 0x%x (%s)\n", name, + client->addr << 1, client->adapter->name); + + decoder->norm = V4L2_STD_NTSC; + decoder->input = 0; + decoder->enable = 1; + + i = bt819_init(sd); + if (i < 0) + v4l2_dbg(1, debug, sd, "init status %d\n", i); + + v4l2_ctrl_handler_init(&decoder->hdl, 4); + v4l2_ctrl_new_std(&decoder->hdl, &bt819_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(&decoder->hdl, &bt819_ctrl_ops, + V4L2_CID_CONTRAST, 0, 511, 1, 0xd8); + v4l2_ctrl_new_std(&decoder->hdl, &bt819_ctrl_ops, + V4L2_CID_SATURATION, 0, 511, 1, 0xfe); + v4l2_ctrl_new_std(&decoder->hdl, &bt819_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + sd->ctrl_handler = &decoder->hdl; + if (decoder->hdl.error) { + int err = decoder->hdl.error; + + v4l2_ctrl_handler_free(&decoder->hdl); + return err; + } + v4l2_ctrl_handler_setup(&decoder->hdl); + return 0; +} + +static void bt819_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct bt819 *decoder = to_bt819(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&decoder->hdl); +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id bt819_id[] = { + { .name = "bt819a" }, + { .name = "bt817a" }, + { .name = "bt815a" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bt819_id); + +static struct i2c_driver bt819_driver = { + .driver = { + .name = "bt819", + }, + .probe = bt819_probe, + .remove = bt819_remove, + .id_table = bt819_id, +}; + +module_i2c_driver(bt819_driver); diff --git a/drivers/media/i2c/bt856.c b/drivers/media/i2c/bt856.c new file mode 100644 index 000000000..656719158 --- /dev/null +++ b/drivers/media/i2c/bt856.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * bt856 - BT856A Digital Video Encoder (Rockwell Part) + * + * Copyright (C) 1999 Mike Bernson + * Copyright (C) 1998 Dave Perks + * + * Modifications for LML33/DC10plus unified driver + * Copyright (C) 2000 Serguei Miridonov + * + * This code was modify/ported from the saa7111 driver written + * by Dave Perks. + * + * Changes by Ronald Bultje + * - moved over to linux>=2.4.x i2c protocol (9/9/2002) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("Brooktree-856A video encoder driver"); +MODULE_AUTHOR("Mike Bernson & Dave Perks"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +/* ----------------------------------------------------------------------- */ + +#define BT856_REG_OFFSET 0xDA +#define BT856_NR_REG 6 + +struct bt856 { + struct v4l2_subdev sd; + unsigned char reg[BT856_NR_REG]; + + v4l2_std_id norm; +}; + +static inline struct bt856 *to_bt856(struct v4l2_subdev *sd) +{ + return container_of(sd, struct bt856, sd); +} + +/* ----------------------------------------------------------------------- */ + +static inline int bt856_write(struct bt856 *encoder, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(&encoder->sd); + + encoder->reg[reg - BT856_REG_OFFSET] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int bt856_setbit(struct bt856 *encoder, u8 reg, u8 bit, u8 value) +{ + return bt856_write(encoder, reg, + (encoder->reg[reg - BT856_REG_OFFSET] & ~(1 << bit)) | + (value ? (1 << bit) : 0)); +} + +static void bt856_dump(struct bt856 *encoder) +{ + int i; + + v4l2_info(&encoder->sd, "register dump:\n"); + for (i = 0; i < BT856_NR_REG; i += 2) + printk(KERN_CONT " %02x", encoder->reg[i]); + printk(KERN_CONT "\n"); +} + +/* ----------------------------------------------------------------------- */ + +static int bt856_init(struct v4l2_subdev *sd, u32 arg) +{ + struct bt856 *encoder = to_bt856(sd); + + /* This is just for testing!!! */ + v4l2_dbg(1, debug, sd, "init\n"); + bt856_write(encoder, 0xdc, 0x18); + bt856_write(encoder, 0xda, 0); + bt856_write(encoder, 0xde, 0); + + bt856_setbit(encoder, 0xdc, 3, 1); + /*bt856_setbit(encoder, 0xdc, 6, 0);*/ + bt856_setbit(encoder, 0xdc, 4, 1); + + if (encoder->norm & V4L2_STD_NTSC) + bt856_setbit(encoder, 0xdc, 2, 0); + else + bt856_setbit(encoder, 0xdc, 2, 1); + + bt856_setbit(encoder, 0xdc, 1, 1); + bt856_setbit(encoder, 0xde, 4, 0); + bt856_setbit(encoder, 0xde, 3, 1); + if (debug != 0) + bt856_dump(encoder); + return 0; +} + +static int bt856_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct bt856 *encoder = to_bt856(sd); + + v4l2_dbg(1, debug, sd, "set norm %llx\n", (unsigned long long)std); + + if (std & V4L2_STD_NTSC) { + bt856_setbit(encoder, 0xdc, 2, 0); + } else if (std & V4L2_STD_PAL) { + bt856_setbit(encoder, 0xdc, 2, 1); + bt856_setbit(encoder, 0xda, 0, 0); + /*bt856_setbit(encoder, 0xda, 0, 1);*/ + } else { + return -EINVAL; + } + encoder->norm = std; + if (debug != 0) + bt856_dump(encoder); + return 0; +} + +static int bt856_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct bt856 *encoder = to_bt856(sd); + + v4l2_dbg(1, debug, sd, "set input %d\n", input); + + /* We only have video bus. + * input= 0: input is from bt819 + * input= 1: input is from ZR36060 */ + switch (input) { + case 0: + bt856_setbit(encoder, 0xde, 4, 0); + bt856_setbit(encoder, 0xde, 3, 1); + bt856_setbit(encoder, 0xdc, 3, 1); + bt856_setbit(encoder, 0xdc, 6, 0); + break; + case 1: + bt856_setbit(encoder, 0xde, 4, 0); + bt856_setbit(encoder, 0xde, 3, 1); + bt856_setbit(encoder, 0xdc, 3, 1); + bt856_setbit(encoder, 0xdc, 6, 1); + break; + case 2: /* Color bar */ + bt856_setbit(encoder, 0xdc, 3, 0); + bt856_setbit(encoder, 0xde, 4, 1); + break; + default: + return -EINVAL; + } + + if (debug != 0) + bt856_dump(encoder); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops bt856_core_ops = { + .init = bt856_init, +}; + +static const struct v4l2_subdev_video_ops bt856_video_ops = { + .s_std_output = bt856_s_std_output, + .s_routing = bt856_s_routing, +}; + +static const struct v4l2_subdev_ops bt856_ops = { + .core = &bt856_core_ops, + .video = &bt856_video_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int bt856_probe(struct i2c_client *client) +{ + struct bt856 *encoder; + struct v4l2_subdev *sd; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + encoder = devm_kzalloc(&client->dev, sizeof(*encoder), GFP_KERNEL); + if (encoder == NULL) + return -ENOMEM; + sd = &encoder->sd; + v4l2_i2c_subdev_init(sd, client, &bt856_ops); + encoder->norm = V4L2_STD_NTSC; + + bt856_write(encoder, 0xdc, 0x18); + bt856_write(encoder, 0xda, 0); + bt856_write(encoder, 0xde, 0); + + bt856_setbit(encoder, 0xdc, 3, 1); + /*bt856_setbit(encoder, 0xdc, 6, 0);*/ + bt856_setbit(encoder, 0xdc, 4, 1); + + if (encoder->norm & V4L2_STD_NTSC) + bt856_setbit(encoder, 0xdc, 2, 0); + else + bt856_setbit(encoder, 0xdc, 2, 1); + + bt856_setbit(encoder, 0xdc, 1, 1); + bt856_setbit(encoder, 0xde, 4, 0); + bt856_setbit(encoder, 0xde, 3, 1); + + if (debug != 0) + bt856_dump(encoder); + return 0; +} + +static void bt856_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); +} + +static const struct i2c_device_id bt856_id[] = { + { .name = "bt856" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bt856_id); + +static struct i2c_driver bt856_driver = { + .driver = { + .name = "bt856", + }, + .probe = bt856_probe, + .remove = bt856_remove, + .id_table = bt856_id, +}; + +module_i2c_driver(bt856_driver); diff --git a/drivers/media/i2c/bt866.c b/drivers/media/i2c/bt866.c new file mode 100644 index 000000000..f5104c7d9 --- /dev/null +++ b/drivers/media/i2c/bt866.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + bt866 - BT866 Digital Video Encoder (Rockwell Part) + + Copyright (C) 1999 Mike Bernson + Copyright (C) 1998 Dave Perks + + Modifications for LML33/DC10plus unified driver + Copyright (C) 2000 Serguei Miridonov + + This code was modify/ported from the saa7111 driver written + by Dave Perks. + + This code was adapted for the bt866 by Christer Weinigel and ported + to 2.6 by Martin Samuelsson. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("Brooktree-866 video encoder driver"); +MODULE_AUTHOR("Mike Bernson & Dave Perks"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +/* ----------------------------------------------------------------------- */ + +struct bt866 { + struct v4l2_subdev sd; + u8 reg[256]; +}; + +static inline struct bt866 *to_bt866(struct v4l2_subdev *sd) +{ + return container_of(sd, struct bt866, sd); +} + +static int bt866_write(struct bt866 *encoder, u8 subaddr, u8 data) +{ + struct i2c_client *client = v4l2_get_subdevdata(&encoder->sd); + u8 buffer[2]; + int err; + + buffer[0] = subaddr; + buffer[1] = data; + + encoder->reg[subaddr] = data; + + v4l_dbg(1, debug, client, "write 0x%02x = 0x%02x\n", subaddr, data); + + for (err = 0; err < 3;) { + if (i2c_master_send(client, buffer, 2) == 2) + break; + err++; + v4l_warn(client, "error #%d writing to 0x%02x\n", + err, subaddr); + schedule_timeout_interruptible(msecs_to_jiffies(100)); + } + if (err == 3) { + v4l_warn(client, "giving up\n"); + return -1; + } + + return 0; +} + +static int bt866_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) +{ + v4l2_dbg(1, debug, sd, "set norm %llx\n", (unsigned long long)std); + + /* Only PAL supported by this driver at the moment! */ + if (!(std & V4L2_STD_NTSC)) + return -EINVAL; + return 0; +} + +static int bt866_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + static const __u8 init[] = { + 0xc8, 0xcc, /* CRSCALE */ + 0xca, 0x91, /* CBSCALE */ + 0xcc, 0x24, /* YC16 | OSDNUM */ + 0xda, 0x00, /* */ + 0xdc, 0x24, /* SETMODE | PAL */ + 0xde, 0x02, /* EACTIVE */ + + /* overlay colors */ + 0x70, 0xEB, 0x90, 0x80, 0xB0, 0x80, /* white */ + 0x72, 0xA2, 0x92, 0x8E, 0xB2, 0x2C, /* yellow */ + 0x74, 0x83, 0x94, 0x2C, 0xB4, 0x9C, /* cyan */ + 0x76, 0x70, 0x96, 0x3A, 0xB6, 0x48, /* green */ + 0x78, 0x54, 0x98, 0xC6, 0xB8, 0xB8, /* magenta */ + 0x7A, 0x41, 0x9A, 0xD4, 0xBA, 0x64, /* red */ + 0x7C, 0x23, 0x9C, 0x72, 0xBC, 0xD4, /* blue */ + 0x7E, 0x10, 0x9E, 0x80, 0xBE, 0x80, /* black */ + + 0x60, 0xEB, 0x80, 0x80, 0xc0, 0x80, /* white */ + 0x62, 0xA2, 0x82, 0x8E, 0xc2, 0x2C, /* yellow */ + 0x64, 0x83, 0x84, 0x2C, 0xc4, 0x9C, /* cyan */ + 0x66, 0x70, 0x86, 0x3A, 0xc6, 0x48, /* green */ + 0x68, 0x54, 0x88, 0xC6, 0xc8, 0xB8, /* magenta */ + 0x6A, 0x41, 0x8A, 0xD4, 0xcA, 0x64, /* red */ + 0x6C, 0x23, 0x8C, 0x72, 0xcC, 0xD4, /* blue */ + 0x6E, 0x10, 0x8E, 0x80, 0xcE, 0x80, /* black */ + }; + struct bt866 *encoder = to_bt866(sd); + u8 val; + int i; + + for (i = 0; i < ARRAY_SIZE(init) / 2; i += 2) + bt866_write(encoder, init[i], init[i+1]); + + val = encoder->reg[0xdc]; + + if (input == 0) + val |= 0x40; /* CBSWAP */ + else + val &= ~0x40; /* !CBSWAP */ + + bt866_write(encoder, 0xdc, val); + + val = encoder->reg[0xcc]; + if (input == 2) + val |= 0x01; /* OSDBAR */ + else + val &= ~0x01; /* !OSDBAR */ + bt866_write(encoder, 0xcc, val); + + v4l2_dbg(1, debug, sd, "set input %d\n", input); + + switch (input) { + case 0: + case 1: + case 2: + break; + default: + return -EINVAL; + } + return 0; +} + +#if 0 +/* Code to setup square pixels, might be of some use in the future, + but is currently unused. */ + val = encoder->reg[0xdc]; + if (*iarg) + val |= 1; /* SQUARE */ + else + val &= ~1; /* !SQUARE */ + bt866_write(client, 0xdc, val); +#endif + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_video_ops bt866_video_ops = { + .s_std_output = bt866_s_std_output, + .s_routing = bt866_s_routing, +}; + +static const struct v4l2_subdev_ops bt866_ops = { + .video = &bt866_video_ops, +}; + +static int bt866_probe(struct i2c_client *client) +{ + struct bt866 *encoder; + struct v4l2_subdev *sd; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + encoder = devm_kzalloc(&client->dev, sizeof(*encoder), GFP_KERNEL); + if (encoder == NULL) + return -ENOMEM; + sd = &encoder->sd; + v4l2_i2c_subdev_init(sd, client, &bt866_ops); + return 0; +} + +static void bt866_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); +} + +static const struct i2c_device_id bt866_id[] = { + { .name = "bt866" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bt866_id); + +static struct i2c_driver bt866_driver = { + .driver = { + .name = "bt866", + }, + .probe = bt866_probe, + .remove = bt866_remove, + .id_table = bt866_id, +}; + +module_i2c_driver(bt866_driver); diff --git a/drivers/media/i2c/ccs-pll.c b/drivers/media/i2c/ccs-pll.c new file mode 100644 index 000000000..1605cfa5d --- /dev/null +++ b/drivers/media/i2c/ccs-pll.c @@ -0,0 +1,910 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/ccs-pll.c + * + * Generic MIPI CCS/SMIA/SMIA++ PLL calculator + * + * Copyright (C) 2020 Intel Corporation + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus + */ + +#include +#include +#include +#include + +#include "ccs-pll.h" + +/* Return an even number or one. */ +static inline u32 clk_div_even(u32 a) +{ + return max_t(u32, 1, a & ~1); +} + +/* Return an even number or one. */ +static inline u32 clk_div_even_up(u32 a) +{ + if (a == 1) + return 1; + return (a + 1) & ~1; +} + +static inline u32 is_one_or_even(u32 a) +{ + if (a == 1) + return 1; + if (a & 1) + return 0; + + return 1; +} + +static inline u32 one_or_more(u32 a) +{ + return a ?: 1; +} + +static int bounds_check(struct device *dev, u32 val, + u32 min, u32 max, const char *prefix, + char *str) +{ + if (val >= min && val <= max) + return 0; + + dev_dbg(dev, "%s_%s out of bounds: %d (%d--%d)\n", prefix, + str, val, min, max); + + return -EINVAL; +} + +#define PLL_OP 1 +#define PLL_VT 2 + +static const char *pll_string(unsigned int which) +{ + switch (which) { + case PLL_OP: + return "op"; + case PLL_VT: + return "vt"; + } + + return NULL; +} + +#define PLL_FL(f) CCS_PLL_FLAG_##f + +static void print_pll(struct device *dev, const struct ccs_pll *pll) +{ + const struct { + const struct ccs_pll_branch_fr *fr; + const struct ccs_pll_branch_bk *bk; + unsigned int which; + } branches[] = { + { &pll->vt_fr, &pll->vt_bk, PLL_VT }, + { &pll->op_fr, &pll->op_bk, PLL_OP } + }, *br; + unsigned int i; + + dev_dbg(dev, "ext_clk_freq_hz\t\t%u\n", pll->ext_clk_freq_hz); + + for (i = 0, br = branches; i < ARRAY_SIZE(branches); i++, br++) { + const char *s = pll_string(br->which); + + if (pll->flags & CCS_PLL_FLAG_DUAL_PLL || + br->which == PLL_VT) { + dev_dbg(dev, "%s_pre_pll_clk_div\t\t%u\n", s, + br->fr->pre_pll_clk_div); + dev_dbg(dev, "%s_pll_multiplier\t\t%u\n", s, + br->fr->pll_multiplier); + + dev_dbg(dev, "%s_pll_ip_clk_freq_hz\t%u\n", s, + br->fr->pll_ip_clk_freq_hz); + dev_dbg(dev, "%s_pll_op_clk_freq_hz\t%u\n", s, + br->fr->pll_op_clk_freq_hz); + } + + if (!(pll->flags & CCS_PLL_FLAG_NO_OP_CLOCKS) || + br->which == PLL_VT) { + dev_dbg(dev, "%s_sys_clk_div\t\t%u\n", s, + br->bk->sys_clk_div); + dev_dbg(dev, "%s_pix_clk_div\t\t%u\n", s, + br->bk->pix_clk_div); + + dev_dbg(dev, "%s_sys_clk_freq_hz\t%u\n", s, + br->bk->sys_clk_freq_hz); + dev_dbg(dev, "%s_pix_clk_freq_hz\t%u\n", s, + br->bk->pix_clk_freq_hz); + } + } + + dev_dbg(dev, "pixel rate in pixel array:\t%u\n", + pll->pixel_rate_pixel_array); + dev_dbg(dev, "pixel rate on CSI-2 bus:\t%u\n", + pll->pixel_rate_csi); +} + +static void print_pll_flags(struct device *dev, struct ccs_pll *pll) +{ + dev_dbg(dev, "PLL flags%s%s%s%s%s%s%s%s%s%s%s\n", + pll->flags & PLL_FL(OP_PIX_CLOCK_PER_LANE) ? " op-pix-clock-per-lane" : "", + pll->flags & PLL_FL(EVEN_PLL_MULTIPLIER) ? " even-pll-multiplier" : "", + pll->flags & PLL_FL(NO_OP_CLOCKS) ? " no-op-clocks" : "", + pll->flags & PLL_FL(LANE_SPEED_MODEL) ? " lane-speed" : "", + pll->flags & PLL_FL(EXT_IP_PLL_DIVIDER) ? + " ext-ip-pll-divider" : "", + pll->flags & PLL_FL(FLEXIBLE_OP_PIX_CLK_DIV) ? + " flexible-op-pix-div" : "", + pll->flags & PLL_FL(FIFO_DERATING) ? " fifo-derating" : "", + pll->flags & PLL_FL(FIFO_OVERRATING) ? " fifo-overrating" : "", + pll->flags & PLL_FL(DUAL_PLL) ? " dual-pll" : "", + pll->flags & PLL_FL(OP_SYS_DDR) ? " op-sys-ddr" : "", + pll->flags & PLL_FL(OP_PIX_DDR) ? " op-pix-ddr" : ""); +} + +static u32 op_sys_ddr(u32 flags) +{ + return flags & CCS_PLL_FLAG_OP_SYS_DDR ? 1 : 0; +} + +static u32 op_pix_ddr(u32 flags) +{ + return flags & CCS_PLL_FLAG_OP_PIX_DDR ? 1 : 0; +} + +static int check_fr_bounds(struct device *dev, + const struct ccs_pll_limits *lim, + const struct ccs_pll *pll, unsigned int which) +{ + const struct ccs_pll_branch_limits_fr *lim_fr; + const struct ccs_pll_branch_fr *pll_fr; + const char *s = pll_string(which); + int rval; + + if (which == PLL_OP) { + lim_fr = &lim->op_fr; + pll_fr = &pll->op_fr; + } else { + lim_fr = &lim->vt_fr; + pll_fr = &pll->vt_fr; + } + + rval = bounds_check(dev, pll_fr->pre_pll_clk_div, + lim_fr->min_pre_pll_clk_div, + lim_fr->max_pre_pll_clk_div, s, "pre_pll_clk_div"); + + if (!rval) + rval = bounds_check(dev, pll_fr->pll_ip_clk_freq_hz, + lim_fr->min_pll_ip_clk_freq_hz, + lim_fr->max_pll_ip_clk_freq_hz, + s, "pll_ip_clk_freq_hz"); + if (!rval) + rval = bounds_check(dev, pll_fr->pll_multiplier, + lim_fr->min_pll_multiplier, + lim_fr->max_pll_multiplier, + s, "pll_multiplier"); + if (!rval) + rval = bounds_check(dev, pll_fr->pll_op_clk_freq_hz, + lim_fr->min_pll_op_clk_freq_hz, + lim_fr->max_pll_op_clk_freq_hz, + s, "pll_op_clk_freq_hz"); + + return rval; +} + +static int check_bk_bounds(struct device *dev, + const struct ccs_pll_limits *lim, + const struct ccs_pll *pll, unsigned int which) +{ + const struct ccs_pll_branch_limits_bk *lim_bk; + const struct ccs_pll_branch_bk *pll_bk; + const char *s = pll_string(which); + int rval; + + if (which == PLL_OP) { + if (pll->flags & CCS_PLL_FLAG_NO_OP_CLOCKS) + return 0; + + lim_bk = &lim->op_bk; + pll_bk = &pll->op_bk; + } else { + lim_bk = &lim->vt_bk; + pll_bk = &pll->vt_bk; + } + + rval = bounds_check(dev, pll_bk->sys_clk_div, + lim_bk->min_sys_clk_div, + lim_bk->max_sys_clk_div, s, "op_sys_clk_div"); + if (!rval) + rval = bounds_check(dev, pll_bk->sys_clk_freq_hz, + lim_bk->min_sys_clk_freq_hz, + lim_bk->max_sys_clk_freq_hz, + s, "sys_clk_freq_hz"); + if (!rval) + rval = bounds_check(dev, pll_bk->sys_clk_div, + lim_bk->min_sys_clk_div, + lim_bk->max_sys_clk_div, + s, "sys_clk_div"); + if (!rval) + rval = bounds_check(dev, pll_bk->pix_clk_freq_hz, + lim_bk->min_pix_clk_freq_hz, + lim_bk->max_pix_clk_freq_hz, + s, "pix_clk_freq_hz"); + + return rval; +} + +static int check_ext_bounds(struct device *dev, const struct ccs_pll *pll) +{ + if (!(pll->flags & CCS_PLL_FLAG_FIFO_DERATING) && + pll->pixel_rate_pixel_array > pll->pixel_rate_csi) { + dev_dbg(dev, "device does not support derating\n"); + return -EINVAL; + } + + if (!(pll->flags & CCS_PLL_FLAG_FIFO_OVERRATING) && + pll->pixel_rate_pixel_array < pll->pixel_rate_csi) { + dev_dbg(dev, "device does not support overrating\n"); + return -EINVAL; + } + + return 0; +} + +static void +ccs_pll_find_vt_sys_div(struct device *dev, const struct ccs_pll_limits *lim, + struct ccs_pll *pll, struct ccs_pll_branch_fr *pll_fr, + u16 min_vt_div, u16 max_vt_div, + u16 *min_sys_div, u16 *max_sys_div) +{ + /* + * Find limits for sys_clk_div. Not all values are possible with all + * values of pix_clk_div. + */ + *min_sys_div = lim->vt_bk.min_sys_clk_div; + dev_dbg(dev, "min_sys_div: %u\n", *min_sys_div); + *min_sys_div = max_t(u16, *min_sys_div, + DIV_ROUND_UP(min_vt_div, + lim->vt_bk.max_pix_clk_div)); + dev_dbg(dev, "min_sys_div: max_vt_pix_clk_div: %u\n", *min_sys_div); + *min_sys_div = max_t(u16, *min_sys_div, + pll_fr->pll_op_clk_freq_hz + / lim->vt_bk.max_sys_clk_freq_hz); + dev_dbg(dev, "min_sys_div: max_pll_op_clk_freq_hz: %u\n", *min_sys_div); + *min_sys_div = clk_div_even_up(*min_sys_div); + dev_dbg(dev, "min_sys_div: one or even: %u\n", *min_sys_div); + + *max_sys_div = lim->vt_bk.max_sys_clk_div; + dev_dbg(dev, "max_sys_div: %u\n", *max_sys_div); + *max_sys_div = min_t(u16, *max_sys_div, + DIV_ROUND_UP(max_vt_div, + lim->vt_bk.min_pix_clk_div)); + dev_dbg(dev, "max_sys_div: min_vt_pix_clk_div: %u\n", *max_sys_div); + *max_sys_div = min_t(u16, *max_sys_div, + DIV_ROUND_UP(pll_fr->pll_op_clk_freq_hz, + lim->vt_bk.min_pix_clk_freq_hz)); + dev_dbg(dev, "max_sys_div: min_vt_pix_clk_freq_hz: %u\n", *max_sys_div); +} + +#define CPHY_CONST 7 +#define DPHY_CONST 16 +#define PHY_CONST_DIV 16 + +static inline int +__ccs_pll_calculate_vt_tree(struct device *dev, + const struct ccs_pll_limits *lim, + struct ccs_pll *pll, u32 mul, u32 div) +{ + const struct ccs_pll_branch_limits_fr *lim_fr = &lim->vt_fr; + const struct ccs_pll_branch_limits_bk *lim_bk = &lim->vt_bk; + struct ccs_pll_branch_fr *pll_fr = &pll->vt_fr; + struct ccs_pll_branch_bk *pll_bk = &pll->vt_bk; + u32 more_mul; + u16 best_pix_div = SHRT_MAX >> 1, best_div = lim_bk->max_sys_clk_div; + u16 vt_div, min_sys_div, max_sys_div, sys_div; + + pll_fr->pll_ip_clk_freq_hz = + pll->ext_clk_freq_hz / pll_fr->pre_pll_clk_div; + + dev_dbg(dev, "vt_pll_ip_clk_freq_hz %u\n", pll_fr->pll_ip_clk_freq_hz); + + more_mul = one_or_more(DIV_ROUND_UP(lim_fr->min_pll_op_clk_freq_hz, + pll_fr->pll_ip_clk_freq_hz * mul)); + + dev_dbg(dev, "more_mul: %u\n", more_mul); + more_mul *= DIV_ROUND_UP(lim_fr->min_pll_multiplier, mul * more_mul); + dev_dbg(dev, "more_mul2: %u\n", more_mul); + + if (pll->flags & CCS_PLL_FLAG_EVEN_PLL_MULTIPLIER && + (mul & 1) && (more_mul & 1)) + more_mul <<= 1; + + pll_fr->pll_multiplier = mul * more_mul; + if (pll_fr->pll_multiplier > lim_fr->max_pll_multiplier) { + dev_dbg(dev, "pll multiplier %u too high\n", + pll_fr->pll_multiplier); + return -EINVAL; + } + + pll_fr->pll_op_clk_freq_hz = + pll_fr->pll_ip_clk_freq_hz * pll_fr->pll_multiplier; + if (pll_fr->pll_op_clk_freq_hz > lim_fr->max_pll_op_clk_freq_hz) { + dev_dbg(dev, "too high OP clock %u\n", + pll_fr->pll_op_clk_freq_hz); + return -EINVAL; + } + + vt_div = div * more_mul; + + ccs_pll_find_vt_sys_div(dev, lim, pll, pll_fr, vt_div, vt_div, + &min_sys_div, &max_sys_div); + + max_sys_div = (vt_div & 1) ? 1 : max_sys_div; + + dev_dbg(dev, "vt min/max_sys_div: %u,%u\n", min_sys_div, max_sys_div); + + for (sys_div = min_sys_div; sys_div <= max_sys_div; + sys_div += 2 - (sys_div & 1)) { + u16 pix_div; + + if (vt_div % sys_div) + continue; + + pix_div = vt_div / sys_div; + + if (pix_div < lim_bk->min_pix_clk_div || + pix_div > lim_bk->max_pix_clk_div) { + dev_dbg(dev, + "pix_div %u too small or too big (%u--%u)\n", + pix_div, + lim_bk->min_pix_clk_div, + lim_bk->max_pix_clk_div); + continue; + } + + dev_dbg(dev, "sys/pix/best_pix: %u,%u,%u\n", sys_div, pix_div, + best_pix_div); + + if (pix_div * sys_div <= best_pix_div) { + best_pix_div = pix_div; + best_div = pix_div * sys_div; + } + } + if (best_pix_div == SHRT_MAX >> 1) + return -EINVAL; + + pll_bk->sys_clk_div = best_div / best_pix_div; + pll_bk->pix_clk_div = best_pix_div; + + pll_bk->sys_clk_freq_hz = + pll_fr->pll_op_clk_freq_hz / pll_bk->sys_clk_div; + pll_bk->pix_clk_freq_hz = + pll_bk->sys_clk_freq_hz / pll_bk->pix_clk_div; + + pll->pixel_rate_pixel_array = + pll_bk->pix_clk_freq_hz * pll->vt_lanes; + + return 0; +} + +static int ccs_pll_calculate_vt_tree(struct device *dev, + const struct ccs_pll_limits *lim, + struct ccs_pll *pll) +{ + const struct ccs_pll_branch_limits_fr *lim_fr = &lim->vt_fr; + struct ccs_pll_branch_fr *pll_fr = &pll->vt_fr; + u16 min_pre_pll_clk_div = lim_fr->min_pre_pll_clk_div; + u16 max_pre_pll_clk_div = lim_fr->max_pre_pll_clk_div; + u32 pre_mul, pre_div; + + pre_div = gcd(pll->pixel_rate_csi, + pll->ext_clk_freq_hz * pll->vt_lanes); + pre_mul = pll->pixel_rate_csi / pre_div; + pre_div = pll->ext_clk_freq_hz * pll->vt_lanes / pre_div; + + /* Make sure PLL input frequency is within limits */ + max_pre_pll_clk_div = + min_t(u16, max_pre_pll_clk_div, + DIV_ROUND_UP(pll->ext_clk_freq_hz, + lim_fr->min_pll_ip_clk_freq_hz)); + + min_pre_pll_clk_div = max_t(u16, min_pre_pll_clk_div, + pll->ext_clk_freq_hz / + lim_fr->max_pll_ip_clk_freq_hz); + if (!(pll->flags & CCS_PLL_FLAG_EXT_IP_PLL_DIVIDER)) + min_pre_pll_clk_div = clk_div_even(min_pre_pll_clk_div); + + dev_dbg(dev, "vt min/max_pre_pll_clk_div: %u,%u\n", + min_pre_pll_clk_div, max_pre_pll_clk_div); + + for (pll_fr->pre_pll_clk_div = min_pre_pll_clk_div; + pll_fr->pre_pll_clk_div <= max_pre_pll_clk_div; + pll_fr->pre_pll_clk_div += + (pll->flags & CCS_PLL_FLAG_EXT_IP_PLL_DIVIDER) ? 1 : + 2 - (pll_fr->pre_pll_clk_div & 1)) { + u32 mul, div; + int rval; + + div = gcd(pre_mul * pll_fr->pre_pll_clk_div, pre_div); + mul = pre_mul * pll_fr->pre_pll_clk_div / div; + div = pre_div / div; + + dev_dbg(dev, "vt pre-div/mul/div: %u,%u,%u\n", + pll_fr->pre_pll_clk_div, mul, div); + + rval = __ccs_pll_calculate_vt_tree(dev, lim, pll, + mul, div); + if (rval) + continue; + + rval = check_fr_bounds(dev, lim, pll, PLL_VT); + if (rval) + continue; + + rval = check_bk_bounds(dev, lim, pll, PLL_VT); + if (rval) + continue; + + return 0; + } + + dev_dbg(dev, "unable to compute VT pre_pll divisor\n"); + return -EINVAL; +} + +static int +ccs_pll_calculate_vt(struct device *dev, const struct ccs_pll_limits *lim, + const struct ccs_pll_branch_limits_bk *op_lim_bk, + struct ccs_pll *pll, struct ccs_pll_branch_fr *pll_fr, + struct ccs_pll_branch_bk *op_pll_bk, bool cphy, + u32 phy_const) +{ + u16 sys_div; + u16 best_pix_div = SHRT_MAX >> 1; + u16 vt_op_binning_div; + u16 min_vt_div, max_vt_div, vt_div; + u16 min_sys_div, max_sys_div; + + if (pll->flags & CCS_PLL_FLAG_NO_OP_CLOCKS) + goto out_calc_pixel_rate; + + /* + * Find out whether a sensor supports derating. If it does not, VT and + * OP domains are required to run at the same pixel rate. + */ + if (!(pll->flags & CCS_PLL_FLAG_FIFO_DERATING)) { + min_vt_div = + op_pll_bk->sys_clk_div * op_pll_bk->pix_clk_div + * pll->vt_lanes * phy_const / pll->op_lanes + / (PHY_CONST_DIV << op_pix_ddr(pll->flags)); + } else { + /* + * Some sensors perform analogue binning and some do this + * digitally. The ones doing this digitally can be roughly be + * found out using this formula. The ones doing this digitally + * should run at higher clock rate, so smaller divisor is used + * on video timing side. + */ + if (lim->min_line_length_pck_bin > lim->min_line_length_pck + / pll->binning_horizontal) + vt_op_binning_div = pll->binning_horizontal; + else + vt_op_binning_div = 1; + dev_dbg(dev, "vt_op_binning_div: %u\n", vt_op_binning_div); + + /* + * Profile 2 supports vt_pix_clk_div E [4, 10] + * + * Horizontal binning can be used as a base for difference in + * divisors. One must make sure that horizontal blanking is + * enough to accommodate the CSI-2 sync codes. + * + * Take scaling factor and number of VT lanes into account as well. + * + * Find absolute limits for the factor of vt divider. + */ + dev_dbg(dev, "scale_m: %u\n", pll->scale_m); + min_vt_div = + DIV_ROUND_UP(pll->bits_per_pixel + * op_pll_bk->sys_clk_div * pll->scale_n + * pll->vt_lanes * phy_const, + (pll->flags & + CCS_PLL_FLAG_LANE_SPEED_MODEL ? + pll->csi2.lanes : 1) + * vt_op_binning_div * pll->scale_m + * PHY_CONST_DIV << op_pix_ddr(pll->flags)); + } + + /* Find smallest and biggest allowed vt divisor. */ + dev_dbg(dev, "min_vt_div: %u\n", min_vt_div); + min_vt_div = max_t(u16, min_vt_div, + DIV_ROUND_UP(pll_fr->pll_op_clk_freq_hz, + lim->vt_bk.max_pix_clk_freq_hz)); + dev_dbg(dev, "min_vt_div: max_vt_pix_clk_freq_hz: %u\n", + min_vt_div); + min_vt_div = max_t(u16, min_vt_div, lim->vt_bk.min_pix_clk_div + * lim->vt_bk.min_sys_clk_div); + dev_dbg(dev, "min_vt_div: min_vt_clk_div: %u\n", min_vt_div); + + max_vt_div = lim->vt_bk.max_sys_clk_div * lim->vt_bk.max_pix_clk_div; + dev_dbg(dev, "max_vt_div: %u\n", max_vt_div); + max_vt_div = min_t(u16, max_vt_div, + DIV_ROUND_UP(pll_fr->pll_op_clk_freq_hz, + lim->vt_bk.min_pix_clk_freq_hz)); + dev_dbg(dev, "max_vt_div: min_vt_pix_clk_freq_hz: %u\n", + max_vt_div); + + ccs_pll_find_vt_sys_div(dev, lim, pll, pll_fr, min_vt_div, + max_vt_div, &min_sys_div, &max_sys_div); + + /* + * Find pix_div such that a legal pix_div * sys_div results + * into a value which is not smaller than div, the desired + * divisor. + */ + for (vt_div = min_vt_div; vt_div <= max_vt_div; vt_div++) { + u16 __max_sys_div = vt_div & 1 ? 1 : max_sys_div; + + for (sys_div = min_sys_div; sys_div <= __max_sys_div; + sys_div += 2 - (sys_div & 1)) { + u16 pix_div; + u16 rounded_div; + + pix_div = DIV_ROUND_UP(vt_div, sys_div); + + if (pix_div < lim->vt_bk.min_pix_clk_div + || pix_div > lim->vt_bk.max_pix_clk_div) { + dev_dbg(dev, + "pix_div %u too small or too big (%u--%u)\n", + pix_div, + lim->vt_bk.min_pix_clk_div, + lim->vt_bk.max_pix_clk_div); + continue; + } + + rounded_div = roundup(vt_div, best_pix_div); + + /* Check if this one is better. */ + if (pix_div * sys_div <= rounded_div) + best_pix_div = pix_div; + + /* Bail out if we've already found the best value. */ + if (vt_div == rounded_div) + break; + } + if (best_pix_div < SHRT_MAX >> 1) + break; + } + if (best_pix_div == SHRT_MAX >> 1) + return -EINVAL; + + pll->vt_bk.sys_clk_div = DIV_ROUND_UP(vt_div, best_pix_div); + pll->vt_bk.pix_clk_div = best_pix_div; + + pll->vt_bk.sys_clk_freq_hz = + pll_fr->pll_op_clk_freq_hz / pll->vt_bk.sys_clk_div; + pll->vt_bk.pix_clk_freq_hz = + pll->vt_bk.sys_clk_freq_hz / pll->vt_bk.pix_clk_div; + +out_calc_pixel_rate: + pll->pixel_rate_pixel_array = + pll->vt_bk.pix_clk_freq_hz * pll->vt_lanes; + + return 0; +} + +/* + * Heuristically guess the PLL tree for a given common multiplier and + * divisor. Begin with the operational timing and continue to video + * timing once operational timing has been verified. + * + * @mul is the PLL multiplier and @div is the common divisor + * (pre_pll_clk_div and op_sys_clk_div combined). The final PLL + * multiplier will be a multiple of @mul. + * + * @return Zero on success, error code on error. + */ +static int +ccs_pll_calculate_op(struct device *dev, const struct ccs_pll_limits *lim, + const struct ccs_pll_branch_limits_fr *op_lim_fr, + const struct ccs_pll_branch_limits_bk *op_lim_bk, + struct ccs_pll *pll, struct ccs_pll_branch_fr *op_pll_fr, + struct ccs_pll_branch_bk *op_pll_bk, u32 mul, + u32 div, u32 op_sys_clk_freq_hz_sdr, u32 l, + bool cphy, u32 phy_const) +{ + /* + * Higher multipliers (and divisors) are often required than + * necessitated by the external clock and the output clocks. + * There are limits for all values in the clock tree. These + * are the minimum and maximum multiplier for mul. + */ + u32 more_mul_min, more_mul_max; + u32 more_mul_factor; + u32 i; + + /* + * Get pre_pll_clk_div so that our pll_op_clk_freq_hz won't be + * too high. + */ + dev_dbg(dev, "op_pre_pll_clk_div %u\n", op_pll_fr->pre_pll_clk_div); + + /* Don't go above max pll multiplier. */ + more_mul_max = op_lim_fr->max_pll_multiplier / mul; + dev_dbg(dev, "more_mul_max: max_op_pll_multiplier check: %u\n", + more_mul_max); + /* Don't go above max pll op frequency. */ + more_mul_max = + min_t(u32, + more_mul_max, + op_lim_fr->max_pll_op_clk_freq_hz + / (pll->ext_clk_freq_hz / + op_pll_fr->pre_pll_clk_div * mul)); + dev_dbg(dev, "more_mul_max: max_pll_op_clk_freq_hz check: %u\n", + more_mul_max); + /* Don't go above the division capability of op sys clock divider. */ + more_mul_max = min(more_mul_max, + op_lim_bk->max_sys_clk_div * op_pll_fr->pre_pll_clk_div + / div); + dev_dbg(dev, "more_mul_max: max_op_sys_clk_div check: %u\n", + more_mul_max); + /* Ensure we won't go above max_pll_multiplier. */ + more_mul_max = min(more_mul_max, op_lim_fr->max_pll_multiplier / mul); + dev_dbg(dev, "more_mul_max: min_pll_multiplier check: %u\n", + more_mul_max); + + /* Ensure we won't go below min_pll_op_clk_freq_hz. */ + more_mul_min = DIV_ROUND_UP(op_lim_fr->min_pll_op_clk_freq_hz, + pll->ext_clk_freq_hz / + op_pll_fr->pre_pll_clk_div * mul); + dev_dbg(dev, "more_mul_min: min_op_pll_op_clk_freq_hz check: %u\n", + more_mul_min); + /* Ensure we won't go below min_pll_multiplier. */ + more_mul_min = max(more_mul_min, + DIV_ROUND_UP(op_lim_fr->min_pll_multiplier, mul)); + dev_dbg(dev, "more_mul_min: min_op_pll_multiplier check: %u\n", + more_mul_min); + + if (more_mul_min > more_mul_max) { + dev_dbg(dev, + "unable to compute more_mul_min and more_mul_max\n"); + return -EINVAL; + } + + more_mul_factor = lcm(div, op_pll_fr->pre_pll_clk_div) / div; + dev_dbg(dev, "more_mul_factor: %u\n", more_mul_factor); + more_mul_factor = lcm(more_mul_factor, op_lim_bk->min_sys_clk_div); + dev_dbg(dev, "more_mul_factor: min_op_sys_clk_div: %d\n", + more_mul_factor); + i = roundup(more_mul_min, more_mul_factor); + if (!is_one_or_even(i)) + i <<= 1; + + if (pll->flags & CCS_PLL_FLAG_EVEN_PLL_MULTIPLIER && + mul & 1 && i & 1) + i <<= 1; + + dev_dbg(dev, "final more_mul: %u\n", i); + if (i > more_mul_max) { + dev_dbg(dev, "final more_mul is bad, max %u\n", more_mul_max); + return -EINVAL; + } + + op_pll_fr->pll_multiplier = mul * i; + op_pll_bk->sys_clk_div = div * i / op_pll_fr->pre_pll_clk_div; + dev_dbg(dev, "op_sys_clk_div: %u\n", op_pll_bk->sys_clk_div); + + op_pll_fr->pll_ip_clk_freq_hz = pll->ext_clk_freq_hz + / op_pll_fr->pre_pll_clk_div; + + op_pll_fr->pll_op_clk_freq_hz = op_pll_fr->pll_ip_clk_freq_hz + * op_pll_fr->pll_multiplier; + + if (pll->flags & CCS_PLL_FLAG_LANE_SPEED_MODEL) + op_pll_bk->pix_clk_div = + (pll->bits_per_pixel + * pll->op_lanes * (phy_const << op_sys_ddr(pll->flags)) + / PHY_CONST_DIV / pll->csi2.lanes / l) + >> op_pix_ddr(pll->flags); + else + op_pll_bk->pix_clk_div = + (pll->bits_per_pixel + * (phy_const << op_sys_ddr(pll->flags)) + / PHY_CONST_DIV / l) >> op_pix_ddr(pll->flags); + + op_pll_bk->pix_clk_freq_hz = + (op_sys_clk_freq_hz_sdr >> op_pix_ddr(pll->flags)) + / op_pll_bk->pix_clk_div; + op_pll_bk->sys_clk_freq_hz = + op_sys_clk_freq_hz_sdr >> op_sys_ddr(pll->flags); + + dev_dbg(dev, "op_pix_clk_div: %u\n", op_pll_bk->pix_clk_div); + + return 0; +} + +int ccs_pll_calculate(struct device *dev, const struct ccs_pll_limits *lim, + struct ccs_pll *pll) +{ + const struct ccs_pll_branch_limits_fr *op_lim_fr; + const struct ccs_pll_branch_limits_bk *op_lim_bk; + struct ccs_pll_branch_fr *op_pll_fr; + struct ccs_pll_branch_bk *op_pll_bk; + bool cphy = pll->bus_type == CCS_PLL_BUS_TYPE_CSI2_CPHY; + u32 phy_const = cphy ? CPHY_CONST : DPHY_CONST; + u32 op_sys_clk_freq_hz_sdr; + u16 min_op_pre_pll_clk_div; + u16 max_op_pre_pll_clk_div; + u32 mul, div; + u32 l = (!pll->op_bits_per_lane || + pll->op_bits_per_lane >= pll->bits_per_pixel) ? 1 : 2; + u32 i; + int rval = -EINVAL; + + print_pll_flags(dev, pll); + + if (!(pll->flags & CCS_PLL_FLAG_LANE_SPEED_MODEL)) { + pll->op_lanes = 1; + pll->vt_lanes = 1; + } + + if (pll->flags & CCS_PLL_FLAG_DUAL_PLL) { + op_lim_fr = &lim->op_fr; + op_lim_bk = &lim->op_bk; + op_pll_fr = &pll->op_fr; + op_pll_bk = &pll->op_bk; + } else if (pll->flags & CCS_PLL_FLAG_NO_OP_CLOCKS) { + /* + * If there's no OP PLL at all, use the VT values + * instead. The OP values are ignored for the rest of + * the PLL calculation. + */ + op_lim_fr = &lim->vt_fr; + op_lim_bk = &lim->vt_bk; + op_pll_fr = &pll->vt_fr; + op_pll_bk = &pll->vt_bk; + } else { + op_lim_fr = &lim->vt_fr; + op_lim_bk = &lim->op_bk; + op_pll_fr = &pll->vt_fr; + op_pll_bk = &pll->op_bk; + } + + if (!pll->op_lanes || !pll->vt_lanes || !pll->bits_per_pixel || + !pll->ext_clk_freq_hz || !pll->link_freq || !pll->scale_m || + !op_lim_fr->min_pll_ip_clk_freq_hz || + !op_lim_fr->max_pll_ip_clk_freq_hz || + !op_lim_fr->min_pll_op_clk_freq_hz || + !op_lim_fr->max_pll_op_clk_freq_hz || + !op_lim_bk->max_sys_clk_div || !op_lim_fr->max_pll_multiplier) + return -EINVAL; + + /* + * Make sure op_pix_clk_div will be integer --- unless flexible + * op_pix_clk_div is supported + */ + if (!(pll->flags & CCS_PLL_FLAG_FLEXIBLE_OP_PIX_CLK_DIV) && + (pll->bits_per_pixel * pll->op_lanes) % + (pll->csi2.lanes * l << op_pix_ddr(pll->flags))) { + dev_dbg(dev, "op_pix_clk_div not an integer (bpp %u, op lanes %u, lanes %u, l %u)\n", + pll->bits_per_pixel, pll->op_lanes, pll->csi2.lanes, l); + return -EINVAL; + } + + dev_dbg(dev, "vt_lanes: %u\n", pll->vt_lanes); + dev_dbg(dev, "op_lanes: %u\n", pll->op_lanes); + + dev_dbg(dev, "binning: %ux%u\n", pll->binning_horizontal, + pll->binning_vertical); + + switch (pll->bus_type) { + case CCS_PLL_BUS_TYPE_CSI2_DPHY: + case CCS_PLL_BUS_TYPE_CSI2_CPHY: + op_sys_clk_freq_hz_sdr = pll->link_freq * 2 + * (pll->flags & CCS_PLL_FLAG_LANE_SPEED_MODEL ? + 1 : pll->csi2.lanes); + break; + default: + return -EINVAL; + } + + pll->pixel_rate_csi = + div_u64((uint64_t)op_sys_clk_freq_hz_sdr + * (pll->flags & CCS_PLL_FLAG_LANE_SPEED_MODEL ? + pll->csi2.lanes : 1) * PHY_CONST_DIV, + phy_const * pll->bits_per_pixel * l); + + /* Figure out limits for OP pre-pll divider based on extclk */ + dev_dbg(dev, "min / max op_pre_pll_clk_div: %u / %u\n", + op_lim_fr->min_pre_pll_clk_div, op_lim_fr->max_pre_pll_clk_div); + max_op_pre_pll_clk_div = + min_t(u16, op_lim_fr->max_pre_pll_clk_div, + DIV_ROUND_UP(pll->ext_clk_freq_hz, + op_lim_fr->min_pll_ip_clk_freq_hz)); + min_op_pre_pll_clk_div = + max_t(u16, op_lim_fr->min_pre_pll_clk_div, + DIV_ROUND_UP(pll->ext_clk_freq_hz, + op_lim_fr->max_pll_ip_clk_freq_hz)); + dev_dbg(dev, "pre-pll check: min / max op_pre_pll_clk_div: %u / %u\n", + min_op_pre_pll_clk_div, max_op_pre_pll_clk_div); + + i = gcd(op_sys_clk_freq_hz_sdr, + pll->ext_clk_freq_hz << op_pix_ddr(pll->flags)); + mul = op_sys_clk_freq_hz_sdr / i; + div = (pll->ext_clk_freq_hz << op_pix_ddr(pll->flags)) / i; + dev_dbg(dev, "mul %u / div %u\n", mul, div); + + min_op_pre_pll_clk_div = + max_t(u16, min_op_pre_pll_clk_div, + clk_div_even_up( + mul / + one_or_more( + DIV_ROUND_UP(op_lim_fr->max_pll_op_clk_freq_hz, + pll->ext_clk_freq_hz)))); + if (!(pll->flags & CCS_PLL_FLAG_EXT_IP_PLL_DIVIDER)) + min_op_pre_pll_clk_div = clk_div_even(min_op_pre_pll_clk_div); + dev_dbg(dev, "pll_op check: min / max op_pre_pll_clk_div: %u / %u\n", + min_op_pre_pll_clk_div, max_op_pre_pll_clk_div); + + for (op_pll_fr->pre_pll_clk_div = min_op_pre_pll_clk_div; + op_pll_fr->pre_pll_clk_div <= max_op_pre_pll_clk_div; + op_pll_fr->pre_pll_clk_div += + (pll->flags & CCS_PLL_FLAG_EXT_IP_PLL_DIVIDER) ? 1 : + 2 - (op_pll_fr->pre_pll_clk_div & 1)) { + rval = ccs_pll_calculate_op(dev, lim, op_lim_fr, op_lim_bk, pll, + op_pll_fr, op_pll_bk, mul, div, + op_sys_clk_freq_hz_sdr, l, cphy, + phy_const); + if (rval) + continue; + + rval = check_fr_bounds(dev, lim, pll, + pll->flags & CCS_PLL_FLAG_DUAL_PLL ? + PLL_OP : PLL_VT); + if (rval) + continue; + + rval = check_bk_bounds(dev, lim, pll, PLL_OP); + if (rval) + continue; + + if (pll->flags & CCS_PLL_FLAG_DUAL_PLL) + break; + + rval = ccs_pll_calculate_vt(dev, lim, op_lim_bk, pll, op_pll_fr, + op_pll_bk, cphy, phy_const); + if (rval) + continue; + + rval = check_bk_bounds(dev, lim, pll, PLL_VT); + if (rval) + continue; + rval = check_ext_bounds(dev, pll); + if (rval) + continue; + + break; + } + + if (rval) { + dev_dbg(dev, "unable to compute OP pre_pll divisor\n"); + return rval; + } + + if (pll->flags & CCS_PLL_FLAG_DUAL_PLL) { + rval = ccs_pll_calculate_vt_tree(dev, lim, pll); + + if (rval) + return rval; + } + + print_pll(dev, pll); + + return 0; +} +EXPORT_SYMBOL_GPL(ccs_pll_calculate); + +MODULE_AUTHOR("Sakari Ailus "); +MODULE_DESCRIPTION("Generic MIPI CCS/SMIA/SMIA++ PLL calculator"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/ccs-pll.h b/drivers/media/i2c/ccs-pll.h new file mode 100644 index 000000000..e22903931 --- /dev/null +++ b/drivers/media/i2c/ccs-pll.h @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/i2c/ccs-pll.h + * + * Generic MIPI CCS/SMIA/SMIA++ PLL calculator + * + * Copyright (C) 2020 Intel Corporation + * Copyright (C) 2012 Nokia Corporation + * Contact: Sakari Ailus + */ + +#ifndef CCS_PLL_H +#define CCS_PLL_H + +#include + +/* CSI-2 or CCP-2 */ +#define CCS_PLL_BUS_TYPE_CSI2_DPHY 0x00 +#define CCS_PLL_BUS_TYPE_CSI2_CPHY 0x01 + +/* Old SMIA and implementation specific flags. */ +/* OP PIX clock is for all lanes in total normally. */ +#define CCS_PLL_FLAG_OP_PIX_CLOCK_PER_LANE BIT(0) +/* If set, the PLL multipliers are required to be even. */ +#define CCS_PLL_FLAG_EVEN_PLL_MULTIPLIER BIT(3) + +/* CCS PLL flags */ + +/* The sensor doesn't have OP clocks at all. */ +#define CCS_PLL_FLAG_NO_OP_CLOCKS BIT(1) +/* System speed model if this flag is unset. */ +#define CCS_PLL_FLAG_LANE_SPEED_MODEL BIT(2) +/* If set, the pre-PLL divider may have odd values, too. */ +#define CCS_PLL_FLAG_EXT_IP_PLL_DIVIDER BIT(4) +/* + * If set, the OP PIX clock doesn't have to exactly match with data rate, it may + * be higher. See "OP Domain Formulas" in MIPI CCS 1.1 spec. + */ +#define CCS_PLL_FLAG_FLEXIBLE_OP_PIX_CLK_DIV BIT(5) +/* If set, the VT domain may run faster than the OP domain. */ +#define CCS_PLL_FLAG_FIFO_DERATING BIT(6) +/* If set, the VT domain may run slower than the OP domain. */ +#define CCS_PLL_FLAG_FIFO_OVERRATING BIT(7) +/* If set, the PLL tree has two PLLs instead of one. */ +#define CCS_PLL_FLAG_DUAL_PLL BIT(8) +/* + * If set, the OP SYS clock is a dual data rate clock, transferring two bits per + * cycle instead of one. + */ +#define CCS_PLL_FLAG_OP_SYS_DDR BIT(9) +/* + * If set, the OP PIX clock is a dual data rate clock, transferring two pixels + * per cycle instead of one. + */ +#define CCS_PLL_FLAG_OP_PIX_DDR BIT(10) + +/** + * struct ccs_pll_branch_fr - CCS PLL configuration (front) + * + * A single branch front-end of the CCS PLL tree. + * + * @pre_pll_clk_div: Pre-PLL clock divisor + * @pll_multiplier: PLL multiplier + * @pll_ip_clk_freq_hz: PLL input clock frequency + * @pll_op_clk_freq_hz: PLL output clock frequency + */ +struct ccs_pll_branch_fr { + u16 pre_pll_clk_div; + u16 pll_multiplier; + u32 pll_ip_clk_freq_hz; + u32 pll_op_clk_freq_hz; +}; + +/** + * struct ccs_pll_branch_bk - CCS PLL configuration (back) + * + * A single branch back-end of the CCS PLL tree. + * + * @sys_clk_div: System clock divider + * @pix_clk_div: Pixel clock divider + * @sys_clk_freq_hz: System clock frequency + * @pix_clk_freq_hz: Pixel clock frequency + */ +struct ccs_pll_branch_bk { + u16 sys_clk_div; + u16 pix_clk_div; + u32 sys_clk_freq_hz; + u32 pix_clk_freq_hz; +}; + +/** + * struct ccs_pll - Full CCS PLL configuration + * + * All information required to calculate CCS PLL configuration. + * + * @bus_type: Type of the data bus, CCS_PLL_BUS_TYPE_* (input) + * @op_lanes: Number of operational lanes (input) + * @vt_lanes: Number of video timing lanes (input) + * @csi2: CSI-2 related parameters + * @csi2.lanes: The number of the CSI-2 data lanes (input) + * @binning_vertical: Vertical binning factor (input) + * @binning_horizontal: Horizontal binning factor (input) + * @scale_m: Downscaling factor, M component, [16, max] (input) + * @scale_n: Downscaling factor, N component, typically 16 (input) + * @bits_per_pixel: Bits per pixel on the output data bus (input) + * @op_bits_per_lane: Number of bits per OP lane (input) + * @flags: CCS_PLL_FLAG_* (input) + * @link_freq: Chosen link frequency (input) + * @ext_clk_freq_hz: External clock frequency, i.e. the sensor's input clock + * (input) + * @vt_fr: Video timing front-end configuration (output) + * @vt_bk: Video timing back-end configuration (output) + * @op_fr: Operational timing front-end configuration (output) + * @op_bk: Operational timing back-end configuration (output) + * @pixel_rate_csi: Pixel rate on the output data bus (output) + * @pixel_rate_pixel_array: Nominal pixel rate in the sensor's pixel array + * (output) + */ +struct ccs_pll { + /* input values */ + u8 bus_type; + u8 op_lanes; + u8 vt_lanes; + struct { + u8 lanes; + } csi2; + u8 binning_horizontal; + u8 binning_vertical; + u8 scale_m; + u8 scale_n; + u8 bits_per_pixel; + u8 op_bits_per_lane; + u16 flags; + u32 link_freq; + u32 ext_clk_freq_hz; + + /* output values */ + struct ccs_pll_branch_fr vt_fr; + struct ccs_pll_branch_bk vt_bk; + struct ccs_pll_branch_fr op_fr; + struct ccs_pll_branch_bk op_bk; + + u32 pixel_rate_csi; + u32 pixel_rate_pixel_array; +}; + +/** + * struct ccs_pll_branch_limits_fr - CCS PLL front-end limits + * + * @min_pre_pll_clk_div: Minimum pre-PLL clock divider + * @max_pre_pll_clk_div: Maximum pre-PLL clock divider + * @min_pll_ip_clk_freq_hz: Minimum PLL input clock frequency + * @max_pll_ip_clk_freq_hz: Maximum PLL input clock frequency + * @min_pll_multiplier: Minimum PLL multiplier + * @max_pll_multiplier: Maximum PLL multiplier + * @min_pll_op_clk_freq_hz: Minimum PLL output clock frequency + * @max_pll_op_clk_freq_hz: Maximum PLL output clock frequency + */ +struct ccs_pll_branch_limits_fr { + u16 min_pre_pll_clk_div; + u16 max_pre_pll_clk_div; + u32 min_pll_ip_clk_freq_hz; + u32 max_pll_ip_clk_freq_hz; + u16 min_pll_multiplier; + u16 max_pll_multiplier; + u32 min_pll_op_clk_freq_hz; + u32 max_pll_op_clk_freq_hz; +}; + +/** + * struct ccs_pll_branch_limits_bk - CCS PLL back-end limits + * + * @min_sys_clk_div: Minimum system clock divider + * @max_sys_clk_div: Maximum system clock divider + * @min_sys_clk_freq_hz: Minimum system clock frequency + * @max_sys_clk_freq_hz: Maximum system clock frequency + * @min_pix_clk_div: Minimum pixel clock divider + * @max_pix_clk_div: Maximum pixel clock divider + * @min_pix_clk_freq_hz: Minimum pixel clock frequency + * @max_pix_clk_freq_hz: Maximum pixel clock frequency + */ +struct ccs_pll_branch_limits_bk { + u16 min_sys_clk_div; + u16 max_sys_clk_div; + u32 min_sys_clk_freq_hz; + u32 max_sys_clk_freq_hz; + u16 min_pix_clk_div; + u16 max_pix_clk_div; + u32 min_pix_clk_freq_hz; + u32 max_pix_clk_freq_hz; +}; + +/** + * struct ccs_pll_limits - CCS PLL limits + * + * @min_ext_clk_freq_hz: Minimum external clock frequency + * @max_ext_clk_freq_hz: Maximum external clock frequency + * @vt_fr: Video timing front-end limits + * @vt_bk: Video timing back-end limits + * @op_fr: Operational timing front-end limits + * @op_bk: Operational timing back-end limits + * @min_line_length_pck_bin: Minimum line length in pixels, with binning + * @min_line_length_pck: Minimum line length in pixels without binning + */ +struct ccs_pll_limits { + /* Strict PLL limits */ + u32 min_ext_clk_freq_hz; + u32 max_ext_clk_freq_hz; + + struct ccs_pll_branch_limits_fr vt_fr; + struct ccs_pll_branch_limits_bk vt_bk; + struct ccs_pll_branch_limits_fr op_fr; + struct ccs_pll_branch_limits_bk op_bk; + + /* Other relevant limits */ + u32 min_line_length_pck_bin; + u32 min_line_length_pck; +}; + +struct device; + +/** + * ccs_pll_calculate - Calculate CCS PLL configuration based on input parameters + * + * @dev: Device pointer, used for printing messages + * @limits: Limits specific to the sensor + * @pll: Given PLL configuration + * + * Calculate the CCS PLL configuration based on the limits as well as given + * device specific, system specific or user configured input data. + */ +int ccs_pll_calculate(struct device *dev, const struct ccs_pll_limits *limits, + struct ccs_pll *pll); + +#endif /* CCS_PLL_H */ diff --git a/drivers/media/i2c/ccs/Kconfig b/drivers/media/i2c/ccs/Kconfig new file mode 100644 index 000000000..710a729ae --- /dev/null +++ b/drivers/media/i2c/ccs/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_CCS + tristate "MIPI CCS/SMIA++/SMIA sensor support" + depends on HAVE_CLK + select V4L2_CCI_I2C + select VIDEO_CCS_PLL + help + This is a generic driver for MIPI CCS, SMIA++ and SMIA compliant + camera sensors. diff --git a/drivers/media/i2c/ccs/Makefile b/drivers/media/i2c/ccs/Makefile new file mode 100644 index 000000000..44601ba8c --- /dev/null +++ b/drivers/media/i2c/ccs/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +ccs-objs += ccs-core.o ccs-reg-access.o \ + ccs-quirk.o ccs-limits.o ccs-data.o +obj-$(CONFIG_VIDEO_CCS) += ccs.o + +ccflags-y += -I $(srctree)/drivers/media/i2c diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c new file mode 100644 index 000000000..8e25f970f --- /dev/null +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -0,0 +1,3732 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/ccs/ccs-core.c + * + * Generic driver for MIPI CCS/SMIA/SMIA++ compliant camera sensors + * + * Copyright (C) 2020 Intel Corporation + * Copyright (C) 2010--2012 Nokia Corporation + * Contact: Sakari Ailus + * + * Based on smiapp driver by Vimarsh Zutshi + * Based on jt8ev1.c by Vimarsh Zutshi + * Based on smia-sensor.c by Tuukka Toivonen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ccs.h" + +#define CCS_ALIGN_DIM(dim, flags) \ + ((flags) & V4L2_SEL_FLAG_GE \ + ? ALIGN((dim), 2) \ + : (dim) & ~1) + +static struct ccs_limit_offset { + u16 lim; + u16 info; +} ccs_limit_offsets[CCS_L_LAST + 1]; + +/* + * ccs_module_idents - supported camera modules + */ +static const struct ccs_module_ident ccs_module_idents[] = { + CCS_IDENT_L(0x01, 0x022b, -1, "vs6555"), + CCS_IDENT_L(0x01, 0x022e, -1, "vw6558"), + CCS_IDENT_L(0x07, 0x7698, -1, "ovm7698"), + CCS_IDENT_L(0x0b, 0x4242, -1, "smiapp-003"), + CCS_IDENT_L(0x0c, 0x208a, -1, "tcm8330md"), + CCS_IDENT_LQ(0x0c, 0x2134, -1, "tcm8500md", &smiapp_tcm8500md_quirk), + CCS_IDENT_L(0x0c, 0x213e, -1, "et8en2"), + CCS_IDENT_L(0x0c, 0x2184, -1, "tcm8580md"), + CCS_IDENT_LQ(0x0c, 0x560f, -1, "jt8ew9", &smiapp_jt8ew9_quirk), + CCS_IDENT_LQ(0x10, 0x4141, -1, "jt8ev1", &smiapp_jt8ev1_quirk), + CCS_IDENT_LQ(0x10, 0x4241, -1, "imx125es", &smiapp_imx125es_quirk), +}; + +#define CCS_DEVICE_FLAG_IS_SMIA BIT(0) + +struct ccs_device { + unsigned char flags; +}; + +static const char * const ccs_regulators[] = { "vcore", "vio", "vana" }; + +/* + * + * Dynamic Capability Identification + * + */ + +static void ccs_assign_limit(void *ptr, unsigned int width, u32 val) +{ + switch (width) { + case sizeof(u8): + *(u8 *)ptr = val; + break; + case sizeof(u16): + *(u16 *)ptr = val; + break; + case sizeof(u32): + *(u32 *)ptr = val; + break; + } +} + +static int ccs_limit_ptr(struct ccs_sensor *sensor, unsigned int limit, + unsigned int offset, void **__ptr) +{ + const struct ccs_limit *linfo; + + if (WARN_ON(limit >= CCS_L_LAST)) + return -EINVAL; + + linfo = &ccs_limits[ccs_limit_offsets[limit].info]; + + if (WARN_ON(!sensor->ccs_limits) || + WARN_ON(offset + CCI_REG_WIDTH_BYTES(linfo->reg) > + ccs_limit_offsets[limit + 1].lim)) + return -EINVAL; + + *__ptr = sensor->ccs_limits + ccs_limit_offsets[limit].lim + offset; + + return 0; +} + +void ccs_replace_limit(struct ccs_sensor *sensor, + unsigned int limit, unsigned int offset, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + const struct ccs_limit *linfo; + void *ptr; + int ret; + + ret = ccs_limit_ptr(sensor, limit, offset, &ptr); + if (ret) + return; + + linfo = &ccs_limits[ccs_limit_offsets[limit].info]; + + dev_dbg(&client->dev, "quirk: 0x%8.8x \"%s\" %u = %u, 0x%x\n", + linfo->reg, linfo->name, offset, val, val); + + ccs_assign_limit(ptr, CCI_REG_WIDTH_BYTES(linfo->reg), val); +} + +u32 ccs_get_limit(struct ccs_sensor *sensor, unsigned int limit, + unsigned int offset) +{ + void *ptr; + u32 val; + int ret; + + ret = ccs_limit_ptr(sensor, limit, offset, &ptr); + if (ret) + return 0; + + switch (CCI_REG_WIDTH_BYTES(ccs_limits[ccs_limit_offsets[limit].info].reg)) { + case sizeof(u8): + val = *(u8 *)ptr; + break; + case sizeof(u16): + val = *(u16 *)ptr; + break; + case sizeof(u32): + val = *(u32 *)ptr; + break; + default: + WARN_ON(1); + return 0; + } + + return ccs_reg_conv(sensor, ccs_limits[limit].reg, val); +} + +static int ccs_read_all_limits(struct ccs_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + void *ptr, *alloc, *end; + unsigned int i, l; + int ret; + + kfree(sensor->ccs_limits); + sensor->ccs_limits = NULL; + + alloc = kzalloc(ccs_limit_offsets[CCS_L_LAST].lim, GFP_KERNEL); + if (!alloc) + return -ENOMEM; + + end = alloc + ccs_limit_offsets[CCS_L_LAST].lim; + + sensor->ccs_limits = alloc; + + for (i = 0, l = 0, ptr = alloc; ccs_limits[i].size; i++) { + u32 reg = ccs_limits[i].reg; + unsigned int width = CCI_REG_WIDTH_BYTES(reg); + unsigned int j; + + if (l == CCS_L_LAST) { + dev_err(&client->dev, + "internal error --- end of limit array\n"); + ret = -EINVAL; + goto out_err; + } + + for (j = 0; j < ccs_limits[i].size / width; + j++, reg += width, ptr += width) { + char str[16] = ""; + u32 val; + + ret = ccs_read_addr_noconv(sensor, reg, &val); + if (ret) + goto out_err; + + if (ptr + width > end) { + dev_err(&client->dev, + "internal error --- no room for regs\n"); + ret = -EINVAL; + goto out_err; + } + + if (!val && j) + break; + + ccs_assign_limit(ptr, width, val); + +#ifdef CONFIG_DYNAMIC_DEBUG + if (reg & (CCS_FL_FLOAT_IREAL | CCS_FL_IREAL)) + snprintf(str, sizeof(str), ", %u", + ccs_reg_conv(sensor, reg, val)); +#endif + + dev_dbg(&client->dev, + "0x%8.8x \"%s\" = %u, 0x%x%s\n", + reg, ccs_limits[i].name, val, val, str); + } + + if (ccs_limits[i].flags & CCS_L_FL_SAME_REG) + continue; + + l++; + ptr = alloc + ccs_limit_offsets[l].lim; + } + + if (l != CCS_L_LAST) { + dev_err(&client->dev, + "internal error --- insufficient limits\n"); + ret = -EINVAL; + goto out_err; + } + + if (CCS_LIM(sensor, SCALER_N_MIN) < 16) + ccs_replace_limit(sensor, CCS_L_SCALER_N_MIN, 0, 16); + + return 0; + +out_err: + sensor->ccs_limits = NULL; + kfree(alloc); + + return ret; +} + +static u8 ccs_mipi_csi2_data_type(unsigned int bpp) +{ + switch (bpp) { + case 6: + return MIPI_CSI2_DT_RAW6; + case 7: + return MIPI_CSI2_DT_RAW7; + case 8: + return MIPI_CSI2_DT_RAW8; + case 10: + return MIPI_CSI2_DT_RAW10; + case 12: + return MIPI_CSI2_DT_RAW12; + case 14: + return MIPI_CSI2_DT_RAW14; + case 16: + return MIPI_CSI2_DT_RAW16; + case 20: + return MIPI_CSI2_DT_RAW20; + case 24: + return MIPI_CSI2_DT_RAW24; + default: + WARN_ON(1); + return 0; + } +} + +static int ccs_read_frame_fmt(struct ccs_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + u8 fmt_model_type, fmt_model_subtype, ncol_desc, nrow_desc; + unsigned int i; + int pixel_count = 0; + int line_count = 0; + + fmt_model_type = CCS_LIM(sensor, FRAME_FORMAT_MODEL_TYPE); + fmt_model_subtype = CCS_LIM(sensor, FRAME_FORMAT_MODEL_SUBTYPE); + + ncol_desc = (fmt_model_subtype + & CCS_FRAME_FORMAT_MODEL_SUBTYPE_COLUMNS_MASK) + >> CCS_FRAME_FORMAT_MODEL_SUBTYPE_COLUMNS_SHIFT; + nrow_desc = fmt_model_subtype + & CCS_FRAME_FORMAT_MODEL_SUBTYPE_ROWS_MASK; + + dev_dbg(&client->dev, "format_model_type %s\n", + fmt_model_type == CCS_FRAME_FORMAT_MODEL_TYPE_2_BYTE + ? "2 byte" : + fmt_model_type == CCS_FRAME_FORMAT_MODEL_TYPE_4_BYTE + ? "4 byte" : "is simply bad"); + + dev_dbg(&client->dev, "%u column and %u row descriptors\n", + ncol_desc, nrow_desc); + + for (i = 0; i < ncol_desc + nrow_desc; i++) { + u32 desc; + u32 pixelcode; + u32 pixels; + char *which; + char *what; + + if (fmt_model_type == CCS_FRAME_FORMAT_MODEL_TYPE_2_BYTE) { + desc = CCS_LIM_AT(sensor, FRAME_FORMAT_DESCRIPTOR, i); + + pixelcode = + (desc + & CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_MASK) + >> CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_SHIFT; + pixels = desc & CCS_FRAME_FORMAT_DESCRIPTOR_PIXELS_MASK; + } else if (fmt_model_type + == CCS_FRAME_FORMAT_MODEL_TYPE_4_BYTE) { + desc = CCS_LIM_AT(sensor, FRAME_FORMAT_DESCRIPTOR_4, i); + + pixelcode = + (desc + & CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_MASK) + >> CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_SHIFT; + pixels = desc & + CCS_FRAME_FORMAT_DESCRIPTOR_4_PIXELS_MASK; + } else { + dev_dbg(&client->dev, + "invalid frame format model type %u\n", + fmt_model_type); + return -EINVAL; + } + + if (i < ncol_desc) + which = "columns"; + else + which = "rows"; + + switch (pixelcode) { + case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_EMBEDDED: + what = "embedded"; + break; + case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_DUMMY_PIXEL: + what = "dummy"; + break; + case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_BLACK_PIXEL: + what = "black"; + break; + case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_DARK_PIXEL: + what = "dark"; + break; + case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_VISIBLE_PIXEL: + what = "visible"; + break; + default: + what = "invalid"; + break; + } + + dev_dbg(&client->dev, + "%s pixels: %u %s (pixelcode %u)\n", + what, pixels, which, pixelcode); + + if (i < ncol_desc) { + if (pixelcode == + CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_VISIBLE_PIXEL) + sensor->visible_pixel_start = pixel_count; + pixel_count += pixels; + continue; + } + + /* Handle row descriptors */ + switch (pixelcode) { + case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_EMBEDDED: + if (sensor->embedded_end) + break; + sensor->embedded_start = line_count; + sensor->embedded_end = line_count + pixels; + break; + case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_VISIBLE_PIXEL: + sensor->image_start = line_count; + break; + } + line_count += pixels; + } + + if (sensor->embedded_end > sensor->image_start) { + dev_dbg(&client->dev, + "adjusting image start line to %u (was %u)\n", + sensor->embedded_end, sensor->image_start); + sensor->image_start = sensor->embedded_end; + } + + dev_dbg(&client->dev, "embedded data from lines %u to %u\n", + sensor->embedded_start, sensor->embedded_end); + dev_dbg(&client->dev, "image data starts at line %u\n", + sensor->image_start); + + return 0; +} + +static int ccs_pll_configure(struct ccs_sensor *sensor) +{ + struct ccs_pll *pll = &sensor->pll; + int rval; + + rval = ccs_write(sensor, VT_PIX_CLK_DIV, pll->vt_bk.pix_clk_div); + if (rval < 0) + return rval; + + rval = ccs_write(sensor, VT_SYS_CLK_DIV, pll->vt_bk.sys_clk_div); + if (rval < 0) + return rval; + + rval = ccs_write(sensor, PRE_PLL_CLK_DIV, pll->vt_fr.pre_pll_clk_div); + if (rval < 0) + return rval; + + rval = ccs_write(sensor, PLL_MULTIPLIER, pll->vt_fr.pll_multiplier); + if (rval < 0) + return rval; + + if (!(CCS_LIM(sensor, PHY_CTRL_CAPABILITY) & + CCS_PHY_CTRL_CAPABILITY_AUTO_PHY_CTL)) { + /* Lane op clock ratio does not apply here. */ + rval = ccs_write(sensor, REQUESTED_LINK_RATE, + DIV_ROUND_UP(pll->op_bk.sys_clk_freq_hz, + 1000000 / 256 / 256) * + (pll->flags & CCS_PLL_FLAG_LANE_SPEED_MODEL ? + sensor->pll.csi2.lanes : 1) << + (pll->flags & CCS_PLL_FLAG_OP_SYS_DDR ? + 1 : 0)); + if (rval < 0) + return rval; + } + + if (sensor->pll.flags & CCS_PLL_FLAG_NO_OP_CLOCKS) + return 0; + + rval = ccs_write(sensor, OP_PIX_CLK_DIV, pll->op_bk.pix_clk_div); + if (rval < 0) + return rval; + + rval = ccs_write(sensor, OP_SYS_CLK_DIV, pll->op_bk.sys_clk_div); + if (rval < 0) + return rval; + + if (!(pll->flags & CCS_PLL_FLAG_DUAL_PLL)) + return 0; + + rval = ccs_write(sensor, PLL_MODE, CCS_PLL_MODE_DUAL); + if (rval < 0) + return rval; + + rval = ccs_write(sensor, OP_PRE_PLL_CLK_DIV, + pll->op_fr.pre_pll_clk_div); + if (rval < 0) + return rval; + + return ccs_write(sensor, OP_PLL_MULTIPLIER, pll->op_fr.pll_multiplier); +} + +static int ccs_pll_try(struct ccs_sensor *sensor, struct ccs_pll *pll) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + struct ccs_pll_limits lim = { + .vt_fr = { + .min_pre_pll_clk_div = CCS_LIM(sensor, MIN_PRE_PLL_CLK_DIV), + .max_pre_pll_clk_div = CCS_LIM(sensor, MAX_PRE_PLL_CLK_DIV), + .min_pll_ip_clk_freq_hz = CCS_LIM(sensor, MIN_PLL_IP_CLK_FREQ_MHZ), + .max_pll_ip_clk_freq_hz = CCS_LIM(sensor, MAX_PLL_IP_CLK_FREQ_MHZ), + .min_pll_multiplier = CCS_LIM(sensor, MIN_PLL_MULTIPLIER), + .max_pll_multiplier = CCS_LIM(sensor, MAX_PLL_MULTIPLIER), + .min_pll_op_clk_freq_hz = CCS_LIM(sensor, MIN_PLL_OP_CLK_FREQ_MHZ), + .max_pll_op_clk_freq_hz = CCS_LIM(sensor, MAX_PLL_OP_CLK_FREQ_MHZ), + }, + .op_fr = { + .min_pre_pll_clk_div = CCS_LIM(sensor, MIN_OP_PRE_PLL_CLK_DIV), + .max_pre_pll_clk_div = CCS_LIM(sensor, MAX_OP_PRE_PLL_CLK_DIV), + .min_pll_ip_clk_freq_hz = CCS_LIM(sensor, MIN_OP_PLL_IP_CLK_FREQ_MHZ), + .max_pll_ip_clk_freq_hz = CCS_LIM(sensor, MAX_OP_PLL_IP_CLK_FREQ_MHZ), + .min_pll_multiplier = CCS_LIM(sensor, MIN_OP_PLL_MULTIPLIER), + .max_pll_multiplier = CCS_LIM(sensor, MAX_OP_PLL_MULTIPLIER), + .min_pll_op_clk_freq_hz = CCS_LIM(sensor, MIN_OP_PLL_OP_CLK_FREQ_MHZ), + .max_pll_op_clk_freq_hz = CCS_LIM(sensor, MAX_OP_PLL_OP_CLK_FREQ_MHZ), + }, + .op_bk = { + .min_sys_clk_div = CCS_LIM(sensor, MIN_OP_SYS_CLK_DIV), + .max_sys_clk_div = CCS_LIM(sensor, MAX_OP_SYS_CLK_DIV), + .min_pix_clk_div = CCS_LIM(sensor, MIN_OP_PIX_CLK_DIV), + .max_pix_clk_div = CCS_LIM(sensor, MAX_OP_PIX_CLK_DIV), + .min_sys_clk_freq_hz = CCS_LIM(sensor, MIN_OP_SYS_CLK_FREQ_MHZ), + .max_sys_clk_freq_hz = CCS_LIM(sensor, MAX_OP_SYS_CLK_FREQ_MHZ), + .min_pix_clk_freq_hz = CCS_LIM(sensor, MIN_OP_PIX_CLK_FREQ_MHZ), + .max_pix_clk_freq_hz = CCS_LIM(sensor, MAX_OP_PIX_CLK_FREQ_MHZ), + }, + .vt_bk = { + .min_sys_clk_div = CCS_LIM(sensor, MIN_VT_SYS_CLK_DIV), + .max_sys_clk_div = CCS_LIM(sensor, MAX_VT_SYS_CLK_DIV), + .min_pix_clk_div = CCS_LIM(sensor, MIN_VT_PIX_CLK_DIV), + .max_pix_clk_div = CCS_LIM(sensor, MAX_VT_PIX_CLK_DIV), + .min_sys_clk_freq_hz = CCS_LIM(sensor, MIN_VT_SYS_CLK_FREQ_MHZ), + .max_sys_clk_freq_hz = CCS_LIM(sensor, MAX_VT_SYS_CLK_FREQ_MHZ), + .min_pix_clk_freq_hz = CCS_LIM(sensor, MIN_VT_PIX_CLK_FREQ_MHZ), + .max_pix_clk_freq_hz = CCS_LIM(sensor, MAX_VT_PIX_CLK_FREQ_MHZ), + }, + .min_line_length_pck_bin = CCS_LIM(sensor, MIN_LINE_LENGTH_PCK_BIN), + .min_line_length_pck = CCS_LIM(sensor, MIN_LINE_LENGTH_PCK), + }; + + return ccs_pll_calculate(&client->dev, &lim, pll); +} + +static void +ccs_get_binning(struct ccs_sensor *sensor, u8 *binning_mode, u8 *binh, u8 *binv) +{ + struct v4l2_subdev_state *state = + v4l2_subdev_get_locked_active_state(&sensor->binner->sd); + const struct v4l2_rect *sink_crop = + v4l2_subdev_state_get_crop(state, CCS_PAD_SINK, + CCS_STREAM_PIXEL); + const struct v4l2_rect *sink_comp = + v4l2_subdev_state_get_compose(state, CCS_PAD_SINK, + CCS_STREAM_PIXEL); + + if (binning_mode) + *binning_mode = sink_crop->width == sink_comp->width && + sink_crop->height == sink_comp->height ? 0 : 1; + + *binh = sink_crop->width / sink_comp->width; + *binv = sink_crop->height / sink_comp->height; +} + +static void ccs_get_scaling(struct ccs_sensor *sensor, u8 *scaling_mode, + u8 *scale_m) +{ + struct v4l2_subdev_state *state = + v4l2_subdev_get_locked_active_state(&sensor->scaler->sd); + const struct v4l2_rect *sink_crop = + v4l2_subdev_state_get_crop(state, CCS_PAD_SINK, + CCS_STREAM_PIXEL); + const struct v4l2_rect *sink_comp = + v4l2_subdev_state_get_compose(state, CCS_PAD_SINK, + CCS_STREAM_PIXEL); + + *scale_m = sink_crop->width * CCS_LIM(sensor, SCALER_N_MIN) / + sink_comp->width; + + if (!scaling_mode) + return; + + if (sink_crop->width == sink_comp->width) + *scaling_mode = CCS_SCALING_MODE_NO_SCALING; + else if (sink_crop->height == sink_comp->height) + *scaling_mode = CCS_SCALING_MODE_HORIZONTAL; + else + *scaling_mode = SMIAPP_SCALING_MODE_BOTH; +} + +static int ccs_pll_update(struct ccs_sensor *sensor) +{ + struct ccs_pll *pll = &sensor->pll; + u8 binh, binv; + u8 scale_m; + int rval; + + ccs_get_binning(sensor, NULL, &binh, &binv); + + if (sensor->scaler) + ccs_get_scaling(sensor, NULL, &scale_m); + else + scale_m = CCS_LIM(sensor, SCALER_N_MIN); + + pll->binning_horizontal = binh; + pll->binning_vertical = binv; + pll->link_freq = + sensor->link_freq->qmenu_int[sensor->link_freq->val]; + pll->scale_m = scale_m; + pll->bits_per_pixel = sensor->csi_format->compressed; + + rval = ccs_pll_try(sensor, pll); + if (rval < 0) + return rval; + + __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_parray, + pll->pixel_rate_pixel_array); + __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_csi, pll->pixel_rate_csi); + + return 0; +} + + +/* + * + * V4L2 Controls handling + * + */ + +static void __ccs_update_exposure_limits(struct ccs_sensor *sensor, + const struct v4l2_rect *pa_src) +{ + struct v4l2_ctrl *ctrl = sensor->exposure; + int max; + + max = pa_src->height + sensor->vblank->val - + CCS_LIM(sensor, COARSE_INTEGRATION_TIME_MAX_MARGIN); + + __v4l2_ctrl_modify_range(ctrl, ctrl->minimum, max, ctrl->step, max); +} + +/* + * Order matters. + * + * 1. Bits-per-pixel, descending. + * 2. Bits-per-pixel compressed, descending. + * 3. Pixel order, same as in pixel_order_str. Formats for all four pixel + * orders must be defined. + */ +static const struct ccs_csi_data_format ccs_csi_data_formats[] = { + { MEDIA_BUS_FMT_SGRBG16_1X16, 16, 16, CCS_PIXEL_ORDER_GRBG, }, + { MEDIA_BUS_FMT_SRGGB16_1X16, 16, 16, CCS_PIXEL_ORDER_RGGB, }, + { MEDIA_BUS_FMT_SBGGR16_1X16, 16, 16, CCS_PIXEL_ORDER_BGGR, }, + { MEDIA_BUS_FMT_SGBRG16_1X16, 16, 16, CCS_PIXEL_ORDER_GBRG, }, + { MEDIA_BUS_FMT_SGRBG14_1X14, 14, 14, CCS_PIXEL_ORDER_GRBG, }, + { MEDIA_BUS_FMT_SRGGB14_1X14, 14, 14, CCS_PIXEL_ORDER_RGGB, }, + { MEDIA_BUS_FMT_SBGGR14_1X14, 14, 14, CCS_PIXEL_ORDER_BGGR, }, + { MEDIA_BUS_FMT_SGBRG14_1X14, 14, 14, CCS_PIXEL_ORDER_GBRG, }, + { MEDIA_BUS_FMT_SGRBG12_1X12, 12, 12, CCS_PIXEL_ORDER_GRBG, }, + { MEDIA_BUS_FMT_SRGGB12_1X12, 12, 12, CCS_PIXEL_ORDER_RGGB, }, + { MEDIA_BUS_FMT_SBGGR12_1X12, 12, 12, CCS_PIXEL_ORDER_BGGR, }, + { MEDIA_BUS_FMT_SGBRG12_1X12, 12, 12, CCS_PIXEL_ORDER_GBRG, }, + { MEDIA_BUS_FMT_SGRBG10_1X10, 10, 10, CCS_PIXEL_ORDER_GRBG, }, + { MEDIA_BUS_FMT_SRGGB10_1X10, 10, 10, CCS_PIXEL_ORDER_RGGB, }, + { MEDIA_BUS_FMT_SBGGR10_1X10, 10, 10, CCS_PIXEL_ORDER_BGGR, }, + { MEDIA_BUS_FMT_SGBRG10_1X10, 10, 10, CCS_PIXEL_ORDER_GBRG, }, + { MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, 10, 8, CCS_PIXEL_ORDER_GRBG, }, + { MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8, 10, 8, CCS_PIXEL_ORDER_RGGB, }, + { MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8, 10, 8, CCS_PIXEL_ORDER_BGGR, }, + { MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8, 10, 8, CCS_PIXEL_ORDER_GBRG, }, + { MEDIA_BUS_FMT_SGRBG8_1X8, 8, 8, CCS_PIXEL_ORDER_GRBG, }, + { MEDIA_BUS_FMT_SRGGB8_1X8, 8, 8, CCS_PIXEL_ORDER_RGGB, }, + { MEDIA_BUS_FMT_SBGGR8_1X8, 8, 8, CCS_PIXEL_ORDER_BGGR, }, + { MEDIA_BUS_FMT_SGBRG8_1X8, 8, 8, CCS_PIXEL_ORDER_GBRG, }, +}; + +static const char *pixel_order_str[] = { "GRBG", "RGGB", "BGGR", "GBRG" }; + +#define to_csi_format_idx(fmt) (((unsigned long)(fmt) \ + - (unsigned long)ccs_csi_data_formats) \ + / sizeof(*ccs_csi_data_formats)) + +static u32 ccs_pixel_order(struct ccs_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int flip = 0; + + if (sensor->hflip) { + if (sensor->hflip->val) + flip |= CCS_IMAGE_ORIENTATION_HORIZONTAL_MIRROR; + + if (sensor->vflip->val) + flip |= CCS_IMAGE_ORIENTATION_VERTICAL_FLIP; + } + + dev_dbg(&client->dev, "flip %u\n", flip); + return sensor->default_pixel_order ^ flip; +} + +static void ccs_update_mbus_formats(struct ccs_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + unsigned int csi_format_idx = + to_csi_format_idx(sensor->csi_format) & ~3; + unsigned int internal_csi_format_idx = + to_csi_format_idx(sensor->internal_csi_format) & ~3; + unsigned int pixel_order = ccs_pixel_order(sensor); + + if (WARN_ON_ONCE(max(internal_csi_format_idx, csi_format_idx) + + pixel_order >= ARRAY_SIZE(ccs_csi_data_formats))) + return; + + sensor->mbus_frame_fmts = + sensor->default_mbus_frame_fmts << pixel_order; + sensor->csi_format = + &ccs_csi_data_formats[csi_format_idx + pixel_order]; + sensor->internal_csi_format = + &ccs_csi_data_formats[internal_csi_format_idx + + pixel_order]; + + dev_dbg(&client->dev, "new pixel order %s\n", + pixel_order_str[pixel_order]); +} + +static const char * const ccs_test_patterns[] = { + "Disabled", + "Solid Colour", + "Eight Vertical Colour Bars", + "Colour Bars With Fade to Grey", + "Pseudorandom Sequence (PN9)", +}; + +static int ccs_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ccs_sensor *sensor = + container_of(ctrl->handler, struct ccs_subdev, ctrl_handler) + ->sensor; + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + struct v4l2_subdev_state *state; + const struct v4l2_rect *pa_src = NULL; + int pm_status; + u32 orient = 0; + unsigned int i; + int exposure; + int rval; + + if (ctrl->id == V4L2_CID_VBLANK || ctrl->id == V4L2_CID_HBLANK) { + state = v4l2_subdev_get_locked_active_state(&sensor->pixel_array->sd); + pa_src = v4l2_subdev_state_get_crop(state, CCS_PA_PAD_SRC, + CCS_STREAM_PIXEL); + } + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + if (sensor->streaming) + return -EBUSY; + + if (sensor->hflip->val) + orient |= CCS_IMAGE_ORIENTATION_HORIZONTAL_MIRROR; + + if (sensor->vflip->val) + orient |= CCS_IMAGE_ORIENTATION_VERTICAL_FLIP; + + ccs_update_mbus_formats(sensor); + + break; + case V4L2_CID_VBLANK: + exposure = sensor->exposure->val; + + __ccs_update_exposure_limits(sensor, pa_src); + + if (exposure > sensor->exposure->maximum) { + sensor->exposure->val = sensor->exposure->maximum; + rval = ccs_set_ctrl(sensor->exposure); + if (rval < 0) + return rval; + } + + break; + case V4L2_CID_LINK_FREQ: + if (sensor->streaming) + return -EBUSY; + + rval = ccs_pll_update(sensor); + if (rval) + return rval; + + return 0; + case V4L2_CID_TEST_PATTERN: + for (i = 0; i < ARRAY_SIZE(sensor->test_data); i++) + v4l2_ctrl_activate( + sensor->test_data[i], + ctrl->val == + V4L2_SMIAPP_TEST_PATTERN_MODE_SOLID_COLOUR); + + break; + } + + pm_status = pm_runtime_get_if_active(&client->dev); + if (!pm_status) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + rval = ccs_write(sensor, ANALOG_GAIN_CODE_GLOBAL, ctrl->val); + + break; + + case V4L2_CID_CCS_ANALOGUE_LINEAR_GAIN: + rval = ccs_write(sensor, ANALOG_LINEAR_GAIN_GLOBAL, ctrl->val); + + break; + + case V4L2_CID_CCS_ANALOGUE_EXPONENTIAL_GAIN: + rval = ccs_write(sensor, ANALOG_EXPONENTIAL_GAIN_GLOBAL, + ctrl->val); + + break; + + case V4L2_CID_DIGITAL_GAIN: + if (CCS_LIM(sensor, DIGITAL_GAIN_CAPABILITY) == + CCS_DIGITAL_GAIN_CAPABILITY_GLOBAL) { + rval = ccs_write(sensor, DIGITAL_GAIN_GLOBAL, + ctrl->val); + break; + } + + rval = ccs_write_addr(sensor, + SMIAPP_REG_U16_DIGITAL_GAIN_GREENR, + ctrl->val); + if (rval) + break; + + rval = ccs_write_addr(sensor, + SMIAPP_REG_U16_DIGITAL_GAIN_RED, + ctrl->val); + if (rval) + break; + + rval = ccs_write_addr(sensor, + SMIAPP_REG_U16_DIGITAL_GAIN_BLUE, + ctrl->val); + if (rval) + break; + + rval = ccs_write_addr(sensor, + SMIAPP_REG_U16_DIGITAL_GAIN_GREENB, + ctrl->val); + + break; + case V4L2_CID_EXPOSURE: + rval = ccs_write(sensor, COARSE_INTEGRATION_TIME, ctrl->val); + + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + rval = ccs_write(sensor, IMAGE_ORIENTATION, orient); + + break; + case V4L2_CID_VBLANK: + rval = ccs_write(sensor, FRAME_LENGTH_LINES, + pa_src->height + ctrl->val); + + break; + case V4L2_CID_HBLANK: + rval = ccs_write(sensor, LINE_LENGTH_PCK, + pa_src->width + ctrl->val); + + break; + case V4L2_CID_TEST_PATTERN: + rval = ccs_write(sensor, TEST_PATTERN_MODE, ctrl->val); + + break; + case V4L2_CID_TEST_PATTERN_RED: + rval = ccs_write(sensor, TEST_DATA_RED, ctrl->val); + + break; + case V4L2_CID_TEST_PATTERN_GREENR: + rval = ccs_write(sensor, TEST_DATA_GREENR, ctrl->val); + + break; + case V4L2_CID_TEST_PATTERN_BLUE: + rval = ccs_write(sensor, TEST_DATA_BLUE, ctrl->val); + + break; + case V4L2_CID_TEST_PATTERN_GREENB: + rval = ccs_write(sensor, TEST_DATA_GREENB, ctrl->val); + + break; + case V4L2_CID_CCS_SHADING_CORRECTION: + rval = ccs_write(sensor, SHADING_CORRECTION_EN, + ctrl->val ? CCS_SHADING_CORRECTION_EN_ENABLE : + 0); + + if (!rval && sensor->luminance_level) + v4l2_ctrl_activate(sensor->luminance_level, ctrl->val); + + break; + case V4L2_CID_CCS_LUMINANCE_CORRECTION_LEVEL: + rval = ccs_write(sensor, LUMINANCE_CORRECTION_LEVEL, ctrl->val); + + break; + case V4L2_CID_PIXEL_RATE: + /* For v4l2_ctrl_s_ctrl_int64() used internally. */ + rval = 0; + + break; + default: + rval = -EINVAL; + } + + if (pm_status > 0) + pm_runtime_put_autosuspend(&client->dev); + + return rval; +} + +static const struct v4l2_ctrl_ops ccs_ctrl_ops = { + .s_ctrl = ccs_set_ctrl, +}; + +static int ccs_init_controls(struct ccs_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + struct v4l2_fwnode_device_properties props; + int rval; + + rval = v4l2_ctrl_handler_init(&sensor->pixel_array->ctrl_handler, 19); + if (rval) + return rval; + + sensor->pixel_array->ctrl_handler.lock = &sensor->mutex; + + rval = v4l2_fwnode_device_parse(&client->dev, &props); + if (rval) + return rval; + + rval = v4l2_ctrl_new_fwnode_properties(&sensor->pixel_array->ctrl_handler, + &ccs_ctrl_ops, &props); + if (rval) + return rval; + + switch (CCS_LIM(sensor, ANALOG_GAIN_CAPABILITY)) { + case CCS_ANALOG_GAIN_CAPABILITY_GLOBAL: { + struct { + const char *name; + u32 id; + s32 value; + } const gain_ctrls[] = { + { "Analogue Gain m0", V4L2_CID_CCS_ANALOGUE_GAIN_M0, + CCS_LIM(sensor, ANALOG_GAIN_M0), }, + { "Analogue Gain c0", V4L2_CID_CCS_ANALOGUE_GAIN_C0, + CCS_LIM(sensor, ANALOG_GAIN_C0), }, + { "Analogue Gain m1", V4L2_CID_CCS_ANALOGUE_GAIN_M1, + CCS_LIM(sensor, ANALOG_GAIN_M1), }, + { "Analogue Gain c1", V4L2_CID_CCS_ANALOGUE_GAIN_C1, + CCS_LIM(sensor, ANALOG_GAIN_C1), }, + }; + struct v4l2_ctrl_config ctrl_cfg = { + .type = V4L2_CTRL_TYPE_INTEGER, + .ops = &ccs_ctrl_ops, + .flags = V4L2_CTRL_FLAG_READ_ONLY, + .step = 1, + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(gain_ctrls); i++) { + ctrl_cfg.name = gain_ctrls[i].name; + ctrl_cfg.id = gain_ctrls[i].id; + ctrl_cfg.min = ctrl_cfg.max = ctrl_cfg.def = + gain_ctrls[i].value; + + v4l2_ctrl_new_custom(&sensor->pixel_array->ctrl_handler, + &ctrl_cfg, NULL); + } + + v4l2_ctrl_new_std(&sensor->pixel_array->ctrl_handler, + &ccs_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + CCS_LIM(sensor, ANALOG_GAIN_CODE_MIN), + CCS_LIM(sensor, ANALOG_GAIN_CODE_MAX), + max(CCS_LIM(sensor, ANALOG_GAIN_CODE_STEP), + 1U), + CCS_LIM(sensor, ANALOG_GAIN_CODE_MIN)); + } + break; + + case CCS_ANALOG_GAIN_CAPABILITY_ALTERNATE_GLOBAL: { + struct { + const char *name; + u32 id; + u16 min, max, step; + } const gain_ctrls[] = { + { + "Analogue Linear Gain", + V4L2_CID_CCS_ANALOGUE_LINEAR_GAIN, + CCS_LIM(sensor, ANALOG_LINEAR_GAIN_MIN), + CCS_LIM(sensor, ANALOG_LINEAR_GAIN_MAX), + max(CCS_LIM(sensor, + ANALOG_LINEAR_GAIN_STEP_SIZE), + 1U), + }, + { + "Analogue Exponential Gain", + V4L2_CID_CCS_ANALOGUE_EXPONENTIAL_GAIN, + CCS_LIM(sensor, ANALOG_EXPONENTIAL_GAIN_MIN), + CCS_LIM(sensor, ANALOG_EXPONENTIAL_GAIN_MAX), + max(CCS_LIM(sensor, + ANALOG_EXPONENTIAL_GAIN_STEP_SIZE), + 1U), + }, + }; + struct v4l2_ctrl_config ctrl_cfg = { + .type = V4L2_CTRL_TYPE_INTEGER, + .ops = &ccs_ctrl_ops, + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(gain_ctrls); i++) { + ctrl_cfg.name = gain_ctrls[i].name; + ctrl_cfg.min = ctrl_cfg.def = gain_ctrls[i].min; + ctrl_cfg.max = gain_ctrls[i].max; + ctrl_cfg.step = gain_ctrls[i].step; + ctrl_cfg.id = gain_ctrls[i].id; + + v4l2_ctrl_new_custom(&sensor->pixel_array->ctrl_handler, + &ctrl_cfg, NULL); + } + } + } + + if (CCS_LIM(sensor, SHADING_CORRECTION_CAPABILITY) & + (CCS_SHADING_CORRECTION_CAPABILITY_COLOR_SHADING | + CCS_SHADING_CORRECTION_CAPABILITY_LUMINANCE_CORRECTION)) { + const struct v4l2_ctrl_config ctrl_cfg = { + .name = "Shading Correction", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .id = V4L2_CID_CCS_SHADING_CORRECTION, + .ops = &ccs_ctrl_ops, + .max = 1, + .step = 1, + }; + + v4l2_ctrl_new_custom(&sensor->pixel_array->ctrl_handler, + &ctrl_cfg, NULL); + } + + if (CCS_LIM(sensor, SHADING_CORRECTION_CAPABILITY) & + CCS_SHADING_CORRECTION_CAPABILITY_LUMINANCE_CORRECTION) { + const struct v4l2_ctrl_config ctrl_cfg = { + .name = "Luminance Correction Level", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .id = V4L2_CID_CCS_LUMINANCE_CORRECTION_LEVEL, + .ops = &ccs_ctrl_ops, + .max = 255, + .step = 1, + .def = 128, + }; + + sensor->luminance_level = + v4l2_ctrl_new_custom(&sensor->pixel_array->ctrl_handler, + &ctrl_cfg, NULL); + } + + if (CCS_LIM(sensor, DIGITAL_GAIN_CAPABILITY) == + CCS_DIGITAL_GAIN_CAPABILITY_GLOBAL || + CCS_LIM(sensor, DIGITAL_GAIN_CAPABILITY) == + SMIAPP_DIGITAL_GAIN_CAPABILITY_PER_CHANNEL) + v4l2_ctrl_new_std(&sensor->pixel_array->ctrl_handler, + &ccs_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + CCS_LIM(sensor, DIGITAL_GAIN_MIN), + CCS_LIM(sensor, DIGITAL_GAIN_MAX), + max(CCS_LIM(sensor, DIGITAL_GAIN_STEP_SIZE), + 1U), + 0x100); + + /* Exposure limits will be updated soon, use just something here. */ + sensor->exposure = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &ccs_ctrl_ops, + V4L2_CID_EXPOSURE, 0, 0, 1, 0); + + sensor->hflip = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &ccs_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + sensor->vflip = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &ccs_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + sensor->vblank = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &ccs_ctrl_ops, + V4L2_CID_VBLANK, 0, 1, 1, 0); + + if (sensor->vblank) + sensor->vblank->flags |= V4L2_CTRL_FLAG_UPDATE; + + sensor->hblank = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &ccs_ctrl_ops, + V4L2_CID_HBLANK, 0, 1, 1, 0); + + if (sensor->hblank) + sensor->hblank->flags |= V4L2_CTRL_FLAG_UPDATE; + + sensor->pixel_rate_parray = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, &ccs_ctrl_ops, + V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1); + + v4l2_ctrl_new_std_menu_items(&sensor->pixel_array->ctrl_handler, + &ccs_ctrl_ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ccs_test_patterns) - 1, + 0, 0, ccs_test_patterns); + + if (sensor->pixel_array->ctrl_handler.error) { + dev_err(&client->dev, + "pixel array controls initialization failed (%d)\n", + sensor->pixel_array->ctrl_handler.error); + return sensor->pixel_array->ctrl_handler.error; + } + + sensor->pixel_array->sd.ctrl_handler = + &sensor->pixel_array->ctrl_handler; + + v4l2_ctrl_cluster(2, &sensor->hflip); + + rval = v4l2_ctrl_handler_init(&sensor->src->ctrl_handler, 0); + if (rval) + return rval; + + sensor->src->ctrl_handler.lock = &sensor->mutex; + + sensor->pixel_rate_csi = v4l2_ctrl_new_std( + &sensor->src->ctrl_handler, &ccs_ctrl_ops, + V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1); + + if (sensor->src->ctrl_handler.error) { + dev_err(&client->dev, + "src controls initialization failed (%d)\n", + sensor->src->ctrl_handler.error); + return sensor->src->ctrl_handler.error; + } + + sensor->src->sd.ctrl_handler = &sensor->src->ctrl_handler; + + return 0; +} + +/* + * For controls that require information on available media bus codes + * and linke frequencies. + */ +static int ccs_init_late_controls(struct ccs_sensor *sensor) +{ + unsigned long *valid_link_freqs = &sensor->valid_link_freqs[ + sensor->csi_format->compressed - sensor->compressed_min_bpp]; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sensor->test_data); i++) { + int max_value = (1 << sensor->csi_format->width) - 1; + + sensor->test_data[i] = v4l2_ctrl_new_std( + &sensor->pixel_array->ctrl_handler, + &ccs_ctrl_ops, V4L2_CID_TEST_PATTERN_RED + i, + 0, max_value, 1, max_value); + } + + sensor->link_freq = v4l2_ctrl_new_int_menu( + &sensor->src->ctrl_handler, &ccs_ctrl_ops, + V4L2_CID_LINK_FREQ, __fls(*valid_link_freqs), + __ffs(*valid_link_freqs), sensor->hwcfg.op_sys_clock); + + return sensor->src->ctrl_handler.error; +} + +static void ccs_free_controls(struct ccs_sensor *sensor) +{ + unsigned int i; + + for (i = 0; i < sensor->ssds_used; i++) + v4l2_ctrl_handler_free(&sensor->ssds[i].ctrl_handler); +} + +static int ccs_get_mbus_formats(struct ccs_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + struct ccs_pll *pll = &sensor->pll; + u8 compressed_max_bpp = 0; + unsigned int type, n; + unsigned int i, pixel_order; + int rval; + + type = CCS_LIM(sensor, DATA_FORMAT_MODEL_TYPE); + + dev_dbg(&client->dev, "data_format_model_type %u\n", type); + + rval = ccs_read(sensor, PIXEL_ORDER, &pixel_order); + if (rval) + return rval; + + if (pixel_order >= ARRAY_SIZE(pixel_order_str)) { + dev_dbg(&client->dev, "bad pixel order %u\n", pixel_order); + return -EINVAL; + } + + dev_dbg(&client->dev, "pixel order %u (%s)\n", pixel_order, + pixel_order_str[pixel_order]); + + switch (type) { + case CCS_DATA_FORMAT_MODEL_TYPE_NORMAL: + n = SMIAPP_DATA_FORMAT_MODEL_TYPE_NORMAL_N; + break; + case CCS_DATA_FORMAT_MODEL_TYPE_EXTENDED: + n = CCS_LIM_DATA_FORMAT_DESCRIPTOR_MAX_N + 1; + break; + default: + return -EINVAL; + } + + sensor->default_pixel_order = pixel_order; + sensor->mbus_frame_fmts = 0; + + for (i = 0; i < n; i++) { + unsigned int fmt, j; + + fmt = CCS_LIM_AT(sensor, DATA_FORMAT_DESCRIPTOR, i); + + dev_dbg(&client->dev, "%u: bpp %u, compressed %u\n", + i, fmt >> 8, (u8)fmt); + + for (j = 0; j < ARRAY_SIZE(ccs_csi_data_formats); j++) { + const struct ccs_csi_data_format *f = + &ccs_csi_data_formats[j]; + + if (f->pixel_order != CCS_PIXEL_ORDER_GRBG) + continue; + + if (f->width != fmt >> + CCS_DATA_FORMAT_DESCRIPTOR_UNCOMPRESSED_SHIFT || + f->compressed != + (fmt & CCS_DATA_FORMAT_DESCRIPTOR_COMPRESSED_MASK)) + continue; + + dev_dbg(&client->dev, "jolly good! %u\n", j); + + sensor->default_mbus_frame_fmts |= BIT_U64(j); + } + } + + /* Figure out which BPP values can be used with which formats. */ + pll->binning_horizontal = 1; + pll->binning_vertical = 1; + pll->scale_m = CCS_LIM(sensor, SCALER_N_MIN); + + for (i = 0; i < ARRAY_SIZE(ccs_csi_data_formats); i++) { + sensor->compressed_min_bpp = + min(ccs_csi_data_formats[i].compressed, + sensor->compressed_min_bpp); + compressed_max_bpp = + max(ccs_csi_data_formats[i].compressed, + compressed_max_bpp); + } + + sensor->valid_link_freqs = devm_kcalloc( + &client->dev, + compressed_max_bpp - sensor->compressed_min_bpp + 1, + sizeof(*sensor->valid_link_freqs), GFP_KERNEL); + if (!sensor->valid_link_freqs) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(ccs_csi_data_formats); i++) { + const struct ccs_csi_data_format *f = + &ccs_csi_data_formats[i]; + unsigned long *valid_link_freqs = + &sensor->valid_link_freqs[ + f->compressed - sensor->compressed_min_bpp]; + unsigned int j; + + if (!(sensor->default_mbus_frame_fmts & BIT_U64(i))) + continue; + + pll->bits_per_pixel = f->compressed; + + for (j = 0; sensor->hwcfg.op_sys_clock[j]; j++) { + pll->link_freq = sensor->hwcfg.op_sys_clock[j]; + + rval = ccs_pll_try(sensor, pll); + dev_dbg(&client->dev, "link freq %u Hz, bpp %u %s\n", + pll->link_freq, pll->bits_per_pixel, + rval ? "not ok" : "ok"); + if (rval) + continue; + + set_bit(j, valid_link_freqs); + } + + if (!*valid_link_freqs) { + dev_info(&client->dev, + "no valid link frequencies for %u bpp\n", + f->compressed); + sensor->default_mbus_frame_fmts &= ~BIT(i); + continue; + } + + if (!sensor->csi_format + || f->width > sensor->csi_format->width + || (f->width == sensor->csi_format->width + && f->compressed > sensor->csi_format->compressed)) { + sensor->csi_format = f; + sensor->internal_csi_format = f; + } + } + + if (!sensor->csi_format) { + dev_err(&client->dev, "no supported mbus code found\n"); + return -EINVAL; + } + + ccs_update_mbus_formats(sensor); + + return 0; +} + +static void ccs_update_blanking(struct ccs_sensor *sensor, + const struct v4l2_rect *pa_src) +{ + struct v4l2_ctrl *vblank = sensor->vblank; + struct v4l2_ctrl *hblank = sensor->hblank; + u16 min_fll, max_fll, min_llp, max_llp, min_lbp; + int min, max; + u8 binh, binv; + + ccs_get_binning(sensor, NULL, &binh, &binv); + + if (binv > 1 || binh > 1) { + min_fll = CCS_LIM(sensor, MIN_FRAME_LENGTH_LINES_BIN); + max_fll = CCS_LIM(sensor, MAX_FRAME_LENGTH_LINES_BIN); + min_llp = CCS_LIM(sensor, MIN_LINE_LENGTH_PCK_BIN); + max_llp = CCS_LIM(sensor, MAX_LINE_LENGTH_PCK_BIN); + min_lbp = CCS_LIM(sensor, MIN_LINE_BLANKING_PCK_BIN); + } else { + min_fll = CCS_LIM(sensor, MIN_FRAME_LENGTH_LINES); + max_fll = CCS_LIM(sensor, MAX_FRAME_LENGTH_LINES); + min_llp = CCS_LIM(sensor, MIN_LINE_LENGTH_PCK); + max_llp = CCS_LIM(sensor, MAX_LINE_LENGTH_PCK); + min_lbp = CCS_LIM(sensor, MIN_LINE_BLANKING_PCK); + } + + min = max_t(int, + CCS_LIM(sensor, MIN_FRAME_BLANKING_LINES), + min_fll - pa_src->height); + max = max_fll - pa_src->height; + + __v4l2_ctrl_modify_range(vblank, min, max, vblank->step, min); + + min = max_t(int, min_llp - pa_src->width, min_lbp); + max = max_llp - pa_src->width; + + __v4l2_ctrl_modify_range(hblank, min, max, hblank->step, min); + + __ccs_update_exposure_limits(sensor, pa_src); +} + +static int ccs_pll_blanking_update(struct ccs_sensor *sensor) +{ + struct v4l2_subdev_state *state = + v4l2_subdev_get_locked_active_state(&sensor->pixel_array->sd); + const struct v4l2_rect *pa_src = + v4l2_subdev_state_get_crop(state, CCS_PA_PAD_SRC, + CCS_STREAM_PIXEL); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + rval = ccs_pll_update(sensor); + if (rval < 0) + return rval; + + /* Output from pixel array, including blanking */ + ccs_update_blanking(sensor, pa_src); + + dev_dbg(&client->dev, "vblank\t\t%d\n", sensor->vblank->val); + dev_dbg(&client->dev, "hblank\t\t%d\n", sensor->hblank->val); + + dev_dbg(&client->dev, "real timeperframe\t100/%d\n", + sensor->pll.pixel_rate_pixel_array / + ((pa_src->width + sensor->hblank->val) * + (pa_src->height + sensor->vblank->val) / 100)); + + return 0; +} + +/* + * + * SMIA++ NVM handling + * + */ + +static int ccs_read_nvm_page(struct ccs_sensor *sensor, u32 p, u8 *nvm, + u8 *status) +{ + unsigned int i; + int rval; + u32 s; + + *status = 0; + + rval = ccs_write(sensor, DATA_TRANSFER_IF_1_PAGE_SELECT, p); + if (rval) + return rval; + + rval = ccs_write(sensor, DATA_TRANSFER_IF_1_CTRL, + CCS_DATA_TRANSFER_IF_1_CTRL_ENABLE); + if (rval) + return rval; + + rval = ccs_read(sensor, DATA_TRANSFER_IF_1_STATUS, &s); + if (rval) + return rval; + + if (s & CCS_DATA_TRANSFER_IF_1_STATUS_IMPROPER_IF_USAGE) { + *status = s; + return -ENODATA; + } + + if (CCS_LIM(sensor, DATA_TRANSFER_IF_CAPABILITY) & + CCS_DATA_TRANSFER_IF_CAPABILITY_POLLING) { + for (i = 1000; i > 0; i--) { + if (s & CCS_DATA_TRANSFER_IF_1_STATUS_READ_IF_READY) + break; + + rval = ccs_read(sensor, DATA_TRANSFER_IF_1_STATUS, &s); + if (rval) + return rval; + } + + if (!i) + return -ETIMEDOUT; + } + + for (i = 0; i <= CCS_LIM_DATA_TRANSFER_IF_1_DATA_MAX_P; i++) { + u32 v; + + rval = ccs_read(sensor, DATA_TRANSFER_IF_1_DATA(i), &v); + if (rval) + return rval; + + *nvm++ = v; + } + + return 0; +} + +static int ccs_read_nvm(struct ccs_sensor *sensor, unsigned char *nvm, + size_t nvm_size) +{ + u8 status = 0; + u32 p; + int rval = 0, rval2; + + for (p = 0; p < nvm_size / (CCS_LIM_DATA_TRANSFER_IF_1_DATA_MAX_P + 1) + && !rval; p++) { + rval = ccs_read_nvm_page(sensor, p, nvm, &status); + nvm += CCS_LIM_DATA_TRANSFER_IF_1_DATA_MAX_P + 1; + } + + if (rval == -ENODATA && + status & CCS_DATA_TRANSFER_IF_1_STATUS_IMPROPER_IF_USAGE) + rval = 0; + + rval2 = ccs_write(sensor, DATA_TRANSFER_IF_1_CTRL, 0); + if (rval < 0) + return rval; + else + return rval2 ?: p * (CCS_LIM_DATA_TRANSFER_IF_1_DATA_MAX_P + 1); +} + +/* + * + * SMIA++ CCI address control + * + */ +static int ccs_change_cci_addr(struct ccs_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + u32 val; + + client->addr = sensor->hwcfg.i2c_addr_dfl; + + rval = read_poll_timeout(ccs_write, rval, !rval, CCS_RESET_DELAY_US, + CCS_RESET_TIMEOUT_US, false, sensor, + CCI_ADDRESS_CTRL, + sensor->hwcfg.i2c_addr_alt << 1); + if (rval) + return rval; + + client->addr = sensor->hwcfg.i2c_addr_alt; + + /* verify addr change went ok */ + rval = ccs_read(sensor, CCI_ADDRESS_CTRL, &val); + if (rval) + return rval; + + if (val != sensor->hwcfg.i2c_addr_alt << 1) + return -ENODEV; + + return 0; +} + +/* + * + * SMIA++ Mode Control + * + */ +static int ccs_setup_flash_strobe(struct ccs_sensor *sensor) +{ + struct ccs_flash_strobe_parms *strobe_setup; + unsigned int ext_freq = sensor->hwcfg.ext_clk; + u32 tmp; + u32 strobe_adjustment; + u32 strobe_width_high_rs; + int rval; + + strobe_setup = sensor->hwcfg.strobe_setup; + + /* + * How to calculate registers related to strobe length. Please + * do not change, or if you do at least know what you're + * doing. :-) + * + * Sakari Ailus 2010-10-25 + * + * flash_strobe_length [us] / 10^6 = (tFlash_strobe_width_ctrl + * / EXTCLK freq [Hz]) * flash_strobe_adjustment + * + * tFlash_strobe_width_ctrl E N, [1 - 0xffff] + * flash_strobe_adjustment E N, [1 - 0xff] + * + * The formula above is written as below to keep it on one + * line: + * + * l / 10^6 = w / e * a + * + * Let's mark w * a by x: + * + * x = w * a + * + * Thus, we get: + * + * x = l * e / 10^6 + * + * The strobe width must be at least as long as requested, + * thus rounding upwards is needed. + * + * x = (l * e + 10^6 - 1) / 10^6 + * ----------------------------- + * + * Maximum possible accuracy is wanted at all times. Thus keep + * a as small as possible. + * + * Calculate a, assuming maximum w, with rounding upwards: + * + * a = (x + (2^16 - 1) - 1) / (2^16 - 1) + * ------------------------------------- + * + * Thus, we also get w, with that a, with rounding upwards: + * + * w = (x + a - 1) / a + * ------------------- + * + * To get limits: + * + * x E [1, (2^16 - 1) * (2^8 - 1)] + * + * Substituting maximum x to the original formula (with rounding), + * the maximum l is thus + * + * (2^16 - 1) * (2^8 - 1) * 10^6 = l * e + 10^6 - 1 + * + * l = (10^6 * (2^16 - 1) * (2^8 - 1) - 10^6 + 1) / e + * -------------------------------------------------- + * + * flash_strobe_length must be clamped between 1 and + * (10^6 * (2^16 - 1) * (2^8 - 1) - 10^6 + 1) / EXTCLK freq. + * + * Then, + * + * flash_strobe_adjustment = ((flash_strobe_length * + * EXTCLK freq + 10^6 - 1) / 10^6 + (2^16 - 1) - 1) / (2^16 - 1) + * + * tFlash_strobe_width_ctrl = ((flash_strobe_length * + * EXTCLK freq + 10^6 - 1) / 10^6 + + * flash_strobe_adjustment - 1) / flash_strobe_adjustment + */ + tmp = div_u64(1000000ULL * ((1 << 16) - 1) * ((1 << 8) - 1) - + 1000000 + 1, ext_freq); + strobe_setup->strobe_width_high_us = + clamp_t(u32, strobe_setup->strobe_width_high_us, 1, tmp); + + tmp = div_u64(((u64)strobe_setup->strobe_width_high_us * (u64)ext_freq + + 1000000 - 1), 1000000ULL); + strobe_adjustment = (tmp + (1 << 16) - 1 - 1) / ((1 << 16) - 1); + strobe_width_high_rs = (tmp + strobe_adjustment - 1) / + strobe_adjustment; + + rval = ccs_write(sensor, FLASH_MODE_RS, strobe_setup->mode); + if (rval < 0) + goto out; + + rval = ccs_write(sensor, FLASH_STROBE_ADJUSTMENT, strobe_adjustment); + if (rval < 0) + goto out; + + rval = ccs_write(sensor, TFLASH_STROBE_WIDTH_HIGH_RS_CTRL, + strobe_width_high_rs); + if (rval < 0) + goto out; + + rval = ccs_write(sensor, TFLASH_STROBE_DELAY_RS_CTRL, + strobe_setup->strobe_delay); + if (rval < 0) + goto out; + + rval = ccs_write(sensor, FLASH_STROBE_START_POINT, + strobe_setup->stobe_start_point); + if (rval < 0) + goto out; + + rval = ccs_write(sensor, FLASH_TRIGGER_RS, strobe_setup->trigger); + +out: + sensor->hwcfg.strobe_setup->trigger = 0; + + return rval; +} + +/* ----------------------------------------------------------------------------- + * Power management + */ + +static int ccs_write_msr_regs(struct ccs_sensor *sensor) +{ + int rval; + + rval = ccs_write_data_regs(sensor, + sensor->sdata.sensor_manufacturer_regs, + sensor->sdata.num_sensor_manufacturer_regs); + if (rval) + return rval; + + return ccs_write_data_regs(sensor, + sensor->mdata.module_manufacturer_regs, + sensor->mdata.num_module_manufacturer_regs); +} + +static int ccs_update_phy_ctrl(struct ccs_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + u8 val; + + if (!sensor->ccs_limits) + return 0; + + if (CCS_LIM(sensor, PHY_CTRL_CAPABILITY) & + CCS_PHY_CTRL_CAPABILITY_AUTO_PHY_CTL) { + val = CCS_PHY_CTRL_AUTO; + } else if (CCS_LIM(sensor, PHY_CTRL_CAPABILITY) & + CCS_PHY_CTRL_CAPABILITY_UI_PHY_CTL) { + val = CCS_PHY_CTRL_UI; + } else { + dev_err(&client->dev, "manual PHY control not supported\n"); + return -EINVAL; + } + + return ccs_write(sensor, PHY_CTRL, val); +} + +static int ccs_power_on(struct device *dev) +{ + struct v4l2_subdev *subdev = dev_get_drvdata(dev); + struct ccs_subdev *ssd = to_ccs_subdev(subdev); + /* + * The sub-device related to the I2C device is always the + * source one, i.e. ssds[0]. + */ + struct ccs_sensor *sensor = + container_of(ssd, struct ccs_sensor, ssds[0]); + const struct ccs_device *ccsdev = device_get_match_data(dev); + int rval; + + rval = regulator_bulk_enable(ARRAY_SIZE(ccs_regulators), + sensor->regulators); + if (rval) { + dev_err(dev, "failed to enable vana regulator\n"); + return rval; + } + + if (sensor->reset || sensor->xshutdown || sensor->ext_clk) { + unsigned int sleep; + + rval = clk_prepare_enable(sensor->ext_clk); + if (rval < 0) { + dev_dbg(dev, "failed to enable xclk\n"); + goto out_xclk_fail; + } + + gpiod_set_value(sensor->reset, 0); + gpiod_set_value(sensor->xshutdown, 1); + + if (ccsdev->flags & CCS_DEVICE_FLAG_IS_SMIA) + sleep = SMIAPP_RESET_DELAY(sensor->hwcfg.ext_clk); + else + sleep = CCS_RESET_DELAY_US; + + usleep_range(sleep, sleep); + } + + /* + * Some devices take longer than the spec-defined time to respond + * after reset. Try until some time has passed before flagging it + * an error. + */ + if (!sensor->reset && !sensor->xshutdown) { + u32 reset; + + rval = read_poll_timeout(ccs_write, rval, !rval, + CCS_RESET_DELAY_US, + CCS_RESET_TIMEOUT_US, + false, sensor, SOFTWARE_RESET, + CCS_SOFTWARE_RESET_ON); + if (rval < 0) { + dev_err(dev, "software reset failed\n"); + goto out_cci_addr_fail; + } + + rval = read_poll_timeout(ccs_read, rval, + !rval && + reset == CCS_SOFTWARE_RESET_OFF, + CCS_RESET_DELAY_US, + CCS_RESET_TIMEOUT_US, false, sensor, + SOFTWARE_RESET, &reset); + if (rval < 0) { + dev_err_probe(dev, rval, + "failed to respond after reset\n"); + goto out_cci_addr_fail; + } + } + + if (sensor->hwcfg.i2c_addr_alt) { + rval = ccs_change_cci_addr(sensor); + if (rval) { + dev_err(dev, "cci address change error\n"); + goto out_cci_addr_fail; + } + } + + rval = ccs_write(sensor, COMPRESSION_MODE, + CCS_COMPRESSION_MODE_DPCM_PCM_SIMPLE); + if (rval) { + dev_err(dev, "compression mode set failed\n"); + goto out_cci_addr_fail; + } + + rval = ccs_write(sensor, EXTCLK_FREQUENCY_MHZ, + sensor->hwcfg.ext_clk / (1000000 / (1 << 8))); + if (rval) { + dev_err(dev, "extclk frequency set failed\n"); + goto out_cci_addr_fail; + } + + rval = ccs_write(sensor, CSI_LANE_MODE, sensor->hwcfg.lanes - 1); + if (rval) { + dev_err(dev, "csi lane mode set failed\n"); + goto out_cci_addr_fail; + } + + rval = ccs_write(sensor, FAST_STANDBY_CTRL, + CCS_FAST_STANDBY_CTRL_FRAME_TRUNCATION); + if (rval) { + dev_err(dev, "fast standby set failed\n"); + goto out_cci_addr_fail; + } + + rval = ccs_write(sensor, CSI_SIGNALING_MODE, + sensor->hwcfg.csi_signalling_mode); + if (rval) { + dev_err(dev, "csi signalling mode set failed\n"); + goto out_cci_addr_fail; + } + + rval = ccs_update_phy_ctrl(sensor); + if (rval < 0) + goto out_cci_addr_fail; + + rval = ccs_write_msr_regs(sensor); + if (rval) + goto out_cci_addr_fail; + + rval = ccs_call_quirk(sensor, post_poweron); + if (rval) { + dev_err(dev, "post_poweron quirks failed\n"); + goto out_cci_addr_fail; + } + + return 0; + +out_cci_addr_fail: + gpiod_set_value(sensor->reset, 1); + gpiod_set_value(sensor->xshutdown, 0); + clk_disable_unprepare(sensor->ext_clk); + +out_xclk_fail: + regulator_bulk_disable(ARRAY_SIZE(ccs_regulators), + sensor->regulators); + + return rval; +} + +static int ccs_power_off(struct device *dev) +{ + struct v4l2_subdev *subdev = dev_get_drvdata(dev); + struct ccs_subdev *ssd = to_ccs_subdev(subdev); + struct ccs_sensor *sensor = + container_of(ssd, struct ccs_sensor, ssds[0]); + + /* + * Currently power/clock to lens are enable/disabled separately + * but they are essentially the same signals. So if the sensor is + * powered off while the lens is powered on the sensor does not + * really see a power off and next time the cci address change + * will fail. So do a soft reset explicitly here. + */ + if (sensor->hwcfg.i2c_addr_alt) + ccs_write(sensor, SOFTWARE_RESET, CCS_SOFTWARE_RESET_ON); + + gpiod_set_value(sensor->reset, 1); + gpiod_set_value(sensor->xshutdown, 0); + clk_disable_unprepare(sensor->ext_clk); + usleep_range(5000, 5000); + regulator_bulk_disable(ARRAY_SIZE(ccs_regulators), + sensor->regulators); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev video operations + */ + +static int ccs_pm_get_init(struct ccs_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + /* + * It can't use pm_runtime_resume_and_get() here, as the driver + * relies at the returned value to detect if the device was already + * active or not. + */ + rval = pm_runtime_get_sync(&client->dev); + if (rval < 0) + goto error; + + /* Device was already active, so don't set controls */ + if (rval == 1 && !sensor->handler_setup_needed) + return 0; + + sensor->handler_setup_needed = false; + + /* Restore V4L2 controls to the previously suspended device */ + rval = __v4l2_ctrl_handler_setup(&sensor->pixel_array->ctrl_handler); + if (rval) + goto error; + + rval = __v4l2_ctrl_handler_setup(&sensor->src->ctrl_handler); + if (rval) + goto error; + + /* Keep PM runtime usage_count incremented on success */ + return 0; + +error: + pm_runtime_put(&client->dev); + return rval; +} + +static int ccs_enable_streams(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + struct v4l2_subdev_state *pa_state = + v4l2_subdev_get_locked_active_state(&sensor->pixel_array->sd); + struct v4l2_subdev_state *src_state = + v4l2_subdev_get_locked_active_state(&sensor->src->sd); + const struct v4l2_rect *pa_src = + v4l2_subdev_state_get_crop(pa_state, CCS_PA_PAD_SRC, + CCS_STREAM_PIXEL); + const struct v4l2_rect *src_src = + v4l2_subdev_state_get_crop(src_state, CCS_PAD_SRC, + CCS_STREAM_PIXEL); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + u8 binning_mode, binh, binv; + int rval; + + if (pad != CCS_PAD_SRC) + return -EINVAL; + + if (sensor->streaming) { + sensor->streaming |= streams_mask; + return 0; + } + + rval = ccs_pm_get_init(sensor); + if (rval) + return rval; + + rval = ccs_write(sensor, CSI_DATA_FORMAT, + (sensor->csi_format->width << 8) | + sensor->csi_format->compressed); + if (rval) + goto err_pm_put; + + /* Binning configuration */ + ccs_get_binning(sensor, &binning_mode, &binh, &binv); + + if (binning_mode) { + rval = ccs_write(sensor, BINNING_TYPE, (binh << 4) | binv); + if (rval < 0) + goto err_pm_put; + } + rval = ccs_write(sensor, BINNING_MODE, binning_mode); + if (rval < 0) + goto err_pm_put; + + /* Set up PLL */ + rval = ccs_pll_configure(sensor); + if (rval) + goto err_pm_put; + + /* Analog crop start coordinates */ + rval = ccs_write(sensor, X_ADDR_START, pa_src->left); + if (rval < 0) + goto err_pm_put; + + rval = ccs_write(sensor, Y_ADDR_START, pa_src->top); + if (rval < 0) + goto err_pm_put; + + /* Analog crop end coordinates */ + rval = ccs_write(sensor, X_ADDR_END, pa_src->left + pa_src->width - 1); + if (rval < 0) + goto err_pm_put; + + rval = ccs_write(sensor, Y_ADDR_END, pa_src->top + pa_src->height - 1); + if (rval < 0) + goto err_pm_put; + + /* + * Output from pixel array, including blanking, is set using + * controls below. No need to set here. + */ + + /* Digital crop */ + if (CCS_LIM(sensor, DIGITAL_CROP_CAPABILITY) + == CCS_DIGITAL_CROP_CAPABILITY_INPUT_CROP) { + struct v4l2_subdev_state *scaler_state = + v4l2_subdev_get_locked_active_state(&sensor->scaler->sd); + const struct v4l2_rect *scaler_sink = + v4l2_subdev_state_get_crop(scaler_state, + sensor->scaler->sink_pad, + CCS_STREAM_PIXEL); + + rval = ccs_write(sensor, DIGITAL_CROP_X_OFFSET, + scaler_sink->left); + if (rval < 0) + goto err_pm_put; + + rval = ccs_write(sensor, DIGITAL_CROP_Y_OFFSET, + scaler_sink->top); + if (rval < 0) + goto err_pm_put; + + rval = ccs_write(sensor, DIGITAL_CROP_IMAGE_WIDTH, + scaler_sink->width); + if (rval < 0) + goto err_pm_put; + + rval = ccs_write(sensor, DIGITAL_CROP_IMAGE_HEIGHT, + scaler_sink->height); + if (rval < 0) + goto err_pm_put; + } + + /* Scaling */ + if (CCS_LIM(sensor, SCALING_CAPABILITY) + != CCS_SCALING_CAPABILITY_NONE) { + u8 scaling_mode, scale_m; + + ccs_get_scaling(sensor, &scaling_mode, &scale_m); + + rval = ccs_write(sensor, SCALING_MODE, scaling_mode); + if (rval < 0) + goto err_pm_put; + + rval = ccs_write(sensor, SCALE_M, scale_m); + if (rval < 0) + goto err_pm_put; + } + + /* Output size from sensor */ + rval = ccs_write(sensor, X_OUTPUT_SIZE, src_src->width); + if (rval < 0) + goto err_pm_put; + rval = ccs_write(sensor, Y_OUTPUT_SIZE, src_src->height); + if (rval < 0) + goto err_pm_put; + + if (CCS_LIM(sensor, FLASH_MODE_CAPABILITY) & + (CCS_FLASH_MODE_CAPABILITY_SINGLE_STROBE | + SMIAPP_FLASH_MODE_CAPABILITY_MULTIPLE_STROBE) && + sensor->hwcfg.strobe_setup != NULL && + sensor->hwcfg.strobe_setup->trigger != 0) { + rval = ccs_setup_flash_strobe(sensor); + if (rval) + goto err_pm_put; + } + + rval = ccs_call_quirk(sensor, pre_streamon); + if (rval) { + dev_err(&client->dev, "pre_streamon quirks failed\n"); + goto err_pm_put; + } + + rval = ccs_write(sensor, MODE_SELECT, CCS_MODE_SELECT_STREAMING); + + sensor->streaming |= streams_mask; + + return 0; + +err_pm_put: + pm_runtime_put_autosuspend(&client->dev); + + return rval; +} + +static int ccs_disable_streams(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + if (pad != CCS_PAD_SRC) + return -EINVAL; + + sensor->streaming &= ~streams_mask; + if (sensor->streaming) + return 0; + + rval = ccs_write(sensor, MODE_SELECT, CCS_MODE_SELECT_SOFTWARE_STANDBY); + if (rval) + return rval; + + rval = ccs_call_quirk(sensor, post_streamoff); + if (rval) + dev_err(&client->dev, "post_streamoff quirks failed\n"); + + pm_runtime_put_autosuspend(&client->dev); + + return 0; +} + +static int ccs_pre_streamon(struct v4l2_subdev *subdev, u32 flags) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + if (flags & V4L2_SUBDEV_PRE_STREAMON_FL_MANUAL_LP) { + switch (sensor->hwcfg.csi_signalling_mode) { + case CCS_CSI_SIGNALING_MODE_CSI_2_DPHY: + if (!(CCS_LIM(sensor, PHY_CTRL_CAPABILITY_2) & + CCS_PHY_CTRL_CAPABILITY_2_MANUAL_LP_DPHY)) + return -EACCES; + break; + case CCS_CSI_SIGNALING_MODE_CSI_2_CPHY: + if (!(CCS_LIM(sensor, PHY_CTRL_CAPABILITY_2) & + CCS_PHY_CTRL_CAPABILITY_2_MANUAL_LP_CPHY)) + return -EACCES; + break; + default: + return -EACCES; + } + } + + scoped_guard(mutex, &sensor->mutex) + rval = ccs_pm_get_init(sensor); + if (rval) + return rval; + + if (flags & V4L2_SUBDEV_PRE_STREAMON_FL_MANUAL_LP) { + rval = ccs_write(sensor, MANUAL_LP_CTRL, + CCS_MANUAL_LP_CTRL_ENABLE); + if (rval) + pm_runtime_put(&client->dev); + } + + return rval; +} + +static int ccs_post_streamoff(struct v4l2_subdev *subdev) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + + pm_runtime_put(&client->dev); + + return 0; +} + +static const struct ccs_csi_data_format +*ccs_validate_csi_data_format(struct ccs_sensor *sensor, u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ccs_csi_data_formats); i++) { + if (sensor->mbus_frame_fmts & BIT_U64(i) && + ccs_csi_data_formats[i].code == code) + return &ccs_csi_data_formats[i]; + } + + return sensor->csi_format; +} + +static int ccs_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + unsigned int i; + int idx = -1; + + dev_err(&client->dev, "subdev %s, pad %u, index %u\n", + subdev->name, code->pad, code->index); + + if (subdev != &sensor->src->sd || code->pad != CCS_PAD_SRC) { + if (code->index) + return -EINVAL; + + code->code = sensor->internal_csi_format->code; + + return 0; + } + + for (i = 0; i < ARRAY_SIZE(ccs_csi_data_formats); i++) { + if (sensor->mbus_frame_fmts & BIT_U64(i)) + idx++; + + if (idx == code->index) { + code->code = ccs_csi_data_formats[i].code; + dev_err(&client->dev, "found index %u, i %u, code %x\n", + code->index, i, code->code); + return 0; + } + } + + return -EINVAL; +} + +static u32 ccs_get_mbus_code(struct v4l2_subdev *subdev, unsigned int pad) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + + if (subdev == &sensor->src->sd && pad == CCS_PAD_SRC) + return sensor->csi_format->code; + else + return sensor->internal_csi_format->code; +} + +static int ccs_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + fmt->format = *v4l2_subdev_state_get_format(sd_state, fmt->pad); + fmt->format.code = ccs_get_mbus_code(subdev, fmt->pad); + + return 0; +} + +/* Changes require propagation only on sink pad. */ +static void ccs_propagate(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, int target) +{ + struct ccs_subdev *ssd = to_ccs_subdev(subdev); + struct v4l2_rect *comp, *crop; + struct v4l2_mbus_framefmt *fmt; + + comp = v4l2_subdev_state_get_compose(sd_state, ssd->sink_pad, + CCS_STREAM_PIXEL); + switch (target) { + case V4L2_SEL_TGT_CROP: + crop = v4l2_subdev_state_get_crop(sd_state, CCS_PAD_SINK, + CCS_STREAM_PIXEL); + comp->width = crop->width; + comp->height = crop->height; + fallthrough; + case V4L2_SEL_TGT_COMPOSE: + crop = v4l2_subdev_state_get_crop(sd_state, CCS_PAD_SRC, + CCS_STREAM_PIXEL); + *crop = *comp; + fmt = v4l2_subdev_state_get_format(sd_state, CCS_PAD_SRC, + CCS_STREAM_PIXEL); + fmt->width = comp->width; + fmt->height = comp->height; + break; + default: + WARN_ON_ONCE(1); + } +} + +static int ccs_set_format_source(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + const struct ccs_csi_data_format *csi_format, + *old_csi_format = sensor->csi_format; + unsigned long *valid_link_freqs; + u32 code = fmt->format.code; + unsigned int i; + int rval; + + rval = ccs_get_format(subdev, sd_state, fmt); + if (rval) + return rval; + + /* + * Media bus code is changeable on src subdev's source pad. On + * other source pads we just get format here. + */ + if (subdev != &sensor->src->sd) + return 0; + + csi_format = ccs_validate_csi_data_format(sensor, code); + + fmt->format.code = csi_format->code; + + if (fmt->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return 0; + + sensor->csi_format = csi_format; + + if (csi_format->width != old_csi_format->width) + for (i = 0; i < ARRAY_SIZE(sensor->test_data); i++) + __v4l2_ctrl_modify_range( + sensor->test_data[i], 0, + (1 << csi_format->width) - 1, 1, 0); + + if (csi_format->compressed == old_csi_format->compressed) + return 0; + + valid_link_freqs = + &sensor->valid_link_freqs[sensor->csi_format->compressed + - sensor->compressed_min_bpp]; + + __v4l2_ctrl_modify_range( + sensor->link_freq, 0, + __fls(*valid_link_freqs), ~*valid_link_freqs, + __ffs(*valid_link_freqs)); + + return ccs_pll_update(sensor); +} + +static int ccs_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + struct ccs_subdev *ssd = to_ccs_subdev(subdev); + struct v4l2_rect *crop; + + if (fmt->pad == ssd->source_pad) { + int rval; + + rval = ccs_set_format_source(subdev, sd_state, fmt); + + return rval; + } + + /* Sink pad. Width and height are changeable here. */ + fmt->format.code = ccs_get_mbus_code(subdev, fmt->pad); + + fmt->format.width &= ~1; + fmt->format.height &= ~1; + fmt->format.field = V4L2_FIELD_NONE; + + fmt->format.width = + clamp(fmt->format.width, + CCS_LIM(sensor, MIN_X_OUTPUT_SIZE), + CCS_LIM(sensor, MAX_X_OUTPUT_SIZE)); + fmt->format.height = + clamp(fmt->format.height, + CCS_LIM(sensor, MIN_Y_OUTPUT_SIZE), + CCS_LIM(sensor, MAX_Y_OUTPUT_SIZE)); + + crop = v4l2_subdev_state_get_crop(sd_state, ssd->sink_pad, + CCS_STREAM_PIXEL); + + crop->left = 0; + crop->top = 0; + crop->width = fmt->format.width; + crop->height = fmt->format.height; + ccs_propagate(subdev, sd_state, V4L2_SEL_TGT_CROP); + + return 0; +} + +/* + * Calculate goodness of scaled image size compared to expected image + * size and flags provided. + */ +#define SCALING_GOODNESS 100000 +#define SCALING_GOODNESS_EXTREME 100000000 +static int scaling_goodness(struct v4l2_subdev *subdev, int w, int ask_w, + int h, int ask_h, u32 flags) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + int val = 0; + + w &= ~1; + ask_w &= ~1; + h &= ~1; + ask_h &= ~1; + + if (flags & V4L2_SEL_FLAG_GE) { + if (w < ask_w) + val -= SCALING_GOODNESS; + if (h < ask_h) + val -= SCALING_GOODNESS; + } + + if (flags & V4L2_SEL_FLAG_LE) { + if (w > ask_w) + val -= SCALING_GOODNESS; + if (h > ask_h) + val -= SCALING_GOODNESS; + } + + val -= abs(w - ask_w); + val -= abs(h - ask_h); + + if (w < CCS_LIM(sensor, MIN_X_OUTPUT_SIZE)) + val -= SCALING_GOODNESS_EXTREME; + + dev_dbg(&client->dev, "w %d ask_w %d h %d ask_h %d goodness %d\n", + w, ask_w, h, ask_h, val); + + return val; +} + +static void ccs_set_compose_binner(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel, + const struct v4l2_rect *sink_crop) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + unsigned int i; + unsigned int binh = 1, binv = 1; + int best = scaling_goodness(subdev, sink_crop->width, sel->r.width, + sink_crop->height, sel->r.height, + sel->flags); + + for (i = 0; i < sensor->nbinning_subtypes; i++) { + int this = scaling_goodness(subdev, + sink_crop->width + / sensor->binning_subtypes[i].horizontal, + sel->r.width, + sink_crop->height + / sensor->binning_subtypes[i].vertical, + sel->r.height, sel->flags); + + if (this > best) { + binh = sensor->binning_subtypes[i].horizontal; + binv = sensor->binning_subtypes[i].vertical; + best = this; + } + } + + sel->r.width = (sink_crop->width / binh) & ~1; + sel->r.height = (sink_crop->height / binv) & ~1; +} + +/* + * Calculate best scaling ratio and mode for given output resolution. + * + * Try all of these: horizontal ratio, vertical ratio and smallest + * size possible (horizontally). + * + * Also try whether horizontal scaler or full scaler gives a better + * result. + */ +static void ccs_set_compose_scaler(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel, + const struct v4l2_rect *sink_crop) +{ + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + u32 min, max, a, b, max_m; + u32 scale_m = CCS_LIM(sensor, SCALER_N_MIN); + int mode = CCS_SCALING_MODE_HORIZONTAL; + u32 try[4]; + u32 ntry = 0; + unsigned int i; + int best = INT_MIN; + + sel->r.width = min_t(unsigned int, sel->r.width, sink_crop->width); + sel->r.height = min_t(unsigned int, sel->r.height, sink_crop->height); + + a = sink_crop->width * CCS_LIM(sensor, SCALER_N_MIN) / sel->r.width; + b = sink_crop->height * CCS_LIM(sensor, SCALER_N_MIN) / sel->r.height; + max_m = sink_crop->width + * CCS_LIM(sensor, SCALER_N_MIN) + / (CCS_LIM(sensor, MIN_X_OUTPUT_SIZE) ?: 1); + + a = clamp(a, CCS_LIM(sensor, SCALER_M_MIN), + CCS_LIM(sensor, SCALER_M_MAX)); + b = clamp(b, CCS_LIM(sensor, SCALER_M_MIN), + CCS_LIM(sensor, SCALER_M_MAX)); + max_m = clamp(max_m, CCS_LIM(sensor, SCALER_M_MIN), + CCS_LIM(sensor, SCALER_M_MAX)); + + dev_dbg(&client->dev, "scaling: a %u b %u max_m %u\n", a, b, max_m); + + min = min(max_m, min(a, b)); + max = min(max_m, max(a, b)); + + try[ntry] = min; + ntry++; + if (min != max) { + try[ntry] = max; + ntry++; + } + if (max != max_m) { + try[ntry] = min + 1; + ntry++; + if (min != max) { + try[ntry] = max + 1; + ntry++; + } + } + + for (i = 0; i < ntry; i++) { + int this = scaling_goodness(subdev, + sink_crop->width + / try[i] + * CCS_LIM(sensor, SCALER_N_MIN), + sel->r.width, sink_crop->height, + sel->r.height, sel->flags); + + dev_dbg(&client->dev, "trying factor %u (%u)\n", try[i], i); + + if (this > best) { + scale_m = try[i]; + mode = CCS_SCALING_MODE_HORIZONTAL; + best = this; + } + + if (CCS_LIM(sensor, SCALING_CAPABILITY) + == CCS_SCALING_CAPABILITY_HORIZONTAL) + continue; + + this = scaling_goodness( + subdev, sink_crop->width / try[i] + * CCS_LIM(sensor, SCALER_N_MIN), + sel->r.width, + sink_crop->height / try[i] + * CCS_LIM(sensor, SCALER_N_MIN), + sel->r.height, + sel->flags); + + if (this > best) { + scale_m = try[i]; + mode = SMIAPP_SCALING_MODE_BOTH; + best = this; + } + } + + sel->r.width = (sink_crop->width / scale_m + * CCS_LIM(sensor, SCALER_N_MIN)) & ~1; + if (mode == SMIAPP_SCALING_MODE_BOTH) + sel->r.height = (sink_crop->height / scale_m + * CCS_LIM(sensor, SCALER_N_MIN)) & ~1; + else + sel->r.height = sink_crop->height; +} +/* We're only called on source pads. This function sets scaling. */ +static int ccs_set_compose(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + struct ccs_subdev *ssd = to_ccs_subdev(subdev); + const struct v4l2_rect *sink_crop; + struct v4l2_rect *comp; + + sink_crop = v4l2_subdev_state_get_crop(sd_state, CCS_PAD_SINK, + CCS_STREAM_PIXEL); + comp = v4l2_subdev_state_get_compose(sd_state, ssd->sink_pad, + CCS_STREAM_PIXEL); + + sel->r.top = 0; + sel->r.left = 0; + + if (ssd == sensor->binner) + ccs_set_compose_binner(subdev, sd_state, sel, sink_crop); + else + ccs_set_compose_scaler(subdev, sd_state, sel, sink_crop); + + *comp = sel->r; + ccs_propagate(subdev, sd_state, V4L2_SEL_TGT_COMPOSE); + + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return ccs_pll_blanking_update(sensor); + + return 0; +} + +static int ccs_sel_supported(struct v4l2_subdev *subdev, + struct v4l2_subdev_selection *sel) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + struct ccs_subdev *ssd = to_ccs_subdev(subdev); + + /* We only implement crop in three places. */ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_BOUNDS: + if (ssd == sensor->pixel_array && sel->pad == CCS_PA_PAD_SRC) + return 0; + if (ssd == sensor->src && sel->pad == CCS_PAD_SRC) + return 0; + if (ssd == sensor->scaler && sel->pad == CCS_PAD_SINK && + CCS_LIM(sensor, DIGITAL_CROP_CAPABILITY) + == CCS_DIGITAL_CROP_CAPABILITY_INPUT_CROP) + return 0; + return -EINVAL; + case V4L2_SEL_TGT_NATIVE_SIZE: + if (ssd == sensor->pixel_array && sel->pad == CCS_PA_PAD_SRC) + return 0; + return -EINVAL; + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + if (sel->pad == ssd->source_pad) + return -EINVAL; + if (ssd == sensor->binner) + return 0; + if (ssd == sensor->scaler && CCS_LIM(sensor, SCALING_CAPABILITY) + != CCS_SCALING_CAPABILITY_NONE) + return 0; + fallthrough; + default: + return -EINVAL; + } +} + +static int ccs_set_crop(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + struct ccs_subdev *ssd = to_ccs_subdev(subdev); + struct v4l2_rect src_size = { 0 }, *crop; + const struct v4l2_rect *comp; + + crop = v4l2_subdev_state_get_crop(sd_state, sel->pad, + CCS_STREAM_PIXEL); + comp = v4l2_subdev_state_get_compose(sd_state, ssd->sink_pad, + CCS_STREAM_PIXEL); + + if (sel->pad == ssd->sink_pad) { + struct v4l2_mbus_framefmt *mfmt = + v4l2_subdev_state_get_format(sd_state, sel->pad); + + src_size.width = mfmt->width; + src_size.height = mfmt->height; + } else { + src_size = *comp; + } + + if (ssd == sensor->src && sel->pad == CCS_PAD_SRC) { + sel->r.left = 0; + sel->r.top = 0; + } + + sel->r.width = min(sel->r.width, src_size.width); + sel->r.height = min(sel->r.height, src_size.height); + + sel->r.left = min_t(int, sel->r.left, src_size.width - sel->r.width); + sel->r.top = min_t(int, sel->r.top, src_size.height - sel->r.height); + + *crop = sel->r; + + if (ssd != sensor->pixel_array && sel->pad == CCS_PAD_SINK) + ccs_propagate(subdev, sd_state, V4L2_SEL_TGT_CROP); + + return 0; +} + +static void ccs_get_native_size(struct ccs_subdev *ssd, struct v4l2_rect *r) +{ + r->top = 0; + r->left = 0; + r->width = CCS_LIM(ssd->sensor, X_ADDR_MAX) + 1; + r->height = CCS_LIM(ssd->sensor, Y_ADDR_MAX) + 1; +} + +static int ccs_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + struct ccs_subdev *ssd = to_ccs_subdev(subdev); + const struct v4l2_rect *crop, *comp; + int ret; + + ret = ccs_sel_supported(subdev, sel); + if (ret) + return ret; + + crop = v4l2_subdev_state_get_crop(sd_state, sel->pad, + CCS_STREAM_PIXEL); + comp = v4l2_subdev_state_get_compose(sd_state, ssd->sink_pad, + CCS_STREAM_PIXEL); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_NATIVE_SIZE: + if (ssd == sensor->pixel_array) { + ccs_get_native_size(ssd, &sel->r); + } else if (sel->pad == ssd->sink_pad) { + struct v4l2_mbus_framefmt *sink_fmt = + v4l2_subdev_state_get_format(sd_state, + ssd->sink_pad); + sel->r.top = sel->r.left = 0; + sel->r.width = sink_fmt->width; + sel->r.height = sink_fmt->height; + } else { + sel->r = *comp; + } + break; + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + sel->r = *crop; + break; + case V4L2_SEL_TGT_COMPOSE: + sel->r = *comp; + break; + } + + return 0; +} + +static int ccs_set_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + int ret; + + ret = ccs_sel_supported(subdev, sel); + if (ret) + return ret; + + sel->r.left = max(0, sel->r.left & ~1); + sel->r.top = max(0, sel->r.top & ~1); + sel->r.width = CCS_ALIGN_DIM(sel->r.width, sel->flags); + sel->r.height = CCS_ALIGN_DIM(sel->r.height, sel->flags); + + sel->r.width = max_t(unsigned int, CCS_LIM(sensor, MIN_X_OUTPUT_SIZE), + sel->r.width); + sel->r.height = max_t(unsigned int, CCS_LIM(sensor, MIN_Y_OUTPUT_SIZE), + sel->r.height); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + ret = ccs_set_crop(subdev, sd_state, sel); + break; + case V4L2_SEL_TGT_COMPOSE: + ret = ccs_set_compose(subdev, sd_state, sel); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int ccs_get_frame_desc(struct v4l2_subdev *subdev, unsigned int pad, + struct v4l2_mbus_frame_desc *desc) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + struct v4l2_mbus_frame_desc_entry *entry = desc->entry; + struct v4l2_subdev_state *sd_state; + + switch (sensor->hwcfg.csi_signalling_mode) { + case CCS_CSI_SIGNALING_MODE_CSI_2_DPHY: + case CCS_CSI_SIGNALING_MODE_CSI_2_CPHY: + desc->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + break; + default: + /* FIXME: CCP2 support */ + return -EINVAL; + } + + sd_state = v4l2_subdev_lock_and_get_active_state(subdev); + if (!sd_state) + return -EINVAL; + + entry->pixelcode = sensor->csi_format->code; + entry->stream = CCS_STREAM_PIXEL; + entry->bus.csi2.dt = + sensor->csi_format->width == sensor->csi_format->compressed ? + ccs_mipi_csi2_data_type(sensor->csi_format->width) : + CCS_DEFAULT_COMPRESSED_DT; + entry++; + desc->num_entries++; + + v4l2_subdev_unlock_state(sd_state); + + return 0; +} + +static int ccs_get_skip_frames(struct v4l2_subdev *subdev, u32 *frames) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + + *frames = sensor->frame_skip; + return 0; +} + +static int ccs_get_skip_top_lines(struct v4l2_subdev *subdev, u32 *lines) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + + *lines = sensor->image_start; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * sysfs attributes + */ + +static ssize_t +nvm_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev)); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + int rval; + + if (!sensor->dev_init_done) + return -EBUSY; + + rval = ccs_pm_get_init(sensor); + if (rval < 0) + return -ENODEV; + + rval = ccs_read_nvm(sensor, buf, PAGE_SIZE); + if (rval < 0) { + pm_runtime_put(&client->dev); + dev_err(&client->dev, "nvm read failed\n"); + return -ENODEV; + } + + pm_runtime_put_autosuspend(&client->dev); + + /* + * NVM is still way below a PAGE_SIZE, so we can safely + * assume this for now. + */ + return rval; +} +static DEVICE_ATTR_RO(nvm); + +static ssize_t +ident_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev)); + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + struct ccs_module_info *minfo = &sensor->minfo; + + if (minfo->mipi_manufacturer_id) + return sysfs_emit(buf, "%4.4x%4.4x%2.2x\n", + minfo->mipi_manufacturer_id, minfo->model_id, + minfo->revision_number) + 1; + else + return sysfs_emit(buf, "%2.2x%4.4x%2.2x\n", + minfo->smia_manufacturer_id, minfo->model_id, + minfo->revision_number) + 1; +} +static DEVICE_ATTR_RO(ident); + +/* ----------------------------------------------------------------------------- + * V4L2 subdev core operations + */ + +static int ccs_identify_module(struct ccs_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + struct ccs_module_info *minfo = &sensor->minfo; + unsigned int i; + u32 rev; + int rval = 0; + + /* Module info */ + rval = ccs_read(sensor, MODULE_MANUFACTURER_ID, + &minfo->mipi_manufacturer_id); + if (!rval && !minfo->mipi_manufacturer_id) + rval = ccs_read_addr(sensor, SMIAPP_REG_U8_MANUFACTURER_ID, + &minfo->smia_manufacturer_id); + if (!rval) + rval = ccs_read(sensor, MODULE_MODEL_ID, &minfo->model_id); + if (!rval) + rval = ccs_read(sensor, MODULE_REVISION_NUMBER_MAJOR, &rev); + if (!rval) { + rval = ccs_read(sensor, MODULE_REVISION_NUMBER_MINOR, + &minfo->revision_number); + minfo->revision_number |= rev << 8; + } + if (!rval) + rval = ccs_read(sensor, MODULE_DATE_YEAR, &minfo->module_year); + if (!rval) + rval = ccs_read(sensor, MODULE_DATE_MONTH, + &minfo->module_month); + if (!rval) + rval = ccs_read(sensor, MODULE_DATE_DAY, &minfo->module_day); + + /* Sensor info */ + if (!rval) + rval = ccs_read(sensor, SENSOR_MANUFACTURER_ID, + &minfo->sensor_mipi_manufacturer_id); + if (!rval && !minfo->sensor_mipi_manufacturer_id) + rval = ccs_read(sensor, SENSOR_MANUFACTURER_ID, + &minfo->sensor_smia_manufacturer_id); + if (!rval) + rval = ccs_read(sensor, SENSOR_MODEL_ID, + &minfo->sensor_model_id); + if (!rval) + rval = ccs_read(sensor, SENSOR_REVISION_NUMBER, + &minfo->sensor_revision_number); + if (!rval && !minfo->sensor_revision_number) + rval = ccs_read(sensor, SENSOR_REVISION_NUMBER_16, + &minfo->sensor_revision_number); + if (!rval) + rval = ccs_read(sensor, SENSOR_FIRMWARE_VERSION, + &minfo->sensor_firmware_version); + + /* SMIA */ + if (!rval) + rval = ccs_read(sensor, MIPI_CCS_VERSION, &minfo->ccs_version); + if (!rval && !minfo->ccs_version) + rval = ccs_read_addr(sensor, SMIAPP_REG_U8_SMIA_VERSION, + &minfo->smia_version); + if (!rval && !minfo->ccs_version) + rval = ccs_read_addr(sensor, SMIAPP_REG_U8_SMIAPP_VERSION, + &minfo->smiapp_version); + + if (rval) { + dev_err(&client->dev, "sensor detection failed\n"); + return -ENODEV; + } + + if (minfo->mipi_manufacturer_id) + dev_dbg(&client->dev, "MIPI CCS module 0x%4.4x-0x%4.4x\n", + minfo->mipi_manufacturer_id, minfo->model_id); + else + dev_dbg(&client->dev, "SMIA module 0x%2.2x-0x%4.4x\n", + minfo->smia_manufacturer_id, minfo->model_id); + + dev_dbg(&client->dev, + "module revision 0x%4.4x date %2.2d-%2.2d-%2.2d\n", + minfo->revision_number, minfo->module_year, minfo->module_month, + minfo->module_day); + + if (minfo->sensor_mipi_manufacturer_id) + dev_dbg(&client->dev, "MIPI CCS sensor 0x%4.4x-0x%4.4x\n", + minfo->sensor_mipi_manufacturer_id, + minfo->sensor_model_id); + else + dev_dbg(&client->dev, "SMIA sensor 0x%2.2x-0x%4.4x\n", + minfo->sensor_smia_manufacturer_id, + minfo->sensor_model_id); + + dev_dbg(&client->dev, + "sensor revision 0x%4.4x firmware version 0x%2.2x\n", + minfo->sensor_revision_number, minfo->sensor_firmware_version); + + if (minfo->ccs_version) { + dev_dbg(&client->dev, "MIPI CCS version %u.%u", + (minfo->ccs_version & CCS_MIPI_CCS_VERSION_MAJOR_MASK) + >> CCS_MIPI_CCS_VERSION_MAJOR_SHIFT, + (minfo->ccs_version & CCS_MIPI_CCS_VERSION_MINOR_MASK)); + minfo->name = CCS_NAME; + } else { + dev_dbg(&client->dev, + "smia version %2.2d smiapp version %2.2d\n", + minfo->smia_version, minfo->smiapp_version); + minfo->name = SMIAPP_NAME; + /* + * Some modules have bad data in the lvalues below. Hope the + * rvalues have better stuff. The lvalues are module + * parameters whereas the rvalues are sensor parameters. + */ + if (minfo->sensor_smia_manufacturer_id && + !minfo->smia_manufacturer_id && !minfo->model_id) { + minfo->smia_manufacturer_id = + minfo->sensor_smia_manufacturer_id; + minfo->model_id = minfo->sensor_model_id; + minfo->revision_number = minfo->sensor_revision_number; + } + } + + for (i = 0; i < ARRAY_SIZE(ccs_module_idents); i++) { + if (ccs_module_idents[i].mipi_manufacturer_id && + ccs_module_idents[i].mipi_manufacturer_id + != minfo->mipi_manufacturer_id) + continue; + if (ccs_module_idents[i].smia_manufacturer_id && + ccs_module_idents[i].smia_manufacturer_id + != minfo->smia_manufacturer_id) + continue; + if (ccs_module_idents[i].model_id != minfo->model_id) + continue; + if (ccs_module_idents[i].flags + & CCS_MODULE_IDENT_FLAG_REV_LE) { + if (ccs_module_idents[i].revision_number_major + < (minfo->revision_number >> 8)) + continue; + } else { + if (ccs_module_idents[i].revision_number_major + != (minfo->revision_number >> 8)) + continue; + } + + minfo->name = ccs_module_idents[i].name; + minfo->quirk = ccs_module_idents[i].quirk; + break; + } + + dev_dbg(&client->dev, "the sensor is called %s\n", minfo->name); + + return 0; +} + +static const struct v4l2_subdev_ops ccs_ops; +static const struct media_entity_operations ccs_entity_ops; + +static int ccs_register_subdev(struct ccs_sensor *sensor, + struct ccs_subdev *ssd, + struct ccs_subdev *sink_ssd, + u16 source_pad, u16 sink_pad, u32 link_flags) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + if (!sink_ssd) + return 0; + + rval = v4l2_device_register_subdev(sensor->src->sd.v4l2_dev, &ssd->sd); + if (rval) { + dev_err(&client->dev, "v4l2_device_register_subdev failed\n"); + return rval; + } + + rval = media_create_pad_link(&ssd->sd.entity, source_pad, + &sink_ssd->sd.entity, sink_pad, + link_flags); + if (rval) { + dev_err(&client->dev, "media_create_pad_link failed\n"); + v4l2_device_unregister_subdev(&ssd->sd); + return rval; + } + + return 0; +} + +static void ccs_unregistered(struct v4l2_subdev *subdev) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + unsigned int i; + + for (i = 1; i < sensor->ssds_used; i++) + v4l2_device_unregister_subdev(&sensor->ssds[i].sd); +} + +static int ccs_registered(struct v4l2_subdev *subdev) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + int rval; + + if (sensor->scaler) { + rval = ccs_register_subdev(sensor, sensor->binner, + sensor->scaler, + CCS_PAD_SRC, CCS_PAD_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (rval < 0) + return rval; + } + + rval = ccs_register_subdev(sensor, sensor->pixel_array, sensor->binner, + CCS_PA_PAD_SRC, CCS_PAD_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (rval) + goto out_err; + + return 0; + +out_err: + ccs_unregistered(subdev); + + return rval; +} + +static void ccs_cleanup(struct ccs_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + unsigned int i; + + for (i = 0; i < sensor->ssds_used; i++) { + v4l2_subdev_cleanup(&sensor->ssds[2].sd); + media_entity_cleanup(&sensor->ssds[i].sd.entity); + } + + device_remove_file(&client->dev, &dev_attr_nvm); + device_remove_file(&client->dev, &dev_attr_ident); + + ccs_free_controls(sensor); +} + +static const struct v4l2_subdev_internal_ops ccs_internal_ops; + +static int ccs_init_subdev(struct ccs_sensor *sensor, + struct ccs_subdev *ssd, const char *name, + unsigned short num_pads, u32 function, + const char *lock_name, + struct lock_class_key *lock_key) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + if (!ssd) + return 0; + + if (ssd != sensor->src) { + v4l2_subdev_init(&ssd->sd, &ccs_ops); + ssd->sd.internal_ops = &ccs_internal_ops; + } + + ssd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ssd->sd.entity.function = function; + ssd->sd.state_lock = &sensor->mutex; + ssd->sensor = sensor; + + ssd->npads = num_pads; + ssd->source_pad = num_pads - 1; + + v4l2_i2c_subdev_set_name(&ssd->sd, client, sensor->minfo.name, name); + + ssd->pads[ssd->source_pad].flags = MEDIA_PAD_FL_SOURCE; + if (ssd != sensor->pixel_array) + ssd->pads[ssd->sink_pad].flags = MEDIA_PAD_FL_SINK; + + ssd->sd.entity.ops = &ccs_entity_ops; + + if (ssd != sensor->src) { + ssd->sd.owner = THIS_MODULE; + ssd->sd.dev = &client->dev; + v4l2_set_subdevdata(&ssd->sd, client); + } + + rval = media_entity_pads_init(&ssd->sd.entity, ssd->npads, ssd->pads); + if (rval) { + dev_err(&client->dev, "media_entity_pads_init failed\n"); + return rval; + } + + rval = __v4l2_subdev_init_finalize(&ssd->sd, lock_name, lock_key); + if (rval) { + media_entity_cleanup(&ssd->sd.entity); + return rval; + } + + return 0; +} + +static int ccs_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct ccs_subdev *ssd = to_ccs_subdev(sd); + struct ccs_sensor *sensor = ssd->sensor; + unsigned int pad = ssd == sensor->pixel_array ? + CCS_PA_PAD_SRC : CCS_PAD_SINK; + struct v4l2_mbus_framefmt *fmt = + v4l2_subdev_state_get_format(sd_state, pad); + struct v4l2_rect *crop = + v4l2_subdev_state_get_crop(sd_state, pad); + + ccs_get_native_size(ssd, crop); + + fmt->width = crop->width; + fmt->height = crop->height; + fmt->code = sensor->internal_csi_format->code; + fmt->field = V4L2_FIELD_NONE; + + if (ssd == sensor->pixel_array) + return 0; + + fmt = v4l2_subdev_state_get_format(sd_state, CCS_PAD_SRC); + fmt->code = ssd == sensor->src ? + sensor->csi_format->code : sensor->internal_csi_format->code; + fmt->field = V4L2_FIELD_NONE; + + ccs_propagate(sd, sd_state, V4L2_SEL_TGT_CROP); + + return 0; +} + +static const struct v4l2_subdev_video_ops ccs_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, + .pre_streamon = ccs_pre_streamon, + .post_streamoff = ccs_post_streamoff, +}; + +static const struct v4l2_subdev_pad_ops ccs_pad_ops = { + .enum_mbus_code = ccs_enum_mbus_code, + .get_fmt = ccs_get_format, + .set_fmt = ccs_set_format, + .get_selection = ccs_get_selection, + .set_selection = ccs_set_selection, + .enable_streams = ccs_enable_streams, + .disable_streams = ccs_disable_streams, + .get_frame_desc = ccs_get_frame_desc, +}; + +static const struct v4l2_subdev_sensor_ops ccs_sensor_ops = { + .g_skip_frames = ccs_get_skip_frames, + .g_skip_top_lines = ccs_get_skip_top_lines, +}; + +static const struct v4l2_subdev_ops ccs_ops = { + .video = &ccs_video_ops, + .pad = &ccs_pad_ops, + .sensor = &ccs_sensor_ops, +}; + +static const struct media_entity_operations ccs_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops ccs_internal_ops = { + .init_state = ccs_init_state, +}; + +static const struct v4l2_subdev_internal_ops ccs_internal_src_ops = { + .init_state = ccs_init_state, + .registered = ccs_registered, + .unregistered = ccs_unregistered, +}; + +/* ----------------------------------------------------------------------------- + * I2C Driver + */ + +static int ccs_get_hwconfig(struct ccs_sensor *sensor, struct device *dev) +{ + struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = V4L2_MBUS_UNKNOWN }; + struct fwnode_handle *fwnode = dev_fwnode(dev), *ep; + struct ccs_hwconfig *hwcfg = &sensor->hwcfg; + int rval; + + ep = fwnode_graph_get_endpoint_by_id(fwnode, 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + return -ENODEV; + + /* + * Note that we do need to rely on detecting the bus type between CSI-2 + * D-PHY and CCP2 as the old bindings did not require it. + */ + rval = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + if (rval) + goto out_err; + + switch (bus_cfg.bus_type) { + case V4L2_MBUS_CSI2_DPHY: + hwcfg->csi_signalling_mode = CCS_CSI_SIGNALING_MODE_CSI_2_DPHY; + hwcfg->lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; + break; + case V4L2_MBUS_CSI2_CPHY: + hwcfg->csi_signalling_mode = CCS_CSI_SIGNALING_MODE_CSI_2_CPHY; + hwcfg->lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; + break; + case V4L2_MBUS_CSI1: + case V4L2_MBUS_CCP2: + hwcfg->csi_signalling_mode = bus_cfg.bus.mipi_csi1.strobe ? + SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_STROBE : + SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_CLOCK; + hwcfg->lanes = 1; + break; + default: + dev_err(dev, "unsupported bus %u\n", bus_cfg.bus_type); + rval = -EINVAL; + goto out_err; + } + + dev_dbg(dev, "signalling mode: %u\n", hwcfg->csi_signalling_mode); + + if (!bus_cfg.nr_of_link_frequencies) { + dev_warn(dev, "no link frequencies defined\n"); + rval = -EINVAL; + goto out_err; + } + + hwcfg->op_sys_clock = + devm_kcalloc(dev, + bus_cfg.nr_of_link_frequencies + 1 /* guardian */, + sizeof(*hwcfg->op_sys_clock), GFP_KERNEL); + if (!hwcfg->op_sys_clock) { + rval = -ENOMEM; + goto out_err; + } + + for (unsigned int i = 0; i < bus_cfg.nr_of_link_frequencies; i++) { + hwcfg->op_sys_clock[i] = bus_cfg.link_frequencies[i]; + dev_dbg(dev, "freq %u: %lld\n", i, hwcfg->op_sys_clock[i]); + } + + fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", + &hwcfg->ext_clk); + +out_err: + v4l2_fwnode_endpoint_free(&bus_cfg); + fwnode_handle_put(ep); + + return rval; +} + +static int ccs_firmware_name(struct i2c_client *client, + struct ccs_sensor *sensor, char *filename, + size_t filename_size, bool is_module) +{ + const struct ccs_device *ccsdev = device_get_match_data(&client->dev); + bool is_ccs = !(ccsdev->flags & CCS_DEVICE_FLAG_IS_SMIA); + bool is_smiapp = sensor->minfo.smiapp_version; + u16 manufacturer_id; + u16 model_id; + u16 revision_number; + + /* + * Old SMIA is module-agnostic. Its sensor identification is based on + * what now are those of the module. + */ + if (is_module || (!is_ccs && !is_smiapp)) { + manufacturer_id = is_ccs ? + sensor->minfo.mipi_manufacturer_id : + sensor->minfo.smia_manufacturer_id; + model_id = sensor->minfo.model_id; + revision_number = sensor->minfo.revision_number; + } else { + manufacturer_id = is_ccs ? + sensor->minfo.sensor_mipi_manufacturer_id : + sensor->minfo.sensor_smia_manufacturer_id; + model_id = sensor->minfo.sensor_model_id; + revision_number = sensor->minfo.sensor_revision_number; + } + + return snprintf(filename, filename_size, + "ccs/%s-%s-%0*x-%4.4x-%0*x.fw", + is_ccs ? "ccs" : is_smiapp ? "smiapp" : "smia", + is_module || (!is_ccs && !is_smiapp) ? + "module" : "sensor", + is_ccs ? 4 : 2, manufacturer_id, model_id, + !is_ccs && !is_module ? 2 : 4, revision_number); +} + +static int ccs_probe(struct i2c_client *client) +{ + static struct lock_class_key pixel_array_lock_key, binner_lock_key, + scaler_lock_key; + const struct ccs_device *ccsdev = device_get_match_data(&client->dev); + struct ccs_sensor *sensor; + const struct firmware *fw; + char filename[40]; + unsigned int i; + int rval; + + sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); + if (sensor == NULL) + return -ENOMEM; + + rval = ccs_get_hwconfig(sensor, &client->dev); + if (rval) + return rval; + + sensor->src = &sensor->ssds[sensor->ssds_used]; + + v4l2_i2c_subdev_init(&sensor->src->sd, client, &ccs_ops); + sensor->src->sd.internal_ops = &ccs_internal_src_ops; + + sensor->regulators = devm_kcalloc(&client->dev, + ARRAY_SIZE(ccs_regulators), + sizeof(*sensor->regulators), + GFP_KERNEL); + if (!sensor->regulators) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(ccs_regulators); i++) + sensor->regulators[i].supply = ccs_regulators[i]; + + rval = devm_regulator_bulk_get(&client->dev, ARRAY_SIZE(ccs_regulators), + sensor->regulators); + if (rval) { + dev_err(&client->dev, "could not get regulators\n"); + return rval; + } + + sensor->ext_clk = devm_v4l2_sensor_clk_get(&client->dev, NULL); + if (IS_ERR(sensor->ext_clk)) + return dev_err_probe(&client->dev, PTR_ERR(sensor->ext_clk), + "could not get clock\n"); + + if (sensor->hwcfg.ext_clk) { + unsigned long rate; + + rval = clk_set_rate(sensor->ext_clk, sensor->hwcfg.ext_clk); + if (rval < 0) + return dev_err_probe(&client->dev, rval, + "unable to set clock freq to %u\n", + sensor->hwcfg.ext_clk); + + rate = clk_get_rate(sensor->ext_clk); + if (rate != sensor->hwcfg.ext_clk) { + return dev_err_probe(&client->dev, -EINVAL, + "can't set clock freq, asked for %u but got %lu\n", + sensor->hwcfg.ext_clk, rate); + } + } else { + sensor->hwcfg.ext_clk = clk_get_rate(sensor->ext_clk); + dev_dbg(&client->dev, "obtained clock freq %u\n", + sensor->hwcfg.ext_clk); + } + + if (!sensor->hwcfg.ext_clk) { + dev_err(&client->dev, "cannot work with xclk frequency 0\n"); + return -EINVAL; + } + + sensor->reset = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(sensor->reset)) + return PTR_ERR(sensor->reset); + /* Support old users that may have used "xshutdown" property. */ + if (!sensor->reset) + sensor->xshutdown = devm_gpiod_get_optional(&client->dev, + "xshutdown", + GPIOD_OUT_LOW); + if (IS_ERR(sensor->xshutdown)) + return PTR_ERR(sensor->xshutdown); + + sensor->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(sensor->regmap)) { + dev_err(&client->dev, "can't initialise CCI (%pe)\n", + sensor->regmap); + return PTR_ERR(sensor->regmap); + } + + rval = ccs_power_on(&client->dev); + if (rval < 0) + return rval; + + mutex_init(&sensor->mutex); + + rval = ccs_identify_module(sensor); + if (rval) { + rval = -ENODEV; + goto out_power_off; + } + + rval = ccs_firmware_name(client, sensor, filename, sizeof(filename), + false); + if (rval >= sizeof(filename)) { + rval = -ENOMEM; + goto out_power_off; + } + + rval = request_firmware(&fw, filename, &client->dev); + if (!rval) { + rval = ccs_data_parse(&sensor->sdata, fw->data, fw->size, + &client->dev, true); + release_firmware(fw); + if (rval) + goto out_power_off; + } + + if (!(ccsdev->flags & CCS_DEVICE_FLAG_IS_SMIA) || + sensor->minfo.smiapp_version) { + rval = ccs_firmware_name(client, sensor, filename, + sizeof(filename), true); + if (rval >= sizeof(filename)) { + rval = -ENOMEM; + goto out_release_sdata; + } + + rval = request_firmware(&fw, filename, &client->dev); + if (!rval) { + rval = ccs_data_parse(&sensor->mdata, fw->data, + fw->size, &client->dev, true); + release_firmware(fw); + if (rval) + goto out_release_sdata; + } + } + + rval = ccs_read_all_limits(sensor); + if (rval) + goto out_release_mdata; + + rval = ccs_read_frame_fmt(sensor); + if (rval) { + rval = -ENODEV; + goto out_free_ccs_limits; + } + + rval = ccs_update_phy_ctrl(sensor); + if (rval < 0) + goto out_free_ccs_limits; + + rval = ccs_call_quirk(sensor, limits); + if (rval) { + dev_err(&client->dev, "limits quirks failed\n"); + goto out_free_ccs_limits; + } + + if (CCS_LIM(sensor, BINNING_CAPABILITY)) { + sensor->nbinning_subtypes = + min_t(u8, CCS_LIM(sensor, BINNING_SUB_TYPES), + CCS_LIM_BINNING_SUB_TYPE_MAX_N); + + for (i = 0; i < sensor->nbinning_subtypes; i++) { + sensor->binning_subtypes[i].horizontal = + CCS_LIM_AT(sensor, BINNING_SUB_TYPE, i) >> + CCS_BINNING_SUB_TYPE_COLUMN_SHIFT; + sensor->binning_subtypes[i].vertical = + CCS_LIM_AT(sensor, BINNING_SUB_TYPE, i) & + CCS_BINNING_SUB_TYPE_ROW_MASK; + + dev_dbg(&client->dev, "binning %xx%x\n", + sensor->binning_subtypes[i].horizontal, + sensor->binning_subtypes[i].vertical); + } + } + + if (device_create_file(&client->dev, &dev_attr_ident) != 0) { + dev_err(&client->dev, "sysfs ident entry creation failed\n"); + rval = -ENOENT; + goto out_free_ccs_limits; + } + + if (sensor->minfo.smiapp_version && + CCS_LIM(sensor, DATA_TRANSFER_IF_CAPABILITY) & + CCS_DATA_TRANSFER_IF_CAPABILITY_SUPPORTED) { + if (device_create_file(&client->dev, &dev_attr_nvm) != 0) { + dev_err(&client->dev, "sysfs nvm entry failed\n"); + rval = -EBUSY; + goto out_cleanup; + } + } + + if (!CCS_LIM(sensor, MIN_OP_SYS_CLK_DIV) || + !CCS_LIM(sensor, MAX_OP_SYS_CLK_DIV) || + !CCS_LIM(sensor, MIN_OP_PIX_CLK_DIV) || + !CCS_LIM(sensor, MAX_OP_PIX_CLK_DIV)) { + /* No OP clock branch */ + sensor->pll.flags |= CCS_PLL_FLAG_NO_OP_CLOCKS; + } else if (CCS_LIM(sensor, SCALING_CAPABILITY) + != CCS_SCALING_CAPABILITY_NONE || + CCS_LIM(sensor, DIGITAL_CROP_CAPABILITY) + == CCS_DIGITAL_CROP_CAPABILITY_INPUT_CROP) { + /* We have a scaler or digital crop. */ + sensor->scaler = &sensor->ssds[sensor->ssds_used]; + sensor->ssds_used++; + } + sensor->binner = &sensor->ssds[sensor->ssds_used]; + sensor->ssds_used++; + sensor->pixel_array = &sensor->ssds[sensor->ssds_used]; + sensor->ssds_used++; + + /* prepare PLL configuration input values */ + switch (sensor->hwcfg.csi_signalling_mode) { + case CCS_CSI_SIGNALING_MODE_CSI_2_CPHY: + sensor->pll.bus_type = CCS_PLL_BUS_TYPE_CSI2_CPHY; + break; + case CCS_CSI_SIGNALING_MODE_CSI_2_DPHY: + case SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_CLOCK: + case SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_STROBE: + sensor->pll.bus_type = CCS_PLL_BUS_TYPE_CSI2_DPHY; + break; + default: + dev_err(&client->dev, "unsupported signalling mode %u\n", + sensor->hwcfg.csi_signalling_mode); + rval = -EINVAL; + goto out_cleanup; + } + sensor->pll.csi2.lanes = sensor->hwcfg.lanes; + if (CCS_LIM(sensor, CLOCK_CALCULATION) & + CCS_CLOCK_CALCULATION_LANE_SPEED) { + sensor->pll.flags |= CCS_PLL_FLAG_LANE_SPEED_MODEL; + if (CCS_LIM(sensor, CLOCK_CALCULATION) & + CCS_CLOCK_CALCULATION_LINK_DECOUPLED) { + sensor->pll.vt_lanes = + CCS_LIM(sensor, NUM_OF_VT_LANES) + 1; + sensor->pll.op_lanes = + CCS_LIM(sensor, NUM_OF_OP_LANES) + 1; + } else { + sensor->pll.vt_lanes = sensor->pll.csi2.lanes; + sensor->pll.op_lanes = sensor->pll.csi2.lanes; + } + } + if (CCS_LIM(sensor, CLOCK_TREE_PLL_CAPABILITY) & + CCS_CLOCK_TREE_PLL_CAPABILITY_EXT_DIVIDER) + sensor->pll.flags |= CCS_PLL_FLAG_EXT_IP_PLL_DIVIDER; + if (CCS_LIM(sensor, CLOCK_TREE_PLL_CAPABILITY) & + CCS_CLOCK_TREE_PLL_CAPABILITY_FLEXIBLE_OP_PIX_CLK_DIV) + sensor->pll.flags |= CCS_PLL_FLAG_FLEXIBLE_OP_PIX_CLK_DIV; + if (CCS_LIM(sensor, FIFO_SUPPORT_CAPABILITY) & + CCS_FIFO_SUPPORT_CAPABILITY_DERATING) + sensor->pll.flags |= CCS_PLL_FLAG_FIFO_DERATING; + if (CCS_LIM(sensor, FIFO_SUPPORT_CAPABILITY) & + CCS_FIFO_SUPPORT_CAPABILITY_DERATING_OVERRATING) + sensor->pll.flags |= CCS_PLL_FLAG_FIFO_DERATING | + CCS_PLL_FLAG_FIFO_OVERRATING; + if (CCS_LIM(sensor, CLOCK_TREE_PLL_CAPABILITY) & + CCS_CLOCK_TREE_PLL_CAPABILITY_DUAL_PLL) { + if (CCS_LIM(sensor, CLOCK_TREE_PLL_CAPABILITY) & + CCS_CLOCK_TREE_PLL_CAPABILITY_SINGLE_PLL) { + u32 v; + + /* Use sensor default in PLL mode selection */ + rval = ccs_read(sensor, PLL_MODE, &v); + if (rval) + goto out_cleanup; + + if (v == CCS_PLL_MODE_DUAL) + sensor->pll.flags |= CCS_PLL_FLAG_DUAL_PLL; + } else { + sensor->pll.flags |= CCS_PLL_FLAG_DUAL_PLL; + } + if (CCS_LIM(sensor, CLOCK_CALCULATION) & + CCS_CLOCK_CALCULATION_DUAL_PLL_OP_SYS_DDR) + sensor->pll.flags |= CCS_PLL_FLAG_OP_SYS_DDR; + if (CCS_LIM(sensor, CLOCK_CALCULATION) & + CCS_CLOCK_CALCULATION_DUAL_PLL_OP_PIX_DDR) + sensor->pll.flags |= CCS_PLL_FLAG_OP_PIX_DDR; + } + sensor->pll.op_bits_per_lane = CCS_LIM(sensor, OP_BITS_PER_LANE); + sensor->pll.ext_clk_freq_hz = sensor->hwcfg.ext_clk; + sensor->pll.scale_n = CCS_LIM(sensor, SCALER_N_MIN); + + rval = ccs_get_mbus_formats(sensor); + if (rval) { + rval = -ENODEV; + goto out_cleanup; + } + + rval = ccs_init_subdev(sensor, sensor->scaler, " scaler", 2, + MEDIA_ENT_F_PROC_VIDEO_SCALER, + "ccs scaler mutex", &scaler_lock_key); + if (rval) + goto out_cleanup; + rval = ccs_init_subdev(sensor, sensor->binner, " binner", 2, + MEDIA_ENT_F_PROC_VIDEO_SCALER, + "ccs binner mutex", &binner_lock_key); + if (rval) + goto out_cleanup; + rval = ccs_init_subdev(sensor, sensor->pixel_array, " pixel_array", 1, + MEDIA_ENT_F_CAM_SENSOR, "ccs pixel array mutex", + &pixel_array_lock_key); + if (rval) + goto out_cleanup; + + rval = ccs_init_controls(sensor); + if (rval < 0) + goto out_cleanup; + + rval = ccs_call_quirk(sensor, init); + if (rval) + goto out_cleanup; + + rval = ccs_init_late_controls(sensor); + if (rval) { + rval = -ENODEV; + goto out_cleanup; + } + + scoped_guard(mutex, &sensor->mutex) + rval = ccs_pll_blanking_update(sensor); + if (rval) { + dev_err(&client->dev, "update mode failed\n"); + goto out_cleanup; + } + + sensor->dev_init_done = true; + sensor->handler_setup_needed = true; + + rval = ccs_write_msr_regs(sensor); + if (rval) + goto out_cleanup; + + pm_runtime_set_active(&client->dev); + pm_runtime_get_noresume(&client->dev); + pm_runtime_enable(&client->dev); + + rval = v4l2_async_register_subdev_sensor(&sensor->src->sd); + if (rval < 0) + goto out_disable_runtime_pm; + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return 0; + +out_disable_runtime_pm: + pm_runtime_put_noidle(&client->dev); + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + +out_cleanup: + ccs_cleanup(sensor); + +out_free_ccs_limits: + kfree(sensor->ccs_limits); + +out_release_mdata: + kvfree(sensor->mdata.backing); + +out_release_sdata: + kvfree(sensor->sdata.backing); + +out_power_off: + ccs_power_off(&client->dev); + mutex_destroy(&sensor->mutex); + + return rval; +} + +static void ccs_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + unsigned int i; + + v4l2_async_unregister_subdev(subdev); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + ccs_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + } + + for (i = 0; i < sensor->ssds_used; i++) + v4l2_device_unregister_subdev(&sensor->ssds[i].sd); + ccs_cleanup(sensor); + mutex_destroy(&sensor->mutex); + kfree(sensor->ccs_limits); + kvfree(sensor->sdata.backing); + kvfree(sensor->mdata.backing); +} + +static const struct ccs_device smia_device = { + .flags = CCS_DEVICE_FLAG_IS_SMIA, +}; + +static const struct ccs_device ccs_device = {}; + +static const struct acpi_device_id ccs_acpi_table[] = { + { .id = "MIPI0200", .driver_data = (unsigned long)&ccs_device }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, ccs_acpi_table); + +static const struct of_device_id ccs_of_table[] = { + { .compatible = "mipi-ccs-1.1", .data = &ccs_device }, + { .compatible = "mipi-ccs-1.0", .data = &ccs_device }, + { .compatible = "mipi-ccs", .data = &ccs_device }, + { .compatible = "nokia,smia", .data = &smia_device }, + { }, +}; +MODULE_DEVICE_TABLE(of, ccs_of_table); + +static const struct dev_pm_ops ccs_pm_ops = { + SET_RUNTIME_PM_OPS(ccs_power_off, ccs_power_on, NULL) +}; + +static struct i2c_driver ccs_i2c_driver = { + .driver = { + .acpi_match_table = ccs_acpi_table, + .of_match_table = ccs_of_table, + .name = CCS_NAME, + .pm = &ccs_pm_ops, + }, + .probe = ccs_probe, + .remove = ccs_remove, +}; + +static int ccs_module_init(void) +{ + unsigned int i, l; + + CCS_BUILD_BUG; + + for (i = 0, l = 0; ccs_limits[i].size && l < CCS_L_LAST; i++) { + if (!(ccs_limits[i].flags & CCS_L_FL_SAME_REG)) { + ccs_limit_offsets[l + 1].lim = + ALIGN(ccs_limit_offsets[l].lim + + ccs_limits[i].size, + ccs_limits[i + 1].reg ? + CCI_REG_WIDTH_BYTES(ccs_limits[i + 1].reg) : + 1U); + ccs_limit_offsets[l].info = i; + l++; + } else { + ccs_limit_offsets[l].lim += ccs_limits[i].size; + } + } + + if (WARN_ON(ccs_limits[i].size)) + return -EINVAL; + + if (WARN_ON(l != CCS_L_LAST)) + return -EINVAL; + + return i2c_register_driver(THIS_MODULE, &ccs_i2c_driver); +} + +static void ccs_module_cleanup(void) +{ + i2c_del_driver(&ccs_i2c_driver); +} + +module_init(ccs_module_init); +module_exit(ccs_module_cleanup); + +MODULE_AUTHOR("Sakari Ailus "); +MODULE_DESCRIPTION("Generic MIPI CCS/SMIA/SMIA++ camera sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("smiapp"); diff --git a/drivers/media/i2c/ccs/ccs-data-defs.h b/drivers/media/i2c/ccs/ccs-data-defs.h new file mode 100644 index 000000000..1c9b1d1ac --- /dev/null +++ b/drivers/media/i2c/ccs/ccs-data-defs.h @@ -0,0 +1,221 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * CCS static data binary format definitions + * + * Copyright 2019--2020 Intel Corporation + */ + +#ifndef __CCS_DATA_DEFS_H__ +#define __CCS_DATA_DEFS_H__ + +#include "ccs-data.h" + +#define CCS_STATIC_DATA_VERSION 0 + +enum __ccs_data_length_specifier_id { + CCS_DATA_LENGTH_SPECIFIER_1 = 0, + CCS_DATA_LENGTH_SPECIFIER_2 = 1, + CCS_DATA_LENGTH_SPECIFIER_3 = 2 +}; + +#define CCS_DATA_LENGTH_SPECIFIER_SIZE_SHIFT 6 + +struct __ccs_data_length_specifier { + u8 length; +} __packed; + +struct __ccs_data_length_specifier2 { + u8 length[2]; +} __packed; + +struct __ccs_data_length_specifier3 { + u8 length[3]; +} __packed; + +struct __ccs_data_block { + u8 id; + struct __ccs_data_length_specifier length; +} __packed; + +#define CCS_DATA_BLOCK_HEADER_ID_VERSION_SHIFT 5 + +struct __ccs_data_block3 { + u8 id; + struct __ccs_data_length_specifier2 length; +} __packed; + +struct __ccs_data_block4 { + u8 id; + struct __ccs_data_length_specifier3 length; +} __packed; + +enum __ccs_data_block_id { + CCS_DATA_BLOCK_ID_DUMMY = 1, + CCS_DATA_BLOCK_ID_DATA_VERSION = 2, + CCS_DATA_BLOCK_ID_SENSOR_READ_ONLY_REGS = 3, + CCS_DATA_BLOCK_ID_MODULE_READ_ONLY_REGS = 4, + CCS_DATA_BLOCK_ID_SENSOR_MANUFACTURER_REGS = 5, + CCS_DATA_BLOCK_ID_MODULE_MANUFACTURER_REGS = 6, + CCS_DATA_BLOCK_ID_SENSOR_RULE_BASED_BLOCK = 32, + CCS_DATA_BLOCK_ID_MODULE_RULE_BASED_BLOCK = 33, + CCS_DATA_BLOCK_ID_SENSOR_PDAF_PIXEL_LOCATION = 36, + CCS_DATA_BLOCK_ID_MODULE_PDAF_PIXEL_LOCATION = 37, + CCS_DATA_BLOCK_ID_LICENSE = 40, + CCS_DATA_BLOCK_ID_END = 127, +}; + +struct __ccs_data_block_version { + u8 static_data_version_major[2]; + u8 static_data_version_minor[2]; + u8 year[2]; + u8 month; + u8 day; +} __packed; + +struct __ccs_data_block_regs { + u8 reg_len; +} __packed; + +#define CCS_DATA_BLOCK_REGS_ADDR_MASK 0x07 +#define CCS_DATA_BLOCK_REGS_LEN_SHIFT 3 +#define CCS_DATA_BLOCK_REGS_LEN_MASK 0x38 +#define CCS_DATA_BLOCK_REGS_SEL_SHIFT 6 + +enum ccs_data_block_regs_sel { + CCS_DATA_BLOCK_REGS_SEL_REGS = 0, + CCS_DATA_BLOCK_REGS_SEL_REGS2 = 1, + CCS_DATA_BLOCK_REGS_SEL_REGS3 = 2, +}; + +struct __ccs_data_block_regs2 { + u8 reg_len; + u8 addr; +} __packed; + +#define CCS_DATA_BLOCK_REGS_2_ADDR_MASK 0x01 +#define CCS_DATA_BLOCK_REGS_2_LEN_SHIFT 1 +#define CCS_DATA_BLOCK_REGS_2_LEN_MASK 0x3e + +struct __ccs_data_block_regs3 { + u8 reg_len; + u8 addr[2]; +} __packed; + +#define CCS_DATA_BLOCK_REGS_3_LEN_MASK 0x3f + +enum __ccs_data_ffd_pixelcode { + CCS_DATA_BLOCK_FFD_PIXELCODE_EMBEDDED = 1, + CCS_DATA_BLOCK_FFD_PIXELCODE_DUMMY = 2, + CCS_DATA_BLOCK_FFD_PIXELCODE_BLACK = 3, + CCS_DATA_BLOCK_FFD_PIXELCODE_DARK = 4, + CCS_DATA_BLOCK_FFD_PIXELCODE_VISIBLE = 5, + CCS_DATA_BLOCK_FFD_PIXELCODE_MS_0 = 8, + CCS_DATA_BLOCK_FFD_PIXELCODE_MS_1 = 9, + CCS_DATA_BLOCK_FFD_PIXELCODE_MS_2 = 10, + CCS_DATA_BLOCK_FFD_PIXELCODE_MS_3 = 11, + CCS_DATA_BLOCK_FFD_PIXELCODE_MS_4 = 12, + CCS_DATA_BLOCK_FFD_PIXELCODE_MS_5 = 13, + CCS_DATA_BLOCK_FFD_PIXELCODE_MS_6 = 14, + CCS_DATA_BLOCK_FFD_PIXELCODE_TOP_OB = 16, + CCS_DATA_BLOCK_FFD_PIXELCODE_BOTTOM_OB = 17, + CCS_DATA_BLOCK_FFD_PIXELCODE_LEFT_OB = 18, + CCS_DATA_BLOCK_FFD_PIXELCODE_RIGHT_OB = 19, + CCS_DATA_BLOCK_FFD_PIXELCODE_TOP_LEFT_OB = 20, + CCS_DATA_BLOCK_FFD_PIXELCODE_TOP_RIGHT_OB = 21, + CCS_DATA_BLOCK_FFD_PIXELCODE_BOTTOM_LEFT_OB = 22, + CCS_DATA_BLOCK_FFD_PIXELCODE_BOTTOM_RIGHT_OB = 23, + CCS_DATA_BLOCK_FFD_PIXELCODE_TOTAL = 24, + CCS_DATA_BLOCK_FFD_PIXELCODE_TOP_PDAF = 32, + CCS_DATA_BLOCK_FFD_PIXELCODE_BOTTOM_PDAF = 33, + CCS_DATA_BLOCK_FFD_PIXELCODE_LEFT_PDAF = 34, + CCS_DATA_BLOCK_FFD_PIXELCODE_RIGHT_PDAF = 35, + CCS_DATA_BLOCK_FFD_PIXELCODE_TOP_LEFT_PDAF = 36, + CCS_DATA_BLOCK_FFD_PIXELCODE_TOP_RIGHT_PDAF = 37, + CCS_DATA_BLOCK_FFD_PIXELCODE_BOTTOM_LEFT_PDAF = 38, + CCS_DATA_BLOCK_FFD_PIXELCODE_BOTTOM_RIGHT_PDAF = 39, + CCS_DATA_BLOCK_FFD_PIXELCODE_SEPARATED_PDAF = 40, + CCS_DATA_BLOCK_FFD_PIXELCODE_ORIGINAL_ORDER_PDAF = 41, + CCS_DATA_BLOCK_FFD_PIXELCODE_VENDOR_PDAF = 41, +}; + +struct __ccs_data_block_ffd_entry { + u8 pixelcode; + u8 reserved; + u8 value[2]; +} __packed; + +struct __ccs_data_block_ffd { + u8 num_column_descs; + u8 num_row_descs; +} __packed; + +enum __ccs_data_block_rule_id { + CCS_DATA_BLOCK_RULE_ID_IF = 1, + CCS_DATA_BLOCK_RULE_ID_READ_ONLY_REGS = 2, + CCS_DATA_BLOCK_RULE_ID_FFD = 3, + CCS_DATA_BLOCK_RULE_ID_MSR = 4, + CCS_DATA_BLOCK_RULE_ID_PDAF_READOUT = 5, +}; + +struct __ccs_data_block_rule_if { + u8 addr[2]; + u8 value; + u8 mask; +} __packed; + +enum __ccs_data_block_pdaf_readout_order { + CCS_DATA_BLOCK_PDAF_READOUT_ORDER_ORIGINAL = 1, + CCS_DATA_BLOCK_PDAF_READOUT_ORDER_SEPARATE_WITHIN_LINE = 2, + CCS_DATA_BLOCK_PDAF_READOUT_ORDER_SEPARATE_TYPES_SEPARATE_LINES = 3, +}; + +struct __ccs_data_block_pdaf_readout { + u8 pdaf_readout_info_reserved; + u8 pdaf_readout_info_order; +} __packed; + +struct __ccs_data_block_pdaf_pix_loc_block_desc { + u8 block_type_id; + u8 repeat_x[2]; +} __packed; + +struct __ccs_data_block_pdaf_pix_loc_block_desc_group { + u8 num_block_descs[2]; + u8 repeat_y; +} __packed; + +enum __ccs_data_block_pdaf_pix_loc_pixel_type { + CCS_DATA_PDAF_PIXEL_TYPE_LEFT_SEPARATED = 0, + CCS_DATA_PDAF_PIXEL_TYPE_RIGHT_SEPARATED = 1, + CCS_DATA_PDAF_PIXEL_TYPE_TOP_SEPARATED = 2, + CCS_DATA_PDAF_PIXEL_TYPE_BOTTOM_SEPARATED = 3, + CCS_DATA_PDAF_PIXEL_TYPE_LEFT_SIDE_BY_SIDE = 4, + CCS_DATA_PDAF_PIXEL_TYPE_RIGHT_SIDE_BY_SIDE = 5, + CCS_DATA_PDAF_PIXEL_TYPE_TOP_SIDE_BY_SIDE = 6, + CCS_DATA_PDAF_PIXEL_TYPE_BOTTOM_SIDE_BY_SIDE = 7, + CCS_DATA_PDAF_PIXEL_TYPE_TOP_LEFT = 8, + CCS_DATA_PDAF_PIXEL_TYPE_TOP_RIGHT = 9, + CCS_DATA_PDAF_PIXEL_TYPE_BOTTOM_LEFT = 10, + CCS_DATA_PDAF_PIXEL_TYPE_BOTTOM_RIGHT = 11, +}; + +struct __ccs_data_block_pdaf_pix_loc_pixel_desc { + u8 pixel_type; + u8 small_offset_x; + u8 small_offset_y; +} __packed; + +struct __ccs_data_block_pdaf_pix_loc { + u8 main_offset_x[2]; + u8 main_offset_y[2]; + u8 global_pdaf_type; + u8 block_width; + u8 block_height; + u8 num_block_desc_groups[2]; +} __packed; + +struct __ccs_data_block_end { + u8 crc[4]; +} __packed; + +#endif /* __CCS_DATA_DEFS_H__ */ diff --git a/drivers/media/i2c/ccs/ccs-data.c b/drivers/media/i2c/ccs/ccs-data.c new file mode 100644 index 000000000..f469afcea --- /dev/null +++ b/drivers/media/i2c/ccs/ccs-data.c @@ -0,0 +1,982 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * CCS static data binary parser library + * + * Copyright 2019--2020 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include + +#include "ccs-data-defs.h" + +struct bin_container { + void *base; + void *now; + void *end; + size_t size; +}; + +static void *bin_alloc(struct bin_container *bin, size_t len) +{ + void *ptr; + + len = ALIGN(len, 8); + + if (bin->end - bin->now < len) + return NULL; + + ptr = bin->now; + bin->now += len; + + return ptr; +} + +static void bin_reserve(struct bin_container *bin, size_t len) +{ + bin->size += ALIGN(len, 8); +} + +static int bin_backing_alloc(struct bin_container *bin) +{ + bin->base = bin->now = kvzalloc(bin->size, GFP_KERNEL); + if (!bin->base) + return -ENOMEM; + + bin->end = bin->base + bin->size; + + return 0; +} + +#define is_contained(var, endp) \ + (sizeof(*var) <= (endp) - (void *)(var)) +#define has_headroom(ptr, headroom, endp) \ + ((headroom) <= (endp) - (void *)(ptr)) +#define is_contained_with_headroom(var, headroom, endp) \ + (sizeof(*var) + (headroom) <= (endp) - (void *)(var)) + +static int +ccs_data_parse_length_specifier(const struct __ccs_data_length_specifier *__len, + size_t *__hlen, size_t *__plen, + const void *endp) +{ + size_t hlen, plen; + + if (!is_contained(__len, endp)) + return -ENODATA; + + switch (__len->length >> CCS_DATA_LENGTH_SPECIFIER_SIZE_SHIFT) { + case CCS_DATA_LENGTH_SPECIFIER_1: + hlen = sizeof(*__len); + plen = __len->length & + ((1 << CCS_DATA_LENGTH_SPECIFIER_SIZE_SHIFT) - 1); + break; + case CCS_DATA_LENGTH_SPECIFIER_2: { + struct __ccs_data_length_specifier2 *__len2 = (void *)__len; + + if (!is_contained(__len2, endp)) + return -ENODATA; + + hlen = sizeof(*__len2); + plen = ((size_t) + (__len2->length[0] & + ((1 << CCS_DATA_LENGTH_SPECIFIER_SIZE_SHIFT) - 1)) + << 8) + __len2->length[1]; + break; + } + case CCS_DATA_LENGTH_SPECIFIER_3: { + struct __ccs_data_length_specifier3 *__len3 = (void *)__len; + + if (!is_contained(__len3, endp)) + return -ENODATA; + + hlen = sizeof(*__len3); + plen = ((size_t) + (__len3->length[0] & + ((1 << CCS_DATA_LENGTH_SPECIFIER_SIZE_SHIFT) - 1)) + << 16) + (__len3->length[1] << 8) + __len3->length[2]; + break; + } + default: + return -EINVAL; + } + + if (!has_headroom(__len, hlen + plen, endp)) + return -ENODATA; + + *__hlen = hlen; + *__plen = plen; + + return 0; +} + +static u8 +ccs_data_parse_format_version(const struct __ccs_data_block *block) +{ + return block->id >> CCS_DATA_BLOCK_HEADER_ID_VERSION_SHIFT; +} + +static u8 ccs_data_parse_block_id(const struct __ccs_data_block *block, + bool is_first) +{ + if (!is_first) + return block->id; + + return block->id & ((1 << CCS_DATA_BLOCK_HEADER_ID_VERSION_SHIFT) - 1); +} + +static int ccs_data_parse_version(struct bin_container *bin, + struct ccs_data_container *ccsdata, + const void *payload, const void *endp) +{ + const struct __ccs_data_block_version *v = payload; + struct ccs_data_block_version *vv; + + if (v + 1 != endp) + return -ENODATA; + + if (!bin->base) { + bin_reserve(bin, sizeof(*ccsdata->version)); + return 0; + } + + ccsdata->version = bin_alloc(bin, sizeof(*ccsdata->version)); + if (!ccsdata->version) + return -ENOMEM; + + vv = ccsdata->version; + vv->version_major = ((u16)v->static_data_version_major[0] << 8) + + v->static_data_version_major[1]; + vv->version_minor = ((u16)v->static_data_version_minor[0] << 8) + + v->static_data_version_minor[1]; + vv->date_year = ((u16)v->year[0] << 8) + v->year[1]; + vv->date_month = v->month; + vv->date_day = v->day; + + return 0; +} + +static void print_ccs_data_version(struct device *dev, + struct ccs_data_block_version *v) +{ + dev_dbg(dev, + "static data version %4.4x.%4.4x, date %4.4u-%2.2u-%2.2u\n", + v->version_major, v->version_minor, + v->date_year, v->date_month, v->date_day); +} + +static int ccs_data_block_parse_header(const struct __ccs_data_block *block, + bool is_first, unsigned int *__block_id, + const void **payload, + const struct __ccs_data_block **next_block, + const void *endp, struct device *dev, + bool verbose) +{ + size_t plen, hlen; + u8 block_id; + int rval; + + if (!is_contained(block, endp)) + return -ENODATA; + + rval = ccs_data_parse_length_specifier(&block->length, &hlen, &plen, + endp); + if (rval < 0) + return rval; + + block_id = ccs_data_parse_block_id(block, is_first); + + if (verbose) + dev_dbg(dev, + "Block ID 0x%2.2x, header length %zu, payload length %zu\n", + block_id, hlen, plen); + + if (!has_headroom(&block->length, hlen + plen, endp)) + return -ENODATA; + + if (__block_id) + *__block_id = block_id; + + if (payload) + *payload = (void *)&block->length + hlen; + + if (next_block) + *next_block = (void *)&block->length + hlen + plen; + + return 0; +} + +static int ccs_data_parse_regs(struct bin_container *bin, + struct ccs_reg **__regs, + size_t *__num_regs, const void *payload, + const void *endp, struct device *dev) +{ + struct ccs_reg *regs_base = NULL, *regs = NULL; + size_t num_regs = 0; + u16 addr = 0; + + if (bin->base && __regs) { + regs = regs_base = bin_alloc(bin, sizeof(*regs) * *__num_regs); + if (!regs) + return -ENOMEM; + } + + while (payload < endp && num_regs < INT_MAX) { + const struct __ccs_data_block_regs *r = payload; + size_t len; + const void *data; + + if (!is_contained(r, endp)) + return -ENODATA; + + switch (r->reg_len >> CCS_DATA_BLOCK_REGS_SEL_SHIFT) { + case CCS_DATA_BLOCK_REGS_SEL_REGS: + addr += r->reg_len & CCS_DATA_BLOCK_REGS_ADDR_MASK; + len = ((r->reg_len & CCS_DATA_BLOCK_REGS_LEN_MASK) + >> CCS_DATA_BLOCK_REGS_LEN_SHIFT) + 1; + + if (!is_contained_with_headroom(r, len, endp)) + return -ENODATA; + + data = r + 1; + break; + case CCS_DATA_BLOCK_REGS_SEL_REGS2: { + const struct __ccs_data_block_regs2 *r2 = payload; + + if (!is_contained(r2, endp)) + return -ENODATA; + + addr += ((u16)(r2->reg_len & + CCS_DATA_BLOCK_REGS_2_ADDR_MASK) << 8) + + r2->addr; + len = ((r2->reg_len & CCS_DATA_BLOCK_REGS_2_LEN_MASK) + >> CCS_DATA_BLOCK_REGS_2_LEN_SHIFT) + 1; + + if (!is_contained_with_headroom(r2, len, endp)) + return -ENODATA; + + data = r2 + 1; + break; + } + case CCS_DATA_BLOCK_REGS_SEL_REGS3: { + const struct __ccs_data_block_regs3 *r3 = payload; + + if (!is_contained(r3, endp)) + return -ENODATA; + + addr = ((u16)r3->addr[0] << 8) + r3->addr[1]; + len = (r3->reg_len & CCS_DATA_BLOCK_REGS_3_LEN_MASK) + 1; + + if (!is_contained_with_headroom(r3, len, endp)) + return -ENODATA; + + data = r3 + 1; + break; + } + default: + return -EINVAL; + } + + num_regs++; + + if (!bin->base) { + bin_reserve(bin, len); + } else if (__regs) { + if (!regs) + return -EIO; + + regs->addr = addr; + regs->len = len; + regs->value = bin_alloc(bin, len); + if (!regs->value) + return -ENOMEM; + + memcpy(regs->value, data, len); + regs++; + } + + addr += len; + payload = data + len; + } + + if (!bin->base) + bin_reserve(bin, sizeof(*regs) * num_regs); + + if (__num_regs) + *__num_regs = num_regs; + + if (bin->base && __regs) { + if (!regs_base) + return -EIO; + + *__regs = regs_base; + } + + return 0; +} + +static int ccs_data_parse_reg_rules(struct bin_container *bin, + struct ccs_reg **__regs, + size_t *__num_regs, + const void *payload, + const void *endp, struct device *dev) +{ + int rval; + + if (!bin->base) + return ccs_data_parse_regs(bin, NULL, NULL, payload, endp, dev); + + rval = ccs_data_parse_regs(bin, NULL, __num_regs, payload, endp, dev); + if (rval) + return rval; + + return ccs_data_parse_regs(bin, __regs, __num_regs, payload, endp, + dev); +} + +static void assign_ffd_entry(struct ccs_frame_format_desc *desc, + const struct __ccs_data_block_ffd_entry *ent) +{ + desc->pixelcode = ent->pixelcode; + desc->value = ((u16)ent->value[0] << 8) + ent->value[1]; +} + +static int ccs_data_parse_ffd(struct bin_container *bin, + struct ccs_frame_format_descs **ffd, + const void *payload, + const void *endp, struct device *dev) +{ + const struct __ccs_data_block_ffd *__ffd = payload; + const struct __ccs_data_block_ffd_entry *__entry; + unsigned int i; + + if (!is_contained(__ffd, endp)) + return -ENODATA; + + if ((void *)__ffd + sizeof(*__ffd) + + ((u32)__ffd->num_column_descs + + (u32)__ffd->num_row_descs) * + sizeof(struct __ccs_data_block_ffd_entry) != endp) + return -ENODATA; + + if (!bin->base) { + bin_reserve(bin, sizeof(**ffd)); + bin_reserve(bin, __ffd->num_column_descs * + sizeof(struct ccs_frame_format_desc)); + bin_reserve(bin, __ffd->num_row_descs * + sizeof(struct ccs_frame_format_desc)); + + return 0; + } + + *ffd = bin_alloc(bin, sizeof(**ffd)); + if (!*ffd) + return -ENOMEM; + + (*ffd)->num_column_descs = __ffd->num_column_descs; + (*ffd)->num_row_descs = __ffd->num_row_descs; + __entry = (void *)(__ffd + 1); + + (*ffd)->column_descs = bin_alloc(bin, __ffd->num_column_descs * + sizeof(*(*ffd)->column_descs)); + if (!(*ffd)->column_descs) + return -ENOMEM; + + for (i = 0; i < __ffd->num_column_descs; i++, __entry++) + assign_ffd_entry(&(*ffd)->column_descs[i], __entry); + + (*ffd)->row_descs = bin_alloc(bin, __ffd->num_row_descs * + sizeof(*(*ffd)->row_descs)); + if (!(*ffd)->row_descs) + return -ENOMEM; + + for (i = 0; i < __ffd->num_row_descs; i++, __entry++) + assign_ffd_entry(&(*ffd)->row_descs[i], __entry); + + if (__entry != endp) + return -EPROTO; + + return 0; +} + +static int ccs_data_parse_pdaf_readout(struct bin_container *bin, + struct ccs_pdaf_readout **pdaf_readout, + const void *payload, + const void *endp, struct device *dev) +{ + const struct __ccs_data_block_pdaf_readout *__pdaf = payload; + + if (!is_contained(__pdaf, endp)) + return -ENODATA; + + if (!bin->base) { + bin_reserve(bin, sizeof(**pdaf_readout)); + } else { + *pdaf_readout = bin_alloc(bin, sizeof(**pdaf_readout)); + if (!*pdaf_readout) + return -ENOMEM; + + (*pdaf_readout)->pdaf_readout_info_order = + __pdaf->pdaf_readout_info_order; + } + + return ccs_data_parse_ffd(bin, !bin->base ? NULL : &(*pdaf_readout)->ffd, + __pdaf + 1, endp, dev); +} + +static int ccs_data_parse_rules(struct bin_container *bin, + struct ccs_rule **__rules, + size_t *__num_rules, const void *payload, + const void *endp, struct device *dev) +{ + struct ccs_rule *rules_base = NULL, *rules = NULL, *next_rule = NULL; + size_t num_rules = 0; + const void *__next_rule = payload; + int rval; + + if (bin->base) { + rules_base = next_rule = + bin_alloc(bin, sizeof(*rules) * *__num_rules); + if (!rules_base) + return -ENOMEM; + } + + while (__next_rule < endp) { + size_t rule_hlen, rule_plen, rule_plen2; + const u8 *__rule_type; + const void *rule_payload; + + /* Size of a single rule */ + rval = ccs_data_parse_length_specifier(__next_rule, &rule_hlen, + &rule_plen, endp); + + if (rval < 0) + return rval; + + __rule_type = __next_rule + rule_hlen; + + if (!is_contained(__rule_type, endp)) + return -ENODATA; + + rule_payload = __rule_type + 1; + rule_plen2 = rule_plen - sizeof(*__rule_type); + + if (*__rule_type == CCS_DATA_BLOCK_RULE_ID_IF) { + const struct __ccs_data_block_rule_if *__if_rules = + rule_payload; + const size_t __num_if_rules = + rule_plen2 / sizeof(*__if_rules); + struct ccs_if_rule *if_rule; + + if (!has_headroom(__if_rules, + sizeof(*__if_rules) * __num_if_rules, + rule_payload + rule_plen2)) + return -ENODATA; + + /* Also check there is no extra data */ + if (__if_rules + __num_if_rules != + rule_payload + rule_plen2) + return -EINVAL; + + if (!bin->base) { + bin_reserve(bin, + sizeof(*if_rule) * + __num_if_rules); + num_rules++; + } else { + unsigned int i; + + if (!next_rule) + return -EIO; + + rules = next_rule; + next_rule++; + + if_rule = bin_alloc(bin, + sizeof(*if_rule) * + __num_if_rules); + if (!if_rule) + return -ENOMEM; + + for (i = 0; i < __num_if_rules; i++) { + if_rule[i].addr = + ((u16)__if_rules[i].addr[0] + << 8) + + __if_rules[i].addr[1]; + if_rule[i].value = __if_rules[i].value; + if_rule[i].mask = __if_rules[i].mask; + } + + rules->if_rules = if_rule; + rules->num_if_rules = __num_if_rules; + } + } else { + /* Check there was an if rule before any other rules */ + if (bin->base && !rules) + return -EINVAL; + + switch (*__rule_type) { + case CCS_DATA_BLOCK_RULE_ID_READ_ONLY_REGS: + rval = ccs_data_parse_reg_rules(bin, + rules ? + &rules->read_only_regs : NULL, + rules ? + &rules->num_read_only_regs : NULL, + rule_payload, + rule_payload + rule_plen2, + dev); + if (rval) + return rval; + break; + case CCS_DATA_BLOCK_RULE_ID_FFD: + rval = ccs_data_parse_ffd(bin, rules ? + &rules->frame_format : NULL, + rule_payload, + rule_payload + rule_plen2, + dev); + if (rval) + return rval; + break; + case CCS_DATA_BLOCK_RULE_ID_MSR: + rval = ccs_data_parse_reg_rules(bin, + rules ? + &rules->manufacturer_regs : NULL, + rules ? + &rules->num_manufacturer_regs : NULL, + rule_payload, + rule_payload + rule_plen2, + dev); + if (rval) + return rval; + break; + case CCS_DATA_BLOCK_RULE_ID_PDAF_READOUT: + rval = ccs_data_parse_pdaf_readout(bin, + rules ? + &rules->pdaf_readout : NULL, + rule_payload, + rule_payload + rule_plen2, + dev); + if (rval) + return rval; + break; + default: + dev_dbg(dev, + "Don't know how to handle rule type %u!\n", + *__rule_type); + return -EINVAL; + } + } + __next_rule = __next_rule + rule_hlen + rule_plen; + } + + if (!bin->base) { + bin_reserve(bin, sizeof(*rules) * num_rules); + *__num_rules = num_rules; + } else { + if (!rules_base) + return -EIO; + + *__rules = rules_base; + } + + return 0; +} + +static int ccs_data_parse_pdaf(struct bin_container *bin, struct ccs_pdaf_pix_loc **pdaf, + const void *payload, const void *endp, + struct device *dev) +{ + const struct __ccs_data_block_pdaf_pix_loc *__pdaf = payload; + const struct __ccs_data_block_pdaf_pix_loc_block_desc_group *__bdesc_group; + const struct __ccs_data_block_pdaf_pix_loc_pixel_desc *__pixel_desc; + unsigned int i; + u16 num_block_desc_groups; + u8 max_block_type_id = 0; + const u8 *__num_pixel_descs; + + if (!is_contained(__pdaf, endp)) + return -ENODATA; + + if (bin->base) { + *pdaf = bin_alloc(bin, sizeof(**pdaf)); + if (!*pdaf) + return -ENOMEM; + } else { + bin_reserve(bin, sizeof(**pdaf)); + } + + num_block_desc_groups = + ((u16)__pdaf->num_block_desc_groups[0] << 8) + + __pdaf->num_block_desc_groups[1]; + + if (bin->base) { + (*pdaf)->main_offset_x = + ((u16)__pdaf->main_offset_x[0] << 8) + + __pdaf->main_offset_x[1]; + (*pdaf)->main_offset_y = + ((u16)__pdaf->main_offset_y[0] << 8) + + __pdaf->main_offset_y[1]; + (*pdaf)->global_pdaf_type = __pdaf->global_pdaf_type; + (*pdaf)->block_width = __pdaf->block_width; + (*pdaf)->block_height = __pdaf->block_height; + (*pdaf)->num_block_desc_groups = num_block_desc_groups; + } + + __bdesc_group = (const void *)(__pdaf + 1); + + if (bin->base) { + (*pdaf)->block_desc_groups = + bin_alloc(bin, + sizeof(struct ccs_pdaf_pix_loc_block_desc_group) * + num_block_desc_groups); + if (!(*pdaf)->block_desc_groups) + return -ENOMEM; + } else { + bin_reserve(bin, sizeof(struct ccs_pdaf_pix_loc_block_desc_group) * + num_block_desc_groups); + } + + for (i = 0; i < num_block_desc_groups; i++) { + const struct __ccs_data_block_pdaf_pix_loc_block_desc *__bdesc; + u16 num_block_descs; + unsigned int j; + + if (!is_contained(__bdesc_group, endp)) + return -ENODATA; + + num_block_descs = + ((u16)__bdesc_group->num_block_descs[0] << 8) + + __bdesc_group->num_block_descs[1]; + + if (bin->base) { + (*pdaf)->block_desc_groups[i].repeat_y = + __bdesc_group->repeat_y; + (*pdaf)->block_desc_groups[i].num_block_descs = + num_block_descs; + } + + __bdesc = (const void *)(__bdesc_group + 1); + + if (bin->base) { + (*pdaf)->block_desc_groups[i].block_descs = + bin_alloc(bin, + sizeof(struct ccs_pdaf_pix_loc_block_desc) * + num_block_descs); + if (!(*pdaf)->block_desc_groups[i].block_descs) + return -ENOMEM; + } else { + bin_reserve(bin, sizeof(struct ccs_pdaf_pix_loc_block_desc) * + num_block_descs); + } + + for (j = 0; j < num_block_descs; j++, __bdesc++) { + struct ccs_pdaf_pix_loc_block_desc *bdesc; + + if (!is_contained(__bdesc, endp)) + return -ENODATA; + + if (max_block_type_id <= __bdesc->block_type_id) + max_block_type_id = __bdesc->block_type_id + 1; + + if (!bin->base) + continue; + + bdesc = &(*pdaf)->block_desc_groups[i].block_descs[j]; + + bdesc->repeat_x = ((u16)__bdesc->repeat_x[0] << 8) + + __bdesc->repeat_x[1]; + + if (__bdesc->block_type_id >= num_block_descs) + return -EINVAL; + + bdesc->block_type_id = __bdesc->block_type_id; + } + + __bdesc_group = (const void *)__bdesc; + } + + __num_pixel_descs = (const void *)__bdesc_group; + + if (bin->base) { + (*pdaf)->pixel_desc_groups = + bin_alloc(bin, + sizeof(struct ccs_pdaf_pix_loc_pixel_desc_group) * + max_block_type_id); + if (!(*pdaf)->pixel_desc_groups) + return -ENOMEM; + (*pdaf)->num_pixel_desc_grups = max_block_type_id; + } else { + bin_reserve(bin, sizeof(struct ccs_pdaf_pix_loc_pixel_desc_group) * + max_block_type_id); + } + + for (i = 0; i < max_block_type_id; i++) { + struct ccs_pdaf_pix_loc_pixel_desc_group *pdgroup = NULL; + unsigned int j; + + if (!is_contained(__num_pixel_descs, endp)) + return -ENODATA; + + if (bin->base) { + pdgroup = &(*pdaf)->pixel_desc_groups[i]; + pdgroup->descs = + bin_alloc(bin, + sizeof(struct ccs_pdaf_pix_loc_pixel_desc) * + *__num_pixel_descs); + if (!pdgroup->descs) + return -ENOMEM; + pdgroup->num_descs = *__num_pixel_descs; + } else { + bin_reserve(bin, sizeof(struct ccs_pdaf_pix_loc_pixel_desc) * + *__num_pixel_descs); + } + + __pixel_desc = (const void *)(__num_pixel_descs + 1); + + for (j = 0; j < *__num_pixel_descs; j++, __pixel_desc++) { + struct ccs_pdaf_pix_loc_pixel_desc *pdesc; + + if (!is_contained(__pixel_desc, endp)) + return -ENODATA; + + if (!bin->base) + continue; + + if (!pdgroup) + return -EIO; + + pdesc = &pdgroup->descs[j]; + pdesc->pixel_type = __pixel_desc->pixel_type; + pdesc->small_offset_x = __pixel_desc->small_offset_x; + pdesc->small_offset_y = __pixel_desc->small_offset_y; + } + + __num_pixel_descs = (const void *)(__pixel_desc + 1); + } + + return 0; +} + +static int ccs_data_parse_license(struct bin_container *bin, + char **__license, + size_t *__license_length, + const void *payload, const void *endp) +{ + size_t size = endp - payload; + char *license; + + if (!bin->base) { + bin_reserve(bin, size); + return 0; + } + + license = bin_alloc(bin, size); + if (!license) + return -ENOMEM; + + memcpy(license, payload, size); + + *__license = license; + *__license_length = size; + + return 0; +} + +static int ccs_data_parse_end(bool *end, const void *payload, const void *endp, + struct device *dev) +{ + const struct __ccs_data_block_end *__end = payload; + + if (__end + 1 != endp) { + dev_dbg(dev, "Invalid end block length %u\n", + (unsigned int)(endp - payload)); + return -ENODATA; + } + + *end = true; + + return 0; +} + +static int __ccs_data_parse(struct bin_container *bin, + struct ccs_data_container *ccsdata, + const void *data, size_t len, struct device *dev, + bool verbose) +{ + const struct __ccs_data_block *block = data; + const struct __ccs_data_block *endp = data + len; + unsigned int version; + bool is_first = true; + int rval; + + version = ccs_data_parse_format_version(block); + if (version != CCS_STATIC_DATA_VERSION) { + dev_dbg(dev, "Don't know how to handle version %u\n", version); + return -EINVAL; + } + + if (verbose) + dev_dbg(dev, "Parsing CCS static data version %u\n", version); + + if (!bin->base) + *ccsdata = (struct ccs_data_container){ 0 }; + + while (block < endp) { + const struct __ccs_data_block *next_block; + unsigned int block_id; + const void *payload; + + rval = ccs_data_block_parse_header(block, is_first, &block_id, + &payload, &next_block, endp, + dev, + bin->base ? false : verbose); + + if (rval < 0) + return rval; + + switch (block_id) { + case CCS_DATA_BLOCK_ID_DUMMY: + break; + case CCS_DATA_BLOCK_ID_DATA_VERSION: + rval = ccs_data_parse_version(bin, ccsdata, payload, + next_block); + if (rval < 0) + return rval; + break; + case CCS_DATA_BLOCK_ID_SENSOR_READ_ONLY_REGS: + rval = ccs_data_parse_regs( + bin, &ccsdata->sensor_read_only_regs, + &ccsdata->num_sensor_read_only_regs, payload, + next_block, dev); + if (rval < 0) + return rval; + break; + case CCS_DATA_BLOCK_ID_SENSOR_MANUFACTURER_REGS: + rval = ccs_data_parse_regs( + bin, &ccsdata->sensor_manufacturer_regs, + &ccsdata->num_sensor_manufacturer_regs, payload, + next_block, dev); + if (rval < 0) + return rval; + break; + case CCS_DATA_BLOCK_ID_MODULE_READ_ONLY_REGS: + rval = ccs_data_parse_regs( + bin, &ccsdata->module_read_only_regs, + &ccsdata->num_module_read_only_regs, payload, + next_block, dev); + if (rval < 0) + return rval; + break; + case CCS_DATA_BLOCK_ID_MODULE_MANUFACTURER_REGS: + rval = ccs_data_parse_regs( + bin, &ccsdata->module_manufacturer_regs, + &ccsdata->num_module_manufacturer_regs, payload, + next_block, dev); + if (rval < 0) + return rval; + break; + case CCS_DATA_BLOCK_ID_SENSOR_PDAF_PIXEL_LOCATION: + rval = ccs_data_parse_pdaf(bin, &ccsdata->sensor_pdaf, + payload, next_block, dev); + if (rval < 0) + return rval; + break; + case CCS_DATA_BLOCK_ID_MODULE_PDAF_PIXEL_LOCATION: + rval = ccs_data_parse_pdaf(bin, &ccsdata->module_pdaf, + payload, next_block, dev); + if (rval < 0) + return rval; + break; + case CCS_DATA_BLOCK_ID_SENSOR_RULE_BASED_BLOCK: + rval = ccs_data_parse_rules( + bin, &ccsdata->sensor_rules, + &ccsdata->num_sensor_rules, payload, next_block, + dev); + if (rval < 0) + return rval; + break; + case CCS_DATA_BLOCK_ID_MODULE_RULE_BASED_BLOCK: + rval = ccs_data_parse_rules( + bin, &ccsdata->module_rules, + &ccsdata->num_module_rules, payload, next_block, + dev); + if (rval < 0) + return rval; + break; + case CCS_DATA_BLOCK_ID_LICENSE: + rval = ccs_data_parse_license(bin, &ccsdata->license, + &ccsdata->license_length, + payload, next_block); + if (rval < 0) + return rval; + break; + case CCS_DATA_BLOCK_ID_END: + rval = ccs_data_parse_end(&ccsdata->end, payload, + next_block, dev); + if (rval < 0) + return rval; + break; + default: + dev_dbg(dev, "WARNING: not handling block ID 0x%2.2x\n", + block_id); + } + + block = next_block; + is_first = false; + } + + return 0; +} + +/** + * ccs_data_parse - Parse a CCS static data file into a usable in-memory + * data structure + * @ccsdata: CCS static data in-memory data structure + * @data: CCS static data binary + * @len: Length of @data + * @dev: Device the data is related to (used for printing debug messages) + * @verbose: Whether to be verbose or not + */ +int ccs_data_parse(struct ccs_data_container *ccsdata, const void *data, + size_t len, struct device *dev, bool verbose) +{ + struct bin_container bin = { 0 }; + int rval; + + rval = __ccs_data_parse(&bin, ccsdata, data, len, dev, verbose); + if (rval) + goto out_cleanup; + + rval = bin_backing_alloc(&bin); + if (rval) + goto out_cleanup; + + rval = __ccs_data_parse(&bin, ccsdata, data, len, dev, false); + if (rval) + goto out_cleanup; + + if (verbose && ccsdata->version) + print_ccs_data_version(dev, ccsdata->version); + + if (bin.now != bin.end) { + rval = -EPROTO; + dev_dbg(dev, "parsing mismatch; base %p; now %p; end %p\n", + bin.base, bin.now, bin.end); + goto out_cleanup; + } + + ccsdata->backing = bin.base; + + return 0; + +out_cleanup: + kvfree(bin.base); + memset(ccsdata, 0, sizeof(*ccsdata)); + dev_warn(dev, "failed to parse CCS static data: %d\n", rval); + + return rval; +} diff --git a/drivers/media/i2c/ccs/ccs-data.h b/drivers/media/i2c/ccs/ccs-data.h new file mode 100644 index 000000000..638df6980 --- /dev/null +++ b/drivers/media/i2c/ccs/ccs-data.h @@ -0,0 +1,230 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * CCS static data in-memory data structure definitions + * + * Copyright 2019--2020 Intel Corporation + */ + +#ifndef __CCS_DATA_H__ +#define __CCS_DATA_H__ + +#include + +struct device; + +/** + * struct ccs_data_block_version - CCS static data version + * @version_major: Major version number + * @version_minor: Minor version number + * @date_year: Year + * @date_month: Month + * @date_day: Day + */ +struct ccs_data_block_version { + u16 version_major; + u16 version_minor; + u16 date_year; + u8 date_month; + u8 date_day; +}; + +/** + * struct ccs_reg - CCS register value + * @addr: The 16-bit address of the register + * @len: Length of the data + * @value: Data + */ +struct ccs_reg { + u16 addr; + u16 len; + u8 *value; +}; + +/** + * struct ccs_if_rule - CCS static data if rule + * @addr: Register address + * @value: Register value + * @mask: Value applied to both actual register value and @value + */ +struct ccs_if_rule { + u16 addr; + u8 value; + u8 mask; +}; + +/** + * struct ccs_frame_format_desc - CCS frame format descriptor + * @pixelcode: The pixelcode; CCS_DATA_BLOCK_FFD_PIXELCODE_* + * @value: Value related to the pixelcode + */ +struct ccs_frame_format_desc { + u8 pixelcode; + u16 value; +}; + +/** + * struct ccs_frame_format_descs - A series of CCS frame format descriptors + * @num_column_descs: Number of column descriptors + * @num_row_descs: Number of row descriptors + * @column_descs: Column descriptors + * @row_descs: Row descriptors + */ +struct ccs_frame_format_descs { + u8 num_column_descs; + u8 num_row_descs; + struct ccs_frame_format_desc *column_descs; + struct ccs_frame_format_desc *row_descs; +}; + +/** + * struct ccs_pdaf_readout - CCS PDAF data readout descriptor + * @pdaf_readout_info_order: PDAF readout order + * @ffd: Frame format of PDAF data + */ +struct ccs_pdaf_readout { + u8 pdaf_readout_info_order; + struct ccs_frame_format_descs *ffd; +}; + +/** + * struct ccs_rule - A CCS static data rule + * @num_if_rules: Number of if rules + * @if_rules: If rules + * @num_read_only_regs: Number of read-only registers + * @read_only_regs: Read-only registers + * @num_manufacturer_regs: Number of manufacturer-specific registers + * @manufacturer_regs: Manufacturer-specific registers + * @frame_format: Frame format + * @pdaf_readout: PDAF readout + */ +struct ccs_rule { + size_t num_if_rules; + struct ccs_if_rule *if_rules; + size_t num_read_only_regs; + struct ccs_reg *read_only_regs; + size_t num_manufacturer_regs; + struct ccs_reg *manufacturer_regs; + struct ccs_frame_format_descs *frame_format; + struct ccs_pdaf_readout *pdaf_readout; +}; + +/** + * struct ccs_pdaf_pix_loc_block_desc - PDAF pixel location block descriptor + * @block_type_id: Block type identifier, from 0 to n + * @repeat_x: Number of times this block is repeated to right + */ +struct ccs_pdaf_pix_loc_block_desc { + u8 block_type_id; + u16 repeat_x; +}; + +/** + * struct ccs_pdaf_pix_loc_block_desc_group - PDAF pixel location block + * descriptor group + * @repeat_y: Number of times the group is repeated down + * @num_block_descs: Number of block descriptors in @block_descs + * @block_descs: Block descriptors + */ +struct ccs_pdaf_pix_loc_block_desc_group { + u8 repeat_y; + u16 num_block_descs; + struct ccs_pdaf_pix_loc_block_desc *block_descs; +}; + +/** + * struct ccs_pdaf_pix_loc_pixel_desc - PDAF pixel location block descriptor + * @pixel_type: Type of the pixel; CCS_DATA_PDAF_PIXEL_TYPE_* + * @small_offset_x: offset X coordinate + * @small_offset_y: offset Y coordinate + */ +struct ccs_pdaf_pix_loc_pixel_desc { + u8 pixel_type; + u8 small_offset_x; + u8 small_offset_y; +}; + +/** + * struct ccs_pdaf_pix_loc_pixel_desc_group - PDAF pixel location pixel + * descriptor group + * @num_descs: Number of descriptors in @descs + * @descs: PDAF pixel location pixel descriptors + */ +struct ccs_pdaf_pix_loc_pixel_desc_group { + u8 num_descs; + struct ccs_pdaf_pix_loc_pixel_desc *descs; +}; + +/** + * struct ccs_pdaf_pix_loc - PDAF pixel locations + * @main_offset_x: Start X coordinate of PDAF pixel blocks + * @main_offset_y: Start Y coordinate of PDAF pixel blocks + * @global_pdaf_type: PDAF pattern type + * @block_width: Width of a block in pixels + * @block_height: Heigth of a block in pixels + * @num_block_desc_groups: Number of block descriptor groups + * @block_desc_groups: Block descriptor groups + * @num_pixel_desc_grups: Number of pixel descriptor groups + * @pixel_desc_groups: Pixel descriptor groups + */ +struct ccs_pdaf_pix_loc { + u16 main_offset_x; + u16 main_offset_y; + u8 global_pdaf_type; + u8 block_width; + u8 block_height; + u16 num_block_desc_groups; + struct ccs_pdaf_pix_loc_block_desc_group *block_desc_groups; + u8 num_pixel_desc_grups; + struct ccs_pdaf_pix_loc_pixel_desc_group *pixel_desc_groups; +}; + +/** + * struct ccs_data_container - In-memory CCS static data + * @version: CCS static data version + * @num_sensor_read_only_regs: Number of the read-only registers for the sensor + * @sensor_read_only_regs: Read-only registers for the sensor + * @num_sensor_manufacturer_regs: Number of the manufacturer-specific registers + * for the sensor + * @sensor_manufacturer_regs: Manufacturer-specific registers for the sensor + * @num_sensor_rules: Number of rules for the sensor + * @sensor_rules: Rules for the sensor + * @num_module_read_only_regs: Number of the read-only registers for the module + * @module_read_only_regs: Read-only registers for the module + * @num_module_manufacturer_regs: Number of the manufacturer-specific registers + * for the module + * @module_manufacturer_regs: Manufacturer-specific registers for the module + * @num_module_rules: Number of rules for the module + * @module_rules: Rules for the module + * @sensor_pdaf: PDAF data for the sensor + * @module_pdaf: PDAF data for the module + * @license_length: Lenght of the license data + * @license: License data + * @end: Whether or not there's an end block + * @backing: Raw data, pointed to from elsewhere so keep it around + */ +struct ccs_data_container { + struct ccs_data_block_version *version; + size_t num_sensor_read_only_regs; + struct ccs_reg *sensor_read_only_regs; + size_t num_sensor_manufacturer_regs; + struct ccs_reg *sensor_manufacturer_regs; + size_t num_sensor_rules; + struct ccs_rule *sensor_rules; + size_t num_module_read_only_regs; + struct ccs_reg *module_read_only_regs; + size_t num_module_manufacturer_regs; + struct ccs_reg *module_manufacturer_regs; + size_t num_module_rules; + struct ccs_rule *module_rules; + struct ccs_pdaf_pix_loc *sensor_pdaf; + struct ccs_pdaf_pix_loc *module_pdaf; + size_t license_length; + char *license; + bool end; + void *backing; +}; + +int ccs_data_parse(struct ccs_data_container *ccsdata, const void *data, + size_t len, struct device *dev, bool verbose); + +#endif /* __CCS_DATA_H__ */ diff --git a/drivers/media/i2c/ccs/ccs-limits.c b/drivers/media/i2c/ccs/ccs-limits.c new file mode 100644 index 000000000..4969fa425 --- /dev/null +++ b/drivers/media/i2c/ccs/ccs-limits.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause +/* Copyright (C) 2019--2020 Intel Corporation */ +/* + * Generated by Documentation/driver-api/media/drivers/ccs/mk-ccs-regs; + * do not modify. + */ + +#include "ccs-limits.h" +#include "ccs-regs.h" + +const struct ccs_limit ccs_limits[] = { + { CCS_R_FRAME_FORMAT_MODEL_TYPE, 1, 0, "frame_format_model_type" }, + { CCS_R_FRAME_FORMAT_MODEL_SUBTYPE, 1, 0, "frame_format_model_subtype" }, + { CCS_R_FRAME_FORMAT_DESCRIPTOR(0), 30, 0, "frame_format_descriptor" }, + { CCS_R_FRAME_FORMAT_DESCRIPTOR_4(0), 32, 0, "frame_format_descriptor_4" }, + { CCS_R_ANALOG_GAIN_CAPABILITY, 2, 0, "analog_gain_capability" }, + { CCS_R_ANALOG_GAIN_CODE_MIN, 2, 0, "analog_gain_code_min" }, + { CCS_R_ANALOG_GAIN_CODE_MAX, 2, 0, "analog_gain_code_max" }, + { CCS_R_ANALOG_GAIN_CODE_STEP, 2, 0, "analog_gain_code_step" }, + { CCS_R_ANALOG_GAIN_TYPE, 2, 0, "analog_gain_type" }, + { CCS_R_ANALOG_GAIN_M0, 2, 0, "analog_gain_m0" }, + { CCS_R_ANALOG_GAIN_C0, 2, 0, "analog_gain_c0" }, + { CCS_R_ANALOG_GAIN_M1, 2, 0, "analog_gain_m1" }, + { CCS_R_ANALOG_GAIN_C1, 2, 0, "analog_gain_c1" }, + { CCS_R_ANALOG_LINEAR_GAIN_MIN, 2, 0, "analog_linear_gain_min" }, + { CCS_R_ANALOG_LINEAR_GAIN_MAX, 2, 0, "analog_linear_gain_max" }, + { CCS_R_ANALOG_LINEAR_GAIN_STEP_SIZE, 2, 0, "analog_linear_gain_step_size" }, + { CCS_R_ANALOG_EXPONENTIAL_GAIN_MIN, 2, 0, "analog_exponential_gain_min" }, + { CCS_R_ANALOG_EXPONENTIAL_GAIN_MAX, 2, 0, "analog_exponential_gain_max" }, + { CCS_R_ANALOG_EXPONENTIAL_GAIN_STEP_SIZE, 2, 0, "analog_exponential_gain_step_size" }, + { CCS_R_DATA_FORMAT_MODEL_TYPE, 1, 0, "data_format_model_type" }, + { CCS_R_DATA_FORMAT_MODEL_SUBTYPE, 1, 0, "data_format_model_subtype" }, + { CCS_R_DATA_FORMAT_DESCRIPTOR(0), 32, 0, "data_format_descriptor" }, + { CCS_R_INTEGRATION_TIME_CAPABILITY, 2, 0, "integration_time_capability" }, + { CCS_R_COARSE_INTEGRATION_TIME_MIN, 2, 0, "coarse_integration_time_min" }, + { CCS_R_COARSE_INTEGRATION_TIME_MAX_MARGIN, 2, 0, "coarse_integration_time_max_margin" }, + { CCS_R_FINE_INTEGRATION_TIME_MIN, 2, 0, "fine_integration_time_min" }, + { CCS_R_FINE_INTEGRATION_TIME_MAX_MARGIN, 2, 0, "fine_integration_time_max_margin" }, + { CCS_R_DIGITAL_GAIN_CAPABILITY, 1, 0, "digital_gain_capability" }, + { CCS_R_DIGITAL_GAIN_MIN, 2, 0, "digital_gain_min" }, + { CCS_R_DIGITAL_GAIN_MAX, 2, 0, "digital_gain_max" }, + { CCS_R_DIGITAL_GAIN_STEP_SIZE, 2, 0, "digital_gain_step_size" }, + { CCS_R_PEDESTAL_CAPABILITY, 1, 0, "Pedestal_capability" }, + { CCS_R_ADC_CAPABILITY, 1, 0, "ADC_capability" }, + { CCS_R_ADC_BIT_DEPTH_CAPABILITY, 4, 0, "ADC_bit_depth_capability" }, + { CCS_R_MIN_EXT_CLK_FREQ_MHZ, 4, 0, "min_ext_clk_freq_mhz" }, + { CCS_R_MAX_EXT_CLK_FREQ_MHZ, 4, 0, "max_ext_clk_freq_mhz" }, + { CCS_R_MIN_PRE_PLL_CLK_DIV, 2, 0, "min_pre_pll_clk_div" }, + { CCS_R_MAX_PRE_PLL_CLK_DIV, 2, 0, "max_pre_pll_clk_div" }, + { CCS_R_MIN_PLL_IP_CLK_FREQ_MHZ, 4, 0, "min_pll_ip_clk_freq_mhz" }, + { CCS_R_MAX_PLL_IP_CLK_FREQ_MHZ, 4, 0, "max_pll_ip_clk_freq_mhz" }, + { CCS_R_MIN_PLL_MULTIPLIER, 2, 0, "min_pll_multiplier" }, + { CCS_R_MAX_PLL_MULTIPLIER, 2, 0, "max_pll_multiplier" }, + { CCS_R_MIN_PLL_OP_CLK_FREQ_MHZ, 4, 0, "min_pll_op_clk_freq_mhz" }, + { CCS_R_MAX_PLL_OP_CLK_FREQ_MHZ, 4, 0, "max_pll_op_clk_freq_mhz" }, + { CCS_R_MIN_VT_SYS_CLK_DIV, 2, 0, "min_vt_sys_clk_div" }, + { CCS_R_MAX_VT_SYS_CLK_DIV, 2, 0, "max_vt_sys_clk_div" }, + { CCS_R_MIN_VT_SYS_CLK_FREQ_MHZ, 4, 0, "min_vt_sys_clk_freq_mhz" }, + { CCS_R_MAX_VT_SYS_CLK_FREQ_MHZ, 4, 0, "max_vt_sys_clk_freq_mhz" }, + { CCS_R_MIN_VT_PIX_CLK_FREQ_MHZ, 4, 0, "min_vt_pix_clk_freq_mhz" }, + { CCS_R_MAX_VT_PIX_CLK_FREQ_MHZ, 4, 0, "max_vt_pix_clk_freq_mhz" }, + { CCS_R_MIN_VT_PIX_CLK_DIV, 2, 0, "min_vt_pix_clk_div" }, + { CCS_R_MAX_VT_PIX_CLK_DIV, 2, 0, "max_vt_pix_clk_div" }, + { CCS_R_CLOCK_CALCULATION, 1, 0, "clock_calculation" }, + { CCS_R_NUM_OF_VT_LANES, 1, 0, "num_of_vt_lanes" }, + { CCS_R_NUM_OF_OP_LANES, 1, 0, "num_of_op_lanes" }, + { CCS_R_OP_BITS_PER_LANE, 1, 0, "op_bits_per_lane" }, + { CCS_R_MIN_FRAME_LENGTH_LINES, 2, 0, "min_frame_length_lines" }, + { CCS_R_MAX_FRAME_LENGTH_LINES, 2, 0, "max_frame_length_lines" }, + { CCS_R_MIN_LINE_LENGTH_PCK, 2, 0, "min_line_length_pck" }, + { CCS_R_MAX_LINE_LENGTH_PCK, 2, 0, "max_line_length_pck" }, + { CCS_R_MIN_LINE_BLANKING_PCK, 2, 0, "min_line_blanking_pck" }, + { CCS_R_MIN_FRAME_BLANKING_LINES, 2, 0, "min_frame_blanking_lines" }, + { CCS_R_MIN_LINE_LENGTH_PCK_STEP_SIZE, 1, 0, "min_line_length_pck_step_size" }, + { CCS_R_TIMING_MODE_CAPABILITY, 1, 0, "timing_mode_capability" }, + { CCS_R_FRAME_MARGIN_MAX_VALUE, 2, 0, "frame_margin_max_value" }, + { CCS_R_FRAME_MARGIN_MIN_VALUE, 1, 0, "frame_margin_min_value" }, + { CCS_R_GAIN_DELAY_TYPE, 1, 0, "gain_delay_type" }, + { CCS_R_MIN_OP_SYS_CLK_DIV, 2, 0, "min_op_sys_clk_div" }, + { CCS_R_MAX_OP_SYS_CLK_DIV, 2, 0, "max_op_sys_clk_div" }, + { CCS_R_MIN_OP_SYS_CLK_FREQ_MHZ, 4, 0, "min_op_sys_clk_freq_mhz" }, + { CCS_R_MAX_OP_SYS_CLK_FREQ_MHZ, 4, 0, "max_op_sys_clk_freq_mhz" }, + { CCS_R_MIN_OP_PIX_CLK_DIV, 2, 0, "min_op_pix_clk_div" }, + { CCS_R_MAX_OP_PIX_CLK_DIV, 2, 0, "max_op_pix_clk_div" }, + { CCS_R_MIN_OP_PIX_CLK_FREQ_MHZ, 4, 0, "min_op_pix_clk_freq_mhz" }, + { CCS_R_MAX_OP_PIX_CLK_FREQ_MHZ, 4, 0, "max_op_pix_clk_freq_mhz" }, + { CCS_R_X_ADDR_MIN, 2, 0, "x_addr_min" }, + { CCS_R_Y_ADDR_MIN, 2, 0, "y_addr_min" }, + { CCS_R_X_ADDR_MAX, 2, 0, "x_addr_max" }, + { CCS_R_Y_ADDR_MAX, 2, 0, "y_addr_max" }, + { CCS_R_MIN_X_OUTPUT_SIZE, 2, 0, "min_x_output_size" }, + { CCS_R_MIN_Y_OUTPUT_SIZE, 2, 0, "min_y_output_size" }, + { CCS_R_MAX_X_OUTPUT_SIZE, 2, 0, "max_x_output_size" }, + { CCS_R_MAX_Y_OUTPUT_SIZE, 2, 0, "max_y_output_size" }, + { CCS_R_X_ADDR_START_DIV_CONSTANT, 1, 0, "x_addr_start_div_constant" }, + { CCS_R_Y_ADDR_START_DIV_CONSTANT, 1, 0, "y_addr_start_div_constant" }, + { CCS_R_X_ADDR_END_DIV_CONSTANT, 1, 0, "x_addr_end_div_constant" }, + { CCS_R_Y_ADDR_END_DIV_CONSTANT, 1, 0, "y_addr_end_div_constant" }, + { CCS_R_X_SIZE_DIV, 1, 0, "x_size_div" }, + { CCS_R_Y_SIZE_DIV, 1, 0, "y_size_div" }, + { CCS_R_X_OUTPUT_DIV, 1, 0, "x_output_div" }, + { CCS_R_Y_OUTPUT_DIV, 1, 0, "y_output_div" }, + { CCS_R_NON_FLEXIBLE_RESOLUTION_SUPPORT, 1, 0, "non_flexible_resolution_support" }, + { CCS_R_MIN_OP_PRE_PLL_CLK_DIV, 2, 0, "min_op_pre_pll_clk_div" }, + { CCS_R_MAX_OP_PRE_PLL_CLK_DIV, 2, 0, "max_op_pre_pll_clk_div" }, + { CCS_R_MIN_OP_PLL_IP_CLK_FREQ_MHZ, 4, 0, "min_op_pll_ip_clk_freq_mhz" }, + { CCS_R_MAX_OP_PLL_IP_CLK_FREQ_MHZ, 4, 0, "max_op_pll_ip_clk_freq_mhz" }, + { CCS_R_MIN_OP_PLL_MULTIPLIER, 2, 0, "min_op_pll_multiplier" }, + { CCS_R_MAX_OP_PLL_MULTIPLIER, 2, 0, "max_op_pll_multiplier" }, + { CCS_R_MIN_OP_PLL_OP_CLK_FREQ_MHZ, 4, 0, "min_op_pll_op_clk_freq_mhz" }, + { CCS_R_MAX_OP_PLL_OP_CLK_FREQ_MHZ, 4, 0, "max_op_pll_op_clk_freq_mhz" }, + { CCS_R_CLOCK_TREE_PLL_CAPABILITY, 1, 0, "clock_tree_pll_capability" }, + { CCS_R_CLOCK_CAPA_TYPE_CAPABILITY, 1, 0, "clock_capa_type_capability" }, + { CCS_R_MIN_EVEN_INC, 2, 0, "min_even_inc" }, + { CCS_R_MIN_ODD_INC, 2, 0, "min_odd_inc" }, + { CCS_R_MAX_EVEN_INC, 2, 0, "max_even_inc" }, + { CCS_R_MAX_ODD_INC, 2, 0, "max_odd_inc" }, + { CCS_R_AUX_SUBSAMP_CAPABILITY, 1, 0, "aux_subsamp_capability" }, + { CCS_R_AUX_SUBSAMP_MONO_CAPABILITY, 1, 0, "aux_subsamp_mono_capability" }, + { CCS_R_MONOCHROME_CAPABILITY, 1, 0, "monochrome_capability" }, + { CCS_R_PIXEL_READOUT_CAPABILITY, 1, 0, "pixel_readout_capability" }, + { CCS_R_MIN_EVEN_INC_MONO, 2, 0, "min_even_inc_mono" }, + { CCS_R_MAX_EVEN_INC_MONO, 2, 0, "max_even_inc_mono" }, + { CCS_R_MIN_ODD_INC_MONO, 2, 0, "min_odd_inc_mono" }, + { CCS_R_MAX_ODD_INC_MONO, 2, 0, "max_odd_inc_mono" }, + { CCS_R_MIN_EVEN_INC_BC2, 2, 0, "min_even_inc_bc2" }, + { CCS_R_MAX_EVEN_INC_BC2, 2, 0, "max_even_inc_bc2" }, + { CCS_R_MIN_ODD_INC_BC2, 2, 0, "min_odd_inc_bc2" }, + { CCS_R_MAX_ODD_INC_BC2, 2, 0, "max_odd_inc_bc2" }, + { CCS_R_MIN_EVEN_INC_MONO_BC2, 2, 0, "min_even_inc_mono_bc2" }, + { CCS_R_MAX_EVEN_INC_MONO_BC2, 2, 0, "max_even_inc_mono_bc2" }, + { CCS_R_MIN_ODD_INC_MONO_BC2, 2, 0, "min_odd_inc_mono_bc2" }, + { CCS_R_MAX_ODD_INC_MONO_BC2, 2, 0, "max_odd_inc_mono_bc2" }, + { CCS_R_SCALING_CAPABILITY, 2, 0, "scaling_capability" }, + { CCS_R_SCALER_M_MIN, 2, 0, "scaler_m_min" }, + { CCS_R_SCALER_M_MAX, 2, 0, "scaler_m_max" }, + { CCS_R_SCALER_N_MIN, 2, 0, "scaler_n_min" }, + { CCS_R_SCALER_N_MAX, 2, 0, "scaler_n_max" }, + { CCS_R_DIGITAL_CROP_CAPABILITY, 1, 0, "digital_crop_capability" }, + { CCS_R_HDR_CAPABILITY_1, 1, 0, "hdr_capability_1" }, + { CCS_R_MIN_HDR_BIT_DEPTH, 1, 0, "min_hdr_bit_depth" }, + { CCS_R_HDR_RESOLUTION_SUB_TYPES, 1, 0, "hdr_resolution_sub_types" }, + { CCS_R_HDR_RESOLUTION_SUB_TYPE(0), 2, 0, "hdr_resolution_sub_type" }, + { CCS_R_HDR_CAPABILITY_2, 1, 0, "hdr_capability_2" }, + { CCS_R_MAX_HDR_BIT_DEPTH, 1, 0, "max_hdr_bit_depth" }, + { CCS_R_USL_SUPPORT_CAPABILITY, 1, 0, "usl_support_capability" }, + { CCS_R_USL_CLOCK_MODE_D_CAPABILITY, 1, 0, "usl_clock_mode_d_capability" }, + { CCS_R_MIN_OP_SYS_CLK_DIV_REV, 1, 0, "min_op_sys_clk_div_rev" }, + { CCS_R_MAX_OP_SYS_CLK_DIV_REV, 1, 0, "max_op_sys_clk_div_rev" }, + { CCS_R_MIN_OP_PIX_CLK_DIV_REV, 1, 0, "min_op_pix_clk_div_rev" }, + { CCS_R_MAX_OP_PIX_CLK_DIV_REV, 1, 0, "max_op_pix_clk_div_rev" }, + { CCS_R_MIN_OP_SYS_CLK_FREQ_REV_MHZ, 4, 0, "min_op_sys_clk_freq_rev_mhz" }, + { CCS_R_MAX_OP_SYS_CLK_FREQ_REV_MHZ, 4, 0, "max_op_sys_clk_freq_rev_mhz" }, + { CCS_R_MIN_OP_PIX_CLK_FREQ_REV_MHZ, 4, 0, "min_op_pix_clk_freq_rev_mhz" }, + { CCS_R_MAX_OP_PIX_CLK_FREQ_REV_MHZ, 4, 0, "max_op_pix_clk_freq_rev_mhz" }, + { CCS_R_MAX_BITRATE_REV_D_MODE_MBPS, 4, 0, "max_bitrate_rev_d_mode_mbps" }, + { CCS_R_MAX_SYMRATE_REV_C_MODE_MSPS, 4, 0, "max_symrate_rev_c_mode_msps" }, + { CCS_R_COMPRESSION_CAPABILITY, 1, 0, "compression_capability" }, + { CCS_R_TEST_MODE_CAPABILITY, 2, 0, "test_mode_capability" }, + { CCS_R_PN9_DATA_FORMAT1, 1, 0, "pn9_data_format1" }, + { CCS_R_PN9_DATA_FORMAT2, 1, 0, "pn9_data_format2" }, + { CCS_R_PN9_DATA_FORMAT3, 1, 0, "pn9_data_format3" }, + { CCS_R_PN9_DATA_FORMAT4, 1, 0, "pn9_data_format4" }, + { CCS_R_PN9_MISC_CAPABILITY, 1, 0, "pn9_misc_capability" }, + { CCS_R_TEST_PATTERN_CAPABILITY, 1, 0, "test_pattern_capability" }, + { CCS_R_PATTERN_SIZE_DIV_M1, 1, 0, "pattern_size_div_m1" }, + { CCS_R_FIFO_SUPPORT_CAPABILITY, 1, 0, "fifo_support_capability" }, + { CCS_R_PHY_CTRL_CAPABILITY, 1, 0, "phy_ctrl_capability" }, + { CCS_R_CSI_DPHY_LANE_MODE_CAPABILITY, 1, 0, "csi_dphy_lane_mode_capability" }, + { CCS_R_CSI_SIGNALING_MODE_CAPABILITY, 1, 0, "csi_signaling_mode_capability" }, + { CCS_R_FAST_STANDBY_CAPABILITY, 1, 0, "fast_standby_capability" }, + { CCS_R_CSI_ADDRESS_CONTROL_CAPABILITY, 1, 0, "csi_address_control_capability" }, + { CCS_R_DATA_TYPE_CAPABILITY, 1, 0, "data_type_capability" }, + { CCS_R_CSI_CPHY_LANE_MODE_CAPABILITY, 1, 0, "csi_cphy_lane_mode_capability" }, + { CCS_R_EMB_DATA_CAPABILITY, 1, 0, "emb_data_capability" }, + { CCS_R_MAX_PER_LANE_BITRATE_LANE_D_MODE_MBPS(0), 16, 0, "max_per_lane_bitrate_lane_d_mode_mbps 0" }, + { CCS_R_MAX_PER_LANE_BITRATE_LANE_D_MODE_MBPS(4), 16, CCS_L_FL_SAME_REG, "max_per_lane_bitrate_lane_d_mode_mbps 4" }, + { CCS_R_TEMP_SENSOR_CAPABILITY, 1, 0, "temp_sensor_capability" }, + { CCS_R_MAX_PER_LANE_BITRATE_LANE_C_MODE_MBPS(0), 16, 0, "max_per_lane_bitrate_lane_c_mode_mbps 0" }, + { CCS_R_MAX_PER_LANE_BITRATE_LANE_C_MODE_MBPS(4), 16, CCS_L_FL_SAME_REG, "max_per_lane_bitrate_lane_c_mode_mbps 4" }, + { CCS_R_DPHY_EQUALIZATION_CAPABILITY, 1, 0, "dphy_equalization_capability" }, + { CCS_R_CPHY_EQUALIZATION_CAPABILITY, 1, 0, "cphy_equalization_capability" }, + { CCS_R_DPHY_PREAMBLE_CAPABILITY, 1, 0, "dphy_preamble_capability" }, + { CCS_R_DPHY_SSC_CAPABILITY, 1, 0, "dphy_ssc_capability" }, + { CCS_R_CPHY_CALIBRATION_CAPABILITY, 1, 0, "cphy_calibration_capability" }, + { CCS_R_DPHY_CALIBRATION_CAPABILITY, 1, 0, "dphy_calibration_capability" }, + { CCS_R_PHY_CTRL_CAPABILITY_2, 1, 0, "phy_ctrl_capability_2" }, + { CCS_R_LRTE_CPHY_CAPABILITY, 1, 0, "lrte_cphy_capability" }, + { CCS_R_LRTE_DPHY_CAPABILITY, 1, 0, "lrte_dphy_capability" }, + { CCS_R_ALPS_CAPABILITY_DPHY, 1, 0, "alps_capability_dphy" }, + { CCS_R_ALPS_CAPABILITY_CPHY, 1, 0, "alps_capability_cphy" }, + { CCS_R_SCRAMBLING_CAPABILITY, 1, 0, "scrambling_capability" }, + { CCS_R_DPHY_MANUAL_CONSTANT, 1, 0, "dphy_manual_constant" }, + { CCS_R_CPHY_MANUAL_CONSTANT, 1, 0, "cphy_manual_constant" }, + { CCS_R_CSI2_INTERFACE_CAPABILITY_MISC, 1, 0, "CSI2_interface_capability_misc" }, + { CCS_R_PHY_CTRL_CAPABILITY_3, 1, 0, "PHY_ctrl_capability_3" }, + { CCS_R_DPHY_SF, 1, 0, "dphy_sf" }, + { CCS_R_CPHY_SF, 1, 0, "cphy_sf" }, + { CCS_R_DPHY_LIMITS_1, 1, 0, "dphy_limits_1" }, + { CCS_R_DPHY_LIMITS_2, 1, 0, "dphy_limits_2" }, + { CCS_R_DPHY_LIMITS_3, 1, 0, "dphy_limits_3" }, + { CCS_R_DPHY_LIMITS_4, 1, 0, "dphy_limits_4" }, + { CCS_R_DPHY_LIMITS_5, 1, 0, "dphy_limits_5" }, + { CCS_R_DPHY_LIMITS_6, 1, 0, "dphy_limits_6" }, + { CCS_R_CPHY_LIMITS_1, 1, 0, "cphy_limits_1" }, + { CCS_R_CPHY_LIMITS_2, 1, 0, "cphy_limits_2" }, + { CCS_R_CPHY_LIMITS_3, 1, 0, "cphy_limits_3" }, + { CCS_R_MIN_FRAME_LENGTH_LINES_BIN, 2, 0, "min_frame_length_lines_bin" }, + { CCS_R_MAX_FRAME_LENGTH_LINES_BIN, 2, 0, "max_frame_length_lines_bin" }, + { CCS_R_MIN_LINE_LENGTH_PCK_BIN, 2, 0, "min_line_length_pck_bin" }, + { CCS_R_MAX_LINE_LENGTH_PCK_BIN, 2, 0, "max_line_length_pck_bin" }, + { CCS_R_MIN_LINE_BLANKING_PCK_BIN, 2, 0, "min_line_blanking_pck_bin" }, + { CCS_R_FINE_INTEGRATION_TIME_MIN_BIN, 2, 0, "fine_integration_time_min_bin" }, + { CCS_R_FINE_INTEGRATION_TIME_MAX_MARGIN_BIN, 2, 0, "fine_integration_time_max_margin_bin" }, + { CCS_R_BINNING_CAPABILITY, 1, 0, "binning_capability" }, + { CCS_R_BINNING_WEIGHTING_CAPABILITY, 1, 0, "binning_weighting_capability" }, + { CCS_R_BINNING_SUB_TYPES, 1, 0, "binning_sub_types" }, + { CCS_R_BINNING_SUB_TYPE(0), 64, 0, "binning_sub_type" }, + { CCS_R_BINNING_WEIGHTING_MONO_CAPABILITY, 1, 0, "binning_weighting_mono_capability" }, + { CCS_R_BINNING_SUB_TYPES_MONO, 1, 0, "binning_sub_types_mono" }, + { CCS_R_BINNING_SUB_TYPE_MONO(0), 64, 0, "binning_sub_type_mono" }, + { CCS_R_DATA_TRANSFER_IF_CAPABILITY, 1, 0, "data_transfer_if_capability" }, + { CCS_R_SHADING_CORRECTION_CAPABILITY, 1, 0, "shading_correction_capability" }, + { CCS_R_GREEN_IMBALANCE_CAPABILITY, 1, 0, "green_imbalance_capability" }, + { CCS_R_MODULE_SPECIFIC_CORRECTION_CAPABILITY, 1, 0, "module_specific_correction_capability" }, + { CCS_R_DEFECT_CORRECTION_CAPABILITY, 2, 0, "defect_correction_capability" }, + { CCS_R_DEFECT_CORRECTION_CAPABILITY_2, 2, 0, "defect_correction_capability_2" }, + { CCS_R_NF_CAPABILITY, 1, 0, "nf_capability" }, + { CCS_R_OB_READOUT_CAPABILITY, 1, 0, "ob_readout_capability" }, + { CCS_R_COLOR_FEEDBACK_CAPABILITY, 1, 0, "color_feedback_capability" }, + { CCS_R_CFA_PATTERN_CAPABILITY, 1, 0, "CFA_pattern_capability" }, + { CCS_R_CFA_PATTERN_CONVERSION_CAPABILITY, 1, 0, "CFA_pattern_conversion_capability" }, + { CCS_R_FLASH_MODE_CAPABILITY, 1, 0, "flash_mode_capability" }, + { CCS_R_SA_STROBE_MODE_CAPABILITY, 1, 0, "sa_strobe_mode_capability" }, + { CCS_R_RESET_MAX_DELAY, 1, 0, "reset_max_delay" }, + { CCS_R_RESET_MIN_TIME, 1, 0, "reset_min_time" }, + { CCS_R_PDAF_CAPABILITY_1, 1, 0, "pdaf_capability_1" }, + { CCS_R_PDAF_CAPABILITY_2, 1, 0, "pdaf_capability_2" }, + { CCS_R_BRACKETING_LUT_CAPABILITY_1, 1, 0, "bracketing_lut_capability_1" }, + { CCS_R_BRACKETING_LUT_CAPABILITY_2, 1, 0, "bracketing_lut_capability_2" }, + { CCS_R_BRACKETING_LUT_SIZE, 1, 0, "bracketing_lut_size" }, + { 0 } /* Guardian */ +}; diff --git a/drivers/media/i2c/ccs/ccs-limits.h b/drivers/media/i2c/ccs/ccs-limits.h new file mode 100644 index 000000000..551d3ee9d --- /dev/null +++ b/drivers/media/i2c/ccs/ccs-limits.h @@ -0,0 +1,263 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause */ +/* Copyright (C) 2019--2020 Intel Corporation */ +/* + * Generated by Documentation/driver-api/media/drivers/ccs/mk-ccs-regs; + * do not modify. + */ + +#ifndef __CCS_LIMITS_H__ +#define __CCS_LIMITS_H__ + +#include +#include + +struct ccs_limit { + u32 reg; + u16 size; + u16 flags; + const char *name; +}; + +#define CCS_L_FL_SAME_REG BIT(0) + +extern const struct ccs_limit ccs_limits[]; + +#define CCS_L_FRAME_FORMAT_MODEL_TYPE 0 +#define CCS_L_FRAME_FORMAT_MODEL_SUBTYPE 1 +#define CCS_L_FRAME_FORMAT_DESCRIPTOR 2 +#define CCS_L_FRAME_FORMAT_DESCRIPTOR_OFFSET(n) ((n) * 2) +#define CCS_L_FRAME_FORMAT_DESCRIPTOR_4 3 +#define CCS_L_FRAME_FORMAT_DESCRIPTOR_4_OFFSET(n) ((n) * 4) +#define CCS_L_ANALOG_GAIN_CAPABILITY 4 +#define CCS_L_ANALOG_GAIN_CODE_MIN 5 +#define CCS_L_ANALOG_GAIN_CODE_MAX 6 +#define CCS_L_ANALOG_GAIN_CODE_STEP 7 +#define CCS_L_ANALOG_GAIN_TYPE 8 +#define CCS_L_ANALOG_GAIN_M0 9 +#define CCS_L_ANALOG_GAIN_C0 10 +#define CCS_L_ANALOG_GAIN_M1 11 +#define CCS_L_ANALOG_GAIN_C1 12 +#define CCS_L_ANALOG_LINEAR_GAIN_MIN 13 +#define CCS_L_ANALOG_LINEAR_GAIN_MAX 14 +#define CCS_L_ANALOG_LINEAR_GAIN_STEP_SIZE 15 +#define CCS_L_ANALOG_EXPONENTIAL_GAIN_MIN 16 +#define CCS_L_ANALOG_EXPONENTIAL_GAIN_MAX 17 +#define CCS_L_ANALOG_EXPONENTIAL_GAIN_STEP_SIZE 18 +#define CCS_L_DATA_FORMAT_MODEL_TYPE 19 +#define CCS_L_DATA_FORMAT_MODEL_SUBTYPE 20 +#define CCS_L_DATA_FORMAT_DESCRIPTOR 21 +#define CCS_L_DATA_FORMAT_DESCRIPTOR_OFFSET(n) ((n) * 2) +#define CCS_L_INTEGRATION_TIME_CAPABILITY 22 +#define CCS_L_COARSE_INTEGRATION_TIME_MIN 23 +#define CCS_L_COARSE_INTEGRATION_TIME_MAX_MARGIN 24 +#define CCS_L_FINE_INTEGRATION_TIME_MIN 25 +#define CCS_L_FINE_INTEGRATION_TIME_MAX_MARGIN 26 +#define CCS_L_DIGITAL_GAIN_CAPABILITY 27 +#define CCS_L_DIGITAL_GAIN_MIN 28 +#define CCS_L_DIGITAL_GAIN_MAX 29 +#define CCS_L_DIGITAL_GAIN_STEP_SIZE 30 +#define CCS_L_PEDESTAL_CAPABILITY 31 +#define CCS_L_ADC_CAPABILITY 32 +#define CCS_L_ADC_BIT_DEPTH_CAPABILITY 33 +#define CCS_L_MIN_EXT_CLK_FREQ_MHZ 34 +#define CCS_L_MAX_EXT_CLK_FREQ_MHZ 35 +#define CCS_L_MIN_PRE_PLL_CLK_DIV 36 +#define CCS_L_MAX_PRE_PLL_CLK_DIV 37 +#define CCS_L_MIN_PLL_IP_CLK_FREQ_MHZ 38 +#define CCS_L_MAX_PLL_IP_CLK_FREQ_MHZ 39 +#define CCS_L_MIN_PLL_MULTIPLIER 40 +#define CCS_L_MAX_PLL_MULTIPLIER 41 +#define CCS_L_MIN_PLL_OP_CLK_FREQ_MHZ 42 +#define CCS_L_MAX_PLL_OP_CLK_FREQ_MHZ 43 +#define CCS_L_MIN_VT_SYS_CLK_DIV 44 +#define CCS_L_MAX_VT_SYS_CLK_DIV 45 +#define CCS_L_MIN_VT_SYS_CLK_FREQ_MHZ 46 +#define CCS_L_MAX_VT_SYS_CLK_FREQ_MHZ 47 +#define CCS_L_MIN_VT_PIX_CLK_FREQ_MHZ 48 +#define CCS_L_MAX_VT_PIX_CLK_FREQ_MHZ 49 +#define CCS_L_MIN_VT_PIX_CLK_DIV 50 +#define CCS_L_MAX_VT_PIX_CLK_DIV 51 +#define CCS_L_CLOCK_CALCULATION 52 +#define CCS_L_NUM_OF_VT_LANES 53 +#define CCS_L_NUM_OF_OP_LANES 54 +#define CCS_L_OP_BITS_PER_LANE 55 +#define CCS_L_MIN_FRAME_LENGTH_LINES 56 +#define CCS_L_MAX_FRAME_LENGTH_LINES 57 +#define CCS_L_MIN_LINE_LENGTH_PCK 58 +#define CCS_L_MAX_LINE_LENGTH_PCK 59 +#define CCS_L_MIN_LINE_BLANKING_PCK 60 +#define CCS_L_MIN_FRAME_BLANKING_LINES 61 +#define CCS_L_MIN_LINE_LENGTH_PCK_STEP_SIZE 62 +#define CCS_L_TIMING_MODE_CAPABILITY 63 +#define CCS_L_FRAME_MARGIN_MAX_VALUE 64 +#define CCS_L_FRAME_MARGIN_MIN_VALUE 65 +#define CCS_L_GAIN_DELAY_TYPE 66 +#define CCS_L_MIN_OP_SYS_CLK_DIV 67 +#define CCS_L_MAX_OP_SYS_CLK_DIV 68 +#define CCS_L_MIN_OP_SYS_CLK_FREQ_MHZ 69 +#define CCS_L_MAX_OP_SYS_CLK_FREQ_MHZ 70 +#define CCS_L_MIN_OP_PIX_CLK_DIV 71 +#define CCS_L_MAX_OP_PIX_CLK_DIV 72 +#define CCS_L_MIN_OP_PIX_CLK_FREQ_MHZ 73 +#define CCS_L_MAX_OP_PIX_CLK_FREQ_MHZ 74 +#define CCS_L_X_ADDR_MIN 75 +#define CCS_L_Y_ADDR_MIN 76 +#define CCS_L_X_ADDR_MAX 77 +#define CCS_L_Y_ADDR_MAX 78 +#define CCS_L_MIN_X_OUTPUT_SIZE 79 +#define CCS_L_MIN_Y_OUTPUT_SIZE 80 +#define CCS_L_MAX_X_OUTPUT_SIZE 81 +#define CCS_L_MAX_Y_OUTPUT_SIZE 82 +#define CCS_L_X_ADDR_START_DIV_CONSTANT 83 +#define CCS_L_Y_ADDR_START_DIV_CONSTANT 84 +#define CCS_L_X_ADDR_END_DIV_CONSTANT 85 +#define CCS_L_Y_ADDR_END_DIV_CONSTANT 86 +#define CCS_L_X_SIZE_DIV 87 +#define CCS_L_Y_SIZE_DIV 88 +#define CCS_L_X_OUTPUT_DIV 89 +#define CCS_L_Y_OUTPUT_DIV 90 +#define CCS_L_NON_FLEXIBLE_RESOLUTION_SUPPORT 91 +#define CCS_L_MIN_OP_PRE_PLL_CLK_DIV 92 +#define CCS_L_MAX_OP_PRE_PLL_CLK_DIV 93 +#define CCS_L_MIN_OP_PLL_IP_CLK_FREQ_MHZ 94 +#define CCS_L_MAX_OP_PLL_IP_CLK_FREQ_MHZ 95 +#define CCS_L_MIN_OP_PLL_MULTIPLIER 96 +#define CCS_L_MAX_OP_PLL_MULTIPLIER 97 +#define CCS_L_MIN_OP_PLL_OP_CLK_FREQ_MHZ 98 +#define CCS_L_MAX_OP_PLL_OP_CLK_FREQ_MHZ 99 +#define CCS_L_CLOCK_TREE_PLL_CAPABILITY 100 +#define CCS_L_CLOCK_CAPA_TYPE_CAPABILITY 101 +#define CCS_L_MIN_EVEN_INC 102 +#define CCS_L_MIN_ODD_INC 103 +#define CCS_L_MAX_EVEN_INC 104 +#define CCS_L_MAX_ODD_INC 105 +#define CCS_L_AUX_SUBSAMP_CAPABILITY 106 +#define CCS_L_AUX_SUBSAMP_MONO_CAPABILITY 107 +#define CCS_L_MONOCHROME_CAPABILITY 108 +#define CCS_L_PIXEL_READOUT_CAPABILITY 109 +#define CCS_L_MIN_EVEN_INC_MONO 110 +#define CCS_L_MAX_EVEN_INC_MONO 111 +#define CCS_L_MIN_ODD_INC_MONO 112 +#define CCS_L_MAX_ODD_INC_MONO 113 +#define CCS_L_MIN_EVEN_INC_BC2 114 +#define CCS_L_MAX_EVEN_INC_BC2 115 +#define CCS_L_MIN_ODD_INC_BC2 116 +#define CCS_L_MAX_ODD_INC_BC2 117 +#define CCS_L_MIN_EVEN_INC_MONO_BC2 118 +#define CCS_L_MAX_EVEN_INC_MONO_BC2 119 +#define CCS_L_MIN_ODD_INC_MONO_BC2 120 +#define CCS_L_MAX_ODD_INC_MONO_BC2 121 +#define CCS_L_SCALING_CAPABILITY 122 +#define CCS_L_SCALER_M_MIN 123 +#define CCS_L_SCALER_M_MAX 124 +#define CCS_L_SCALER_N_MIN 125 +#define CCS_L_SCALER_N_MAX 126 +#define CCS_L_DIGITAL_CROP_CAPABILITY 127 +#define CCS_L_HDR_CAPABILITY_1 128 +#define CCS_L_MIN_HDR_BIT_DEPTH 129 +#define CCS_L_HDR_RESOLUTION_SUB_TYPES 130 +#define CCS_L_HDR_RESOLUTION_SUB_TYPE 131 +#define CCS_L_HDR_RESOLUTION_SUB_TYPE_OFFSET(n) (n) +#define CCS_L_HDR_CAPABILITY_2 132 +#define CCS_L_MAX_HDR_BIT_DEPTH 133 +#define CCS_L_USL_SUPPORT_CAPABILITY 134 +#define CCS_L_USL_CLOCK_MODE_D_CAPABILITY 135 +#define CCS_L_MIN_OP_SYS_CLK_DIV_REV 136 +#define CCS_L_MAX_OP_SYS_CLK_DIV_REV 137 +#define CCS_L_MIN_OP_PIX_CLK_DIV_REV 138 +#define CCS_L_MAX_OP_PIX_CLK_DIV_REV 139 +#define CCS_L_MIN_OP_SYS_CLK_FREQ_REV_MHZ 140 +#define CCS_L_MAX_OP_SYS_CLK_FREQ_REV_MHZ 141 +#define CCS_L_MIN_OP_PIX_CLK_FREQ_REV_MHZ 142 +#define CCS_L_MAX_OP_PIX_CLK_FREQ_REV_MHZ 143 +#define CCS_L_MAX_BITRATE_REV_D_MODE_MBPS 144 +#define CCS_L_MAX_SYMRATE_REV_C_MODE_MSPS 145 +#define CCS_L_COMPRESSION_CAPABILITY 146 +#define CCS_L_TEST_MODE_CAPABILITY 147 +#define CCS_L_PN9_DATA_FORMAT1 148 +#define CCS_L_PN9_DATA_FORMAT2 149 +#define CCS_L_PN9_DATA_FORMAT3 150 +#define CCS_L_PN9_DATA_FORMAT4 151 +#define CCS_L_PN9_MISC_CAPABILITY 152 +#define CCS_L_TEST_PATTERN_CAPABILITY 153 +#define CCS_L_PATTERN_SIZE_DIV_M1 154 +#define CCS_L_FIFO_SUPPORT_CAPABILITY 155 +#define CCS_L_PHY_CTRL_CAPABILITY 156 +#define CCS_L_CSI_DPHY_LANE_MODE_CAPABILITY 157 +#define CCS_L_CSI_SIGNALING_MODE_CAPABILITY 158 +#define CCS_L_FAST_STANDBY_CAPABILITY 159 +#define CCS_L_CSI_ADDRESS_CONTROL_CAPABILITY 160 +#define CCS_L_DATA_TYPE_CAPABILITY 161 +#define CCS_L_CSI_CPHY_LANE_MODE_CAPABILITY 162 +#define CCS_L_EMB_DATA_CAPABILITY 163 +#define CCS_L_MAX_PER_LANE_BITRATE_LANE_D_MODE_MBPS 164 +#define CCS_L_MAX_PER_LANE_BITRATE_LANE_D_MODE_MBPS_OFFSET(n) ((n) * 4) +#define CCS_L_TEMP_SENSOR_CAPABILITY 165 +#define CCS_L_MAX_PER_LANE_BITRATE_LANE_C_MODE_MBPS 166 +#define CCS_L_MAX_PER_LANE_BITRATE_LANE_C_MODE_MBPS_OFFSET(n) ((n) * 4) +#define CCS_L_DPHY_EQUALIZATION_CAPABILITY 167 +#define CCS_L_CPHY_EQUALIZATION_CAPABILITY 168 +#define CCS_L_DPHY_PREAMBLE_CAPABILITY 169 +#define CCS_L_DPHY_SSC_CAPABILITY 170 +#define CCS_L_CPHY_CALIBRATION_CAPABILITY 171 +#define CCS_L_DPHY_CALIBRATION_CAPABILITY 172 +#define CCS_L_PHY_CTRL_CAPABILITY_2 173 +#define CCS_L_LRTE_CPHY_CAPABILITY 174 +#define CCS_L_LRTE_DPHY_CAPABILITY 175 +#define CCS_L_ALPS_CAPABILITY_DPHY 176 +#define CCS_L_ALPS_CAPABILITY_CPHY 177 +#define CCS_L_SCRAMBLING_CAPABILITY 178 +#define CCS_L_DPHY_MANUAL_CONSTANT 179 +#define CCS_L_CPHY_MANUAL_CONSTANT 180 +#define CCS_L_CSI2_INTERFACE_CAPABILITY_MISC 181 +#define CCS_L_PHY_CTRL_CAPABILITY_3 182 +#define CCS_L_DPHY_SF 183 +#define CCS_L_CPHY_SF 184 +#define CCS_L_DPHY_LIMITS_1 185 +#define CCS_L_DPHY_LIMITS_2 186 +#define CCS_L_DPHY_LIMITS_3 187 +#define CCS_L_DPHY_LIMITS_4 188 +#define CCS_L_DPHY_LIMITS_5 189 +#define CCS_L_DPHY_LIMITS_6 190 +#define CCS_L_CPHY_LIMITS_1 191 +#define CCS_L_CPHY_LIMITS_2 192 +#define CCS_L_CPHY_LIMITS_3 193 +#define CCS_L_MIN_FRAME_LENGTH_LINES_BIN 194 +#define CCS_L_MAX_FRAME_LENGTH_LINES_BIN 195 +#define CCS_L_MIN_LINE_LENGTH_PCK_BIN 196 +#define CCS_L_MAX_LINE_LENGTH_PCK_BIN 197 +#define CCS_L_MIN_LINE_BLANKING_PCK_BIN 198 +#define CCS_L_FINE_INTEGRATION_TIME_MIN_BIN 199 +#define CCS_L_FINE_INTEGRATION_TIME_MAX_MARGIN_BIN 200 +#define CCS_L_BINNING_CAPABILITY 201 +#define CCS_L_BINNING_WEIGHTING_CAPABILITY 202 +#define CCS_L_BINNING_SUB_TYPES 203 +#define CCS_L_BINNING_SUB_TYPE 204 +#define CCS_L_BINNING_SUB_TYPE_OFFSET(n) (n) +#define CCS_L_BINNING_WEIGHTING_MONO_CAPABILITY 205 +#define CCS_L_BINNING_SUB_TYPES_MONO 206 +#define CCS_L_BINNING_SUB_TYPE_MONO 207 +#define CCS_L_BINNING_SUB_TYPE_MONO_OFFSET(n) (n) +#define CCS_L_DATA_TRANSFER_IF_CAPABILITY 208 +#define CCS_L_SHADING_CORRECTION_CAPABILITY 209 +#define CCS_L_GREEN_IMBALANCE_CAPABILITY 210 +#define CCS_L_MODULE_SPECIFIC_CORRECTION_CAPABILITY 211 +#define CCS_L_DEFECT_CORRECTION_CAPABILITY 212 +#define CCS_L_DEFECT_CORRECTION_CAPABILITY_2 213 +#define CCS_L_NF_CAPABILITY 214 +#define CCS_L_OB_READOUT_CAPABILITY 215 +#define CCS_L_COLOR_FEEDBACK_CAPABILITY 216 +#define CCS_L_CFA_PATTERN_CAPABILITY 217 +#define CCS_L_CFA_PATTERN_CONVERSION_CAPABILITY 218 +#define CCS_L_FLASH_MODE_CAPABILITY 219 +#define CCS_L_SA_STROBE_MODE_CAPABILITY 220 +#define CCS_L_RESET_MAX_DELAY 221 +#define CCS_L_RESET_MIN_TIME 222 +#define CCS_L_PDAF_CAPABILITY_1 223 +#define CCS_L_PDAF_CAPABILITY_2 224 +#define CCS_L_BRACKETING_LUT_CAPABILITY_1 225 +#define CCS_L_BRACKETING_LUT_CAPABILITY_2 226 +#define CCS_L_BRACKETING_LUT_SIZE 227 +#define CCS_L_LAST 228 + +#endif /* __CCS_LIMITS_H__ */ diff --git a/drivers/media/i2c/ccs/ccs-quirk.c b/drivers/media/i2c/ccs/ccs-quirk.c new file mode 100644 index 000000000..e48a4fa1f --- /dev/null +++ b/drivers/media/i2c/ccs/ccs-quirk.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/ccs/ccs-quirk.c + * + * Generic driver for MIPI CCS/SMIA/SMIA++ compliant camera sensors + * + * Copyright (C) 2020 Intel Corporation + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus + */ + +#include + +#include "ccs.h" +#include "ccs-limits.h" + +static int ccs_write_addr_8s(struct ccs_sensor *sensor, + const struct ccs_reg_8 *regs, int len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + for (; len > 0; len--, regs++) { + rval = ccs_write_addr(sensor, regs->reg, regs->val); + if (rval < 0) { + dev_err(&client->dev, + "error %d writing reg 0x%4.4x, val 0x%2.2x", + rval, regs->reg, regs->val); + return rval; + } + } + + return 0; +} + +static int jt8ew9_limits(struct ccs_sensor *sensor) +{ + if (sensor->minfo.revision_number < 0x0300) + sensor->frame_skip = 1; + + /* Below 24 gain doesn't have effect at all, */ + /* but ~59 is needed for full dynamic range */ + ccs_replace_limit(sensor, CCS_L_ANALOG_GAIN_CODE_MIN, 0, 59); + ccs_replace_limit(sensor, CCS_L_ANALOG_GAIN_CODE_MAX, 0, 6000); + + return 0; +} + +static int jt8ew9_post_poweron(struct ccs_sensor *sensor) +{ + static const struct ccs_reg_8 regs[] = { + { 0x30a3, 0xd8 }, /* Output port control : LVDS ports only */ + { 0x30ae, 0x00 }, /* 0x0307 pll_multiplier maximum value on PLL input 9.6MHz ( 19.2MHz is divided on pre_pll_div) */ + { 0x30af, 0xd0 }, /* 0x0307 pll_multiplier maximum value on PLL input 9.6MHz ( 19.2MHz is divided on pre_pll_div) */ + { 0x322d, 0x04 }, /* Adjusting Processing Image Size to Scaler Toshiba Recommendation Setting */ + { 0x3255, 0x0f }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */ + { 0x3256, 0x15 }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */ + { 0x3258, 0x70 }, /* Analog Gain Control Toshiba Recommendation Setting */ + { 0x3259, 0x70 }, /* Analog Gain Control Toshiba Recommendation Setting */ + { 0x325f, 0x7c }, /* Analog Gain Control Toshiba Recommendation Setting */ + { 0x3302, 0x06 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */ + { 0x3304, 0x00 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */ + { 0x3307, 0x22 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */ + { 0x3308, 0x8d }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */ + { 0x331e, 0x0f }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3320, 0x30 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3321, 0x11 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3322, 0x98 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3323, 0x64 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x3325, 0x83 }, /* Read Out Timing Control Toshiba Recommendation Setting */ + { 0x3330, 0x18 }, /* Read Out Timing Control Toshiba Recommendation Setting */ + { 0x333c, 0x01 }, /* Read Out Timing Control Toshiba Recommendation Setting */ + { 0x3345, 0x2f }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */ + { 0x33de, 0x38 }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */ + /* Taken from v03. No idea what the rest are. */ + { 0x32e0, 0x05 }, + { 0x32e1, 0x05 }, + { 0x32e2, 0x04 }, + { 0x32e5, 0x04 }, + { 0x32e6, 0x04 }, + + }; + + return ccs_write_addr_8s(sensor, regs, ARRAY_SIZE(regs)); +} + +const struct ccs_quirk smiapp_jt8ew9_quirk = { + .limits = jt8ew9_limits, + .post_poweron = jt8ew9_post_poweron, +}; + +static int imx125es_post_poweron(struct ccs_sensor *sensor) +{ + /* Taken from v02. No idea what the other two are. */ + static const struct ccs_reg_8 regs[] = { + /* + * 0x3302: clk during frame blanking: + * 0x00 - HS mode, 0x01 - LP11 + */ + { 0x3302, 0x01 }, + { 0x302d, 0x00 }, + { 0x3b08, 0x8c }, + }; + + return ccs_write_addr_8s(sensor, regs, ARRAY_SIZE(regs)); +} + +const struct ccs_quirk smiapp_imx125es_quirk = { + .post_poweron = imx125es_post_poweron, +}; + +static int jt8ev1_limits(struct ccs_sensor *sensor) +{ + ccs_replace_limit(sensor, CCS_L_X_ADDR_MAX, 0, 4271); + ccs_replace_limit(sensor, CCS_L_MIN_LINE_BLANKING_PCK_BIN, 0, 184); + + return 0; +} + +static int jt8ev1_post_poweron(struct ccs_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + static const struct ccs_reg_8 regs[] = { + { 0x3031, 0xcd }, /* For digital binning (EQ_MONI) */ + { 0x30a3, 0xd0 }, /* FLASH STROBE enable */ + { 0x3237, 0x00 }, /* For control of pulse timing for ADC */ + { 0x3238, 0x43 }, + { 0x3301, 0x06 }, /* For analog bias for sensor */ + { 0x3302, 0x06 }, + { 0x3304, 0x00 }, + { 0x3305, 0x88 }, + { 0x332a, 0x14 }, + { 0x332c, 0x6b }, + { 0x3336, 0x01 }, + { 0x333f, 0x1f }, + { 0x3355, 0x00 }, + { 0x3356, 0x20 }, + { 0x33bf, 0x20 }, /* Adjust the FBC speed */ + { 0x33c9, 0x20 }, + { 0x33ce, 0x30 }, /* Adjust the parameter for logic function */ + { 0x33cf, 0xec }, /* For Black sun */ + { 0x3328, 0x80 }, /* Ugh. No idea what's this. */ + }; + static const struct ccs_reg_8 regs_96[] = { + { 0x30ae, 0x00 }, /* For control of ADC clock */ + { 0x30af, 0xd0 }, + { 0x30b0, 0x01 }, + }; + + rval = ccs_write_addr_8s(sensor, regs, ARRAY_SIZE(regs)); + if (rval < 0) + return rval; + + switch (sensor->hwcfg.ext_clk) { + case 9600000: + return ccs_write_addr_8s(sensor, regs_96, + ARRAY_SIZE(regs_96)); + default: + dev_warn(&client->dev, "no MSRs for %d Hz ext_clk\n", + sensor->hwcfg.ext_clk); + return 0; + } +} + +static int jt8ev1_pre_streamon(struct ccs_sensor *sensor) +{ + return ccs_write_addr(sensor, 0x3328, 0x00); +} + +static int jt8ev1_post_streamoff(struct ccs_sensor *sensor) +{ + int rval; + + /* Workaround: allows fast standby to work properly */ + rval = ccs_write_addr(sensor, 0x3205, 0x04); + if (rval < 0) + return rval; + + /* Wait for 1 ms + one line => 2 ms is likely enough */ + usleep_range(2000, 2050); + + /* Restore it */ + rval = ccs_write_addr(sensor, 0x3205, 0x00); + if (rval < 0) + return rval; + + return ccs_write_addr(sensor, 0x3328, 0x80); +} + +static int jt8ev1_init(struct ccs_sensor *sensor) +{ + sensor->pll.flags |= CCS_PLL_FLAG_LANE_SPEED_MODEL; + sensor->pll.vt_lanes = 1; + sensor->pll.op_lanes = sensor->pll.csi2.lanes; + + return 0; +} + +const struct ccs_quirk smiapp_jt8ev1_quirk = { + .limits = jt8ev1_limits, + .post_poweron = jt8ev1_post_poweron, + .pre_streamon = jt8ev1_pre_streamon, + .post_streamoff = jt8ev1_post_streamoff, + .init = jt8ev1_init, +}; + +static int tcm8500md_limits(struct ccs_sensor *sensor) +{ + ccs_replace_limit(sensor, CCS_L_MIN_PLL_IP_CLK_FREQ_MHZ, 0, 2700000); + + return 0; +} + +const struct ccs_quirk smiapp_tcm8500md_quirk = { + .limits = tcm8500md_limits, +}; diff --git a/drivers/media/i2c/ccs/ccs-quirk.h b/drivers/media/i2c/ccs/ccs-quirk.h new file mode 100644 index 000000000..392c97109 --- /dev/null +++ b/drivers/media/i2c/ccs/ccs-quirk.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/i2c/ccs/ccs-quirk.h + * + * Generic driver for MIPI CCS/SMIA/SMIA++ compliant camera sensors + * + * Copyright (C) 2020 Intel Corporation + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus + */ + +#ifndef __CCS_QUIRK__ +#define __CCS_QUIRK__ + +struct ccs_sensor; + +/** + * struct ccs_quirk - quirks for sensors that deviate from SMIA++ standard + * + * @limits: Replace sensor->limits with values which can't be read from + * sensor registers. Called the first time the sensor is powered up. + * @post_poweron: Called always after the sensor has been fully powered on. + * @pre_streamon: Called just before streaming is enabled. + * @post_streamoff: Called right after stopping streaming. + * @pll_flags: Return flags for the PLL calculator. + * @init: Quirk initialisation, called the last in probe(). This is + * also appropriate for adding sensor specific controls, for instance. + * @reg_access: Register access quirk. The quirk may divert the access + * to another register, or no register at all. + * + * -write: Is this read (false) or write (true) access? + * -reg: Pointer to the register to access + * -val: Register value, set by the caller on write, or + * by the quirk on read + * -return: 0 on success, -ENOIOCTLCMD if no register + * access may be done by the caller (default read + * value is zero), else negative error code on error + * @flags: Quirk flags + */ +struct ccs_quirk { + int (*limits)(struct ccs_sensor *sensor); + int (*post_poweron)(struct ccs_sensor *sensor); + int (*pre_streamon)(struct ccs_sensor *sensor); + int (*post_streamoff)(struct ccs_sensor *sensor); + unsigned long (*pll_flags)(struct ccs_sensor *sensor); + int (*init)(struct ccs_sensor *sensor); + int (*reg_access)(struct ccs_sensor *sensor, bool write, u32 *reg, + u32 *val); + unsigned long flags; +}; + +#define CCS_QUIRK_FLAG_8BIT_READ_ONLY (1 << 0) + +struct ccs_reg_8 { + u16 reg; + u8 val; +}; + +#define CCS_MK_QUIRK_REG_8(_reg, _val) \ + { \ + .reg = (u16)_reg, \ + .val = _val, \ + } + +#define ccs_call_quirk(sensor, _quirk, ...) \ + ((sensor)->minfo.quirk && \ + (sensor)->minfo.quirk->_quirk ? \ + (sensor)->minfo.quirk->_quirk(sensor, ##__VA_ARGS__) : 0) + +#define ccs_needs_quirk(sensor, _quirk) \ + ((sensor)->minfo.quirk ? \ + (sensor)->minfo.quirk->flags & _quirk : 0) + +extern const struct ccs_quirk smiapp_jt8ev1_quirk; +extern const struct ccs_quirk smiapp_imx125es_quirk; +extern const struct ccs_quirk smiapp_jt8ew9_quirk; +extern const struct ccs_quirk smiapp_tcm8500md_quirk; + +#endif /* __CCS_QUIRK__ */ diff --git a/drivers/media/i2c/ccs/ccs-reg-access.c b/drivers/media/i2c/ccs/ccs-reg-access.c new file mode 100644 index 000000000..a0181a5d2 --- /dev/null +++ b/drivers/media/i2c/ccs/ccs-reg-access.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/i2c/ccs/ccs-reg-access.c + * + * Generic driver for MIPI CCS/SMIA/SMIA++ compliant camera sensors + * + * Copyright (C) 2020 Intel Corporation + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus + */ + +#include + +#include +#include +#include + +#include "ccs.h" +#include "ccs-limits.h" + +static u32 float_to_u32_mul_1000000(struct i2c_client *client, u32 phloat) +{ + s32 exp; + u64 man; + + if (phloat >= 0x80000000) { + dev_err(&client->dev, "this is a negative number\n"); + return 0; + } + + if (phloat == 0x7f800000) + return ~0; /* Inf. */ + + if ((phloat & 0x7f800000) == 0x7f800000) { + dev_err(&client->dev, "NaN or other special number\n"); + return 0; + } + + /* Valid cases begin here */ + if (phloat == 0) + return 0; /* Valid zero */ + + if (phloat > 0x4f800000) + return ~0; /* larger than 4294967295 */ + + /* + * Unbias exponent (note how phloat is now guaranteed to + * have 0 in the high bit) + */ + exp = ((int32_t)phloat >> 23) - 127; + + /* Extract mantissa, add missing '1' bit and it's in MHz */ + man = ((phloat & 0x7fffff) | 0x800000) * 1000000ULL; + + if (exp < 0) + man >>= -exp; + else + man <<= exp; + + man >>= 23; /* Remove mantissa bias */ + + return man & 0xffffffff; +} + + +static u32 ireal32_to_u32_mul_1000000(struct i2c_client *client, u32 val) +{ + if (val >> 10 > U32_MAX / 15625) { + dev_warn(&client->dev, "value %u overflows!\n", val); + return U32_MAX; + } + + return ((val >> 10) * 15625) + + (val & GENMASK(9, 0)) * 15625 / 1024; +} + +u32 ccs_reg_conv(struct ccs_sensor *sensor, u32 reg, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + + if (reg & CCS_FL_FLOAT_IREAL) { + if (CCS_LIM(sensor, CLOCK_CAPA_TYPE_CAPABILITY) & + CCS_CLOCK_CAPA_TYPE_CAPABILITY_IREAL) + val = ireal32_to_u32_mul_1000000(client, val); + else + val = float_to_u32_mul_1000000(client, val); + } else if (reg & CCS_FL_IREAL) { + val = ireal32_to_u32_mul_1000000(client, val); + } + + return val; +} + +/* + * Read a 8/16/32-bit i2c register. The value is returned in 'val'. + * Returns zero if successful, or non-zero otherwise. + */ +static int __ccs_read_addr(struct ccs_sensor *sensor, u32 reg, u32 *val, + bool only8, bool conv) +{ + u64 __val; + int rval; + + rval = cci_read(sensor->regmap, reg, &__val, NULL); + if (rval < 0) + return rval; + + *val = conv ? ccs_reg_conv(sensor, reg, __val) : __val; + + return 0; +} + +static int __ccs_static_data_read_ro_reg(struct ccs_reg *regs, size_t num_regs, + u32 reg, u32 *val) +{ + unsigned int width = CCI_REG_WIDTH_BYTES(reg); + size_t i; + + for (i = 0; i < num_regs; i++, regs++) { + u8 *data; + + if (regs->addr + regs->len < CCS_REG_ADDR(reg) + width) + continue; + + if (regs->addr > CCS_REG_ADDR(reg)) + break; + + data = ®s->value[CCS_REG_ADDR(reg) - regs->addr]; + + switch (width) { + case sizeof(u8): + *val = *data; + break; + case sizeof(u16): + *val = get_unaligned_be16(data); + break; + case sizeof(u32): + *val = get_unaligned_be32(data); + break; + default: + WARN_ON(1); + return -EINVAL; + } + + return 0; + } + + return -ENOENT; +} + +static int +ccs_static_data_read_ro_reg(struct ccs_sensor *sensor, u32 reg, u32 *val) +{ + if (!__ccs_static_data_read_ro_reg(sensor->sdata.sensor_read_only_regs, + sensor->sdata.num_sensor_read_only_regs, + reg, val)) + return 0; + + return __ccs_static_data_read_ro_reg(sensor->mdata.module_read_only_regs, + sensor->mdata.num_module_read_only_regs, + reg, val); +} + +static int ccs_read_addr_raw(struct ccs_sensor *sensor, u32 reg, u32 *val, + bool force8, bool quirk, bool conv, bool data) +{ + int rval; + + if (data) { + rval = ccs_static_data_read_ro_reg(sensor, reg, val); + if (!rval) + return 0; + } + + if (quirk) { + *val = 0; + rval = ccs_call_quirk(sensor, reg_access, false, ®, val); + if (rval == -ENOIOCTLCMD) + return 0; + if (rval < 0) + return rval; + + if (force8) + return __ccs_read_addr(sensor, reg, val, true, conv); + } + + return __ccs_read_addr(sensor, reg, val, + ccs_needs_quirk(sensor, + CCS_QUIRK_FLAG_8BIT_READ_ONLY), + conv); +} + +int ccs_read_addr(struct ccs_sensor *sensor, u32 reg, u32 *val) +{ + return ccs_read_addr_raw(sensor, reg, val, false, true, true, true); +} + +int ccs_read_addr_8only(struct ccs_sensor *sensor, u32 reg, u32 *val) +{ + return ccs_read_addr_raw(sensor, reg, val, true, true, true, true); +} + +int ccs_read_addr_noconv(struct ccs_sensor *sensor, u32 reg, u32 *val) +{ + return ccs_read_addr_raw(sensor, reg, val, false, true, false, true); +} + +/* + * Write to a 8/16-bit register. + * Returns zero if successful, or non-zero otherwise. + */ +int ccs_write_addr(struct ccs_sensor *sensor, u32 reg, u32 val) +{ + int rval; + + rval = ccs_call_quirk(sensor, reg_access, true, ®, &val); + if (rval == -ENOIOCTLCMD) + return 0; + if (rval < 0) + return rval; + + return cci_write(sensor->regmap, reg, val, NULL); +} + +#define MAX_WRITE_LEN 32U + +int ccs_write_data_regs(struct ccs_sensor *sensor, struct ccs_reg *regs, + size_t num_regs) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + size_t i; + + for (i = 0; i < num_regs; i++, regs++) { + unsigned char *regdata = regs->value; + unsigned int j; + int len; + + for (j = 0; j < regs->len; j += len, regdata += len) { + char printbuf[(MAX_WRITE_LEN << 1) + + 1 /* \0 */] = { 0 }; + unsigned int retries = 10; + int rval; + + len = min(regs->len - j, MAX_WRITE_LEN); + + bin2hex(printbuf, regdata, len); + dev_dbg(&client->dev, + "writing msr reg 0x%4.4x value 0x%s\n", + regs->addr + j, printbuf); + + do { + rval = regmap_bulk_write(sensor->regmap, + regs->addr + j, + regdata, len); + if (rval) + fsleep(1000); + } while (rval && --retries); + + if (rval) { + dev_err(&client->dev, + "error writing %u octets to address 0x%4.4x\n", + len, regs->addr + j); + return rval; + } + } + } + + return 0; +} diff --git a/drivers/media/i2c/ccs/ccs-reg-access.h b/drivers/media/i2c/ccs/ccs-reg-access.h new file mode 100644 index 000000000..4b56b21a2 --- /dev/null +++ b/drivers/media/i2c/ccs/ccs-reg-access.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * include/media/ccs/ccs-reg-access.h + * + * Generic driver for MIPI CCS/SMIA/SMIA++ compliant camera sensors + * + * Copyright (C) 2020 Intel Corporation + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus + */ + +#ifndef SMIAPP_REGS_H +#define SMIAPP_REGS_H + +#include +#include + +#include "ccs-regs.h" + +#define CCS_REG_ADDR(reg) ((u16)reg) + +struct ccs_sensor; + +int ccs_read_addr(struct ccs_sensor *sensor, u32 reg, u32 *val); +int ccs_read_addr_8only(struct ccs_sensor *sensor, u32 reg, u32 *val); +int ccs_read_addr_noconv(struct ccs_sensor *sensor, u32 reg, u32 *val); +int ccs_write_addr(struct ccs_sensor *sensor, u32 reg, u32 val); +int ccs_write_data_regs(struct ccs_sensor *sensor, struct ccs_reg *regs, + size_t num_regs); + +u32 ccs_reg_conv(struct ccs_sensor *sensor, u32 reg, u32 val); + +#define ccs_read(sensor, reg_name, val) \ + ccs_read_addr(sensor, CCS_R_##reg_name, val) + +#define ccs_write(sensor, reg_name, val) \ + ccs_write_addr(sensor, CCS_R_##reg_name, val) + +#endif diff --git a/drivers/media/i2c/ccs/ccs-regs.h b/drivers/media/i2c/ccs/ccs-regs.h new file mode 100644 index 000000000..7b5dbc86e --- /dev/null +++ b/drivers/media/i2c/ccs/ccs-regs.h @@ -0,0 +1,958 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause */ +/* Copyright (C) 2019--2020 Intel Corporation */ +/* + * Generated by Documentation/driver-api/media/drivers/ccs/mk-ccs-regs; + * do not modify. + */ + +#ifndef __CCS_REGS_H__ +#define __CCS_REGS_H__ + +#include + +#include + +#define CCS_FL_BASE CCI_REG_PRIVATE_SHIFT +#define CCS_FL_FLOAT_IREAL BIT(CCS_FL_BASE) +#define CCS_FL_IREAL BIT(CCS_FL_BASE + 1) +#define CCS_BUILD_BUG \ + BUILD_BUG_ON(~CCI_REG_PRIVATE_MASK & (BIT(CCS_FL_BASE) | BIT(CCS_FL_BASE + 1))) +#define CCS_R_MODULE_MODEL_ID CCI_REG16(0x0000) +#define CCS_R_MODULE_REVISION_NUMBER_MAJOR CCI_REG8(0x0002) +#define CCS_R_FRAME_COUNT CCI_REG8(0x0005) +#define CCS_R_PIXEL_ORDER CCI_REG8(0x0006) +#define CCS_PIXEL_ORDER_GRBG 0U +#define CCS_PIXEL_ORDER_RGGB 1U +#define CCS_PIXEL_ORDER_BGGR 2U +#define CCS_PIXEL_ORDER_GBRG 3U +#define CCS_R_MIPI_CCS_VERSION CCI_REG8(0x0007) +#define CCS_MIPI_CCS_VERSION_V1_0 0x10 +#define CCS_MIPI_CCS_VERSION_V1_1 0x11 +#define CCS_MIPI_CCS_VERSION_MAJOR_SHIFT 4U +#define CCS_MIPI_CCS_VERSION_MAJOR_MASK 0xf0 +#define CCS_MIPI_CCS_VERSION_MINOR_SHIFT 0U +#define CCS_MIPI_CCS_VERSION_MINOR_MASK 0xf +#define CCS_R_DATA_PEDESTAL CCI_REG16(0x0008) +#define CCS_R_MODULE_MANUFACTURER_ID CCI_REG16(0x000e) +#define CCS_R_MODULE_REVISION_NUMBER_MINOR CCI_REG8(0x0010) +#define CCS_R_MODULE_DATE_YEAR CCI_REG8(0x0012) +#define CCS_R_MODULE_DATE_MONTH CCI_REG8(0x0013) +#define CCS_R_MODULE_DATE_DAY CCI_REG8(0x0014) +#define CCS_R_MODULE_DATE_PHASE CCI_REG8(0x0015) +#define CCS_MODULE_DATE_PHASE_SHIFT 0U +#define CCS_MODULE_DATE_PHASE_MASK 0x7 +#define CCS_MODULE_DATE_PHASE_TS 0U +#define CCS_MODULE_DATE_PHASE_ES 1U +#define CCS_MODULE_DATE_PHASE_CS 2U +#define CCS_MODULE_DATE_PHASE_MP 3U +#define CCS_R_SENSOR_MODEL_ID CCI_REG16(0x0016) +#define CCS_R_SENSOR_REVISION_NUMBER CCI_REG8(0x0018) +#define CCS_R_SENSOR_FIRMWARE_VERSION CCI_REG8(0x001a) +#define CCS_R_SERIAL_NUMBER CCI_REG32(0x001c) +#define CCS_R_SENSOR_MANUFACTURER_ID CCI_REG16(0x0020) +#define CCS_R_SENSOR_REVISION_NUMBER_16 CCI_REG16(0x0022) +#define CCS_R_FRAME_FORMAT_MODEL_TYPE CCI_REG8(0x0040) +#define CCS_FRAME_FORMAT_MODEL_TYPE_2_BYTE 1U +#define CCS_FRAME_FORMAT_MODEL_TYPE_4_BYTE 2U +#define CCS_R_FRAME_FORMAT_MODEL_SUBTYPE CCI_REG8(0x0041) +#define CCS_FRAME_FORMAT_MODEL_SUBTYPE_ROWS_SHIFT 0U +#define CCS_FRAME_FORMAT_MODEL_SUBTYPE_ROWS_MASK 0xf +#define CCS_FRAME_FORMAT_MODEL_SUBTYPE_COLUMNS_SHIFT 4U +#define CCS_FRAME_FORMAT_MODEL_SUBTYPE_COLUMNS_MASK 0xf0 +#define CCS_R_FRAME_FORMAT_DESCRIPTOR(n) CCI_REG16(0x0042 + (n) * 2) +#define CCS_LIM_FRAME_FORMAT_DESCRIPTOR_MIN_N 0U +#define CCS_LIM_FRAME_FORMAT_DESCRIPTOR_MAX_N 14U +#define CCS_R_FRAME_FORMAT_DESCRIPTOR_4(n) CCI_REG32(0x0060 + (n) * 4) +#define CCS_FRAME_FORMAT_DESCRIPTOR_PIXELS_SHIFT 0U +#define CCS_FRAME_FORMAT_DESCRIPTOR_PIXELS_MASK 0xfff +#define CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_SHIFT 12U +#define CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_MASK 0xf000 +#define CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_EMBEDDED 1U +#define CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_DUMMY_PIXEL 2U +#define CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_BLACK_PIXEL 3U +#define CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_DARK_PIXEL 4U +#define CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_VISIBLE_PIXEL 5U +#define CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_MANUF_SPECIFIC_0 8U +#define CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_MANUF_SPECIFIC_1 9U +#define CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_MANUF_SPECIFIC_2 10U +#define CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_MANUF_SPECIFIC_3 11U +#define CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_MANUF_SPECIFIC_4 12U +#define CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_MANUF_SPECIFIC_5 13U +#define CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_MANUF_SPECIFIC_6 14U +#define CCS_LIM_FRAME_FORMAT_DESCRIPTOR_4_MIN_N 0U +#define CCS_LIM_FRAME_FORMAT_DESCRIPTOR_4_MAX_N 7U +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PIXELS_SHIFT 0U +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PIXELS_MASK 0xffff +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_SHIFT 28U +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_MASK 0xf0000000 +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_EMBEDDED 1U +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_DUMMY_PIXEL 2U +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_BLACK_PIXEL 3U +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_DARK_PIXEL 4U +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_VISIBLE_PIXEL 5U +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_MANUF_SPECIFIC_0 8U +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_MANUF_SPECIFIC_1 9U +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_MANUF_SPECIFIC_2 10U +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_MANUF_SPECIFIC_3 11U +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_MANUF_SPECIFIC_4 12U +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_MANUF_SPECIFIC_5 13U +#define CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_MANUF_SPECIFIC_6 14U +#define CCS_R_ANALOG_GAIN_CAPABILITY CCI_REG16(0x0080) +#define CCS_ANALOG_GAIN_CAPABILITY_GLOBAL 0U +#define CCS_ANALOG_GAIN_CAPABILITY_ALTERNATE_GLOBAL 2U +#define CCS_R_ANALOG_GAIN_CODE_MIN CCI_REG16(0x0084) +#define CCS_R_ANALOG_GAIN_CODE_MAX CCI_REG16(0x0086) +#define CCS_R_ANALOG_GAIN_CODE_STEP CCI_REG16(0x0088) +#define CCS_R_ANALOG_GAIN_TYPE CCI_REG16(0x008a) +#define CCS_R_ANALOG_GAIN_M0 CCI_REG16(0x008c) +#define CCS_R_ANALOG_GAIN_C0 CCI_REG16(0x008e) +#define CCS_R_ANALOG_GAIN_M1 CCI_REG16(0x0090) +#define CCS_R_ANALOG_GAIN_C1 CCI_REG16(0x0092) +#define CCS_R_ANALOG_LINEAR_GAIN_MIN CCI_REG16(0x0094) +#define CCS_R_ANALOG_LINEAR_GAIN_MAX CCI_REG16(0x0096) +#define CCS_R_ANALOG_LINEAR_GAIN_STEP_SIZE CCI_REG16(0x0098) +#define CCS_R_ANALOG_EXPONENTIAL_GAIN_MIN CCI_REG16(0x009a) +#define CCS_R_ANALOG_EXPONENTIAL_GAIN_MAX CCI_REG16(0x009c) +#define CCS_R_ANALOG_EXPONENTIAL_GAIN_STEP_SIZE CCI_REG16(0x009e) +#define CCS_R_DATA_FORMAT_MODEL_TYPE CCI_REG8(0x00c0) +#define CCS_DATA_FORMAT_MODEL_TYPE_NORMAL 1U +#define CCS_DATA_FORMAT_MODEL_TYPE_EXTENDED 2U +#define CCS_R_DATA_FORMAT_MODEL_SUBTYPE CCI_REG8(0x00c1) +#define CCS_DATA_FORMAT_MODEL_SUBTYPE_ROWS_SHIFT 0U +#define CCS_DATA_FORMAT_MODEL_SUBTYPE_ROWS_MASK 0xf +#define CCS_DATA_FORMAT_MODEL_SUBTYPE_COLUMNS_SHIFT 4U +#define CCS_DATA_FORMAT_MODEL_SUBTYPE_COLUMNS_MASK 0xf0 +#define CCS_R_DATA_FORMAT_DESCRIPTOR(n) CCI_REG16(0x00c2 + (n) * 2) +#define CCS_LIM_DATA_FORMAT_DESCRIPTOR_MIN_N 0U +#define CCS_LIM_DATA_FORMAT_DESCRIPTOR_MAX_N 15U +#define CCS_DATA_FORMAT_DESCRIPTOR_COMPRESSED_SHIFT 0U +#define CCS_DATA_FORMAT_DESCRIPTOR_COMPRESSED_MASK 0xff +#define CCS_DATA_FORMAT_DESCRIPTOR_UNCOMPRESSED_SHIFT 8U +#define CCS_DATA_FORMAT_DESCRIPTOR_UNCOMPRESSED_MASK 0xff00 +#define CCS_R_MODE_SELECT CCI_REG8(0x0100) +#define CCS_MODE_SELECT_SOFTWARE_STANDBY 0U +#define CCS_MODE_SELECT_STREAMING 1U +#define CCS_R_IMAGE_ORIENTATION CCI_REG8(0x0101) +#define CCS_IMAGE_ORIENTATION_HORIZONTAL_MIRROR BIT(0) +#define CCS_IMAGE_ORIENTATION_VERTICAL_FLIP BIT(1) +#define CCS_R_SOFTWARE_RESET CCI_REG8(0x0103) +#define CCS_SOFTWARE_RESET_OFF 0U +#define CCS_SOFTWARE_RESET_ON 1U +#define CCS_R_GROUPED_PARAMETER_HOLD CCI_REG8(0x0104) +#define CCS_R_MASK_CORRUPTED_FRAMES CCI_REG8(0x0105) +#define CCS_MASK_CORRUPTED_FRAMES_ALLOW 0U +#define CCS_MASK_CORRUPTED_FRAMES_MASK 1U +#define CCS_R_FAST_STANDBY_CTRL CCI_REG8(0x0106) +#define CCS_FAST_STANDBY_CTRL_COMPLETE_FRAMES 0U +#define CCS_FAST_STANDBY_CTRL_FRAME_TRUNCATION 1U +#define CCS_R_CCI_ADDRESS_CTRL CCI_REG8(0x0107) +#define CCS_R_2ND_CCI_IF_CTRL CCI_REG8(0x0108) +#define CCS_2ND_CCI_IF_CTRL_ENABLE BIT(0) +#define CCS_2ND_CCI_IF_CTRL_ACK BIT(1) +#define CCS_R_2ND_CCI_ADDRESS_CTRL CCI_REG8(0x0109) +#define CCS_R_CSI_CHANNEL_IDENTIFIER CCI_REG8(0x0110) +#define CCS_R_CSI_SIGNALING_MODE CCI_REG8(0x0111) +#define CCS_CSI_SIGNALING_MODE_CSI_2_DPHY 2U +#define CCS_CSI_SIGNALING_MODE_CSI_2_CPHY 3U +#define CCS_R_CSI_DATA_FORMAT CCI_REG16(0x0112) +#define CCS_R_CSI_LANE_MODE CCI_REG8(0x0114) +#define CCS_R_DPCM_FRAME_DT CCI_REG8(0x011d) +#define CCS_R_BOTTOM_EMBEDDED_DATA_DT CCI_REG8(0x011e) +#define CCS_R_BOTTOM_EMBEDDED_DATA_VC CCI_REG8(0x011f) +#define CCS_R_GAIN_MODE CCI_REG8(0x0120) +#define CCS_GAIN_MODE_GLOBAL 0U +#define CCS_GAIN_MODE_ALTERNATE 1U +#define CCS_R_ADC_BIT_DEPTH CCI_REG8(0x0121) +#define CCS_R_EMB_DATA_CTRL CCI_REG8(0x0122) +#define CCS_EMB_DATA_CTRL_RAW8_PACKING_FOR_RAW16 BIT(0) +#define CCS_EMB_DATA_CTRL_RAW10_PACKING_FOR_RAW20 BIT(1) +#define CCS_EMB_DATA_CTRL_RAW12_PACKING_FOR_RAW24 BIT(2) +#define CCS_R_GPIO_TRIG_MODE CCI_REG8(0x0130) +#define CCS_R_EXTCLK_FREQUENCY_MHZ (CCI_REG16(0x0136) | CCS_FL_IREAL) +#define CCS_R_TEMP_SENSOR_CTRL CCI_REG8(0x0138) +#define CCS_TEMP_SENSOR_CTRL_ENABLE BIT(0) +#define CCS_R_TEMP_SENSOR_MODE CCI_REG8(0x0139) +#define CCS_R_TEMP_SENSOR_OUTPUT CCI_REG8(0x013a) +#define CCS_R_FINE_INTEGRATION_TIME CCI_REG16(0x0200) +#define CCS_R_COARSE_INTEGRATION_TIME CCI_REG16(0x0202) +#define CCS_R_ANALOG_GAIN_CODE_GLOBAL CCI_REG16(0x0204) +#define CCS_R_ANALOG_LINEAR_GAIN_GLOBAL CCI_REG16(0x0206) +#define CCS_R_ANALOG_EXPONENTIAL_GAIN_GLOBAL CCI_REG16(0x0208) +#define CCS_R_DIGITAL_GAIN_GLOBAL CCI_REG16(0x020e) +#define CCS_R_SHORT_ANALOG_GAIN_GLOBAL CCI_REG16(0x0216) +#define CCS_R_SHORT_DIGITAL_GAIN_GLOBAL CCI_REG16(0x0218) +#define CCS_R_HDR_MODE CCI_REG8(0x0220) +#define CCS_HDR_MODE_ENABLED BIT(0) +#define CCS_HDR_MODE_SEPARATE_ANALOG_GAIN BIT(1) +#define CCS_HDR_MODE_UPSCALING BIT(2) +#define CCS_HDR_MODE_RESET_SYNC BIT(3) +#define CCS_HDR_MODE_TIMING_MODE BIT(4) +#define CCS_HDR_MODE_EXPOSURE_CTRL_DIRECT BIT(5) +#define CCS_HDR_MODE_SEPARATE_DIGITAL_GAIN BIT(6) +#define CCS_R_HDR_RESOLUTION_REDUCTION CCI_REG8(0x0221) +#define CCS_HDR_RESOLUTION_REDUCTION_ROW_SHIFT 0U +#define CCS_HDR_RESOLUTION_REDUCTION_ROW_MASK 0xf +#define CCS_HDR_RESOLUTION_REDUCTION_COLUMN_SHIFT 4U +#define CCS_HDR_RESOLUTION_REDUCTION_COLUMN_MASK 0xf0 +#define CCS_R_EXPOSURE_RATIO CCI_REG8(0x0222) +#define CCS_R_HDR_INTERNAL_BIT_DEPTH CCI_REG8(0x0223) +#define CCS_R_DIRECT_SHORT_INTEGRATION_TIME CCI_REG16(0x0224) +#define CCS_R_SHORT_ANALOG_LINEAR_GAIN_GLOBAL CCI_REG16(0x0226) +#define CCS_R_SHORT_ANALOG_EXPONENTIAL_GAIN_GLOBAL CCI_REG16(0x0228) +#define CCS_R_VT_PIX_CLK_DIV CCI_REG16(0x0300) +#define CCS_R_VT_SYS_CLK_DIV CCI_REG16(0x0302) +#define CCS_R_PRE_PLL_CLK_DIV CCI_REG16(0x0304) +#define CCS_R_PLL_MULTIPLIER CCI_REG16(0x0306) +#define CCS_R_OP_PIX_CLK_DIV CCI_REG16(0x0308) +#define CCS_R_OP_SYS_CLK_DIV CCI_REG16(0x030a) +#define CCS_R_OP_PRE_PLL_CLK_DIV CCI_REG16(0x030c) +#define CCS_R_OP_PLL_MULTIPLIER CCI_REG16(0x030e) +#define CCS_R_PLL_MODE CCI_REG8(0x0310) +#define CCS_PLL_MODE_SHIFT 0U +#define CCS_PLL_MODE_MASK 0x1 +#define CCS_PLL_MODE_SINGLE 0U +#define CCS_PLL_MODE_DUAL 1U +#define CCS_R_OP_PIX_CLK_DIV_REV CCI_REG16(0x0312) +#define CCS_R_OP_SYS_CLK_DIV_REV CCI_REG16(0x0314) +#define CCS_R_FRAME_LENGTH_LINES CCI_REG16(0x0340) +#define CCS_R_LINE_LENGTH_PCK CCI_REG16(0x0342) +#define CCS_R_X_ADDR_START CCI_REG16(0x0344) +#define CCS_R_Y_ADDR_START CCI_REG16(0x0346) +#define CCS_R_X_ADDR_END CCI_REG16(0x0348) +#define CCS_R_Y_ADDR_END CCI_REG16(0x034a) +#define CCS_R_X_OUTPUT_SIZE CCI_REG16(0x034c) +#define CCS_R_Y_OUTPUT_SIZE CCI_REG16(0x034e) +#define CCS_R_FRAME_LENGTH_CTRL CCI_REG8(0x0350) +#define CCS_FRAME_LENGTH_CTRL_AUTOMATIC BIT(0) +#define CCS_R_TIMING_MODE_CTRL CCI_REG8(0x0352) +#define CCS_TIMING_MODE_CTRL_MANUAL_READOUT BIT(0) +#define CCS_TIMING_MODE_CTRL_DELAYED_EXPOSURE BIT(1) +#define CCS_R_START_READOUT_RS CCI_REG8(0x0353) +#define CCS_START_READOUT_RS_MANUAL_READOUT_START BIT(0) +#define CCS_R_FRAME_MARGIN CCI_REG16(0x0354) +#define CCS_R_X_EVEN_INC CCI_REG16(0x0380) +#define CCS_R_X_ODD_INC CCI_REG16(0x0382) +#define CCS_R_Y_EVEN_INC CCI_REG16(0x0384) +#define CCS_R_Y_ODD_INC CCI_REG16(0x0386) +#define CCS_R_MONOCHROME_EN CCI_REG8(0x0390) +#define CCS_MONOCHROME_EN_ENABLED 0U +#define CCS_R_SCALING_MODE CCI_REG16(0x0400) +#define CCS_SCALING_MODE_NO_SCALING 0U +#define CCS_SCALING_MODE_HORIZONTAL 1U +#define CCS_R_SCALE_M CCI_REG16(0x0404) +#define CCS_R_SCALE_N CCI_REG16(0x0406) +#define CCS_R_DIGITAL_CROP_X_OFFSET CCI_REG16(0x0408) +#define CCS_R_DIGITAL_CROP_Y_OFFSET CCI_REG16(0x040a) +#define CCS_R_DIGITAL_CROP_IMAGE_WIDTH CCI_REG16(0x040c) +#define CCS_R_DIGITAL_CROP_IMAGE_HEIGHT CCI_REG16(0x040e) +#define CCS_R_COMPRESSION_MODE CCI_REG16(0x0500) +#define CCS_COMPRESSION_MODE_NONE 0U +#define CCS_COMPRESSION_MODE_DPCM_PCM_SIMPLE 1U +#define CCS_R_TEST_PATTERN_MODE CCI_REG16(0x0600) +#define CCS_TEST_PATTERN_MODE_NONE 0U +#define CCS_TEST_PATTERN_MODE_SOLID_COLOR 1U +#define CCS_TEST_PATTERN_MODE_COLOR_BARS 2U +#define CCS_TEST_PATTERN_MODE_FADE_TO_GREY 3U +#define CCS_TEST_PATTERN_MODE_PN9 4U +#define CCS_TEST_PATTERN_MODE_COLOR_TILE 5U +#define CCS_R_TEST_DATA_RED CCI_REG16(0x0602) +#define CCS_R_TEST_DATA_GREENR CCI_REG16(0x0604) +#define CCS_R_TEST_DATA_BLUE CCI_REG16(0x0606) +#define CCS_R_TEST_DATA_GREENB CCI_REG16(0x0608) +#define CCS_R_VALUE_STEP_SIZE_SMOOTH CCI_REG8(0x060a) +#define CCS_R_VALUE_STEP_SIZE_QUANTISED CCI_REG8(0x060b) +#define CCS_R_TCLK_POST CCI_REG8(0x0800) +#define CCS_R_THS_PREPARE CCI_REG8(0x0801) +#define CCS_R_THS_ZERO_MIN CCI_REG8(0x0802) +#define CCS_R_THS_TRAIL CCI_REG8(0x0803) +#define CCS_R_TCLK_TRAIL_MIN CCI_REG8(0x0804) +#define CCS_R_TCLK_PREPARE CCI_REG8(0x0805) +#define CCS_R_TCLK_ZERO CCI_REG8(0x0806) +#define CCS_R_TLPX CCI_REG8(0x0807) +#define CCS_R_PHY_CTRL CCI_REG8(0x0808) +#define CCS_PHY_CTRL_AUTO 0U +#define CCS_PHY_CTRL_UI 1U +#define CCS_PHY_CTRL_MANUAL 2U +#define CCS_R_TCLK_POST_EX CCI_REG16(0x080a) +#define CCS_R_THS_PREPARE_EX CCI_REG16(0x080c) +#define CCS_R_THS_ZERO_MIN_EX CCI_REG16(0x080e) +#define CCS_R_THS_TRAIL_EX CCI_REG16(0x0810) +#define CCS_R_TCLK_TRAIL_MIN_EX CCI_REG16(0x0812) +#define CCS_R_TCLK_PREPARE_EX CCI_REG16(0x0814) +#define CCS_R_TCLK_ZERO_EX CCI_REG16(0x0816) +#define CCS_R_TLPX_EX CCI_REG16(0x0818) +#define CCS_R_REQUESTED_LINK_RATE CCI_REG32(0x0820) +#define CCS_R_DPHY_EQUALIZATION_MODE CCI_REG8(0x0824) +#define CCS_DPHY_EQUALIZATION_MODE_EQ2 BIT(0) +#define CCS_R_PHY_EQUALIZATION_CTRL CCI_REG8(0x0825) +#define CCS_PHY_EQUALIZATION_CTRL_ENABLE BIT(0) +#define CCS_R_DPHY_PREAMBLE_CTRL CCI_REG8(0x0826) +#define CCS_DPHY_PREAMBLE_CTRL_ENABLE BIT(0) +#define CCS_R_DPHY_PREAMBLE_LENGTH CCI_REG8(0x0826) +#define CCS_R_PHY_SSC_CTRL CCI_REG8(0x0828) +#define CCS_PHY_SSC_CTRL_ENABLE BIT(0) +#define CCS_R_MANUAL_LP_CTRL CCI_REG8(0x0829) +#define CCS_MANUAL_LP_CTRL_ENABLE BIT(0) +#define CCS_R_TWAKEUP CCI_REG8(0x082a) +#define CCS_R_TINIT CCI_REG8(0x082b) +#define CCS_R_THS_EXIT CCI_REG8(0x082c) +#define CCS_R_THS_EXIT_EX CCI_REG16(0x082e) +#define CCS_R_PHY_PERIODIC_CALIBRATION_CTRL CCI_REG8(0x0830) +#define CCS_PHY_PERIODIC_CALIBRATION_CTRL_FRAME_BLANKING BIT(0) +#define CCS_R_PHY_PERIODIC_CALIBRATION_INTERVAL CCI_REG8(0x0831) +#define CCS_R_PHY_INIT_CALIBRATION_CTRL CCI_REG8(0x0832) +#define CCS_PHY_INIT_CALIBRATION_CTRL_STREAM_START BIT(0) +#define CCS_R_DPHY_CALIBRATION_MODE CCI_REG8(0x0833) +#define CCS_DPHY_CALIBRATION_MODE_ALSO_ALTERNATE BIT(0) +#define CCS_R_CPHY_CALIBRATION_MODE CCI_REG8(0x0834) +#define CCS_CPHY_CALIBRATION_MODE_FORMAT_1 0U +#define CCS_CPHY_CALIBRATION_MODE_FORMAT_2 1U +#define CCS_CPHY_CALIBRATION_MODE_FORMAT_3 2U +#define CCS_R_T3_CALPREAMBLE_LENGTH CCI_REG8(0x0835) +#define CCS_R_T3_CALPREAMBLE_LENGTH_PER CCI_REG8(0x0836) +#define CCS_R_T3_CALALTSEQ_LENGTH CCI_REG8(0x0837) +#define CCS_R_T3_CALALTSEQ_LENGTH_PER CCI_REG8(0x0838) +#define CCS_R_FM2_INIT_SEED CCI_REG16(0x083a) +#define CCS_R_T3_CALUDEFSEQ_LENGTH CCI_REG16(0x083c) +#define CCS_R_T3_CALUDEFSEQ_LENGTH_PER CCI_REG16(0x083e) +#define CCS_R_TGR_PREAMBLE_LENGTH CCI_REG8(0x0841) +#define CCS_TGR_PREAMBLE_LENGTH_PREAMABLE_PROG_SEQ BIT(7) +#define CCS_TGR_PREAMBLE_LENGTH_BEGIN_PREAMBLE_LENGTH_SHIFT 0U +#define CCS_TGR_PREAMBLE_LENGTH_BEGIN_PREAMBLE_LENGTH_MASK 0x3f +#define CCS_R_TGR_POST_LENGTH CCI_REG8(0x0842) +#define CCS_TGR_POST_LENGTH_POST_LENGTH_SHIFT 0U +#define CCS_TGR_POST_LENGTH_POST_LENGTH_MASK 0x1f +#define CCS_R_TGR_PREAMBLE_PROG_SEQUENCE(n2) CCI_REG8(0x0843 + (n2)) +#define CCS_LIM_TGR_PREAMBLE_PROG_SEQUENCE_MIN_N2 0U +#define CCS_LIM_TGR_PREAMBLE_PROG_SEQUENCE_MAX_N2 6U +#define CCS_TGR_PREAMBLE_PROG_SEQUENCE_SYMBOL_N_1_SHIFT 3U +#define CCS_TGR_PREAMBLE_PROG_SEQUENCE_SYMBOL_N_1_MASK 0x38 +#define CCS_TGR_PREAMBLE_PROG_SEQUENCE_SYMBOL_N_SHIFT 0U +#define CCS_TGR_PREAMBLE_PROG_SEQUENCE_SYMBOL_N_MASK 0x7 +#define CCS_R_T3_PREPARE CCI_REG16(0x084e) +#define CCS_R_T3_LPX CCI_REG16(0x0850) +#define CCS_R_ALPS_CTRL CCI_REG8(0x085a) +#define CCS_ALPS_CTRL_LVLP_DPHY BIT(0) +#define CCS_ALPS_CTRL_LVLP_CPHY BIT(1) +#define CCS_ALPS_CTRL_ALP_CPHY BIT(2) +#define CCS_R_TX_REG_CSI_EPD_EN_SSP_CPHY CCI_REG16(0x0860) +#define CCS_R_TX_REG_CSI_EPD_OP_SLP_CPHY CCI_REG16(0x0862) +#define CCS_R_TX_REG_CSI_EPD_EN_SSP_DPHY CCI_REG16(0x0864) +#define CCS_R_TX_REG_CSI_EPD_OP_SLP_DPHY CCI_REG16(0x0866) +#define CCS_R_TX_REG_CSI_EPD_MISC_OPTION_CPHY CCI_REG8(0x0868) +#define CCS_R_TX_REG_CSI_EPD_MISC_OPTION_DPHY CCI_REG8(0x0869) +#define CCS_R_SCRAMBLING_CTRL CCI_REG8(0x0870) +#define CCS_SCRAMBLING_CTRL_ENABLED BIT(0) +#define CCS_SCRAMBLING_CTRL_SHIFT 2U +#define CCS_SCRAMBLING_CTRL_MASK 0xc +#define CCS_SCRAMBLING_CTRL_1_SEED_CPHY 0U +#define CCS_SCRAMBLING_CTRL_4_SEED_CPHY 3U +#define CCS_R_LANE_SEED_VALUE(seed, lane) CCI_REG16(0x0872 + (seed) * 16 + (lane) * 2) +#define CCS_LIM_LANE_SEED_VALUE_MIN_SEED 0U +#define CCS_LIM_LANE_SEED_VALUE_MAX_SEED 3U +#define CCS_LIM_LANE_SEED_VALUE_MIN_LANE 0U +#define CCS_LIM_LANE_SEED_VALUE_MAX_LANE 7U +#define CCS_R_TX_USL_REV_ENTRY CCI_REG16(0x08c0) +#define CCS_R_TX_USL_REV_CLOCK_COUNTER CCI_REG16(0x08c2) +#define CCS_R_TX_USL_REV_LP_COUNTER CCI_REG16(0x08c4) +#define CCS_R_TX_USL_REV_FRAME_COUNTER CCI_REG16(0x08c6) +#define CCS_R_TX_USL_REV_CHRONOLOGICAL_TIMER CCI_REG16(0x08c8) +#define CCS_R_TX_USL_FWD_ENTRY CCI_REG16(0x08ca) +#define CCS_R_TX_USL_GPIO CCI_REG16(0x08cc) +#define CCS_R_TX_USL_OPERATION CCI_REG16(0x08ce) +#define CCS_TX_USL_OPERATION_RESET BIT(0) +#define CCS_R_TX_USL_ALP_CTRL CCI_REG16(0x08d0) +#define CCS_TX_USL_ALP_CTRL_CLOCK_PAUSE BIT(0) +#define CCS_R_TX_USL_APP_BTA_ACK_TIMEOUT CCI_REG16(0x08d2) +#define CCS_R_TX_USL_SNS_BTA_ACK_TIMEOUT CCI_REG16(0x08d2) +#define CCS_R_USL_CLOCK_MODE_D_CTRL CCI_REG8(0x08d2) +#define CCS_USL_CLOCK_MODE_D_CTRL_CONT_CLOCK_STANDBY BIT(0) +#define CCS_USL_CLOCK_MODE_D_CTRL_CONT_CLOCK_VBLANK BIT(1) +#define CCS_USL_CLOCK_MODE_D_CTRL_CONT_CLOCK_HBLANK BIT(2) +#define CCS_R_BINNING_MODE CCI_REG8(0x0900) +#define CCS_R_BINNING_TYPE CCI_REG8(0x0901) +#define CCS_R_BINNING_WEIGHTING CCI_REG8(0x0902) +#define CCS_R_DATA_TRANSFER_IF_1_CTRL CCI_REG8(0x0a00) +#define CCS_DATA_TRANSFER_IF_1_CTRL_ENABLE BIT(0) +#define CCS_DATA_TRANSFER_IF_1_CTRL_WRITE BIT(1) +#define CCS_DATA_TRANSFER_IF_1_CTRL_CLEAR_ERROR BIT(2) +#define CCS_R_DATA_TRANSFER_IF_1_STATUS CCI_REG8(0x0a01) +#define CCS_DATA_TRANSFER_IF_1_STATUS_READ_IF_READY BIT(0) +#define CCS_DATA_TRANSFER_IF_1_STATUS_WRITE_IF_READY BIT(1) +#define CCS_DATA_TRANSFER_IF_1_STATUS_DATA_CORRUPTED BIT(2) +#define CCS_DATA_TRANSFER_IF_1_STATUS_IMPROPER_IF_USAGE BIT(3) +#define CCS_R_DATA_TRANSFER_IF_1_PAGE_SELECT CCI_REG8(0x0a02) +#define CCS_R_DATA_TRANSFER_IF_1_DATA(p) CCI_REG8(0x0a04 + (p)) +#define CCS_LIM_DATA_TRANSFER_IF_1_DATA_MIN_P 0U +#define CCS_LIM_DATA_TRANSFER_IF_1_DATA_MAX_P 63U +#define CCS_R_SHADING_CORRECTION_EN CCI_REG8(0x0b00) +#define CCS_SHADING_CORRECTION_EN_ENABLE BIT(0) +#define CCS_R_LUMINANCE_CORRECTION_LEVEL CCI_REG8(0x0b01) +#define CCS_R_GREEN_IMBALANCE_FILTER_EN CCI_REG8(0x0b02) +#define CCS_GREEN_IMBALANCE_FILTER_EN_ENABLE BIT(0) +#define CCS_R_MAPPED_DEFECT_CORRECT_EN CCI_REG8(0x0b05) +#define CCS_MAPPED_DEFECT_CORRECT_EN_ENABLE BIT(0) +#define CCS_R_SINGLE_DEFECT_CORRECT_EN CCI_REG8(0x0b06) +#define CCS_SINGLE_DEFECT_CORRECT_EN_ENABLE BIT(0) +#define CCS_R_DYNAMIC_COUPLET_CORRECT_EN CCI_REG8(0x0b08) +#define CCS_DYNAMIC_COUPLET_CORRECT_EN_ENABLE BIT(0) +#define CCS_R_COMBINED_DEFECT_CORRECT_EN CCI_REG8(0x0b0a) +#define CCS_COMBINED_DEFECT_CORRECT_EN_ENABLE BIT(0) +#define CCS_R_MODULE_SPECIFIC_CORRECTION_EN CCI_REG8(0x0b0c) +#define CCS_MODULE_SPECIFIC_CORRECTION_EN_ENABLE BIT(0) +#define CCS_R_DYNAMIC_TRIPLET_DEFECT_CORRECT_EN CCI_REG8(0x0b13) +#define CCS_DYNAMIC_TRIPLET_DEFECT_CORRECT_EN_ENABLE BIT(0) +#define CCS_R_NF_CTRL CCI_REG8(0x0b15) +#define CCS_NF_CTRL_LUMA BIT(0) +#define CCS_NF_CTRL_CHROMA BIT(1) +#define CCS_NF_CTRL_COMBINED BIT(2) +#define CCS_R_OB_READOUT_CONTROL CCI_REG8(0x0b30) +#define CCS_OB_READOUT_CONTROL_ENABLE BIT(0) +#define CCS_OB_READOUT_CONTROL_INTERLEAVING BIT(1) +#define CCS_R_OB_VIRTUAL_CHANNEL CCI_REG8(0x0b31) +#define CCS_R_OB_DT CCI_REG8(0x0b32) +#define CCS_R_OB_DATA_FORMAT CCI_REG8(0x0b33) +#define CCS_R_COLOR_TEMPERATURE CCI_REG16(0x0b8c) +#define CCS_R_ABSOLUTE_GAIN_GREENR CCI_REG16(0x0b8e) +#define CCS_R_ABSOLUTE_GAIN_RED CCI_REG16(0x0b90) +#define CCS_R_ABSOLUTE_GAIN_BLUE CCI_REG16(0x0b92) +#define CCS_R_ABSOLUTE_GAIN_GREENB CCI_REG16(0x0b94) +#define CCS_R_CFA_CONVERSION_CTRL CCI_REG8(0x0ba0) +#define CCS_CFA_CONVERSION_CTRL_BAYER_CONVERSION_ENABLE BIT(0) +#define CCS_R_FLASH_STROBE_ADJUSTMENT CCI_REG8(0x0c12) +#define CCS_R_FLASH_STROBE_START_POINT CCI_REG16(0x0c14) +#define CCS_R_TFLASH_STROBE_DELAY_RS_CTRL CCI_REG16(0x0c16) +#define CCS_R_TFLASH_STROBE_WIDTH_HIGH_RS_CTRL CCI_REG16(0x0c18) +#define CCS_R_FLASH_MODE_RS CCI_REG8(0x0c1a) +#define CCS_FLASH_MODE_RS_CONTINUOUS BIT(0) +#define CCS_FLASH_MODE_RS_TRUNCATE BIT(1) +#define CCS_FLASH_MODE_RS_ASYNC BIT(3) +#define CCS_R_FLASH_TRIGGER_RS CCI_REG8(0x0c1b) +#define CCS_R_FLASH_STATUS CCI_REG8(0x0c1c) +#define CCS_FLASH_STATUS_RETIMED BIT(0) +#define CCS_R_SA_STROBE_MODE CCI_REG8(0x0c1d) +#define CCS_SA_STROBE_MODE_CONTINUOUS BIT(0) +#define CCS_SA_STROBE_MODE_TRUNCATE BIT(1) +#define CCS_SA_STROBE_MODE_ASYNC BIT(3) +#define CCS_SA_STROBE_MODE_ADJUST_EDGE BIT(4) +#define CCS_R_SA_STROBE_START_POINT CCI_REG16(0x0c1e) +#define CCS_R_TSA_STROBE_DELAY_CTRL CCI_REG16(0x0c20) +#define CCS_R_TSA_STROBE_WIDTH_CTRL CCI_REG16(0x0c22) +#define CCS_R_SA_STROBE_TRIGGER CCI_REG8(0x0c24) +#define CCS_R_SA_STROBE_STATUS CCI_REG8(0x0c25) +#define CCS_SA_STROBE_STATUS_RETIMED BIT(0) +#define CCS_R_TSA_STROBE_RE_DELAY_CTRL CCI_REG16(0x0c30) +#define CCS_R_TSA_STROBE_FE_DELAY_CTRL CCI_REG16(0x0c32) +#define CCS_R_PDAF_CTRL CCI_REG16(0x0d00) +#define CCS_PDAF_CTRL_ENABLE BIT(0) +#define CCS_PDAF_CTRL_PROCESSED BIT(1) +#define CCS_PDAF_CTRL_INTERLEAVED BIT(2) +#define CCS_PDAF_CTRL_VISIBLE_PDAF_CORRECTION BIT(3) +#define CCS_R_PDAF_VC CCI_REG8(0x0d02) +#define CCS_R_PDAF_DT CCI_REG8(0x0d03) +#define CCS_R_PD_X_ADDR_START CCI_REG16(0x0d04) +#define CCS_R_PD_Y_ADDR_START CCI_REG16(0x0d06) +#define CCS_R_PD_X_ADDR_END CCI_REG16(0x0d08) +#define CCS_R_PD_Y_ADDR_END CCI_REG16(0x0d0a) +#define CCS_R_BRACKETING_LUT_CTRL CCI_REG8(0x0e00) +#define CCS_R_BRACKETING_LUT_MODE CCI_REG8(0x0e01) +#define CCS_BRACKETING_LUT_MODE_CONTINUE_STREAMING BIT(0) +#define CCS_BRACKETING_LUT_MODE_LOOP_MODE BIT(1) +#define CCS_R_BRACKETING_LUT_ENTRY_CTRL CCI_REG8(0x0e02) +#define CCS_R_BRACKETING_LUT_FRAME(n) CCI_REG8(0x0e10 + (n)) +#define CCS_LIM_BRACKETING_LUT_FRAME_MIN_N 0U +#define CCS_LIM_BRACKETING_LUT_FRAME_MAX_N 239U +#define CCS_R_INTEGRATION_TIME_CAPABILITY CCI_REG16(0x1000) +#define CCS_INTEGRATION_TIME_CAPABILITY_FINE BIT(0) +#define CCS_R_COARSE_INTEGRATION_TIME_MIN CCI_REG16(0x1004) +#define CCS_R_COARSE_INTEGRATION_TIME_MAX_MARGIN CCI_REG16(0x1006) +#define CCS_R_FINE_INTEGRATION_TIME_MIN CCI_REG16(0x1008) +#define CCS_R_FINE_INTEGRATION_TIME_MAX_MARGIN CCI_REG16(0x100a) +#define CCS_R_DIGITAL_GAIN_CAPABILITY CCI_REG8(0x1081) +#define CCS_DIGITAL_GAIN_CAPABILITY_NONE 0U +#define CCS_DIGITAL_GAIN_CAPABILITY_GLOBAL 2U +#define CCS_R_DIGITAL_GAIN_MIN CCI_REG16(0x1084) +#define CCS_R_DIGITAL_GAIN_MAX CCI_REG16(0x1086) +#define CCS_R_DIGITAL_GAIN_STEP_SIZE CCI_REG16(0x1088) +#define CCS_R_PEDESTAL_CAPABILITY CCI_REG8(0x10e0) +#define CCS_R_ADC_CAPABILITY CCI_REG8(0x10f0) +#define CCS_ADC_CAPABILITY_BIT_DEPTH_CTRL BIT(0) +#define CCS_R_ADC_BIT_DEPTH_CAPABILITY CCI_REG32(0x10f4) +#define CCS_R_MIN_EXT_CLK_FREQ_MHZ (CCI_REG32(0x1100) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MAX_EXT_CLK_FREQ_MHZ (CCI_REG32(0x1104) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MIN_PRE_PLL_CLK_DIV CCI_REG16(0x1108) +#define CCS_R_MAX_PRE_PLL_CLK_DIV CCI_REG16(0x110a) +#define CCS_R_MIN_PLL_IP_CLK_FREQ_MHZ (CCI_REG32(0x110c) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MAX_PLL_IP_CLK_FREQ_MHZ (CCI_REG32(0x1110) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MIN_PLL_MULTIPLIER CCI_REG16(0x1114) +#define CCS_R_MAX_PLL_MULTIPLIER CCI_REG16(0x1116) +#define CCS_R_MIN_PLL_OP_CLK_FREQ_MHZ (CCI_REG32(0x1118) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MAX_PLL_OP_CLK_FREQ_MHZ (CCI_REG32(0x111c) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MIN_VT_SYS_CLK_DIV CCI_REG16(0x1120) +#define CCS_R_MAX_VT_SYS_CLK_DIV CCI_REG16(0x1122) +#define CCS_R_MIN_VT_SYS_CLK_FREQ_MHZ (CCI_REG32(0x1124) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MAX_VT_SYS_CLK_FREQ_MHZ (CCI_REG32(0x1128) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MIN_VT_PIX_CLK_FREQ_MHZ (CCI_REG32(0x112c) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MAX_VT_PIX_CLK_FREQ_MHZ (CCI_REG32(0x1130) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MIN_VT_PIX_CLK_DIV CCI_REG16(0x1134) +#define CCS_R_MAX_VT_PIX_CLK_DIV CCI_REG16(0x1136) +#define CCS_R_CLOCK_CALCULATION CCI_REG8(0x1138) +#define CCS_CLOCK_CALCULATION_LANE_SPEED BIT(0) +#define CCS_CLOCK_CALCULATION_LINK_DECOUPLED BIT(1) +#define CCS_CLOCK_CALCULATION_DUAL_PLL_OP_SYS_DDR BIT(2) +#define CCS_CLOCK_CALCULATION_DUAL_PLL_OP_PIX_DDR BIT(3) +#define CCS_R_NUM_OF_VT_LANES CCI_REG8(0x1139) +#define CCS_R_NUM_OF_OP_LANES CCI_REG8(0x113a) +#define CCS_R_OP_BITS_PER_LANE CCI_REG8(0x113b) +#define CCS_R_MIN_FRAME_LENGTH_LINES CCI_REG16(0x1140) +#define CCS_R_MAX_FRAME_LENGTH_LINES CCI_REG16(0x1142) +#define CCS_R_MIN_LINE_LENGTH_PCK CCI_REG16(0x1144) +#define CCS_R_MAX_LINE_LENGTH_PCK CCI_REG16(0x1146) +#define CCS_R_MIN_LINE_BLANKING_PCK CCI_REG16(0x1148) +#define CCS_R_MIN_FRAME_BLANKING_LINES CCI_REG16(0x114a) +#define CCS_R_MIN_LINE_LENGTH_PCK_STEP_SIZE CCI_REG8(0x114c) +#define CCS_R_TIMING_MODE_CAPABILITY CCI_REG8(0x114d) +#define CCS_TIMING_MODE_CAPABILITY_AUTO_FRAME_LENGTH BIT(0) +#define CCS_TIMING_MODE_CAPABILITY_ROLLING_SHUTTER_MANUAL_READOUT BIT(2) +#define CCS_TIMING_MODE_CAPABILITY_DELAYED_EXPOSURE_START BIT(3) +#define CCS_TIMING_MODE_CAPABILITY_MANUAL_EXPOSURE_EMBEDDED_DATA BIT(4) +#define CCS_R_FRAME_MARGIN_MAX_VALUE CCI_REG16(0x114e) +#define CCS_R_FRAME_MARGIN_MIN_VALUE CCI_REG8(0x1150) +#define CCS_R_GAIN_DELAY_TYPE CCI_REG8(0x1151) +#define CCS_GAIN_DELAY_TYPE_FIXED 0U +#define CCS_GAIN_DELAY_TYPE_VARIABLE 1U +#define CCS_R_MIN_OP_SYS_CLK_DIV CCI_REG16(0x1160) +#define CCS_R_MAX_OP_SYS_CLK_DIV CCI_REG16(0x1162) +#define CCS_R_MIN_OP_SYS_CLK_FREQ_MHZ (CCI_REG32(0x1164) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MAX_OP_SYS_CLK_FREQ_MHZ (CCI_REG32(0x1168) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MIN_OP_PIX_CLK_DIV CCI_REG16(0x116c) +#define CCS_R_MAX_OP_PIX_CLK_DIV CCI_REG16(0x116e) +#define CCS_R_MIN_OP_PIX_CLK_FREQ_MHZ (CCI_REG32(0x1170) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MAX_OP_PIX_CLK_FREQ_MHZ (CCI_REG32(0x1174) | CCS_FL_FLOAT_IREAL) +#define CCS_R_X_ADDR_MIN CCI_REG16(0x1180) +#define CCS_R_Y_ADDR_MIN CCI_REG16(0x1182) +#define CCS_R_X_ADDR_MAX CCI_REG16(0x1184) +#define CCS_R_Y_ADDR_MAX CCI_REG16(0x1186) +#define CCS_R_MIN_X_OUTPUT_SIZE CCI_REG16(0x1188) +#define CCS_R_MIN_Y_OUTPUT_SIZE CCI_REG16(0x118a) +#define CCS_R_MAX_X_OUTPUT_SIZE CCI_REG16(0x118c) +#define CCS_R_MAX_Y_OUTPUT_SIZE CCI_REG16(0x118e) +#define CCS_R_X_ADDR_START_DIV_CONSTANT CCI_REG8(0x1190) +#define CCS_R_Y_ADDR_START_DIV_CONSTANT CCI_REG8(0x1191) +#define CCS_R_X_ADDR_END_DIV_CONSTANT CCI_REG8(0x1192) +#define CCS_R_Y_ADDR_END_DIV_CONSTANT CCI_REG8(0x1193) +#define CCS_R_X_SIZE_DIV CCI_REG8(0x1194) +#define CCS_R_Y_SIZE_DIV CCI_REG8(0x1195) +#define CCS_R_X_OUTPUT_DIV CCI_REG8(0x1196) +#define CCS_R_Y_OUTPUT_DIV CCI_REG8(0x1197) +#define CCS_R_NON_FLEXIBLE_RESOLUTION_SUPPORT CCI_REG8(0x1198) +#define CCS_NON_FLEXIBLE_RESOLUTION_SUPPORT_NEW_PIX_ADDR BIT(0) +#define CCS_NON_FLEXIBLE_RESOLUTION_SUPPORT_NEW_OUTPUT_RES BIT(1) +#define CCS_NON_FLEXIBLE_RESOLUTION_SUPPORT_OUTPUT_CROP_NO_PAD BIT(2) +#define CCS_NON_FLEXIBLE_RESOLUTION_SUPPORT_OUTPUT_SIZE_LANE_DEP BIT(3) +#define CCS_R_MIN_OP_PRE_PLL_CLK_DIV CCI_REG16(0x11a0) +#define CCS_R_MAX_OP_PRE_PLL_CLK_DIV CCI_REG16(0x11a2) +#define CCS_R_MIN_OP_PLL_IP_CLK_FREQ_MHZ (CCI_REG32(0x11a4) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MAX_OP_PLL_IP_CLK_FREQ_MHZ (CCI_REG32(0x11a8) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MIN_OP_PLL_MULTIPLIER CCI_REG16(0x11ac) +#define CCS_R_MAX_OP_PLL_MULTIPLIER CCI_REG16(0x11ae) +#define CCS_R_MIN_OP_PLL_OP_CLK_FREQ_MHZ (CCI_REG32(0x11b0) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MAX_OP_PLL_OP_CLK_FREQ_MHZ (CCI_REG32(0x11b4) | CCS_FL_FLOAT_IREAL) +#define CCS_R_CLOCK_TREE_PLL_CAPABILITY CCI_REG8(0x11b8) +#define CCS_CLOCK_TREE_PLL_CAPABILITY_DUAL_PLL BIT(0) +#define CCS_CLOCK_TREE_PLL_CAPABILITY_SINGLE_PLL BIT(1) +#define CCS_CLOCK_TREE_PLL_CAPABILITY_EXT_DIVIDER BIT(2) +#define CCS_CLOCK_TREE_PLL_CAPABILITY_FLEXIBLE_OP_PIX_CLK_DIV BIT(3) +#define CCS_R_CLOCK_CAPA_TYPE_CAPABILITY CCI_REG8(0x11b9) +#define CCS_CLOCK_CAPA_TYPE_CAPABILITY_IREAL BIT(0) +#define CCS_R_MIN_EVEN_INC CCI_REG16(0x11c0) +#define CCS_R_MIN_ODD_INC CCI_REG16(0x11c2) +#define CCS_R_MAX_EVEN_INC CCI_REG16(0x11c4) +#define CCS_R_MAX_ODD_INC CCI_REG16(0x11c6) +#define CCS_R_AUX_SUBSAMP_CAPABILITY CCI_REG8(0x11c8) +#define CCS_AUX_SUBSAMP_CAPABILITY_FACTOR_POWER_OF_2 BIT(1) +#define CCS_R_AUX_SUBSAMP_MONO_CAPABILITY CCI_REG8(0x11c9) +#define CCS_AUX_SUBSAMP_MONO_CAPABILITY_FACTOR_POWER_OF_2 BIT(1) +#define CCS_R_MONOCHROME_CAPABILITY CCI_REG8(0x11ca) +#define CCS_MONOCHROME_CAPABILITY_INC_ODD 0U +#define CCS_MONOCHROME_CAPABILITY_INC_EVEN 1U +#define CCS_R_PIXEL_READOUT_CAPABILITY CCI_REG8(0x11cb) +#define CCS_PIXEL_READOUT_CAPABILITY_BAYER 0U +#define CCS_PIXEL_READOUT_CAPABILITY_MONOCHROME 1U +#define CCS_PIXEL_READOUT_CAPABILITY_BAYER_AND_MONO 2U +#define CCS_R_MIN_EVEN_INC_MONO CCI_REG16(0x11cc) +#define CCS_R_MAX_EVEN_INC_MONO CCI_REG16(0x11ce) +#define CCS_R_MIN_ODD_INC_MONO CCI_REG16(0x11d0) +#define CCS_R_MAX_ODD_INC_MONO CCI_REG16(0x11d2) +#define CCS_R_MIN_EVEN_INC_BC2 CCI_REG16(0x11d4) +#define CCS_R_MAX_EVEN_INC_BC2 CCI_REG16(0x11d6) +#define CCS_R_MIN_ODD_INC_BC2 CCI_REG16(0x11d8) +#define CCS_R_MAX_ODD_INC_BC2 CCI_REG16(0x11da) +#define CCS_R_MIN_EVEN_INC_MONO_BC2 CCI_REG16(0x11dc) +#define CCS_R_MAX_EVEN_INC_MONO_BC2 CCI_REG16(0x11de) +#define CCS_R_MIN_ODD_INC_MONO_BC2 CCI_REG16(0x11f0) +#define CCS_R_MAX_ODD_INC_MONO_BC2 CCI_REG16(0x11f2) +#define CCS_R_SCALING_CAPABILITY CCI_REG16(0x1200) +#define CCS_SCALING_CAPABILITY_NONE 0U +#define CCS_SCALING_CAPABILITY_HORIZONTAL 1U +#define CCS_SCALING_CAPABILITY_RESERVED 2U +#define CCS_R_SCALER_M_MIN CCI_REG16(0x1204) +#define CCS_R_SCALER_M_MAX CCI_REG16(0x1206) +#define CCS_R_SCALER_N_MIN CCI_REG16(0x1208) +#define CCS_R_SCALER_N_MAX CCI_REG16(0x120a) +#define CCS_R_DIGITAL_CROP_CAPABILITY CCI_REG8(0x120e) +#define CCS_DIGITAL_CROP_CAPABILITY_NONE 0U +#define CCS_DIGITAL_CROP_CAPABILITY_INPUT_CROP 1U +#define CCS_R_HDR_CAPABILITY_1 CCI_REG8(0x1210) +#define CCS_HDR_CAPABILITY_1_2X2_BINNING BIT(0) +#define CCS_HDR_CAPABILITY_1_COMBINED_ANALOG_GAIN BIT(1) +#define CCS_HDR_CAPABILITY_1_SEPARATE_ANALOG_GAIN BIT(2) +#define CCS_HDR_CAPABILITY_1_UPSCALING BIT(3) +#define CCS_HDR_CAPABILITY_1_RESET_SYNC BIT(4) +#define CCS_HDR_CAPABILITY_1_DIRECT_SHORT_EXP_TIMING BIT(5) +#define CCS_HDR_CAPABILITY_1_DIRECT_SHORT_EXP_SYNTHESIS BIT(6) +#define CCS_R_MIN_HDR_BIT_DEPTH CCI_REG8(0x1211) +#define CCS_R_HDR_RESOLUTION_SUB_TYPES CCI_REG8(0x1212) +#define CCS_R_HDR_RESOLUTION_SUB_TYPE(n) CCI_REG8(0x1213 + (n)) +#define CCS_LIM_HDR_RESOLUTION_SUB_TYPE_MIN_N 0U +#define CCS_LIM_HDR_RESOLUTION_SUB_TYPE_MAX_N 1U +#define CCS_HDR_RESOLUTION_SUB_TYPE_ROW_SHIFT 0U +#define CCS_HDR_RESOLUTION_SUB_TYPE_ROW_MASK 0xf +#define CCS_HDR_RESOLUTION_SUB_TYPE_COLUMN_SHIFT 4U +#define CCS_HDR_RESOLUTION_SUB_TYPE_COLUMN_MASK 0xf0 +#define CCS_R_HDR_CAPABILITY_2 CCI_REG8(0x121b) +#define CCS_HDR_CAPABILITY_2_COMBINED_DIGITAL_GAIN BIT(0) +#define CCS_HDR_CAPABILITY_2_SEPARATE_DIGITAL_GAIN BIT(1) +#define CCS_HDR_CAPABILITY_2_TIMING_MODE BIT(3) +#define CCS_HDR_CAPABILITY_2_SYNTHESIS_MODE BIT(4) +#define CCS_R_MAX_HDR_BIT_DEPTH CCI_REG8(0x121c) +#define CCS_R_USL_SUPPORT_CAPABILITY CCI_REG8(0x1230) +#define CCS_USL_SUPPORT_CAPABILITY_CLOCK_TREE BIT(0) +#define CCS_USL_SUPPORT_CAPABILITY_REV_CLOCK_TREE BIT(1) +#define CCS_USL_SUPPORT_CAPABILITY_REV_CLOCK_CALC BIT(2) +#define CCS_R_USL_CLOCK_MODE_D_CAPABILITY CCI_REG8(0x1231) +#define CCS_USL_CLOCK_MODE_D_CAPABILITY_CONT_CLOCK_STANDBY BIT(0) +#define CCS_USL_CLOCK_MODE_D_CAPABILITY_CONT_CLOCK_VBLANK BIT(1) +#define CCS_USL_CLOCK_MODE_D_CAPABILITY_CONT_CLOCK_HBLANK BIT(2) +#define CCS_USL_CLOCK_MODE_D_CAPABILITY_NONCONT_CLOCK_STANDBY BIT(3) +#define CCS_USL_CLOCK_MODE_D_CAPABILITY_NONCONT_CLOCK_VBLANK BIT(4) +#define CCS_USL_CLOCK_MODE_D_CAPABILITY_NONCONT_CLOCK_HBLANK BIT(5) +#define CCS_R_MIN_OP_SYS_CLK_DIV_REV CCI_REG8(0x1234) +#define CCS_R_MAX_OP_SYS_CLK_DIV_REV CCI_REG8(0x1236) +#define CCS_R_MIN_OP_PIX_CLK_DIV_REV CCI_REG8(0x1238) +#define CCS_R_MAX_OP_PIX_CLK_DIV_REV CCI_REG8(0x123a) +#define CCS_R_MIN_OP_SYS_CLK_FREQ_REV_MHZ (CCI_REG32(0x123c) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MAX_OP_SYS_CLK_FREQ_REV_MHZ (CCI_REG32(0x1240) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MIN_OP_PIX_CLK_FREQ_REV_MHZ (CCI_REG32(0x1244) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MAX_OP_PIX_CLK_FREQ_REV_MHZ (CCI_REG32(0x1248) | CCS_FL_FLOAT_IREAL) +#define CCS_R_MAX_BITRATE_REV_D_MODE_MBPS (CCI_REG32(0x124c) | CCS_FL_IREAL) +#define CCS_R_MAX_SYMRATE_REV_C_MODE_MSPS (CCI_REG32(0x1250) | CCS_FL_IREAL) +#define CCS_R_COMPRESSION_CAPABILITY CCI_REG8(0x1300) +#define CCS_COMPRESSION_CAPABILITY_DPCM_PCM_SIMPLE BIT(0) +#define CCS_R_TEST_MODE_CAPABILITY CCI_REG16(0x1310) +#define CCS_TEST_MODE_CAPABILITY_SOLID_COLOR BIT(0) +#define CCS_TEST_MODE_CAPABILITY_COLOR_BARS BIT(1) +#define CCS_TEST_MODE_CAPABILITY_FADE_TO_GREY BIT(2) +#define CCS_TEST_MODE_CAPABILITY_PN9 BIT(3) +#define CCS_TEST_MODE_CAPABILITY_COLOR_TILE BIT(5) +#define CCS_R_PN9_DATA_FORMAT1 CCI_REG8(0x1312) +#define CCS_R_PN9_DATA_FORMAT2 CCI_REG8(0x1313) +#define CCS_R_PN9_DATA_FORMAT3 CCI_REG8(0x1314) +#define CCS_R_PN9_DATA_FORMAT4 CCI_REG8(0x1315) +#define CCS_R_PN9_MISC_CAPABILITY CCI_REG8(0x1316) +#define CCS_PN9_MISC_CAPABILITY_NUM_PIXELS_SHIFT 0U +#define CCS_PN9_MISC_CAPABILITY_NUM_PIXELS_MASK 0x7 +#define CCS_PN9_MISC_CAPABILITY_COMPRESSION BIT(3) +#define CCS_R_TEST_PATTERN_CAPABILITY CCI_REG8(0x1317) +#define CCS_TEST_PATTERN_CAPABILITY_NO_REPEAT BIT(1) +#define CCS_R_PATTERN_SIZE_DIV_M1 CCI_REG8(0x1318) +#define CCS_R_FIFO_SUPPORT_CAPABILITY CCI_REG8(0x1502) +#define CCS_FIFO_SUPPORT_CAPABILITY_NONE 0U +#define CCS_FIFO_SUPPORT_CAPABILITY_DERATING 1U +#define CCS_FIFO_SUPPORT_CAPABILITY_DERATING_OVERRATING 2U +#define CCS_R_PHY_CTRL_CAPABILITY CCI_REG8(0x1600) +#define CCS_PHY_CTRL_CAPABILITY_AUTO_PHY_CTL BIT(0) +#define CCS_PHY_CTRL_CAPABILITY_UI_PHY_CTL BIT(1) +#define CCS_PHY_CTRL_CAPABILITY_DPHY_TIME_UI_REG_1_CTL BIT(2) +#define CCS_PHY_CTRL_CAPABILITY_DPHY_TIME_UI_REG_2_CTL BIT(3) +#define CCS_PHY_CTRL_CAPABILITY_DPHY_TIME_CTL BIT(4) +#define CCS_PHY_CTRL_CAPABILITY_DPHY_EXT_TIME_UI_REG_1_CTL BIT(5) +#define CCS_PHY_CTRL_CAPABILITY_DPHY_EXT_TIME_UI_REG_2_CTL BIT(6) +#define CCS_PHY_CTRL_CAPABILITY_DPHY_EXT_TIME_CTL BIT(7) +#define CCS_R_CSI_DPHY_LANE_MODE_CAPABILITY CCI_REG8(0x1601) +#define CCS_CSI_DPHY_LANE_MODE_CAPABILITY_1_LANE BIT(0) +#define CCS_CSI_DPHY_LANE_MODE_CAPABILITY_2_LANE BIT(1) +#define CCS_CSI_DPHY_LANE_MODE_CAPABILITY_3_LANE BIT(2) +#define CCS_CSI_DPHY_LANE_MODE_CAPABILITY_4_LANE BIT(3) +#define CCS_CSI_DPHY_LANE_MODE_CAPABILITY_5_LANE BIT(4) +#define CCS_CSI_DPHY_LANE_MODE_CAPABILITY_6_LANE BIT(5) +#define CCS_CSI_DPHY_LANE_MODE_CAPABILITY_7_LANE BIT(6) +#define CCS_CSI_DPHY_LANE_MODE_CAPABILITY_8_LANE BIT(7) +#define CCS_R_CSI_SIGNALING_MODE_CAPABILITY CCI_REG8(0x1602) +#define CCS_CSI_SIGNALING_MODE_CAPABILITY_CSI_DPHY BIT(2) +#define CCS_CSI_SIGNALING_MODE_CAPABILITY_CSI_CPHY BIT(3) +#define CCS_R_FAST_STANDBY_CAPABILITY CCI_REG8(0x1603) +#define CCS_FAST_STANDBY_CAPABILITY_NO_FRAME_TRUNCATION 0U +#define CCS_FAST_STANDBY_CAPABILITY_FRAME_TRUNCATION 1U +#define CCS_R_CSI_ADDRESS_CONTROL_CAPABILITY CCI_REG8(0x1604) +#define CCS_CSI_ADDRESS_CONTROL_CAPABILITY_CCI_ADDR_CHANGE BIT(0) +#define CCS_CSI_ADDRESS_CONTROL_CAPABILITY_2ND_CCI_ADDR BIT(1) +#define CCS_CSI_ADDRESS_CONTROL_CAPABILITY_SW_CHANGEABLE_2ND_CCI_ADDR BIT(2) +#define CCS_R_DATA_TYPE_CAPABILITY CCI_REG8(0x1605) +#define CCS_DATA_TYPE_CAPABILITY_DPCM_PROGRAMMABLE BIT(0) +#define CCS_DATA_TYPE_CAPABILITY_BOTTOM_EMBEDDED_DT_PROGRAMMABLE BIT(1) +#define CCS_DATA_TYPE_CAPABILITY_BOTTOM_EMBEDDED_VC_PROGRAMMABLE BIT(2) +#define CCS_DATA_TYPE_CAPABILITY_EXT_VC_RANGE BIT(3) +#define CCS_R_CSI_CPHY_LANE_MODE_CAPABILITY CCI_REG8(0x1606) +#define CCS_CSI_CPHY_LANE_MODE_CAPABILITY_1_LANE BIT(0) +#define CCS_CSI_CPHY_LANE_MODE_CAPABILITY_2_LANE BIT(1) +#define CCS_CSI_CPHY_LANE_MODE_CAPABILITY_3_LANE BIT(2) +#define CCS_CSI_CPHY_LANE_MODE_CAPABILITY_4_LANE BIT(3) +#define CCS_CSI_CPHY_LANE_MODE_CAPABILITY_5_LANE BIT(4) +#define CCS_CSI_CPHY_LANE_MODE_CAPABILITY_6_LANE BIT(5) +#define CCS_CSI_CPHY_LANE_MODE_CAPABILITY_7_LANE BIT(6) +#define CCS_CSI_CPHY_LANE_MODE_CAPABILITY_8_LANE BIT(7) +#define CCS_R_EMB_DATA_CAPABILITY CCI_REG8(0x1607) +#define CCS_EMB_DATA_CAPABILITY_TWO_BYTES_PER_RAW16 BIT(0) +#define CCS_EMB_DATA_CAPABILITY_TWO_BYTES_PER_RAW20 BIT(1) +#define CCS_EMB_DATA_CAPABILITY_TWO_BYTES_PER_RAW24 BIT(2) +#define CCS_EMB_DATA_CAPABILITY_NO_ONE_BYTE_PER_RAW16 BIT(3) +#define CCS_EMB_DATA_CAPABILITY_NO_ONE_BYTE_PER_RAW20 BIT(4) +#define CCS_EMB_DATA_CAPABILITY_NO_ONE_BYTE_PER_RAW24 BIT(5) +#define CCS_R_MAX_PER_LANE_BITRATE_LANE_D_MODE_MBPS(n) (CCI_REG32(0x1608 + ((n) < 4 ? (n) * 4 : 0x32 + ((n) - 4) * 4)) | CCS_FL_IREAL) +#define CCS_LIM_MAX_PER_LANE_BITRATE_LANE_D_MODE_MBPS_MIN_N 0U +#define CCS_LIM_MAX_PER_LANE_BITRATE_LANE_D_MODE_MBPS_MAX_N 7U +#define CCS_R_TEMP_SENSOR_CAPABILITY CCI_REG8(0x1618) +#define CCS_TEMP_SENSOR_CAPABILITY_SUPPORTED BIT(0) +#define CCS_TEMP_SENSOR_CAPABILITY_CCS_FORMAT BIT(1) +#define CCS_TEMP_SENSOR_CAPABILITY_RESET_0X80 BIT(2) +#define CCS_R_MAX_PER_LANE_BITRATE_LANE_C_MODE_MBPS(n) (CCI_REG32(0x161a + ((n) < 4 ? (n) * 4 : 0x30 + ((n) - 4) * 4)) | CCS_FL_IREAL) +#define CCS_LIM_MAX_PER_LANE_BITRATE_LANE_C_MODE_MBPS_MIN_N 0U +#define CCS_LIM_MAX_PER_LANE_BITRATE_LANE_C_MODE_MBPS_MAX_N 7U +#define CCS_R_DPHY_EQUALIZATION_CAPABILITY CCI_REG8(0x162b) +#define CCS_DPHY_EQUALIZATION_CAPABILITY_EQUALIZATION_CTRL BIT(0) +#define CCS_DPHY_EQUALIZATION_CAPABILITY_EQ1 BIT(1) +#define CCS_DPHY_EQUALIZATION_CAPABILITY_EQ2 BIT(2) +#define CCS_R_CPHY_EQUALIZATION_CAPABILITY CCI_REG8(0x162c) +#define CCS_CPHY_EQUALIZATION_CAPABILITY_EQUALIZATION_CTRL BIT(0) +#define CCS_R_DPHY_PREAMBLE_CAPABILITY CCI_REG8(0x162d) +#define CCS_DPHY_PREAMBLE_CAPABILITY_PREAMBLE_SEQ_CTRL BIT(0) +#define CCS_R_DPHY_SSC_CAPABILITY CCI_REG8(0x162e) +#define CCS_DPHY_SSC_CAPABILITY_SUPPORTED BIT(0) +#define CCS_R_CPHY_CALIBRATION_CAPABILITY CCI_REG8(0x162f) +#define CCS_CPHY_CALIBRATION_CAPABILITY_MANUAL BIT(0) +#define CCS_CPHY_CALIBRATION_CAPABILITY_MANUAL_STREAMING BIT(1) +#define CCS_CPHY_CALIBRATION_CAPABILITY_FORMAT_1_CTRL BIT(2) +#define CCS_CPHY_CALIBRATION_CAPABILITY_FORMAT_2_CTRL BIT(3) +#define CCS_CPHY_CALIBRATION_CAPABILITY_FORMAT_3_CTRL BIT(4) +#define CCS_R_DPHY_CALIBRATION_CAPABILITY CCI_REG8(0x1630) +#define CCS_DPHY_CALIBRATION_CAPABILITY_MANUAL BIT(0) +#define CCS_DPHY_CALIBRATION_CAPABILITY_MANUAL_STREAMING BIT(1) +#define CCS_DPHY_CALIBRATION_CAPABILITY_ALTERNATE_SEQ BIT(2) +#define CCS_R_PHY_CTRL_CAPABILITY_2 CCI_REG8(0x1631) +#define CCS_PHY_CTRL_CAPABILITY_2_TGR_LENGTH BIT(0) +#define CCS_PHY_CTRL_CAPABILITY_2_TGR_PREAMBLE_PROG_SEQ BIT(1) +#define CCS_PHY_CTRL_CAPABILITY_2_EXTRA_CPHY_MANUAL_TIMING BIT(2) +#define CCS_PHY_CTRL_CAPABILITY_2_CLOCK_BASED_MANUAL_CDPHY BIT(3) +#define CCS_PHY_CTRL_CAPABILITY_2_CLOCK_BASED_MANUAL_DPHY BIT(4) +#define CCS_PHY_CTRL_CAPABILITY_2_CLOCK_BASED_MANUAL_CPHY BIT(5) +#define CCS_PHY_CTRL_CAPABILITY_2_MANUAL_LP_DPHY BIT(6) +#define CCS_PHY_CTRL_CAPABILITY_2_MANUAL_LP_CPHY BIT(7) +#define CCS_R_LRTE_CPHY_CAPABILITY CCI_REG8(0x1632) +#define CCS_LRTE_CPHY_CAPABILITY_PDQ_SHORT BIT(0) +#define CCS_LRTE_CPHY_CAPABILITY_SPACER_SHORT BIT(1) +#define CCS_LRTE_CPHY_CAPABILITY_PDQ_LONG BIT(2) +#define CCS_LRTE_CPHY_CAPABILITY_SPACER_LONG BIT(3) +#define CCS_LRTE_CPHY_CAPABILITY_SPACER_NO_PDQ BIT(4) +#define CCS_R_LRTE_DPHY_CAPABILITY CCI_REG8(0x1633) +#define CCS_LRTE_DPHY_CAPABILITY_PDQ_SHORT_OPT1 BIT(0) +#define CCS_LRTE_DPHY_CAPABILITY_SPACER_SHORT_OPT1 BIT(1) +#define CCS_LRTE_DPHY_CAPABILITY_PDQ_LONG_OPT1 BIT(2) +#define CCS_LRTE_DPHY_CAPABILITY_SPACER_LONG_OPT1 BIT(3) +#define CCS_LRTE_DPHY_CAPABILITY_SPACER_SHORT_OPT2 BIT(4) +#define CCS_LRTE_DPHY_CAPABILITY_SPACER_LONG_OPT2 BIT(5) +#define CCS_LRTE_DPHY_CAPABILITY_SPACER_NO_PDQ_OPT1 BIT(6) +#define CCS_LRTE_DPHY_CAPABILITY_SPACER_VARIABLE_OPT2 BIT(7) +#define CCS_R_ALPS_CAPABILITY_DPHY CCI_REG8(0x1634) +#define CCS_ALPS_CAPABILITY_DPHY_LVLP_NOT_SUPPORTED 0U +#define CCS_ALPS_CAPABILITY_DPHY_LVLP_SUPPORTED 1U +#define CCS_ALPS_CAPABILITY_DPHY_CONTROLLABLE_LVLP 2U +#define CCS_R_ALPS_CAPABILITY_CPHY CCI_REG8(0x1635) +#define CCS_ALPS_CAPABILITY_CPHY_LVLP_NOT_SUPPORTED 0U +#define CCS_ALPS_CAPABILITY_CPHY_LVLP_SUPPORTED 1U +#define CCS_ALPS_CAPABILITY_CPHY_CONTROLLABLE_LVLP 2U +#define CCS_ALPS_CAPABILITY_CPHY_ALP_NOT_SUPPORTED 0xc +#define CCS_ALPS_CAPABILITY_CPHY_ALP_SUPPORTED 0xd +#define CCS_ALPS_CAPABILITY_CPHY_CONTROLLABLE_ALP 0xe +#define CCS_R_SCRAMBLING_CAPABILITY CCI_REG8(0x1636) +#define CCS_SCRAMBLING_CAPABILITY_SCRAMBLING_SUPPORTED BIT(0) +#define CCS_SCRAMBLING_CAPABILITY_MAX_SEEDS_PER_LANE_C_SHIFT 1U +#define CCS_SCRAMBLING_CAPABILITY_MAX_SEEDS_PER_LANE_C_MASK 0x6 +#define CCS_SCRAMBLING_CAPABILITY_MAX_SEEDS_PER_LANE_C_1 0U +#define CCS_SCRAMBLING_CAPABILITY_MAX_SEEDS_PER_LANE_C_4 3U +#define CCS_SCRAMBLING_CAPABILITY_NUM_SEED_REGS_SHIFT 3U +#define CCS_SCRAMBLING_CAPABILITY_NUM_SEED_REGS_MASK 0x38 +#define CCS_SCRAMBLING_CAPABILITY_NUM_SEED_REGS_0 0U +#define CCS_SCRAMBLING_CAPABILITY_NUM_SEED_REGS_1 1U +#define CCS_SCRAMBLING_CAPABILITY_NUM_SEED_REGS_4 4U +#define CCS_SCRAMBLING_CAPABILITY_NUM_SEED_PER_LANE BIT(6) +#define CCS_R_DPHY_MANUAL_CONSTANT CCI_REG8(0x1637) +#define CCS_R_CPHY_MANUAL_CONSTANT CCI_REG8(0x1638) +#define CCS_R_CSI2_INTERFACE_CAPABILITY_MISC CCI_REG8(0x1639) +#define CCS_CSI2_INTERFACE_CAPABILITY_MISC_EOTP_SHORT_PKT_OPT2 BIT(0) +#define CCS_R_PHY_CTRL_CAPABILITY_3 CCI_REG8(0x165c) +#define CCS_PHY_CTRL_CAPABILITY_3_DPHY_TIMING_NOT_MULTIPLE BIT(0) +#define CCS_PHY_CTRL_CAPABILITY_3_DPHY_MIN_TIMING_VALUE_1 BIT(1) +#define CCS_PHY_CTRL_CAPABILITY_3_TWAKEUP_SUPPORTED BIT(2) +#define CCS_PHY_CTRL_CAPABILITY_3_TINIT_SUPPORTED BIT(3) +#define CCS_PHY_CTRL_CAPABILITY_3_THS_EXIT_SUPPORTED BIT(4) +#define CCS_PHY_CTRL_CAPABILITY_3_CPHY_TIMING_NOT_MULTIPLE BIT(5) +#define CCS_PHY_CTRL_CAPABILITY_3_CPHY_MIN_TIMING_VALUE_1 BIT(6) +#define CCS_R_DPHY_SF CCI_REG8(0x165d) +#define CCS_R_CPHY_SF CCI_REG8(0x165e) +#define CCS_CPHY_SF_TWAKEUP_SHIFT 0U +#define CCS_CPHY_SF_TWAKEUP_MASK 0xf +#define CCS_CPHY_SF_TINIT_SHIFT 4U +#define CCS_CPHY_SF_TINIT_MASK 0xf0 +#define CCS_R_DPHY_LIMITS_1 CCI_REG8(0x165f) +#define CCS_DPHY_LIMITS_1_THS_PREPARE_SHIFT 0U +#define CCS_DPHY_LIMITS_1_THS_PREPARE_MASK 0xf +#define CCS_DPHY_LIMITS_1_THS_ZERO_SHIFT 4U +#define CCS_DPHY_LIMITS_1_THS_ZERO_MASK 0xf0 +#define CCS_R_DPHY_LIMITS_2 CCI_REG8(0x1660) +#define CCS_DPHY_LIMITS_2_THS_TRAIL_SHIFT 0U +#define CCS_DPHY_LIMITS_2_THS_TRAIL_MASK 0xf +#define CCS_DPHY_LIMITS_2_TCLK_TRAIL_MIN_SHIFT 4U +#define CCS_DPHY_LIMITS_2_TCLK_TRAIL_MIN_MASK 0xf0 +#define CCS_R_DPHY_LIMITS_3 CCI_REG8(0x1661) +#define CCS_DPHY_LIMITS_3_TCLK_PREPARE_SHIFT 0U +#define CCS_DPHY_LIMITS_3_TCLK_PREPARE_MASK 0xf +#define CCS_DPHY_LIMITS_3_TCLK_ZERO_SHIFT 4U +#define CCS_DPHY_LIMITS_3_TCLK_ZERO_MASK 0xf0 +#define CCS_R_DPHY_LIMITS_4 CCI_REG8(0x1662) +#define CCS_DPHY_LIMITS_4_TCLK_POST_SHIFT 0U +#define CCS_DPHY_LIMITS_4_TCLK_POST_MASK 0xf +#define CCS_DPHY_LIMITS_4_TLPX_SHIFT 4U +#define CCS_DPHY_LIMITS_4_TLPX_MASK 0xf0 +#define CCS_R_DPHY_LIMITS_5 CCI_REG8(0x1663) +#define CCS_DPHY_LIMITS_5_THS_EXIT_SHIFT 0U +#define CCS_DPHY_LIMITS_5_THS_EXIT_MASK 0xf +#define CCS_DPHY_LIMITS_5_TWAKEUP_SHIFT 4U +#define CCS_DPHY_LIMITS_5_TWAKEUP_MASK 0xf0 +#define CCS_R_DPHY_LIMITS_6 CCI_REG8(0x1664) +#define CCS_DPHY_LIMITS_6_TINIT_SHIFT 0U +#define CCS_DPHY_LIMITS_6_TINIT_MASK 0xf +#define CCS_R_CPHY_LIMITS_1 CCI_REG8(0x1665) +#define CCS_CPHY_LIMITS_1_T3_PREPARE_MAX_SHIFT 0U +#define CCS_CPHY_LIMITS_1_T3_PREPARE_MAX_MASK 0xf +#define CCS_CPHY_LIMITS_1_T3_LPX_MAX_SHIFT 4U +#define CCS_CPHY_LIMITS_1_T3_LPX_MAX_MASK 0xf0 +#define CCS_R_CPHY_LIMITS_2 CCI_REG8(0x1666) +#define CCS_CPHY_LIMITS_2_THS_EXIT_MAX_SHIFT 0U +#define CCS_CPHY_LIMITS_2_THS_EXIT_MAX_MASK 0xf +#define CCS_CPHY_LIMITS_2_TWAKEUP_MAX_SHIFT 4U +#define CCS_CPHY_LIMITS_2_TWAKEUP_MAX_MASK 0xf0 +#define CCS_R_CPHY_LIMITS_3 CCI_REG8(0x1667) +#define CCS_CPHY_LIMITS_3_TINIT_MAX_SHIFT 0U +#define CCS_CPHY_LIMITS_3_TINIT_MAX_MASK 0xf +#define CCS_R_MIN_FRAME_LENGTH_LINES_BIN CCI_REG16(0x1700) +#define CCS_R_MAX_FRAME_LENGTH_LINES_BIN CCI_REG16(0x1702) +#define CCS_R_MIN_LINE_LENGTH_PCK_BIN CCI_REG16(0x1704) +#define CCS_R_MAX_LINE_LENGTH_PCK_BIN CCI_REG16(0x1706) +#define CCS_R_MIN_LINE_BLANKING_PCK_BIN CCI_REG16(0x1708) +#define CCS_R_FINE_INTEGRATION_TIME_MIN_BIN CCI_REG16(0x170a) +#define CCS_R_FINE_INTEGRATION_TIME_MAX_MARGIN_BIN CCI_REG16(0x170c) +#define CCS_R_BINNING_CAPABILITY CCI_REG8(0x1710) +#define CCS_BINNING_CAPABILITY_UNSUPPORTED 0U +#define CCS_BINNING_CAPABILITY_BINNING_THEN_SUBSAMPLING 1U +#define CCS_BINNING_CAPABILITY_SUBSAMPLING_THEN_BINNING 2U +#define CCS_R_BINNING_WEIGHTING_CAPABILITY CCI_REG8(0x1711) +#define CCS_BINNING_WEIGHTING_CAPABILITY_AVERAGED BIT(0) +#define CCS_BINNING_WEIGHTING_CAPABILITY_SUMMED BIT(1) +#define CCS_BINNING_WEIGHTING_CAPABILITY_BAYER_CORRECTED BIT(2) +#define CCS_BINNING_WEIGHTING_CAPABILITY_MODULE_SPECIFIC_WEIGHT BIT(3) +#define CCS_R_BINNING_SUB_TYPES CCI_REG8(0x1712) +#define CCS_R_BINNING_SUB_TYPE(n) CCI_REG8(0x1713 + (n)) +#define CCS_LIM_BINNING_SUB_TYPE_MIN_N 0U +#define CCS_LIM_BINNING_SUB_TYPE_MAX_N 63U +#define CCS_BINNING_SUB_TYPE_ROW_SHIFT 0U +#define CCS_BINNING_SUB_TYPE_ROW_MASK 0xf +#define CCS_BINNING_SUB_TYPE_COLUMN_SHIFT 4U +#define CCS_BINNING_SUB_TYPE_COLUMN_MASK 0xf0 +#define CCS_R_BINNING_WEIGHTING_MONO_CAPABILITY CCI_REG8(0x1771) +#define CCS_BINNING_WEIGHTING_MONO_CAPABILITY_AVERAGED BIT(0) +#define CCS_BINNING_WEIGHTING_MONO_CAPABILITY_SUMMED BIT(1) +#define CCS_BINNING_WEIGHTING_MONO_CAPABILITY_BAYER_CORRECTED BIT(2) +#define CCS_BINNING_WEIGHTING_MONO_CAPABILITY_MODULE_SPECIFIC_WEIGHT BIT(3) +#define CCS_R_BINNING_SUB_TYPES_MONO CCI_REG8(0x1772) +#define CCS_R_BINNING_SUB_TYPE_MONO(n) CCI_REG8(0x1773 + (n)) +#define CCS_LIM_BINNING_SUB_TYPE_MONO_MIN_N 0U +#define CCS_LIM_BINNING_SUB_TYPE_MONO_MAX_N 63U +#define CCS_R_DATA_TRANSFER_IF_CAPABILITY CCI_REG8(0x1800) +#define CCS_DATA_TRANSFER_IF_CAPABILITY_SUPPORTED BIT(0) +#define CCS_DATA_TRANSFER_IF_CAPABILITY_POLLING BIT(2) +#define CCS_R_SHADING_CORRECTION_CAPABILITY CCI_REG8(0x1900) +#define CCS_SHADING_CORRECTION_CAPABILITY_COLOR_SHADING BIT(0) +#define CCS_SHADING_CORRECTION_CAPABILITY_LUMINANCE_CORRECTION BIT(1) +#define CCS_R_GREEN_IMBALANCE_CAPABILITY CCI_REG8(0x1901) +#define CCS_GREEN_IMBALANCE_CAPABILITY_SUPPORTED BIT(0) +#define CCS_R_MODULE_SPECIFIC_CORRECTION_CAPABILITY CCI_REG8(0x1903) +#define CCS_R_DEFECT_CORRECTION_CAPABILITY CCI_REG16(0x1904) +#define CCS_DEFECT_CORRECTION_CAPABILITY_MAPPED_DEFECT BIT(0) +#define CCS_DEFECT_CORRECTION_CAPABILITY_DYNAMIC_COUPLET BIT(2) +#define CCS_DEFECT_CORRECTION_CAPABILITY_DYNAMIC_SINGLE BIT(5) +#define CCS_DEFECT_CORRECTION_CAPABILITY_COMBINED_DYNAMIC BIT(8) +#define CCS_R_DEFECT_CORRECTION_CAPABILITY_2 CCI_REG16(0x1906) +#define CCS_DEFECT_CORRECTION_CAPABILITY_2_DYNAMIC_TRIPLET BIT(3) +#define CCS_R_NF_CAPABILITY CCI_REG8(0x1908) +#define CCS_NF_CAPABILITY_LUMA BIT(0) +#define CCS_NF_CAPABILITY_CHROMA BIT(1) +#define CCS_NF_CAPABILITY_COMBINED BIT(2) +#define CCS_R_OB_READOUT_CAPABILITY CCI_REG8(0x1980) +#define CCS_OB_READOUT_CAPABILITY_CONTROLLABLE_READOUT BIT(0) +#define CCS_OB_READOUT_CAPABILITY_VISIBLE_PIXEL_READOUT BIT(1) +#define CCS_OB_READOUT_CAPABILITY_DIFFERENT_VC_READOUT BIT(2) +#define CCS_OB_READOUT_CAPABILITY_DIFFERENT_DT_READOUT BIT(3) +#define CCS_OB_READOUT_CAPABILITY_PROG_DATA_FORMAT BIT(4) +#define CCS_R_COLOR_FEEDBACK_CAPABILITY CCI_REG8(0x1987) +#define CCS_COLOR_FEEDBACK_CAPABILITY_KELVIN BIT(0) +#define CCS_COLOR_FEEDBACK_CAPABILITY_AWB_GAIN BIT(1) +#define CCS_R_CFA_PATTERN_CAPABILITY CCI_REG8(0x1990) +#define CCS_CFA_PATTERN_CAPABILITY_BAYER 0U +#define CCS_CFA_PATTERN_CAPABILITY_MONOCHROME 1U +#define CCS_CFA_PATTERN_CAPABILITY_4X4_QUAD_BAYER 2U +#define CCS_CFA_PATTERN_CAPABILITY_VENDOR_SPECIFIC 3U +#define CCS_R_CFA_PATTERN_CONVERSION_CAPABILITY CCI_REG8(0x1991) +#define CCS_CFA_PATTERN_CONVERSION_CAPABILITY_BAYER BIT(0) +#define CCS_R_FLASH_MODE_CAPABILITY CCI_REG8(0x1a02) +#define CCS_FLASH_MODE_CAPABILITY_SINGLE_STROBE BIT(0) +#define CCS_R_SA_STROBE_MODE_CAPABILITY CCI_REG8(0x1a03) +#define CCS_SA_STROBE_MODE_CAPABILITY_FIXED_WIDTH BIT(0) +#define CCS_SA_STROBE_MODE_CAPABILITY_EDGE_CTRL BIT(1) +#define CCS_R_RESET_MAX_DELAY CCI_REG8(0x1a10) +#define CCS_R_RESET_MIN_TIME CCI_REG8(0x1a11) +#define CCS_R_PDAF_CAPABILITY_1 CCI_REG8(0x1b80) +#define CCS_PDAF_CAPABILITY_1_SUPPORTED BIT(0) +#define CCS_PDAF_CAPABILITY_1_PROCESSED_BOTTOM_EMBEDDED BIT(1) +#define CCS_PDAF_CAPABILITY_1_PROCESSED_INTERLEAVED BIT(2) +#define CCS_PDAF_CAPABILITY_1_RAW_BOTTOM_EMBEDDED BIT(3) +#define CCS_PDAF_CAPABILITY_1_RAW_INTERLEAVED BIT(4) +#define CCS_PDAF_CAPABILITY_1_VISIBLE_PDAF_CORRECTION BIT(5) +#define CCS_PDAF_CAPABILITY_1_VC_INTERLEAVING BIT(6) +#define CCS_PDAF_CAPABILITY_1_DT_INTERLEAVING BIT(7) +#define CCS_R_PDAF_CAPABILITY_2 CCI_REG8(0x1b81) +#define CCS_PDAF_CAPABILITY_2_ROI BIT(0) +#define CCS_PDAF_CAPABILITY_2_AFTER_DIGITAL_CROP BIT(1) +#define CCS_PDAF_CAPABILITY_2_CTRL_RETIMED BIT(2) +#define CCS_R_BRACKETING_LUT_CAPABILITY_1 CCI_REG8(0x1c00) +#define CCS_BRACKETING_LUT_CAPABILITY_1_COARSE_INTEGRATION BIT(0) +#define CCS_BRACKETING_LUT_CAPABILITY_1_GLOBAL_ANALOG_GAIN BIT(1) +#define CCS_BRACKETING_LUT_CAPABILITY_1_FLASH BIT(4) +#define CCS_BRACKETING_LUT_CAPABILITY_1_GLOBAL_DIGITAL_GAIN BIT(5) +#define CCS_BRACKETING_LUT_CAPABILITY_1_ALTERNATE_GLOBAL_ANALOG_GAIN BIT(6) +#define CCS_R_BRACKETING_LUT_CAPABILITY_2 CCI_REG8(0x1c01) +#define CCS_BRACKETING_LUT_CAPABILITY_2_SINGLE_BRACKETING_MODE BIT(0) +#define CCS_BRACKETING_LUT_CAPABILITY_2_LOOPED_BRACKETING_MODE BIT(1) +#define CCS_R_BRACKETING_LUT_SIZE CCI_REG8(0x1c02) + +#endif /* __CCS_REGS_H__ */ diff --git a/drivers/media/i2c/ccs/ccs.h b/drivers/media/i2c/ccs/ccs.h new file mode 100644 index 000000000..58952c5bf --- /dev/null +++ b/drivers/media/i2c/ccs/ccs.h @@ -0,0 +1,276 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/i2c/smiapp/ccs.h + * + * Generic driver for MIPI CCS/SMIA/SMIA++ compliant camera sensors + * + * Copyright (C) 2020 Intel Corporation + * Copyright (C) 2010--2012 Nokia Corporation + * Contact: Sakari Ailus + */ + +#ifndef __CCS_H__ +#define __CCS_H__ + +#include +#include +#include +#include + +#include "ccs-data.h" +#include "ccs-limits.h" +#include "ccs-quirk.h" +#include "ccs-regs.h" +#include "ccs-reg-access.h" +#include "../ccs-pll.h" +#include "smiapp-reg-defs.h" + +/* + * Standard SMIA++ constants + */ +#define SMIA_VERSION_1 10 +#define SMIAPP_VERSION_0_8 8 /* Draft 0.8 */ +#define SMIAPP_VERSION_0_9 9 /* Draft 0.9 */ +#define SMIAPP_VERSION_1 10 + +#define SMIAPP_PROFILE_0 0 +#define SMIAPP_PROFILE_1 1 +#define SMIAPP_PROFILE_2 2 + +#define SMIAPP_NVM_PAGE_SIZE 64 /* bytes */ + +#define SMIAPP_RESET_DELAY_CLOCKS 2400 +#define SMIAPP_RESET_DELAY(clk) \ + (1000 + (SMIAPP_RESET_DELAY_CLOCKS * 1000 \ + + (clk) / 1000 - 1) / ((clk) / 1000)) +#define CCS_RESET_DELAY_US 5000 +#define CCS_RESET_TIMEOUT_US 1000000 + +#define CCS_COLOUR_COMPONENTS 4 + +#define CCS_DEFAULT_COMPRESSED_DT MIPI_CSI2_DT_USER_DEFINED(0) + +#define SMIAPP_NAME "smiapp" +#define CCS_NAME "ccs" + +#define CCS_DFL_I2C_ADDR (0x20 >> 1) /* Default I2C Address */ +#define CCS_ALT_I2C_ADDR (0x6e >> 1) /* Alternate I2C Address */ + +#define CCS_LIM(sensor, limit) \ + ccs_get_limit(sensor, CCS_L_##limit, 0) + +#define CCS_LIM_AT(sensor, limit, offset) \ + ccs_get_limit(sensor, CCS_L_##limit, CCS_L_##limit##_OFFSET(offset)) + +struct ccs_flash_strobe_parms { + u8 mode; + u32 strobe_width_high_us; + u16 strobe_delay; + u16 stobe_start_point; + u8 trigger; +}; + +struct ccs_hwconfig { + /* + * Change the cci address if i2c_addr_alt is set. + * Both default and alternate cci addr need to be present + */ + unsigned short i2c_addr_dfl; /* Default i2c addr */ + unsigned short i2c_addr_alt; /* Alternate i2c addr */ + + u32 ext_clk; /* sensor external clk */ + + unsigned int lanes; /* Number of CSI-2 lanes */ + u32 csi_signalling_mode; /* CCS_CSI_SIGNALLING_MODE_* */ + u64 *op_sys_clock; + + struct ccs_flash_strobe_parms *strobe_setup; +}; + +struct ccs_quirk; + +#define CCS_MODULE_IDENT_FLAG_REV_LE (1 << 0) + +struct ccs_module_ident { + u16 mipi_manufacturer_id; + u16 model_id; + u8 smia_manufacturer_id; + u8 revision_number_major; + + u8 flags; + + char *name; + const struct ccs_quirk *quirk; +}; + +struct ccs_module_info { + u32 smia_manufacturer_id; + u32 mipi_manufacturer_id; + u32 model_id; + u32 revision_number; + + u32 module_year; + u32 module_month; + u32 module_day; + + u32 sensor_smia_manufacturer_id; + u32 sensor_mipi_manufacturer_id; + u32 sensor_model_id; + u32 sensor_revision_number; + u32 sensor_firmware_version; + + u32 smia_version; + u32 smiapp_version; + u32 ccs_version; + + char *name; + const struct ccs_quirk *quirk; +}; + +#define CCS_IDENT_FQ(manufacturer, model, rev, fl, _name, _quirk) \ + { .smia_manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = fl, \ + .name = _name, \ + .quirk = _quirk, } + +#define CCS_IDENT_LQ(manufacturer, model, rev, _name, _quirk) \ + { .smia_manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = CCS_MODULE_IDENT_FLAG_REV_LE, \ + .name = _name, \ + .quirk = _quirk, } + +#define CCS_IDENT_L(manufacturer, model, rev, _name) \ + { .smia_manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = CCS_MODULE_IDENT_FLAG_REV_LE, \ + .name = _name, } + +#define CCS_IDENT_Q(manufacturer, model, rev, _name, _quirk) \ + { .smia_manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = 0, \ + .name = _name, \ + .quirk = _quirk, } + +#define CCS_IDENT(manufacturer, model, rev, _name) \ + { .smia_manufacturer_id = manufacturer, \ + .model_id = model, \ + .revision_number_major = rev, \ + .flags = 0, \ + .name = _name, } + +struct ccs_csi_data_format { + u32 code; + u8 width; + u8 compressed; + u8 pixel_order; +}; + +#define CCS_SUBDEVS 3 + +#define CCS_PA_PAD_SRC 0 +#define CCS_PAD_SINK 0 +#define CCS_PAD_SRC 1 +#define CCS_PADS 2 + +#define CCS_STREAM_PIXEL 0 + +struct ccs_binning_subtype { + u8 horizontal:4; + u8 vertical:4; +} __packed; + +struct ccs_subdev { + struct v4l2_subdev sd; + struct media_pad pads[CCS_PADS]; + unsigned short sink_pad; + unsigned short source_pad; + int npads; + struct ccs_sensor *sensor; + struct v4l2_ctrl_handler ctrl_handler; +}; + +/* + * struct ccs_sensor - Main device structure + */ +struct ccs_sensor { + /* + * "mutex" is used to serialise access to all fields here + * except v4l2_ctrls at the end of the struct. "mutex" is also + * used to serialise access to file handle specific + * information. + */ + struct mutex mutex; + struct ccs_subdev ssds[CCS_SUBDEVS]; + u32 ssds_used; + struct ccs_subdev *src; + struct ccs_subdev *binner; + struct ccs_subdev *scaler; + struct ccs_subdev *pixel_array; + struct ccs_hwconfig hwcfg; + struct regulator_bulk_data *regulators; + struct clk *ext_clk; + struct gpio_desc *xshutdown; + struct gpio_desc *reset; + struct regmap *regmap; + void *ccs_limits; + u8 nbinning_subtypes; + struct ccs_binning_subtype binning_subtypes[CCS_LIM_BINNING_SUB_TYPE_MAX_N + 1]; + u64 mbus_frame_fmts; + const struct ccs_csi_data_format *csi_format; + const struct ccs_csi_data_format *internal_csi_format; + u64 default_mbus_frame_fmts; + int default_pixel_order; + struct ccs_data_container sdata, mdata; + + u8 frame_skip; + u16 embedded_start; /* embedded data start line */ + u16 embedded_end; + u16 image_start; /* image data start line */ + u16 visible_pixel_start; /* start pixel of the visible image */ + + u8 streaming; + bool dev_init_done; + bool handler_setup_needed; + u8 compressed_min_bpp; + + struct ccs_module_info minfo; + + struct ccs_pll pll; + + /* Is a default format supported for a given BPP? */ + unsigned long *valid_link_freqs; + + /* Pixel array controls */ + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *pixel_rate_parray; + struct v4l2_ctrl *luminance_level; + /* src controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate_csi; + /* test pattern colour components */ + struct v4l2_ctrl *test_data[CCS_COLOUR_COMPONENTS]; +}; + +#define to_ccs_subdev(_sd) \ + container_of(_sd, struct ccs_subdev, sd) + +#define to_ccs_sensor(_sd) \ + (to_ccs_subdev(_sd)->sensor) + +void ccs_replace_limit(struct ccs_sensor *sensor, + unsigned int limit, unsigned int offset, u32 val); +u32 ccs_get_limit(struct ccs_sensor *sensor, unsigned int limit, + unsigned int offset); + +#endif /* __CCS_H__ */ diff --git a/drivers/media/i2c/ccs/smiapp-reg-defs.h b/drivers/media/i2c/ccs/smiapp-reg-defs.h new file mode 100644 index 000000000..ebd0f90e1 --- /dev/null +++ b/drivers/media/i2c/ccs/smiapp-reg-defs.h @@ -0,0 +1,585 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/i2c/smiapp/smiapp-reg-defs.h + * + * Generic driver for MIPI CCS/SMIA/SMIA++ compliant camera sensors + * + * Copyright (C) 2020 Intel Corporation + * Copyright (C) 2011--2012 Nokia Corporation + * Contact: Sakari Ailus + */ + +#ifndef __SMIAPP_REG_DEFS_H__ +#define __SMIAPP_REG_DEFS_H__ + +#include +#include + +/* Register addresses */ +#define SMIAPP_REG_U16_MODEL_ID CCI_REG16(0x0000) +#define SMIAPP_REG_U8_REVISION_NUMBER_MAJOR CCI_REG8(0x0002) +#define SMIAPP_REG_U8_MANUFACTURER_ID CCI_REG8(0x0003) +#define SMIAPP_REG_U8_SMIA_VERSION CCI_REG8(0x0004) +#define SMIAPP_REG_U8_FRAME_COUNT CCI_REG8(0x0005) +#define SMIAPP_REG_U8_PIXEL_ORDER CCI_REG8(0x0006) +#define SMIAPP_REG_U16_DATA_PEDESTAL CCI_REG16(0x0008) +#define SMIAPP_REG_U8_PIXEL_DEPTH CCI_REG8(0x000c) +#define SMIAPP_REG_U8_REVISION_NUMBER_MINOR CCI_REG8(0x0010) +#define SMIAPP_REG_U8_SMIAPP_VERSION CCI_REG8(0x0011) +#define SMIAPP_REG_U8_MODULE_DATE_YEAR CCI_REG8(0x0012) +#define SMIAPP_REG_U8_MODULE_DATE_MONTH CCI_REG8(0x0013) +#define SMIAPP_REG_U8_MODULE_DATE_DAY CCI_REG8(0x0014) +#define SMIAPP_REG_U8_MODULE_DATE_PHASE CCI_REG8(0x0015) +#define SMIAPP_REG_U16_SENSOR_MODEL_ID CCI_REG16(0x0016) +#define SMIAPP_REG_U8_SENSOR_REVISION_NUMBER CCI_REG8(0x0018) +#define SMIAPP_REG_U8_SENSOR_MANUFACTURER_ID CCI_REG8(0x0019) +#define SMIAPP_REG_U8_SENSOR_FIRMWARE_VERSION CCI_REG8(0x001a) +#define SMIAPP_REG_U32_SERIAL_NUMBER CCI_REG32(0x001c) +#define SMIAPP_REG_U8_FRAME_FORMAT_MODEL_TYPE CCI_REG8(0x0040) +#define SMIAPP_REG_U8_FRAME_FORMAT_MODEL_SUBTYPE CCI_REG8(0x0041) +#define SMIAPP_REG_U16_FRAME_FORMAT_DESCRIPTOR_2(n) CCI_REG16(0x0042 + ((n) << 1)) /* 0 <= n <= 14 */ +#define SMIAPP_REG_U32_FRAME_FORMAT_DESCRIPTOR_4(n) CCI_REG32(0x0060 + ((n) << 2)) /* 0 <= n <= 7 */ +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CAPABILITY CCI_REG16(0x0080) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_MIN CCI_REG16(0x0084) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_MAX CCI_REG16(0x0086) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_STEP CCI_REG16(0x0088) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_TYPE CCI_REG16(0x008a) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_M0 CCI_REG16(0x008c) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_C0 CCI_REG16(0x008e) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_M1 CCI_REG16(0x0090) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_C1 CCI_REG16(0x0092) +#define SMIAPP_REG_U8_DATA_FORMAT_MODEL_TYPE CCI_REG8(0x00c0) +#define SMIAPP_REG_U8_DATA_FORMAT_MODEL_SUBTYPE CCI_REG8(0x00c1) +#define SMIAPP_REG_U16_DATA_FORMAT_DESCRIPTOR(n) CCI_REG16(0x00c2 + ((n) << 1)) +#define SMIAPP_REG_U8_MODE_SELECT CCI_REG8(0x0100) +#define SMIAPP_REG_U8_IMAGE_ORIENTATION CCI_REG8(0x0101) +#define SMIAPP_REG_U8_SOFTWARE_RESET CCI_REG8(0x0103) +#define SMIAPP_REG_U8_GROUPED_PARAMETER_HOLD CCI_REG8(0x0104) +#define SMIAPP_REG_U8_MASK_CORRUPTED_FRAMES CCI_REG8(0x0105) +#define SMIAPP_REG_U8_FAST_STANDBY_CTRL CCI_REG8(0x0106) +#define SMIAPP_REG_U8_CCI_ADDRESS_CONTROL CCI_REG8(0x0107) +#define SMIAPP_REG_U8_2ND_CCI_IF_CONTROL CCI_REG8(0x0108) +#define SMIAPP_REG_U8_2ND_CCI_ADDRESS_CONTROL CCI_REG8(0x0109) +#define SMIAPP_REG_U8_CSI_CHANNEL_IDENTIFIER CCI_REG8(0x0110) +#define SMIAPP_REG_U8_CSI_SIGNALLING_MODE CCI_REG8(0x0111) +#define SMIAPP_REG_U16_CSI_DATA_FORMAT CCI_REG16(0x0112) +#define SMIAPP_REG_U8_CSI_LANE_MODE CCI_REG8(0x0114) +#define SMIAPP_REG_U8_CSI2_10_TO_8_DT CCI_REG8(0x0115) +#define SMIAPP_REG_U8_CSI2_10_TO_7_DT CCI_REG8(0x0116) +#define SMIAPP_REG_U8_CSI2_10_TO_6_DT CCI_REG8(0x0117) +#define SMIAPP_REG_U8_CSI2_12_TO_8_DT CCI_REG8(0x0118) +#define SMIAPP_REG_U8_CSI2_12_TO_7_DT CCI_REG8(0x0119) +#define SMIAPP_REG_U8_CSI2_12_TO_6_DT CCI_REG8(0x011a) +#define SMIAPP_REG_U8_CSI2_14_TO_10_DT CCI_REG8(0x011b) +#define SMIAPP_REG_U8_CSI2_14_TO_8_DT CCI_REG8(0x011c) +#define SMIAPP_REG_U8_CSI2_16_TO_10_DT CCI_REG8(0x011d) +#define SMIAPP_REG_U8_CSI2_16_TO_8_DT CCI_REG8(0x011e) +#define SMIAPP_REG_U8_GAIN_MODE CCI_REG8(0x0120) +#define SMIAPP_REG_U16_VANA_VOLTAGE CCI_REG16(0x0130) +#define SMIAPP_REG_U16_VDIG_VOLTAGE CCI_REG16(0x0132) +#define SMIAPP_REG_U16_VIO_VOLTAGE CCI_REG16(0x0134) +#define SMIAPP_REG_U16_EXTCLK_FREQUENCY_MHZ CCI_REG16(0x0136) +#define SMIAPP_REG_U8_TEMP_SENSOR_CONTROL CCI_REG8(0x0138) +#define SMIAPP_REG_U8_TEMP_SENSOR_MODE CCI_REG8(0x0139) +#define SMIAPP_REG_U8_TEMP_SENSOR_OUTPUT CCI_REG8(0x013a) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME CCI_REG16(0x0200) +#define SMIAPP_REG_U16_COARSE_INTEGRATION_TIME CCI_REG16(0x0202) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_GLOBAL CCI_REG16(0x0204) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_GREENR CCI_REG16(0x0206) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_RED CCI_REG16(0x0208) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_BLUE CCI_REG16(0x020a) +#define SMIAPP_REG_U16_ANALOGUE_GAIN_CODE_GREENB CCI_REG16(0x020c) +#define SMIAPP_REG_U16_DIGITAL_GAIN_GREENR CCI_REG16(0x020e) +#define SMIAPP_REG_U16_DIGITAL_GAIN_RED CCI_REG16(0x0210) +#define SMIAPP_REG_U16_DIGITAL_GAIN_BLUE CCI_REG16(0x0212) +#define SMIAPP_REG_U16_DIGITAL_GAIN_GREENB CCI_REG16(0x0214) +#define SMIAPP_REG_U16_VT_PIX_CLK_DIV CCI_REG16(0x0300) +#define SMIAPP_REG_U16_VT_SYS_CLK_DIV CCI_REG16(0x0302) +#define SMIAPP_REG_U16_PRE_PLL_CLK_DIV CCI_REG16(0x0304) +#define SMIAPP_REG_U16_PLL_MULTIPLIER CCI_REG16(0x0306) +#define SMIAPP_REG_U16_OP_PIX_CLK_DIV CCI_REG16(0x0308) +#define SMIAPP_REG_U16_OP_SYS_CLK_DIV CCI_REG16(0x030a) +#define SMIAPP_REG_U16_FRAME_LENGTH_LINES CCI_REG16(0x0340) +#define SMIAPP_REG_U16_LINE_LENGTH_PCK CCI_REG16(0x0342) +#define SMIAPP_REG_U16_X_ADDR_START CCI_REG16(0x0344) +#define SMIAPP_REG_U16_Y_ADDR_START CCI_REG16(0x0346) +#define SMIAPP_REG_U16_X_ADDR_END CCI_REG16(0x0348) +#define SMIAPP_REG_U16_Y_ADDR_END CCI_REG16(0x034a) +#define SMIAPP_REG_U16_X_OUTPUT_SIZE CCI_REG16(0x034c) +#define SMIAPP_REG_U16_Y_OUTPUT_SIZE CCI_REG16(0x034e) +#define SMIAPP_REG_U16_X_EVEN_INC CCI_REG16(0x0380) +#define SMIAPP_REG_U16_X_ODD_INC CCI_REG16(0x0382) +#define SMIAPP_REG_U16_Y_EVEN_INC CCI_REG16(0x0384) +#define SMIAPP_REG_U16_Y_ODD_INC CCI_REG16(0x0386) +#define SMIAPP_REG_U16_SCALING_MODE CCI_REG16(0x0400) +#define SMIAPP_REG_U16_SPATIAL_SAMPLING CCI_REG16(0x0402) +#define SMIAPP_REG_U16_SCALE_M CCI_REG16(0x0404) +#define SMIAPP_REG_U16_SCALE_N CCI_REG16(0x0406) +#define SMIAPP_REG_U16_DIGITAL_CROP_X_OFFSET CCI_REG16(0x0408) +#define SMIAPP_REG_U16_DIGITAL_CROP_Y_OFFSET CCI_REG16(0x040a) +#define SMIAPP_REG_U16_DIGITAL_CROP_IMAGE_WIDTH CCI_REG16(0x040c) +#define SMIAPP_REG_U16_DIGITAL_CROP_IMAGE_HEIGHT CCI_REG16(0x040e) +#define SMIAPP_REG_U16_COMPRESSION_MODE CCI_REG16(0x0500) +#define SMIAPP_REG_U16_TEST_PATTERN_MODE CCI_REG16(0x0600) +#define SMIAPP_REG_U16_TEST_DATA_RED CCI_REG16(0x0602) +#define SMIAPP_REG_U16_TEST_DATA_GREENR CCI_REG16(0x0604) +#define SMIAPP_REG_U16_TEST_DATA_BLUE CCI_REG16(0x0606) +#define SMIAPP_REG_U16_TEST_DATA_GREENB CCI_REG16(0x0608) +#define SMIAPP_REG_U16_HORIZONTAL_CURSOR_WIDTH CCI_REG16(0x060a) +#define SMIAPP_REG_U16_HORIZONTAL_CURSOR_POSITION CCI_REG16(0x060c) +#define SMIAPP_REG_U16_VERTICAL_CURSOR_WIDTH CCI_REG16(0x060e) +#define SMIAPP_REG_U16_VERTICAL_CURSOR_POSITION CCI_REG16(0x0610) +#define SMIAPP_REG_U16_FIFO_WATER_MARK_PIXELS CCI_REG16(0x0700) +#define SMIAPP_REG_U8_TCLK_POST CCI_REG8(0x0800) +#define SMIAPP_REG_U8_THS_PREPARE CCI_REG8(0x0801) +#define SMIAPP_REG_U8_THS_ZERO_MIN CCI_REG8(0x0802) +#define SMIAPP_REG_U8_THS_TRAIL CCI_REG8(0x0803) +#define SMIAPP_REG_U8_TCLK_TRAIL_MIN CCI_REG8(0x0804) +#define SMIAPP_REG_U8_TCLK_PREPARE CCI_REG8(0x0805) +#define SMIAPP_REG_U8_TCLK_ZERO CCI_REG8(0x0806) +#define SMIAPP_REG_U8_TLPX CCI_REG8(0x0807) +#define SMIAPP_REG_U8_DPHY_CTRL CCI_REG8(0x0808) +#define SMIAPP_REG_U32_REQUESTED_LINK_BIT_RATE_MBPS CCI_REG32(0x0820) +#define SMIAPP_REG_U8_BINNING_MODE CCI_REG8(0x0900) +#define SMIAPP_REG_U8_BINNING_TYPE CCI_REG8(0x0901) +#define SMIAPP_REG_U8_BINNING_WEIGHTING CCI_REG8(0x0902) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_CTRL CCI_REG8(0x0a00) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_STATUS CCI_REG8(0x0a01) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_PAGE_SELECT CCI_REG8(0x0a02) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_0 CCI_REG8(0x0a04) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_1 CCI_REG8(0x0a05) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_2 CCI_REG8(0x0a06) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_3 CCI_REG8(0x0a07) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_4 CCI_REG8(0x0a08) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_5 CCI_REG8(0x0a09) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_12 CCI_REG8(0x0a10) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_13 CCI_REG8(0x0a11) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_14 CCI_REG8(0x0a12) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_15 CCI_REG8(0x0a13) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_16 CCI_REG8(0x0a14) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_17 CCI_REG8(0x0a15) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_18 CCI_REG8(0x0a16) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_19 CCI_REG8(0x0a17) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_20 CCI_REG8(0x0a18) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_21 CCI_REG8(0x0a19) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_22 CCI_REG8(0x0a1a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_23 CCI_REG8(0x0a1b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_24 CCI_REG8(0x0a1c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_25 CCI_REG8(0x0a1d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_26 CCI_REG8(0x0a1e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_27 CCI_REG8(0x0a1f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_28 CCI_REG8(0x0a20) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_29 CCI_REG8(0x0a21) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_30 CCI_REG8(0x0a22) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_31 CCI_REG8(0x0a23) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_32 CCI_REG8(0x0a24) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_33 CCI_REG8(0x0a25) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_34 CCI_REG8(0x0a26) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_35 CCI_REG8(0x0a27) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_36 CCI_REG8(0x0a28) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_37 CCI_REG8(0x0a29) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_38 CCI_REG8(0x0a2a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_39 CCI_REG8(0x0a2b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_40 CCI_REG8(0x0a2c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_41 CCI_REG8(0x0a2d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_42 CCI_REG8(0x0a2e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_43 CCI_REG8(0x0a2f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_44 CCI_REG8(0x0a30) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_45 CCI_REG8(0x0a31) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_46 CCI_REG8(0x0a32) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_47 CCI_REG8(0x0a33) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_48 CCI_REG8(0x0a34) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_49 CCI_REG8(0x0a35) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_50 CCI_REG8(0x0a36) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_51 CCI_REG8(0x0a37) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_52 CCI_REG8(0x0a38) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_53 CCI_REG8(0x0a39) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_54 CCI_REG8(0x0a3a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_55 CCI_REG8(0x0a3b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_56 CCI_REG8(0x0a3c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_57 CCI_REG8(0x0a3d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_58 CCI_REG8(0x0a3e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_59 CCI_REG8(0x0a3f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_60 CCI_REG8(0x0a40) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_61 CCI_REG8(0x0a41) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_62 CCI_REG8(0x0a42) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_1_DATA_63 CCI_REG8(0x0a43) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_CTRL CCI_REG8(0x0a44) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_STATUS CCI_REG8(0x0a45) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_PAGE_SELECT CCI_REG8(0x0a46) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_0 CCI_REG8(0x0a48) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_1 CCI_REG8(0x0a49) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_2 CCI_REG8(0x0a4a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_3 CCI_REG8(0x0a4b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_4 CCI_REG8(0x0a4c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_5 CCI_REG8(0x0a4d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_6 CCI_REG8(0x0a4e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_7 CCI_REG8(0x0a4f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_8 CCI_REG8(0x0a50) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_9 CCI_REG8(0x0a51) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_10 CCI_REG8(0x0a52) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_11 CCI_REG8(0x0a53) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_12 CCI_REG8(0x0a54) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_13 CCI_REG8(0x0a55) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_14 CCI_REG8(0x0a56) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_15 CCI_REG8(0x0a57) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_16 CCI_REG8(0x0a58) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_17 CCI_REG8(0x0a59) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_18 CCI_REG8(0x0a5a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_19 CCI_REG8(0x0a5b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_20 CCI_REG8(0x0a5c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_21 CCI_REG8(0x0a5d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_22 CCI_REG8(0x0a5e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_23 CCI_REG8(0x0a5f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_24 CCI_REG8(0x0a60) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_25 CCI_REG8(0x0a61) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_26 CCI_REG8(0x0a62) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_27 CCI_REG8(0x0a63) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_28 CCI_REG8(0x0a64) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_29 CCI_REG8(0x0a65) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_30 CCI_REG8(0x0a66) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_31 CCI_REG8(0x0a67) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_32 CCI_REG8(0x0a68) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_33 CCI_REG8(0x0a69) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_34 CCI_REG8(0x0a6a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_35 CCI_REG8(0x0a6b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_36 CCI_REG8(0x0a6c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_37 CCI_REG8(0x0a6d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_38 CCI_REG8(0x0a6e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_39 CCI_REG8(0x0a6f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_40 CCI_REG8(0x0a70) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_41 CCI_REG8(0x0a71) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_42 CCI_REG8(0x0a72) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_43 CCI_REG8(0x0a73) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_44 CCI_REG8(0x0a74) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_45 CCI_REG8(0x0a75) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_46 CCI_REG8(0x0a76) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_47 CCI_REG8(0x0a77) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_48 CCI_REG8(0x0a78) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_49 CCI_REG8(0x0a79) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_50 CCI_REG8(0x0a7a) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_51 CCI_REG8(0x0a7b) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_52 CCI_REG8(0x0a7c) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_53 CCI_REG8(0x0a7d) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_54 CCI_REG8(0x0a7e) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_55 CCI_REG8(0x0a7f) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_56 CCI_REG8(0x0a80) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_57 CCI_REG8(0x0a81) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_58 CCI_REG8(0x0a82) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_59 CCI_REG8(0x0a83) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_60 CCI_REG8(0x0a84) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_61 CCI_REG8(0x0a85) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_62 CCI_REG8(0x0a86) +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_2_DATA_63 CCI_REG8(0x0a87) +#define SMIAPP_REG_U8_SHADING_CORRECTION_ENABLE CCI_REG8(0x0b00) +#define SMIAPP_REG_U8_LUMINANCE_CORRECTION_LEVEL CCI_REG8(0x0b01) +#define SMIAPP_REG_U8_GREEN_IMBALANCE_FILTER_ENABLE CCI_REG8(0x0b02) +#define SMIAPP_REG_U8_GREEN_IMBALANCE_FILTER_WEIGHT CCI_REG8(0x0b03) +#define SMIAPP_REG_U8_BLACK_LEVEL_CORRECTION_ENABLE CCI_REG8(0x0b04) +#define SMIAPP_REG_U8_MAPPED_COUPLET_CORRECT_ENABLE CCI_REG8(0x0b05) +#define SMIAPP_REG_U8_SINGLE_DEFECT_CORRECT_ENABLE CCI_REG8(0x0b06) +#define SMIAPP_REG_U8_SINGLE_DEFECT_CORRECT_WEIGHT CCI_REG8(0x0b07) +#define SMIAPP_REG_U8_DYNAMIC_COUPLET_CORRECT_ENABLE CCI_REG8(0x0b08) +#define SMIAPP_REG_U8_DYNAMIC_COUPLET_CORRECT_WEIGHT CCI_REG8(0x0b09) +#define SMIAPP_REG_U8_COMBINED_DEFECT_CORRECT_ENABLE CCI_REG8(0x0b0a) +#define SMIAPP_REG_U8_COMBINED_DEFECT_CORRECT_WEIGHT CCI_REG8(0x0b0b) +#define SMIAPP_REG_U8_MODULE_SPECIFIC_CORRECTION_ENABLE CCI_REG8(0x0b0c) +#define SMIAPP_REG_U8_MODULE_SPECIFIC_CORRECTION_WEIGHT CCI_REG8(0x0b0d) +#define SMIAPP_REG_U8_MAPPED_LINE_DEFECT_CORRECT_ENABLE CCI_REG8(0x0b0e) +#define SMIAPP_REG_U8_MAPPED_LINE_DEFECT_CORRECT_ADJUST CCI_REG8(0x0b0f) +#define SMIAPP_REG_U8_MAPPED_COUPLET_CORRECT_ADJUST CCI_REG8(0x0b10) +#define SMIAPP_REG_U8_MAPPED_TRIPLET_DEFECT_CORRECT_ENABLE CCI_REG8(0x0b11) +#define SMIAPP_REG_U8_MAPPED_TRIPLET_DEFECT_CORRECT_ADJUST CCI_REG8(0x0b12) +#define SMIAPP_REG_U8_DYNAMIC_TRIPLET_DEFECT_CORRECT_ENABLE CCI_REG8(0x0b13) +#define SMIAPP_REG_U8_DYNAMIC_TRIPLET_DEFECT_CORRECT_ADJUST CCI_REG8(0x0b14) +#define SMIAPP_REG_U8_DYNAMIC_LINE_DEFECT_CORRECT_ENABLE CCI_REG8(0x0b15) +#define SMIAPP_REG_U8_DYNAMIC_LINE_DEFECT_CORRECT_ADJUST CCI_REG8(0x0b16) +#define SMIAPP_REG_U8_EDOF_MODE CCI_REG8(0x0b80) +#define SMIAPP_REG_U8_SHARPNESS CCI_REG8(0x0b83) +#define SMIAPP_REG_U8_DENOISING CCI_REG8(0x0b84) +#define SMIAPP_REG_U8_MODULE_SPECIFIC CCI_REG8(0x0b85) +#define SMIAPP_REG_U16_DEPTH_OF_FIELD CCI_REG16(0x0b86) +#define SMIAPP_REG_U16_FOCUS_DISTANCE CCI_REG16(0x0b88) +#define SMIAPP_REG_U8_ESTIMATION_MODE_CTRL CCI_REG8(0x0b8a) +#define SMIAPP_REG_U16_COLOUR_TEMPERATURE CCI_REG16(0x0b8c) +#define SMIAPP_REG_U16_ABSOLUTE_GAIN_GREENR CCI_REG16(0x0b8e) +#define SMIAPP_REG_U16_ABSOLUTE_GAIN_RED CCI_REG16(0x0b90) +#define SMIAPP_REG_U16_ABSOLUTE_GAIN_BLUE CCI_REG16(0x0b92) +#define SMIAPP_REG_U16_ABSOLUTE_GAIN_GREENB CCI_REG16(0x0b94) +#define SMIAPP_REG_U8_ESTIMATION_ZONE_MODE CCI_REG8(0x0bc0) +#define SMIAPP_REG_U16_FIXED_ZONE_WEIGHTING CCI_REG16(0x0bc2) +#define SMIAPP_REG_U16_CUSTOM_ZONE_X_START CCI_REG16(0x0bc4) +#define SMIAPP_REG_U16_CUSTOM_ZONE_Y_START CCI_REG16(0x0bc6) +#define SMIAPP_REG_U16_CUSTOM_ZONE_WIDTH CCI_REG16(0x0bc8) +#define SMIAPP_REG_U16_CUSTOM_ZONE_HEIGHT CCI_REG16(0x0bca) +#define SMIAPP_REG_U8_GLOBAL_RESET_CTRL1 CCI_REG8(0x0c00) +#define SMIAPP_REG_U8_GLOBAL_RESET_CTRL2 CCI_REG8(0x0c01) +#define SMIAPP_REG_U8_GLOBAL_RESET_MODE_CONFIG_1 CCI_REG8(0x0c02) +#define SMIAPP_REG_U8_GLOBAL_RESET_MODE_CONFIG_2 CCI_REG8(0x0c03) +#define SMIAPP_REG_U16_TRDY_CTRL CCI_REG16(0x0c04) +#define SMIAPP_REG_U16_TRDOUT_CTRL CCI_REG16(0x0c06) +#define SMIAPP_REG_U16_TSHUTTER_STROBE_DELAY_CTRL CCI_REG16(0x0c08) +#define SMIAPP_REG_U16_TSHUTTER_STROBE_WIDTH_CTRL CCI_REG16(0x0c0a) +#define SMIAPP_REG_U16_TFLASH_STROBE_DELAY_CTRL CCI_REG16(0x0c0c) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH_HIGH_CTRL CCI_REG16(0x0c0e) +#define SMIAPP_REG_U16_TGRST_INTERVAL_CTRL CCI_REG16(0x0c10) +#define SMIAPP_REG_U8_FLASH_STROBE_ADJUSTMENT CCI_REG8(0x0c12) +#define SMIAPP_REG_U16_FLASH_STROBE_START_POINT CCI_REG16(0x0c14) +#define SMIAPP_REG_U16_TFLASH_STROBE_DELAY_RS_CTRL CCI_REG16(0x0c16) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH_HIGH_RS_CTRL CCI_REG16(0x0c18) +#define SMIAPP_REG_U8_FLASH_MODE_RS CCI_REG8(0x0c1a) +#define SMIAPP_REG_U8_FLASH_TRIGGER_RS CCI_REG8(0x0c1b) +#define SMIAPP_REG_U8_FLASH_STATUS CCI_REG8(0x0c1c) +#define SMIAPP_REG_U8_SA_STROBE_MODE CCI_REG8(0x0c1d) +#define SMIAPP_REG_U16_SA_STROBE_START_POINT CCI_REG16(0x0c1e) +#define SMIAPP_REG_U16_TSA_STROBE_DELAY_CTRL CCI_REG16(0x0c20) +#define SMIAPP_REG_U16_TSA_STROBE_WIDTH_CTRL CCI_REG16(0x0c22) +#define SMIAPP_REG_U8_SA_STROBE_TRIGGER CCI_REG8(0x0c24) +#define SMIAPP_REG_U8_SPECIAL_ACTUATOR_STATUS CCI_REG8(0x0c25) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH2_HIGH_RS_CTRL CCI_REG16(0x0c26) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH_LOW_RS_CTRL CCI_REG16(0x0c28) +#define SMIAPP_REG_U8_TFLASH_STROBE_COUNT_RS_CTRL CCI_REG8(0x0c2a) +#define SMIAPP_REG_U8_TFLASH_STROBE_COUNT_CTRL CCI_REG8(0x0c2b) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH2_HIGH_CTRL CCI_REG16(0x0c2c) +#define SMIAPP_REG_U16_TFLASH_STROBE_WIDTH_LOW_CTRL CCI_REG16(0x0c2e) +#define SMIAPP_REG_U8_LOW_LEVEL_CTRL CCI_REG8(0x0c80) +#define SMIAPP_REG_U16_MAIN_TRIGGER_REF_POINT CCI_REG16(0x0c82) +#define SMIAPP_REG_U16_MAIN_TRIGGER_T3 CCI_REG16(0x0c84) +#define SMIAPP_REG_U8_MAIN_TRIGGER_COUNT CCI_REG8(0x0c86) +#define SMIAPP_REG_U16_PHASE1_TRIGGER_T3 CCI_REG16(0x0c88) +#define SMIAPP_REG_U8_PHASE1_TRIGGER_COUNT CCI_REG8(0x0c8a) +#define SMIAPP_REG_U16_PHASE2_TRIGGER_T3 CCI_REG16(0x0c8c) +#define SMIAPP_REG_U8_PHASE2_TRIGGER_COUNT CCI_REG8(0x0c8e) +#define SMIAPP_REG_U8_MECH_SHUTTER_CTRL CCI_REG8(0x0d00) +#define SMIAPP_REG_U8_OPERATION_MODE CCI_REG8(0x0d01) +#define SMIAPP_REG_U8_ACT_STATE1 CCI_REG8(0x0d02) +#define SMIAPP_REG_U8_ACT_STATE2 CCI_REG8(0x0d03) +#define SMIAPP_REG_U16_FOCUS_CHANGE CCI_REG16(0x0d80) +#define SMIAPP_REG_U16_FOCUS_CHANGE_CONTROL CCI_REG16(0x0d82) +#define SMIAPP_REG_U16_FOCUS_CHANGE_NUMBER_PHASE1 CCI_REG16(0x0d84) +#define SMIAPP_REG_U16_FOCUS_CHANGE_NUMBER_PHASE2 CCI_REG16(0x0d86) +#define SMIAPP_REG_U8_STROBE_COUNT_PHASE1 CCI_REG8(0x0d88) +#define SMIAPP_REG_U8_STROBE_COUNT_PHASE2 CCI_REG8(0x0d89) +#define SMIAPP_REG_U8_POSITION CCI_REG8(0x0d8a) +#define SMIAPP_REG_U8_BRACKETING_LUT_CONTROL CCI_REG8(0x0e00) +#define SMIAPP_REG_U8_BRACKETING_LUT_MODE CCI_REG8(0x0e01) +#define SMIAPP_REG_U8_BRACKETING_LUT_ENTRY_CONTROL CCI_REG8(0x0e02) +#define SMIAPP_REG_U8_LUT_PARAMETERS_START CCI_REG8(0x0e10) +#define SMIAPP_REG_U8_LUT_PARAMETERS_END CCI_REG8(0x0eff) +#define SMIAPP_REG_U16_INTEGRATION_TIME_CAPABILITY CCI_REG16(0x1000) +#define SMIAPP_REG_U16_COARSE_INTEGRATION_TIME_MIN CCI_REG16(0x1004) +#define SMIAPP_REG_U16_COARSE_INTEGRATION_TIME_MAX_MARGIN CCI_REG16(0x1006) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MIN CCI_REG16(0x1008) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MAX_MARGIN CCI_REG16(0x100a) +#define SMIAPP_REG_U16_DIGITAL_GAIN_CAPABILITY CCI_REG16(0x1080) +#define SMIAPP_REG_U16_DIGITAL_GAIN_MIN CCI_REG16(0x1084) +#define SMIAPP_REG_U16_DIGITAL_GAIN_MAX CCI_REG16(0x1086) +#define SMIAPP_REG_U16_DIGITAL_GAIN_STEP_SIZE CCI_REG16(0x1088) +#define SMIAPP_REG_F32_MIN_EXT_CLK_FREQ_HZ (CCI_REG32(0x1100) | CCS_FL_FLOAT_IREAL) +#define SMIAPP_REG_F32_MAX_EXT_CLK_FREQ_HZ (CCI_REG32(0x1104) | CCS_FL_FLOAT_IREAL) +#define SMIAPP_REG_U16_MIN_PRE_PLL_CLK_DIV CCI_REG16(0x1108) +#define SMIAPP_REG_U16_MAX_PRE_PLL_CLK_DIV CCI_REG16(0x110a) +#define SMIAPP_REG_F32_MIN_PLL_IP_FREQ_HZ (CCI_REG32(0x110c) | CCS_FL_FLOAT_IREAL) +#define SMIAPP_REG_F32_MAX_PLL_IP_FREQ_HZ (CCI_REG32(0x1110) | CCS_FL_FLOAT_IREAL) +#define SMIAPP_REG_U16_MIN_PLL_MULTIPLIER CCI_REG16(0x1114) +#define SMIAPP_REG_U16_MAX_PLL_MULTIPLIER CCI_REG16(0x1116) +#define SMIAPP_REG_F32_MIN_PLL_OP_FREQ_HZ (CCI_REG32(0x1118) | CCS_FL_FLOAT_IREAL) +#define SMIAPP_REG_F32_MAX_PLL_OP_FREQ_HZ (CCI_REG32(0x111c) | CCS_FL_FLOAT_IREAL) +#define SMIAPP_REG_U16_MIN_VT_SYS_CLK_DIV CCI_REG16(0x1120) +#define SMIAPP_REG_U16_MAX_VT_SYS_CLK_DIV CCI_REG16(0x1122) +#define SMIAPP_REG_F32_MIN_VT_SYS_CLK_FREQ_HZ (CCI_REG32(0x1124) | CCS_FL_FLOAT_IREAL) +#define SMIAPP_REG_F32_MAX_VT_SYS_CLK_FREQ_HZ (CCI_REG32(0x1128) | CCS_FL_FLOAT_IREAL) +#define SMIAPP_REG_F32_MIN_VT_PIX_CLK_FREQ_HZ (CCI_REG32(0x112c) | CCS_FL_FLOAT_IREAL) +#define SMIAPP_REG_F32_MAX_VT_PIX_CLK_FREQ_HZ (CCI_REG32(0x1130) | CCS_FL_FLOAT_IREAL) +#define SMIAPP_REG_U16_MIN_VT_PIX_CLK_DIV CCI_REG16(0x1134) +#define SMIAPP_REG_U16_MAX_VT_PIX_CLK_DIV CCI_REG16(0x1136) +#define SMIAPP_REG_U16_MIN_FRAME_LENGTH_LINES CCI_REG16(0x1140) +#define SMIAPP_REG_U16_MAX_FRAME_LENGTH_LINES CCI_REG16(0x1142) +#define SMIAPP_REG_U16_MIN_LINE_LENGTH_PCK CCI_REG16(0x1144) +#define SMIAPP_REG_U16_MAX_LINE_LENGTH_PCK CCI_REG16(0x1146) +#define SMIAPP_REG_U16_MIN_LINE_BLANKING_PCK CCI_REG16(0x1148) +#define SMIAPP_REG_U16_MIN_FRAME_BLANKING_LINES CCI_REG16(0x114a) +#define SMIAPP_REG_U8_MIN_LINE_LENGTH_PCK_STEP_SIZE CCI_REG8(0x114c) +#define SMIAPP_REG_U16_MIN_OP_SYS_CLK_DIV CCI_REG16(0x1160) +#define SMIAPP_REG_U16_MAX_OP_SYS_CLK_DIV CCI_REG16(0x1162) +#define SMIAPP_REG_F32_MIN_OP_SYS_CLK_FREQ_HZ (CCI_REG32(0x1164) | CCS_FL_FLOAT_IREAL) +#define SMIAPP_REG_F32_MAX_OP_SYS_CLK_FREQ_HZ (CCI_REG32(0x1168) | CCS_FL_FLOAT_IREAL) +#define SMIAPP_REG_U16_MIN_OP_PIX_CLK_DIV CCI_REG16(0x116c) +#define SMIAPP_REG_U16_MAX_OP_PIX_CLK_DIV CCI_REG16(0x116e) +#define SMIAPP_REG_F32_MIN_OP_PIX_CLK_FREQ_HZ (CCI_REG32(0x1170) | CCS_FL_FLOAT_IREAL) +#define SMIAPP_REG_F32_MAX_OP_PIX_CLK_FREQ_HZ (CCI_REG32(0x1174) | CCS_FL_FLOAT_IREAL) +#define SMIAPP_REG_U16_X_ADDR_MIN CCI_REG16(0x1180) +#define SMIAPP_REG_U16_Y_ADDR_MIN CCI_REG16(0x1182) +#define SMIAPP_REG_U16_X_ADDR_MAX CCI_REG16(0x1184) +#define SMIAPP_REG_U16_Y_ADDR_MAX CCI_REG16(0x1186) +#define SMIAPP_REG_U16_MIN_X_OUTPUT_SIZE CCI_REG16(0x1188) +#define SMIAPP_REG_U16_MIN_Y_OUTPUT_SIZE CCI_REG16(0x118a) +#define SMIAPP_REG_U16_MAX_X_OUTPUT_SIZE CCI_REG16(0x118c) +#define SMIAPP_REG_U16_MAX_Y_OUTPUT_SIZE CCI_REG16(0x118e) +#define SMIAPP_REG_U16_MIN_EVEN_INC CCI_REG16(0x11c0) +#define SMIAPP_REG_U16_MAX_EVEN_INC CCI_REG16(0x11c2) +#define SMIAPP_REG_U16_MIN_ODD_INC CCI_REG16(0x11c4) +#define SMIAPP_REG_U16_MAX_ODD_INC CCI_REG16(0x11c6) +#define SMIAPP_REG_U16_SCALING_CAPABILITY CCI_REG16(0x1200) +#define SMIAPP_REG_U16_SCALER_M_MIN CCI_REG16(0x1204) +#define SMIAPP_REG_U16_SCALER_M_MAX CCI_REG16(0x1206) +#define SMIAPP_REG_U16_SCALER_N_MIN CCI_REG16(0x1208) +#define SMIAPP_REG_U16_SCALER_N_MAX CCI_REG16(0x120a) +#define SMIAPP_REG_U16_SPATIAL_SAMPLING_CAPABILITY CCI_REG16(0x120c) +#define SMIAPP_REG_U8_DIGITAL_CROP_CAPABILITY CCI_REG8(0x120e) +#define SMIAPP_REG_U16_COMPRESSION_CAPABILITY CCI_REG16(0x1300) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_REDINRED CCI_REG16(0x1400) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_GREENINRED CCI_REG16(0x1402) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_BLUEINRED CCI_REG16(0x1404) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_REDINGREEN CCI_REG16(0x1406) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_GREENINGREEN CCI_REG16(0x1408) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_BLUEINGREEN CCI_REG16(0x140a) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_REDINBLUE CCI_REG16(0x140c) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_GREENINBLUE CCI_REG16(0x140e) +#define SMIAPP_REG_U16_MATRIX_ELEMENT_BLUEINBLUE CCI_REG16(0x1410) +#define SMIAPP_REG_U16_FIFO_SIZE_PIXELS CCI_REG16(0x1500) +#define SMIAPP_REG_U8_FIFO_SUPPORT_CAPABILITY CCI_REG8(0x1502) +#define SMIAPP_REG_U8_DPHY_CTRL_CAPABILITY CCI_REG8(0x1600) +#define SMIAPP_REG_U8_CSI_LANE_MODE_CAPABILITY CCI_REG8(0x1601) +#define SMIAPP_REG_U8_CSI_SIGNALLING_MODE_CAPABILITY CCI_REG8(0x1602) +#define SMIAPP_REG_U8_FAST_STANDBY_CAPABILITY CCI_REG8(0x1603) +#define SMIAPP_REG_U8_CCI_ADDRESS_CONTROL_CAPABILITY CCI_REG8(0x1604) +#define SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_1_LANE_MODE_MBPS CCI_REG32(0x1608) +#define SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_2_LANE_MODE_MBPS CCI_REG32(0x160c) +#define SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_3_LANE_MODE_MBPS CCI_REG32(0x1610) +#define SMIAPP_REG_U32_MAX_PER_LANE_BITRATE_4_LANE_MODE_MBPS CCI_REG32(0x1614) +#define SMIAPP_REG_U8_TEMP_SENSOR_CAPABILITY CCI_REG8(0x1618) +#define SMIAPP_REG_U16_MIN_FRAME_LENGTH_LINES_BIN CCI_REG16(0x1700) +#define SMIAPP_REG_U16_MAX_FRAME_LENGTH_LINES_BIN CCI_REG16(0x1702) +#define SMIAPP_REG_U16_MIN_LINE_LENGTH_PCK_BIN CCI_REG16(0x1704) +#define SMIAPP_REG_U16_MAX_LINE_LENGTH_PCK_BIN CCI_REG16(0x1706) +#define SMIAPP_REG_U16_MIN_LINE_BLANKING_PCK_BIN CCI_REG16(0x1708) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MIN_BIN CCI_REG16(0x170a) +#define SMIAPP_REG_U16_FINE_INTEGRATION_TIME_MAX_MARGIN_BIN CCI_REG16(0x170c) +#define SMIAPP_REG_U8_BINNING_CAPABILITY CCI_REG8(0x1710) +#define SMIAPP_REG_U8_BINNING_WEIGHTING_CAPABILITY CCI_REG8(0x1711) +#define SMIAPP_REG_U8_BINNING_SUBTYPES CCI_REG8(0x1712) +#define SMIAPP_REG_U8_BINNING_TYPE_n(n) CCI_REG8(0x1713 + (n)) /* 1 <= n <= 237 */ +#define SMIAPP_REG_U8_DATA_TRANSFER_IF_CAPABILITY CCI_REG8(0x1800) +#define SMIAPP_REG_U8_SHADING_CORRECTION_CAPABILITY CCI_REG8(0x1900) +#define SMIAPP_REG_U8_GREEN_IMBALANCE_CAPABILITY CCI_REG8(0x1901) +#define SMIAPP_REG_U8_BLACK_LEVEL_CAPABILITY CCI_REG8(0x1902) +#define SMIAPP_REG_U8_MODULE_SPECIFIC_CORRECTION_CAPABILITY CCI_REG8(0x1903) +#define SMIAPP_REG_U16_DEFECT_CORRECTION_CAPABILITY CCI_REG16(0x1904) +#define SMIAPP_REG_U16_DEFECT_CORRECTION_CAPABILITY_2 CCI_REG16(0x1906) +#define SMIAPP_REG_U8_EDOF_CAPABILITY CCI_REG8(0x1980) +#define SMIAPP_REG_U8_ESTIMATION_FRAMES CCI_REG8(0x1981) +#define SMIAPP_REG_U8_SUPPORTS_SHARPNESS_ADJ CCI_REG8(0x1982) +#define SMIAPP_REG_U8_SUPPORTS_DENOISING_ADJ CCI_REG8(0x1983) +#define SMIAPP_REG_U8_SUPPORTS_MODULE_SPECIFIC_ADJ CCI_REG8(0x1984) +#define SMIAPP_REG_U8_SUPPORTS_DEPTH_OF_FIELD_ADJ CCI_REG8(0x1985) +#define SMIAPP_REG_U8_SUPPORTS_FOCUS_DISTANCE_ADJ CCI_REG8(0x1986) +#define SMIAPP_REG_U8_COLOUR_FEEDBACK_CAPABILITY CCI_REG8(0x1987) +#define SMIAPP_REG_U8_EDOF_SUPPORT_AB_NXM CCI_REG8(0x1988) +#define SMIAPP_REG_U8_ESTIMATION_MODE_CAPABILITY CCI_REG8(0x19c0) +#define SMIAPP_REG_U8_ESTIMATION_ZONE_CAPABILITY CCI_REG8(0x19c1) +#define SMIAPP_REG_U16_EST_DEPTH_OF_FIELD CCI_REG16(0x19c2) +#define SMIAPP_REG_U16_EST_FOCUS_DISTANCE CCI_REG16(0x19c4) +#define SMIAPP_REG_U16_CAPABILITY_TRDY_MIN CCI_REG16(0x1a00) +#define SMIAPP_REG_U8_FLASH_MODE_CAPABILITY CCI_REG8(0x1a02) +#define SMIAPP_REG_U16_MECH_SHUT_AND_ACT_START_ADDR CCI_REG16(0x1b02) +#define SMIAPP_REG_U8_ACTUATOR_CAPABILITY CCI_REG8(0x1b04) +#define SMIAPP_REG_U16_ACTUATOR_TYPE CCI_REG16(0x1b40) +#define SMIAPP_REG_U8_AF_DEVICE_ADDRESS CCI_REG8(0x1b42) +#define SMIAPP_REG_U16_FOCUS_CHANGE_ADDRESS CCI_REG16(0x1b44) +#define SMIAPP_REG_U8_BRACKETING_LUT_CAPABILITY_1 CCI_REG8(0x1c00) +#define SMIAPP_REG_U8_BRACKETING_LUT_CAPABILITY_2 CCI_REG8(0x1c01) +#define SMIAPP_REG_U8_BRACKETING_LUT_SIZE CCI_REG8(0x1c02) + +/* Register bit definitions */ +#define SMIAPP_IMAGE_ORIENTATION_HFLIP BIT(0) +#define SMIAPP_IMAGE_ORIENTATION_VFLIP BIT(1) + +#define SMIAPP_DATA_TRANSFER_IF_1_CTRL_EN BIT(0) +#define SMIAPP_DATA_TRANSFER_IF_1_CTRL_WR_EN BIT(1) +#define SMIAPP_DATA_TRANSFER_IF_1_CTRL_ERR_CLEAR BIT(2) +#define SMIAPP_DATA_TRANSFER_IF_1_STATUS_RD_READY BIT(0) +#define SMIAPP_DATA_TRANSFER_IF_1_STATUS_WR_READY BIT(1) +#define SMIAPP_DATA_TRANSFER_IF_1_STATUS_EDATA BIT(2) +#define SMIAPP_DATA_TRANSFER_IF_1_STATUS_EUSAGE BIT(3) + +#define SMIAPP_DATA_TRANSFER_IF_CAPABILITY_SUPPORTED BIT(0) +#define SMIAPP_DATA_TRANSFER_IF_CAPABILITY_POLL BIT(2) + +#define SMIAPP_SOFTWARE_RESET BIT(0) + +#define SMIAPP_FLASH_MODE_CAPABILITY_SINGLE_STROBE BIT(0) +#define SMIAPP_FLASH_MODE_CAPABILITY_MULTIPLE_STROBE BIT(1) + +#define SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_CLOCK 0 +#define SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_STROBE 1 +#define SMIAPP_CSI_SIGNALLING_MODE_CSI2 2 + +#define SMIAPP_DPHY_CTRL_AUTOMATIC 0 +/* DPHY control based on REQUESTED_LINK_BIT_RATE_MBPS */ +#define SMIAPP_DPHY_CTRL_UI 1 +#define SMIAPP_DPHY_CTRL_REGISTER 2 + +#define SMIAPP_COMPRESSION_MODE_SIMPLE_PREDICTOR 1 +#define SMIAPP_COMPRESSION_MODE_ADVANCED_PREDICTOR 2 + +#define SMIAPP_MODE_SELECT_SOFTWARE_STANDBY 0 +#define SMIAPP_MODE_SELECT_STREAMING 1 + +#define SMIAPP_SCALING_MODE_NONE 0 +#define SMIAPP_SCALING_MODE_HORIZONTAL 1 +#define SMIAPP_SCALING_MODE_BOTH 2 + +#define SMIAPP_SCALING_CAPABILITY_NONE 0 +#define SMIAPP_SCALING_CAPABILITY_HORIZONTAL 1 +#define SMIAPP_SCALING_CAPABILITY_BOTH 2 /* horizontal/both */ + +/* digital crop right before scaler */ +#define SMIAPP_DIGITAL_CROP_CAPABILITY_NONE 0 +#define SMIAPP_DIGITAL_CROP_CAPABILITY_INPUT_CROP 1 + +#define SMIAPP_DIGITAL_GAIN_CAPABILITY_PER_CHANNEL 1 + +#define SMIAPP_BINNING_CAPABILITY_NO 0 +#define SMIAPP_BINNING_CAPABILITY_YES 1 + +/* Maximum number of binning subtypes */ +#define SMIAPP_BINNING_SUBTYPES 253 + +#define SMIAPP_PIXEL_ORDER_GRBG 0 +#define SMIAPP_PIXEL_ORDER_RGGB 1 +#define SMIAPP_PIXEL_ORDER_BGGR 2 +#define SMIAPP_PIXEL_ORDER_GBRG 3 + +#define SMIAPP_DATA_FORMAT_MODEL_TYPE_NORMAL 1 +#define SMIAPP_DATA_FORMAT_MODEL_TYPE_EXTENDED 2 +#define SMIAPP_DATA_FORMAT_MODEL_TYPE_NORMAL_N 8 +#define SMIAPP_DATA_FORMAT_MODEL_TYPE_EXTENDED_N 16 + +#define SMIAPP_FRAME_FORMAT_MODEL_TYPE_2BYTE 0x01 +#define SMIAPP_FRAME_FORMAT_MODEL_TYPE_4BYTE 0x02 +#define SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NROWS_MASK 0x0f +#define SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NCOLS_MASK 0xf0 +#define SMIAPP_FRAME_FORMAT_MODEL_SUBTYPE_NCOLS_SHIFT 4 + +#define SMIAPP_FRAME_FORMAT_DESC_2_PIXELCODE_MASK 0xf000 +#define SMIAPP_FRAME_FORMAT_DESC_2_PIXELCODE_SHIFT 12 +#define SMIAPP_FRAME_FORMAT_DESC_2_PIXELS_MASK 0x0fff + +#define SMIAPP_FRAME_FORMAT_DESC_4_PIXELCODE_MASK 0xf0000000 +#define SMIAPP_FRAME_FORMAT_DESC_4_PIXELCODE_SHIFT 28 +#define SMIAPP_FRAME_FORMAT_DESC_4_PIXELS_MASK 0x0000ffff + +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_EMBEDDED 1 +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_DUMMY 2 +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_BLACK 3 +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_DARK 4 +#define SMIAPP_FRAME_FORMAT_DESC_PIXELCODE_VISIBLE 5 + +#define SMIAPP_FAST_STANDBY_CTRL_COMPLETE_FRAMES 0 +#define SMIAPP_FAST_STANDBY_CTRL_IMMEDIATE 1 + +/* Scaling N factor */ +#define SMIAPP_SCALE_N 16 + +#endif /* __SMIAPP_REG_DEFS_H__ */ diff --git a/drivers/media/i2c/cs3308.c b/drivers/media/i2c/cs3308.c new file mode 100644 index 000000000..ad37aeecf --- /dev/null +++ b/drivers/media/i2c/cs3308.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Cirrus Logic cs3308 8-Channel Analog Volume Control + * + * Copyright (C) 2010 Devin Heitmueller + * Copyright (C) 2012 Steven Toth + * + * Derived from cs5345.c Copyright (C) 2007 Hans Verkuil + */ + + +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("i2c device driver for cs3308 8-channel volume control"); +MODULE_AUTHOR("Devin Heitmueller"); +MODULE_LICENSE("GPL"); + +static inline int cs3308_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int cs3308_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int cs3308_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + reg->val = cs3308_read(sd, reg->reg & 0xffff); + reg->size = 1; + return 0; +} + +static int cs3308_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + cs3308_write(sd, reg->reg & 0xffff, reg->val & 0xff); + return 0; +} +#endif + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops cs3308_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = cs3308_g_register, + .s_register = cs3308_s_register, +#endif +}; + +static const struct v4l2_subdev_ops cs3308_ops = { + .core = &cs3308_core_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int cs3308_probe(struct i2c_client *client) +{ + struct v4l2_subdev *sd; + unsigned i; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + if ((i2c_smbus_read_byte_data(client, 0x1c) & 0xf0) != 0xe0) + return -ENODEV; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + sd = kzalloc_obj(struct v4l2_subdev); + if (sd == NULL) + return -ENOMEM; + + v4l2_i2c_subdev_init(sd, client, &cs3308_ops); + + /* Set some reasonable defaults */ + cs3308_write(sd, 0x0d, 0x00); /* Power up all channels */ + cs3308_write(sd, 0x0e, 0x00); /* Master Power */ + cs3308_write(sd, 0x0b, 0x00); /* Device Configuration */ + /* Set volume for each channel */ + for (i = 1; i <= 8; i++) + cs3308_write(sd, i, 0xd2); + cs3308_write(sd, 0x0a, 0x00); /* Unmute all channels */ + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static void cs3308_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + kfree(sd); +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id cs3308_id[] = { + { .name = "cs3308" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cs3308_id); + +static struct i2c_driver cs3308_driver = { + .driver = { + .name = "cs3308", + }, + .probe = cs3308_probe, + .remove = cs3308_remove, + .id_table = cs3308_id, +}; + +module_i2c_driver(cs3308_driver); diff --git a/drivers/media/i2c/cs5345.c b/drivers/media/i2c/cs5345.c new file mode 100644 index 000000000..49b8020fb --- /dev/null +++ b/drivers/media/i2c/cs5345.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cs5345 Cirrus Logic 24-bit, 192 kHz Stereo Audio ADC + * Copyright (C) 2007 Hans Verkuil + */ + + +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("i2c device driver for cs5345 Audio ADC"); +MODULE_AUTHOR("Hans Verkuil"); +MODULE_LICENSE("GPL"); + +static bool debug; + +module_param(debug, bool, 0644); + +MODULE_PARM_DESC(debug, "Debugging messages, 0=Off (default), 1=On"); + +struct cs5345_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; +}; + +static inline struct cs5345_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct cs5345_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct cs5345_state, hdl)->sd; +} + +/* ----------------------------------------------------------------------- */ + +static inline int cs5345_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int cs5345_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int cs5345_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + if ((input & 0xf) > 6) { + v4l2_err(sd, "Invalid input %d.\n", input); + return -EINVAL; + } + cs5345_write(sd, 0x09, input & 0xf); + cs5345_write(sd, 0x05, input & 0xf0); + return 0; +} + +static int cs5345_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + cs5345_write(sd, 0x04, ctrl->val ? 0x80 : 0); + return 0; + case V4L2_CID_AUDIO_VOLUME: + cs5345_write(sd, 0x07, ((u8)ctrl->val) & 0x3f); + cs5345_write(sd, 0x08, ((u8)ctrl->val) & 0x3f); + return 0; + } + return -EINVAL; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int cs5345_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + reg->size = 1; + reg->val = cs5345_read(sd, reg->reg & 0x1f); + return 0; +} + +static int cs5345_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + cs5345_write(sd, reg->reg & 0x1f, reg->val & 0xff); + return 0; +} +#endif + +static int cs5345_log_status(struct v4l2_subdev *sd) +{ + u8 v = cs5345_read(sd, 0x09) & 7; + u8 m = cs5345_read(sd, 0x04); + int vol = cs5345_read(sd, 0x08) & 0x3f; + + v4l2_info(sd, "Input: %d%s\n", v, + (m & 0x80) ? " (muted)" : ""); + if (vol >= 32) + vol = vol - 64; + v4l2_info(sd, "Volume: %d dB\n", vol); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops cs5345_ctrl_ops = { + .s_ctrl = cs5345_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops cs5345_core_ops = { + .log_status = cs5345_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = cs5345_g_register, + .s_register = cs5345_s_register, +#endif +}; + +static const struct v4l2_subdev_audio_ops cs5345_audio_ops = { + .s_routing = cs5345_s_routing, +}; + +static const struct v4l2_subdev_ops cs5345_ops = { + .core = &cs5345_core_ops, + .audio = &cs5345_audio_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static int cs5345_probe(struct i2c_client *client) +{ + struct cs5345_state *state; + struct v4l2_subdev *sd; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &cs5345_ops); + + v4l2_ctrl_handler_init(&state->hdl, 2); + v4l2_ctrl_new_std(&state->hdl, &cs5345_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + v4l2_ctrl_new_std(&state->hdl, &cs5345_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, -24, 24, 1, 0); + sd->ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + return err; + } + /* set volume/mute */ + v4l2_ctrl_handler_setup(&state->hdl); + + cs5345_write(sd, 0x02, 0x00); + cs5345_write(sd, 0x04, 0x01); + cs5345_write(sd, 0x09, 0x01); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static void cs5345_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct cs5345_state *state = to_state(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&state->hdl); +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id cs5345_id[] = { + { .name = "cs5345" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cs5345_id); + +static struct i2c_driver cs5345_driver = { + .driver = { + .name = "cs5345", + }, + .probe = cs5345_probe, + .remove = cs5345_remove, + .id_table = cs5345_id, +}; + +module_i2c_driver(cs5345_driver); diff --git a/drivers/media/i2c/cs53l32a.c b/drivers/media/i2c/cs53l32a.c new file mode 100644 index 000000000..a894dcf6b --- /dev/null +++ b/drivers/media/i2c/cs53l32a.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * cs53l32a (Adaptec AVC-2010 and AVC-2410) i2c ivtv driver. + * Copyright (C) 2005 Martin Vaughan + * + * Audio source switching for Adaptec AVC-2410 added by Trev Jackson + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("i2c device driver for cs53l32a Audio ADC"); +MODULE_AUTHOR("Martin Vaughan"); +MODULE_LICENSE("GPL"); + +static bool debug; + +module_param(debug, bool, 0644); + +MODULE_PARM_DESC(debug, "Debugging messages, 0=Off (default), 1=On"); + + +struct cs53l32a_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; +}; + +static inline struct cs53l32a_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct cs53l32a_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct cs53l32a_state, hdl)->sd; +} + +/* ----------------------------------------------------------------------- */ + +static int cs53l32a_write(struct v4l2_subdev *sd, u8 reg, u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int cs53l32a_read(struct v4l2_subdev *sd, u8 reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int cs53l32a_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + /* There are 2 physical inputs, but the second input can be + placed in two modes, the first mode bypasses the PGA (gain), + the second goes through the PGA. Hence there are three + possible inputs to choose from. */ + if (input > 2) { + v4l2_err(sd, "Invalid input %d.\n", input); + return -EINVAL; + } + cs53l32a_write(sd, 0x01, 0x01 + (input << 4)); + return 0; +} + +static int cs53l32a_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + cs53l32a_write(sd, 0x03, ctrl->val ? 0xf0 : 0x30); + return 0; + case V4L2_CID_AUDIO_VOLUME: + cs53l32a_write(sd, 0x04, (u8)ctrl->val); + cs53l32a_write(sd, 0x05, (u8)ctrl->val); + return 0; + } + return -EINVAL; +} + +static int cs53l32a_log_status(struct v4l2_subdev *sd) +{ + struct cs53l32a_state *state = to_state(sd); + u8 v = cs53l32a_read(sd, 0x01); + + v4l2_info(sd, "Input: %d\n", (v >> 4) & 3); + v4l2_ctrl_handler_log_status(&state->hdl, sd->name); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops cs53l32a_ctrl_ops = { + .s_ctrl = cs53l32a_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops cs53l32a_core_ops = { + .log_status = cs53l32a_log_status, +}; + +static const struct v4l2_subdev_audio_ops cs53l32a_audio_ops = { + .s_routing = cs53l32a_s_routing, +}; + +static const struct v4l2_subdev_ops cs53l32a_ops = { + .core = &cs53l32a_core_ops, + .audio = &cs53l32a_audio_ops, +}; + +/* ----------------------------------------------------------------------- */ + +/* i2c implementation */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ + +static int cs53l32a_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct cs53l32a_state *state; + struct v4l2_subdev *sd; + int i; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + if (!id) + strscpy(client->name, "cs53l32a", sizeof(client->name)); + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &cs53l32a_ops); + + for (i = 1; i <= 7; i++) { + u8 v = cs53l32a_read(sd, i); + + v4l2_dbg(1, debug, sd, "Read Reg %d %02x\n", i, v); + } + + v4l2_ctrl_handler_init(&state->hdl, 2); + v4l2_ctrl_new_std(&state->hdl, &cs53l32a_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, -96, 12, 1, 0); + v4l2_ctrl_new_std(&state->hdl, &cs53l32a_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + sd->ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + return err; + } + + /* Set cs53l32a internal register for Adaptec 2010/2410 setup */ + + cs53l32a_write(sd, 0x01, 0x21); + cs53l32a_write(sd, 0x02, 0x29); + cs53l32a_write(sd, 0x03, 0x30); + cs53l32a_write(sd, 0x04, 0x00); + cs53l32a_write(sd, 0x05, 0x00); + cs53l32a_write(sd, 0x06, 0x00); + cs53l32a_write(sd, 0x07, 0x00); + + /* Display results, should be 0x21,0x29,0x30,0x00,0x00,0x00,0x00 */ + + for (i = 1; i <= 7; i++) { + u8 v = cs53l32a_read(sd, i); + + v4l2_dbg(1, debug, sd, "Read Reg %d %02x\n", i, v); + } + return 0; +} + +static void cs53l32a_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct cs53l32a_state *state = to_state(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&state->hdl); +} + +static const struct i2c_device_id cs53l32a_id[] = { + { .name = "cs53l32a" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cs53l32a_id); + +static struct i2c_driver cs53l32a_driver = { + .driver = { + .name = "cs53l32a", + }, + .probe = cs53l32a_probe, + .remove = cs53l32a_remove, + .id_table = cs53l32a_id, +}; + +module_i2c_driver(cs53l32a_driver); diff --git a/drivers/media/i2c/cvs/Kconfig b/drivers/media/i2c/cvs/Kconfig new file mode 100644 index 000000000..4309d20dd --- /dev/null +++ b/drivers/media/i2c/cvs/Kconfig @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VIDEO_INTEL_CVS + tristate "Intel CVS CSI-2 bridge support" + depends on I2C && ACPI && VIDEO_DEV + depends on IPU_BRIDGE || !IPU_BRIDGE + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This adds support for the Intel Computer Vision Sensing (CVS). + + The driver registers a V4L2 sub-device to arbitrate CSI-2 link + ownership between the host and CVS firmware, and configures the + device for camera sensor streaming. + + The driver can operate with full I2C transport or in a reduced + platform (GPIO-only) mode when I2C is unavailable. + + Say Y to build into the kernel, or M to build as a module. + The module will be named intel_cvs. If unsure, say N. diff --git a/drivers/media/i2c/cvs/Makefile b/drivers/media/i2c/cvs/Makefile new file mode 100644 index 000000000..0aeb2c2e3 --- /dev/null +++ b/drivers/media/i2c/cvs/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +intel_cvs-y := core.o v4l2.o + +obj-$(CONFIG_VIDEO_INTEL_CVS) += intel_cvs.o diff --git a/drivers/media/i2c/cvs/core.c b/drivers/media/i2c/cvs/core.c new file mode 100644 index 000000000..d4a3b9c3b --- /dev/null +++ b/drivers/media/i2c/cvs/core.c @@ -0,0 +1,1045 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "icvs.h" + +/* Command timeouts determined experimentally */ +#define CMD_TIMEOUT (5 * HZ) +#define FW_READY_DELAY_MS 100 + +#define PCI_DEVICE_ID_INTEL_IPU7 0x645d /* MTL / LNL */ +#define PCI_DEVICE_ID_INTEL_IPU7P5 0xb05d /* ARL / PTL */ +#define PCI_DEVICE_ID_INTEL_IPU8 0xd719 /* NVL */ + +/* + * IPU7 PCI device IDs not covered by ipu6_pci_tbl in ipu6-pci-table.h. + * Once the IPU6 driver gains support for IPU7, this table can be dropped. + */ +static const struct pci_device_id icvs_ipu7_tbl[] = { + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU7) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU7P5) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU8) }, + { } +}; + +static const struct acpi_gpio_params gpio_wake = { 0, 0, false }; +static const struct acpi_gpio_params gpio_rst = { 1, 0, false }; +static const struct acpi_gpio_params gpio_req = { 2, 0, false }; +static const struct acpi_gpio_params gpio_resp = { 3, 0, false }; +static const struct acpi_gpio_mapping icvs_acpi_gpios[] = { + { "wake-gpio", &gpio_wake, 1 }, + { "rst-gpio", &gpio_rst, 1 }, + { "req-gpio", &gpio_req, 1 }, + { "resp-gpio", &gpio_resp, 1 }, + { } +}; + +static const struct acpi_gpio_params lgpio_req = { 0, 0, false }; +static const struct acpi_gpio_params lgpio_resp = { 1, 0, false }; +static const struct acpi_gpio_mapping icvs_acpi_lgpios[] = { + { "req-gpio", &lgpio_req, 1 }, + { "resp-gpio", &lgpio_resp, 1 }, + { } +}; + +/* Device quirk table */ +static const struct icvs_device_quirk cvs_quirk_table[] = { + { 0x2ac1, 0x20d0, ICVS_NO_MIPI_CONFIG | + ICVS_NO_CAPS | + ICVS_NO_FW_UPDATE + }, /* Lattice NX33 */ + { 0x06CB, 0x0701, ICVS_SKIP_FW_RESET | + ICVS_HOST_SENSOR_PWR_CTRL | + ICVS_HOST_PRIV_CTRL | + ICVS_FW_BUF_SIZE_256 | + ICVS_FW_HEADER_SIZE_256 + }, /* Synaptics SVP7xxx */ + { } +}; + +/** + * cvs_set_quirks - Match device VID/PID and set quirks + * @ctx: CVS device context + * @vid: Vendor ID + * @pid: Product ID + * + * Searches the quirk table for a matching VID/PID and populates ctx->quirks + * with the corresponding quirk flags. + * If no match is found, quirks is set to 0. + */ +static void cvs_set_quirks(struct icvs *ctx, u16 vid, u16 pid) +{ + ctx->quirks = 0; + + for (unsigned int i = 0; i < ARRAY_SIZE(cvs_quirk_table); i++) { + if (cvs_quirk_table[i].vid == vid && + cvs_quirk_table[i].pid == pid) { + ctx->quirks = cvs_quirk_table[i].quirks; + dev_info(cvs_dev(ctx), + "Quirks: 0x%lx (VID:0x%04x PID:0x%04x)\n", + ctx->quirks, vid, pid); + return; + } + } + + dev_info(cvs_dev(ctx), + "No quirks for device (VID:0x%04x PID:0x%04x)\n", vid, pid); +} + +/* I2C transport helpers */ + +/** + * cvs_read_i2c - Issue a read-type command and fetch device response + * @ctx: CVS device context + * @cmd_id: Command identifier (big endian) + * @resp: Destination buffer for response payload + * @size: Size of payload to read into @resp (without prefix) + * + * Sends @cmd_id and reads back the response in a single I2C transaction. + * When the device prepends a 4-byte protocol prefix, the combined + * prefix+payload is read into a temporary buffer and only the payload is + * copied to @resp, avoiding any dependency on the layout of the caller's + * buffer. + * + * Return: 0 on success or negative errno. + */ +static int cvs_read_i2c(struct icvs *ctx, __be16 cmd_id, void *resp, + size_t size) +{ + size_t prefix_size = ctx->prefix ? sizeof(u32) : 0; + size_t read_size = size + prefix_size; + struct i2c_client *i2c = ctx->i2c_client; + u8 *buf __free(kfree) = NULL; + int cnt; + + if (!resp || !size) + return -EINVAL; + + cnt = i2c_master_send(i2c, (const char *)&cmd_id, sizeof(cmd_id)); + if (cnt != sizeof(cmd_id)) + return cnt < 0 ? cnt : -EIO; + + buf = kmalloc(read_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + cnt = i2c_master_recv(i2c, buf, read_size); + if (cnt != read_size) { + dev_dbg(cvs_dev(ctx), "recv cmd 0x%04x short read (%d/%zu)\n", + be16_to_cpu(cmd_id), cnt, read_size); + return cnt < 0 ? cnt : -EIO; + } + + memcpy(resp, buf + prefix_size, size); + + return 0; +} + +/** + * cvs_write_i2c - Write a raw command buffer to the device over I2C + * @ctx: CVS device context + * @data: Buffer containing command + payload + * @size: Total bytes to write + * + * Return: 0 on success or negative errno. + */ +static int cvs_write_i2c(struct icvs *ctx, const void *data, int size) +{ + struct i2c_client *i2c = ctx->i2c_client; + int cnt; + + if (size < 0 || !data) + return -EINVAL; + + cnt = i2c_master_send(i2c, data, size); + if (cnt != size) { + dev_dbg(cvs_dev(ctx), "send short (%d/%d)\n", cnt, size); + return cnt < 0 ? cnt : -EIO; + } + + return 0; +} + +/** + * cvs_checksum - Simple additive checksum helper + * @data: 32-bit aligned data buffer + * @len: Length in bytes (multiple of 4) + * + * Return: 32-bit additive checksum of the dwords in @data. + */ +static u32 cvs_checksum(const void *data, size_t len) +{ + const u32 *words = data; + u32 csum = 0; + + if (WARN_ON_ONCE(len % sizeof(u32))) + return 0; + + for (unsigned int i = 0; i < len / sizeof(u32); i++) + csum += words[i]; + + return csum; +} + +/** + * cvs_schedule_and_wait - Schedule polling work then wait for completion + * @ctx: CVS device context + * @work_delay_ms: Delay in milliseconds before polling work executes + * @wait_jiffies: Timeout (jiffies) to wait for cmd completion + * + * Queues ctx->work to run after @work_delay_ms and then waits up to + * @wait_jiffies for ctx->cmd_completion. + * + * Return: 0 on success, -ETIMEDOUT on timeout, negative errno on error. + */ +static int cvs_schedule_and_wait(struct icvs *ctx, unsigned int work_delay_ms, + unsigned long wait_jiffies) +{ + int ret; + + schedule_delayed_work(&ctx->work, msecs_to_jiffies(work_delay_ms)); + ret = wait_for_completion_killable_timeout(&ctx->cmd_completion, + wait_jiffies); + if (ret < 0) + return ret; + if (!ret) + return -ETIMEDOUT; + + return 0; +} + +/** + * cvs_wait_wake_or_sleep - Wait for wake IRQ or sleep fallback + * @ctx: CVS device context + * @timeout_jiffies: Timeout (jiffies) for wake event on full-cap devices + * @sleep_ms: Milliseconds to sleep on light-cap devices + * + * For full capability devices (ICVS_FULLCAP) waits interruptibly on + * ctx->hostwake_event until ctx->hostwake_event_arg becomes true or + * @timeout_jiffies elapses. The flag is cleared after the wait. + * For light capability devices performs a blocking msleep(@sleep_ms). + * + * Return codes normalized for caller switch handling: + * <0 : error / interrupted + * -ETIMEDOUT : timeout (wake event not observed) + * 0 : success (wake observed OR light-cap sleep elapsed) + * + * Return: negative errno, -ETIMEDOUT on timeout, 0 on success. + */ +static int cvs_wait_wake_or_sleep(struct icvs *ctx, + unsigned long timeout_jiffies, + unsigned int sleep_ms) +{ + int ret; + + if (ctx->res == ICVS_FULLCAP) { + ret = wait_event_interruptible_timeout(ctx->hostwake_event, + ctx->hostwake_event_arg, + timeout_jiffies); + ctx->hostwake_event_arg = false; + if (ret < 0) + return ret; + if (!ret) + return -ETIMEDOUT; + return 0; + } + + msleep(sleep_ms); + + return 0; /* treat sleep path as success */ +} + +/** + * cvs_config_mipi - Send a HOST_SET_MIPI_CONFIG command + * @ctx: CVS device context + * @c: Command container with conf field populated + * @len: Length of original command structure (unused except for symmetry) + * + * Packages @c->param.conf into a cvs_mipi_data_packet including size and + * checksum, then writes it to the device. + * + * Firmware note: the CVS firmware expects the MIPI configuration to be + * sent as a single transaction, including all relevant parameters and checksum. + * + * Return: 0 on success, NO_MIPI_CONFIG or negative errno from I2C write. + */ +static int cvs_config_mipi(struct icvs *ctx, struct icvs_cmd *c, size_t len) +{ + struct icvs_mipi_data_packet pkt = { + .cmd_id = c->cmd_id, + .size = sizeof(c->param.conf), + .crc = cvs_checksum(&c->param.conf, sizeof(c->param.conf)), + .conf = c->param.conf, + }; + + if (ctx->quirks & ICVS_NO_MIPI_CONFIG) + return 0; + + return cvs_write_i2c(ctx, &pkt, sizeof(pkt)); +} + +/** + * cvs_get_device_state - Query current device state bitfield + * @ctx: CVS device context + * @state: Returned state value + * + * Issues GET_DEV_STATE and fills @state. + * + * Return: 0 on success or negative errno. + */ +static int cvs_get_device_state(struct icvs *ctx, u8 *state) +{ + struct icvs_resp n = { + .cmd_id = cpu_to_be16(ICVS_GET_DEV_STATE), + }; + int ret; + + ret = cvs_read_i2c(ctx, n.cmd_id, &n.resp.state, sizeof(n.resp.state)); + if (ret) + return ret; + + *state = n.resp.state; + + return 0; +} + +/** + * cvs_get_device_caps - Read protocol capabilities + * @ctx: CVS device context + * @caps: Capability structure to populate + * + * Return: 0 on success or negative errno. + */ +static int cvs_get_device_caps(struct icvs *ctx, + struct icvs_dev_capabilities *caps) +{ + struct icvs_resp n = { + .cmd_id = cpu_to_be16(ICVS_GET_DEV_CAPABILITY), + }; + int ret; + + if (ctx->quirks & ICVS_NO_CAPS) + return 0; + + ret = cvs_read_i2c(ctx, n.cmd_id, &n.resp.cap, sizeof(n.resp.cap)); + if (ret) + return ret; + + *caps = n.resp.cap; + + return 0; +} + +/** + * cvs_hw_init - Probe device for prefix support and apply quirks + * @ctx: CVS device context + * + * Sends GET_DEV_VID_PID and probes for a 32-bit prefix. + * If it matches ICVS_PREFIX_VAL, sets ctx->prefix for subsequent reads. + * Then reads VID/PID and applies matching quirks. + * GET_DEV_VID_PID is supported by all protocol versions. + * + * Return: 0 on success or negative errno. + */ +static int cvs_hw_init(struct icvs *ctx) +{ + struct icvs_resp n = { }; + __be16 cmd = cpu_to_be16(ICVS_GET_DEV_VID_PID); + u32 resp; + int ret; + + /* + * Clear prefix so cvs_read_i2c always reads exactly sizeof(u32) bytes + * here, regardless of any value left over from a previous call (e.g. + * on resume). + */ + ctx->prefix = false; + ret = cvs_read_i2c(ctx, cmd, &resp, sizeof(resp)); + if (ret) + return ret; + + ctx->prefix = resp == ICVS_PREFIX_VAL; + + /* Now read VID/PID to apply quirks */ + ret = cvs_read_i2c(ctx, cmd, + &n.resp.vid_pid, sizeof(n.resp.vid_pid)); + if (ret) + return ret; + + cvs_set_quirks(ctx, n.resp.vid_pid.v_id, n.resp.vid_pid.p_id); + + return 0; +} + +/** + * cvs_irq_handler - Wake IRQ handler (full capability devices) + * @irq: IRQ number + * @dev_id: Device context pointer + * + * Sets a waitqueue flag and wakes up sleeping waiters. + * + * Return: IRQ_HANDLED always. + */ +static irqreturn_t cvs_irq_handler(int irq, void *dev_id) +{ + struct icvs *ctx = dev_id; + + ctx->hostwake_event_arg = true; + wake_up_interruptible(&ctx->hostwake_event); + + return IRQ_HANDLED; +} + +/** + * cvs_reset - Toggle reset GPIO for full capability devices + * @ctx: CVS device context + * + * Drives reset low briefly then high if device resources indicate full + * capability. Light devices have no reset line. + */ +static void cvs_reset(struct icvs *ctx) +{ + if (ctx->quirks & ICVS_SKIP_FW_RESET) + return; + + if (ctx->res == ICVS_FULLCAP) { + gpiod_set_value(ctx->rst, 0); + fsleep(2000); + gpiod_set_value(ctx->rst, 1); + } +} + +/** + * cvs_recv - Delayed work handler polling for command completion + * @work: Embedded delayed_work member + * + * Re-reads device state; if device_busy remains set, re-schedules itself. + * Otherwise stores state into wq_resp and completes the command. + */ +static void cvs_recv(struct work_struct *work) +{ + struct icvs *ctx = container_of(work, struct icvs, work.work); + u8 state = 0; + int ret; + + ret = cvs_get_device_state(ctx, &state); + if (ret < 0) { + dev_dbg(cvs_dev(ctx), "state read failed: %d\n", ret); + return; + } + + if (state & ICVS_DEV_STATE_BUSY) { + dev_dbg(cvs_dev(ctx), "device busy, reschedule\n"); + schedule_delayed_work(&ctx->work, + msecs_to_jiffies(FW_READY_DELAY_MS)); + return; + } + + ctx->wq_resp.resp.state = state; + complete(&ctx->cmd_completion); +} + +/** + * cvs_send - Common command submission path + * @ctx: CVS device context + * @cmd: Command buffer (icvs_cmd) with cmd_id and param populated + * @len: Buffer length + * + * Dispatches a set of supported commands: + * - ICVS_SET_DEV_HOST_ID, + * - ICVS_HOST_SENSOR_OWNER, + * - ICVS_HOST_SET_MIPI_CONFIG + * - ICVS_FW_LOADER_* + * + * For I2C based commands it sets big-endian cmd ids, writes to the device + * and waits (via delayed work) for completion or timeout. + * GPIO based ownership toggles are handled locally. + * + * Caller must hold ctx->lock when invoking this function and check for i2c + * bus availability. + * + * Return: 0 on success, negative errno, -EINVAL for unsupported command + * or status from device in ctx->wq_resp. + */ +int cvs_send(struct icvs *ctx, struct icvs_cmd *cmd, size_t len) +{ + int ret, status = 0; + + lockdep_assert_held(&ctx->lock); + + dev_dbg(cvs_dev(ctx), "send cmd = 0x%04x", be16_to_cpu(cmd->cmd_id)); + + reinit_completion(&ctx->cmd_completion); + + switch (be16_to_cpu(cmd->cmd_id)) { + case ICVS_SET_DEV_HOST_ID: + cmd->cmd_id = cpu_to_be16(ICVS_SET_DEV_HOST_ID); + ret = cvs_write_i2c(ctx, cmd, len); + if (ret < 0) + break; + + ret = cvs_schedule_and_wait(ctx, FW_READY_DELAY_MS, + CMD_TIMEOUT); + if (ret < 0) + break; + + status = ctx->wq_resp.resp.state & + ICVS_DEV_STATE_ERROR ? -EINVAL : 0; + break; + case ICVS_HOST_SENSOR_OWNER: + gpiod_set_value_cansleep(ctx->req, cmd->param.param); + fsleep(FW_READY_DELAY_MS * USEC_PER_MSEC); + ret = gpiod_get_value_cansleep(ctx->resp); + status = cmd->param.param == ret ? 0 : -EINVAL; + ret = 0; /* success */ + break; + case ICVS_HOST_SET_MIPI_CONFIG: + cmd->cmd_id = cpu_to_be16(ICVS_HOST_SET_MIPI_CONFIG); + ret = cvs_config_mipi(ctx, cmd, len); + if (ret < 0) + break; + + ret = cvs_schedule_and_wait(ctx, FW_READY_DELAY_MS, + CMD_TIMEOUT); + status = (ctx->wq_resp.resp.state & + ICVS_DEV_STATE_ERROR) ? -EINVAL : 0; + break; + case ICVS_FW_LOADER_START: + cmd->cmd_id = cpu_to_be16(ICVS_FW_LOADER_START); + ret = cvs_write_i2c(ctx, cmd, len); + if (ret < 0) + break; + + ret = cvs_wait_wake_or_sleep(ctx, CMD_TIMEOUT, + FW_READY_DELAY_MS); + if (ret) + break; + + ret = cvs_schedule_and_wait(ctx, FW_READY_DELAY_MS, + CMD_TIMEOUT); + status = (ctx->wq_resp.resp.state & + ICVS_DEV_STATE_DOWNLOAD) ? 0 : -EINVAL; + break; + case ICVS_FW_LOADER_DATA: + /* Quirk for older protocols */ + if (ctx->caps.protocol_version_major >= 2 && + ctx->caps.protocol_version_minor >= 2) { + cmd->cmd_id = cpu_to_be16(ICVS_FW_LOADER_DATA); + ret = cvs_write_i2c(ctx, cmd, len); + } else { + ret = cvs_write_i2c(ctx, &cmd->param, + len - sizeof(cmd->cmd_id)); + } + + if (ret < 0) + return ret; + + ret = cvs_wait_wake_or_sleep(ctx, FW_READY_DELAY_MS, + FW_READY_DELAY_MS); + if (ret) + break; + + ret = cvs_schedule_and_wait(ctx, FW_READY_DELAY_MS, + CMD_TIMEOUT); + status = ctx->wq_resp.resp.state & + ICVS_DEV_STATE_ERROR ? -EINVAL : 0; + break; + case ICVS_FW_LOADER_END: + cmd->cmd_id = cpu_to_be16(ICVS_FW_LOADER_END); + ret = cvs_write_i2c(ctx, cmd, len); + if (ret < 0) + break; + + ret = cvs_wait_wake_or_sleep(ctx, CMD_TIMEOUT, + FW_READY_DELAY_MS); + if (ret) + break; + + ret = cvs_schedule_and_wait(ctx, FW_READY_DELAY_MS, + CMD_TIMEOUT); + status = !(ctx->wq_resp.resp.state & + ICVS_DEV_STATE_DOWNLOAD) ? 0 : -EINVAL; + break; + default: + ret = -EINVAL; + break; + } + + if (ret < 0) + return ret; + + return ctx->wq_resp.status = status; +} + +/** + * cvs_set_link_owner - Switch CSI-2 link ownership between host and device + * @ctx: CVS device context + * @owner: Desired owner (ICVS_CSI_LINK_HOST or ICVS_CSI_LINK_CVS) + * + * Called from runtime PM callbacks to claim or release the CSI-2 link. + * Also callable directly for error recovery paths. + * + * Return: 0 on success or negative errno. + */ +int cvs_set_link_owner(struct icvs *ctx, enum icvs_csi_link_owner owner) +{ + struct icvs_cmd cmd = { + .cmd_id = cpu_to_be16(ICVS_HOST_SENSOR_OWNER), + .param.param = owner, + }; + size_t cmd_size = sizeof(cmd.cmd_id) + sizeof(cmd.param.param); + + guard(mutex)(&ctx->lock); + return cvs_send(ctx, &cmd, cmd_size); +} + +/** + * cvs_configure_dev_caps - Configure device capability ownership bits + * @ctx: CVS device context + * + * Tells the CVS device which of its features (privacy LED, RGB camera + * power-up, vision sensing) are controlled by the host, then sends + * SET_DEV_HOST_ID. + * + * Return: 0 on success or negative errno. + */ +static int cvs_configure_dev_caps(struct icvs *ctx) +{ + struct icvs_cmd cmd = { .cmd_id = cpu_to_be16(ICVS_SET_DEV_HOST_ID) }; + size_t sz = sizeof(cmd.cmd_id) + sizeof(cmd.param.host_id); + + if (ctx->quirks & ICVS_NO_CAPS) + return 0; + + if (ctx->quirks & ICVS_HOST_VISION_SENSING) + cmd.param.host_id |= ICVS_HOST_ID_VISION_SENSING; + if (ctx->quirks & ICVS_HOST_PRIV_CTRL) + cmd.param.host_id |= ICVS_HOST_ID_PRIVACY_LED; + if (ctx->quirks & ICVS_HOST_SENSOR_PWR_CTRL) + cmd.param.host_id |= ICVS_HOST_ID_RGBCAMERA_PWRUP; + + guard(mutex)(&ctx->lock); + return cvs_send(ctx, &cmd, sz); +} + +/** + * cvs_core_probe - Shared probe path for I2C & platform instantiation + * @dev: Parent device + * @i2c: I2C client (NULL for platform devices) + * + * Discovers IPU, parses ACPI resources, sets up GPIOs/IRQs, initializes + * sub-device (CSI) and host identifier, and exposes sysfs firmware interface. + * + * Return: 0 on success or negative errno. + */ +static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) +{ + struct pci_dev *ipu = NULL; + struct icvs *ctx; + int ret; + + /* Locate IPU device */ + for (unsigned int i = 0; !ipu && ipu6_pci_tbl[i].vendor; i++) + ipu = pci_get_device(ipu6_pci_tbl[i].vendor, + ipu6_pci_tbl[i].device, NULL); + for (unsigned int i = 0; !ipu && icvs_ipu7_tbl[i].vendor; i++) + ipu = pci_get_device(icvs_ipu7_tbl[i].vendor, + icvs_ipu7_tbl[i].device, NULL); + if (!ipu) + return -ENODEV; + + ret = ipu_bridge_init(&ipu->dev, ipu_bridge_parse_ssdb); + if (ret < 0) + goto err_put_ipu; + + if (!dev_fwnode(dev)) { + ret = -ENXIO; + goto err_put_ipu; + } + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + ret = -ENOMEM; + goto err_put_ipu; + } + + ctx->i2c_client = i2c; + + ret = gpiod_count(dev, NULL); + switch (ret) { + case ICVS_GPIO_SYNC: + ctx->res = ICVS_LIGHTCAP; + break; + case ICVS_GPIO_ASYNC: + ctx->res = ICVS_FULLCAP; + break; + default: + dev_err(dev, "unexpected GPIO count %d\n", ret); + ret = -EINVAL; + goto err_put_ipu; + } + + ret = devm_acpi_dev_add_driver_gpios(dev, + ctx->res == ICVS_FULLCAP ? + icvs_acpi_gpios : + icvs_acpi_lgpios); + if (ret) { + dev_err_probe(dev, ret, "failed to add ACPI GPIOs\n"); + goto err_put_ipu; + } + + ctx->req = devm_gpiod_get(dev, "req", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->req)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->req), + "failed to get req GPIO\n"); + goto err_put_ipu; + } + + ctx->resp = devm_gpiod_get(dev, "resp", GPIOD_IN); + if (IS_ERR(ctx->resp)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->resp), + "failed to get resp GPIO\n"); + goto err_put_ipu; + } + + if (ctx->res == ICVS_FULLCAP) { + struct gpio_desc *wake; + + ctx->rst = devm_gpiod_get(dev, "rst", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->rst)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->rst), + "failed to get rst GPIO\n"); + goto err_put_ipu; + } + + wake = devm_gpiod_get(dev, "wake", GPIOD_IN); + if (IS_ERR(wake)) { + ret = dev_err_probe(dev, PTR_ERR(wake), + "failed to get wake GPIO\n"); + goto err_put_ipu; + } + + ctx->irq = gpiod_to_irq(wake); + if (ctx->irq < 0) { + ret = dev_err_probe(dev, ctx->irq, + "failed to get wake IRQ\n"); + goto err_put_ipu; + } + + ret = devm_request_threaded_irq(dev, ctx->irq, NULL, + cvs_irq_handler, + IRQF_ONESHOT | IRQF_NO_SUSPEND, + "cvs_wake", ctx); + if (ret) { + dev_err_probe(dev, ret, "failed to request IRQ\n"); + goto err_put_ipu; + } + } + + ret = devm_mutex_init(dev, &ctx->lock); + if (ret) + goto err_put_ipu; + + init_completion(&ctx->cmd_completion); + init_waitqueue_head(&ctx->hostwake_event); + INIT_DELAYED_WORK(&ctx->work, cvs_recv); + + if (i2c) { + ret = cvs_hw_init(ctx); + if (ret) { + dev_err(dev, "HW init failed (%d)\n", ret); + /* + * Fallback to GPIO-only mode. + * Some BIOS show the device on the I2C bus, however, + * the device is not accessible via I2C. + */ + ctx->i2c_client = NULL; + goto fail_i2c; + } + + ret = cvs_get_device_caps(ctx, &ctx->caps); + if (ret) { + dev_err_probe(dev, ret, "get caps failed\n"); + goto err_put_ipu; + } + + ret = cvs_configure_dev_caps(ctx); + if (ret) { + dev_err_probe(dev, ret, + "configure dev caps failed\n"); + goto err_put_ipu; + } + } + +fail_i2c: + ret = cvs_csi_init(ctx, dev, i2c); + if (ret) { + dev_err_probe(dev, ret, "CSI init failed\n"); + goto err_put_ipu; + } + + dev_set_drvdata(dev, ctx); + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + /* + * Create a PM runtime device link with IPU as consumer and CVS as + * supplier. When the IPU runtime-resumes to start streaming, the PM + * framework automatically resumes CVS first, triggering + * cvs_runtime_resume() which hands CSI-2 link ownership to the host. + */ + ctx->ipu_link = device_link_add(&ipu->dev, dev, + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE | + DL_FLAG_STATELESS); + if (!ctx->ipu_link) { + dev_err(dev, "IPU device link failed\n"); + ret = -ENODEV; + goto err_csi_remove; + } + + if (has_acpi_companion(dev)) + acpi_dev_clear_dependencies(ACPI_COMPANION(dev)); + + put_device(&ipu->dev); + + return 0; + +err_csi_remove: + if (ctx->ipu_link) + device_link_del(ctx->ipu_link); + cvs_csi_remove(ctx); + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + +err_put_ipu: + put_device(&ipu->dev); + + return ret; +} + +/** + * cvs_probe - I2C driver probe entry + * @i2c: I2C client + * + * Return: 0 on success or negative errno. + */ +static int cvs_probe(struct i2c_client *i2c) +{ + return cvs_core_probe(&i2c->dev, i2c); +} + +/** + * cvs_core_remove - Shared remove logic + * @dev: Device + */ +static void cvs_core_remove(struct device *dev) +{ + struct icvs *ctx = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&ctx->work); + cvs_csi_remove(ctx); + + if (ctx->ipu_link) + device_link_del(ctx->ipu_link); + + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + + cvs_reset(ctx); +} + +/** + * cvs_remove - I2C driver remove + * @client: I2C client + */ +static void cvs_remove(struct i2c_client *client) +{ + cvs_core_remove(&client->dev); +} + +/** + * cvs_suspend - System suspend callback + * @dev: Device + * + * Return: 0. + */ +static int __maybe_unused cvs_suspend(struct device *dev) +{ + struct icvs *ctx = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&ctx->work); + + return 0; +} + +/** + * cvs_resume - System resume callback + * @dev: Device + * + * Re-validates I2C link prefix and re-sends host id if transport available. + * + * Return: 0 on success or negative errno if I2C check fails. + */ +static int __maybe_unused cvs_resume(struct device *dev) +{ + struct icvs *ctx = dev_get_drvdata(dev); + int ret; + + if (ctx->i2c_client) { + ret = cvs_hw_init(ctx); + if (ret) + return ret; + return cvs_configure_dev_caps(ctx); + } + + return 0; +} + +/** + * cvs_runtime_resume - Runtime PM resume: claim CSI-2 link ownership + * @dev: Device + * + * Triggered automatically when the IPU (consumer) runtime-resumes, because + * a DL_FLAG_PM_RUNTIME device link makes CVS the supplier. Transfers CSI-2 + * link ownership to the host so the IPU can start receiving sensor frames. + * + * Return: 0 on success or negative errno. + */ +static int __maybe_unused cvs_runtime_resume(struct device *dev) +{ + struct icvs *ctx = dev_get_drvdata(dev); + + return cvs_set_link_owner(ctx, ICVS_CSI_LINK_HOST); +} + +/** + * cvs_runtime_suspend - Runtime PM suspend: release CSI-2 link ownership + * @dev: Device + * + * Called when the streaming reference is dropped by cvs_csi_disable_streams + * via pm_runtime_put_autosuspend. Returns CSI-2 link ownership to CVS firmware. + * + * Return: 0 on success or negative errno. + */ +static int __maybe_unused cvs_runtime_suspend(struct device *dev) +{ + struct icvs *ctx = dev_get_drvdata(dev); + + return cvs_set_link_owner(ctx, ICVS_CSI_LINK_CVS); +} + +static const struct dev_pm_ops __maybe_unused cvs_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cvs_suspend, cvs_resume) + SET_RUNTIME_PM_OPS(cvs_runtime_suspend, cvs_runtime_resume, NULL) +}; + +static const struct acpi_device_id intel_cvs_acpi_match[] = { + { "INTC10DE" }, /* LNL */ + { "INTC10E0" }, /* ARL */ + { "INTC10E1" }, /* PTL */ + { "INTC10FA" }, /* NVL */ + { } +}; +MODULE_DEVICE_TABLE(acpi, intel_cvs_acpi_match); + +static struct i2c_driver cvs_driver = { + .driver = { + .name = "intel_cvs", + .acpi_match_table = intel_cvs_acpi_match, + .pm = pm_ptr(&cvs_pm_ops), + }, + .probe = cvs_probe, + .remove = cvs_remove, +}; + +static int cvs_platform_probe(struct platform_device *pdev) +{ + return cvs_core_probe(&pdev->dev, NULL); +} + +static void cvs_platform_remove(struct platform_device *pdev) +{ + cvs_core_remove(&pdev->dev); +} + +/* + * Platform driver structure. + * + * Some platforms may instantiate the CVS device as a platform device + * without I2C support. This driver binding allows such platforms to use the + * CVS core functionality (GPIOs, CSI sub-device) without I2C. + */ +static struct platform_driver cvs_platform_driver = { + .driver = { + .name = "cvs_platform", + .acpi_match_table = intel_cvs_acpi_match, + .pm = pm_ptr(&cvs_pm_ops), + }, + .probe = cvs_platform_probe, + .remove = cvs_platform_remove, +}; + +/** + * cvs_init - Module init registering I2C and platform drivers + * + * Return: 0 on success or negative errno. + */ +static int __init cvs_init(void) +{ + int ret; + + ret = i2c_add_driver(&cvs_driver); + if (ret) + return ret; + + ret = platform_driver_register(&cvs_platform_driver); + if (ret) { + i2c_del_driver(&cvs_driver); + return ret; + } + + return 0; +} + +/** + * cvs_exit - Module exit unregistering drivers + */ +static void __exit cvs_exit(void) +{ + platform_driver_unregister(&cvs_platform_driver); + i2c_del_driver(&cvs_driver); +} +module_init(cvs_init); +module_exit(cvs_exit); + +MODULE_IMPORT_NS("INTEL_IPU_BRIDGE"); +MODULE_AUTHOR("Miguel Vadillo "); +MODULE_DESCRIPTION("Intel Vision Sensing Controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/cvs/icvs.h b/drivers/media/i2c/cvs/icvs.h new file mode 100644 index 000000000..17beb0920 --- /dev/null +++ b/drivers/media/i2c/cvs/icvs.h @@ -0,0 +1,493 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2026 Intel Corporation + */ + +#ifndef _ICVS_H +#define _ICVS_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * PCI device IDs for all IPU generations that co-exist with the CVS bridge. + * IPU7 (0x645d) is shared by MTL and LNL; IPU7P5 (0xb05d) is shared by + * ARL and PTL. These are not yet in the shared ipu6-pci-table so they are + * listed here alongside the IPU6 family for probe-time IPU discovery. + */ +struct gpio_desc; +struct i2c_client; + +/* + * GPIO resource counts (ACPI enumerated). + * + * 4-GPIO configuration has a wake IRQ and supports asynchronous messaging. + * 2-GPIO configuration has no IRQ, so communication is synchronous only. + */ +#define ICVS_GPIO_ASYNC 4 +#define ICVS_GPIO_SYNC 2 + +/* Firmware response prefix (optional, for protocol revision 2.x or newer) */ +#define ICVS_PREFIX_VAL 0xCAFEB0BA + +/* + * CSI bridge sub-device definitions + */ +/** + * enum icvs_csi_cmd_id - Low-level CSI bridge command identifiers + * + * These numeric IDs are part of the legacy CSI-side protocol not mapped + * directly to the higher level firmware opcodes in enum icvs_command. Only + * a minimal subset is presently issued/handled by the driver. Others are + * reserved for future expansion of the CSI bridge feature set. + * + * @ICVS_CSI_SET_OWNER: Set CSI sensor ownership between host and CVS + * @ICVS_CSI_SET_CONF: Apply CSI link configuration parameters + * @ICVS_CSI_PRIVACY_NOTIF: Notify host of a privacy state transition + */ +enum icvs_csi_cmd_id { + ICVS_CSI_SET_OWNER = 0, + ICVS_CSI_SET_CONF = 2, + ICVS_CSI_PRIVACY_NOTIF = 6, +}; + +/** + * enum icvs_csi_link_owner - CSI-2 link / sensor ownership + * + * Ownership reflects which endpoint currently controls the attached image + * sensor over the CSI-2 link. Transitions may gate streaming or reconfigure + * link parameters. The host requests ownership changes via protocol opcodes + * and may need to assert GPIO signals on platforms without full capability. + * + * @ICVS_CSI_LINK_HOST: Host (Linux) owns the sensor and may start streaming + * @ICVS_CSI_LINK_CVS: CVS firmware owns the sensor (host must not stream) + */ +enum icvs_csi_link_owner { + ICVS_CSI_LINK_HOST, + ICVS_CSI_LINK_CVS, +}; + +/** + * enum icvs_csi_privacy_status - Reported privacy state + * + * Reflects user privacy (e.g. camera LED assertion and stream gating). The + * MAX value is a sentinel used for bounds checking and is not a real state. + * + * @ICVS_CSI_PRIVACY_OFF: Privacy not asserted (LED off, streaming permitted) + * @ICVS_CSI_PRIVACY_ON: Privacy asserted (LED on and/or stream gated) + * @ICVS_CSI_PRIVACY_MAX: Sentinel; not a valid operational value + */ +enum icvs_csi_privacy_status { + ICVS_CSI_PRIVACY_OFF, + ICVS_CSI_PRIVACY_ON, + ICVS_CSI_PRIVACY_MAX +}; + +/** + * enum icvs_csi_pads - Media pads exposed by the CVS sub-device + * + * The bridge presents a single sink (from the remote sensor) and a single + * source (toward the rest of the media graph / consumers). NUM_PADS is used + * for sizing arrays and iteration; it is not a real pad index. + * + * @ICVS_CSI_PAD_SINK: Sink pad receiving frames from remote sensor + * @ICVS_CSI_PAD_SOURCE: Source pad emitting frames to downstream entities + * @ICVS_CSI_NUM_PADS: Count sentinel (array size / iteration bound) + */ +enum icvs_csi_pads { + ICVS_CSI_PAD_SINK, + ICVS_CSI_PAD_SOURCE, + ICVS_CSI_NUM_PADS +}; + +/* + * Core driver structures and functions used by both I2C and platform modes + */ + +/** + * DOC: CVS device quirk flags + * + * These bit flag macros describe per-device behavioral adjustments applied + * after VID/PID matching (see cvs_i2c_check() and cvs_apply_quirks()). They + * allow the driver to selectively alter logic paths for firmware / hardware + * variants without introducing hard-coded conditionals at each call site. + * + * @ICVS_NO_MIPI_CONFIG: Device firmware performs its own MIPI link setup; + * skip sending HOST_SET_MIPI_CONFIG. + * @ICVS_SKIP_FW_RESET: Skip issuing a post firmware-update reset sequence. + * @ICVS_NO_CAPS: Device firmware does not support GET_DEV_CAPABILITY; + * skip capability query and treat caps as unsupported. + * @ICVS_FW_BUF_SIZE_256: Firmware expects chunk transfer buffer size of 256 + * bytes (override defaults if they differ). + * @ICVS_FW_HEADER_SIZE_256: Firmware header size fixed at 256 bytes offset + * for start of payload data. + * @ICVS_HOST_SENSOR_PWR_CTRL: Host must control sensor power sequencing. + * @ICVS_HOST_PRIV_CTRL: Host owns privacy LED gating. + * @ICVS_HOST_VISION_SENSING: Host enables vision sensing capability bit + * @ICVS_NO_FW_UPDATE: Device does not support firmware update. + */ +#define ICVS_NO_MIPI_CONFIG BIT(0) +#define ICVS_SKIP_FW_RESET BIT(1) +#define ICVS_NO_CAPS BIT(2) +#define ICVS_FW_BUF_SIZE_256 BIT(3) +#define ICVS_FW_HEADER_SIZE_256 BIT(4) +#define ICVS_HOST_SENSOR_PWR_CTRL BIT(5) +#define ICVS_HOST_PRIV_CTRL BIT(6) +#define ICVS_HOST_VISION_SENSING BIT(7) +#define ICVS_NO_FW_UPDATE BIT(8) + +/** + * struct icvs_device_quirk - Device-specific quirk entry + * @vid: Vendor ID + * @pid: Product ID + * @quirks: Quirk flags for this device + */ +struct icvs_device_quirk { + u16 vid; + u16 pid; + unsigned long quirks; +}; + +/** + * struct icvs_dt_config - Data type configuration for a virtual channel + * @pixel_width: Pixel width in pixels + * @pixel_height: Pixel height in pixels + * @data_type: MIPI CSI-2 data type (RAW10, RAW12, etc.) + * @reserved: Reserved for future use + */ +struct icvs_dt_config { + u16 pixel_width; + u16 pixel_height; + u8 data_type; + u8 reserved[3]; +}; + +/** + * struct icvs_vc_config - Virtual channel configuration + * @vc: Virtual channel index (0-31) + * @dt_count: Number of data types configured + * @dt_configs: Array of data type configurations (up to 4) + * @reserved: Reserved for future use + */ +struct icvs_vc_config { + u8 vc; + u8 dt_count; + struct icvs_dt_config dt_configs[4]; + u8 reserved[6]; +}; + +/** + * struct icvs_link_cfg - Host to CVS CSI link configuration + * @fps: Frames per second + * @nr_of_lanes: Number of CSI-2 data lanes used + * @phy_mode: 0 = DPHY, 1 = CPHY + * @vc_count: Number of virtual channels enabled + * @vc_configs: Per-VC configuration + * @link_freq: Link frequency in Hz + * @reserved: Reserved for future use + */ +struct icvs_link_cfg { + u8 fps; + u8 nr_of_lanes; + u8 phy_mode; + u8 vc_count; + struct icvs_vc_config vc_configs[4]; + u32 link_freq; + u8 reserved[8]; +}; + +/** + * struct icvs_fw_version - Firmware version tuple + * @major: Major version + * @minor: Minor version + * @hotfix: Hotfix/patch level + * @build: Build number + */ +struct icvs_fw_version { + u32 major; + u32 minor; + u32 hotfix; + u32 build; +}; + +/** + * struct icvs_vid_pid - Device vendor/product IDs + * @v_id: Vendor ID + * @p_id: Product ID + */ +struct icvs_vid_pid { + u16 v_id; + u16 p_id; +}; + +/** + * struct icvs_mipi_data_packet - Encapsulated MIPI link configuration packet + * @cmd_id: Command identifier + * @size: Payload size (bytes) + * @crc: Checksum over payload + * @conf: CSI link configuration + * @reserved: Reserved for future use + */ +struct icvs_mipi_data_packet { + __be16 cmd_id; + u32 size; + u32 crc; + struct icvs_link_cfg conf; + u8 reserved[70]; +} __packed; + +/** + * struct icvs_mipi_read_packet - Read-back MIPI configuration + * @size: Payload size + * @crc: Payload checksum + * @conf: CSI link configuration + */ +struct icvs_mipi_read_packet { + u32 size; + u32 crc; + struct icvs_link_cfg conf; +}; + +/* Host identifier bitfield masks */ +#define ICVS_HOST_ID_RGBCAMERA_PWRUP BIT(31) +#define ICVS_HOST_ID_PRIVACY_LED BIT(30) +#define ICVS_HOST_ID_DEVICE_POWER GENMASK(29, 28) +#define ICVS_HOST_ID_VISION_SENSING BIT(27) + +/* Device state bitfield masks (u8) */ +#define ICVS_DEV_STATE_PRIVACY BIT(0) +#define ICVS_DEV_STATE_ON BIT(1) +#define ICVS_DEV_STATE_SENSOR_OWNER BIT(2) +#define ICVS_DEV_STATE_DOWNLOAD BIT(4) +#define ICVS_DEV_STATE_ERROR BIT(6) +#define ICVS_DEV_STATE_BUSY BIT(7) + +/* Device capability bitfield masks (u16) */ +#define ICVS_CAP_HOST_MIPI_REQUIRED BIT(15) +#define ICVS_CAP_FW_ANTIROLLBACK BIT(14) +#define ICVS_CAP_PRIVACY2VISIONDRIVER BIT(13) +#define ICVS_CAP_FWUPDATE_RESET_HOST BIT(12) +#define ICVS_CAP_NO_CAMERA_FWUPDATE BIT(11) +#define ICVS_CAP_POWER_DOMAIN_SUPPORT BIT(10) +#define ICVS_CAP_FW_FLASHED_IN_PLACE BIT(9) +#define ICVS_CAP_IO_CONTEXT_HOT BIT(8) + +/** + * struct icvs_dev_capabilities - Protocol capabilities reported by device + * @protocol_version_major: Major protocol version + * @protocol_version_minor: Minor protocol version + * @capability: Capability bitfield - use ICVS_CAP_* masks + * @max_packet_time: Max packet processing time (ms) + * @max_post_dl_time: Max post-download time (s) + */ +struct icvs_dev_capabilities { + u8 protocol_version_major; + u8 protocol_version_minor; + u16 capability; + u16 max_packet_time; + u16 max_post_dl_time; +}; + +/** + * struct icvs_cmd - Generic command container + * @cmd_id: Command identifier + * @param: Parameter union providing multiple variants + * @param.param: Raw parameter + * @param.host_id: Host identifier (ICVS_SET_DEV_HOST_ID) - use + * ICVS_HOST_ID_* masks + * @param.conf: CSI link configuration (ICVS_HOST_SET_MIPI_CONFIG) + */ +struct icvs_cmd { + __be16 cmd_id; + union { + u32 param; + u32 host_id; + struct icvs_link_cfg conf; + } param; +} __packed; + +/** + * struct icvs_resp - Firmware response container + * @status: Internal status code + * @cmd_id: Original command identifier + * @resp: Response union containing variant payload + * @resp.state: Device state response (u8, use ICVS_DEV_STATE_* masks) + * @resp.cap: Capability response + * @resp.conf: Link configuration response + * @resp.mipi_read: Raw link config read-back + * @resp.vid_pid: Vendor/product identifiers + * @resp.fw_version: Firmware version tuple + */ +struct icvs_resp { + u32 status; + __be16 cmd_id; + union { + u8 state; + struct icvs_dev_capabilities cap; + struct icvs_link_cfg conf; + struct icvs_mipi_read_packet mipi_read; + struct icvs_vid_pid vid_pid; + struct icvs_fw_version fw_version; + } resp; +}; + +/** + * enum icvs_resources - Device capability / resource category + * + * Categorizes hardware resource availability which influences protocol + * features (e.g. reset control, wake IRQ, GPIO mediated ownership). Light + * capability devices expose a reduced set of control GPIOs; full capability + * devices provide all optional signals and features. NOTSUP represents an + * unsupported or uninitialized state. + * + * @ICVS_NOTSUP: Capability not supported / not yet determined + * @ICVS_LIGHTCAP: Light capability (limited GPIO / no dedicated wake IRQ) + * @ICVS_FULLCAP: Full capability (reset GPIO, wake IRQ, extended protocol) + */ +enum icvs_resources { + ICVS_NOTSUP, + ICVS_LIGHTCAP, + ICVS_FULLCAP, +}; + +/** + * enum icvs_command - Protocol command opcodes (firmware space 0x0800+) + * @ICVS_GET_DEV_STATE: Query current device state bitfield + * @ICVS_GET_DEV_FW_VERSION: Retrieve firmware version tuple + * @ICVS_GET_DEV_VID_PID: Read vendor / product identifiers + * @ICVS_GET_DEV_ERR_CODE: Fetch last error code (if any) + * @ICVS_GET_DEV_CAPABILITY: Read protocol capability structure + * @ICVS_SET_DEV_HOST_ID: Set host identity / ownership bits + * @ICVS_GET_DEV_HOST_ID: Read back host identity + * @ICVS_FW_LOADER_START: Begin firmware download sequence + * @ICVS_FW_LOADER_DATA: Stream a chunk of firmware payload + * @ICVS_FW_LOADER_END: End firmware download / trigger flash + * @ICVS_HOST_GET_MIPI_CONFIG: Request current MIPI CSI link configuration + * @ICVS_HOST_SET_MIPI_CONFIG: Apply new MIPI CSI link configuration + * @ICVS_HOST_SENSOR_OWNER: Toggle CSI sensor ownership (GPIO assist) + */ +enum icvs_command { + ICVS_GET_DEV_STATE = 0x0800, + ICVS_GET_DEV_FW_VERSION = 0x0801, + ICVS_GET_DEV_VID_PID = 0x0802, + ICVS_GET_DEV_ERR_CODE = 0x0803, + ICVS_GET_DEV_CAPABILITY = 0x0804, + ICVS_SET_DEV_HOST_ID = 0x0805, + ICVS_GET_DEV_HOST_ID = 0x0806, + ICVS_FW_LOADER_START = 0x0820, + ICVS_FW_LOADER_DATA = 0x0821, + ICVS_FW_LOADER_END = 0x0822, + ICVS_HOST_GET_MIPI_CONFIG = 0x082F, + ICVS_HOST_SET_MIPI_CONFIG = 0x0830, + ICVS_HOST_SENSOR_OWNER = 0x0831, +}; + +/** + * enum icvs_state - Device state bitfield flags + * + * These flags correspond to bits in the device state byte returned via + * GET_DEV_STATE and decoded in union icvs_dev_state. Multiple bits may be + * asserted simultaneously. Reserved bits are omitted. + * + * @ICVS_DEVICE_OFF_STATE: Raw zero value; device is powered off or + * not yet ready + * @ICVS_DEVICE_PRIVACY_ON: Privacy mode active (LED asserted and/or + * stream gated) + * @ICVS_DEVICE_ON_STATE: Device powered and responsive to protocol commands + * @ICVS_DEVICE_SENSOR_OWNER: CVS currently owns the attached CSI sensor + * @ICVS_DEVICE_DWNLD_STATE: Firmware download / flash operation in progress + * @ICVS_DEVICE_ERROR_STATE: Device has reported an error (query + * ICVS_GET_DEV_ERR_CODE) + * @ICVS_DEVICE_BUSY_STATE: Device is busy processing a prior command + */ +enum icvs_state { + ICVS_DEVICE_OFF_STATE = 0x00, + ICVS_DEVICE_PRIVACY_ON = BIT(0), + ICVS_DEVICE_ON_STATE = BIT(1), + ICVS_DEVICE_SENSOR_OWNER = BIT(2), + ICVS_DEVICE_DWNLD_STATE = BIT(4), + ICVS_DEVICE_ERROR_STATE = BIT(6), + ICVS_DEVICE_BUSY_STATE = BIT(7), +}; + +/** + * struct icvs - Core CVS device context + * @i2c_client: I2C client (NULL in platform-only mode) + * @work: Delayed work for polling device state / completion + * @wq_resp: Last response container populated by workqueue + * @cmd_completion: Completion for command waiters + * @lock: Mutex protecting command submission & shared state + * @subdev: V4L2 sub-device representing the CSI bridge entity + * @remote: Remote media pad connected to upstream camera sensor + * @notifier: Async notifier for remote sensor discovery + * @ctrl_handler: V4L2 control handler + * @freq_ctrl: (future) frequency control pointer + * @pads: Local media pads (sink/source) + * @nr_of_lanes: Active CSI-2 lane count + * @ipu_link: PM runtime device link (IPU consumer, CVS supplier) + * @res: Resource capability (light/full) + * @caps: Reported device protocol capabilities + * @prefix: Firmware response prefix present + * @quirks: Device-specific quirk flags + * @rst: Reset GPIO (full capability only) + * @req: Request GPIO (ownership signaling) + * @resp: Response GPIO (ownership signaling) + * @irq: Wake IRQ (full capability) + * @hostwake_event: Waitqueue for wake events + * @hostwake_event_arg: Wake event flag + */ +struct icvs { + struct i2c_client *i2c_client; + struct delayed_work work; + struct icvs_resp wq_resp; + struct completion cmd_completion; + struct mutex lock; /* Protects command execution and device state */ + struct v4l2_subdev subdev; + struct media_pad *remote; + struct v4l2_async_notifier notifier; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *freq_ctrl; + struct media_pad pads[ICVS_CSI_NUM_PADS]; + u32 nr_of_lanes; + struct device_link *ipu_link; + enum icvs_resources res; + struct icvs_dev_capabilities caps; + bool prefix; + unsigned long quirks; + struct gpio_desc *rst; + struct gpio_desc *req; + struct gpio_desc *resp; + int irq; + wait_queue_head_t hostwake_event; + bool hostwake_event_arg; +}; + +/** + * cvs_dev - Helper returning the struct device for a CVS context + * @ctx: CVS context + * + * Avoids repeating transport conditional logic at each call site when + * acquiring the device pointer for logging or PM operations. + * + * Return: Device pointer (never NULL if @ctx is valid). + */ +static inline struct device *cvs_dev(struct icvs *ctx) +{ + return ctx->i2c_client ? &ctx->i2c_client->dev : ctx->subdev.dev; +} + +/* Cross-unit interfaces */ +int cvs_send(struct icvs *ctx, struct icvs_cmd *cmd, size_t len); +int cvs_set_link_owner(struct icvs *ctx, enum icvs_csi_link_owner owner); +int cvs_csi_init(struct icvs *ctx, struct device *dev, struct i2c_client *i2c); +void cvs_csi_remove(struct icvs *ctx); + +#endif /* _ICVS_H */ diff --git a/drivers/media/i2c/cvs/v4l2.c b/drivers/media/i2c/cvs/v4l2.c new file mode 100644 index 000000000..9fadca7a3 --- /dev/null +++ b/drivers/media/i2c/cvs/v4l2.c @@ -0,0 +1,617 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Intel Corporation + * CVS driver - CSI/V4L2 subdev support + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "icvs.h" + +/* + * Helpers + */ +static inline struct icvs *notifier_to_csi(struct v4l2_async_notifier *n) +{ + return container_of(n, struct icvs, notifier); +} + +static inline struct icvs *sd_to_csi(struct v4l2_subdev *sd) +{ + return container_of(sd, struct icvs, subdev); +} + +/* + * Default formats + */ +static const struct v4l2_mbus_framefmt cvs_csi_format_mbus_default = { + .width = 1, + .height = 1, + .code = MEDIA_BUS_FMT_Y8_1X8, + .field = V4L2_FIELD_NONE, +}; + +/** + * csi_set_link_cfg - Program default CSI-2 link parameters + * @ctx: CVS device context + * @link_freq: Link frequency (Hz) + * + * Populates a HOST_SET_MIPI_CONFIG command using current lane count and + * link frequency, then submits it to the device. + * Rest of the link parameters are left at firmware defaults. + * + * Return: 0 on success or negative errno. + */ +static int csi_set_link_cfg(struct icvs *ctx, u64 link_freq) +{ + struct icvs_cmd cmd = { + .cmd_id = cpu_to_be16(ICVS_HOST_SET_MIPI_CONFIG), + .param.conf.nr_of_lanes = ctx->nr_of_lanes, + .param.conf.link_freq = link_freq, + }; + size_t cmd_size = sizeof(cmd.cmd_id) + sizeof(cmd.param.conf); + + guard(mutex)(&ctx->lock); + return cvs_send(ctx, &cmd, cmd_size); +} + +/* + * Streaming + */ + +/** + * cvs_csi_enable_streams - Start streaming through the bridge + * @sd: Sub-device pointer + * @state: Active state + * @pad: Pad identifier (must be ICVS_CSI_PAD_SOURCE) + * @streams_mask: Streams to enable (bit 0 supported) + * + * Runtime-resumes the bridge (triggering cvs_runtime_resume() to claim CSI-2 + * link ownership), fetches the link frequency, programs the MIPI configuration, + * and forwards the enable request downstream. + * + * Return: 0 on success or negative errno. + */ +static int cvs_csi_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct icvs *ctx = sd_to_csi(sd); + struct v4l2_subdev *remote_sd = + media_entity_to_v4l2_subdev(ctx->remote->entity); + struct device *dev = cvs_dev(ctx); + s64 link_freq; + int ret; + + /* cvs_set_link_owner(ICVS_CSI_LINK_HOST) */ + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + link_freq = v4l2_get_link_freq(ctx->remote, 0, 0); + if (link_freq < 0) { + ret = link_freq; + goto err_rpm_put; + } + + if (ctx->i2c_client) { + ret = csi_set_link_cfg(ctx, link_freq); + if (ret < 0) + goto err_rpm_put_sync; + } + + ret = v4l2_subdev_enable_streams(remote_sd, + ctx->remote->index, + streams_mask); + if (ret) + goto err_rpm_put_sync; + + return 0; + +err_rpm_put_sync: + /* Bypass autosuspend to immediately release ownership on error */ + pm_runtime_put_sync(dev); + return ret; +err_rpm_put: + pm_runtime_put_autosuspend(dev); + return ret; +} + +/** + * cvs_csi_disable_streams - Stop streaming through the bridge + * @sd: Sub-device pointer + * @state: Active state + * @pad: Pad identifier (must be ICVS_CSI_PAD_SOURCE) + * @streams_mask: Streams to disable (bit 0 supported) + * + * Disables the remote sensor stream then drops the PM reference acquired + * during enable. After the autosuspend delay, cvs_runtime_suspend() will + * return CSI-2 link ownership to CVS firmware. + * + * Return: 0 on success or negative errno. + */ +static int cvs_csi_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct icvs *ctx = sd_to_csi(sd); + struct v4l2_subdev *remote_sd = + media_entity_to_v4l2_subdev(ctx->remote->entity); + struct device *dev = cvs_dev(ctx); + int ret; + + ret = v4l2_subdev_disable_streams(remote_sd, + ctx->remote->index, + streams_mask); + if (ret) + dev_err(dev, "disable streams failed: %d\n", ret); + + /* cvs_set_link_owner(ICVS_CSI_LINK_CVS) */ + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* + * Pad operations / formats + */ +/** + * cvs_csi_init_state - Initialize pad formats in subdev state + * @sd: Sub-device + * @state: State container + * + * Sets all pad formats to a minimal 1x1 default. + * + * Return: 0. + */ +static int cvs_csi_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + for (unsigned int i = 0; i < sd->entity.num_pads; i++) + *v4l2_subdev_state_get_format(state, i) = + cvs_csi_format_mbus_default; + + return 0; +} + +/** + * cvs_csi_set_fmt - Negotiate pad format + * @sd: Sub-device + * @state: State + * @format: Desired / returned format + * + * Mirrors sink format onto source pad. Accepts many media bus codes, falling + * back to Y8 if unsupported. Normalizes field setting. + * + * Return: 0. + */ +static int cvs_csi_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *src = + v4l2_subdev_state_get_format(state, ICVS_CSI_PAD_SOURCE); + struct v4l2_mbus_framefmt *sink = + v4l2_subdev_state_get_format(state, ICVS_CSI_PAD_SINK); + + if (format->pad == ICVS_CSI_PAD_SOURCE) { /* source pad mirrors sink */ + *src = *sink; + return 0; + } + + v4l_bound_align_image(&format->format.width, 1, 65536, 0, + &format->format.height, 1, 65536, 0, 0); + + switch (format->format.code) { + /* Accept a large list; default fallback to Y8 */ + case MEDIA_BUS_FMT_RGB444_1X12: + case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE: + case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE: + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE: + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE: + case MEDIA_BUS_FMT_RGB565_1X16: + case MEDIA_BUS_FMT_BGR565_2X8_BE: + case MEDIA_BUS_FMT_BGR565_2X8_LE: + case MEDIA_BUS_FMT_RGB565_2X8_BE: + case MEDIA_BUS_FMT_RGB565_2X8_LE: + case MEDIA_BUS_FMT_RGB666_1X18: + case MEDIA_BUS_FMT_RBG888_1X24: + case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: + case MEDIA_BUS_FMT_BGR888_1X24: + case MEDIA_BUS_FMT_GBR888_1X24: + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB888_2X12_BE: + case MEDIA_BUS_FMT_RGB888_2X12_LE: + case MEDIA_BUS_FMT_ARGB8888_1X32: + case MEDIA_BUS_FMT_RGB888_1X32_PADHI: + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_RGB121212_1X36: + case MEDIA_BUS_FMT_RGB161616_1X48: + case MEDIA_BUS_FMT_Y8_1X8: + case MEDIA_BUS_FMT_UV8_1X8: + case MEDIA_BUS_FMT_UYVY8_1_5X8: + case MEDIA_BUS_FMT_VYUY8_1_5X8: + case MEDIA_BUS_FMT_YUYV8_1_5X8: + case MEDIA_BUS_FMT_YVYU8_1_5X8: + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_VYUY8_2X8: + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_YVYU8_2X8: + case MEDIA_BUS_FMT_Y10_1X10: + case MEDIA_BUS_FMT_UYVY10_2X10: + case MEDIA_BUS_FMT_VYUY10_2X10: + case MEDIA_BUS_FMT_YUYV10_2X10: + case MEDIA_BUS_FMT_YVYU10_2X10: + case MEDIA_BUS_FMT_Y12_1X12: + case MEDIA_BUS_FMT_UYVY12_2X12: + case MEDIA_BUS_FMT_VYUY12_2X12: + case MEDIA_BUS_FMT_YUYV12_2X12: + case MEDIA_BUS_FMT_YVYU12_2X12: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_VYUY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_YVYU8_1X16: + case MEDIA_BUS_FMT_YDYUYDYV8_1X16: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_VYUY10_1X20: + case MEDIA_BUS_FMT_YUYV10_1X20: + case MEDIA_BUS_FMT_YVYU10_1X20: + case MEDIA_BUS_FMT_VUY8_1X24: + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_UYVY12_1X24: + case MEDIA_BUS_FMT_VYUY12_1X24: + case MEDIA_BUS_FMT_YUYV12_1X24: + case MEDIA_BUS_FMT_YVYU12_1X24: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + case MEDIA_BUS_FMT_AYUV8_1X32: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + case MEDIA_BUS_FMT_YUV12_1X36: + case MEDIA_BUS_FMT_YUV16_1X48: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + case MEDIA_BUS_FMT_JPEG_1X8: + case MEDIA_BUS_FMT_AHSV8888_1X32: + 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_SBGGR10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SBGGR12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SRGGB12_1X12: + case MEDIA_BUS_FMT_SBGGR14_1X14: + case MEDIA_BUS_FMT_SGBRG14_1X14: + case MEDIA_BUS_FMT_SGRBG14_1X14: + case MEDIA_BUS_FMT_SRGGB14_1X14: + case MEDIA_BUS_FMT_SBGGR16_1X16: + case MEDIA_BUS_FMT_SGBRG16_1X16: + case MEDIA_BUS_FMT_SGRBG16_1X16: + case MEDIA_BUS_FMT_SRGGB16_1X16: + break; + default: + format->format.code = MEDIA_BUS_FMT_Y8_1X8; + break; + } + + if (format->format.field == V4L2_FIELD_ANY) + format->format.field = V4L2_FIELD_NONE; + + *sink = format->format; + *src = *sink; + + return 0; +} + +/** + * cvs_csi_get_mbus_config - Provide current CSI-2 bus configuration + * @sd: Sub-device + * @pad: Pad index + * @cfg: Returned bus config + * + * Fills lane ordering and number of lanes; retrieves link frequency from + * remote entity. + * + * Return: 0 on success or negative errno. + */ +static int cvs_csi_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct icvs *ctx = sd_to_csi(sd); + s64 freq; + + cfg->type = V4L2_MBUS_CSI2_DPHY; + for (unsigned int i = 0; i < V4L2_MBUS_CSI2_MAX_DATA_LANES; i++) + cfg->bus.mipi_csi2.data_lanes[i] = i + 1; + cfg->bus.mipi_csi2.num_data_lanes = ctx->nr_of_lanes; + + freq = v4l2_get_link_freq(ctx->remote, 0, 0); + if (freq < 0) + return -EINVAL; + + cfg->link_freq = freq; + + return 0; +} + +static const struct v4l2_subdev_core_ops cvs_csi_subdev_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops cvs_csi_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops cvs_csi_pad_ops = { + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = cvs_csi_set_fmt, + .get_mbus_config = cvs_csi_get_mbus_config, + .enable_streams = cvs_csi_enable_streams, + .disable_streams = cvs_csi_disable_streams, +}; + +static const struct v4l2_subdev_ops cvs_csi_subdev_ops = { + .core = &cvs_csi_subdev_core_ops, + .video = &cvs_csi_video_ops, + .pad = &cvs_csi_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops cvs_csi_internal_ops = { + .init_state = cvs_csi_init_state, +}; + +static const struct media_entity_operations cvs_csi_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * Async notifier + */ +/** + * cvs_csi_notify_bound - Remote sensor bound callback + * @notifier: Async notifier + * @sd: Remote subdev + * @asc: Async match connection + * + * Locates the source pad of the remote sensor and creates a media link to + * the CVS bridge sink pad enabling it by default. + * + * Return: 0 on success or negative errno. + */ +static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asc) +{ + struct icvs *ctx = notifier_to_csi(notifier); + int pad; + + pad = media_entity_get_fwnode_pad(&sd->entity, asc->match.fwnode, + MEDIA_PAD_FL_SOURCE); + if (pad < 0) + return pad; + + ctx->remote = &sd->entity.pads[pad]; + + return media_create_pad_link(&sd->entity, pad, &ctx->subdev.entity, + ICVS_CSI_PAD_SINK, MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +/** + * cvs_csi_notify_unbind - Remote sensor unbind callback + * @notifier: Notifier + * @sd: Remote subdev + * @asc: Connection + */ +static void cvs_csi_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asc) +{ + struct icvs *ctx = notifier_to_csi(notifier); + + ctx->remote = NULL; +} + +static const struct v4l2_async_notifier_operations cvs_csi_notify_ops = { + .bound = cvs_csi_notify_bound, + .unbind = cvs_csi_notify_unbind, +}; + +/* + * Controls + */ +/** + * cvs_csi_init_controls - Initialize V4L2 controls + * @ctx: CVS context + * + * Currently sets up a read-only privacy control placeholder. + * + * Return: 0 on success or negative errno. + */ +static int cvs_csi_init_controls(struct icvs *ctx) +{ + struct v4l2_ctrl *privacy_ctrl; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 1); + + privacy_ctrl = v4l2_ctrl_new_std(&ctx->ctrl_handler, NULL, + V4L2_CID_PRIVACY, 0, 1, 1, 0); + if (privacy_ctrl) + privacy_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + if (ctx->ctrl_handler.error) { + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return ctx->ctrl_handler.error; + } + + ctx->subdev.ctrl_handler = &ctx->ctrl_handler; + + return 0; +} + +/* + * Firmware (graph) parsing + */ +/** + * cvs_csi_parse_firmware - Parse firmware (ACPI graph) endpoints + * @ctx: CVS context + * + * Discovers sink and remote source endpoints, validates lane counts and + * registers an async notifier for the remote sensor. + * + * Return: 0 on success or negative errno. + */ +static int cvs_csi_parse_firmware(struct icvs *ctx) +{ + struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY }; + struct device *dev = cvs_dev(ctx); + struct fwnode_handle *sink_ep, *source_ep; + struct v4l2_async_connection *asc; + int ret; + + sink_ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); + v4l2_async_subdev_nf_init(&ctx->notifier, &ctx->subdev); + ctx->notifier.ops = &cvs_csi_notify_ops; + + ret = v4l2_fwnode_endpoint_parse(sink_ep, &ep); + if (ret) + goto err_nf_cleanup; + + ctx->nr_of_lanes = ep.bus.mipi_csi2.num_data_lanes; + source_ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 1, 0, 0); + ret = v4l2_fwnode_endpoint_parse(source_ep, &ep); + fwnode_handle_put(source_ep); + if (ret) + goto err_nf_cleanup; + + if (ctx->nr_of_lanes != ep.bus.mipi_csi2.num_data_lanes) { + ret = -EINVAL; + goto err_nf_cleanup; + } + + asc = v4l2_async_nf_add_fwnode_remote(&ctx->notifier, sink_ep, + struct v4l2_async_connection); + if (IS_ERR(asc)) { + ret = PTR_ERR(asc); + goto err_nf_cleanup; + } + + ret = v4l2_async_nf_register(&ctx->notifier); + if (ret) + goto err_nf_cleanup; + + fwnode_handle_put(sink_ep); + + return 0; + +err_nf_cleanup: + v4l2_async_nf_cleanup(&ctx->notifier); + fwnode_handle_put(sink_ep); + + return ret; +} + +/* + * Public CSI init/cleanup used by core probe/remove + */ +/** + * cvs_csi_init - Initialize CSI/V4L2 sub-device side of bridge + * @ctx: CVS context + * @dev: Parent device + * @i2c: I2C client (may be NULL for platform mode) + * + * Sets up sub-device, media entity pads, async notifier, controls and + * registers with the V4L2 framework. + * + * Return: 0 on success or negative errno. + */ +int cvs_csi_init(struct icvs *ctx, struct device *dev, struct i2c_client *i2c) +{ + int ret; + + if (i2c) { + v4l2_i2c_subdev_init(&ctx->subdev, i2c, &cvs_csi_subdev_ops); + } else { + v4l2_subdev_init(&ctx->subdev, &cvs_csi_subdev_ops); + ctx->subdev.dev = dev; + } + + ctx->subdev.internal_ops = &cvs_csi_internal_ops; + v4l2_set_subdevdata(&ctx->subdev, ctx); + ctx->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + ctx->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + ctx->subdev.entity.ops = &cvs_csi_entity_ops; + snprintf(ctx->subdev.name, sizeof(ctx->subdev.name), "Intel CVS"); + + ret = cvs_csi_parse_firmware(ctx); + if (ret) + return ret; + + ret = cvs_csi_init_controls(ctx); + if (ret) + goto err_nf_unreg; + + ctx->pads[ICVS_CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + ctx->pads[ICVS_CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&ctx->subdev.entity, ICVS_CSI_NUM_PADS, + ctx->pads); + if (ret) + goto err_ctrl_cleanup; + + ctx->subdev.state_lock = ctx->ctrl_handler.lock; + ret = v4l2_subdev_init_finalize(&ctx->subdev); + if (ret) + goto err_entity_cleanup; + + ret = v4l2_async_register_subdev(&ctx->subdev); + if (ret) + goto err_entity_cleanup; + + return 0; + +err_entity_cleanup: + media_entity_cleanup(&ctx->subdev.entity); + +err_ctrl_cleanup: + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + +err_nf_unreg: + v4l2_async_nf_unregister(&ctx->notifier); + v4l2_async_nf_cleanup(&ctx->notifier); + + return ret; +} + +/** + * cvs_csi_remove - Cleanup CSI/V4L2 sub-device + * @ctx: CVS context + */ +void cvs_csi_remove(struct icvs *ctx) +{ + v4l2_async_nf_unregister(&ctx->notifier); + v4l2_async_nf_cleanup(&ctx->notifier); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_async_unregister_subdev(&ctx->subdev); + v4l2_subdev_cleanup(&ctx->subdev); + media_entity_cleanup(&ctx->subdev.entity); +} + +MODULE_AUTHOR("Miguel Vadillo "); +MODULE_DESCRIPTION("CSI/V4L2 support for Intel Vision Sensing Controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/cx25840/Kconfig b/drivers/media/i2c/cx25840/Kconfig new file mode 100644 index 000000000..46f15702c --- /dev/null +++ b/drivers/media/i2c/cx25840/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_CX25840 + tristate "Conexant CX2584x audio/video decoders" + depends on VIDEO_DEV && I2C + help + Support for the Conexant CX2584x audio/video decoders. + + To compile this driver as a module, choose M here: the + module will be called cx25840 diff --git a/drivers/media/i2c/cx25840/Makefile b/drivers/media/i2c/cx25840/Makefile new file mode 100644 index 000000000..3681df295 --- /dev/null +++ b/drivers/media/i2c/cx25840/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +cx25840-objs := cx25840-core.o cx25840-audio.o cx25840-firmware.o \ + cx25840-vbi.o cx25840-ir.o + +obj-$(CONFIG_VIDEO_CX25840) += cx25840.o diff --git a/drivers/media/i2c/cx25840/cx25840-audio.c b/drivers/media/i2c/cx25840/cx25840-audio.c new file mode 100644 index 000000000..eb77ba088 --- /dev/null +++ b/drivers/media/i2c/cx25840/cx25840-audio.c @@ -0,0 +1,558 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* cx25840 audio functions + */ + + +#include +#include +#include +#include + +#include "cx25840-core.h" + +/* + * Note: The PLL and SRC parameters are based on a reference frequency that + * would ideally be: + * + * NTSC Color subcarrier freq * 8 = 4.5 MHz/286 * 455/2 * 8 = 28.63636363... MHz + * + * However, it's not the exact reference frequency that matters, only that the + * firmware and modules that comprise the driver for a particular board all + * use the same value (close to the ideal value). + * + * Comments below will note which reference frequency is assumed for various + * parameters. They will usually be one of + * + * ref_freq = 28.636360 MHz + * or + * ref_freq = 28.636363 MHz + */ + +static int cx25840_set_audclk_freq(struct i2c_client *client, u32 freq) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + + if (state->aud_input != CX25840_AUDIO_SERIAL) { + switch (freq) { + case 32000: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x06, AUX PLL Post Divider = 0x10 + */ + cx25840_write4(client, 0x108, 0x1006040f); + + /* + * VID_PLL Fraction (register 0x10c) = 0x2be2fe + * 28636360 * 0xf.15f17f0/4 = 108 MHz + * 432 MHz pre-postdivide + */ + + /* + * AUX_PLL Fraction = 0x1bb39ee + * 28636363 * 0x6.dd9cf70/0x10 = 32000 * 384 + * 196.6 MHz pre-postdivide + * FIXME < 200 MHz is out of specified valid range + * FIXME 28636363 ref_freq doesn't match VID PLL ref + */ + cx25840_write4(client, 0x110, 0x01bb39ee); + + /* + * SA_MCLK_SEL = 1 + * SA_MCLK_DIV = 0x10 = 384/384 * AUX_PLL post dvivider + */ + cx25840_write(client, 0x127, 0x50); + + if (is_cx2583x(state)) + break; + + /* src3/4/6_ctl */ + /* 0x1.f77f = (4 * 28636360/8 * 2/455) / 32000 */ + cx25840_write4(client, 0x900, 0x0801f77f); + cx25840_write4(client, 0x904, 0x0801f77f); + cx25840_write4(client, 0x90c, 0x0801f77f); + break; + + case 44100: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x09, AUX PLL Post Divider = 0x10 + */ + cx25840_write4(client, 0x108, 0x1009040f); + + /* + * VID_PLL Fraction (register 0x10c) = 0x2be2fe + * 28636360 * 0xf.15f17f0/4 = 108 MHz + * 432 MHz pre-postdivide + */ + + /* + * AUX_PLL Fraction = 0x0ec6bd6 + * 28636363 * 0x9.7635eb0/0x10 = 44100 * 384 + * 271 MHz pre-postdivide + * FIXME 28636363 ref_freq doesn't match VID PLL ref + */ + cx25840_write4(client, 0x110, 0x00ec6bd6); + + /* + * SA_MCLK_SEL = 1 + * SA_MCLK_DIV = 0x10 = 384/384 * AUX_PLL post dvivider + */ + cx25840_write(client, 0x127, 0x50); + + if (is_cx2583x(state)) + break; + + /* src3/4/6_ctl */ + /* 0x1.6d59 = (4 * 28636360/8 * 2/455) / 44100 */ + cx25840_write4(client, 0x900, 0x08016d59); + cx25840_write4(client, 0x904, 0x08016d59); + cx25840_write4(client, 0x90c, 0x08016d59); + break; + + case 48000: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x0a, AUX PLL Post Divider = 0x10 + */ + cx25840_write4(client, 0x108, 0x100a040f); + + /* + * VID_PLL Fraction (register 0x10c) = 0x2be2fe + * 28636360 * 0xf.15f17f0/4 = 108 MHz + * 432 MHz pre-postdivide + */ + + /* + * AUX_PLL Fraction = 0x098d6e5 + * 28636363 * 0xa.4c6b728/0x10 = 48000 * 384 + * 295 MHz pre-postdivide + * FIXME 28636363 ref_freq doesn't match VID PLL ref + */ + cx25840_write4(client, 0x110, 0x0098d6e5); + + /* + * SA_MCLK_SEL = 1 + * SA_MCLK_DIV = 0x10 = 384/384 * AUX_PLL post dvivider + */ + cx25840_write(client, 0x127, 0x50); + + if (is_cx2583x(state)) + break; + + /* src3/4/6_ctl */ + /* 0x1.4faa = (4 * 28636360/8 * 2/455) / 48000 */ + cx25840_write4(client, 0x900, 0x08014faa); + cx25840_write4(client, 0x904, 0x08014faa); + cx25840_write4(client, 0x90c, 0x08014faa); + break; + } + } else { + switch (freq) { + case 32000: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x08, AUX PLL Post Divider = 0x1e + */ + cx25840_write4(client, 0x108, 0x1e08040f); + + /* + * VID_PLL Fraction (register 0x10c) = 0x2be2fe + * 28636360 * 0xf.15f17f0/4 = 108 MHz + * 432 MHz pre-postdivide + */ + + /* + * AUX_PLL Fraction = 0x12a0869 + * 28636363 * 0x8.9504348/0x1e = 32000 * 256 + * 246 MHz pre-postdivide + * FIXME 28636363 ref_freq doesn't match VID PLL ref + */ + cx25840_write4(client, 0x110, 0x012a0869); + + /* + * SA_MCLK_SEL = 1 + * SA_MCLK_DIV = 0x14 = 256/384 * AUX_PLL post dvivider + */ + cx25840_write(client, 0x127, 0x54); + + if (is_cx2583x(state)) + break; + + /* src1_ctl */ + /* 0x1.0000 = 32000/32000 */ + cx25840_write4(client, 0x8f8, 0x08010000); + + /* src3/4/6_ctl */ + /* 0x2.0000 = 2 * (32000/32000) */ + cx25840_write4(client, 0x900, 0x08020000); + cx25840_write4(client, 0x904, 0x08020000); + cx25840_write4(client, 0x90c, 0x08020000); + break; + + case 44100: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x09, AUX PLL Post Divider = 0x18 + */ + cx25840_write4(client, 0x108, 0x1809040f); + + /* + * VID_PLL Fraction (register 0x10c) = 0x2be2fe + * 28636360 * 0xf.15f17f0/4 = 108 MHz + * 432 MHz pre-postdivide + */ + + /* + * AUX_PLL Fraction = 0x0ec6bd6 + * 28636363 * 0x9.7635eb0/0x18 = 44100 * 256 + * 271 MHz pre-postdivide + * FIXME 28636363 ref_freq doesn't match VID PLL ref + */ + cx25840_write4(client, 0x110, 0x00ec6bd6); + + /* + * SA_MCLK_SEL = 1 + * SA_MCLK_DIV = 0x10 = 256/384 * AUX_PLL post dvivider + */ + cx25840_write(client, 0x127, 0x50); + + if (is_cx2583x(state)) + break; + + /* src1_ctl */ + /* 0x1.60cd = 44100/32000 */ + cx25840_write4(client, 0x8f8, 0x080160cd); + + /* src3/4/6_ctl */ + /* 0x1.7385 = 2 * (32000/44100) */ + cx25840_write4(client, 0x900, 0x08017385); + cx25840_write4(client, 0x904, 0x08017385); + cx25840_write4(client, 0x90c, 0x08017385); + break; + + case 48000: + /* + * VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04 + * AUX_PLL Integer = 0x0a, AUX PLL Post Divider = 0x18 + */ + cx25840_write4(client, 0x108, 0x180a040f); + + /* + * VID_PLL Fraction (register 0x10c) = 0x2be2fe + * 28636360 * 0xf.15f17f0/4 = 108 MHz + * 432 MHz pre-postdivide + */ + + /* + * AUX_PLL Fraction = 0x098d6e5 + * 28636363 * 0xa.4c6b728/0x18 = 48000 * 256 + * 295 MHz pre-postdivide + * FIXME 28636363 ref_freq doesn't match VID PLL ref + */ + cx25840_write4(client, 0x110, 0x0098d6e5); + + /* + * SA_MCLK_SEL = 1 + * SA_MCLK_DIV = 0x10 = 256/384 * AUX_PLL post dvivider + */ + cx25840_write(client, 0x127, 0x50); + + if (is_cx2583x(state)) + break; + + /* src1_ctl */ + /* 0x1.8000 = 48000/32000 */ + cx25840_write4(client, 0x8f8, 0x08018000); + + /* src3/4/6_ctl */ + /* 0x1.5555 = 2 * (32000/48000) */ + cx25840_write4(client, 0x900, 0x08015555); + cx25840_write4(client, 0x904, 0x08015555); + cx25840_write4(client, 0x90c, 0x08015555); + break; + } + } + + state->audclk_freq = freq; + + return 0; +} + +static inline int cx25836_set_audclk_freq(struct i2c_client *client, u32 freq) +{ + return cx25840_set_audclk_freq(client, freq); +} + +static int cx23885_set_audclk_freq(struct i2c_client *client, u32 freq) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + + if (state->aud_input != CX25840_AUDIO_SERIAL) { + switch (freq) { + case 32000: + case 44100: + case 48000: + /* We don't have register values + * so avoid destroying registers. */ + /* FIXME return -EINVAL; */ + break; + } + } else { + switch (freq) { + case 32000: + case 44100: + /* We don't have register values + * so avoid destroying registers. */ + /* FIXME return -EINVAL; */ + break; + + case 48000: + /* src1_ctl */ + /* 0x1.867c = 48000 / (2 * 28636360/8 * 2/455) */ + cx25840_write4(client, 0x8f8, 0x0801867c); + + /* src3/4/6_ctl */ + /* 0x1.4faa = (4 * 28636360/8 * 2/455) / 48000 */ + cx25840_write4(client, 0x900, 0x08014faa); + cx25840_write4(client, 0x904, 0x08014faa); + cx25840_write4(client, 0x90c, 0x08014faa); + break; + } + } + + state->audclk_freq = freq; + + return 0; +} + +static int cx231xx_set_audclk_freq(struct i2c_client *client, u32 freq) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + + if (state->aud_input != CX25840_AUDIO_SERIAL) { + switch (freq) { + case 32000: + /* src3/4/6_ctl */ + /* 0x1.f77f = (4 * 28636360/8 * 2/455) / 32000 */ + cx25840_write4(client, 0x900, 0x0801f77f); + cx25840_write4(client, 0x904, 0x0801f77f); + cx25840_write4(client, 0x90c, 0x0801f77f); + break; + + case 44100: + /* src3/4/6_ctl */ + /* 0x1.6d59 = (4 * 28636360/8 * 2/455) / 44100 */ + cx25840_write4(client, 0x900, 0x08016d59); + cx25840_write4(client, 0x904, 0x08016d59); + cx25840_write4(client, 0x90c, 0x08016d59); + break; + + case 48000: + /* src3/4/6_ctl */ + /* 0x1.4faa = (4 * 28636360/8 * 2/455) / 48000 */ + cx25840_write4(client, 0x900, 0x08014faa); + cx25840_write4(client, 0x904, 0x08014faa); + cx25840_write4(client, 0x90c, 0x08014faa); + break; + } + } else { + switch (freq) { + /* FIXME These cases make different assumptions about audclk */ + case 32000: + /* src1_ctl */ + /* 0x1.0000 = 32000/32000 */ + cx25840_write4(client, 0x8f8, 0x08010000); + + /* src3/4/6_ctl */ + /* 0x2.0000 = 2 * (32000/32000) */ + cx25840_write4(client, 0x900, 0x08020000); + cx25840_write4(client, 0x904, 0x08020000); + cx25840_write4(client, 0x90c, 0x08020000); + break; + + case 44100: + /* src1_ctl */ + /* 0x1.60cd = 44100/32000 */ + cx25840_write4(client, 0x8f8, 0x080160cd); + + /* src3/4/6_ctl */ + /* 0x1.7385 = 2 * (32000/44100) */ + cx25840_write4(client, 0x900, 0x08017385); + cx25840_write4(client, 0x904, 0x08017385); + cx25840_write4(client, 0x90c, 0x08017385); + break; + + case 48000: + /* src1_ctl */ + /* 0x1.867c = 48000 / (2 * 28636360/8 * 2/455) */ + cx25840_write4(client, 0x8f8, 0x0801867c); + + /* src3/4/6_ctl */ + /* 0x1.4faa = (4 * 28636360/8 * 2/455) / 48000 */ + cx25840_write4(client, 0x900, 0x08014faa); + cx25840_write4(client, 0x904, 0x08014faa); + cx25840_write4(client, 0x90c, 0x08014faa); + break; + } + } + + state->audclk_freq = freq; + + return 0; +} + +static int set_audclk_freq(struct i2c_client *client, u32 freq) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + + if (freq != 32000 && freq != 44100 && freq != 48000) + return -EINVAL; + + if (is_cx231xx(state)) + return cx231xx_set_audclk_freq(client, freq); + + if (is_cx2388x(state)) + return cx23885_set_audclk_freq(client, freq); + + if (is_cx2583x(state)) + return cx25836_set_audclk_freq(client, freq); + + return cx25840_set_audclk_freq(client, freq); +} + +void cx25840_audio_set_path(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + + if (!is_cx2583x(state)) { + /* assert soft reset */ + cx25840_and_or(client, 0x810, ~0x1, 0x01); + + /* stop microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0); + + /* Mute everything to prevent the PFFT! */ + cx25840_write(client, 0x8d3, 0x1f); + + if (state->aud_input == CX25840_AUDIO_SERIAL) { + /* Set Path1 to Serial Audio Input */ + cx25840_write4(client, 0x8d0, 0x01011012); + + /* The microcontroller should not be started for the + * non-tuner inputs: autodetection is specific for + * TV audio. */ + } else { + /* Set Path1 to Analog Demod Main Channel */ + cx25840_write4(client, 0x8d0, 0x1f063870); + } + } + + set_audclk_freq(client, state->audclk_freq); + + if (!is_cx2583x(state)) { + if (state->aud_input != CX25840_AUDIO_SERIAL) { + /* When the microcontroller detects the + * audio format, it will unmute the lines */ + cx25840_and_or(client, 0x803, ~0x10, 0x10); + } + + /* deassert soft reset */ + cx25840_and_or(client, 0x810, ~0x1, 0x00); + + /* Ensure the controller is running when we exit */ + if (is_cx2388x(state) || is_cx231xx(state)) + cx25840_and_or(client, 0x803, ~0x10, 0x10); + } +} + +static void set_volume(struct i2c_client *client, int volume) +{ + int vol; + + /* Convert the volume to msp3400 values (0-127) */ + vol = volume >> 9; + + /* now scale it up to cx25840 values + * -114dB to -96dB maps to 0 + * this should be 19, but in my testing that was 4dB too loud */ + if (vol <= 23) { + vol = 0; + } else { + vol -= 23; + } + + /* PATH1_VOLUME */ + cx25840_write(client, 0x8d4, 228 - (vol * 2)); +} + +static void set_balance(struct i2c_client *client, int balance) +{ + int bal = balance >> 8; + if (bal > 0x80) { + /* PATH1_BAL_LEFT */ + cx25840_and_or(client, 0x8d5, 0x7f, 0x80); + /* PATH1_BAL_LEVEL */ + cx25840_and_or(client, 0x8d5, ~0x7f, bal & 0x7f); + } else { + /* PATH1_BAL_LEFT */ + cx25840_and_or(client, 0x8d5, 0x7f, 0x00); + /* PATH1_BAL_LEVEL */ + cx25840_and_or(client, 0x8d5, ~0x7f, 0x80 - bal); + } +} + +int cx25840_s_clock_freq(struct v4l2_subdev *sd, u32 freq) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct cx25840_state *state = to_state(sd); + int retval; + + if (!is_cx2583x(state)) + cx25840_and_or(client, 0x810, ~0x1, 1); + if (state->aud_input != CX25840_AUDIO_SERIAL) { + cx25840_and_or(client, 0x803, ~0x10, 0); + cx25840_write(client, 0x8d3, 0x1f); + } + retval = set_audclk_freq(client, freq); + if (state->aud_input != CX25840_AUDIO_SERIAL) + cx25840_and_or(client, 0x803, ~0x10, 0x10); + if (!is_cx2583x(state)) + cx25840_and_or(client, 0x810, ~0x1, 0); + return retval; +} + +static int cx25840_audio_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: + if (state->mute->val) + set_volume(client, 0); + else + set_volume(client, state->volume->val); + break; + case V4L2_CID_AUDIO_BASS: + /* PATH1_EQ_BASS_VOL */ + cx25840_and_or(client, 0x8d9, ~0x3f, + 48 - (ctrl->val * 48 / 0xffff)); + break; + case V4L2_CID_AUDIO_TREBLE: + /* PATH1_EQ_TREBLE_VOL */ + cx25840_and_or(client, 0x8db, ~0x3f, + 48 - (ctrl->val * 48 / 0xffff)); + break; + case V4L2_CID_AUDIO_BALANCE: + set_balance(client, ctrl->val); + break; + default: + return -EINVAL; + } + return 0; +} + +const struct v4l2_ctrl_ops cx25840_audio_ctrl_ops = { + .s_ctrl = cx25840_audio_s_ctrl, +}; diff --git a/drivers/media/i2c/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c new file mode 100644 index 000000000..8b7dd43ed --- /dev/null +++ b/drivers/media/i2c/cx25840/cx25840-core.c @@ -0,0 +1,4008 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* cx25840 - Conexant CX25840 audio/video decoder driver + * + * Copyright (C) 2004 Ulf Eklund + * + * Based on the saa7115 driver and on the first version of Chris Kennedy's + * cx25840 driver. + * + * Changes by Tyler Trafford + * - cleanup/rewrite for V4L2 API (2005) + * + * VBI support by Hans Verkuil . + * + * NTSC sliced VBI support by Christopher Neufeld + * with additional fixes by Hans Verkuil . + * + * CX23885 support by Steven Toth . + * + * CX2388[578] IRQ handling, IO Pin mux configuration and other small fixes are + * Copyright (C) 2010 Andy Walls + * + * CX23888 DIF support for the HVR1850 + * Copyright (C) 2011 Steven Toth + * + * CX2584x pin to pad mapping and output format configuration support are + * Copyright (C) 2011 Maciej S. Szmigiero + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cx25840-core.h" + +MODULE_DESCRIPTION("Conexant CX25840 audio/video decoder driver"); +MODULE_AUTHOR("Ulf Eklund, Chris Kennedy, Hans Verkuil, Tyler Trafford"); +MODULE_LICENSE("GPL"); + +#define CX25840_VID_INT_STAT_REG 0x410 +#define CX25840_VID_INT_STAT_BITS 0x0000ffff +#define CX25840_VID_INT_MASK_BITS 0xffff0000 +#define CX25840_VID_INT_MASK_SHFT 16 +#define CX25840_VID_INT_MASK_REG 0x412 + +#define CX23885_AUD_MC_INT_MASK_REG 0x80c +#define CX23885_AUD_MC_INT_STAT_BITS 0xffff0000 +#define CX23885_AUD_MC_INT_CTRL_BITS 0x0000ffff +#define CX23885_AUD_MC_INT_STAT_SHFT 16 + +#define CX25840_AUD_INT_CTRL_REG 0x812 +#define CX25840_AUD_INT_STAT_REG 0x813 + +#define CX23885_PIN_CTRL_IRQ_REG 0x123 +#define CX23885_PIN_CTRL_IRQ_IR_STAT 0x40 +#define CX23885_PIN_CTRL_IRQ_AUD_STAT 0x20 +#define CX23885_PIN_CTRL_IRQ_VID_STAT 0x10 + +#define CX25840_IR_STATS_REG 0x210 +#define CX25840_IR_IRQEN_REG 0x214 + +static int cx25840_debug; + +module_param_named(debug, cx25840_debug, int, 0644); + +MODULE_PARM_DESC(debug, "Debugging messages [0=Off (default) 1=On]"); + +/* ----------------------------------------------------------------------- */ +static void cx23888_std_setup(struct i2c_client *client); + +int cx25840_write(struct i2c_client *client, u16 addr, u8 value) +{ + u8 buffer[3]; + + buffer[0] = addr >> 8; + buffer[1] = addr & 0xff; + buffer[2] = value; + return i2c_master_send(client, buffer, 3); +} + +int cx25840_write4(struct i2c_client *client, u16 addr, u32 value) +{ + u8 buffer[6]; + + buffer[0] = addr >> 8; + buffer[1] = addr & 0xff; + buffer[2] = value & 0xff; + buffer[3] = (value >> 8) & 0xff; + buffer[4] = (value >> 16) & 0xff; + buffer[5] = value >> 24; + return i2c_master_send(client, buffer, 6); +} + +u8 cx25840_read(struct i2c_client *client, u16 addr) +{ + struct i2c_msg msgs[2]; + u8 tx_buf[2], rx_buf[1]; + + /* Write register address */ + tx_buf[0] = addr >> 8; + tx_buf[1] = addr & 0xff; + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (char *)tx_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = 1; + msgs[1].buf = (char *)rx_buf; + + if (i2c_transfer(client->adapter, msgs, 2) < 2) + return 0; + + return rx_buf[0]; +} + +u32 cx25840_read4(struct i2c_client *client, u16 addr) +{ + struct i2c_msg msgs[2]; + u8 tx_buf[2], rx_buf[4]; + + /* Write register address */ + tx_buf[0] = addr >> 8; + tx_buf[1] = addr & 0xff; + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (char *)tx_buf; + + /* Read data from registers */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = 4; + msgs[1].buf = (char *)rx_buf; + + if (i2c_transfer(client->adapter, msgs, 2) < 2) + return 0; + + return (rx_buf[3] << 24) | (rx_buf[2] << 16) | (rx_buf[1] << 8) | + rx_buf[0]; +} + +int cx25840_and_or(struct i2c_client *client, u16 addr, unsigned int and_mask, + u8 or_value) +{ + return cx25840_write(client, addr, + (cx25840_read(client, addr) & and_mask) | + or_value); +} + +int cx25840_and_or4(struct i2c_client *client, u16 addr, u32 and_mask, + u32 or_value) +{ + return cx25840_write4(client, addr, + (cx25840_read4(client, addr) & and_mask) | + or_value); +} + +/* ----------------------------------------------------------------------- */ + +static int set_input(struct i2c_client *client, + enum cx25840_video_input vid_input, + enum cx25840_audio_input aud_input); + +/* ----------------------------------------------------------------------- */ + +static int cx23885_s_io_pin_config(struct v4l2_subdev *sd, size_t n, + struct v4l2_subdev_io_pin_config *p) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int i; + u32 pin_ctrl; + u8 gpio_oe, gpio_data, strength; + + pin_ctrl = cx25840_read4(client, 0x120); + gpio_oe = cx25840_read(client, 0x160); + gpio_data = cx25840_read(client, 0x164); + + for (i = 0; i < n; i++) { + strength = p[i].strength; + if (strength > CX25840_PIN_DRIVE_FAST) + strength = CX25840_PIN_DRIVE_FAST; + + switch (p[i].pin) { + case CX23885_PIN_IRQ_N_GPIO16: + if (p[i].function != CX23885_PAD_IRQ_N) { + /* GPIO16 */ + pin_ctrl &= ~(0x1 << 25); + } else { + /* IRQ_N */ + if (p[i].flags & + (BIT(V4L2_SUBDEV_IO_PIN_DISABLE) | + BIT(V4L2_SUBDEV_IO_PIN_INPUT))) { + pin_ctrl &= ~(0x1 << 25); + } else { + pin_ctrl |= (0x1 << 25); + } + if (p[i].flags & + BIT(V4L2_SUBDEV_IO_PIN_ACTIVE_LOW)) { + pin_ctrl &= ~(0x1 << 24); + } else { + pin_ctrl |= (0x1 << 24); + } + } + break; + case CX23885_PIN_IR_RX_GPIO19: + if (p[i].function != CX23885_PAD_GPIO19) { + /* IR_RX */ + gpio_oe |= (0x1 << 0); + pin_ctrl &= ~(0x3 << 18); + pin_ctrl |= (strength << 18); + } else { + /* GPIO19 */ + gpio_oe &= ~(0x1 << 0); + if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_SET_VALUE)) { + gpio_data &= ~(0x1 << 0); + gpio_data |= ((p[i].value & 0x1) << 0); + } + pin_ctrl &= ~(0x3 << 12); + pin_ctrl |= (strength << 12); + } + break; + case CX23885_PIN_IR_TX_GPIO20: + if (p[i].function != CX23885_PAD_GPIO20) { + /* IR_TX */ + gpio_oe |= (0x1 << 1); + if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_DISABLE)) + pin_ctrl &= ~(0x1 << 10); + else + pin_ctrl |= (0x1 << 10); + pin_ctrl &= ~(0x3 << 18); + pin_ctrl |= (strength << 18); + } else { + /* GPIO20 */ + gpio_oe &= ~(0x1 << 1); + if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_SET_VALUE)) { + gpio_data &= ~(0x1 << 1); + gpio_data |= ((p[i].value & 0x1) << 1); + } + pin_ctrl &= ~(0x3 << 12); + pin_ctrl |= (strength << 12); + } + break; + case CX23885_PIN_I2S_SDAT_GPIO21: + if (p[i].function != CX23885_PAD_GPIO21) { + /* I2S_SDAT */ + /* TODO: Input or Output config */ + gpio_oe |= (0x1 << 2); + pin_ctrl &= ~(0x3 << 22); + pin_ctrl |= (strength << 22); + } else { + /* GPIO21 */ + gpio_oe &= ~(0x1 << 2); + if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_SET_VALUE)) { + gpio_data &= ~(0x1 << 2); + gpio_data |= ((p[i].value & 0x1) << 2); + } + pin_ctrl &= ~(0x3 << 12); + pin_ctrl |= (strength << 12); + } + break; + case CX23885_PIN_I2S_WCLK_GPIO22: + if (p[i].function != CX23885_PAD_GPIO22) { + /* I2S_WCLK */ + /* TODO: Input or Output config */ + gpio_oe |= (0x1 << 3); + pin_ctrl &= ~(0x3 << 22); + pin_ctrl |= (strength << 22); + } else { + /* GPIO22 */ + gpio_oe &= ~(0x1 << 3); + if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_SET_VALUE)) { + gpio_data &= ~(0x1 << 3); + gpio_data |= ((p[i].value & 0x1) << 3); + } + pin_ctrl &= ~(0x3 << 12); + pin_ctrl |= (strength << 12); + } + break; + case CX23885_PIN_I2S_BCLK_GPIO23: + if (p[i].function != CX23885_PAD_GPIO23) { + /* I2S_BCLK */ + /* TODO: Input or Output config */ + gpio_oe |= (0x1 << 4); + pin_ctrl &= ~(0x3 << 22); + pin_ctrl |= (strength << 22); + } else { + /* GPIO23 */ + gpio_oe &= ~(0x1 << 4); + if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_SET_VALUE)) { + gpio_data &= ~(0x1 << 4); + gpio_data |= ((p[i].value & 0x1) << 4); + } + pin_ctrl &= ~(0x3 << 12); + pin_ctrl |= (strength << 12); + } + break; + } + } + + cx25840_write(client, 0x164, gpio_data); + cx25840_write(client, 0x160, gpio_oe); + cx25840_write4(client, 0x120, pin_ctrl); + return 0; +} + +static u8 cx25840_function_to_pad(struct i2c_client *client, u8 function) +{ + if (function > CX25840_PAD_VRESET) { + v4l_err(client, "invalid function %u, assuming default\n", + (unsigned int)function); + return 0; + } + + return function; +} + +static void cx25840_set_invert(u8 *pinctrl3, u8 *voutctrl4, u8 function, + u8 pin, bool invert) +{ + switch (function) { + case CX25840_PAD_IRQ_N: + if (invert) + *pinctrl3 &= ~2; + else + *pinctrl3 |= 2; + break; + + case CX25840_PAD_ACTIVE: + if (invert) + *voutctrl4 |= BIT(2); + else + *voutctrl4 &= ~BIT(2); + break; + + case CX25840_PAD_VACTIVE: + if (invert) + *voutctrl4 |= BIT(5); + else + *voutctrl4 &= ~BIT(5); + break; + + case CX25840_PAD_CBFLAG: + if (invert) + *voutctrl4 |= BIT(4); + else + *voutctrl4 &= ~BIT(4); + break; + + case CX25840_PAD_VRESET: + if (invert) + *voutctrl4 |= BIT(0); + else + *voutctrl4 &= ~BIT(0); + break; + } + + if (function != CX25840_PAD_DEFAULT) + return; + + switch (pin) { + case CX25840_PIN_DVALID_PRGM0: + if (invert) + *voutctrl4 |= BIT(6); + else + *voutctrl4 &= ~BIT(6); + break; + + case CX25840_PIN_HRESET_PRGM2: + if (invert) + *voutctrl4 |= BIT(1); + else + *voutctrl4 &= ~BIT(1); + break; + } +} + +static int cx25840_s_io_pin_config(struct v4l2_subdev *sd, size_t n, + struct v4l2_subdev_io_pin_config *p) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned int i; + u8 pinctrl[6], pinconf[10], voutctrl4; + + for (i = 0; i < 6; i++) + pinctrl[i] = cx25840_read(client, 0x114 + i); + + for (i = 0; i < 10; i++) + pinconf[i] = cx25840_read(client, 0x11c + i); + + voutctrl4 = cx25840_read(client, 0x407); + + for (i = 0; i < n; i++) { + u8 strength = p[i].strength; + + if (strength != CX25840_PIN_DRIVE_SLOW && + strength != CX25840_PIN_DRIVE_MEDIUM && + strength != CX25840_PIN_DRIVE_FAST) { + v4l_err(client, + "invalid drive speed for pin %u (%u), assuming fast\n", + (unsigned int)p[i].pin, + (unsigned int)strength); + + strength = CX25840_PIN_DRIVE_FAST; + } + + switch (p[i].pin) { + case CX25840_PIN_DVALID_PRGM0: + if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_DISABLE)) + pinctrl[0] &= ~BIT(6); + else + pinctrl[0] |= BIT(6); + + pinconf[3] &= 0xf0; + pinconf[3] |= cx25840_function_to_pad(client, + p[i].function); + + cx25840_set_invert(&pinctrl[3], &voutctrl4, + p[i].function, + CX25840_PIN_DVALID_PRGM0, + p[i].flags & + BIT(V4L2_SUBDEV_IO_PIN_ACTIVE_LOW)); + + pinctrl[4] &= ~(3 << 2); /* CX25840_PIN_DRIVE_MEDIUM */ + switch (strength) { + case CX25840_PIN_DRIVE_SLOW: + pinctrl[4] |= 1 << 2; + break; + + case CX25840_PIN_DRIVE_FAST: + pinctrl[4] |= 2 << 2; + break; + } + + break; + + case CX25840_PIN_HRESET_PRGM2: + if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_DISABLE)) + pinctrl[1] &= ~BIT(0); + else + pinctrl[1] |= BIT(0); + + pinconf[4] &= 0xf0; + pinconf[4] |= cx25840_function_to_pad(client, + p[i].function); + + cx25840_set_invert(&pinctrl[3], &voutctrl4, + p[i].function, + CX25840_PIN_HRESET_PRGM2, + p[i].flags & + BIT(V4L2_SUBDEV_IO_PIN_ACTIVE_LOW)); + + pinctrl[4] &= ~(3 << 2); /* CX25840_PIN_DRIVE_MEDIUM */ + switch (strength) { + case CX25840_PIN_DRIVE_SLOW: + pinctrl[4] |= 1 << 2; + break; + + case CX25840_PIN_DRIVE_FAST: + pinctrl[4] |= 2 << 2; + break; + } + + break; + + case CX25840_PIN_PLL_CLK_PRGM7: + if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_DISABLE)) + pinctrl[2] &= ~BIT(2); + else + pinctrl[2] |= BIT(2); + + switch (p[i].function) { + case CX25840_PAD_XTI_X5_DLL: + pinconf[6] = 0; + break; + + case CX25840_PAD_AUX_PLL: + pinconf[6] = 1; + break; + + case CX25840_PAD_VID_PLL: + pinconf[6] = 5; + break; + + case CX25840_PAD_XTI: + pinconf[6] = 2; + break; + + default: + pinconf[6] = 3; + pinconf[6] |= + cx25840_function_to_pad(client, + p[i].function) + << 4; + } + + break; + + default: + v4l_err(client, "invalid or unsupported pin %u\n", + (unsigned int)p[i].pin); + break; + } + } + + cx25840_write(client, 0x407, voutctrl4); + + for (i = 0; i < 6; i++) + cx25840_write(client, 0x114 + i, pinctrl[i]); + + for (i = 0; i < 10; i++) + cx25840_write(client, 0x11c + i, pinconf[i]); + + return 0; +} + +static int common_s_io_pin_config(struct v4l2_subdev *sd, size_t n, + struct v4l2_subdev_io_pin_config *pincfg) +{ + struct cx25840_state *state = to_state(sd); + + if (is_cx2388x(state)) + return cx23885_s_io_pin_config(sd, n, pincfg); + else if (is_cx2584x(state)) + return cx25840_s_io_pin_config(sd, n, pincfg); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static void init_dll1(struct i2c_client *client) +{ + /* + * This is the Hauppauge sequence used to + * initialize the Delay Lock Loop 1 (ADC DLL). + */ + cx25840_write(client, 0x159, 0x23); + cx25840_write(client, 0x15a, 0x87); + cx25840_write(client, 0x15b, 0x06); + udelay(10); + cx25840_write(client, 0x159, 0xe1); + udelay(10); + cx25840_write(client, 0x15a, 0x86); + cx25840_write(client, 0x159, 0xe0); + cx25840_write(client, 0x159, 0xe1); + cx25840_write(client, 0x15b, 0x10); +} + +static void init_dll2(struct i2c_client *client) +{ + /* + * This is the Hauppauge sequence used to + * initialize the Delay Lock Loop 2 (ADC DLL). + */ + cx25840_write(client, 0x15d, 0xe3); + cx25840_write(client, 0x15e, 0x86); + cx25840_write(client, 0x15f, 0x06); + udelay(10); + cx25840_write(client, 0x15d, 0xe1); + cx25840_write(client, 0x15d, 0xe0); + cx25840_write(client, 0x15d, 0xe1); +} + +static void cx25836_initialize(struct i2c_client *client) +{ + /* + *reset configuration is described on page 3-77 + * of the CX25836 datasheet + */ + + /* 2. */ + cx25840_and_or(client, 0x000, ~0x01, 0x01); + cx25840_and_or(client, 0x000, ~0x01, 0x00); + /* 3a. */ + cx25840_and_or(client, 0x15a, ~0x70, 0x00); + /* 3b. */ + cx25840_and_or(client, 0x15b, ~0x1e, 0x06); + /* 3c. */ + cx25840_and_or(client, 0x159, ~0x02, 0x02); + /* 3d. */ + udelay(10); + /* 3e. */ + cx25840_and_or(client, 0x159, ~0x02, 0x00); + /* 3f. */ + cx25840_and_or(client, 0x159, ~0xc0, 0xc0); + /* 3g. */ + cx25840_and_or(client, 0x159, ~0x01, 0x00); + cx25840_and_or(client, 0x159, ~0x01, 0x01); + /* 3h. */ + cx25840_and_or(client, 0x15b, ~0x1e, 0x10); +} + +static void cx25840_work_handler(struct work_struct *work) +{ + struct cx25840_state *state = container_of(work, struct cx25840_state, fw_work); + + cx25840_loadfw(state->c); + wake_up(&state->fw_wait); +} + +#define CX25840_VCONFIG_SET_BIT(state, opt_msk, voc, idx, bit, oneval) \ + do { \ + if ((state)->vid_config & (opt_msk)) { \ + if (((state)->vid_config & (opt_msk)) == \ + (oneval)) \ + (voc)[idx] |= BIT(bit); \ + else \ + (voc)[idx] &= ~BIT(bit); \ + } \ + } while (0) + +/* apply current vconfig to hardware regs */ +static void cx25840_vconfig_apply(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + u8 voutctrl[3]; + unsigned int i; + + for (i = 0; i < 3; i++) + voutctrl[i] = cx25840_read(client, 0x404 + i); + + if (state->vid_config & CX25840_VCONFIG_FMT_MASK) + voutctrl[0] &= ~3; + switch (state->vid_config & CX25840_VCONFIG_FMT_MASK) { + case CX25840_VCONFIG_FMT_BT656: + voutctrl[0] |= 1; + break; + + case CX25840_VCONFIG_FMT_VIP11: + voutctrl[0] |= 2; + break; + + case CX25840_VCONFIG_FMT_VIP2: + voutctrl[0] |= 3; + break; + + case CX25840_VCONFIG_FMT_BT601: + /* zero */ + default: + break; + } + + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_RES_MASK, voutctrl, + 0, 2, CX25840_VCONFIG_RES_10BIT); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VBIRAW_MASK, voutctrl, + 0, 3, CX25840_VCONFIG_VBIRAW_ENABLED); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_ANCDATA_MASK, voutctrl, + 0, 4, CX25840_VCONFIG_ANCDATA_ENABLED); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_TASKBIT_MASK, voutctrl, + 0, 5, CX25840_VCONFIG_TASKBIT_ONE); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_ACTIVE_MASK, voutctrl, + 1, 2, CX25840_VCONFIG_ACTIVE_HORIZONTAL); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VALID_MASK, voutctrl, + 1, 3, CX25840_VCONFIG_VALID_ANDACTIVE); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_HRESETW_MASK, voutctrl, + 1, 4, CX25840_VCONFIG_HRESETW_PIXCLK); + + if (state->vid_config & CX25840_VCONFIG_CLKGATE_MASK) + voutctrl[1] &= ~(3 << 6); + switch (state->vid_config & CX25840_VCONFIG_CLKGATE_MASK) { + case CX25840_VCONFIG_CLKGATE_VALID: + voutctrl[1] |= 2; + break; + + case CX25840_VCONFIG_CLKGATE_VALIDACTIVE: + voutctrl[1] |= 3; + break; + + case CX25840_VCONFIG_CLKGATE_NONE: + /* zero */ + default: + break; + } + + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_DCMODE_MASK, voutctrl, + 2, 0, CX25840_VCONFIG_DCMODE_BYTES); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_IDID0S_MASK, voutctrl, + 2, 1, CX25840_VCONFIG_IDID0S_LINECNT); + CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VIPCLAMP_MASK, voutctrl, + 2, 4, CX25840_VCONFIG_VIPCLAMP_ENABLED); + + for (i = 0; i < 3; i++) + cx25840_write(client, 0x404 + i, voutctrl[i]); +} + +static void cx25840_initialize(struct i2c_client *client) +{ + DEFINE_WAIT(wait); + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + struct workqueue_struct *q; + + /* datasheet startup in numbered steps, refer to page 3-77 */ + /* 2. */ + cx25840_and_or(client, 0x803, ~0x10, 0x00); + /* + * The default of this register should be 4, but I get 0 instead. + * Set this register to 4 manually. + */ + cx25840_write(client, 0x000, 0x04); + /* 3. */ + init_dll1(client); + init_dll2(client); + cx25840_write(client, 0x136, 0x0a); + /* 4. */ + cx25840_write(client, 0x13c, 0x01); + cx25840_write(client, 0x13c, 0x00); + /* 5. */ + /* + * Do the firmware load in a work handler to prevent. + * Otherwise the kernel is blocked waiting for the + * bit-banging i2c interface to finish uploading the + * firmware. + */ + INIT_WORK(&state->fw_work, cx25840_work_handler); + init_waitqueue_head(&state->fw_wait); + q = create_singlethread_workqueue("cx25840_fw"); + if (q) { + prepare_to_wait(&state->fw_wait, &wait, TASK_UNINTERRUPTIBLE); + queue_work(q, &state->fw_work); + schedule(); + finish_wait(&state->fw_wait, &wait); + destroy_workqueue(q); + } + + /* 6. */ + cx25840_write(client, 0x115, 0x8c); + cx25840_write(client, 0x116, 0x07); + cx25840_write(client, 0x118, 0x02); + /* 7. */ + cx25840_write(client, 0x4a5, 0x80); + cx25840_write(client, 0x4a5, 0x00); + cx25840_write(client, 0x402, 0x00); + /* 8. */ + cx25840_and_or(client, 0x401, ~0x18, 0); + cx25840_and_or(client, 0x4a2, ~0x10, 0x10); + /* steps 8c and 8d are done in change_input() */ + /* 10. */ + cx25840_write(client, 0x8d3, 0x1f); + cx25840_write(client, 0x8e3, 0x03); + + cx25840_std_setup(client); + + /* trial and error says these are needed to get audio */ + cx25840_write(client, 0x914, 0xa0); + cx25840_write(client, 0x918, 0xa0); + cx25840_write(client, 0x919, 0x01); + + /* stereo preferred */ + cx25840_write(client, 0x809, 0x04); + /* AC97 shift */ + cx25840_write(client, 0x8cf, 0x0f); + + /* (re)set input */ + set_input(client, state->vid_input, state->aud_input); + + if (state->generic_mode) + cx25840_vconfig_apply(client); + + /* start microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x10); +} + +static void cx23885_initialize(struct i2c_client *client) +{ + DEFINE_WAIT(wait); + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + u32 clk_freq = 0; + struct workqueue_struct *q; + + /* cx23885 sets hostdata to clk_freq pointer */ + if (v4l2_get_subdev_hostdata(&state->sd)) + clk_freq = *((u32 *)v4l2_get_subdev_hostdata(&state->sd)); + + /* + * Come out of digital power down + * The CX23888, at least, needs this, otherwise registers aside from + * 0x0-0x2 can't be read or written. + */ + cx25840_write(client, 0x000, 0); + + /* Internal Reset */ + cx25840_and_or(client, 0x102, ~0x01, 0x01); + cx25840_and_or(client, 0x102, ~0x01, 0x00); + + /* Stop microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x00); + + /* DIF in reset? */ + cx25840_write(client, 0x398, 0); + + /* + * Trust the default xtal, no division + * '885: 28.636363... MHz + * '887: 25.000000 MHz + * '888: 50.000000 MHz + */ + cx25840_write(client, 0x2, 0x76); + + /* Power up all the PLL's and DLL */ + cx25840_write(client, 0x1, 0x40); + + /* Sys PLL */ + switch (state->id) { + case CX23888_AV: + /* + * 50.0 MHz * (0xb + 0xe8ba26/0x2000000)/4 = 5 * 28.636363 MHz + * 572.73 MHz before post divide + */ + if (clk_freq == 25000000) { + /* 888/ImpactVCBe or 25Mhz xtal */ + ; /* nothing to do */ + } else { + /* HVR1850 or 50MHz xtal */ + cx25840_write(client, 0x2, 0x71); + } + cx25840_write4(client, 0x11c, 0x01d1744c); + cx25840_write4(client, 0x118, 0x00000416); + cx25840_write4(client, 0x404, 0x0010253e); + cx25840_write4(client, 0x42c, 0x42600000); + cx25840_write4(client, 0x44c, 0x161f1000); + break; + case CX23887_AV: + /* + * 25.0 MHz * (0x16 + 0x1d1744c/0x2000000)/4 = 5 * 28.636363 MHz + * 572.73 MHz before post divide + */ + cx25840_write4(client, 0x11c, 0x01d1744c); + cx25840_write4(client, 0x118, 0x00000416); + break; + case CX23885_AV: + default: + /* + * 28.636363 MHz * (0x14 + 0x0/0x2000000)/4 = 5 * 28.636363 MHz + * 572.73 MHz before post divide + */ + cx25840_write4(client, 0x11c, 0x00000000); + cx25840_write4(client, 0x118, 0x00000414); + break; + } + + /* Disable DIF bypass */ + cx25840_write4(client, 0x33c, 0x00000001); + + /* DIF Src phase inc */ + cx25840_write4(client, 0x340, 0x0df7df83); + + /* + * Vid PLL + * Setup for a BT.656 pixel clock of 13.5 Mpixels/second + * + * 28.636363 MHz * (0xf + 0x02be2c9/0x2000000)/4 = 8 * 13.5 MHz + * 432.0 MHz before post divide + */ + + /* HVR1850 */ + switch (state->id) { + case CX23888_AV: + if (clk_freq == 25000000) { + /* 888/ImpactVCBe or 25MHz xtal */ + cx25840_write4(client, 0x10c, 0x01b6db7b); + cx25840_write4(client, 0x108, 0x00000512); + } else { + /* 888/HVR1250 or 50MHz xtal */ + cx25840_write4(client, 0x10c, 0x13333333); + cx25840_write4(client, 0x108, 0x00000515); + } + break; + default: + cx25840_write4(client, 0x10c, 0x002be2c9); + cx25840_write4(client, 0x108, 0x0000040f); + } + + /* Luma */ + cx25840_write4(client, 0x414, 0x00107d12); + + /* Chroma */ + if (is_cx23888(state)) + cx25840_write4(client, 0x418, 0x1d008282); + else + cx25840_write4(client, 0x420, 0x3d008282); + + /* + * Aux PLL + * Initial setup for audio sample clock: + * 48 ksps, 16 bits/sample, x160 multiplier = 122.88 MHz + * Initial I2S output/master clock(?): + * 48 ksps, 16 bits/sample, x16 multiplier = 12.288 MHz + */ + switch (state->id) { + case CX23888_AV: + /* + * 50.0 MHz * (0x7 + 0x0bedfa4/0x2000000)/3 = 122.88 MHz + * 368.64 MHz before post divide + * 122.88 MHz / 0xa = 12.288 MHz + */ + /* HVR1850 or 50MHz xtal or 25MHz xtal */ + cx25840_write4(client, 0x114, 0x017dbf48); + cx25840_write4(client, 0x110, 0x000a030e); + break; + case CX23887_AV: + /* + * 25.0 MHz * (0xe + 0x17dbf48/0x2000000)/3 = 122.88 MHz + * 368.64 MHz before post divide + * 122.88 MHz / 0xa = 12.288 MHz + */ + cx25840_write4(client, 0x114, 0x017dbf48); + cx25840_write4(client, 0x110, 0x000a030e); + break; + case CX23885_AV: + default: + /* + * 28.636363 MHz * (0xc + 0x1bf0c9e/0x2000000)/3 = 122.88 MHz + * 368.64 MHz before post divide + * 122.88 MHz / 0xa = 12.288 MHz + */ + cx25840_write4(client, 0x114, 0x01bf0c9e); + cx25840_write4(client, 0x110, 0x000a030c); + break; + } + + /* ADC2 input select */ + cx25840_write(client, 0x102, 0x10); + + /* VIN1 & VIN5 */ + cx25840_write(client, 0x103, 0x11); + + /* Enable format auto detect */ + cx25840_write(client, 0x400, 0); + /* Fast subchroma lock */ + /* White crush, Chroma AGC & Chroma Killer enabled */ + cx25840_write(client, 0x401, 0xe8); + + /* Select AFE clock pad output source */ + cx25840_write(client, 0x144, 0x05); + + /* Drive GPIO2 direction and values for HVR1700 + * where an onboard mux selects the output of demodulator + * vs the 417. Failure to set this results in no DTV. + * It's safe to set this across all Hauppauge boards + * currently, regardless of the board type. + */ + cx25840_write(client, 0x160, 0x1d); + cx25840_write(client, 0x164, 0x00); + + /* + * Do the firmware load in a work handler to prevent. + * Otherwise the kernel is blocked waiting for the + * bit-banging i2c interface to finish uploading the + * firmware. + */ + INIT_WORK(&state->fw_work, cx25840_work_handler); + init_waitqueue_head(&state->fw_wait); + q = create_singlethread_workqueue("cx25840_fw"); + if (q) { + prepare_to_wait(&state->fw_wait, &wait, TASK_UNINTERRUPTIBLE); + queue_work(q, &state->fw_work); + schedule(); + finish_wait(&state->fw_wait, &wait); + destroy_workqueue(q); + } + + /* + * Call the cx23888 specific std setup func, we no longer rely on + * the generic cx24840 func. + */ + if (is_cx23888(state)) + cx23888_std_setup(client); + else + cx25840_std_setup(client); + + /* (re)set input */ + set_input(client, state->vid_input, state->aud_input); + + /* start microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x10); + + /* Disable and clear video interrupts - we don't use them */ + cx25840_write4(client, CX25840_VID_INT_STAT_REG, 0xffffffff); + + /* Disable and clear audio interrupts - we don't use them */ + cx25840_write(client, CX25840_AUD_INT_CTRL_REG, 0xff); + cx25840_write(client, CX25840_AUD_INT_STAT_REG, 0xff); + + /* CC raw enable */ + + /* + * - VIP 1.1 control codes - 10bit, blue field enable. + * - enable raw data during vertical blanking. + * - enable ancillary Data insertion for 656 or VIP. + */ + cx25840_write4(client, 0x404, 0x0010253e); + + /* CC on - VBI_LINE_CTRL3, FLD_VBI_MD_LINE12 */ + cx25840_write(client, state->vbi_regs_offset + 0x42f, 0x66); + + /* HVR-1250 / HVR1850 DIF related */ + /* Power everything up */ + cx25840_write4(client, 0x130, 0x0); + + /* SRC_COMB_CFG */ + if (is_cx23888(state)) + cx25840_write4(client, 0x454, 0x6628021F); + else + cx25840_write4(client, 0x478, 0x6628021F); + + /* AFE_CLK_OUT_CTRL - Select the clock output source as output */ + cx25840_write4(client, 0x144, 0x5); + + /* I2C_OUT_CTL - I2S output configuration as + * Master, Sony, Left justified, left sample on WS=1 + */ + cx25840_write4(client, 0x918, 0x1a0); + + /* AFE_DIAG_CTRL1 */ + cx25840_write4(client, 0x134, 0x000a1800); + + /* AFE_DIAG_CTRL3 - Inverted Polarity for Audio and Video */ + cx25840_write4(client, 0x13c, 0x00310000); +} + +/* ----------------------------------------------------------------------- */ + +static void cx231xx_initialize(struct i2c_client *client) +{ + DEFINE_WAIT(wait); + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + struct workqueue_struct *q; + + /* Internal Reset */ + cx25840_and_or(client, 0x102, ~0x01, 0x01); + cx25840_and_or(client, 0x102, ~0x01, 0x00); + + /* Stop microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x00); + + /* DIF in reset? */ + cx25840_write(client, 0x398, 0); + + /* Trust the default xtal, no division */ + /* This changes for the cx23888 products */ + cx25840_write(client, 0x2, 0x76); + + /* Bring down the regulator for AUX clk */ + cx25840_write(client, 0x1, 0x40); + + /* Disable DIF bypass */ + cx25840_write4(client, 0x33c, 0x00000001); + + /* DIF Src phase inc */ + cx25840_write4(client, 0x340, 0x0df7df83); + + /* Luma */ + cx25840_write4(client, 0x414, 0x00107d12); + + /* Chroma */ + cx25840_write4(client, 0x420, 0x3d008282); + + /* ADC2 input select */ + cx25840_write(client, 0x102, 0x10); + + /* VIN1 & VIN5 */ + cx25840_write(client, 0x103, 0x11); + + /* Enable format auto detect */ + cx25840_write(client, 0x400, 0); + /* Fast subchroma lock */ + /* White crush, Chroma AGC & Chroma Killer enabled */ + cx25840_write(client, 0x401, 0xe8); + + /* + * Do the firmware load in a work handler to prevent. + * Otherwise the kernel is blocked waiting for the + * bit-banging i2c interface to finish uploading the + * firmware. + */ + INIT_WORK(&state->fw_work, cx25840_work_handler); + init_waitqueue_head(&state->fw_wait); + q = create_singlethread_workqueue("cx25840_fw"); + if (q) { + prepare_to_wait(&state->fw_wait, &wait, TASK_UNINTERRUPTIBLE); + queue_work(q, &state->fw_work); + schedule(); + finish_wait(&state->fw_wait, &wait); + destroy_workqueue(q); + } + + cx25840_std_setup(client); + + /* (re)set input */ + set_input(client, state->vid_input, state->aud_input); + + /* start microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x10); + + /* CC raw enable */ + cx25840_write(client, 0x404, 0x0b); + + /* CC on */ + cx25840_write(client, 0x42f, 0x66); + cx25840_write4(client, 0x474, 0x1e1e601a); +} + +/* ----------------------------------------------------------------------- */ + +void cx25840_std_setup(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + v4l2_std_id std = state->std; + int hblank, hactive, burst, vblank, vactive, sc; + int vblank656, src_decimation; + int luma_lpf, uv_lpf, comb; + u32 pll_int, pll_frac, pll_post; + + /* datasheet startup, step 8d */ + if (std & ~V4L2_STD_NTSC) + cx25840_write(client, 0x49f, 0x11); + else + cx25840_write(client, 0x49f, 0x14); + + /* generic mode uses the values that the chip autoconfig would set */ + if (std & V4L2_STD_625_50) { + hblank = 132; + hactive = 720; + burst = 93; + if (state->generic_mode) { + vblank = 34; + vactive = 576; + vblank656 = 38; + } else { + vblank = 36; + vactive = 580; + vblank656 = 40; + } + src_decimation = 0x21f; + luma_lpf = 2; + + if (std & V4L2_STD_SECAM) { + uv_lpf = 0; + comb = 0; + sc = 0x0a425f; + } else if (std == V4L2_STD_PAL_Nc) { + if (state->generic_mode) { + burst = 95; + luma_lpf = 1; + } + uv_lpf = 1; + comb = 0x20; + sc = 556453; + } else { + uv_lpf = 1; + comb = 0x20; + sc = 688739; + } + } else { + hactive = 720; + hblank = 122; + vactive = 487; + luma_lpf = 1; + uv_lpf = 1; + if (state->generic_mode) { + vblank = 20; + vblank656 = 24; + } + + src_decimation = 0x21f; + if (std == V4L2_STD_PAL_60) { + if (!state->generic_mode) { + vblank = 26; + vblank656 = 26; + burst = 0x5b; + } else { + burst = 0x59; + } + luma_lpf = 2; + comb = 0x20; + sc = 688739; + } else if (std == V4L2_STD_PAL_M) { + vblank = 20; + vblank656 = 24; + burst = 0x61; + comb = 0x20; + sc = 555452; + } else { + if (!state->generic_mode) { + vblank = 26; + vblank656 = 26; + } + burst = 0x5b; + comb = 0x66; + sc = 556063; + } + } + + /* DEBUG: Displays configured PLL frequency */ + if (!is_cx231xx(state)) { + pll_int = cx25840_read(client, 0x108); + pll_frac = cx25840_read4(client, 0x10c) & 0x1ffffff; + pll_post = cx25840_read(client, 0x109); + v4l_dbg(1, cx25840_debug, client, + "PLL regs = int: %u, frac: %u, post: %u\n", + pll_int, pll_frac, pll_post); + + if (pll_post) { + int fin, fsc; + int pll = (28636363L * ((((u64)pll_int) << 25L) + pll_frac)) >> 25L; + + pll /= pll_post; + v4l_dbg(1, cx25840_debug, client, + "PLL = %d.%06d MHz\n", + pll / 1000000, pll % 1000000); + v4l_dbg(1, cx25840_debug, client, + "PLL/8 = %d.%06d MHz\n", + pll / 8000000, (pll / 8) % 1000000); + + fin = ((u64)src_decimation * pll) >> 12; + v4l_dbg(1, cx25840_debug, client, + "ADC Sampling freq = %d.%06d MHz\n", + fin / 1000000, fin % 1000000); + + fsc = (((u64)sc) * pll) >> 24L; + v4l_dbg(1, cx25840_debug, client, + "Chroma sub-carrier freq = %d.%06d MHz\n", + fsc / 1000000, fsc % 1000000); + + v4l_dbg(1, cx25840_debug, client, + "hblank %i, hactive %i, vblank %i, vactive %i, vblank656 %i, src_dec %i, burst 0x%02x, luma_lpf %i, uv_lpf %i, comb 0x%02x, sc 0x%06x\n", + hblank, hactive, vblank, vactive, vblank656, + src_decimation, burst, luma_lpf, uv_lpf, + comb, sc); + } + } + + /* Sets horizontal blanking delay and active lines */ + cx25840_write(client, 0x470, hblank); + cx25840_write(client, 0x471, + (((hblank >> 8) & 0x3) | (hactive << 4)) & 0xff); + cx25840_write(client, 0x472, hactive >> 4); + + /* Sets burst gate delay */ + cx25840_write(client, 0x473, burst); + + /* Sets vertical blanking delay and active duration */ + cx25840_write(client, 0x474, vblank); + cx25840_write(client, 0x475, + (((vblank >> 8) & 0x3) | (vactive << 4)) & 0xff); + cx25840_write(client, 0x476, vactive >> 4); + cx25840_write(client, 0x477, vblank656); + + /* Sets src decimation rate */ + cx25840_write(client, 0x478, src_decimation & 0xff); + cx25840_write(client, 0x479, (src_decimation >> 8) & 0xff); + + /* Sets Luma and UV Low pass filters */ + cx25840_write(client, 0x47a, luma_lpf << 6 | ((uv_lpf << 4) & 0x30)); + + /* Enables comb filters */ + cx25840_write(client, 0x47b, comb); + + /* Sets SC Step*/ + cx25840_write(client, 0x47c, sc); + cx25840_write(client, 0x47d, (sc >> 8) & 0xff); + cx25840_write(client, 0x47e, (sc >> 16) & 0xff); + + /* Sets VBI parameters */ + if (std & V4L2_STD_625_50) { + cx25840_write(client, 0x47f, 0x01); + state->vbi_line_offset = 5; + } else { + cx25840_write(client, 0x47f, 0x00); + state->vbi_line_offset = 8; + } +} + +/* ----------------------------------------------------------------------- */ + +static void input_change(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + v4l2_std_id std = state->std; + + /* Follow step 8c and 8d of section 3.16 in the cx25840 datasheet */ + if (std & V4L2_STD_SECAM) { + cx25840_write(client, 0x402, 0); + } else { + cx25840_write(client, 0x402, 0x04); + cx25840_write(client, 0x49f, + (std & V4L2_STD_NTSC) ? 0x14 : 0x11); + } + cx25840_and_or(client, 0x401, ~0x60, 0); + cx25840_and_or(client, 0x401, ~0x60, 0x60); + + /* Don't write into audio registers on cx2583x chips */ + if (is_cx2583x(state)) + return; + + cx25840_and_or(client, 0x810, ~0x01, 1); + + if (state->radio) { + cx25840_write(client, 0x808, 0xf9); + cx25840_write(client, 0x80b, 0x00); + } else if (std & V4L2_STD_525_60) { + /* + * Certain Hauppauge PVR150 models have a hardware bug + * that causes audio to drop out. For these models the + * audio standard must be set explicitly. + * To be precise: it affects cards with tuner models + * 85, 99 and 112 (model numbers from tveeprom). + */ + int hw_fix = state->pvr150_workaround; + + if (std == V4L2_STD_NTSC_M_JP) { + /* Japan uses EIAJ audio standard */ + cx25840_write(client, 0x808, hw_fix ? 0x2f : 0xf7); + } else if (std == V4L2_STD_NTSC_M_KR) { + /* South Korea uses A2 audio standard */ + cx25840_write(client, 0x808, hw_fix ? 0x3f : 0xf8); + } else { + /* Others use the BTSC audio standard */ + cx25840_write(client, 0x808, hw_fix ? 0x1f : 0xf6); + } + cx25840_write(client, 0x80b, 0x00); + } else if (std & V4L2_STD_PAL) { + /* Autodetect audio standard and audio system */ + cx25840_write(client, 0x808, 0xff); + /* + * Since system PAL-L is pretty much non-existent and + * not used by any public broadcast network, force + * 6.5 MHz carrier to be interpreted as System DK, + * this avoids DK audio detection instability + */ + cx25840_write(client, 0x80b, 0x00); + } else if (std & V4L2_STD_SECAM) { + /* Autodetect audio standard and audio system */ + cx25840_write(client, 0x808, 0xff); + /* + * If only one of SECAM-DK / SECAM-L is required, then force + * 6.5MHz carrier, else autodetect it + */ + if ((std & V4L2_STD_SECAM_DK) && + !(std & (V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC))) { + /* 6.5 MHz carrier to be interpreted as System DK */ + cx25840_write(client, 0x80b, 0x00); + } else if (!(std & V4L2_STD_SECAM_DK) && + (std & (V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC))) { + /* 6.5 MHz carrier to be interpreted as System L */ + cx25840_write(client, 0x80b, 0x08); + } else { + /* 6.5 MHz carrier to be autodetected */ + cx25840_write(client, 0x80b, 0x10); + } + } + + cx25840_and_or(client, 0x810, ~0x01, 0); +} + +static int set_input(struct i2c_client *client, + enum cx25840_video_input vid_input, + enum cx25840_audio_input aud_input) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + u8 is_composite = (vid_input >= CX25840_COMPOSITE1 && + vid_input <= CX25840_COMPOSITE8); + u8 is_component = (vid_input & CX25840_COMPONENT_ON) == + CX25840_COMPONENT_ON; + u8 is_dif = (vid_input & CX25840_DIF_ON) == + CX25840_DIF_ON; + u8 is_svideo = (vid_input & CX25840_SVIDEO_ON) == + CX25840_SVIDEO_ON; + int luma = vid_input & 0xf0; + int chroma = vid_input & 0xf00; + u8 reg; + u32 val; + + v4l_dbg(1, cx25840_debug, client, + "decoder set video input %d, audio input %d\n", + vid_input, aud_input); + + if (vid_input >= CX25840_VIN1_CH1) { + v4l_dbg(1, cx25840_debug, client, "vid_input 0x%x\n", + vid_input); + reg = vid_input & 0xff; + is_composite = !is_component && + ((vid_input & CX25840_SVIDEO_ON) != CX25840_SVIDEO_ON); + + v4l_dbg(1, cx25840_debug, client, "mux cfg 0x%x comp=%d\n", + reg, is_composite); + } else if (is_composite) { + reg = 0xf0 + (vid_input - CX25840_COMPOSITE1); + } else { + if ((vid_input & ~0xff0) || + luma < CX25840_SVIDEO_LUMA1 || + luma > CX25840_SVIDEO_LUMA8 || + chroma < CX25840_SVIDEO_CHROMA4 || + chroma > CX25840_SVIDEO_CHROMA8) { + v4l_err(client, "0x%04x is not a valid video input!\n", + vid_input); + return -EINVAL; + } + reg = 0xf0 + ((luma - CX25840_SVIDEO_LUMA1) >> 4); + if (chroma >= CX25840_SVIDEO_CHROMA7) { + reg &= 0x3f; + reg |= (chroma - CX25840_SVIDEO_CHROMA7) >> 2; + } else { + reg &= 0xcf; + reg |= (chroma - CX25840_SVIDEO_CHROMA4) >> 4; + } + } + + /* The caller has previously prepared the correct routing + * configuration in reg (for the cx23885) so we have no + * need to attempt to flip bits for earlier av decoders. + */ + if (!is_cx2388x(state) && !is_cx231xx(state)) { + switch (aud_input) { + case CX25840_AUDIO_SERIAL: + /* do nothing, use serial audio input */ + break; + case CX25840_AUDIO4: + reg &= ~0x30; + break; + case CX25840_AUDIO5: + reg &= ~0x30; + reg |= 0x10; + break; + case CX25840_AUDIO6: + reg &= ~0x30; + reg |= 0x20; + break; + case CX25840_AUDIO7: + reg &= ~0xc0; + break; + case CX25840_AUDIO8: + reg &= ~0xc0; + reg |= 0x40; + break; + default: + v4l_err(client, "0x%04x is not a valid audio input!\n", + aud_input); + return -EINVAL; + } + } + + cx25840_write(client, 0x103, reg); + + /* Set INPUT_MODE to Composite, S-Video or Component */ + if (is_component) + cx25840_and_or(client, 0x401, ~0x6, 0x6); + else + cx25840_and_or(client, 0x401, ~0x6, is_composite ? 0 : 0x02); + + if (is_cx2388x(state)) { + /* Enable or disable the DIF for tuner use */ + if (is_dif) { + cx25840_and_or(client, 0x102, ~0x80, 0x80); + + /* Set of defaults for NTSC and PAL */ + cx25840_write4(client, 0x31c, 0xc2262600); + cx25840_write4(client, 0x320, 0xc2262600); + + /* 18271 IF - Nobody else yet uses a different + * tuner with the DIF, so these are reasonable + * assumptions (HVR1250 and HVR1850 specific). + */ + cx25840_write4(client, 0x318, 0xda262600); + cx25840_write4(client, 0x33c, 0x2a24c800); + cx25840_write4(client, 0x104, 0x0704dd00); + } else { + cx25840_write4(client, 0x300, 0x015c28f5); + + cx25840_and_or(client, 0x102, ~0x80, 0); + cx25840_write4(client, 0x340, 0xdf7df83); + cx25840_write4(client, 0x104, 0x0704dd80); + cx25840_write4(client, 0x314, 0x22400600); + cx25840_write4(client, 0x318, 0x40002600); + cx25840_write4(client, 0x324, 0x40002600); + cx25840_write4(client, 0x32c, 0x0250e620); + cx25840_write4(client, 0x39c, 0x01FF0B00); + + cx25840_write4(client, 0x410, 0xffff0dbf); + cx25840_write4(client, 0x414, 0x00137d03); + + if (is_cx23888(state)) { + /* 888 MISC_TIM_CTRL */ + cx25840_write4(client, 0x42c, 0x42600000); + /* 888 FIELD_COUNT */ + cx25840_write4(client, 0x430, 0x0000039b); + /* 888 VSCALE_CTRL */ + cx25840_write4(client, 0x438, 0x00000000); + /* 888 DFE_CTRL1 */ + cx25840_write4(client, 0x440, 0xF8E3E824); + /* 888 DFE_CTRL2 */ + cx25840_write4(client, 0x444, 0x401040dc); + /* 888 DFE_CTRL3 */ + cx25840_write4(client, 0x448, 0xcd3f02a0); + /* 888 PLL_CTRL */ + cx25840_write4(client, 0x44c, 0x161f1000); + /* 888 HTL_CTRL */ + cx25840_write4(client, 0x450, 0x00000802); + } + cx25840_write4(client, 0x91c, 0x01000000); + cx25840_write4(client, 0x8e0, 0x03063870); + cx25840_write4(client, 0x8d4, 0x7FFF0024); + cx25840_write4(client, 0x8d0, 0x00063073); + + cx25840_write4(client, 0x8c8, 0x00010000); + cx25840_write4(client, 0x8cc, 0x00080023); + + /* DIF BYPASS */ + cx25840_write4(client, 0x33c, 0x2a04c800); + } + + /* Reset the DIF */ + cx25840_write4(client, 0x398, 0); + } + + if (!is_cx2388x(state) && !is_cx231xx(state)) { + /* Set CH_SEL_ADC2 to 1 if input comes from CH3 */ + cx25840_and_or(client, 0x102, ~0x2, (reg & 0x80) == 0 ? 2 : 0); + /* Set DUAL_MODE_ADC2 to 1 if input comes from both CH2&CH3 */ + if ((reg & 0xc0) != 0xc0 && (reg & 0x30) != 0x30) + cx25840_and_or(client, 0x102, ~0x4, 4); + else + cx25840_and_or(client, 0x102, ~0x4, 0); + } else { + /* Set DUAL_MODE_ADC2 to 1 if component*/ + cx25840_and_or(client, 0x102, ~0x4, is_component ? 0x4 : 0x0); + if (is_composite) { + /* ADC2 input select channel 2 */ + cx25840_and_or(client, 0x102, ~0x2, 0); + } else if (!is_component) { + /* S-Video */ + if (chroma >= CX25840_SVIDEO_CHROMA7) { + /* ADC2 input select channel 3 */ + cx25840_and_or(client, 0x102, ~0x2, 2); + } else { + /* ADC2 input select channel 2 */ + cx25840_and_or(client, 0x102, ~0x2, 0); + } + } + + /* cx23885 / SVIDEO */ + if (is_cx2388x(state) && is_svideo) { +#define AFE_CTRL (0x104) +#define MODE_CTRL (0x400) + cx25840_and_or(client, 0x102, ~0x2, 0x2); + + val = cx25840_read4(client, MODE_CTRL); + val &= 0xFFFFF9FF; + + /* YC */ + val |= 0x00000200; + val &= ~0x2000; + cx25840_write4(client, MODE_CTRL, val); + + val = cx25840_read4(client, AFE_CTRL); + + /* Chroma in select */ + val |= 0x00001000; + val &= 0xfffffe7f; + /* Clear VGA_SEL_CH2 and VGA_SEL_CH3 (bits 7 and 8). + * This sets them to use video rather than audio. + * Only one of the two will be in use. + */ + cx25840_write4(client, AFE_CTRL, val); + } else { + cx25840_and_or(client, 0x102, ~0x2, 0); + } + } + + state->vid_input = vid_input; + state->aud_input = aud_input; + cx25840_audio_set_path(client); + input_change(client); + + if (is_cx2388x(state)) { + /* Audio channel 1 src : Parallel 1 */ + cx25840_write(client, 0x124, 0x03); + + /* Select AFE clock pad output source */ + cx25840_write(client, 0x144, 0x05); + + /* I2S_IN_CTL: I2S_IN_SONY_MODE, LEFT SAMPLE on WS=1 */ + cx25840_write(client, 0x914, 0xa0); + + /* I2S_OUT_CTL: + * I2S_IN_SONY_MODE, LEFT SAMPLE on WS=1 + * I2S_OUT_MASTER_MODE = Master + */ + cx25840_write(client, 0x918, 0xa0); + cx25840_write(client, 0x919, 0x01); + } else if (is_cx231xx(state)) { + /* Audio channel 1 src : Parallel 1 */ + cx25840_write(client, 0x124, 0x03); + + /* I2S_IN_CTL: I2S_IN_SONY_MODE, LEFT SAMPLE on WS=1 */ + cx25840_write(client, 0x914, 0xa0); + + /* I2S_OUT_CTL: + * I2S_IN_SONY_MODE, LEFT SAMPLE on WS=1 + * I2S_OUT_MASTER_MODE = Master + */ + cx25840_write(client, 0x918, 0xa0); + cx25840_write(client, 0x919, 0x01); + } + + if (is_cx2388x(state) && + ((aud_input == CX25840_AUDIO7) || (aud_input == CX25840_AUDIO6))) { + /* Configure audio from LR1 or LR2 input */ + cx25840_write4(client, 0x910, 0); + cx25840_write4(client, 0x8d0, 0x63073); + } else if (is_cx2388x(state) && (aud_input == CX25840_AUDIO8)) { + /* Configure audio from tuner/sif input */ + cx25840_write4(client, 0x910, 0x12b000c9); + cx25840_write4(client, 0x8d0, 0x1f063870); + } + + if (is_cx23888(state)) { + /* + * HVR1850 + * + * AUD_IO_CTRL - I2S Input, Parallel1 + * - Channel 1 src - Parallel1 (Merlin out) + * - Channel 2 src - Parallel2 (Merlin out) + * - Channel 3 src - Parallel3 (Merlin AC97 out) + * - I2S source and dir - Merlin, output + */ + cx25840_write4(client, 0x124, 0x100); + + if (!is_dif) { + /* + * Stop microcontroller if we don't need it + * to avoid audio popping on svideo/composite use. + */ + cx25840_and_or(client, 0x803, ~0x10, 0x00); + } + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int set_v4lstd(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + u8 fmt = 0; /* zero is autodetect */ + u8 pal_m = 0; + u8 pal_n = 0; + u8 ntsc_j = 0; + u8 tmp_reg = 0; + + /* First tests should be against specific std */ + if (state->std == V4L2_STD_NTSC_M_JP) { + fmt = 0x2; + ntsc_j = 0x80; + } else if (state->std == V4L2_STD_NTSC_443) { + fmt = 0x3; + } else if (state->std == V4L2_STD_PAL_M) { + pal_m = 1; + fmt = 0x5; + } else if (state->std == V4L2_STD_PAL_N) { + fmt = 0x6; + pal_n = 0x40; + } else if (state->std == V4L2_STD_PAL_Nc) { + fmt = 0x7; + } else if (state->std == V4L2_STD_PAL_60) { + fmt = 0x8; + } else { + /* Then, test against generic ones */ + if (state->std & V4L2_STD_NTSC) + fmt = 0x1; + else if (state->std & V4L2_STD_PAL) + fmt = 0x4; + else if (state->std & V4L2_STD_SECAM) + fmt = 0xc; + } + + v4l_dbg(1, cx25840_debug, client, + "changing video std to fmt %i\n", fmt); + + /* + * Follow step 9 of section 3.16 in the cx25840 datasheet. + * Without this PAL may display a vertical ghosting effect. + * This happens for example with the Yuan MPC622. + */ + if (fmt >= 4 && fmt < 8) { + /* Set format to NTSC-M */ + cx25840_and_or(client, 0x400, ~0xf, 1); + /* Turn off LCOMB */ + cx25840_and_or(client, 0x47b, ~0x6, 0); + } else if (fmt == 0xc) { /* SECAM - Step 9c - toggle CKILLEN */ + tmp_reg = cx25840_read(client, 0x401); + cx25840_and_or(client, 0x401, ~0x20, tmp_reg & 0x20 ? 0x00 : 0x20); + cx25840_and_or(client, 0x401, ~0x20, tmp_reg & 0x20 ? 0x20 : 0x00); + } + + cx25840_and_or(client, 0x400, ~0xf, fmt); + + if (fmt >= 4 && fmt < 8) { + tmp_reg = cx25840_read(client, 0x401); + cx25840_and_or(client, 0x401, ~0x40, tmp_reg & 0x40 ? 0x00 : 0x40); /* CAGCEN */ + cx25840_and_or(client, 0x401, ~0x40, tmp_reg & 0x40 ? 0x40 : 0x00); + cx25840_and_or(client, 0x401, ~0x20, tmp_reg & 0x20 ? 0x00 : 0x20); /* CKILLEN */ + cx25840_and_or(client, 0x401, ~0x20, tmp_reg & 0x20 ? 0x20 : 0x00); + } + + if (pal_m) + cx25840_and_or(client, 0x403, ~0x3, pal_m); + else if (pal_n) /* cx25840 datasheet table 3-19 */ + cx25840_and_or(client, 0x403, ~0x40, pal_n); + else if (ntsc_j) /* cx25840 datasheet table 3-19 */ + cx25840_and_or(client, 0x403, ~0x80, ntsc_j); + + if (is_cx23888(state)) + cx23888_std_setup(client); + else + cx25840_std_setup(client); + if (!is_cx2583x(state)) + input_change(client); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int cx25840_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + cx25840_write(client, 0x414, ctrl->val - 128); + break; + + case V4L2_CID_CONTRAST: + cx25840_write(client, 0x415, ctrl->val << 1); + break; + + case V4L2_CID_SATURATION: + if (is_cx23888(state)) { + cx25840_write(client, 0x418, ctrl->val << 1); + cx25840_write(client, 0x419, ctrl->val << 1); + } else { + cx25840_write(client, 0x420, ctrl->val << 1); + cx25840_write(client, 0x421, ctrl->val << 1); + } + break; + + case V4L2_CID_HUE: + if (is_cx23888(state)) + cx25840_write(client, 0x41a, ctrl->val); + else + cx25840_write(client, 0x422, ctrl->val); + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int cx25840_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt = &format->format; + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u32 hsc, vsc, v_src, h_src, v_add; + int filter; + int is_50hz = !(state->std & V4L2_STD_525_60); + + if (format->pad || fmt->code != MEDIA_BUS_FMT_FIXED) + return -EINVAL; + + fmt->field = V4L2_FIELD_INTERLACED; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + + if (is_cx23888(state)) { + v_src = (cx25840_read(client, 0x42a) & 0x3f) << 4; + v_src |= (cx25840_read(client, 0x429) & 0xf0) >> 4; + } else { + v_src = (cx25840_read(client, 0x476) & 0x3f) << 4; + v_src |= (cx25840_read(client, 0x475) & 0xf0) >> 4; + } + + if (is_cx23888(state)) { + h_src = (cx25840_read(client, 0x426) & 0x3f) << 4; + h_src |= (cx25840_read(client, 0x425) & 0xf0) >> 4; + } else { + h_src = (cx25840_read(client, 0x472) & 0x3f) << 4; + h_src |= (cx25840_read(client, 0x471) & 0xf0) >> 4; + } + + if (!state->generic_mode) { + v_add = is_50hz ? 4 : 7; + + /* + * cx23888 in 525-line mode is programmed for 486 active lines + * while other chips use 487 active lines. + * + * See reg 0x428 bits [21:12] in cx23888_std_setup() vs + * vactive in cx25840_std_setup(). + */ + if (is_cx23888(state) && !is_50hz) + v_add--; + } else { + v_add = 0; + } + + if (h_src == 0 || + v_src <= v_add) { + v4l_err(client, + "chip reported picture size (%u x %u) is far too small\n", + (unsigned int)h_src, (unsigned int)v_src); + /* + * that's the best we can do since the output picture + * size is completely unknown in this case + */ + return -EINVAL; + } + + fmt->width = clamp(fmt->width, (h_src + 15) / 16, h_src); + + if (v_add * 8 >= v_src) + fmt->height = clamp(fmt->height, (u32)1, v_src - v_add); + else + fmt->height = clamp(fmt->height, (v_src - v_add * 8 + 7) / 8, + v_src - v_add); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + + hsc = (h_src * (1 << 20)) / fmt->width - (1 << 20); + vsc = (1 << 16) - (v_src * (1 << 9) / (fmt->height + v_add) - (1 << 9)); + vsc &= 0x1fff; + + if (fmt->width >= 385) + filter = 0; + else if (fmt->width > 192) + filter = 1; + else if (fmt->width > 96) + filter = 2; + else + filter = 3; + + v4l_dbg(1, cx25840_debug, client, + "decoder set size %u x %u with scale %x x %x\n", + (unsigned int)fmt->width, (unsigned int)fmt->height, + (unsigned int)hsc, (unsigned int)vsc); + + /* HSCALE=hsc */ + if (is_cx23888(state)) { + cx25840_write4(client, 0x434, hsc | (1 << 24)); + /* VSCALE=vsc VS_INTRLACE=1 VFILT=filter */ + cx25840_write4(client, 0x438, vsc | (1 << 19) | (filter << 16)); + } else { + cx25840_write(client, 0x418, hsc & 0xff); + cx25840_write(client, 0x419, (hsc >> 8) & 0xff); + cx25840_write(client, 0x41a, hsc >> 16); + /* VSCALE=vsc */ + cx25840_write(client, 0x41c, vsc & 0xff); + cx25840_write(client, 0x41d, vsc >> 8); + /* VS_INTRLACE=1 VFILT=filter */ + cx25840_write(client, 0x41e, 0x8 | filter); + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static void log_video_status(struct i2c_client *client) +{ + static const char *const fmt_strs[] = { + "0x0", + "NTSC-M", "NTSC-J", "NTSC-4.43", + "PAL-BDGHI", "PAL-M", "PAL-N", "PAL-Nc", "PAL-60", + "0x9", "0xA", "0xB", + "SECAM", + "0xD", "0xE", "0xF" + }; + + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + u8 vidfmt_sel = cx25840_read(client, 0x400) & 0xf; + u8 gen_stat1 = cx25840_read(client, 0x40d); + u8 gen_stat2 = cx25840_read(client, 0x40e); + int vid_input = state->vid_input; + + v4l_info(client, "Video signal: %spresent\n", + (gen_stat2 & 0x20) ? "" : "not "); + v4l_info(client, "Detected format: %s\n", + fmt_strs[gen_stat1 & 0xf]); + + v4l_info(client, "Specified standard: %s\n", + vidfmt_sel ? fmt_strs[vidfmt_sel] : "automatic detection"); + + if (vid_input >= CX25840_COMPOSITE1 && + vid_input <= CX25840_COMPOSITE8) { + v4l_info(client, "Specified video input: Composite %d\n", + vid_input - CX25840_COMPOSITE1 + 1); + } else { + v4l_info(client, + "Specified video input: S-Video (Luma In%d, Chroma In%d)\n", + (vid_input & 0xf0) >> 4, (vid_input & 0xf00) >> 8); + } + + v4l_info(client, "Specified audioclock freq: %d Hz\n", + state->audclk_freq); +} + +/* ----------------------------------------------------------------------- */ + +static void log_audio_status(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + u8 download_ctl = cx25840_read(client, 0x803); + u8 mod_det_stat0 = cx25840_read(client, 0x804); + u8 mod_det_stat1 = cx25840_read(client, 0x805); + u8 audio_config = cx25840_read(client, 0x808); + u8 pref_mode = cx25840_read(client, 0x809); + u8 afc0 = cx25840_read(client, 0x80b); + u8 mute_ctl = cx25840_read(client, 0x8d3); + int aud_input = state->aud_input; + char *p; + + switch (mod_det_stat0) { + case 0x00: + p = "mono"; + break; + case 0x01: + p = "stereo"; + break; + case 0x02: + p = "dual"; + break; + case 0x04: + p = "tri"; + break; + case 0x10: + p = "mono with SAP"; + break; + case 0x11: + p = "stereo with SAP"; + break; + case 0x12: + p = "dual with SAP"; + break; + case 0x14: + p = "tri with SAP"; + break; + case 0xfe: + p = "forced mode"; + break; + default: + p = "not defined"; + } + v4l_info(client, "Detected audio mode: %s\n", p); + + switch (mod_det_stat1) { + case 0x00: + p = "not defined"; + break; + case 0x01: + p = "EIAJ"; + break; + case 0x02: + p = "A2-M"; + break; + case 0x03: + p = "A2-BG"; + break; + case 0x04: + p = "A2-DK1"; + break; + case 0x05: + p = "A2-DK2"; + break; + case 0x06: + p = "A2-DK3"; + break; + case 0x07: + p = "A1 (6.0 MHz FM Mono)"; + break; + case 0x08: + p = "AM-L"; + break; + case 0x09: + p = "NICAM-BG"; + break; + case 0x0a: + p = "NICAM-DK"; + break; + case 0x0b: + p = "NICAM-I"; + break; + case 0x0c: + p = "NICAM-L"; + break; + case 0x0d: + p = "BTSC/EIAJ/A2-M Mono (4.5 MHz FMMono)"; + break; + case 0x0e: + p = "IF FM Radio"; + break; + case 0x0f: + p = "BTSC"; + break; + case 0x10: + p = "high-deviation FM"; + break; + case 0x11: + p = "very high-deviation FM"; + break; + case 0xfd: + p = "unknown audio standard"; + break; + case 0xfe: + p = "forced audio standard"; + break; + case 0xff: + p = "no detected audio standard"; + break; + default: + p = "not defined"; + } + v4l_info(client, "Detected audio standard: %s\n", p); + v4l_info(client, "Audio microcontroller: %s\n", + (download_ctl & 0x10) ? + ((mute_ctl & 0x2) ? "detecting" : "running") : "stopped"); + + switch (audio_config >> 4) { + case 0x00: + p = "undefined"; + break; + case 0x01: + p = "BTSC"; + break; + case 0x02: + p = "EIAJ"; + break; + case 0x03: + p = "A2-M"; + break; + case 0x04: + p = "A2-BG"; + break; + case 0x05: + p = "A2-DK1"; + break; + case 0x06: + p = "A2-DK2"; + break; + case 0x07: + p = "A2-DK3"; + break; + case 0x08: + p = "A1 (6.0 MHz FM Mono)"; + break; + case 0x09: + p = "AM-L"; + break; + case 0x0a: + p = "NICAM-BG"; + break; + case 0x0b: + p = "NICAM-DK"; + break; + case 0x0c: + p = "NICAM-I"; + break; + case 0x0d: + p = "NICAM-L"; + break; + case 0x0e: + p = "FM radio"; + break; + case 0x0f: + p = "automatic detection"; + break; + default: + p = "undefined"; + } + v4l_info(client, "Configured audio standard: %s\n", p); + + if ((audio_config >> 4) < 0xF) { + switch (audio_config & 0xF) { + case 0x00: + p = "MONO1 (LANGUAGE A/Mono L+R channel for BTSC, EIAJ, A2)"; + break; + case 0x01: + p = "MONO2 (LANGUAGE B)"; + break; + case 0x02: + p = "MONO3 (STEREO forced MONO)"; + break; + case 0x03: + p = "MONO4 (NICAM ANALOG-Language C/Analog Fallback)"; + break; + case 0x04: + p = "STEREO"; + break; + case 0x05: + p = "DUAL1 (AB)"; + break; + case 0x06: + p = "DUAL2 (AC) (FM)"; + break; + case 0x07: + p = "DUAL3 (BC) (FM)"; + break; + case 0x08: + p = "DUAL4 (AC) (AM)"; + break; + case 0x09: + p = "DUAL5 (BC) (AM)"; + break; + case 0x0a: + p = "SAP"; + break; + default: + p = "undefined"; + } + v4l_info(client, "Configured audio mode: %s\n", p); + } else { + switch (audio_config & 0xF) { + case 0x00: + p = "BG"; + break; + case 0x01: + p = "DK1"; + break; + case 0x02: + p = "DK2"; + break; + case 0x03: + p = "DK3"; + break; + case 0x04: + p = "I"; + break; + case 0x05: + p = "L"; + break; + case 0x06: + p = "BTSC"; + break; + case 0x07: + p = "EIAJ"; + break; + case 0x08: + p = "A2-M"; + break; + case 0x09: + p = "FM Radio"; + break; + case 0x0f: + p = "automatic standard and mode detection"; + break; + default: + p = "undefined"; + } + v4l_info(client, "Configured audio system: %s\n", p); + } + + if (aud_input) { + v4l_info(client, "Specified audio input: Tuner (In%d)\n", + aud_input); + } else { + v4l_info(client, "Specified audio input: External\n"); + } + + switch (pref_mode & 0xf) { + case 0: + p = "mono/language A"; + break; + case 1: + p = "language B"; + break; + case 2: + p = "language C"; + break; + case 3: + p = "analog fallback"; + break; + case 4: + p = "stereo"; + break; + case 5: + p = "language AC"; + break; + case 6: + p = "language BC"; + break; + case 7: + p = "language AB"; + break; + default: + p = "undefined"; + } + v4l_info(client, "Preferred audio mode: %s\n", p); + + if ((audio_config & 0xf) == 0xf) { + switch ((afc0 >> 3) & 0x3) { + case 0: + p = "system DK"; + break; + case 1: + p = "system L"; + break; + case 2: + p = "autodetect"; + break; + default: + p = "undefined"; + } + v4l_info(client, "Selected 65 MHz format: %s\n", p); + + switch (afc0 & 0x7) { + case 0: + p = "chroma"; + break; + case 1: + p = "BTSC"; + break; + case 2: + p = "EIAJ"; + break; + case 3: + p = "A2-M"; + break; + case 4: + p = "autodetect"; + break; + default: + p = "undefined"; + } + v4l_info(client, "Selected 45 MHz format: %s\n", p); + } +} + +#define CX25840_VCONFIG_OPTION(state, cfg_in, opt_msk) \ + do { \ + if ((cfg_in) & (opt_msk)) { \ + (state)->vid_config &= ~(opt_msk); \ + (state)->vid_config |= (cfg_in) & (opt_msk); \ + } \ + } while (0) + +/* apply incoming options to the current vconfig */ +static void cx25840_vconfig_add(struct cx25840_state *state, u32 cfg_in) +{ + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_FMT_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_RES_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VBIRAW_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_ANCDATA_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_TASKBIT_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_ACTIVE_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VALID_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_HRESETW_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_CLKGATE_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_DCMODE_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_IDID0S_MASK); + CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VIPCLAMP_MASK); +} + +/* ----------------------------------------------------------------------- */ + +/* + * Initializes the device in the generic mode. + * For cx2584x chips also adds additional video output settings provided + * in @val parameter (CX25840_VCONFIG_*). + * + * The generic mode disables some of the ivtv-related hacks in this driver. + * For cx2584x chips it also enables setting video output configuration while + * setting it according to datasheet defaults by default. + */ +static int cx25840_init(struct v4l2_subdev *sd, u32 val) +{ + struct cx25840_state *state = to_state(sd); + + state->generic_mode = true; + + if (is_cx2584x(state)) { + /* set datasheet video output defaults */ + state->vid_config = CX25840_VCONFIG_FMT_BT656 | + CX25840_VCONFIG_RES_8BIT | + CX25840_VCONFIG_VBIRAW_DISABLED | + CX25840_VCONFIG_ANCDATA_ENABLED | + CX25840_VCONFIG_TASKBIT_ONE | + CX25840_VCONFIG_ACTIVE_HORIZONTAL | + CX25840_VCONFIG_VALID_NORMAL | + CX25840_VCONFIG_HRESETW_NORMAL | + CX25840_VCONFIG_CLKGATE_NONE | + CX25840_VCONFIG_DCMODE_DWORDS | + CX25840_VCONFIG_IDID0S_NORMAL | + CX25840_VCONFIG_VIPCLAMP_DISABLED; + + /* add additional settings */ + cx25840_vconfig_add(state, val); + } else { + /* TODO: generic mode needs to be developed for other chips */ + WARN_ON(1); + } + + return 0; +} + +static int cx25840_reset(struct v4l2_subdev *sd, u32 val) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (is_cx2583x(state)) + cx25836_initialize(client); + else if (is_cx2388x(state)) + cx23885_initialize(client); + else if (is_cx231xx(state)) + cx231xx_initialize(client); + else + cx25840_initialize(client); + + state->is_initialized = 1; + + return 0; +} + +/* + * This load_fw operation must be called to load the driver's firmware. + * This will load the firmware on the first invocation (further ones are NOP). + * Without this the audio standard detection will fail and you will + * only get mono. + * Alternatively, you can call the reset operation instead of this one. + * + * Since loading the firmware is often problematic when the driver is + * compiled into the kernel I recommend postponing calling this function + * until the first open of the video device. Another reason for + * postponing it is that loading this firmware takes a long time (seconds) + * due to the slow i2c bus speed. So it will speed up the boot process if + * you can avoid loading the fw as long as the video device isn't used. + */ +static int cx25840_load_fw(struct v4l2_subdev *sd) +{ + struct cx25840_state *state = to_state(sd); + + if (!state->is_initialized) { + /* initialize and load firmware */ + cx25840_reset(sd, 0); + } + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int cx25840_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + reg->size = 1; + reg->val = cx25840_read(client, reg->reg & 0x0fff); + return 0; +} + +static int cx25840_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + cx25840_write(client, reg->reg & 0x0fff, reg->val & 0xff); + return 0; +} +#endif + +static int cx25840_s_audio_stream(struct v4l2_subdev *sd, int enable) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 v; + + if (is_cx2583x(state) || is_cx2388x(state) || is_cx231xx(state)) + return 0; + + v4l_dbg(1, cx25840_debug, client, "%s audio output\n", + enable ? "enable" : "disable"); + + if (enable) { + v = cx25840_read(client, 0x115) | 0x80; + cx25840_write(client, 0x115, v); + v = cx25840_read(client, 0x116) | 0x03; + cx25840_write(client, 0x116, v); + } else { + v = cx25840_read(client, 0x115) & ~(0x80); + cx25840_write(client, 0x115, v); + v = cx25840_read(client, 0x116) & ~(0x03); + cx25840_write(client, 0x116, v); + } + return 0; +} + +static int cx25840_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 v; + + v4l_dbg(1, cx25840_debug, client, "%s video output\n", + enable ? "enable" : "disable"); + + /* + * It's not clear what should be done for these devices. + * The original code used the same addresses as for the cx25840, but + * those addresses do something else entirely on the cx2388x and + * cx231xx. Since it never did anything in the first place, just do + * nothing. + */ + if (is_cx2388x(state) || is_cx231xx(state)) + return 0; + + if (enable) { + v = cx25840_read(client, 0x115) | 0x0c; + cx25840_write(client, 0x115, v); + v = cx25840_read(client, 0x116) | 0x04; + cx25840_write(client, 0x116, v); + } else { + v = cx25840_read(client, 0x115) & ~(0x0c); + cx25840_write(client, 0x115, v); + v = cx25840_read(client, 0x116) & ~(0x04); + cx25840_write(client, 0x116, v); + } + return 0; +} + +/* Query the current detected video format */ +static int cx25840_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + static const v4l2_std_id stds[] = { + /* 0000 */ V4L2_STD_UNKNOWN, + + /* 0001 */ V4L2_STD_NTSC_M, + /* 0010 */ V4L2_STD_NTSC_M_JP, + /* 0011 */ V4L2_STD_NTSC_443, + /* 0100 */ V4L2_STD_PAL, + /* 0101 */ V4L2_STD_PAL_M, + /* 0110 */ V4L2_STD_PAL_N, + /* 0111 */ V4L2_STD_PAL_Nc, + /* 1000 */ V4L2_STD_PAL_60, + + /* 1001 */ V4L2_STD_UNKNOWN, + /* 1010 */ V4L2_STD_UNKNOWN, + /* 1011 */ V4L2_STD_UNKNOWN, + /* 1100 */ V4L2_STD_SECAM, + /* 1101 */ V4L2_STD_UNKNOWN, + /* 1110 */ V4L2_STD_UNKNOWN, + /* 1111 */ V4L2_STD_UNKNOWN + }; + + u32 fmt = (cx25840_read4(client, 0x40c) >> 8) & 0xf; + *std = stds[fmt]; + + v4l_dbg(1, cx25840_debug, client, + "querystd fmt = %x, v4l2_std_id = 0x%x\n", + fmt, (unsigned int)stds[fmt]); + + return 0; +} + +static int cx25840_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + /* + * A limited function that checks for signal status and returns + * the state. + */ + + /* Check for status of Horizontal lock (SRC lock isn't reliable) */ + if ((cx25840_read4(client, 0x40c) & 0x00010000) == 0) + *status |= V4L2_IN_ST_NO_SIGNAL; + + return 0; +} + +static int cx25840_g_std(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct cx25840_state *state = to_state(sd); + + *std = state->std; + + return 0; +} + +static int cx25840_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (state->radio == 0 && state->std == std) + return 0; + state->radio = 0; + state->std = std; + return set_v4lstd(client); +} + +static int cx25840_s_radio(struct v4l2_subdev *sd) +{ + struct cx25840_state *state = to_state(sd); + + state->radio = 1; + return 0; +} + +static int cx25840_s_video_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (is_cx23888(state)) + cx23888_std_setup(client); + + if (is_cx2584x(state) && state->generic_mode && config) { + cx25840_vconfig_add(state, config); + cx25840_vconfig_apply(client); + } + + return set_input(client, input, state->aud_input); +} + +static int cx25840_s_audio_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (is_cx23888(state)) + cx23888_std_setup(client); + return set_input(client, state->vid_input, input); +} + +static int cx25840_s_frequency(struct v4l2_subdev *sd, + const struct v4l2_frequency *freq) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + input_change(client); + return 0; +} + +static int cx25840_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 vpres = cx25840_read(client, 0x40e) & 0x20; + u8 mode; + int val = 0; + + if (state->radio) + return 0; + + vt->signal = vpres ? 0xffff : 0x0; + if (is_cx2583x(state)) + return 0; + + vt->capability |= V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; + + mode = cx25840_read(client, 0x804); + + /* get rxsubchans and audmode */ + if ((mode & 0xf) == 1) + val |= V4L2_TUNER_SUB_STEREO; + else + val |= V4L2_TUNER_SUB_MONO; + + if (mode == 2 || mode == 4) + val = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + + if (mode & 0x10) + val |= V4L2_TUNER_SUB_SAP; + + vt->rxsubchans = val; + vt->audmode = state->audmode; + return 0; +} + +static int cx25840_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (state->radio || is_cx2583x(state)) + return 0; + + switch (vt->audmode) { + case V4L2_TUNER_MODE_MONO: + /* + * mono -> mono + * stereo -> mono + * bilingual -> lang1 + */ + cx25840_and_or(client, 0x809, ~0xf, 0x00); + break; + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1: + /* + * mono -> mono + * stereo -> stereo + * bilingual -> lang1 + */ + cx25840_and_or(client, 0x809, ~0xf, 0x04); + break; + case V4L2_TUNER_MODE_LANG1_LANG2: + /* + * mono -> mono + * stereo -> stereo + * bilingual -> lang1/lang2 + */ + cx25840_and_or(client, 0x809, ~0xf, 0x07); + break; + case V4L2_TUNER_MODE_LANG2: + /* + * mono -> mono + * stereo -> stereo + * bilingual -> lang2 + */ + cx25840_and_or(client, 0x809, ~0xf, 0x01); + break; + default: + return -EINVAL; + } + state->audmode = vt->audmode; + return 0; +} + +static int cx25840_log_status(struct v4l2_subdev *sd) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + log_video_status(client); + if (!is_cx2583x(state)) + log_audio_status(client); + cx25840_ir_log_status(sd); + v4l2_ctrl_handler_log_status(&state->hdl, sd->name); + return 0; +} + +static int cx23885_irq_handler(struct v4l2_subdev *sd, u32 status, + bool *handled) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *c = v4l2_get_subdevdata(sd); + u8 irq_stat, aud_stat, aud_en, ir_stat, ir_en; + u32 vid_stat, aud_mc_stat; + bool block_handled; + int ret = 0; + + irq_stat = cx25840_read(c, CX23885_PIN_CTRL_IRQ_REG); + v4l_dbg(2, cx25840_debug, c, "AV Core IRQ status (entry): %s %s %s\n", + irq_stat & CX23885_PIN_CTRL_IRQ_IR_STAT ? "ir" : " ", + irq_stat & CX23885_PIN_CTRL_IRQ_AUD_STAT ? "aud" : " ", + irq_stat & CX23885_PIN_CTRL_IRQ_VID_STAT ? "vid" : " "); + + if ((is_cx23885(state) || is_cx23887(state))) { + ir_stat = cx25840_read(c, CX25840_IR_STATS_REG); + ir_en = cx25840_read(c, CX25840_IR_IRQEN_REG); + v4l_dbg(2, cx25840_debug, c, + "AV Core ir IRQ status: %#04x disables: %#04x\n", + ir_stat, ir_en); + if (irq_stat & CX23885_PIN_CTRL_IRQ_IR_STAT) { + block_handled = false; + ret = cx25840_ir_irq_handler(sd, + status, &block_handled); + if (block_handled) + *handled = true; + } + } + + aud_stat = cx25840_read(c, CX25840_AUD_INT_STAT_REG); + aud_en = cx25840_read(c, CX25840_AUD_INT_CTRL_REG); + v4l_dbg(2, cx25840_debug, c, + "AV Core audio IRQ status: %#04x disables: %#04x\n", + aud_stat, aud_en); + aud_mc_stat = cx25840_read4(c, CX23885_AUD_MC_INT_MASK_REG); + v4l_dbg(2, cx25840_debug, c, + "AV Core audio MC IRQ status: %#06x enables: %#06x\n", + aud_mc_stat >> CX23885_AUD_MC_INT_STAT_SHFT, + aud_mc_stat & CX23885_AUD_MC_INT_CTRL_BITS); + if (irq_stat & CX23885_PIN_CTRL_IRQ_AUD_STAT) { + if (aud_stat) { + cx25840_write(c, CX25840_AUD_INT_STAT_REG, aud_stat); + *handled = true; + } + } + + vid_stat = cx25840_read4(c, CX25840_VID_INT_STAT_REG); + v4l_dbg(2, cx25840_debug, c, + "AV Core video IRQ status: %#06x disables: %#06x\n", + vid_stat & CX25840_VID_INT_STAT_BITS, + vid_stat >> CX25840_VID_INT_MASK_SHFT); + if (irq_stat & CX23885_PIN_CTRL_IRQ_VID_STAT) { + if (vid_stat & CX25840_VID_INT_STAT_BITS) { + cx25840_write4(c, CX25840_VID_INT_STAT_REG, vid_stat); + *handled = true; + } + } + + irq_stat = cx25840_read(c, CX23885_PIN_CTRL_IRQ_REG); + v4l_dbg(2, cx25840_debug, c, "AV Core IRQ status (exit): %s %s %s\n", + irq_stat & CX23885_PIN_CTRL_IRQ_IR_STAT ? "ir" : " ", + irq_stat & CX23885_PIN_CTRL_IRQ_AUD_STAT ? "aud" : " ", + irq_stat & CX23885_PIN_CTRL_IRQ_VID_STAT ? "vid" : " "); + + return ret; +} + +static int cx25840_irq_handler(struct v4l2_subdev *sd, u32 status, + bool *handled) +{ + struct cx25840_state *state = to_state(sd); + + *handled = false; + + /* Only support the CX2388[578] AV Core for now */ + if (is_cx2388x(state)) + return cx23885_irq_handler(sd, status, handled); + + return -ENODEV; +} + +/* ----------------------------------------------------------------------- */ + +#define DIF_PLL_FREQ_WORD (0x300) +#define DIF_BPF_COEFF01 (0x348) +#define DIF_BPF_COEFF23 (0x34c) +#define DIF_BPF_COEFF45 (0x350) +#define DIF_BPF_COEFF67 (0x354) +#define DIF_BPF_COEFF89 (0x358) +#define DIF_BPF_COEFF1011 (0x35c) +#define DIF_BPF_COEFF1213 (0x360) +#define DIF_BPF_COEFF1415 (0x364) +#define DIF_BPF_COEFF1617 (0x368) +#define DIF_BPF_COEFF1819 (0x36c) +#define DIF_BPF_COEFF2021 (0x370) +#define DIF_BPF_COEFF2223 (0x374) +#define DIF_BPF_COEFF2425 (0x378) +#define DIF_BPF_COEFF2627 (0x37c) +#define DIF_BPF_COEFF2829 (0x380) +#define DIF_BPF_COEFF3031 (0x384) +#define DIF_BPF_COEFF3233 (0x388) +#define DIF_BPF_COEFF3435 (0x38c) +#define DIF_BPF_COEFF36 (0x390) + +static const u32 ifhz_coeffs[][19] = { + { // 3.0 MHz + 0x00000002, 0x00080012, 0x001e0024, 0x001bfff8, + 0xffb4ff50, 0xfed8fe68, 0xfe24fe34, 0xfebaffc7, + 0x014d031f, 0x04f0065d, 0x07010688, 0x04c901d6, + 0xfe00f9d3, 0xf600f342, 0xf235f337, 0xf64efb22, + 0x0105070f, 0x0c460fce, 0x110d0000, + }, { // 3.1 MHz + 0x00000001, 0x00070012, 0x00220032, 0x00370026, + 0xfff0ff91, 0xff0efe7c, 0xfe01fdcc, 0xfe0afedb, + 0x00440224, 0x0434060c, 0x0738074e, 0x06090361, + 0xff99fb39, 0xf6fef3b6, 0xf21af2a5, 0xf573fa33, + 0x0034067d, 0x0bfb0fb9, 0x110d0000, + }, { // 3.2 MHz + 0x00000000, 0x0004000e, 0x00200038, 0x004c004f, + 0x002fffdf, 0xff5cfeb6, 0xfe0dfd92, 0xfd7ffe03, + 0xff36010a, 0x03410575, 0x072607d2, 0x071804d5, + 0x0134fcb7, 0xf81ff451, 0xf223f22e, 0xf4a7f94b, + 0xff6405e8, 0x0bae0fa4, 0x110d0000, + }, { // 3.3 MHz + 0x0000ffff, 0x00000008, 0x001a0036, 0x0056006d, + 0x00670030, 0xffbdff10, 0xfe46fd8d, 0xfd25fd4f, + 0xfe35ffe0, 0x0224049f, 0x06c9080e, 0x07ef0627, + 0x02c9fe45, 0xf961f513, 0xf250f1d2, 0xf3ecf869, + 0xfe930552, 0x0b5f0f8f, 0x110d0000, + }, { // 3.4 MHz + 0xfffffffe, 0xfffd0001, 0x000f002c, 0x0054007d, + 0x0093007c, 0x0024ff82, 0xfea6fdbb, 0xfd03fcca, + 0xfd51feb9, 0x00eb0392, 0x06270802, 0x08880750, + 0x044dffdb, 0xfabdf5f8, 0xf2a0f193, 0xf342f78f, + 0xfdc404b9, 0x0b0e0f78, 0x110d0000, + }, { // 3.5 MHz + 0xfffffffd, 0xfffafff9, 0x0002001b, 0x0046007d, + 0x00ad00ba, 0x00870000, 0xff26fe1a, 0xfd1bfc7e, + 0xfc99fda4, 0xffa5025c, 0x054507ad, 0x08dd0847, + 0x05b80172, 0xfc2ef6ff, 0xf313f170, 0xf2abf6bd, + 0xfcf6041f, 0x0abc0f61, 0x110d0000, + }, { // 3.6 MHz + 0xfffffffd, 0xfff8fff3, 0xfff50006, 0x002f006c, + 0x00b200e3, 0x00dc007e, 0xffb9fea0, 0xfd6bfc71, + 0xfc17fcb1, 0xfe65010b, 0x042d0713, 0x08ec0906, + 0x07020302, 0xfdaff823, 0xf3a7f16a, 0xf228f5f5, + 0xfc2a0384, 0x0a670f4a, 0x110d0000, + }, { // 3.7 MHz + 0x0000fffd, 0xfff7ffef, 0xffe9fff1, 0x0010004d, + 0x00a100f2, 0x011a00f0, 0x0053ff44, 0xfdedfca2, + 0xfbd3fbef, 0xfd39ffae, 0x02ea0638, 0x08b50987, + 0x08230483, 0xff39f960, 0xf45bf180, 0xf1b8f537, + 0xfb6102e7, 0x0a110f32, 0x110d0000, + }, { // 3.8 MHz + 0x0000fffe, 0xfff9ffee, 0xffe1ffdd, 0xfff00024, + 0x007c00e5, 0x013a014a, 0x00e6fff8, 0xfe98fd0f, + 0xfbd3fb67, 0xfc32fe54, 0x01880525, 0x083909c7, + 0x091505ee, 0x00c7fab3, 0xf52df1b4, 0xf15df484, + 0xfa9b0249, 0x09ba0f19, 0x110d0000, + }, { // 3.9 MHz + 0x00000000, 0xfffbfff0, 0xffdeffcf, 0xffd1fff6, + 0x004800be, 0x01390184, 0x016300ac, 0xff5efdb1, + 0xfc17fb23, 0xfb5cfd0d, 0x001703e4, 0x077b09c4, + 0x09d2073c, 0x0251fc18, 0xf61cf203, 0xf118f3dc, + 0xf9d801aa, 0x09600eff, 0x110d0000, + }, { // 4.0 MHz + 0x00000001, 0xfffefff4, 0xffe1ffc8, 0xffbaffca, + 0x000b0082, 0x01170198, 0x01c10152, 0x0030fe7b, + 0xfc99fb24, 0xfac3fbe9, 0xfea5027f, 0x0683097f, + 0x0a560867, 0x03d2fd89, 0xf723f26f, 0xf0e8f341, + 0xf919010a, 0x09060ee5, 0x110d0000, + }, { // 4.1 MHz + 0x00010002, 0x0002fffb, 0xffe8ffca, 0xffacffa4, + 0xffcd0036, 0x00d70184, 0x01f601dc, 0x00ffff60, + 0xfd51fb6d, 0xfa6efaf5, 0xfd410103, 0x055708f9, + 0x0a9e0969, 0x0543ff02, 0xf842f2f5, 0xf0cef2b2, + 0xf85e006b, 0x08aa0ecb, 0x110d0000, + }, { // 4.2 MHz + 0x00010003, 0x00050003, 0xfff3ffd3, 0xffaaff8b, + 0xff95ffe5, 0x0080014a, 0x01fe023f, 0x01ba0050, + 0xfe35fbf8, 0xfa62fa3b, 0xfbf9ff7e, 0x04010836, + 0x0aa90a3d, 0x069f007f, 0xf975f395, 0xf0cbf231, + 0xf7a9ffcb, 0x084c0eaf, 0x110d0000, + }, { // 4.3 MHz + 0x00010003, 0x0008000a, 0x0000ffe4, 0xffb4ff81, + 0xff6aff96, 0x001c00f0, 0x01d70271, 0x0254013b, + 0xff36fcbd, 0xfa9ff9c5, 0xfadbfdfe, 0x028c073b, + 0x0a750adf, 0x07e101fa, 0xfab8f44e, 0xf0ddf1be, + 0xf6f9ff2b, 0x07ed0e94, 0x110d0000, + }, { // 4.4 MHz + 0x00000003, 0x0009000f, 0x000efff8, 0xffc9ff87, + 0xff52ff54, 0xffb5007e, 0x01860270, 0x02c00210, + 0x0044fdb2, 0xfb22f997, 0xf9f2fc90, 0x0102060f, + 0x0a050b4c, 0x0902036e, 0xfc0af51e, 0xf106f15a, + 0xf64efe8b, 0x078d0e77, 0x110d0000, + }, { // 4.5 MHz + 0x00000002, 0x00080012, 0x0019000e, 0xffe5ff9e, + 0xff4fff25, 0xff560000, 0x0112023b, 0x02f702c0, + 0x014dfec8, 0xfbe5f9b3, 0xf947fb41, 0xff7004b9, + 0x095a0b81, 0x0a0004d8, 0xfd65f603, 0xf144f104, + 0xf5aafdec, 0x072b0e5a, 0x110d0000, + }, { // 4.6 MHz + 0x00000001, 0x00060012, 0x00200022, 0x0005ffc1, + 0xff61ff10, 0xff09ff82, 0x008601d7, 0x02f50340, + 0x0241fff0, 0xfcddfa19, 0xf8e2fa1e, 0xfde30343, + 0x08790b7f, 0x0ad50631, 0xfec7f6fc, 0xf198f0bd, + 0xf50dfd4e, 0x06c90e3d, 0x110d0000, + }, { // 4.7 MHz + 0x0000ffff, 0x0003000f, 0x00220030, 0x0025ffed, + 0xff87ff15, 0xfed6ff10, 0xffed014c, 0x02b90386, + 0x03110119, 0xfdfefac4, 0xf8c6f92f, 0xfc6701b7, + 0x07670b44, 0x0b7e0776, 0x002df807, 0xf200f086, + 0xf477fcb1, 0x06650e1e, 0x110d0000, + }, { // 4.8 MHz + 0xfffffffe, 0xffff0009, 0x001e0038, 0x003f001b, + 0xffbcff36, 0xfec2feb6, 0xff5600a5, 0x0248038d, + 0x03b00232, 0xff39fbab, 0xf8f4f87f, 0xfb060020, + 0x062a0ad2, 0x0bf908a3, 0x0192f922, 0xf27df05e, + 0xf3e8fc14, 0x06000e00, 0x110d0000, + }, { // 4.9 MHz + 0xfffffffd, 0xfffc0002, 0x00160037, 0x00510046, + 0xfff9ff6d, 0xfed0fe7c, 0xfecefff0, 0x01aa0356, + 0x0413032b, 0x007ffcc5, 0xf96cf812, 0xf9cefe87, + 0x04c90a2c, 0x0c4309b4, 0x02f3fa4a, 0xf30ef046, + 0xf361fb7a, 0x059b0de0, 0x110d0000, + }, { // 5.0 MHz + 0xfffffffd, 0xfff9fffa, 0x000a002d, 0x00570067, + 0x0037ffb5, 0xfefffe68, 0xfe62ff3d, 0x00ec02e3, + 0x043503f6, 0x01befe05, 0xfa27f7ee, 0xf8c6fcf8, + 0x034c0954, 0x0c5c0aa4, 0x044cfb7e, 0xf3b1f03f, + 0xf2e2fae1, 0x05340dc0, 0x110d0000, + }, { // 5.1 MHz + 0x0000fffd, 0xfff8fff4, 0xfffd001e, 0x0051007b, + 0x006e0006, 0xff48fe7c, 0xfe1bfe9a, 0x001d023e, + 0x04130488, 0x02e6ff5b, 0xfb1ef812, 0xf7f7fb7f, + 0x01bc084e, 0x0c430b72, 0x059afcba, 0xf467f046, + 0xf26cfa4a, 0x04cd0da0, 0x110d0000, + }, { // 5.2 MHz + 0x0000fffe, 0xfff8ffef, 0xfff00009, 0x003f007f, + 0x00980056, 0xffa5feb6, 0xfe00fe15, 0xff4b0170, + 0x03b004d7, 0x03e800b9, 0xfc48f87f, 0xf768fa23, + 0x0022071f, 0x0bf90c1b, 0x06dafdfd, 0xf52df05e, + 0xf1fef9b5, 0x04640d7f, 0x110d0000, + }, { // 5.3 MHz + 0x0000ffff, 0xfff9ffee, 0xffe6fff3, 0x00250072, + 0x00af009c, 0x000cff10, 0xfe13fdb8, 0xfe870089, + 0x031104e1, 0x04b8020f, 0xfd98f92f, 0xf71df8f0, + 0xfe8805ce, 0x0b7e0c9c, 0x0808ff44, 0xf603f086, + 0xf19af922, 0x03fb0d5e, 0x110d0000, + }, { // 5.4 MHz + 0x00000001, 0xfffcffef, 0xffe0ffe0, 0x00050056, + 0x00b000d1, 0x0071ff82, 0xfe53fd8c, 0xfddfff99, + 0x024104a3, 0x054a034d, 0xff01fa1e, 0xf717f7ed, + 0xfcf50461, 0x0ad50cf4, 0x0921008d, 0xf6e7f0bd, + 0xf13ff891, 0x03920d3b, 0x110d0000, + }, { // 5.5 MHz + 0x00010002, 0xfffffff3, 0xffdeffd1, 0xffe5002f, + 0x009c00ed, 0x00cb0000, 0xfebafd94, 0xfd61feb0, + 0x014d0422, 0x05970464, 0x0074fb41, 0xf759f721, + 0xfb7502de, 0x0a000d21, 0x0a2201d4, 0xf7d9f104, + 0xf0edf804, 0x03280d19, 0x110d0000, + }, { // 5.6 MHz + 0x00010003, 0x0003fffa, 0xffe3ffc9, 0xffc90002, + 0x007500ef, 0x010e007e, 0xff3dfdcf, 0xfd16fddd, + 0x00440365, 0x059b0548, 0x01e3fc90, 0xf7dff691, + 0xfa0f014d, 0x09020d23, 0x0b0a0318, 0xf8d7f15a, + 0xf0a5f779, 0x02bd0cf6, 0x110d0000, + }, { // 5.7 MHz + 0x00010003, 0x00060001, 0xffecffc9, 0xffb4ffd4, + 0x004000d5, 0x013600f0, 0xffd3fe39, 0xfd04fd31, + 0xff360277, 0x055605ef, 0x033efdfe, 0xf8a5f642, + 0xf8cbffb6, 0x07e10cfb, 0x0bd50456, 0xf9dff1be, + 0xf067f6f2, 0x02520cd2, 0x110d0000, + }, { // 5.8 MHz + 0x00000003, 0x00080009, 0xfff8ffd2, 0xffaaffac, + 0x000200a3, 0x013c014a, 0x006dfec9, 0xfd2bfcb7, + 0xfe350165, 0x04cb0651, 0x0477ff7e, 0xf9a5f635, + 0xf7b1fe20, 0x069f0ca8, 0x0c81058b, 0xfaf0f231, + 0xf033f66d, 0x01e60cae, 0x110d0000, + }, { // 5.9 MHz + 0x00000002, 0x0009000e, 0x0005ffe1, 0xffacff90, + 0xffc5005f, 0x01210184, 0x00fcff72, 0xfd8afc77, + 0xfd51003f, 0x04020669, 0x05830103, 0xfad7f66b, + 0xf6c8fc93, 0x05430c2b, 0x0d0d06b5, 0xfc08f2b2, + 0xf00af5ec, 0x017b0c89, 0x110d0000, + }, { // 6.0 MHz + 0x00000001, 0x00070012, 0x0012fff5, 0xffbaff82, + 0xff8e000f, 0x00e80198, 0x01750028, 0xfe18fc75, + 0xfc99ff15, 0x03050636, 0x0656027f, 0xfc32f6e2, + 0xf614fb17, 0x03d20b87, 0x0d7707d2, 0xfd26f341, + 0xefeaf56f, 0x010f0c64, 0x110d0000, + }, { // 6.1 MHz + 0xffff0000, 0x00050012, 0x001c000b, 0xffd1ff84, + 0xff66ffbe, 0x00960184, 0x01cd00da, 0xfeccfcb2, + 0xfc17fdf9, 0x01e005bc, 0x06e703e4, 0xfdabf798, + 0xf599f9b3, 0x02510abd, 0x0dbf08df, 0xfe48f3dc, + 0xefd5f4f6, 0x00a20c3e, 0x110d0000, + }, { // 6.2 MHz + 0xfffffffe, 0x0002000f, 0x0021001f, 0xfff0ff97, + 0xff50ff74, 0x0034014a, 0x01fa0179, 0xff97fd2a, + 0xfbd3fcfa, 0x00a304fe, 0x07310525, 0xff37f886, + 0xf55cf86e, 0x00c709d0, 0x0de209db, 0xff6df484, + 0xefcbf481, 0x00360c18, 0x110d0000, + }, { // 6.3 MHz + 0xfffffffd, 0xfffe000a, 0x0021002f, 0x0010ffb8, + 0xff50ff3b, 0xffcc00f0, 0x01fa01fa, 0x0069fdd4, + 0xfbd3fc26, 0xff5d0407, 0x07310638, 0x00c9f9a8, + 0xf55cf74e, 0xff3908c3, 0x0de20ac3, 0x0093f537, + 0xefcbf410, 0xffca0bf2, 0x110d0000, + }, { // 6.4 MHz + 0xfffffffd, 0xfffb0003, 0x001c0037, 0x002fffe2, + 0xff66ff17, 0xff6a007e, 0x01cd0251, 0x0134fea5, + 0xfc17fb8b, 0xfe2002e0, 0x06e70713, 0x0255faf5, + 0xf599f658, 0xfdaf0799, 0x0dbf0b96, 0x01b8f5f5, + 0xefd5f3a3, 0xff5e0bca, 0x110d0000, + }, { // 6.5 MHz + 0x0000fffd, 0xfff9fffb, 0x00120037, 0x00460010, + 0xff8eff0f, 0xff180000, 0x01750276, 0x01e8ff8d, + 0xfc99fb31, 0xfcfb0198, 0x065607ad, 0x03cefc64, + 0xf614f592, 0xfc2e0656, 0x0d770c52, 0x02daf6bd, + 0xefeaf33b, 0xfef10ba3, 0x110d0000, + }, { // 6.6 MHz + 0x0000fffe, 0xfff7fff5, 0x0005002f, 0x0054003c, + 0xffc5ff22, 0xfedfff82, 0x00fc0267, 0x0276007e, + 0xfd51fb1c, 0xfbfe003e, 0x05830802, 0x0529fdec, + 0xf6c8f4fe, 0xfabd04ff, 0x0d0d0cf6, 0x03f8f78f, + 0xf00af2d7, 0xfe850b7b, 0x110d0000, + }, { // 6.7 MHz + 0x0000ffff, 0xfff8fff0, 0xfff80020, 0x00560060, + 0x0002ff4e, 0xfec4ff10, 0x006d0225, 0x02d50166, + 0xfe35fb4e, 0xfb35fee1, 0x0477080e, 0x065bff82, + 0xf7b1f4a0, 0xf9610397, 0x0c810d80, 0x0510f869, + 0xf033f278, 0xfe1a0b52, 0x110d0000, + }, { // 6.8 MHz + 0x00010000, 0xfffaffee, 0xffec000c, 0x004c0078, + 0x0040ff8e, 0xfecafeb6, 0xffd301b6, 0x02fc0235, + 0xff36fbc5, 0xfaaafd90, 0x033e07d2, 0x075b011b, + 0xf8cbf47a, 0xf81f0224, 0x0bd50def, 0x0621f94b, + 0xf067f21e, 0xfdae0b29, 0x110d0000, + }, { // 6.9 MHz + 0x00010001, 0xfffdffef, 0xffe3fff6, 0x0037007f, + 0x0075ffdc, 0xfef2fe7c, 0xff3d0122, 0x02ea02dd, + 0x0044fc79, 0xfa65fc5d, 0x01e3074e, 0x082102ad, + 0xfa0ff48c, 0xf6fe00a9, 0x0b0a0e43, 0x0729fa33, + 0xf0a5f1c9, 0xfd430b00, 0x110d0000, + }, { // 7.0 MHz + 0x00010002, 0x0001fff3, 0xffdeffe2, 0x001b0076, + 0x009c002d, 0xff35fe68, 0xfeba0076, 0x029f0352, + 0x014dfd60, 0xfa69fb53, 0x00740688, 0x08a7042d, + 0xfb75f4d6, 0xf600ff2d, 0x0a220e7a, 0x0827fb22, + 0xf0edf17a, 0xfcd80ad6, 0x110d0000, + }, { // 7.1 MHz + 0x00000003, 0x0004fff9, 0xffe0ffd2, 0xfffb005e, + 0x00b0007a, 0xff8ffe7c, 0xfe53ffc1, 0x0221038c, + 0x0241fe6e, 0xfab6fa80, 0xff010587, 0x08e90590, + 0xfcf5f556, 0xf52bfdb3, 0x09210e95, 0x0919fc15, + 0xf13ff12f, 0xfc6e0aab, 0x110d0000, + }, { // 7.2 MHz + 0x00000003, 0x00070000, 0xffe6ffc9, 0xffdb0039, + 0x00af00b8, 0xfff4feb6, 0xfe13ff10, 0x01790388, + 0x0311ff92, 0xfb48f9ed, 0xfd980453, 0x08e306cd, + 0xfe88f60a, 0xf482fc40, 0x08080e93, 0x09fdfd0c, + 0xf19af0ea, 0xfc050a81, 0x110d0000, + }, { // 7.3 MHz + 0x00000002, 0x00080008, 0xfff0ffc9, 0xffc1000d, + 0x009800e2, 0x005bff10, 0xfe00fe74, 0x00b50345, + 0x03b000bc, 0xfc18f9a1, 0xfc4802f9, 0x089807dc, + 0x0022f6f0, 0xf407fada, 0x06da0e74, 0x0ad3fe06, + 0xf1fef0ab, 0xfb9c0a55, 0x110d0000, + }, { // 7.4 MHz + 0x00000001, 0x0008000e, 0xfffdffd0, 0xffafffdf, + 0x006e00f2, 0x00b8ff82, 0xfe1bfdf8, 0xffe302c8, + 0x041301dc, 0xfd1af99e, 0xfb1e0183, 0x080908b5, + 0x01bcf801, 0xf3bdf985, 0x059a0e38, 0x0b99ff03, + 0xf26cf071, 0xfb330a2a, 0x110d0000, + }, { // 7.5 MHz + 0xffff0000, 0x00070011, 0x000affdf, 0xffa9ffb5, + 0x003700e6, 0x01010000, 0xfe62fda8, 0xff140219, + 0x043502e1, 0xfe42f9e6, 0xfa270000, 0x073a0953, + 0x034cf939, 0xf3a4f845, 0x044c0de1, 0x0c4f0000, + 0xf2e2f03c, 0xfacc09fe, 0x110d0000, + }, { // 7.6 MHz + 0xffffffff, 0x00040012, 0x0016fff3, 0xffafff95, + 0xfff900c0, 0x0130007e, 0xfecefd89, 0xfe560146, + 0x041303bc, 0xff81fa76, 0xf96cfe7d, 0x063209b1, + 0x04c9fa93, 0xf3bdf71e, 0x02f30d6e, 0x0cf200fd, + 0xf361f00e, 0xfa6509d1, 0x110d0000, + }, { // 7.7 MHz + 0xfffffffe, 0x00010010, 0x001e0008, 0xffc1ff84, + 0xffbc0084, 0x013e00f0, 0xff56fd9f, 0xfdb8005c, + 0x03b00460, 0x00c7fb45, 0xf8f4fd07, 0x04fa09ce, + 0x062afc07, 0xf407f614, 0x01920ce0, 0x0d8301fa, + 0xf3e8efe5, 0xfa0009a4, 0x110d0000, + }, { // 7.8 MHz + 0x0000fffd, 0xfffd000b, 0x0022001d, 0xffdbff82, + 0xff870039, 0x012a014a, 0xffedfde7, 0xfd47ff6b, + 0x031104c6, 0x0202fc4c, 0xf8c6fbad, 0x039909a7, + 0x0767fd8e, 0xf482f52b, 0x002d0c39, 0x0e0002f4, + 0xf477efc2, 0xf99b0977, 0x110d0000, + }, { // 7.9 MHz + 0x0000fffd, 0xfffa0004, 0x0020002d, 0xfffbff91, + 0xff61ffe8, 0x00f70184, 0x0086fe5c, 0xfd0bfe85, + 0x024104e5, 0x0323fd7d, 0xf8e2fa79, 0x021d093f, + 0x0879ff22, 0xf52bf465, 0xfec70b79, 0x0e6803eb, + 0xf50defa5, 0xf937094a, 0x110d0000, + }, { // 8.0 MHz + 0x0000fffe, 0xfff8fffd, 0x00190036, 0x001bffaf, + 0xff4fff99, 0x00aa0198, 0x0112fef3, 0xfd09fdb9, + 0x014d04be, 0x041bfecc, 0xf947f978, 0x00900897, + 0x095a00b9, 0xf600f3c5, 0xfd650aa3, 0x0ebc04de, + 0xf5aaef8e, 0xf8d5091c, 0x110d0000, + }, { // 8.1 MHz + 0x0000ffff, 0xfff7fff6, 0x000e0038, 0x0037ffd7, + 0xff52ff56, 0x004b0184, 0x0186ffa1, 0xfd40fd16, + 0x00440452, 0x04de0029, 0xf9f2f8b2, 0xfefe07b5, + 0x0a05024d, 0xf6fef34d, 0xfc0a09b8, 0x0efa05cd, + 0xf64eef7d, 0xf87308ed, 0x110d0000, + }, { // 8.2 MHz + 0x00010000, 0xfff8fff0, 0x00000031, 0x004c0005, + 0xff6aff27, 0xffe4014a, 0x01d70057, 0xfdacfca6, + 0xff3603a7, 0x05610184, 0xfadbf82e, 0xfd74069f, + 0x0a7503d6, 0xf81ff2ff, 0xfab808b9, 0x0f2306b5, + 0xf6f9ef72, 0xf81308bf, 0x110d0000, + }, { // 8.3 MHz + 0x00010001, 0xfffbffee, 0xfff30022, 0x00560032, + 0xff95ff10, 0xff8000f0, 0x01fe0106, 0xfe46fc71, + 0xfe3502c7, 0x059e02ce, 0xfbf9f7f2, 0xfbff055b, + 0x0aa9054c, 0xf961f2db, 0xf97507aa, 0x0f350797, + 0xf7a9ef6d, 0xf7b40890, 0x110d0000, + }, { // 8.4 MHz + 0x00010002, 0xfffeffee, 0xffe8000f, 0x00540058, + 0xffcdff14, 0xff29007e, 0x01f6019e, 0xff01fc7c, + 0xfd5101bf, 0x059203f6, 0xfd41f7fe, 0xfaa903f3, + 0x0a9e06a9, 0xfabdf2e2, 0xf842068b, 0x0f320871, + 0xf85eef6e, 0xf7560860, 0x110d0000, + }, { // 8.5 MHz + 0x00000003, 0x0002fff2, 0xffe1fff9, 0x00460073, + 0x000bff34, 0xfee90000, 0x01c10215, 0xffd0fcc5, + 0xfc99009d, 0x053d04f1, 0xfea5f853, 0xf97d0270, + 0x0a5607e4, 0xfc2ef314, 0xf723055f, 0x0f180943, + 0xf919ef75, 0xf6fa0830, 0x110d0000, + }, { // 8.6 MHz + 0x00000003, 0x0005fff8, 0xffdeffe4, 0x002f007f, + 0x0048ff6b, 0xfec7ff82, 0x0163025f, 0x00a2fd47, + 0xfc17ff73, 0x04a405b2, 0x0017f8ed, 0xf88500dc, + 0x09d208f9, 0xfdaff370, 0xf61c0429, 0x0ee80a0b, + 0xf9d8ef82, 0xf6a00800, 0x110d0000, + }, { // 8.7 MHz + 0x00000003, 0x0007ffff, 0xffe1ffd4, 0x0010007a, + 0x007cffb2, 0xfec6ff10, 0x00e60277, 0x0168fdf9, + 0xfbd3fe50, 0x03ce0631, 0x0188f9c8, 0xf7c7ff43, + 0x091509e3, 0xff39f3f6, 0xf52d02ea, 0x0ea30ac9, + 0xfa9bef95, 0xf64607d0, 0x110d0000, + }, { // 8.8 MHz + 0x00000002, 0x00090007, 0xffe9ffca, 0xfff00065, + 0x00a10003, 0xfee6feb6, 0x0053025b, 0x0213fed0, + 0xfbd3fd46, 0x02c70668, 0x02eafadb, 0xf74bfdae, + 0x08230a9c, 0x00c7f4a3, 0xf45b01a6, 0x0e480b7c, + 0xfb61efae, 0xf5ef079f, 0x110d0000, + }, { // 8.9 MHz + 0xffff0000, 0x0008000d, 0xfff5ffc8, 0xffd10043, + 0x00b20053, 0xff24fe7c, 0xffb9020c, 0x0295ffbb, + 0xfc17fc64, 0x019b0654, 0x042dfc1c, 0xf714fc2a, + 0x07020b21, 0x0251f575, 0xf3a7005e, 0x0dd80c24, + 0xfc2aefcd, 0xf599076e, 0x110d0000, + }, { // 9.0 MHz + 0xffffffff, 0x00060011, 0x0002ffcf, 0xffba0018, + 0x00ad009a, 0xff79fe68, 0xff260192, 0x02e500ab, + 0xfc99fbb6, 0x005b05f7, 0x0545fd81, 0xf723fabf, + 0x05b80b70, 0x03d2f669, 0xf313ff15, 0x0d550cbf, + 0xfcf6eff2, 0xf544073d, 0x110d0000, + }, { // 9.1 MHz + 0xfffffffe, 0x00030012, 0x000fffdd, 0xffacffea, + 0x009300cf, 0xffdcfe7c, 0xfea600f7, 0x02fd0190, + 0xfd51fb46, 0xff150554, 0x0627fefd, 0xf778f978, + 0x044d0b87, 0x0543f77d, 0xf2a0fdcf, 0x0cbe0d4e, + 0xfdc4f01d, 0xf4f2070b, 0x110d0000, + }, { // 9.2 MHz + 0x0000fffd, 0x00000010, 0x001afff0, 0xffaaffbf, + 0x006700ed, 0x0043feb6, 0xfe460047, 0x02db0258, + 0xfe35fb1b, 0xfddc0473, 0x06c90082, 0xf811f85e, + 0x02c90b66, 0x069ff8ad, 0xf250fc8d, 0x0c140dcf, + 0xfe93f04d, 0xf4a106d9, 0x110d0000, + }, { // 9.3 MHz + 0x0000fffd, 0xfffc000c, 0x00200006, 0xffb4ff9c, + 0x002f00ef, 0x00a4ff10, 0xfe0dff92, 0x028102f7, + 0xff36fb37, 0xfcbf035e, 0x07260202, 0xf8e8f778, + 0x01340b0d, 0x07e1f9f4, 0xf223fb51, 0x0b590e42, + 0xff64f083, 0xf45206a7, 0x110d0000, + }, { // 9.4 MHz + 0x0000fffd, 0xfff90005, 0x0022001a, 0xffc9ff86, + 0xfff000d7, 0x00f2ff82, 0xfe01fee5, 0x01f60362, + 0x0044fb99, 0xfbcc0222, 0x07380370, 0xf9f7f6cc, + 0xff990a7e, 0x0902fb50, 0xf21afa1f, 0x0a8d0ea6, + 0x0034f0bf, 0xf4050675, 0x110d0000, + }, { // 9.5 MHz + 0x0000fffe, 0xfff8fffe, 0x001e002b, 0xffe5ff81, + 0xffb400a5, 0x01280000, 0xfe24fe50, 0x01460390, + 0x014dfc3a, 0xfb1000ce, 0x070104bf, 0xfb37f65f, + 0xfe0009bc, 0x0a00fcbb, 0xf235f8f8, 0x09b20efc, + 0x0105f101, 0xf3ba0642, 0x110d0000, + }, { // 9.6 MHz + 0x0001ffff, 0xfff8fff7, 0x00150036, 0x0005ff8c, + 0xff810061, 0x013d007e, 0xfe71fddf, 0x007c0380, + 0x0241fd13, 0xfa94ff70, 0x068005e2, 0xfc9bf633, + 0xfc7308ca, 0x0ad5fe30, 0xf274f7e0, 0x08c90f43, + 0x01d4f147, 0xf371060f, 0x110d0000, + }, { // 9.7 MHz + 0x00010001, 0xfff9fff1, 0x00090038, 0x0025ffa7, + 0xff5e0012, 0x013200f0, 0xfee3fd9b, 0xffaa0331, + 0x0311fe15, 0xfa60fe18, 0x05bd06d1, 0xfe1bf64a, + 0xfafa07ae, 0x0b7effab, 0xf2d5f6d7, 0x07d30f7a, + 0x02a3f194, 0xf32905dc, 0x110d0000, + }, { // 9.8 MHz + 0x00010002, 0xfffcffee, 0xfffb0032, 0x003fffcd, + 0xff4effc1, 0x0106014a, 0xff6efd8a, 0xfedd02aa, + 0x03b0ff34, 0xfa74fcd7, 0x04bf0781, 0xffaaf6a3, + 0xf99e066b, 0x0bf90128, 0xf359f5e1, 0x06d20fa2, + 0x0370f1e5, 0xf2e405a8, 0x110d0000, + }, { // 9.9 MHz + 0x00000003, 0xffffffee, 0xffef0024, 0x0051fffa, + 0xff54ff77, 0x00be0184, 0x0006fdad, 0xfe2701f3, + 0x0413005e, 0xfad1fbba, 0x039007ee, 0x013bf73d, + 0xf868050a, 0x0c4302a1, 0xf3fdf4fe, 0x05c70fba, + 0x043bf23c, 0xf2a10575, 0x110d0000, + }, { // 10.0 MHz + 0x00000003, 0x0003fff1, 0xffe50011, 0x00570027, + 0xff70ff3c, 0x00620198, 0x009efe01, 0xfd95011a, + 0x04350183, 0xfb71fad0, 0x023c0812, 0x02c3f811, + 0xf75e0390, 0x0c5c0411, 0xf4c1f432, 0x04b30fc1, + 0x0503f297, 0xf2610541, 0x110d0000, + }, { // 10.1 MHz + 0x00000003, 0x0006fff7, 0xffdffffc, 0x00510050, + 0xff9dff18, 0xfffc0184, 0x0128fe80, 0xfd32002e, + 0x04130292, 0xfc4dfa21, 0x00d107ee, 0x0435f91c, + 0xf6850205, 0x0c430573, 0xf5a1f37d, 0x03990fba, + 0x05c7f2f8, 0xf222050d, 0x110d0000, + }, { // 10.2 MHz + 0x00000002, 0x0008fffe, 0xffdfffe7, 0x003f006e, + 0xffd6ff0f, 0xff96014a, 0x0197ff1f, 0xfd05ff3e, + 0x03b0037c, 0xfd59f9b7, 0xff5d0781, 0x0585fa56, + 0xf5e4006f, 0x0bf906c4, 0xf69df2e0, 0x02790fa2, + 0x0688f35d, 0xf1e604d8, 0x110d0000, + }, { // 10.3 MHz + 0xffff0001, 0x00090005, 0xffe4ffd6, 0x0025007e, + 0x0014ff20, 0xff3c00f0, 0x01e1ffd0, 0xfd12fe5c, + 0x03110433, 0xfe88f996, 0xfdf106d1, 0x06aafbb7, + 0xf57efed8, 0x0b7e07ff, 0xf7b0f25e, 0x01560f7a, + 0x0745f3c7, 0xf1ac04a4, 0x110d0000, + }, { // 10.4 MHz + 0xffffffff, 0x0008000c, 0xffedffcb, 0x0005007d, + 0x0050ff4c, 0xfef6007e, 0x01ff0086, 0xfd58fd97, + 0x024104ad, 0xffcaf9c0, 0xfc9905e2, 0x079afd35, + 0xf555fd46, 0x0ad50920, 0xf8d9f1f6, 0x00310f43, + 0x07fdf435, 0xf174046f, 0x110d0000, + }, { // 10.5 MHz + 0xfffffffe, 0x00050011, 0xfffaffc8, 0xffe5006b, + 0x0082ff8c, 0xfecc0000, 0x01f00130, 0xfdd2fcfc, + 0x014d04e3, 0x010efa32, 0xfb6404bf, 0x084efec5, + 0xf569fbc2, 0x0a000a23, 0xfa15f1ab, 0xff0b0efc, + 0x08b0f4a7, 0xf13f043a, 0x110d0000, + }, { // 10.6 MHz + 0x0000fffd, 0x00020012, 0x0007ffcd, 0xffc9004c, + 0x00a4ffd9, 0xfec3ff82, 0x01b401c1, 0xfe76fc97, + 0x004404d2, 0x0245fae8, 0xfa5f0370, 0x08c1005f, + 0xf5bcfa52, 0x09020b04, 0xfb60f17b, 0xfde70ea6, + 0x095df51e, 0xf10c0405, 0x110d0000, + }, { // 10.7 MHz + 0x0000fffd, 0xffff0011, 0x0014ffdb, 0xffb40023, + 0x00b2002a, 0xfedbff10, 0x0150022d, 0xff38fc6f, + 0xff36047b, 0x035efbda, 0xf9940202, 0x08ee01f5, + 0xf649f8fe, 0x07e10bc2, 0xfcb6f169, 0xfcc60e42, + 0x0a04f599, 0xf0db03d0, 0x110d0000, + }, { // 10.8 MHz + 0x0000fffd, 0xfffb000d, 0x001dffed, 0xffaafff5, + 0x00aa0077, 0xff13feb6, 0x00ce026b, 0x000afc85, + 0xfe3503e3, 0x044cfcfb, 0xf90c0082, 0x08d5037f, + 0xf710f7cc, 0x069f0c59, 0xfe16f173, 0xfbaa0dcf, + 0x0aa5f617, 0xf0ad039b, 0x110d0000, + }, { // 10.9 MHz + 0x0000fffe, 0xfff90006, 0x00210003, 0xffacffc8, + 0x008e00b6, 0xff63fe7c, 0x003a0275, 0x00dafcda, + 0xfd510313, 0x0501fe40, 0xf8cbfefd, 0x087604f0, + 0xf80af6c2, 0x05430cc8, 0xff7af19a, 0xfa940d4e, + 0x0b3ff699, 0xf0810365, 0x110d0000, + }, { // 11.0 MHz + 0x0001ffff, 0xfff8ffff, 0x00210018, 0xffbaffa3, + 0x006000e1, 0xffc4fe68, 0xffa0024b, 0x019afd66, + 0xfc990216, 0x0575ff99, 0xf8d4fd81, 0x07d40640, + 0xf932f5e6, 0x03d20d0d, 0x00dff1de, 0xf9860cbf, + 0x0bd1f71e, 0xf058032f, 0x110d0000, + }, { // 11.1 MHz + 0x00010000, 0xfff8fff8, 0x001b0029, 0xffd1ff8a, + 0x002600f2, 0x002cfe7c, 0xff0f01f0, 0x023bfe20, + 0xfc1700fa, 0x05a200f7, 0xf927fc1c, 0x06f40765, + 0xfa82f53b, 0x02510d27, 0x0243f23d, 0xf8810c24, + 0x0c5cf7a7, 0xf03102fa, 0x110d0000, + }, { // 11.2 MHz + 0x00010002, 0xfffafff2, 0x00110035, 0xfff0ff81, + 0xffe700e7, 0x008ffeb6, 0xfe94016d, 0x02b0fefb, + 0xfbd3ffd1, 0x05850249, 0xf9c1fadb, 0x05de0858, + 0xfbf2f4c4, 0x00c70d17, 0x03a0f2b8, 0xf7870b7c, + 0x0cdff833, 0xf00d02c4, 0x110d0000, + }, { // 11.3 MHz + 0x00000003, 0xfffdffee, 0x00040038, 0x0010ff88, + 0xffac00c2, 0x00e2ff10, 0xfe3900cb, 0x02f1ffe9, + 0xfbd3feaa, 0x05210381, 0xfa9cf9c8, 0x04990912, + 0xfd7af484, 0xff390cdb, 0x04f4f34d, 0xf69a0ac9, + 0x0d5af8c1, 0xefec028e, 0x110d0000, + }, { // 11.4 MHz + 0x00000003, 0x0000ffee, 0xfff60033, 0x002fff9f, + 0xff7b0087, 0x011eff82, 0xfe080018, 0x02f900d8, + 0xfc17fd96, 0x04790490, 0xfbadf8ed, 0x032f098e, + 0xff10f47d, 0xfdaf0c75, 0x063cf3fc, 0xf5ba0a0b, + 0x0dccf952, 0xefcd0258, 0x110d0000, + }, { // 11.5 MHz + 0x00000003, 0x0004fff1, 0xffea0026, 0x0046ffc3, + 0xff5a003c, 0x013b0000, 0xfe04ff63, 0x02c801b8, + 0xfc99fca6, 0x0397056a, 0xfcecf853, 0x01ad09c9, + 0x00acf4ad, 0xfc2e0be7, 0x0773f4c2, 0xf4e90943, + 0x0e35f9e6, 0xefb10221, 0x110d0000, + }, { // 11.6 MHz + 0x00000002, 0x0007fff6, 0xffe20014, 0x0054ffee, + 0xff4effeb, 0x0137007e, 0xfe2efebb, 0x0260027a, + 0xfd51fbe6, 0x02870605, 0xfe4af7fe, 0x001d09c1, + 0x0243f515, 0xfabd0b32, 0x0897f59e, 0xf4280871, + 0x0e95fa7c, 0xef9701eb, 0x110d0000, + }, { // 11.7 MHz + 0xffff0001, 0x0008fffd, 0xffdeffff, 0x0056001d, + 0xff57ff9c, 0x011300f0, 0xfe82fe2e, 0x01ca0310, + 0xfe35fb62, 0x0155065a, 0xffbaf7f2, 0xfe8c0977, + 0x03cef5b2, 0xf9610a58, 0x09a5f68f, 0xf3790797, + 0x0eebfb14, 0xef8001b5, 0x110d0000, + }, { // 11.8 MHz + 0xffff0000, 0x00080004, 0xffe0ffe9, 0x004c0047, + 0xff75ff58, 0x00d1014a, 0xfef9fdc8, 0x0111036f, + 0xff36fb21, 0x00120665, 0x012df82e, 0xfd0708ec, + 0x0542f682, 0xf81f095c, 0x0a9af792, 0xf2db06b5, + 0x0f38fbad, 0xef6c017e, 0x110d0000, + }, { // 11.9 MHz + 0xffffffff, 0x0007000b, 0xffe7ffd8, 0x00370068, + 0xffa4ff28, 0x00790184, 0xff87fd91, 0x00430392, + 0x0044fb26, 0xfece0626, 0x0294f8b2, 0xfb990825, + 0x0698f77f, 0xf6fe0842, 0x0b73f8a7, 0xf25105cd, + 0x0f7bfc48, 0xef5a0148, 0x110d0000, + }, { // 12.0 MHz + 0x0000fffe, 0x00050010, 0xfff2ffcc, 0x001b007b, + 0xffdfff10, 0x00140198, 0x0020fd8e, 0xff710375, + 0x014dfb73, 0xfd9a059f, 0x03e0f978, 0xfa4e0726, + 0x07c8f8a7, 0xf600070c, 0x0c2ff9c9, 0xf1db04de, + 0x0fb4fce5, 0xef4b0111, 0x110d0000, + }, { // 12.1 MHz + 0x0000fffd, 0x00010012, 0xffffffc8, 0xfffb007e, + 0x001dff14, 0xffad0184, 0x00b7fdbe, 0xfea9031b, + 0x0241fc01, 0xfc8504d6, 0x0504fa79, 0xf93005f6, + 0x08caf9f2, 0xf52b05c0, 0x0ccbfaf9, 0xf17903eb, + 0x0fe3fd83, 0xef3f00db, 0x110d0000, + }, { // 12.2 MHz + 0x0000fffd, 0xfffe0011, 0x000cffcc, 0xffdb0071, + 0x0058ff32, 0xff4f014a, 0x013cfe1f, 0xfdfb028a, + 0x0311fcc9, 0xfb9d03d6, 0x05f4fbad, 0xf848049d, + 0x0999fb5b, 0xf4820461, 0x0d46fc32, 0xf12d02f4, + 0x1007fe21, 0xef3600a4, 0x110d0000, + }, { // 12.3 MHz + 0x0000fffe, 0xfffa000e, 0x0017ffd9, 0xffc10055, + 0x0088ff68, 0xff0400f0, 0x01a6fea7, 0xfd7501cc, + 0x03b0fdc0, 0xfaef02a8, 0x06a7fd07, 0xf79d0326, + 0x0a31fcda, 0xf40702f3, 0x0d9ffd72, 0xf0f601fa, + 0x1021fec0, 0xef2f006d, 0x110d0000, + }, { // 12.4 MHz + 0x0001ffff, 0xfff80007, 0x001fffeb, 0xffaf002d, + 0x00a8ffb0, 0xfed3007e, 0x01e9ff4c, 0xfd2000ee, + 0x0413fed8, 0xfa82015c, 0x0715fe7d, 0xf7340198, + 0x0a8dfe69, 0xf3bd017c, 0x0dd5feb8, 0xf0d500fd, + 0x1031ff60, 0xef2b0037, 0x110d0000, + }, { // 12.5 MHz + 0x00010000, 0xfff70000, 0x00220000, 0xffa90000, + 0x00b30000, 0xfec20000, 0x02000000, 0xfd030000, + 0x04350000, 0xfa5e0000, 0x073b0000, 0xf7110000, + 0x0aac0000, 0xf3a40000, 0x0de70000, 0xf0c90000, + 0x10360000, 0xef290000, 0x110d0000, + }, { // 12.6 MHz + 0x00010001, 0xfff8fff9, 0x001f0015, 0xffafffd3, + 0x00a80050, 0xfed3ff82, 0x01e900b4, 0xfd20ff12, + 0x04130128, 0xfa82fea4, 0x07150183, 0xf734fe68, + 0x0a8d0197, 0xf3bdfe84, 0x0dd50148, 0xf0d5ff03, + 0x103100a0, 0xef2bffc9, 0x110d0000, + }, { // 12.7 MHz + 0x00000002, 0xfffafff2, 0x00170027, 0xffc1ffab, + 0x00880098, 0xff04ff10, 0x01a60159, 0xfd75fe34, + 0x03b00240, 0xfaeffd58, 0x06a702f9, 0xf79dfcda, + 0x0a310326, 0xf407fd0d, 0x0d9f028e, 0xf0f6fe06, + 0x10210140, 0xef2fff93, 0x110d0000, + }, { // 12.8 MHz + 0x00000003, 0xfffeffef, 0x000c0034, 0xffdbff8f, + 0x005800ce, 0xff4ffeb6, 0x013c01e1, 0xfdfbfd76, + 0x03110337, 0xfb9dfc2a, 0x05f40453, 0xf848fb63, + 0x099904a5, 0xf482fb9f, 0x0d4603ce, 0xf12dfd0c, + 0x100701df, 0xef36ff5c, 0x110d0000, + }, { // 12.9 MHz + 0x00000003, 0x0001ffee, 0xffff0038, 0xfffbff82, + 0x001d00ec, 0xffadfe7c, 0x00b70242, 0xfea9fce5, + 0x024103ff, 0xfc85fb2a, 0x05040587, 0xf930fa0a, + 0x08ca060e, 0xf52bfa40, 0x0ccb0507, 0xf179fc15, + 0x0fe3027d, 0xef3fff25, 0x110d0000, + }, { // 13.0 MHz + 0x00000002, 0x0005fff0, 0xfff20034, 0x001bff85, + 0xffdf00f0, 0x0014fe68, 0x00200272, 0xff71fc8b, + 0x014d048d, 0xfd9afa61, 0x03e00688, 0xfa4ef8da, + 0x07c80759, 0xf600f8f4, 0x0c2f0637, 0xf1dbfb22, + 0x0fb4031b, 0xef4bfeef, 0x110d0000, + }, { // 13.1 MHz + 0xffff0001, 0x0007fff5, 0xffe70028, 0x0037ff98, + 0xffa400d8, 0x0079fe7c, 0xff87026f, 0x0043fc6e, + 0x004404da, 0xfecef9da, 0x0294074e, 0xfb99f7db, + 0x06980881, 0xf6fef7be, 0x0b730759, 0xf251fa33, + 0x0f7b03b8, 0xef5afeb8, 0x110d0000, + }, { // 13.2 MHz + 0xffff0000, 0x0008fffc, 0xffe00017, 0x004cffb9, + 0xff7500a8, 0x00d1feb6, 0xfef90238, 0x0111fc91, + 0xff3604df, 0x0012f99b, 0x012d07d2, 0xfd07f714, + 0x0542097e, 0xf81ff6a4, 0x0a9a086e, 0xf2dbf94b, + 0x0f380453, 0xef6cfe82, 0x110d0000, + }, { // 13.3 MHz + 0xffffffff, 0x00080003, 0xffde0001, 0x0056ffe3, + 0xff570064, 0x0113ff10, 0xfe8201d2, 0x01cafcf0, + 0xfe35049e, 0x0155f9a6, 0xffba080e, 0xfe8cf689, + 0x03ce0a4e, 0xf961f5a8, 0x09a50971, 0xf379f869, + 0x0eeb04ec, 0xef80fe4b, 0x110d0000, + }, { // 13.4 MHz + 0x0000fffe, 0x0007000a, 0xffe2ffec, 0x00540012, + 0xff4e0015, 0x0137ff82, 0xfe2e0145, 0x0260fd86, + 0xfd51041a, 0x0287f9fb, 0xfe4a0802, 0x001df63f, + 0x02430aeb, 0xfabdf4ce, 0x08970a62, 0xf428f78f, + 0x0e950584, 0xef97fe15, 0x110d0000, + }, { // 13.5 MHz + 0x0000fffd, 0x0004000f, 0xffeaffda, 0x0046003d, + 0xff5affc4, 0x013b0000, 0xfe04009d, 0x02c8fe48, + 0xfc99035a, 0x0397fa96, 0xfcec07ad, 0x01adf637, + 0x00ac0b53, 0xfc2ef419, 0x07730b3e, 0xf4e9f6bd, + 0x0e35061a, 0xefb1fddf, 0x110d0000, + }, { // 13.6 MHz + 0x0000fffd, 0x00000012, 0xfff6ffcd, 0x002f0061, + 0xff7bff79, 0x011e007e, 0xfe08ffe8, 0x02f9ff28, + 0xfc17026a, 0x0479fb70, 0xfbad0713, 0x032ff672, + 0xff100b83, 0xfdaff38b, 0x063c0c04, 0xf5baf5f5, + 0x0dcc06ae, 0xefcdfda8, 0x110d0000, + }, { // 13.7 MHz + 0x0000fffd, 0xfffd0012, 0x0004ffc8, 0x00100078, + 0xffacff3e, 0x00e200f0, 0xfe39ff35, 0x02f10017, + 0xfbd30156, 0x0521fc7f, 0xfa9c0638, 0x0499f6ee, + 0xfd7a0b7c, 0xff39f325, 0x04f40cb3, 0xf69af537, + 0x0d5a073f, 0xefecfd72, 0x110d0000, + }, { // 13.8 MHz + 0x0001fffe, 0xfffa000e, 0x0011ffcb, 0xfff0007f, + 0xffe7ff19, 0x008f014a, 0xfe94fe93, 0x02b00105, + 0xfbd3002f, 0x0585fdb7, 0xf9c10525, 0x05def7a8, + 0xfbf20b3c, 0x00c7f2e9, 0x03a00d48, 0xf787f484, + 0x0cdf07cd, 0xf00dfd3c, 0x110d0000, + }, { // 13.9 MHz + 0x00010000, 0xfff80008, 0x001bffd7, 0xffd10076, + 0x0026ff0e, 0x002c0184, 0xff0ffe10, 0x023b01e0, + 0xfc17ff06, 0x05a2ff09, 0xf92703e4, 0x06f4f89b, + 0xfa820ac5, 0x0251f2d9, 0x02430dc3, 0xf881f3dc, + 0x0c5c0859, 0xf031fd06, 0x110d0000, + }, { // 14.0 MHz + 0x00010001, 0xfff80001, 0x0021ffe8, 0xffba005d, + 0x0060ff1f, 0xffc40198, 0xffa0fdb5, 0x019a029a, + 0xfc99fdea, 0x05750067, 0xf8d4027f, 0x07d4f9c0, + 0xf9320a1a, 0x03d2f2f3, 0x00df0e22, 0xf986f341, + 0x0bd108e2, 0xf058fcd1, 0x110d0000, + }, { // 14.1 MHz + 0x00000002, 0xfff9fffa, 0x0021fffd, 0xffac0038, + 0x008eff4a, 0xff630184, 0x003afd8b, 0x00da0326, + 0xfd51fced, 0x050101c0, 0xf8cb0103, 0x0876fb10, + 0xf80a093e, 0x0543f338, 0xff7a0e66, 0xfa94f2b2, + 0x0b3f0967, 0xf081fc9b, 0x110d0000, + }, { // 14.2 MHz + 0x00000003, 0xfffbfff3, 0x001d0013, 0xffaa000b, + 0x00aaff89, 0xff13014a, 0x00cefd95, 0x000a037b, + 0xfe35fc1d, 0x044c0305, 0xf90cff7e, 0x08d5fc81, + 0xf7100834, 0x069ff3a7, 0xfe160e8d, 0xfbaaf231, + 0x0aa509e9, 0xf0adfc65, 0x110d0000, + }, { // 14.3 MHz + 0x00000003, 0xffffffef, 0x00140025, 0xffb4ffdd, + 0x00b2ffd6, 0xfedb00f0, 0x0150fdd3, 0xff380391, + 0xff36fb85, 0x035e0426, 0xf994fdfe, 0x08eefe0b, + 0xf6490702, 0x07e1f43e, 0xfcb60e97, 0xfcc6f1be, + 0x0a040a67, 0xf0dbfc30, 0x110d0000, + }, { // 14.4 MHz + 0x00000003, 0x0002ffee, 0x00070033, 0xffc9ffb4, + 0x00a40027, 0xfec3007e, 0x01b4fe3f, 0xfe760369, + 0x0044fb2e, 0x02450518, 0xfa5ffc90, 0x08c1ffa1, + 0xf5bc05ae, 0x0902f4fc, 0xfb600e85, 0xfde7f15a, + 0x095d0ae2, 0xf10cfbfb, 0x110d0000, + }, { // 14.5 MHz + 0xffff0002, 0x0005ffef, 0xfffa0038, 0xffe5ff95, + 0x00820074, 0xfecc0000, 0x01f0fed0, 0xfdd20304, + 0x014dfb1d, 0x010e05ce, 0xfb64fb41, 0x084e013b, + 0xf569043e, 0x0a00f5dd, 0xfa150e55, 0xff0bf104, + 0x08b00b59, 0xf13ffbc6, 0x110d0000, + }, { // 14.6 MHz + 0xffff0001, 0x0008fff4, 0xffed0035, 0x0005ff83, + 0x005000b4, 0xfef6ff82, 0x01ffff7a, 0xfd580269, + 0x0241fb53, 0xffca0640, 0xfc99fa1e, 0x079a02cb, + 0xf55502ba, 0x0ad5f6e0, 0xf8d90e0a, 0x0031f0bd, + 0x07fd0bcb, 0xf174fb91, 0x110d0000, + }, { // 14.7 MHz + 0xffffffff, 0x0009fffb, 0xffe4002a, 0x0025ff82, + 0x001400e0, 0xff3cff10, 0x01e10030, 0xfd1201a4, + 0x0311fbcd, 0xfe88066a, 0xfdf1f92f, 0x06aa0449, + 0xf57e0128, 0x0b7ef801, 0xf7b00da2, 0x0156f086, + 0x07450c39, 0xf1acfb5c, 0x110d0000, + }, { // 14.8 MHz + 0x0000fffe, 0x00080002, 0xffdf0019, 0x003fff92, + 0xffd600f1, 0xff96feb6, 0x019700e1, 0xfd0500c2, + 0x03b0fc84, 0xfd590649, 0xff5df87f, 0x058505aa, + 0xf5e4ff91, 0x0bf9f93c, 0xf69d0d20, 0x0279f05e, + 0x06880ca3, 0xf1e6fb28, 0x110d0000, + }, { // 14.9 MHz + 0x0000fffd, 0x00060009, 0xffdf0004, 0x0051ffb0, + 0xff9d00e8, 0xfffcfe7c, 0x01280180, 0xfd32ffd2, + 0x0413fd6e, 0xfc4d05df, 0x00d1f812, 0x043506e4, + 0xf685fdfb, 0x0c43fa8d, 0xf5a10c83, 0x0399f046, + 0x05c70d08, 0xf222faf3, 0x110d0000, + }, { // 15.0 MHz + 0x0000fffd, 0x0003000f, 0xffe5ffef, 0x0057ffd9, + 0xff7000c4, 0x0062fe68, 0x009e01ff, 0xfd95fee6, + 0x0435fe7d, 0xfb710530, 0x023cf7ee, 0x02c307ef, + 0xf75efc70, 0x0c5cfbef, 0xf4c10bce, 0x04b3f03f, + 0x05030d69, 0xf261fabf, 0x110d0000, + }, { // 15.1 MHz + 0x0000fffd, 0xffff0012, 0xffefffdc, 0x00510006, + 0xff540089, 0x00befe7c, 0x00060253, 0xfe27fe0d, + 0x0413ffa2, 0xfad10446, 0x0390f812, 0x013b08c3, + 0xf868faf6, 0x0c43fd5f, 0xf3fd0b02, 0x05c7f046, + 0x043b0dc4, 0xf2a1fa8b, 0x110d0000, + }, { // 15.2 MHz + 0x0001fffe, 0xfffc0012, 0xfffbffce, 0x003f0033, + 0xff4e003f, 0x0106feb6, 0xff6e0276, 0xfeddfd56, + 0x03b000cc, 0xfa740329, 0x04bff87f, 0xffaa095d, + 0xf99ef995, 0x0bf9fed8, 0xf3590a1f, 0x06d2f05e, + 0x03700e1b, 0xf2e4fa58, 0x110d0000, + }, { // 15.3 MHz + 0x0001ffff, 0xfff9000f, 0x0009ffc8, 0x00250059, + 0xff5effee, 0x0132ff10, 0xfee30265, 0xffaafccf, + 0x031101eb, 0xfa6001e8, 0x05bdf92f, 0xfe1b09b6, + 0xfafaf852, 0x0b7e0055, 0xf2d50929, 0x07d3f086, + 0x02a30e6c, 0xf329fa24, 0x110d0000, + }, { // 15.4 MHz + 0x00010001, 0xfff80009, 0x0015ffca, 0x00050074, + 0xff81ff9f, 0x013dff82, 0xfe710221, 0x007cfc80, + 0x024102ed, 0xfa940090, 0x0680fa1e, 0xfc9b09cd, + 0xfc73f736, 0x0ad501d0, 0xf2740820, 0x08c9f0bd, + 0x01d40eb9, 0xf371f9f1, 0x110d0000, + }, { // 15.5 MHz + 0x00000002, 0xfff80002, 0x001effd5, 0xffe5007f, + 0xffb4ff5b, 0x01280000, 0xfe2401b0, 0x0146fc70, + 0x014d03c6, 0xfb10ff32, 0x0701fb41, 0xfb3709a1, + 0xfe00f644, 0x0a000345, 0xf2350708, 0x09b2f104, + 0x01050eff, 0xf3baf9be, 0x110d0000, + }, { // 15.6 MHz + 0x00000003, 0xfff9fffb, 0x0022ffe6, 0xffc9007a, + 0xfff0ff29, 0x00f2007e, 0xfe01011b, 0x01f6fc9e, + 0x00440467, 0xfbccfdde, 0x0738fc90, 0xf9f70934, + 0xff99f582, 0x090204b0, 0xf21a05e1, 0x0a8df15a, + 0x00340f41, 0xf405f98b, 0x110d0000, + }, { // 15.7 MHz + 0x00000003, 0xfffcfff4, 0x0020fffa, 0xffb40064, + 0x002fff11, 0x00a400f0, 0xfe0d006e, 0x0281fd09, + 0xff3604c9, 0xfcbffca2, 0x0726fdfe, 0xf8e80888, + 0x0134f4f3, 0x07e1060c, 0xf22304af, 0x0b59f1be, + 0xff640f7d, 0xf452f959, 0x110d0000, + }, { // 15.8 MHz + 0x00000003, 0x0000fff0, 0x001a0010, 0xffaa0041, + 0x0067ff13, 0x0043014a, 0xfe46ffb9, 0x02dbfda8, + 0xfe3504e5, 0xfddcfb8d, 0x06c9ff7e, 0xf81107a2, + 0x02c9f49a, 0x069f0753, 0xf2500373, 0x0c14f231, + 0xfe930fb3, 0xf4a1f927, 0x110d0000, + }, { // 15.9 MHz + 0xffff0002, 0x0003ffee, 0x000f0023, 0xffac0016, + 0x0093ff31, 0xffdc0184, 0xfea6ff09, 0x02fdfe70, + 0xfd5104ba, 0xff15faac, 0x06270103, 0xf7780688, + 0x044df479, 0x05430883, 0xf2a00231, 0x0cbef2b2, + 0xfdc40fe3, 0xf4f2f8f5, 0x110d0000, + }, { // 16.0 MHz + 0xffff0001, 0x0006ffef, 0x00020031, 0xffbaffe8, + 0x00adff66, 0xff790198, 0xff26fe6e, 0x02e5ff55, + 0xfc99044a, 0x005bfa09, 0x0545027f, 0xf7230541, + 0x05b8f490, 0x03d20997, 0xf31300eb, 0x0d55f341, + 0xfcf6100e, 0xf544f8c3, 0x110d0000, + } +}; + +static void cx23885_dif_setup(struct i2c_client *client, u32 ifHz) +{ + u64 pll_freq; + u32 pll_freq_word; + const u32 *coeffs; + + v4l_dbg(1, cx25840_debug, client, "%s(%d)\n", __func__, ifHz); + + /* Assuming TV */ + /* Calculate the PLL frequency word based on the adjusted ifHz */ + pll_freq = div_u64((u64)ifHz * 268435456, 50000000); + pll_freq_word = (u32)pll_freq; + + cx25840_write4(client, DIF_PLL_FREQ_WORD, pll_freq_word); + + /* Round down to the nearest 100KHz */ + ifHz = (ifHz / 100000) * 100000; + + if (ifHz < 3000000) + ifHz = 3000000; + + if (ifHz > 16000000) + ifHz = 16000000; + + v4l_dbg(1, cx25840_debug, client, "%s(%d) again\n", __func__, ifHz); + + coeffs = ifhz_coeffs[(ifHz - 3000000) / 100000]; + cx25840_write4(client, DIF_BPF_COEFF01, coeffs[0]); + cx25840_write4(client, DIF_BPF_COEFF23, coeffs[1]); + cx25840_write4(client, DIF_BPF_COEFF45, coeffs[2]); + cx25840_write4(client, DIF_BPF_COEFF67, coeffs[3]); + cx25840_write4(client, DIF_BPF_COEFF89, coeffs[4]); + cx25840_write4(client, DIF_BPF_COEFF1011, coeffs[5]); + cx25840_write4(client, DIF_BPF_COEFF1213, coeffs[6]); + cx25840_write4(client, DIF_BPF_COEFF1415, coeffs[7]); + cx25840_write4(client, DIF_BPF_COEFF1617, coeffs[8]); + cx25840_write4(client, DIF_BPF_COEFF1819, coeffs[9]); + cx25840_write4(client, DIF_BPF_COEFF2021, coeffs[10]); + cx25840_write4(client, DIF_BPF_COEFF2223, coeffs[11]); + cx25840_write4(client, DIF_BPF_COEFF2425, coeffs[12]); + cx25840_write4(client, DIF_BPF_COEFF2627, coeffs[13]); + cx25840_write4(client, DIF_BPF_COEFF2829, coeffs[14]); + cx25840_write4(client, DIF_BPF_COEFF3031, coeffs[15]); + cx25840_write4(client, DIF_BPF_COEFF3233, coeffs[16]); + cx25840_write4(client, DIF_BPF_COEFF3435, coeffs[17]); + cx25840_write4(client, DIF_BPF_COEFF36, coeffs[18]); +} + +static void cx23888_std_setup(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + v4l2_std_id std = state->std; + u32 ifHz; + + cx25840_write4(client, 0x478, 0x6628021F); + cx25840_write4(client, 0x400, 0x0); + cx25840_write4(client, 0x4b4, 0x20524030); + cx25840_write4(client, 0x47c, 0x010a8263); + + if (std & V4L2_STD_525_60) { + v4l_dbg(1, cx25840_debug, client, "%s() Selecting NTSC", + __func__); + + /* Horiz / vert timing */ + cx25840_write4(client, 0x428, 0x1e1e601a); + cx25840_write4(client, 0x424, 0x5b2d007a); + + /* DIF NTSC */ + cx25840_write4(client, 0x304, 0x6503bc0c); + cx25840_write4(client, 0x308, 0xbd038c85); + cx25840_write4(client, 0x30c, 0x1db4640a); + cx25840_write4(client, 0x310, 0x00008800); + cx25840_write4(client, 0x314, 0x44400400); + cx25840_write4(client, 0x32c, 0x0c800800); + cx25840_write4(client, 0x330, 0x27000100); + cx25840_write4(client, 0x334, 0x1f296e1f); + cx25840_write4(client, 0x338, 0x009f50c1); + cx25840_write4(client, 0x340, 0x1befbf06); + cx25840_write4(client, 0x344, 0x000035e8); + + /* DIF I/F */ + ifHz = 5400000; + + } else { + v4l_dbg(1, cx25840_debug, client, "%s() Selecting PAL-BG", + __func__); + + /* Horiz / vert timing */ + cx25840_write4(client, 0x428, 0x28244024); + cx25840_write4(client, 0x424, 0x5d2d0084); + + /* DIF */ + cx25840_write4(client, 0x304, 0x6503bc0c); + cx25840_write4(client, 0x308, 0xbd038c85); + cx25840_write4(client, 0x30c, 0x1db4640a); + cx25840_write4(client, 0x310, 0x00008800); + cx25840_write4(client, 0x314, 0x44400600); + cx25840_write4(client, 0x32c, 0x0c800800); + cx25840_write4(client, 0x330, 0x27000100); + cx25840_write4(client, 0x334, 0x213530ec); + cx25840_write4(client, 0x338, 0x00a65ba8); + cx25840_write4(client, 0x340, 0x1befbf06); + cx25840_write4(client, 0x344, 0x000035e8); + + /* DIF I/F */ + ifHz = 6000000; + } + + cx23885_dif_setup(client, ifHz); + + /* Explicitly ensure the inputs are reconfigured after + * a standard change. + */ + set_input(client, state->vid_input, state->aud_input); +} + +/* ----------------------------------------------------------------------- */ + +static const struct v4l2_ctrl_ops cx25840_ctrl_ops = { + .s_ctrl = cx25840_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops cx25840_core_ops = { + .log_status = cx25840_log_status, + .reset = cx25840_reset, + /* calling the (optional) init op will turn on the generic mode */ + .init = cx25840_init, + .load_fw = cx25840_load_fw, + .s_io_pin_config = common_s_io_pin_config, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = cx25840_g_register, + .s_register = cx25840_s_register, +#endif + .interrupt_service_routine = cx25840_irq_handler, +}; + +static const struct v4l2_subdev_tuner_ops cx25840_tuner_ops = { + .s_frequency = cx25840_s_frequency, + .s_radio = cx25840_s_radio, + .g_tuner = cx25840_g_tuner, + .s_tuner = cx25840_s_tuner, +}; + +static const struct v4l2_subdev_audio_ops cx25840_audio_ops = { + .s_clock_freq = cx25840_s_clock_freq, + .s_routing = cx25840_s_audio_routing, + .s_stream = cx25840_s_audio_stream, +}; + +static const struct v4l2_subdev_video_ops cx25840_video_ops = { + .g_std = cx25840_g_std, + .s_std = cx25840_s_std, + .querystd = cx25840_querystd, + .s_routing = cx25840_s_video_routing, + .s_stream = cx25840_s_stream, + .g_input_status = cx25840_g_input_status, +}; + +static const struct v4l2_subdev_vbi_ops cx25840_vbi_ops = { + .decode_vbi_line = cx25840_decode_vbi_line, + .s_raw_fmt = cx25840_s_raw_fmt, + .s_sliced_fmt = cx25840_s_sliced_fmt, + .g_sliced_fmt = cx25840_g_sliced_fmt, +}; + +static const struct v4l2_subdev_pad_ops cx25840_pad_ops = { + .set_fmt = cx25840_set_fmt, +}; + +static const struct v4l2_subdev_ops cx25840_ops = { + .core = &cx25840_core_ops, + .tuner = &cx25840_tuner_ops, + .audio = &cx25840_audio_ops, + .video = &cx25840_video_ops, + .vbi = &cx25840_vbi_ops, + .pad = &cx25840_pad_ops, + .ir = &cx25840_ir_ops, +}; + +/* ----------------------------------------------------------------------- */ + +static u32 get_cx2388x_ident(struct i2c_client *client) +{ + u32 ret; + + /* Come out of digital power down */ + cx25840_write(client, 0x000, 0); + + /* + * Detecting whether the part is cx23885/7/8 is more + * difficult than it needs to be. No ID register. Instead we + * probe certain registers indicated in the datasheets to look + * for specific defaults that differ between the silicon designs. + */ + + /* It's either 885/7 if the IR Tx Clk Divider register exists */ + if (cx25840_read4(client, 0x204) & 0xffff) { + /* + * CX23885 returns bogus repetitive byte values for the DIF, + * which doesn't exist for it. (Ex. 8a8a8a8a or 31313131) + */ + ret = cx25840_read4(client, 0x300); + if (((ret & 0xffff0000) >> 16) == (ret & 0xffff)) { + /* No DIF */ + ret = CX23885_AV; + } else { + /* + * CX23887 has a broken DIF, but the registers + * appear valid (but unused), good enough to detect. + */ + ret = CX23887_AV; + } + } else if (cx25840_read4(client, 0x300) & 0x0fffffff) { + /* DIF PLL Freq Word reg exists; chip must be a CX23888 */ + ret = CX23888_AV; + } else { + v4l_err(client, "Unable to detect h/w, assuming cx23887\n"); + ret = CX23887_AV; + } + + /* Back into digital power down */ + cx25840_write(client, 0x000, 2); + return ret; +} + +static int cx25840_probe(struct i2c_client *client) +{ + struct cx25840_state *state; + struct v4l2_subdev *sd; + int default_volume; + u32 id; + u16 device_id; +#if defined(CONFIG_MEDIA_CONTROLLER) + int ret; +#endif + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_dbg(1, cx25840_debug, client, + "detecting cx25840 client on address 0x%x\n", + client->addr << 1); + + device_id = cx25840_read(client, 0x101) << 8; + device_id |= cx25840_read(client, 0x100); + v4l_dbg(1, cx25840_debug, client, "device_id = 0x%04x\n", device_id); + + /* + * The high byte of the device ID should be + * 0x83 for the cx2583x and 0x84 for the cx2584x + */ + if ((device_id & 0xff00) == 0x8300) { + id = CX25836 + ((device_id >> 4) & 0xf) - 6; + } else if ((device_id & 0xff00) == 0x8400) { + id = CX25840 + ((device_id >> 4) & 0xf); + } else if (device_id == 0x0000) { + id = get_cx2388x_ident(client); + } else if ((device_id & 0xfff0) == 0x5A30) { + /* The CX23100 (0x5A3C = 23100) doesn't have an A/V decoder */ + id = CX2310X_AV; + } else if ((device_id & 0xff) == (device_id >> 8)) { + v4l_err(client, + "likely a confused/unresponsive cx2388[578] A/V decoder found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + v4l_err(client, + "A method to reset it from the cx25840 driver software is not known at this time\n"); + return -ENODEV; + } else { + v4l_dbg(1, cx25840_debug, client, "cx25840 not found\n"); + return -ENODEV; + } + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &cx25840_ops); +#if defined(CONFIG_MEDIA_CONTROLLER) + /* + * TODO: add media controller support for analog video inputs like + * composite, svideo, etc. + * A real input pad for this analog demod would be like: + * ___________ + * TUNER --------> | | + * | | + * SVIDEO .......> | cx25840 | + * | | + * COMPOSITE1 ...> |_________| + * + * However, at least for now, there's no much gain on modelling + * those extra inputs. So, let's add it only when needed. + */ + state->pads[CX25840_PAD_INPUT].flags = MEDIA_PAD_FL_SINK; + state->pads[CX25840_PAD_INPUT].sig_type = PAD_SIGNAL_ANALOG; + state->pads[CX25840_PAD_VID_OUT].flags = MEDIA_PAD_FL_SOURCE; + state->pads[CX25840_PAD_VID_OUT].sig_type = PAD_SIGNAL_DV; + sd->entity.function = MEDIA_ENT_F_ATV_DECODER; + + ret = media_entity_pads_init(&sd->entity, ARRAY_SIZE(state->pads), + state->pads); + if (ret < 0) { + v4l_info(client, "failed to initialize media entity!\n"); + return ret; + } +#endif + + switch (id) { + case CX23885_AV: + v4l_info(client, "cx23885 A/V decoder found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + break; + case CX23887_AV: + v4l_info(client, "cx23887 A/V decoder found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + break; + case CX23888_AV: + v4l_info(client, "cx23888 A/V decoder found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + break; + case CX2310X_AV: + v4l_info(client, "cx%d A/V decoder found @ 0x%x (%s)\n", + device_id, client->addr << 1, client->adapter->name); + break; + case CX25840: + case CX25841: + case CX25842: + case CX25843: + /* + * Note: revision '(device_id & 0x0f) == 2' was never built. + * The marking skips from 0x1 == 22 to 0x3 == 23. + */ + v4l_info(client, "cx25%3x-2%x found @ 0x%x (%s)\n", + (device_id & 0xfff0) >> 4, + (device_id & 0x0f) < 3 ? (device_id & 0x0f) + 1 + : (device_id & 0x0f), + client->addr << 1, client->adapter->name); + break; + case CX25836: + case CX25837: + default: + v4l_info(client, "cx25%3x-%x found @ 0x%x (%s)\n", + (device_id & 0xfff0) >> 4, device_id & 0x0f, + client->addr << 1, client->adapter->name); + break; + } + + state->c = client; + state->vid_input = CX25840_COMPOSITE7; + state->aud_input = CX25840_AUDIO8; + state->audclk_freq = 48000; + state->audmode = V4L2_TUNER_MODE_LANG1; + state->vbi_line_offset = 8; + state->id = id; + state->rev = device_id; + state->vbi_regs_offset = id == CX23888_AV ? 0x500 - 0x424 : 0; + state->std = V4L2_STD_NTSC_M; + v4l2_ctrl_handler_init(&state->hdl, 9); + v4l2_ctrl_new_std(&state->hdl, &cx25840_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(&state->hdl, &cx25840_ctrl_ops, + V4L2_CID_CONTRAST, 0, 127, 1, 64); + v4l2_ctrl_new_std(&state->hdl, &cx25840_ctrl_ops, + V4L2_CID_SATURATION, 0, 127, 1, 64); + v4l2_ctrl_new_std(&state->hdl, &cx25840_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + if (!is_cx2583x(state)) { + default_volume = cx25840_read(client, 0x8d4); + /* + * Enforce the legacy PVR-350/MSP3400 to PVR-150/CX25843 volume + * scale mapping limits to avoid -ERANGE errors when + * initializing the volume control + */ + if (default_volume > 228) { + /* Bottom out at -96 dB, v4l2 vol range 0x2e00-0x2fff */ + default_volume = 228; + cx25840_write(client, 0x8d4, 228); + } else if (default_volume < 20) { + /* Top out at + 8 dB, v4l2 vol range 0xfe00-0xffff */ + default_volume = 20; + cx25840_write(client, 0x8d4, 20); + } + default_volume = (((228 - default_volume) >> 1) + 23) << 9; + + state->volume = v4l2_ctrl_new_std(&state->hdl, + &cx25840_audio_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, + 0, 65535, 65535 / 100, + default_volume); + state->mute = v4l2_ctrl_new_std(&state->hdl, + &cx25840_audio_ctrl_ops, + V4L2_CID_AUDIO_MUTE, + 0, 1, 1, 0); + v4l2_ctrl_new_std(&state->hdl, &cx25840_audio_ctrl_ops, + V4L2_CID_AUDIO_BALANCE, + 0, 65535, 65535 / 100, 32768); + v4l2_ctrl_new_std(&state->hdl, &cx25840_audio_ctrl_ops, + V4L2_CID_AUDIO_BASS, + 0, 65535, 65535 / 100, 32768); + v4l2_ctrl_new_std(&state->hdl, &cx25840_audio_ctrl_ops, + V4L2_CID_AUDIO_TREBLE, + 0, 65535, 65535 / 100, 32768); + } + sd->ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + media_entity_cleanup(&sd->entity); + return err; + } + if (!is_cx2583x(state)) + v4l2_ctrl_cluster(2, &state->volume); + v4l2_ctrl_handler_setup(&state->hdl); + + if (client->dev.platform_data) { + struct cx25840_platform_data *pdata = client->dev.platform_data; + + state->pvr150_workaround = pdata->pvr150_workaround; + } + + cx25840_ir_probe(sd); + return 0; +} + +static void cx25840_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct cx25840_state *state = to_state(sd); + + cx25840_ir_remove(sd); + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&state->hdl); + media_entity_cleanup(&sd->entity); +} + +static const struct i2c_device_id cx25840_id[] = { + { .name = "cx25840" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cx25840_id); + +static struct i2c_driver cx25840_driver = { + .driver = { + .name = "cx25840", + }, + .probe = cx25840_probe, + .remove = cx25840_remove, + .id_table = cx25840_id, +}; + +module_i2c_driver(cx25840_driver); diff --git a/drivers/media/i2c/cx25840/cx25840-core.h b/drivers/media/i2c/cx25840/cx25840-core.h new file mode 100644 index 000000000..8b89e9068 --- /dev/null +++ b/drivers/media/i2c/cx25840/cx25840-core.h @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* cx25840 internal API header + * + * Copyright (C) 2003-2004 Chris Kennedy + */ + +#ifndef _CX25840_CORE_H_ +#define _CX25840_CORE_H_ + +#include +#include +#include +#include + +struct cx25840_ir_state; + +enum cx25840_model { + CX23885_AV, + CX23887_AV, + CX23888_AV, + CX2310X_AV, + CX25840, + CX25841, + CX25842, + CX25843, + CX25836, + CX25837, +}; + +enum cx25840_media_pads { + CX25840_PAD_INPUT, + CX25840_PAD_VID_OUT, + + CX25840_NUM_PADS +}; + +/** + * struct cx25840_state - a device instance private data + * @c: i2c_client struct representing this device + * @sd: our V4L2 sub-device + * @hdl: our V4L2 control handler + * @volume: audio volume V4L2 control (non-cx2583x devices only) + * @mute: audio mute V4L2 control (non-cx2583x devices only) + * @pvr150_workaround: whether we enable workaround for Hauppauge PVR150 + * hardware bug (audio dropping out) + * @generic_mode: whether we disable ivtv-specific hacks + * this mode gets turned on when the bridge driver calls + * cx25840 subdevice init core op + * @radio: set if we are currently in the radio mode, otherwise + * the current mode is non-radio (that is, video) + * @std: currently set video standard + * @vid_input: currently set video input + * @vid_config: currently set video output configuration + * only used in the generic mode + * @aud_input: currently set audio input + * @audclk_freq: currently set audio sample rate + * @audmode: currently set audio mode (when in non-radio mode) + * @vbi_line_offset: vbi line number offset + * @id: exact device model + * @rev: raw device id read from the chip + * @is_initialized: whether we have already loaded firmware into the chip + * and initialized it + * @vbi_regs_offset: offset of vbi regs + * @fw_wait: wait queue to wake an initialization function up when + * firmware loading (on a separate workqueue) finishes + * @fw_work: a work that actually loads the firmware on a separate + * workqueue + * @ir_state: a pointer to chip IR controller private data + * @pads: array of supported chip pads (currently only a stub) + */ +struct cx25840_state { + struct i2c_client *c; + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct { + /* volume cluster */ + struct v4l2_ctrl *volume; + struct v4l2_ctrl *mute; + }; + int pvr150_workaround; + bool generic_mode; + int radio; + v4l2_std_id std; + enum cx25840_video_input vid_input; + u32 vid_config; + enum cx25840_audio_input aud_input; + u32 audclk_freq; + int audmode; + int vbi_line_offset; + enum cx25840_model id; + u32 rev; + int is_initialized; + unsigned int vbi_regs_offset; + wait_queue_head_t fw_wait; + struct work_struct fw_work; + struct cx25840_ir_state *ir_state; +#if defined(CONFIG_MEDIA_CONTROLLER) + struct media_pad pads[CX25840_NUM_PADS]; +#endif +}; + +static inline struct cx25840_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct cx25840_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct cx25840_state, hdl)->sd; +} + +static inline bool is_cx2583x(struct cx25840_state *state) +{ + return state->id == CX25836 || + state->id == CX25837; +} + +static inline bool is_cx2584x(struct cx25840_state *state) +{ + return state->id == CX25840 || + state->id == CX25841 || + state->id == CX25842 || + state->id == CX25843; +} + +static inline bool is_cx231xx(struct cx25840_state *state) +{ + return state->id == CX2310X_AV; +} + +static inline bool is_cx2388x(struct cx25840_state *state) +{ + return state->id == CX23885_AV || + state->id == CX23887_AV || + state->id == CX23888_AV; +} + +static inline bool is_cx23885(struct cx25840_state *state) +{ + return state->id == CX23885_AV; +} + +static inline bool is_cx23887(struct cx25840_state *state) +{ + return state->id == CX23887_AV; +} + +static inline bool is_cx23888(struct cx25840_state *state) +{ + return state->id == CX23888_AV; +} + +/* ----------------------------------------------------------------------- */ +/* cx25850-core.c */ +int cx25840_write(struct i2c_client *client, u16 addr, u8 value); +int cx25840_write4(struct i2c_client *client, u16 addr, u32 value); +u8 cx25840_read(struct i2c_client *client, u16 addr); +u32 cx25840_read4(struct i2c_client *client, u16 addr); +int cx25840_and_or(struct i2c_client *client, u16 addr, unsigned int mask, + u8 value); +int cx25840_and_or4(struct i2c_client *client, u16 addr, u32 and_mask, + u32 or_value); +void cx25840_std_setup(struct i2c_client *client); + +/* ----------------------------------------------------------------------- */ +/* cx25850-firmware.c */ +int cx25840_loadfw(struct i2c_client *client); + +/* ----------------------------------------------------------------------- */ +/* cx25850-audio.c */ +void cx25840_audio_set_path(struct i2c_client *client); +int cx25840_s_clock_freq(struct v4l2_subdev *sd, u32 freq); + +extern const struct v4l2_ctrl_ops cx25840_audio_ctrl_ops; + +/* ----------------------------------------------------------------------- */ +/* cx25850-vbi.c */ +int cx25840_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt); +int cx25840_s_sliced_fmt(struct v4l2_subdev *sd, + struct v4l2_sliced_vbi_format *fmt); +int cx25840_g_sliced_fmt(struct v4l2_subdev *sd, + struct v4l2_sliced_vbi_format *fmt); +int cx25840_decode_vbi_line(struct v4l2_subdev *sd, + struct v4l2_decode_vbi_line *vbi); + +/* ----------------------------------------------------------------------- */ +/* cx25850-ir.c */ +extern const struct v4l2_subdev_ir_ops cx25840_ir_ops; +int cx25840_ir_log_status(struct v4l2_subdev *sd); +int cx25840_ir_irq_handler(struct v4l2_subdev *sd, u32 status, bool *handled); +int cx25840_ir_probe(struct v4l2_subdev *sd); +int cx25840_ir_remove(struct v4l2_subdev *sd); + +#endif diff --git a/drivers/media/i2c/cx25840/cx25840-firmware.c b/drivers/media/i2c/cx25840/cx25840-firmware.c new file mode 100644 index 000000000..02df45ccf --- /dev/null +++ b/drivers/media/i2c/cx25840/cx25840-firmware.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* cx25840 firmware functions + */ + +#include +#include +#include +#include +#include + +#include "cx25840-core.h" + +/* + * Mike Isely - The FWSEND parameter controls the + * size of the firmware chunks sent down the I2C bus to the chip. + * Previously this had been set to 1024 but unfortunately some I2C + * implementations can't transfer data in such big gulps. + * Specifically, the pvrusb2 driver has a hard limit of around 60 + * bytes, due to the encapsulation there of I2C traffic into USB + * messages. So we have to significantly reduce this parameter. + */ +#define FWSEND 48 + +#define FWDEV(x) &((x)->dev) + +static char *firmware = ""; + +module_param(firmware, charp, 0444); + +MODULE_PARM_DESC(firmware, "Firmware image to load"); + +static void start_fw_load(struct i2c_client *client) +{ + /* DL_ADDR_LB=0 DL_ADDR_HB=0 */ + cx25840_write(client, 0x800, 0x00); + cx25840_write(client, 0x801, 0x00); + // DL_MAP=3 DL_AUTO_INC=0 DL_ENABLE=1 + cx25840_write(client, 0x803, 0x0b); + /* AUTO_INC_DIS=1 */ + cx25840_write(client, 0x000, 0x20); +} + +static void end_fw_load(struct i2c_client *client) +{ + /* AUTO_INC_DIS=0 */ + cx25840_write(client, 0x000, 0x00); + /* DL_ENABLE=0 */ + cx25840_write(client, 0x803, 0x03); +} + +#define CX2388x_FIRMWARE "v4l-cx23885-avcore-01.fw" +#define CX231xx_FIRMWARE "v4l-cx231xx-avcore-01.fw" +#define CX25840_FIRMWARE "v4l-cx25840.fw" + +static const char *get_fw_name(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + + if (firmware[0]) + return firmware; + if (is_cx2388x(state)) + return CX2388x_FIRMWARE; + if (is_cx231xx(state)) + return CX231xx_FIRMWARE; + return CX25840_FIRMWARE; +} + +static int check_fw_load(struct i2c_client *client, int size) +{ + /* DL_ADDR_HB DL_ADDR_LB */ + int s = cx25840_read(client, 0x801) << 8; + s |= cx25840_read(client, 0x800); + + if (size != s) { + v4l_err(client, "firmware %s load failed\n", + get_fw_name(client)); + return -EINVAL; + } + + v4l_info(client, "loaded %s firmware (%d bytes)\n", + get_fw_name(client), size); + return 0; +} + +static int fw_write(struct i2c_client *client, const u8 *data, int size) +{ + if (i2c_master_send(client, data, size) < size) { + v4l_err(client, "firmware load i2c failure\n"); + return -ENOSYS; + } + + return 0; +} + +int cx25840_loadfw(struct i2c_client *client) +{ + struct cx25840_state *state = to_state(i2c_get_clientdata(client)); + const struct firmware *fw = NULL; + u8 buffer[FWSEND]; + const u8 *ptr; + const char *fwname = get_fw_name(client); + int size, retval; + int max_buf_size = FWSEND; + u32 gpio_oe = 0, gpio_da = 0; + + if (is_cx2388x(state)) { + /* Preserve the GPIO OE and output bits */ + gpio_oe = cx25840_read(client, 0x160); + gpio_da = cx25840_read(client, 0x164); + } + + /* cx231xx cannot accept more than 16 bytes at a time */ + if (is_cx231xx(state) && max_buf_size > 16) + max_buf_size = 16; + + if (request_firmware(&fw, fwname, FWDEV(client)) != 0) { + v4l_err(client, "unable to open firmware %s\n", fwname); + return -EINVAL; + } + + start_fw_load(client); + + buffer[0] = 0x08; + buffer[1] = 0x02; + + size = fw->size; + ptr = fw->data; + while (size > 0) { + int len = min(max_buf_size - 2, size); + + memcpy(buffer + 2, ptr, len); + + retval = fw_write(client, buffer, len + 2); + + if (retval < 0) { + release_firmware(fw); + return retval; + } + + size -= len; + ptr += len; + } + + end_fw_load(client); + + size = fw->size; + release_firmware(fw); + + if (is_cx2388x(state)) { + /* Restore GPIO configuration after f/w load */ + cx25840_write(client, 0x160, gpio_oe); + cx25840_write(client, 0x164, gpio_da); + } + + return check_fw_load(client, size); +} + +MODULE_FIRMWARE(CX2388x_FIRMWARE); +MODULE_FIRMWARE(CX231xx_FIRMWARE); +MODULE_FIRMWARE(CX25840_FIRMWARE); + diff --git a/drivers/media/i2c/cx25840/cx25840-ir.c b/drivers/media/i2c/cx25840/cx25840-ir.c new file mode 100644 index 000000000..8cef9656c --- /dev/null +++ b/drivers/media/i2c/cx25840/cx25840-ir.c @@ -0,0 +1,1239 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Conexant CX2584x Audio/Video decoder chip and related cores + * + * Integrated Consumer Infrared Controller + * + * Copyright (C) 2010 Andy Walls + */ + +#include +#include +#include +#include +#include + +#include "cx25840-core.h" + +static unsigned int ir_debug; +module_param(ir_debug, int, 0644); +MODULE_PARM_DESC(ir_debug, "enable integrated IR debug messages"); + +#define CX25840_IR_REG_BASE 0x200 + +#define CX25840_IR_CNTRL_REG 0x200 +#define CNTRL_WIN_3_3 0x00000000 +#define CNTRL_WIN_4_3 0x00000001 +#define CNTRL_WIN_3_4 0x00000002 +#define CNTRL_WIN_4_4 0x00000003 +#define CNTRL_WIN 0x00000003 +#define CNTRL_EDG_NONE 0x00000000 +#define CNTRL_EDG_FALL 0x00000004 +#define CNTRL_EDG_RISE 0x00000008 +#define CNTRL_EDG_BOTH 0x0000000C +#define CNTRL_EDG 0x0000000C +#define CNTRL_DMD 0x00000010 +#define CNTRL_MOD 0x00000020 +#define CNTRL_RFE 0x00000040 +#define CNTRL_TFE 0x00000080 +#define CNTRL_RXE 0x00000100 +#define CNTRL_TXE 0x00000200 +#define CNTRL_RIC 0x00000400 +#define CNTRL_TIC 0x00000800 +#define CNTRL_CPL 0x00001000 +#define CNTRL_LBM 0x00002000 +#define CNTRL_R 0x00004000 + +#define CX25840_IR_TXCLK_REG 0x204 +#define TXCLK_TCD 0x0000FFFF + +#define CX25840_IR_RXCLK_REG 0x208 +#define RXCLK_RCD 0x0000FFFF + +#define CX25840_IR_CDUTY_REG 0x20C +#define CDUTY_CDC 0x0000000F + +#define CX25840_IR_STATS_REG 0x210 +#define STATS_RTO 0x00000001 +#define STATS_ROR 0x00000002 +#define STATS_RBY 0x00000004 +#define STATS_TBY 0x00000008 +#define STATS_RSR 0x00000010 +#define STATS_TSR 0x00000020 + +#define CX25840_IR_IRQEN_REG 0x214 +#define IRQEN_RTE 0x00000001 +#define IRQEN_ROE 0x00000002 +#define IRQEN_RSE 0x00000010 +#define IRQEN_TSE 0x00000020 +#define IRQEN_MSK 0x00000033 + +#define CX25840_IR_FILTR_REG 0x218 +#define FILTR_LPF 0x0000FFFF + +#define CX25840_IR_FIFO_REG 0x23C +#define FIFO_RXTX 0x0000FFFF +#define FIFO_RXTX_LVL 0x00010000 +#define FIFO_RXTX_RTO 0x0001FFFF +#define FIFO_RX_NDV 0x00020000 +#define FIFO_RX_DEPTH 8 +#define FIFO_TX_DEPTH 8 + +#define CX25840_VIDCLK_FREQ 108000000 /* 108 MHz, BT.656 */ +#define CX25840_IR_REFCLK_FREQ (CX25840_VIDCLK_FREQ / 2) + +/* + * We use this union internally for convenience, but callers to tx_write + * and rx_read will be expecting records of type struct ir_raw_event. + * Always ensure the size of this union is dictated by struct ir_raw_event. + */ +union cx25840_ir_fifo_rec { + u32 hw_fifo_data; + struct ir_raw_event ir_core_data; +}; + +#define CX25840_IR_RX_KFIFO_SIZE (256 * sizeof(union cx25840_ir_fifo_rec)) +#define CX25840_IR_TX_KFIFO_SIZE (256 * sizeof(union cx25840_ir_fifo_rec)) + +struct cx25840_ir_state { + struct i2c_client *c; + + struct v4l2_subdev_ir_parameters rx_params; + struct mutex rx_params_lock; /* protects Rx parameter settings cache */ + atomic_t rxclk_divider; + atomic_t rx_invert; + + struct kfifo rx_kfifo; + spinlock_t rx_kfifo_lock; /* protect Rx data kfifo */ + + struct v4l2_subdev_ir_parameters tx_params; + struct mutex tx_params_lock; /* protects Tx parameter settings cache */ + atomic_t txclk_divider; +}; + +static inline struct cx25840_ir_state *to_ir_state(struct v4l2_subdev *sd) +{ + struct cx25840_state *state = to_state(sd); + return state ? state->ir_state : NULL; +} + + +/* + * Rx and Tx Clock Divider register computations + * + * Note the largest clock divider value of 0xffff corresponds to: + * (0xffff + 1) * 1000 / 108/2 MHz = 1,213,629.629... ns + * which fits in 21 bits, so we'll use unsigned int for time arguments. + */ +static inline u16 count_to_clock_divider(unsigned int d) +{ + if (d > RXCLK_RCD + 1) + d = RXCLK_RCD; + else if (d < 2) + d = 1; + else + d--; + return (u16) d; +} + +static inline u16 carrier_freq_to_clock_divider(unsigned int freq) +{ + return count_to_clock_divider( + DIV_ROUND_CLOSEST(CX25840_IR_REFCLK_FREQ, freq * 16)); +} + +static inline unsigned int clock_divider_to_carrier_freq(unsigned int divider) +{ + return DIV_ROUND_CLOSEST(CX25840_IR_REFCLK_FREQ, (divider + 1) * 16); +} + +static inline unsigned int clock_divider_to_freq(unsigned int divider, + unsigned int rollovers) +{ + return DIV_ROUND_CLOSEST(CX25840_IR_REFCLK_FREQ, + (divider + 1) * rollovers); +} + +/* + * Low Pass Filter register calculations + * + * Note the largest count value of 0xffff corresponds to: + * 0xffff * 1000 / 108/2 MHz = 1,213,611.11... ns + * which fits in 21 bits, so we'll use unsigned int for time arguments. + */ +static inline u16 count_to_lpf_count(unsigned int d) +{ + if (d > FILTR_LPF) + d = FILTR_LPF; + else if (d < 4) + d = 0; + return (u16) d; +} + +static inline u16 ns_to_lpf_count(unsigned int ns) +{ + return count_to_lpf_count( + DIV_ROUND_CLOSEST(CX25840_IR_REFCLK_FREQ / 1000000 * ns, 1000)); +} + +static inline unsigned int lpf_count_to_ns(unsigned int count) +{ + /* Duration of the Low Pass Filter rejection window in ns */ + return DIV_ROUND_CLOSEST(count * 1000, + CX25840_IR_REFCLK_FREQ / 1000000); +} + +static inline unsigned int lpf_count_to_us(unsigned int count) +{ + /* Duration of the Low Pass Filter rejection window in us */ + return DIV_ROUND_CLOSEST(count, CX25840_IR_REFCLK_FREQ / 1000000); +} + +/* + * FIFO register pulse width count computations + */ +static u32 clock_divider_to_resolution(u16 divider) +{ + /* + * Resolution is the duration of 1 tick of the readable portion of + * the pulse width counter as read from the FIFO. The two lsb's are + * not readable, hence the << 2. This function returns ns. + */ + return DIV_ROUND_CLOSEST((1 << 2) * ((u32) divider + 1) * 1000, + CX25840_IR_REFCLK_FREQ / 1000000); +} + +static u64 pulse_width_count_to_ns(u16 count, u16 divider) +{ + u64 n; + u32 rem; + + /* + * The 2 lsb's of the pulse width timer count are not readable, hence + * the (count << 2) | 0x3 + */ + n = (((u64) count << 2) | 0x3) * (divider + 1) * 1000; /* millicycles */ + rem = do_div(n, CX25840_IR_REFCLK_FREQ / 1000000); /* / MHz => ns */ + if (rem >= CX25840_IR_REFCLK_FREQ / 1000000 / 2) + n++; + return n; +} + +#if 0 +/* Keep as we will need this for Transmit functionality */ +static u16 ns_to_pulse_width_count(u32 ns, u16 divider) +{ + u64 n; + u32 d; + u32 rem; + + /* + * The 2 lsb's of the pulse width timer count are not accessible, hence + * the (1 << 2) + */ + n = ((u64) ns) * CX25840_IR_REFCLK_FREQ / 1000000; /* millicycles */ + d = (1 << 2) * ((u32) divider + 1) * 1000; /* millicycles/count */ + rem = do_div(n, d); + if (rem >= d / 2) + n++; + + if (n > FIFO_RXTX) + n = FIFO_RXTX; + else if (n == 0) + n = 1; + return (u16) n; +} + +#endif +static unsigned int pulse_width_count_to_us(u16 count, u16 divider) +{ + u64 n; + u32 rem; + + /* + * The 2 lsb's of the pulse width timer count are not readable, hence + * the (count << 2) | 0x3 + */ + n = (((u64) count << 2) | 0x3) * (divider + 1); /* cycles */ + rem = do_div(n, CX25840_IR_REFCLK_FREQ / 1000000); /* / MHz => us */ + if (rem >= CX25840_IR_REFCLK_FREQ / 1000000 / 2) + n++; + return (unsigned int) n; +} + +/* + * Pulse Clocks computations: Combined Pulse Width Count & Rx Clock Counts + * + * The total pulse clock count is an 18 bit pulse width timer count as the most + * significant part and (up to) 16 bit clock divider count as a modulus. + * When the Rx clock divider ticks down to 0, it increments the 18 bit pulse + * width timer count's least significant bit. + */ +static u64 ns_to_pulse_clocks(u32 ns) +{ + u64 clocks; + u32 rem; + clocks = CX25840_IR_REFCLK_FREQ / 1000000 * (u64) ns; /* millicycles */ + rem = do_div(clocks, 1000); /* /1000 = cycles */ + if (rem >= 1000 / 2) + clocks++; + return clocks; +} + +static u16 pulse_clocks_to_clock_divider(u64 count) +{ + do_div(count, (FIFO_RXTX << 2) | 0x3); + + /* net result needs to be rounded down and decremented by 1 */ + if (count > RXCLK_RCD + 1) + count = RXCLK_RCD; + else if (count < 2) + count = 1; + else + count--; + return (u16) count; +} + +/* + * IR Control Register helpers + */ +enum tx_fifo_watermark { + TX_FIFO_HALF_EMPTY = 0, + TX_FIFO_EMPTY = CNTRL_TIC, +}; + +enum rx_fifo_watermark { + RX_FIFO_HALF_FULL = 0, + RX_FIFO_NOT_EMPTY = CNTRL_RIC, +}; + +static inline void control_tx_irq_watermark(struct i2c_client *c, + enum tx_fifo_watermark level) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~CNTRL_TIC, level); +} + +static inline void control_rx_irq_watermark(struct i2c_client *c, + enum rx_fifo_watermark level) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~CNTRL_RIC, level); +} + +static inline void control_tx_enable(struct i2c_client *c, bool enable) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~(CNTRL_TXE | CNTRL_TFE), + enable ? (CNTRL_TXE | CNTRL_TFE) : 0); +} + +static inline void control_rx_enable(struct i2c_client *c, bool enable) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~(CNTRL_RXE | CNTRL_RFE), + enable ? (CNTRL_RXE | CNTRL_RFE) : 0); +} + +static inline void control_tx_modulation_enable(struct i2c_client *c, + bool enable) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~CNTRL_MOD, + enable ? CNTRL_MOD : 0); +} + +static inline void control_rx_demodulation_enable(struct i2c_client *c, + bool enable) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~CNTRL_DMD, + enable ? CNTRL_DMD : 0); +} + +static inline void control_rx_s_edge_detection(struct i2c_client *c, + u32 edge_types) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~CNTRL_EDG_BOTH, + edge_types & CNTRL_EDG_BOTH); +} + +static void control_rx_s_carrier_window(struct i2c_client *c, + unsigned int carrier, + unsigned int *carrier_range_low, + unsigned int *carrier_range_high) +{ + u32 v; + unsigned int c16 = carrier * 16; + + if (*carrier_range_low < DIV_ROUND_CLOSEST(c16, 16 + 3)) { + v = CNTRL_WIN_3_4; + *carrier_range_low = DIV_ROUND_CLOSEST(c16, 16 + 4); + } else { + v = CNTRL_WIN_3_3; + *carrier_range_low = DIV_ROUND_CLOSEST(c16, 16 + 3); + } + + if (*carrier_range_high > DIV_ROUND_CLOSEST(c16, 16 - 3)) { + v |= CNTRL_WIN_4_3; + *carrier_range_high = DIV_ROUND_CLOSEST(c16, 16 - 4); + } else { + v |= CNTRL_WIN_3_3; + *carrier_range_high = DIV_ROUND_CLOSEST(c16, 16 - 3); + } + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~CNTRL_WIN, v); +} + +static inline void control_tx_polarity_invert(struct i2c_client *c, + bool invert) +{ + cx25840_and_or4(c, CX25840_IR_CNTRL_REG, ~CNTRL_CPL, + invert ? CNTRL_CPL : 0); +} + +/* + * IR Rx & Tx Clock Register helpers + */ +static unsigned int txclk_tx_s_carrier(struct i2c_client *c, + unsigned int freq, + u16 *divider) +{ + *divider = carrier_freq_to_clock_divider(freq); + cx25840_write4(c, CX25840_IR_TXCLK_REG, *divider); + return clock_divider_to_carrier_freq(*divider); +} + +static unsigned int rxclk_rx_s_carrier(struct i2c_client *c, + unsigned int freq, + u16 *divider) +{ + *divider = carrier_freq_to_clock_divider(freq); + cx25840_write4(c, CX25840_IR_RXCLK_REG, *divider); + return clock_divider_to_carrier_freq(*divider); +} + +static u32 txclk_tx_s_max_pulse_width(struct i2c_client *c, u32 ns, + u16 *divider) +{ + u64 pulse_clocks; + + if (ns > IR_MAX_DURATION) + ns = IR_MAX_DURATION; + pulse_clocks = ns_to_pulse_clocks(ns); + *divider = pulse_clocks_to_clock_divider(pulse_clocks); + cx25840_write4(c, CX25840_IR_TXCLK_REG, *divider); + return (u32) pulse_width_count_to_ns(FIFO_RXTX, *divider); +} + +static u32 rxclk_rx_s_max_pulse_width(struct i2c_client *c, u32 ns, + u16 *divider) +{ + u64 pulse_clocks; + + if (ns > IR_MAX_DURATION) + ns = IR_MAX_DURATION; + pulse_clocks = ns_to_pulse_clocks(ns); + *divider = pulse_clocks_to_clock_divider(pulse_clocks); + cx25840_write4(c, CX25840_IR_RXCLK_REG, *divider); + return (u32) pulse_width_count_to_ns(FIFO_RXTX, *divider); +} + +/* + * IR Tx Carrier Duty Cycle register helpers + */ +static unsigned int cduty_tx_s_duty_cycle(struct i2c_client *c, + unsigned int duty_cycle) +{ + u32 n; + n = DIV_ROUND_CLOSEST(duty_cycle * 100, 625); /* 16ths of 100% */ + if (n != 0) + n--; + if (n > 15) + n = 15; + cx25840_write4(c, CX25840_IR_CDUTY_REG, n); + return DIV_ROUND_CLOSEST((n + 1) * 100, 16); +} + +/* + * IR Filter Register helpers + */ +static u32 filter_rx_s_min_width(struct i2c_client *c, u32 min_width_ns) +{ + u32 count = ns_to_lpf_count(min_width_ns); + cx25840_write4(c, CX25840_IR_FILTR_REG, count); + return lpf_count_to_ns(count); +} + +/* + * IR IRQ Enable Register helpers + */ +static inline void irqenable_rx(struct v4l2_subdev *sd, u32 mask) +{ + struct cx25840_state *state = to_state(sd); + + if (is_cx23885(state) || is_cx23887(state)) + mask ^= IRQEN_MSK; + mask &= (IRQEN_RTE | IRQEN_ROE | IRQEN_RSE); + cx25840_and_or4(state->c, CX25840_IR_IRQEN_REG, + ~(IRQEN_RTE | IRQEN_ROE | IRQEN_RSE), mask); +} + +static inline void irqenable_tx(struct v4l2_subdev *sd, u32 mask) +{ + struct cx25840_state *state = to_state(sd); + + if (is_cx23885(state) || is_cx23887(state)) + mask ^= IRQEN_MSK; + mask &= IRQEN_TSE; + cx25840_and_or4(state->c, CX25840_IR_IRQEN_REG, ~IRQEN_TSE, mask); +} + +/* + * V4L2 Subdevice IR Ops + */ +int cx25840_ir_irq_handler(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + struct cx25840_state *state = to_state(sd); + struct cx25840_ir_state *ir_state = to_ir_state(sd); + struct i2c_client *c = NULL; + unsigned long flags; + + union cx25840_ir_fifo_rec rx_data[FIFO_RX_DEPTH]; + unsigned int i, j, k; + u32 events, v; + int tsr, rsr, rto, ror, tse, rse, rte, roe, kror; + u32 cntrl, irqen, stats; + + *handled = false; + if (ir_state == NULL) + return -ENODEV; + + c = ir_state->c; + + /* Only support the IR controller for the CX2388[57] AV Core for now */ + if (!(is_cx23885(state) || is_cx23887(state))) + return -ENODEV; + + cntrl = cx25840_read4(c, CX25840_IR_CNTRL_REG); + irqen = cx25840_read4(c, CX25840_IR_IRQEN_REG); + if (is_cx23885(state) || is_cx23887(state)) + irqen ^= IRQEN_MSK; + stats = cx25840_read4(c, CX25840_IR_STATS_REG); + + tsr = stats & STATS_TSR; /* Tx FIFO Service Request */ + rsr = stats & STATS_RSR; /* Rx FIFO Service Request */ + rto = stats & STATS_RTO; /* Rx Pulse Width Timer Time Out */ + ror = stats & STATS_ROR; /* Rx FIFO Over Run */ + + tse = irqen & IRQEN_TSE; /* Tx FIFO Service Request IRQ Enable */ + rse = irqen & IRQEN_RSE; /* Rx FIFO Service Request IRQ Enable */ + rte = irqen & IRQEN_RTE; /* Rx Pulse Width Timer Time Out IRQ Enable */ + roe = irqen & IRQEN_ROE; /* Rx FIFO Over Run IRQ Enable */ + + v4l2_dbg(2, ir_debug, sd, "IR IRQ Status: %s %s %s %s %s %s\n", + tsr ? "tsr" : " ", rsr ? "rsr" : " ", + rto ? "rto" : " ", ror ? "ror" : " ", + stats & STATS_TBY ? "tby" : " ", + stats & STATS_RBY ? "rby" : " "); + + v4l2_dbg(2, ir_debug, sd, "IR IRQ Enables: %s %s %s %s\n", + tse ? "tse" : " ", rse ? "rse" : " ", + rte ? "rte" : " ", roe ? "roe" : " "); + + /* + * Transmitter interrupt service + */ + if (tse && tsr) { + /* + * TODO: + * Check the watermark threshold setting + * Pull FIFO_TX_DEPTH or FIFO_TX_DEPTH/2 entries from tx_kfifo + * Push the data to the hardware FIFO. + * If there was nothing more to send in the tx_kfifo, disable + * the TSR IRQ and notify the v4l2_device. + * If there was something in the tx_kfifo, check the tx_kfifo + * level and notify the v4l2_device, if it is low. + */ + /* For now, inhibit TSR interrupt until Tx is implemented */ + irqenable_tx(sd, 0); + events = V4L2_SUBDEV_IR_TX_FIFO_SERVICE_REQ; + v4l2_subdev_notify(sd, V4L2_SUBDEV_IR_TX_NOTIFY, &events); + *handled = true; + } + + /* + * Receiver interrupt service + */ + kror = 0; + if ((rse && rsr) || (rte && rto)) { + /* + * Receive data on RSR to clear the STATS_RSR. + * Receive data on RTO, since we may not have yet hit the RSR + * watermark when we receive the RTO. + */ + for (i = 0, v = FIFO_RX_NDV; + (v & FIFO_RX_NDV) && !kror; i = 0) { + for (j = 0; + (v & FIFO_RX_NDV) && j < FIFO_RX_DEPTH; j++) { + v = cx25840_read4(c, CX25840_IR_FIFO_REG); + rx_data[i].hw_fifo_data = v & ~FIFO_RX_NDV; + i++; + } + if (i == 0) + break; + j = i * sizeof(union cx25840_ir_fifo_rec); + k = kfifo_in_locked(&ir_state->rx_kfifo, + (unsigned char *) rx_data, j, + &ir_state->rx_kfifo_lock); + if (k != j) + kror++; /* rx_kfifo over run */ + } + *handled = true; + } + + events = 0; + v = 0; + if (kror) { + events |= V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN; + v4l2_err(sd, "IR receiver software FIFO overrun\n"); + } + if (roe && ror) { + /* + * The RX FIFO Enable (CNTRL_RFE) must be toggled to clear + * the Rx FIFO Over Run status (STATS_ROR) + */ + v |= CNTRL_RFE; + events |= V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN; + v4l2_err(sd, "IR receiver hardware FIFO overrun\n"); + } + if (rte && rto) { + /* + * The IR Receiver Enable (CNTRL_RXE) must be toggled to clear + * the Rx Pulse Width Timer Time Out (STATS_RTO) + */ + v |= CNTRL_RXE; + events |= V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED; + } + if (v) { + /* Clear STATS_ROR & STATS_RTO as needed by resetting hardware */ + cx25840_write4(c, CX25840_IR_CNTRL_REG, cntrl & ~v); + cx25840_write4(c, CX25840_IR_CNTRL_REG, cntrl); + *handled = true; + } + spin_lock_irqsave(&ir_state->rx_kfifo_lock, flags); + if (kfifo_len(&ir_state->rx_kfifo) >= CX25840_IR_RX_KFIFO_SIZE / 2) + events |= V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ; + spin_unlock_irqrestore(&ir_state->rx_kfifo_lock, flags); + + if (events) + v4l2_subdev_notify(sd, V4L2_SUBDEV_IR_RX_NOTIFY, &events); + return 0; +} + +/* Receiver */ +static int cx25840_ir_rx_read(struct v4l2_subdev *sd, u8 *buf, size_t count, + ssize_t *num) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + bool invert; + u16 divider; + unsigned int i, n; + union cx25840_ir_fifo_rec *p; + unsigned u, v, w; + + if (ir_state == NULL) + return -ENODEV; + + invert = (bool) atomic_read(&ir_state->rx_invert); + divider = (u16) atomic_read(&ir_state->rxclk_divider); + + n = count / sizeof(union cx25840_ir_fifo_rec) + * sizeof(union cx25840_ir_fifo_rec); + if (n == 0) { + *num = 0; + return 0; + } + + n = kfifo_out_locked(&ir_state->rx_kfifo, buf, n, + &ir_state->rx_kfifo_lock); + + n /= sizeof(union cx25840_ir_fifo_rec); + *num = n * sizeof(union cx25840_ir_fifo_rec); + + for (p = (union cx25840_ir_fifo_rec *) buf, i = 0; i < n; p++, i++) { + + if ((p->hw_fifo_data & FIFO_RXTX_RTO) == FIFO_RXTX_RTO) { + /* Assume RTO was because of no IR light input */ + u = 0; + w = 1; + } else { + u = (p->hw_fifo_data & FIFO_RXTX_LVL) ? 1 : 0; + if (invert) + u = u ? 0 : 1; + w = 0; + } + + v = (unsigned) pulse_width_count_to_ns( + (u16)(p->hw_fifo_data & FIFO_RXTX), divider) / 1000; + if (v > IR_MAX_DURATION) + v = IR_MAX_DURATION; + + p->ir_core_data = (struct ir_raw_event) + { .pulse = u, .duration = v, .timeout = w }; + + v4l2_dbg(2, ir_debug, sd, "rx read: %10u ns %s %s\n", + v, u ? "mark" : "space", w ? "(timed out)" : ""); + if (w) + v4l2_dbg(2, ir_debug, sd, "rx read: end of rx\n"); + } + return 0; +} + +static int cx25840_ir_rx_g_parameters(struct v4l2_subdev *sd, + struct v4l2_subdev_ir_parameters *p) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + + if (ir_state == NULL) + return -ENODEV; + + mutex_lock(&ir_state->rx_params_lock); + memcpy(p, &ir_state->rx_params, + sizeof(struct v4l2_subdev_ir_parameters)); + mutex_unlock(&ir_state->rx_params_lock); + return 0; +} + +static int cx25840_ir_rx_shutdown(struct v4l2_subdev *sd) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + struct i2c_client *c; + + if (ir_state == NULL) + return -ENODEV; + + c = ir_state->c; + mutex_lock(&ir_state->rx_params_lock); + + /* Disable or slow down all IR Rx circuits and counters */ + irqenable_rx(sd, 0); + control_rx_enable(c, false); + control_rx_demodulation_enable(c, false); + control_rx_s_edge_detection(c, CNTRL_EDG_NONE); + filter_rx_s_min_width(c, 0); + cx25840_write4(c, CX25840_IR_RXCLK_REG, RXCLK_RCD); + + ir_state->rx_params.shutdown = true; + + mutex_unlock(&ir_state->rx_params_lock); + return 0; +} + +static int cx25840_ir_rx_s_parameters(struct v4l2_subdev *sd, + struct v4l2_subdev_ir_parameters *p) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + struct i2c_client *c; + struct v4l2_subdev_ir_parameters *o; + u16 rxclk_divider; + + if (ir_state == NULL) + return -ENODEV; + + if (p->shutdown) + return cx25840_ir_rx_shutdown(sd); + + if (p->mode != V4L2_SUBDEV_IR_MODE_PULSE_WIDTH) + return -ENOSYS; + + c = ir_state->c; + o = &ir_state->rx_params; + + mutex_lock(&ir_state->rx_params_lock); + + o->shutdown = p->shutdown; + + p->mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH; + o->mode = p->mode; + + p->bytes_per_data_element = sizeof(union cx25840_ir_fifo_rec); + o->bytes_per_data_element = p->bytes_per_data_element; + + /* Before we tweak the hardware, we have to disable the receiver */ + irqenable_rx(sd, 0); + control_rx_enable(c, false); + + control_rx_demodulation_enable(c, p->modulation); + o->modulation = p->modulation; + + if (p->modulation) { + p->carrier_freq = rxclk_rx_s_carrier(c, p->carrier_freq, + &rxclk_divider); + + o->carrier_freq = p->carrier_freq; + + p->duty_cycle = 50; + o->duty_cycle = p->duty_cycle; + + control_rx_s_carrier_window(c, p->carrier_freq, + &p->carrier_range_lower, + &p->carrier_range_upper); + o->carrier_range_lower = p->carrier_range_lower; + o->carrier_range_upper = p->carrier_range_upper; + + p->max_pulse_width = + (u32) pulse_width_count_to_ns(FIFO_RXTX, rxclk_divider); + } else { + p->max_pulse_width = + rxclk_rx_s_max_pulse_width(c, p->max_pulse_width, + &rxclk_divider); + } + o->max_pulse_width = p->max_pulse_width; + atomic_set(&ir_state->rxclk_divider, rxclk_divider); + + p->noise_filter_min_width = + filter_rx_s_min_width(c, p->noise_filter_min_width); + o->noise_filter_min_width = p->noise_filter_min_width; + + p->resolution = clock_divider_to_resolution(rxclk_divider); + o->resolution = p->resolution; + + /* FIXME - make this dependent on resolution for better performance */ + control_rx_irq_watermark(c, RX_FIFO_HALF_FULL); + + control_rx_s_edge_detection(c, CNTRL_EDG_BOTH); + + o->invert_level = p->invert_level; + atomic_set(&ir_state->rx_invert, p->invert_level); + + o->interrupt_enable = p->interrupt_enable; + o->enable = p->enable; + if (p->enable) { + unsigned long flags; + + spin_lock_irqsave(&ir_state->rx_kfifo_lock, flags); + kfifo_reset(&ir_state->rx_kfifo); + spin_unlock_irqrestore(&ir_state->rx_kfifo_lock, flags); + if (p->interrupt_enable) + irqenable_rx(sd, IRQEN_RSE | IRQEN_RTE | IRQEN_ROE); + control_rx_enable(c, p->enable); + } + + mutex_unlock(&ir_state->rx_params_lock); + return 0; +} + +/* Transmitter */ +static int cx25840_ir_tx_write(struct v4l2_subdev *sd, u8 *buf, size_t count, + ssize_t *num) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + + if (ir_state == NULL) + return -ENODEV; + +#if 0 + /* + * FIXME - the code below is an incomplete and untested sketch of what + * may need to be done. The critical part is to get 4 (or 8) pulses + * from the tx_kfifo, or converted from ns to the proper units from the + * input, and push them off to the hardware Tx FIFO right away, if the + * HW TX fifo needs service. The rest can be pushed to the tx_kfifo in + * a less critical timeframe. Also watch out for overruning the + * tx_kfifo - don't let it happen and let the caller know not all his + * pulses were written. + */ + u32 *ns_pulse = (u32 *) buf; + unsigned int n; + u32 fifo_pulse[FIFO_TX_DEPTH]; + u32 mark; + + /* Compute how much we can fit in the tx kfifo */ + n = CX25840_IR_TX_KFIFO_SIZE - kfifo_len(ir_state->tx_kfifo); + n = min(n, (unsigned int) count); + n /= sizeof(u32); + + /* FIXME - turn on Tx Fifo service interrupt + * check hardware fifo level, and other stuff + */ + for (i = 0; i < n; ) { + for (j = 0; j < FIFO_TX_DEPTH / 2 && i < n; j++) { + mark = ns_pulse[i] & LEVEL_MASK; + fifo_pulse[j] = ns_to_pulse_width_count( + ns_pulse[i] & + ~LEVEL_MASK, + ir_state->txclk_divider); + if (mark) + fifo_pulse[j] &= FIFO_RXTX_LVL; + i++; + } + kfifo_put(ir_state->tx_kfifo, (u8 *) fifo_pulse, + j * sizeof(u32)); + } + *num = n * sizeof(u32); +#else + /* For now enable the Tx FIFO Service interrupt & pretend we did work */ + irqenable_tx(sd, IRQEN_TSE); + *num = count; +#endif + return 0; +} + +static int cx25840_ir_tx_g_parameters(struct v4l2_subdev *sd, + struct v4l2_subdev_ir_parameters *p) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + + if (ir_state == NULL) + return -ENODEV; + + mutex_lock(&ir_state->tx_params_lock); + memcpy(p, &ir_state->tx_params, + sizeof(struct v4l2_subdev_ir_parameters)); + mutex_unlock(&ir_state->tx_params_lock); + return 0; +} + +static int cx25840_ir_tx_shutdown(struct v4l2_subdev *sd) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + struct i2c_client *c; + + if (ir_state == NULL) + return -ENODEV; + + c = ir_state->c; + mutex_lock(&ir_state->tx_params_lock); + + /* Disable or slow down all IR Tx circuits and counters */ + irqenable_tx(sd, 0); + control_tx_enable(c, false); + control_tx_modulation_enable(c, false); + cx25840_write4(c, CX25840_IR_TXCLK_REG, TXCLK_TCD); + + ir_state->tx_params.shutdown = true; + + mutex_unlock(&ir_state->tx_params_lock); + return 0; +} + +static int cx25840_ir_tx_s_parameters(struct v4l2_subdev *sd, + struct v4l2_subdev_ir_parameters *p) +{ + struct cx25840_ir_state *ir_state = to_ir_state(sd); + struct i2c_client *c; + struct v4l2_subdev_ir_parameters *o; + u16 txclk_divider; + + if (ir_state == NULL) + return -ENODEV; + + if (p->shutdown) + return cx25840_ir_tx_shutdown(sd); + + if (p->mode != V4L2_SUBDEV_IR_MODE_PULSE_WIDTH) + return -ENOSYS; + + c = ir_state->c; + o = &ir_state->tx_params; + mutex_lock(&ir_state->tx_params_lock); + + o->shutdown = p->shutdown; + + p->mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH; + o->mode = p->mode; + + p->bytes_per_data_element = sizeof(union cx25840_ir_fifo_rec); + o->bytes_per_data_element = p->bytes_per_data_element; + + /* Before we tweak the hardware, we have to disable the transmitter */ + irqenable_tx(sd, 0); + control_tx_enable(c, false); + + control_tx_modulation_enable(c, p->modulation); + o->modulation = p->modulation; + + if (p->modulation) { + p->carrier_freq = txclk_tx_s_carrier(c, p->carrier_freq, + &txclk_divider); + o->carrier_freq = p->carrier_freq; + + p->duty_cycle = cduty_tx_s_duty_cycle(c, p->duty_cycle); + o->duty_cycle = p->duty_cycle; + + p->max_pulse_width = + (u32) pulse_width_count_to_ns(FIFO_RXTX, txclk_divider); + } else { + p->max_pulse_width = + txclk_tx_s_max_pulse_width(c, p->max_pulse_width, + &txclk_divider); + } + o->max_pulse_width = p->max_pulse_width; + atomic_set(&ir_state->txclk_divider, txclk_divider); + + p->resolution = clock_divider_to_resolution(txclk_divider); + o->resolution = p->resolution; + + /* FIXME - make this dependent on resolution for better performance */ + control_tx_irq_watermark(c, TX_FIFO_HALF_EMPTY); + + control_tx_polarity_invert(c, p->invert_carrier_sense); + o->invert_carrier_sense = p->invert_carrier_sense; + + /* + * FIXME: we don't have hardware help for IO pin level inversion + * here like we have on the CX23888. + * Act on this with some mix of logical inversion of data levels, + * carrier polarity, and carrier duty cycle. + */ + o->invert_level = p->invert_level; + + o->interrupt_enable = p->interrupt_enable; + o->enable = p->enable; + if (p->enable) { + /* reset tx_fifo here */ + if (p->interrupt_enable) + irqenable_tx(sd, IRQEN_TSE); + control_tx_enable(c, p->enable); + } + + mutex_unlock(&ir_state->tx_params_lock); + return 0; +} + + +/* + * V4L2 Subdevice Core Ops support + */ +int cx25840_ir_log_status(struct v4l2_subdev *sd) +{ + struct cx25840_state *state = to_state(sd); + struct i2c_client *c = state->c; + char *s; + int i, j; + u32 cntrl, txclk, rxclk, cduty, stats, irqen, filtr; + + /* The CX23888 chip doesn't have an IR controller on the A/V core */ + if (is_cx23888(state)) + return 0; + + cntrl = cx25840_read4(c, CX25840_IR_CNTRL_REG); + txclk = cx25840_read4(c, CX25840_IR_TXCLK_REG) & TXCLK_TCD; + rxclk = cx25840_read4(c, CX25840_IR_RXCLK_REG) & RXCLK_RCD; + cduty = cx25840_read4(c, CX25840_IR_CDUTY_REG) & CDUTY_CDC; + stats = cx25840_read4(c, CX25840_IR_STATS_REG); + irqen = cx25840_read4(c, CX25840_IR_IRQEN_REG); + if (is_cx23885(state) || is_cx23887(state)) + irqen ^= IRQEN_MSK; + filtr = cx25840_read4(c, CX25840_IR_FILTR_REG) & FILTR_LPF; + + v4l2_info(sd, "IR Receiver:\n"); + v4l2_info(sd, "\tEnabled: %s\n", + cntrl & CNTRL_RXE ? "yes" : "no"); + v4l2_info(sd, "\tDemodulation from a carrier: %s\n", + cntrl & CNTRL_DMD ? "enabled" : "disabled"); + v4l2_info(sd, "\tFIFO: %s\n", + cntrl & CNTRL_RFE ? "enabled" : "disabled"); + switch (cntrl & CNTRL_EDG) { + case CNTRL_EDG_NONE: + s = "disabled"; + break; + case CNTRL_EDG_FALL: + s = "falling edge"; + break; + case CNTRL_EDG_RISE: + s = "rising edge"; + break; + case CNTRL_EDG_BOTH: + s = "rising & falling edges"; + break; + default: + s = "??? edge"; + break; + } + v4l2_info(sd, "\tPulse timers' start/stop trigger: %s\n", s); + v4l2_info(sd, "\tFIFO data on pulse timer overflow: %s\n", + cntrl & CNTRL_R ? "not loaded" : "overflow marker"); + v4l2_info(sd, "\tFIFO interrupt watermark: %s\n", + cntrl & CNTRL_RIC ? "not empty" : "half full or greater"); + v4l2_info(sd, "\tLoopback mode: %s\n", + cntrl & CNTRL_LBM ? "loopback active" : "normal receive"); + if (cntrl & CNTRL_DMD) { + v4l2_info(sd, "\tExpected carrier (16 clocks): %u Hz\n", + clock_divider_to_carrier_freq(rxclk)); + switch (cntrl & CNTRL_WIN) { + case CNTRL_WIN_3_3: + i = 3; + j = 3; + break; + case CNTRL_WIN_4_3: + i = 4; + j = 3; + break; + case CNTRL_WIN_3_4: + i = 3; + j = 4; + break; + case CNTRL_WIN_4_4: + i = 4; + j = 4; + break; + default: + i = 0; + j = 0; + break; + } + v4l2_info(sd, "\tNext carrier edge window: 16 clocks -%1d/+%1d, %u to %u Hz\n", + i, j, + clock_divider_to_freq(rxclk, 16 + j), + clock_divider_to_freq(rxclk, 16 - i)); + } + v4l2_info(sd, "\tMax measurable pulse width: %u us, %llu ns\n", + pulse_width_count_to_us(FIFO_RXTX, rxclk), + pulse_width_count_to_ns(FIFO_RXTX, rxclk)); + v4l2_info(sd, "\tLow pass filter: %s\n", + filtr ? "enabled" : "disabled"); + if (filtr) + v4l2_info(sd, "\tMin acceptable pulse width (LPF): %u us, %u ns\n", + lpf_count_to_us(filtr), + lpf_count_to_ns(filtr)); + v4l2_info(sd, "\tPulse width timer timed-out: %s\n", + stats & STATS_RTO ? "yes" : "no"); + v4l2_info(sd, "\tPulse width timer time-out intr: %s\n", + irqen & IRQEN_RTE ? "enabled" : "disabled"); + v4l2_info(sd, "\tFIFO overrun: %s\n", + stats & STATS_ROR ? "yes" : "no"); + v4l2_info(sd, "\tFIFO overrun interrupt: %s\n", + irqen & IRQEN_ROE ? "enabled" : "disabled"); + v4l2_info(sd, "\tBusy: %s\n", + stats & STATS_RBY ? "yes" : "no"); + v4l2_info(sd, "\tFIFO service requested: %s\n", + stats & STATS_RSR ? "yes" : "no"); + v4l2_info(sd, "\tFIFO service request interrupt: %s\n", + irqen & IRQEN_RSE ? "enabled" : "disabled"); + + v4l2_info(sd, "IR Transmitter:\n"); + v4l2_info(sd, "\tEnabled: %s\n", + cntrl & CNTRL_TXE ? "yes" : "no"); + v4l2_info(sd, "\tModulation onto a carrier: %s\n", + cntrl & CNTRL_MOD ? "enabled" : "disabled"); + v4l2_info(sd, "\tFIFO: %s\n", + cntrl & CNTRL_TFE ? "enabled" : "disabled"); + v4l2_info(sd, "\tFIFO interrupt watermark: %s\n", + cntrl & CNTRL_TIC ? "not empty" : "half full or less"); + v4l2_info(sd, "\tCarrier polarity: %s\n", + cntrl & CNTRL_CPL ? "space:burst mark:noburst" + : "space:noburst mark:burst"); + if (cntrl & CNTRL_MOD) { + v4l2_info(sd, "\tCarrier (16 clocks): %u Hz\n", + clock_divider_to_carrier_freq(txclk)); + v4l2_info(sd, "\tCarrier duty cycle: %2u/16\n", + cduty + 1); + } + v4l2_info(sd, "\tMax pulse width: %u us, %llu ns\n", + pulse_width_count_to_us(FIFO_RXTX, txclk), + pulse_width_count_to_ns(FIFO_RXTX, txclk)); + v4l2_info(sd, "\tBusy: %s\n", + stats & STATS_TBY ? "yes" : "no"); + v4l2_info(sd, "\tFIFO service requested: %s\n", + stats & STATS_TSR ? "yes" : "no"); + v4l2_info(sd, "\tFIFO service request interrupt: %s\n", + irqen & IRQEN_TSE ? "enabled" : "disabled"); + + return 0; +} + + +const struct v4l2_subdev_ir_ops cx25840_ir_ops = { + .rx_read = cx25840_ir_rx_read, + .rx_g_parameters = cx25840_ir_rx_g_parameters, + .rx_s_parameters = cx25840_ir_rx_s_parameters, + + .tx_write = cx25840_ir_tx_write, + .tx_g_parameters = cx25840_ir_tx_g_parameters, + .tx_s_parameters = cx25840_ir_tx_s_parameters, +}; + + +static const struct v4l2_subdev_ir_parameters default_rx_params = { + .bytes_per_data_element = sizeof(union cx25840_ir_fifo_rec), + .mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH, + + .enable = false, + .interrupt_enable = false, + .shutdown = true, + + .modulation = true, + .carrier_freq = 36000, /* 36 kHz - RC-5, and RC-6 carrier */ + + /* RC-5: 666,667 ns = 1/36 kHz * 32 cycles * 1 mark * 0.75 */ + /* RC-6: 333,333 ns = 1/36 kHz * 16 cycles * 1 mark * 0.75 */ + .noise_filter_min_width = 333333, /* ns */ + .carrier_range_lower = 35000, + .carrier_range_upper = 37000, + .invert_level = false, +}; + +static const struct v4l2_subdev_ir_parameters default_tx_params = { + .bytes_per_data_element = sizeof(union cx25840_ir_fifo_rec), + .mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH, + + .enable = false, + .interrupt_enable = false, + .shutdown = true, + + .modulation = true, + .carrier_freq = 36000, /* 36 kHz - RC-5 carrier */ + .duty_cycle = 25, /* 25 % - RC-5 carrier */ + .invert_level = false, + .invert_carrier_sense = false, +}; + +int cx25840_ir_probe(struct v4l2_subdev *sd) +{ + struct cx25840_state *state = to_state(sd); + struct cx25840_ir_state *ir_state; + struct v4l2_subdev_ir_parameters default_params; + + /* Only init the IR controller for the CX2388[57] AV Core for now */ + if (!(is_cx23885(state) || is_cx23887(state))) + return 0; + + ir_state = devm_kzalloc(&state->c->dev, sizeof(*ir_state), GFP_KERNEL); + if (ir_state == NULL) + return -ENOMEM; + + spin_lock_init(&ir_state->rx_kfifo_lock); + if (kfifo_alloc(&ir_state->rx_kfifo, + CX25840_IR_RX_KFIFO_SIZE, GFP_KERNEL)) + return -ENOMEM; + + ir_state->c = state->c; + state->ir_state = ir_state; + + /* Ensure no interrupts arrive yet */ + if (is_cx23885(state) || is_cx23887(state)) + cx25840_write4(ir_state->c, CX25840_IR_IRQEN_REG, IRQEN_MSK); + else + cx25840_write4(ir_state->c, CX25840_IR_IRQEN_REG, 0); + + mutex_init(&ir_state->rx_params_lock); + default_params = default_rx_params; + v4l2_subdev_call(sd, ir, rx_s_parameters, &default_params); + + mutex_init(&ir_state->tx_params_lock); + default_params = default_tx_params; + v4l2_subdev_call(sd, ir, tx_s_parameters, &default_params); + + return 0; +} + +int cx25840_ir_remove(struct v4l2_subdev *sd) +{ + struct cx25840_state *state = to_state(sd); + struct cx25840_ir_state *ir_state = to_ir_state(sd); + + if (ir_state == NULL) + return -ENODEV; + + cx25840_ir_rx_shutdown(sd); + cx25840_ir_tx_shutdown(sd); + + kfifo_free(&ir_state->rx_kfifo); + state->ir_state = NULL; + return 0; +} diff --git a/drivers/media/i2c/cx25840/cx25840-vbi.c b/drivers/media/i2c/cx25840/cx25840-vbi.c new file mode 100644 index 000000000..a066d5f0f --- /dev/null +++ b/drivers/media/i2c/cx25840/cx25840-vbi.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* cx25840 VBI functions + */ + + +#include +#include +#include +#include + +#include "cx25840-core.h" + +static int odd_parity(u8 c) +{ + c ^= (c >> 4); + c ^= (c >> 2); + c ^= (c >> 1); + + return c & 1; +} + +static int decode_vps(u8 * dst, u8 * p) +{ + static const u8 biphase_tbl[] = { + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xc3, 0x4b, 0x43, 0xc3, 0x87, 0x0f, 0x07, 0x87, + 0x83, 0x0b, 0x03, 0x83, 0xc3, 0x4b, 0x43, 0xc3, + 0xc1, 0x49, 0x41, 0xc1, 0x85, 0x0d, 0x05, 0x85, + 0x81, 0x09, 0x01, 0x81, 0xc1, 0x49, 0x41, 0xc1, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xc2, 0x4a, 0x42, 0xc2, 0x86, 0x0e, 0x06, 0x86, + 0x82, 0x0a, 0x02, 0x82, 0xc2, 0x4a, 0x42, 0xc2, + 0xc0, 0x48, 0x40, 0xc0, 0x84, 0x0c, 0x04, 0x84, + 0x80, 0x08, 0x00, 0x80, 0xc0, 0x48, 0x40, 0xc0, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + }; + + u8 c, err = 0; + int i; + + for (i = 0; i < 2 * 13; i += 2) { + err |= biphase_tbl[p[i]] | biphase_tbl[p[i + 1]]; + c = (biphase_tbl[p[i + 1]] & 0xf) | + ((biphase_tbl[p[i]] & 0xf) << 4); + dst[i / 2] = c; + } + + return err & 0xf0; +} + +int cx25840_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct cx25840_state *state = to_state(sd); + static const u16 lcr2vbi[] = { + 0, V4L2_SLICED_TELETEXT_B, 0, /* 1 */ + 0, V4L2_SLICED_WSS_625, 0, /* 4 */ + V4L2_SLICED_CAPTION_525, /* 6 */ + 0, 0, V4L2_SLICED_VPS, 0, 0, /* 9 */ + 0, 0, 0, 0 + }; + int is_pal = !(state->std & V4L2_STD_525_60); + int i; + + memset(svbi->service_lines, 0, sizeof(svbi->service_lines)); + svbi->service_set = 0; + /* we're done if raw VBI is active */ + /* TODO: this will have to be changed for generic_mode VBI */ + if ((cx25840_read(client, 0x404) & 0x10) == 0) + return 0; + + if (is_pal) { + for (i = 7; i <= 23; i++) { + u8 v = cx25840_read(client, + state->vbi_regs_offset + 0x424 + i - 7); + + svbi->service_lines[0][i] = lcr2vbi[v >> 4]; + svbi->service_lines[1][i] = lcr2vbi[v & 0xf]; + svbi->service_set |= svbi->service_lines[0][i] | + svbi->service_lines[1][i]; + } + } else { + for (i = 10; i <= 21; i++) { + u8 v = cx25840_read(client, + state->vbi_regs_offset + 0x424 + i - 10); + + svbi->service_lines[0][i] = lcr2vbi[v >> 4]; + svbi->service_lines[1][i] = lcr2vbi[v & 0xf]; + svbi->service_set |= svbi->service_lines[0][i] | + svbi->service_lines[1][i]; + } + } + return 0; +} + +int cx25840_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct cx25840_state *state = to_state(sd); + int is_pal = !(state->std & V4L2_STD_525_60); + int vbi_offset = is_pal ? 1 : 0; + + /* Setup standard */ + cx25840_std_setup(client); + + /* VBI Offset */ + if (is_cx23888(state)) + cx25840_write(client, 0x54f, vbi_offset); + else + cx25840_write(client, 0x47f, vbi_offset); + /* TODO: this will have to be changed for generic_mode VBI */ + cx25840_write(client, 0x404, 0x2e); + return 0; +} + +int cx25840_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct cx25840_state *state = to_state(sd); + int is_pal = !(state->std & V4L2_STD_525_60); + int vbi_offset = is_pal ? 1 : 0; + int i, x; + u8 lcr[24]; + + for (x = 0; x <= 23; x++) + lcr[x] = 0x00; + + /* Setup standard */ + cx25840_std_setup(client); + + /* Sliced VBI */ + /* TODO: this will have to be changed for generic_mode VBI */ + cx25840_write(client, 0x404, 0x32); /* Ancillary data */ + cx25840_write(client, 0x406, 0x13); + if (is_cx23888(state)) + cx25840_write(client, 0x54f, vbi_offset); + else + cx25840_write(client, 0x47f, vbi_offset); + + if (is_pal) { + for (i = 0; i <= 6; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + } else { + for (i = 0; i <= 9; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + + for (i = 22; i <= 23; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + } + + for (i = 7; i <= 23; i++) { + for (x = 0; x <= 1; x++) { + switch (svbi->service_lines[1-x][i]) { + case V4L2_SLICED_TELETEXT_B: + lcr[i] |= 1 << (4 * x); + break; + case V4L2_SLICED_WSS_625: + lcr[i] |= 4 << (4 * x); + break; + case V4L2_SLICED_CAPTION_525: + lcr[i] |= 6 << (4 * x); + break; + case V4L2_SLICED_VPS: + lcr[i] |= 9 << (4 * x); + break; + } + } + } + + if (is_pal) { + for (x = 1, i = state->vbi_regs_offset + 0x424; + i <= state->vbi_regs_offset + 0x434; i++, x++) + cx25840_write(client, i, lcr[6 + x]); + } else { + for (x = 1, i = state->vbi_regs_offset + 0x424; + i <= state->vbi_regs_offset + 0x430; i++, x++) + cx25840_write(client, i, lcr[9 + x]); + for (i = state->vbi_regs_offset + 0x431; + i <= state->vbi_regs_offset + 0x434; i++) + cx25840_write(client, i, 0); + } + + cx25840_write(client, state->vbi_regs_offset + 0x43c, 0x16); + /* TODO: this will have to be changed for generic_mode VBI */ + if (is_cx23888(state)) + cx25840_write(client, 0x428, is_pal ? 0x2a : 0x22); + else + cx25840_write(client, 0x474, is_pal ? 0x2a : 0x22); + return 0; +} + +int cx25840_decode_vbi_line(struct v4l2_subdev *sd, struct v4l2_decode_vbi_line *vbi) +{ + struct cx25840_state *state = to_state(sd); + u8 *p = vbi->p; + int id1, id2, l, err = 0; + + if (p[0] || p[1] != 0xff || p[2] != 0xff || + (p[3] != 0x55 && p[3] != 0x91)) { + vbi->line = vbi->type = 0; + return 0; + } + + p += 4; + id1 = p[-1]; + id2 = p[0] & 0xf; + l = p[2] & 0x3f; + l += state->vbi_line_offset; + p += 4; + + switch (id2) { + case 1: + id2 = V4L2_SLICED_TELETEXT_B; + break; + case 4: + id2 = V4L2_SLICED_WSS_625; + break; + case 6: + id2 = V4L2_SLICED_CAPTION_525; + err = !odd_parity(p[0]) || !odd_parity(p[1]); + break; + case 9: + id2 = V4L2_SLICED_VPS; + if (decode_vps(p, p) != 0) + err = 1; + break; + default: + id2 = 0; + err = 1; + break; + } + + vbi->type = err ? 0 : id2; + vbi->line = err ? 0 : l; + vbi->is_second_field = err ? 0 : (id1 == 0x55); + vbi->p = p; + return 0; +} diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c new file mode 100644 index 000000000..6abb5e324 --- /dev/null +++ b/drivers/media/i2c/ds90ub913.c @@ -0,0 +1,901 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Texas Instruments DS90UB913 video serializer + * + * Based on a driver from Luca Ceresoli + * + * Copyright (c) 2019 Luca Ceresoli + * Copyright (c) 2023 Tomi Valkeinen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define UB913_PAD_SINK 0 +#define UB913_PAD_SOURCE 1 + +/* + * UB913 has 4 gpios, but gpios 3 and 4 are reserved for external oscillator + * mode. Thus we only support 2 gpios for now. + */ +#define UB913_NUM_GPIOS 2 + +#define UB913_REG_RESET_CTL 0x01 +#define UB913_REG_RESET_CTL_DIGITAL_RESET_1 BIT(1) +#define UB913_REG_RESET_CTL_DIGITAL_RESET_0 BIT(0) + +#define UB913_REG_GENERAL_CFG 0x03 +#define UB913_REG_GENERAL_CFG_CRC_ERR_RESET BIT(5) +#define UB913_REG_GENERAL_CFG_PCLK_RISING BIT(0) + +#define UB913_REG_MODE_SEL 0x05 +#define UB913_REG_MODE_SEL_MODE_OVERRIDE BIT(5) +#define UB913_REG_MODE_SEL_MODE_UP_TO_DATE BIT(4) +#define UB913_REG_MODE_SEL_MODE_MASK GENMASK(3, 0) + +#define UB913_REG_CRC_ERRORS_LSB 0x0a +#define UB913_REG_CRC_ERRORS_MSB 0x0b + +#define UB913_REG_GENERAL_STATUS 0x0c + +#define UB913_REG_GPIO_CFG(n) (0x0d + (n)) +#define UB913_REG_GPIO_CFG_ENABLE(n) BIT(0 + (n) * 4) +#define UB913_REG_GPIO_CFG_DIR_INPUT(n) BIT(1 + (n) * 4) +#define UB913_REG_GPIO_CFG_REMOTE_EN(n) BIT(2 + (n) * 4) +#define UB913_REG_GPIO_CFG_OUT_VAL(n) BIT(3 + (n) * 4) +#define UB913_REG_GPIO_CFG_MASK(n) (0xf << ((n) * 4)) + +#define UB913_REG_SCL_HIGH_TIME 0x11 +#define UB913_REG_SCL_LOW_TIME 0x12 + +#define UB913_REG_PLL_OVR 0x35 + +struct ub913_data { + struct i2c_client *client; + struct regmap *regmap; + struct clk *clkin; + + struct gpio_chip gpio_chip; + + struct v4l2_subdev sd; + struct media_pad pads[2]; + + struct v4l2_async_notifier notifier; + + struct v4l2_subdev *source_sd; + u16 source_sd_pad; + + u64 enabled_source_streams; + + struct clk_hw *clkout_clk_hw; + + struct ds90ub9xx_platform_data *plat_data; + + bool pclk_polarity_rising; +}; + +static inline struct ub913_data *sd_to_ub913(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ub913_data, sd); +} + +struct ub913_format_info { + u32 incode; + u32 outcode; +}; + +static const struct ub913_format_info ub913_formats[] = { + /* Only RAW10 with 8-bit payload is supported at the moment */ + { .incode = MEDIA_BUS_FMT_YUYV8_2X8, .outcode = MEDIA_BUS_FMT_YUYV8_1X16 }, + { .incode = MEDIA_BUS_FMT_UYVY8_2X8, .outcode = MEDIA_BUS_FMT_UYVY8_1X16 }, + { .incode = MEDIA_BUS_FMT_VYUY8_2X8, .outcode = MEDIA_BUS_FMT_VYUY8_1X16 }, + { .incode = MEDIA_BUS_FMT_YVYU8_2X8, .outcode = MEDIA_BUS_FMT_YVYU8_1X16 }, +}; + +static const struct ub913_format_info *ub913_find_format(u32 incode) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ub913_formats); i++) { + if (ub913_formats[i].incode == incode) + return &ub913_formats[i]; + } + + return NULL; +} + +static int ub913_read(const struct ub913_data *priv, u8 reg, u8 *val, + int *err) +{ + unsigned int v; + int ret; + + if (err && *err) + return *err; + + ret = regmap_read(priv->regmap, reg, &v); + if (ret) { + dev_err(&priv->client->dev, + "Cannot read register 0x%02x: %d!\n", reg, ret); + goto out; + } + + *val = v; + +out: + if (ret && err) + *err = ret; + + return ret; +} + +static int ub913_write(const struct ub913_data *priv, u8 reg, u8 val, + int *err) +{ + int ret; + + if (err && *err) + return *err; + + ret = regmap_write(priv->regmap, reg, val); + if (ret < 0) + dev_err(&priv->client->dev, + "Cannot write register 0x%02x: %d!\n", reg, ret); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub913_update_bits(const struct ub913_data *priv, u8 reg, u8 mask, + u8 val, int *err) +{ + int ret; + + if (err && *err) + return *err; + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + if (ret < 0) + dev_err(&priv->client->dev, + "Cannot update register 0x%02x %d!\n", reg, ret); + + if (ret && err) + *err = ret; + + return ret; +} + +/* + * GPIO chip + */ +static int ub913_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int ub913_gpio_direction_out(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct ub913_data *priv = gpiochip_get_data(gc); + unsigned int reg_idx = offset / 2; + unsigned int field_idx = offset % 2; + + return regmap_update_bits(priv->regmap, UB913_REG_GPIO_CFG(reg_idx), + UB913_REG_GPIO_CFG_MASK(field_idx), + UB913_REG_GPIO_CFG_ENABLE(field_idx) | + (value ? UB913_REG_GPIO_CFG_OUT_VAL(field_idx) : + 0)); +} + +static int ub913_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + return ub913_gpio_direction_out(gc, offset, value); +} + +static int ub913_gpio_of_xlate(struct gpio_chip *gc, + const struct of_phandle_args *gpiospec, + u32 *flags) +{ + if (flags) + *flags = gpiospec->args[1]; + + return gpiospec->args[0]; +} + +static int ub913_gpiochip_probe(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + struct gpio_chip *gc = &priv->gpio_chip; + int ret; + + /* Initialize GPIOs 0 and 1 to local control, tri-state */ + ub913_write(priv, UB913_REG_GPIO_CFG(0), 0, NULL); + + gc->label = dev_name(dev); + gc->parent = dev; + gc->owner = THIS_MODULE; + gc->base = -1; + gc->can_sleep = true; + gc->ngpio = UB913_NUM_GPIOS; + gc->get_direction = ub913_gpio_get_direction; + gc->direction_output = ub913_gpio_direction_out; + gc->set = ub913_gpio_set; + gc->of_xlate = ub913_gpio_of_xlate; + gc->of_gpio_n_cells = 2; + + ret = gpiochip_add_data(gc, priv); + if (ret) { + dev_err(dev, "Failed to add GPIOs: %d\n", ret); + return ret; + } + + return 0; +} + +static void ub913_gpiochip_remove(struct ub913_data *priv) +{ + gpiochip_remove(&priv->gpio_chip); +} + +static const struct regmap_config ub913_regmap_config = { + .name = "ds90ub913", + .reg_bits = 8, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_DEFAULT, + .val_format_endian = REGMAP_ENDIAN_DEFAULT, +}; + +/* + * V4L2 + */ + +static int ub913_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ub913_data *priv = sd_to_ub913(sd); + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, UB913_PAD_SOURCE, + UB913_PAD_SINK, + &streams_mask); + + ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams |= streams_mask; + + return 0; +} + +static int ub913_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ub913_data *priv = sd_to_ub913(sd); + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, UB913_PAD_SOURCE, + UB913_PAD_SINK, + &streams_mask); + + ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams &= ~streams_mask; + + return 0; +} + +static int _ub913_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + static const struct v4l2_mbus_framefmt in_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, + }; + static const struct v4l2_mbus_framefmt out_format = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .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, + }; + struct v4l2_subdev_route *route; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing(sd, state, routing); + if (ret) + return ret; + + for_each_active_route(&state->routing, route) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); + *fmt = in_format; + fmt = v4l2_subdev_state_get_format(state, route->source_pad, + route->source_stream); + *fmt = out_format; + } + + return 0; +} + +static int ub913_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct ub913_data *priv = sd_to_ub913(sd); + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams) + return -EBUSY; + + return _ub913_set_routing(sd, state, routing); +} + +static int ub913_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct ub913_data *priv = sd_to_ub913(sd); + struct v4l2_mbus_framefmt *fmt; + const struct ub913_format_info *finfo; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && + priv->enabled_source_streams) + return -EBUSY; + + /* Source format is fully defined by the sink format, so not settable */ + if (format->pad == UB913_PAD_SOURCE) + return v4l2_subdev_get_fmt(sd, state, format); + + finfo = ub913_find_format(format->format.code); + if (!finfo) { + finfo = &ub913_formats[0]; + format->format.code = finfo->incode; + } + + /* Set sink format */ + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + /* Propagate to source format, and adjust the mbus code */ + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + fmt->code = finfo->outcode; + + return 0; +} + +static int ub913_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[] = { + { + .sink_pad = UB913_PAD_SINK, + .sink_stream = 0, + .source_pad = UB913_PAD_SOURCE, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return _ub913_set_routing(sd, state, &routing); +} + +static int ub913_log_status(struct v4l2_subdev *sd) +{ + struct ub913_data *priv = sd_to_ub913(sd); + struct device *dev = &priv->client->dev; + u8 v, v1, v2; + int ret; + + ret = ub913_read(priv, UB913_REG_MODE_SEL, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "MODE_SEL %#02x\n", v); + + ub913_read(priv, UB913_REG_CRC_ERRORS_LSB, &v1, &ret); + ub913_read(priv, UB913_REG_CRC_ERRORS_MSB, &v2, &ret); + if (ret) + return ret; + + dev_info(dev, "CRC errors %u\n", v1 | (v2 << 8)); + + /* clear CRC errors */ + ub913_read(priv, UB913_REG_GENERAL_CFG, &v, &ret); + ub913_write(priv, UB913_REG_GENERAL_CFG, + v | UB913_REG_GENERAL_CFG_CRC_ERR_RESET, &ret); + ub913_write(priv, UB913_REG_GENERAL_CFG, v, &ret); + + if (ret) + return ret; + + ret = ub913_read(priv, UB913_REG_GENERAL_STATUS, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "GENERAL_STATUS %#02x\n", v); + + ret = ub913_read(priv, UB913_REG_PLL_OVR, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "PLL_OVR %#02x\n", v); + + return 0; +} + +static const struct v4l2_subdev_core_ops ub913_subdev_core_ops = { + .log_status = ub913_log_status, +}; + +static const struct v4l2_subdev_pad_ops ub913_pad_ops = { + .enable_streams = ub913_enable_streams, + .disable_streams = ub913_disable_streams, + .set_routing = ub913_set_routing, + .get_frame_desc = v4l2_subdev_get_frame_desc_passthrough, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = ub913_set_fmt, +}; + +static const struct v4l2_subdev_ops ub913_subdev_ops = { + .core = &ub913_subdev_core_ops, + .pad = &ub913_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops ub913_internal_ops = { + .init_state = ub913_init_state, +}; + +static const struct media_entity_operations ub913_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int ub913_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *source_subdev, + struct v4l2_async_connection *asd) +{ + struct ub913_data *priv = sd_to_ub913(notifier->sd); + struct device *dev = &priv->client->dev; + int ret; + + ret = media_entity_get_fwnode_pad(&source_subdev->entity, + source_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "Failed to find pad for %s\n", + source_subdev->name); + return ret; + } + + priv->source_sd = source_subdev; + priv->source_sd_pad = ret; + + ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad, + &priv->sd.entity, UB913_PAD_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:0\n", + source_subdev->name, priv->source_sd_pad, + priv->sd.name); + return ret; + } + + return 0; +} + +static const struct v4l2_async_notifier_operations ub913_notify_ops = { + .bound = ub913_notify_bound, +}; + +static int ub913_v4l2_notifier_register(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + struct v4l2_async_connection *asd; + struct fwnode_handle *ep_fwnode; + int ret; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + UB913_PAD_SINK, 0, 0); + if (!ep_fwnode) { + dev_err(dev, "No graph endpoint\n"); + return -ENODEV; + } + + v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd); + + asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode, + struct v4l2_async_connection); + + fwnode_handle_put(ep_fwnode); + + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev: %pe", asd); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + priv->notifier.ops = &ub913_notify_ops; + + ret = v4l2_async_nf_register(&priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void ub913_v4l2_nf_unregister(struct ub913_data *priv) +{ + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +} + +static int ub913_register_clkout(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + const char *name; + int ret; + + name = kasprintf(GFP_KERNEL, "ds90ub913.%s.clk_out", dev_name(dev)); + if (!name) + return -ENOMEM; + + priv->clkout_clk_hw = devm_clk_hw_register_fixed_factor(dev, name, + __clk_get_name(priv->clkin), 0, 1, 2); + + kfree(name); + + if (IS_ERR(priv->clkout_clk_hw)) + return dev_err_probe(dev, PTR_ERR(priv->clkout_clk_hw), + "Cannot register clkout hw\n"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + priv->clkout_clk_hw); + if (ret) + return dev_err_probe(dev, ret, + "Cannot add OF clock provider\n"); + + return 0; +} + +static int ub913_i2c_master_init(struct ub913_data *priv) +{ + /* i2c fast mode */ + u32 scl_high = 600 + 300; /* high period + rise time, ns */ + u32 scl_low = 1300 + 300; /* low period + fall time, ns */ + unsigned long ref; + int ret; + + ref = clk_get_rate(priv->clkin) / 2; + + scl_high = div64_u64((u64)scl_high * ref, 1000000000); + scl_low = div64_u64((u64)scl_low * ref, 1000000000); + + ret = ub913_write(priv, UB913_REG_SCL_HIGH_TIME, scl_high, NULL); + if (ret) + return ret; + + ret = ub913_write(priv, UB913_REG_SCL_LOW_TIME, scl_low, NULL); + if (ret) + return ret; + + return 0; +} + +static int ub913_add_i2c_adapter(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + struct i2c_atr_adap_desc desc = { }; + struct fwnode_handle *i2c_handle; + int ret; + + i2c_handle = device_get_named_child_node(dev, "i2c"); + if (!i2c_handle) + return 0; + + desc.chan_id = priv->plat_data->port; + desc.parent = dev; + desc.bus_handle = i2c_handle; + desc.num_aliases = 0; + + ret = i2c_atr_add_adapter(priv->plat_data->atr, &desc); + + fwnode_handle_put(i2c_handle); + + if (ret) + return ret; + + return 0; +} + +static int ub913_parse_dt(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_PARALLEL, + }; + struct fwnode_handle *ep_fwnode; + int ret; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + UB913_PAD_SINK, 0, 0); + if (!ep_fwnode) + return dev_err_probe(dev, -ENOENT, "No sink endpoint\n"); + + ret = v4l2_fwnode_endpoint_parse(ep_fwnode, &vep); + + fwnode_handle_put(ep_fwnode); + + if (ret) + return dev_err_probe(dev, ret, + "failed to parse sink endpoint data\n"); + + if (vep.bus.parallel.flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + priv->pclk_polarity_rising = true; + else if (vep.bus.parallel.flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + priv->pclk_polarity_rising = false; + else + return dev_err_probe(dev, -EINVAL, + "bad value for 'pclk-sample'\n"); + + return 0; +} + +static int ub913_hw_init(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + bool mode_override; + u8 mode; + int ret; + u8 v; + + ret = ub913_read(priv, UB913_REG_MODE_SEL, &v, NULL); + if (ret) + return ret; + + if (!(v & UB913_REG_MODE_SEL_MODE_UP_TO_DATE)) + return dev_err_probe(dev, -ENODEV, + "Mode value not stabilized\n"); + + mode_override = v & UB913_REG_MODE_SEL_MODE_OVERRIDE; + mode = v & UB913_REG_MODE_SEL_MODE_MASK; + + dev_dbg(dev, "mode from %s: %#x\n", + mode_override ? "reg" : "deserializer", mode); + + ret = ub913_i2c_master_init(priv); + if (ret) + return dev_err_probe(dev, ret, "i2c master init failed\n"); + + ret = ub913_update_bits(priv, UB913_REG_GENERAL_CFG, + UB913_REG_GENERAL_CFG_PCLK_RISING, + FIELD_PREP(UB913_REG_GENERAL_CFG_PCLK_RISING, + priv->pclk_polarity_rising), NULL); + + if (ret) + return ret; + + return 0; +} + +static int ub913_subdev_init(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + int ret; + + v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub913_subdev_ops); + priv->sd.internal_ops = &ub913_internal_ops; + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; + priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops = &ub913_entity_ops; + + priv->pads[0].flags = MEDIA_PAD_FL_SINK; + priv->pads[1].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads); + if (ret) + return dev_err_probe(dev, ret, "Failed to init pads\n"); + + ret = v4l2_subdev_init_finalize(&priv->sd); + if (ret) + goto err_entity_cleanup; + + ret = ub913_v4l2_notifier_register(priv); + if (ret) { + dev_err_probe(dev, ret, + "v4l2 subdev notifier register failed\n"); + goto err_subdev_cleanup; + } + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) { + dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n"); + goto err_unreg_notif; + } + + return 0; + +err_unreg_notif: + ub913_v4l2_nf_unregister(priv); +err_subdev_cleanup: + v4l2_subdev_cleanup(&priv->sd); +err_entity_cleanup: + media_entity_cleanup(&priv->sd.entity); + + return ret; +} + +static void ub913_subdev_uninit(struct ub913_data *priv) +{ + v4l2_async_unregister_subdev(&priv->sd); + ub913_v4l2_nf_unregister(priv); + v4l2_subdev_cleanup(&priv->sd); + media_entity_cleanup(&priv->sd.entity); +} + +static int ub913_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ub913_data *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + + priv->plat_data = dev_get_platdata(&client->dev); + if (!priv->plat_data) + return dev_err_probe(dev, -ENODEV, "Platform data missing\n"); + + priv->regmap = devm_regmap_init_i2c(client, &ub913_regmap_config); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), + "Failed to init regmap\n"); + + /* + * ub913 can also work without ext clock, but that is not supported by + * the driver yet. + */ + priv->clkin = devm_clk_get(dev, "clkin"); + if (IS_ERR(priv->clkin)) + return dev_err_probe(dev, PTR_ERR(priv->clkin), + "Cannot get CLKIN\n"); + + ret = ub913_parse_dt(priv); + if (ret) + return ret; + + ret = ub913_hw_init(priv); + if (ret) + return ret; + + ret = ub913_gpiochip_probe(priv); + if (ret) + return dev_err_probe(dev, ret, "Failed to init gpiochip\n"); + + ret = ub913_register_clkout(priv); + if (ret) { + dev_err_probe(dev, ret, "Failed to register clkout\n"); + goto err_gpiochip_remove; + } + + ret = ub913_subdev_init(priv); + if (ret) + goto err_gpiochip_remove; + + ret = ub913_add_i2c_adapter(priv); + if (ret) { + dev_err_probe(dev, ret, "failed to add remote i2c adapter\n"); + goto err_subdev_uninit; + } + + return 0; + +err_subdev_uninit: + ub913_subdev_uninit(priv); +err_gpiochip_remove: + ub913_gpiochip_remove(priv); + + return ret; +} + +static void ub913_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ub913_data *priv = sd_to_ub913(sd); + + i2c_atr_del_adapter(priv->plat_data->atr, priv->plat_data->port); + + ub913_subdev_uninit(priv); + + ub913_gpiochip_remove(priv); +} + +static const struct i2c_device_id ub913_id[] = { + { .name = "ds90ub913a-q1" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ub913_id); + +static const struct of_device_id ub913_dt_ids[] = { + { .compatible = "ti,ds90ub913a-q1" }, + {} +}; +MODULE_DEVICE_TABLE(of, ub913_dt_ids); + +static struct i2c_driver ds90ub913_driver = { + .probe = ub913_probe, + .remove = ub913_remove, + .id_table = ub913_id, + .driver = { + .name = "ds90ub913a", + .of_match_table = ub913_dt_ids, + }, +}; +module_i2c_driver(ds90ub913_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Texas Instruments DS90UB913 FPD-Link III Serializer Driver"); +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_AUTHOR("Tomi Valkeinen "); +MODULE_IMPORT_NS("I2C_ATR"); diff --git a/drivers/media/i2c/ds90ub953.c b/drivers/media/i2c/ds90ub953.c new file mode 100644 index 000000000..d4228e113 --- /dev/null +++ b/drivers/media/i2c/ds90ub953.c @@ -0,0 +1,1378 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Texas Instruments DS90UB953 video serializer + * + * Based on a driver from Luca Ceresoli + * + * Copyright (c) 2019 Luca Ceresoli + * Copyright (c) 2023 Tomi Valkeinen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ds90ub953.h" + +#define UB953_PAD_SINK 0 +#define UB953_PAD_SOURCE 1 + +#define UB953_NUM_GPIOS 4 + +#define UB953_DEFAULT_CLKOUT_RATE 25000000UL + +/* Note: Only sync mode supported for now */ +enum ub953_mode { + /* FPD-Link III CSI-2 synchronous mode */ + UB953_MODE_SYNC, + /* FPD-Link III CSI-2 non-synchronous mode, external ref clock */ + UB953_MODE_NONSYNC_EXT, + /* FPD-Link III CSI-2 non-synchronous mode, internal ref clock */ + UB953_MODE_NONSYNC_INT, + /* FPD-Link III DVP mode */ + UB953_MODE_DVP, +}; + +struct ub953_hw_data { + const char *model; + bool is_ub971; +}; + +struct ub953_clkout_data { + u32 hs_div; + u32 m; + u32 n; + unsigned long rate; +}; + +struct ub953_data { + const struct ub953_hw_data *hw_data; + + struct i2c_client *client; + struct regmap *regmap; + struct clk *clkin; + + u32 num_data_lanes; + bool non_continous_clk; + + struct gpio_chip gpio_chip; + + struct v4l2_subdev sd; + struct media_pad pads[2]; + + struct v4l2_async_notifier notifier; + + struct v4l2_subdev *source_sd; + u16 source_sd_pad; + + u64 enabled_source_streams; + + /* lock for register access */ + struct mutex reg_lock; + + u8 current_indirect_target; + + struct clk_hw clkout_clk_hw; + + enum ub953_mode mode; + + const struct ds90ub9xx_platform_data *plat_data; +}; + +static inline struct ub953_data *sd_to_ub953(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ub953_data, sd); +} + +/* + * HW Access + */ + +static int ub953_read(struct ub953_data *priv, u8 reg, u8 *val, int *err) +{ + unsigned int v; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = regmap_read(priv->regmap, reg, &v); + if (ret) { + dev_err(&priv->client->dev, "Cannot read register 0x%02x: %d\n", + reg, ret); + goto out_unlock; + } + + *val = v; + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub953_write(struct ub953_data *priv, u8 reg, u8 val, int *err) +{ + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = regmap_write(priv->regmap, reg, val); + if (ret) + dev_err(&priv->client->dev, + "Cannot write register 0x%02x: %d\n", reg, ret); + + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub953_select_ind_reg_block(struct ub953_data *priv, u8 block) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (priv->current_indirect_target == block) + return 0; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_CTL, block << 2); + if (ret) { + dev_err(dev, "%s: cannot select indirect target %u (%d)\n", + __func__, block, ret); + return ret; + } + + priv->current_indirect_target = block; + + return 0; +} + +__maybe_unused +static int ub953_read_ind(struct ub953_data *priv, u8 block, u8 reg, u8 *val, + int *err) +{ + unsigned int v; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = ub953_select_ind_reg_block(priv, block); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg); + if (ret) { + dev_err(&priv->client->dev, + "Write to IND_ACC_ADDR failed when reading %u:0x%02x: %d\n", + block, reg, ret); + goto out_unlock; + } + + ret = regmap_read(priv->regmap, UB953_REG_IND_ACC_DATA, &v); + if (ret) { + dev_err(&priv->client->dev, + "Write to IND_ACC_DATA failed when reading %u:0x%02x: %d\n", + block, reg, ret); + goto out_unlock; + } + + *val = v; + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +__maybe_unused +static int ub953_write_ind(struct ub953_data *priv, u8 block, u8 reg, u8 val, + int *err) +{ + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = ub953_select_ind_reg_block(priv, block); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg); + if (ret) { + dev_err(&priv->client->dev, + "Write to IND_ACC_ADDR failed when writing %u:0x%02x: %d\n", + block, reg, ret); + goto out_unlock; + } + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val); + if (ret) { + dev_err(&priv->client->dev, + "Write to IND_ACC_DATA failed when writing %u:0x%02x: %d\n", + block, reg, ret); + } + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +/* + * GPIO chip + */ +static int ub953_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + int ret; + u8 v; + + ret = ub953_read(priv, UB953_REG_GPIO_INPUT_CTRL, &v, NULL); + if (ret) + return ret; + + if (v & UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset)) + return GPIO_LINE_DIRECTION_IN; + else + return GPIO_LINE_DIRECTION_OUT; +} + +static int ub953_gpio_direction_in(struct gpio_chip *gc, unsigned int offset) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + + return regmap_update_bits(priv->regmap, UB953_REG_GPIO_INPUT_CTRL, + UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset) | + UB953_REG_GPIO_INPUT_CTRL_OUT_EN(offset), + UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset)); +} + +static int ub953_gpio_direction_out(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + int ret; + + ret = regmap_update_bits(priv->regmap, UB953_REG_LOCAL_GPIO_DATA, + UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset), + value ? UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset) : + 0); + + if (ret) + return ret; + + return regmap_update_bits(priv->regmap, UB953_REG_GPIO_INPUT_CTRL, + UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset) | + UB953_REG_GPIO_INPUT_CTRL_OUT_EN(offset), + UB953_REG_GPIO_INPUT_CTRL_OUT_EN(offset)); +} + +static int ub953_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + int ret; + u8 v; + + ret = ub953_read(priv, UB953_REG_GPIO_PIN_STS, &v, NULL); + if (ret) + return ret; + + return !!(v & UB953_REG_GPIO_PIN_STS_GPIO_STS(offset)); +} + +static int ub953_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + + return regmap_update_bits(priv->regmap, UB953_REG_LOCAL_GPIO_DATA, + UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset), + value ? UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset) : 0); +} + +static int ub953_gpio_of_xlate(struct gpio_chip *gc, + const struct of_phandle_args *gpiospec, + u32 *flags) +{ + if (flags) + *flags = gpiospec->args[1]; + + return gpiospec->args[0]; +} + +static int ub953_gpiochip_probe(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + struct gpio_chip *gc = &priv->gpio_chip; + int ret; + + /* Set all GPIOs to local input mode */ + ret = ub953_write(priv, UB953_REG_LOCAL_GPIO_DATA, 0, NULL); + if (ret) + return ret; + + ret = ub953_write(priv, UB953_REG_GPIO_INPUT_CTRL, 0xf, NULL); + if (ret) + return ret; + + gc->label = dev_name(dev); + gc->parent = dev; + gc->owner = THIS_MODULE; + gc->base = -1; + gc->can_sleep = true; + gc->ngpio = UB953_NUM_GPIOS; + gc->get_direction = ub953_gpio_get_direction; + gc->direction_input = ub953_gpio_direction_in; + gc->direction_output = ub953_gpio_direction_out; + gc->get = ub953_gpio_get; + gc->set = ub953_gpio_set; + gc->of_xlate = ub953_gpio_of_xlate; + gc->of_gpio_n_cells = 2; + + ret = gpiochip_add_data(gc, priv); + if (ret) { + dev_err(dev, "Failed to add GPIOs: %d\n", ret); + return ret; + } + + return 0; +} + +static void ub953_gpiochip_remove(struct ub953_data *priv) +{ + gpiochip_remove(&priv->gpio_chip); +} + +/* + * V4L2 + */ + +static int _ub953_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + static const struct v4l2_mbus_framefmt format = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .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, + }; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); + if (ret) + return ret; + + return 0; +} + +static int ub953_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct ub953_data *priv = sd_to_ub953(sd); + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams) + return -EBUSY; + + return _ub953_set_routing(sd, state, routing); +} + + +static int ub953_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct ub953_data *priv = sd_to_ub953(sd); + struct v4l2_mbus_framefmt *fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && + priv->enabled_source_streams) + return -EBUSY; + + /* No transcoding, source and sink formats must match. */ + if (format->pad == UB953_PAD_SOURCE) + return v4l2_subdev_get_fmt(sd, state, format); + + /* Set sink format */ + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + /* Propagate to source format */ + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + return 0; +} + +static int ub953_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[] = { + { + .sink_pad = UB953_PAD_SINK, + .sink_stream = 0, + .source_pad = UB953_PAD_SOURCE, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return _ub953_set_routing(sd, state, &routing); +} + +static int ub953_log_status(struct v4l2_subdev *sd) +{ + struct ub953_data *priv = sd_to_ub953(sd); + struct device *dev = &priv->client->dev; + char id[UB953_REG_FPD3_RX_ID_LEN]; + u8 gpio_local_data; + u8 gpio_input_ctrl; + u8 gpio_pin_sts; + unsigned int i; + u8 v, v1, v2; + int ret; + + for (i = 0; i < sizeof(id); i++) { + ret = ub953_read(priv, UB953_REG_FPD3_RX_ID(i), &id[i], NULL); + if (ret) + return ret; + } + + dev_info(dev, "ID '%.*s'\n", (int)sizeof(id), id); + + ret = ub953_read(priv, UB953_REG_GENERAL_STATUS, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "GENERAL_STATUS %#02x\n", v); + + ub953_read(priv, UB953_REG_CRC_ERR_CNT1, &v1, &ret); + ub953_read(priv, UB953_REG_CRC_ERR_CNT2, &v2, &ret); + if (ret) + return ret; + + dev_info(dev, "CRC error count %u\n", v1 | (v2 << 8)); + + /* Clear CRC error counter */ + if (v1 || v2) + regmap_update_bits(priv->regmap, UB953_REG_BC_CTRL, + UB953_REG_BC_CTRL_CRC_ERR_CLR, + UB953_REG_BC_CTRL_CRC_ERR_CLR); + + ret = ub953_read(priv, UB953_REG_CSI_ERR_CNT, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "CSI error count %u\n", v); + + ret = ub953_read(priv, UB953_REG_CSI_ERR_STATUS, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "CSI_ERR_STATUS %#02x\n", v); + + ret = ub953_read(priv, UB953_REG_CSI_ERR_DLANE01, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "CSI_ERR_DLANE01 %#02x\n", v); + + ret = ub953_read(priv, UB953_REG_CSI_ERR_DLANE23, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "CSI_ERR_DLANE23 %#02x\n", v); + + ret = ub953_read(priv, UB953_REG_CSI_ERR_CLK_LANE, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "CSI_ERR_CLK_LANE %#02x\n", v); + + ret = ub953_read(priv, UB953_REG_CSI_PKT_HDR_VC_ID, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "CSI packet header VC %u ID %u\n", v >> 6, v & 0x3f); + + ub953_read(priv, UB953_REG_PKT_HDR_WC_LSB, &v1, &ret); + ub953_read(priv, UB953_REG_PKT_HDR_WC_MSB, &v2, &ret); + if (ret) + return ret; + + dev_info(dev, "CSI packet header WC %u\n", (v2 << 8) | v1); + + ret = ub953_read(priv, UB953_REG_CSI_ECC, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "CSI ECC %#02x\n", v); + + ub953_read(priv, UB953_REG_LOCAL_GPIO_DATA, &gpio_local_data, &ret); + ub953_read(priv, UB953_REG_GPIO_INPUT_CTRL, &gpio_input_ctrl, &ret); + ub953_read(priv, UB953_REG_GPIO_PIN_STS, &gpio_pin_sts, &ret); + if (ret) + return ret; + + for (i = 0; i < UB953_NUM_GPIOS; i++) { + dev_info(dev, + "GPIO%u: remote: %u is_input: %u is_output: %u val: %u sts: %u\n", + i, + !!(gpio_local_data & UB953_REG_LOCAL_GPIO_DATA_GPIO_RMTEN(i)), + !!(gpio_input_ctrl & UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(i)), + !!(gpio_input_ctrl & UB953_REG_GPIO_INPUT_CTRL_OUT_EN(i)), + !!(gpio_local_data & UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(i)), + !!(gpio_pin_sts & UB953_REG_GPIO_PIN_STS_GPIO_STS(i))); + } + + return 0; +} + +static int ub953_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ub953_data *priv = sd_to_ub953(sd); + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, UB953_PAD_SOURCE, + UB953_PAD_SINK, + &streams_mask); + + ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams |= streams_mask; + + return 0; +} + +static int ub953_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ub953_data *priv = sd_to_ub953(sd); + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, UB953_PAD_SOURCE, + UB953_PAD_SINK, + &streams_mask); + + ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams &= ~streams_mask; + + return 0; +} + +static const struct v4l2_subdev_pad_ops ub953_pad_ops = { + .enable_streams = ub953_enable_streams, + .disable_streams = ub953_disable_streams, + .set_routing = ub953_set_routing, + .get_frame_desc = v4l2_subdev_get_frame_desc_passthrough, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = ub953_set_fmt, +}; + +static const struct v4l2_subdev_core_ops ub953_subdev_core_ops = { + .log_status = ub953_log_status, +}; + +static const struct v4l2_subdev_ops ub953_subdev_ops = { + .core = &ub953_subdev_core_ops, + .pad = &ub953_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops ub953_internal_ops = { + .init_state = ub953_init_state, +}; + +static const struct media_entity_operations ub953_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int ub953_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *source_subdev, + struct v4l2_async_connection *asd) +{ + struct ub953_data *priv = sd_to_ub953(notifier->sd); + struct device *dev = &priv->client->dev; + int ret; + + ret = media_entity_get_fwnode_pad(&source_subdev->entity, + source_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "Failed to find pad for %s\n", + source_subdev->name); + return ret; + } + + priv->source_sd = source_subdev; + priv->source_sd_pad = ret; + + ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad, + &priv->sd.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:0\n", + source_subdev->name, priv->source_sd_pad, + priv->sd.name); + return ret; + } + + return 0; +} + +static const struct v4l2_async_notifier_operations ub953_notify_ops = { + .bound = ub953_notify_bound, +}; + +static int ub953_v4l2_notifier_register(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + struct v4l2_async_connection *asd; + struct fwnode_handle *ep_fwnode; + int ret; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + UB953_PAD_SINK, 0, 0); + if (!ep_fwnode) { + dev_err(dev, "No graph endpoint\n"); + return -ENODEV; + } + + v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd); + + asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode, + struct v4l2_async_connection); + + fwnode_handle_put(ep_fwnode); + + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev: %pe", asd); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + priv->notifier.ops = &ub953_notify_ops; + + ret = v4l2_async_nf_register(&priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void ub953_v4l2_notifier_unregister(struct ub953_data *priv) +{ + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +} + +/* + * Probing + */ + +static int ub953_i2c_master_init(struct ub953_data *priv) +{ + /* i2c fast mode */ + u32 ref = 26250000; + u32 scl_high = 915; /* ns */ + u32 scl_low = 1641; /* ns */ + int ret; + + scl_high = div64_u64((u64)scl_high * ref, 1000000000) - 5; + scl_low = div64_u64((u64)scl_low * ref, 1000000000) - 5; + + ret = ub953_write(priv, UB953_REG_SCL_HIGH_TIME, scl_high, NULL); + if (ret) + return ret; + + ret = ub953_write(priv, UB953_REG_SCL_LOW_TIME, scl_low, NULL); + if (ret) + return ret; + + return 0; +} + +static u64 ub953_get_fc_rate(struct ub953_data *priv) +{ + switch (priv->mode) { + case UB953_MODE_SYNC: + if (priv->hw_data->is_ub971) + return priv->plat_data->bc_rate * 160ull; + else + return priv->plat_data->bc_rate / 2 * 160ull; + + case UB953_MODE_NONSYNC_EXT: + /* CLKIN_DIV = 1 always */ + return clk_get_rate(priv->clkin) * 80ull; + + default: + /* Not supported */ + return 0; + } +} + +static unsigned long ub953_calc_clkout_ub953(struct ub953_data *priv, + unsigned long target, u64 fc, + u8 *hs_div, u8 *m, u8 *n) +{ + /* + * We always use 4 as a pre-divider (HS_CLK_DIV = 2). + * + * According to the datasheet: + * - "HS_CLK_DIV typically should be set to either 16, 8, or 4 (default)." + * - "if it is not possible to have an integer ratio of N/M, it is best to + * select a smaller value for HS_CLK_DIV. + * + * For above reasons the default HS_CLK_DIV seems the best in the average + * case. Use always that value to keep the code simple. + */ + static const unsigned long hs_clk_div = 4; + + u64 fc_divided; + unsigned long mul, div; + unsigned long res; + + /* clkout = fc / hs_clk_div * m / n */ + + fc_divided = div_u64(fc, hs_clk_div); + + rational_best_approximation(target, fc_divided, (1 << 5) - 1, + (1 << 8) - 1, &mul, &div); + + res = div_u64(fc_divided * mul, div); + + *hs_div = hs_clk_div; + *m = mul; + *n = div; + + return res; +} + +static unsigned long ub953_calc_clkout_ub971(struct ub953_data *priv, + unsigned long target, u64 fc, + u8 *m, u8 *n) +{ + u64 fc_divided; + unsigned long mul, div; + unsigned long res; + + /* clkout = fc * m / (8 * n) */ + + fc_divided = div_u64(fc, 8); + + rational_best_approximation(target, fc_divided, (1 << 5) - 1, + (1 << 8) - 1, &mul, &div); + + res = div_u64(fc_divided * mul, div); + + *m = mul; + *n = div; + + return res; +} + +static void ub953_calc_clkout_params(struct ub953_data *priv, + unsigned long target_rate, + struct ub953_clkout_data *clkout_data) +{ + struct device *dev = &priv->client->dev; + unsigned long clkout_rate; + u64 fc_rate; + + fc_rate = ub953_get_fc_rate(priv); + + if (priv->hw_data->is_ub971) { + u8 m, n; + + clkout_rate = ub953_calc_clkout_ub971(priv, target_rate, + fc_rate, &m, &n); + + clkout_data->m = m; + clkout_data->n = n; + + dev_dbg(dev, "%s %llu * %u / (8 * %u) = %lu (requested %lu)", + __func__, fc_rate, m, n, clkout_rate, target_rate); + } else { + u8 hs_div, m, n; + + clkout_rate = ub953_calc_clkout_ub953(priv, target_rate, + fc_rate, &hs_div, &m, &n); + + clkout_data->hs_div = hs_div; + clkout_data->m = m; + clkout_data->n = n; + + dev_dbg(dev, "%s %llu / %u * %u / %u = %lu (requested %lu)", + __func__, fc_rate, hs_div, m, n, clkout_rate, + target_rate); + } + + clkout_data->rate = clkout_rate; +} + +static int ub953_write_clkout_regs(struct ub953_data *priv, + const struct ub953_clkout_data *clkout_data) +{ + u8 clkout_ctrl0, clkout_ctrl1; + int ret; + + if (priv->hw_data->is_ub971) + clkout_ctrl0 = clkout_data->m; + else + clkout_ctrl0 = (__ffs(clkout_data->hs_div) << 5) | + clkout_data->m; + + clkout_ctrl1 = clkout_data->n; + + ret = ub953_write(priv, UB953_REG_CLKOUT_CTRL0, clkout_ctrl0, NULL); + if (ret) + return ret; + + ret = ub953_write(priv, UB953_REG_CLKOUT_CTRL1, clkout_ctrl1, NULL); + if (ret) + return ret; + + return 0; +} + +static unsigned long ub953_clkout_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ub953_data *priv = container_of(hw, struct ub953_data, clkout_clk_hw); + struct device *dev = &priv->client->dev; + u8 ctrl0, ctrl1; + u32 mul, div; + u64 fc_rate; + u32 hs_clk_div; + u64 rate; + int ret; + + ret = ub953_read(priv, UB953_REG_CLKOUT_CTRL0, &ctrl0, NULL); + if (ret) { + dev_err(dev, "Failed to read CLKOUT_CTRL0: %d\n", ret); + return 0; + } + + ret = ub953_read(priv, UB953_REG_CLKOUT_CTRL1, &ctrl1, NULL); + if (ret) { + dev_err(dev, "Failed to read CLKOUT_CTRL1: %d\n", ret); + return 0; + } + + fc_rate = ub953_get_fc_rate(priv); + + if (priv->hw_data->is_ub971) { + mul = ctrl0 & 0x1f; + div = ctrl1; + + if (div == 0) + return 0; + + rate = div_u64(fc_rate * mul, 8 * div); + + dev_dbg(dev, "clkout: fc rate %llu, mul %u, div %u = %llu\n", + fc_rate, mul, div, rate); + } else { + mul = ctrl0 & 0x1f; + hs_clk_div = 1 << (ctrl0 >> 5); + div = ctrl1; + + if (div == 0) + return 0; + + rate = div_u64(div_u64(fc_rate, hs_clk_div) * mul, div); + + dev_dbg(dev, + "clkout: fc rate %llu, hs_clk_div %u, mul %u, div %u = %llu\n", + fc_rate, hs_clk_div, mul, div, rate); + } + + return rate; +} + +static int ub953_clkout_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct ub953_data *priv = container_of(hw, struct ub953_data, clkout_clk_hw); + struct ub953_clkout_data clkout_data; + + ub953_calc_clkout_params(priv, req->rate, &clkout_data); + + req->rate = clkout_data.rate; + + return 0; +} + +static int ub953_clkout_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ub953_data *priv = container_of(hw, struct ub953_data, clkout_clk_hw); + struct ub953_clkout_data clkout_data; + + ub953_calc_clkout_params(priv, rate, &clkout_data); + + dev_dbg(&priv->client->dev, "%s %lu (requested %lu)\n", __func__, + clkout_data.rate, rate); + + return ub953_write_clkout_regs(priv, &clkout_data); +} + +static const struct clk_ops ub953_clkout_ops = { + .recalc_rate = ub953_clkout_recalc_rate, + .determine_rate = ub953_clkout_determine_rate, + .set_rate = ub953_clkout_set_rate, +}; + +static int ub953_register_clkout(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + const struct clk_init_data init = { + .name = kasprintf(GFP_KERNEL, "ds90%s.%s.clk_out", + priv->hw_data->model, dev_name(dev)), + .ops = &ub953_clkout_ops, + }; + struct ub953_clkout_data clkout_data; + int ret; + + if (!init.name) + return -ENOMEM; + + /* Initialize clkout to 25MHz by default */ + ub953_calc_clkout_params(priv, UB953_DEFAULT_CLKOUT_RATE, &clkout_data); + ret = ub953_write_clkout_regs(priv, &clkout_data); + if (ret) + return ret; + + priv->clkout_clk_hw.init = &init; + + ret = devm_clk_hw_register(dev, &priv->clkout_clk_hw); + kfree(init.name); + if (ret) + return dev_err_probe(dev, ret, "Cannot register clock HW\n"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &priv->clkout_clk_hw); + if (ret) + return dev_err_probe(dev, ret, + "Cannot add OF clock provider\n"); + + return 0; +} + +static int ub953_add_i2c_adapter(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + struct i2c_atr_adap_desc desc = { }; + struct fwnode_handle *i2c_handle; + int ret; + + i2c_handle = device_get_named_child_node(dev, "i2c"); + if (!i2c_handle) + return 0; + + desc.chan_id = priv->plat_data->port; + desc.parent = dev; + desc.bus_handle = i2c_handle; + desc.num_aliases = 0; + + ret = i2c_atr_add_adapter(priv->plat_data->atr, &desc); + + fwnode_handle_put(i2c_handle); + + if (ret) + return ret; + + return 0; +} + +static const struct regmap_config ub953_regmap_config = { + .name = "ds90ub953", + .reg_bits = 8, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_DEFAULT, + .val_format_endian = REGMAP_ENDIAN_DEFAULT, +}; + +static int ub953_parse_dt(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct fwnode_handle *ep_fwnode; + unsigned char nlanes; + int ret; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + UB953_PAD_SINK, 0, 0); + if (!ep_fwnode) + return dev_err_probe(dev, -ENOENT, "no endpoint found\n"); + + ret = v4l2_fwnode_endpoint_parse(ep_fwnode, &vep); + + fwnode_handle_put(ep_fwnode); + + if (ret) + return dev_err_probe(dev, ret, + "failed to parse sink endpoint data\n"); + + nlanes = vep.bus.mipi_csi2.num_data_lanes; + if (nlanes != 1 && nlanes != 2 && nlanes != 4) + return dev_err_probe(dev, -EINVAL, + "bad number of data-lanes: %u\n", nlanes); + + priv->num_data_lanes = nlanes; + + priv->non_continous_clk = vep.bus.mipi_csi2.flags & + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK; + + return 0; +} + +static int ub953_hw_init(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + bool mode_override; + int ret; + u8 v; + + ret = ub953_read(priv, UB953_REG_MODE_SEL, &v, NULL); + if (ret) + return ret; + + if (!(v & UB953_REG_MODE_SEL_MODE_DONE)) + return dev_err_probe(dev, -EIO, "Mode value not stabilized\n"); + + mode_override = v & UB953_REG_MODE_SEL_MODE_OVERRIDE; + + switch (v & UB953_REG_MODE_SEL_MODE_MASK) { + case 0: + priv->mode = UB953_MODE_SYNC; + break; + case 2: + priv->mode = UB953_MODE_NONSYNC_EXT; + break; + case 3: + priv->mode = UB953_MODE_NONSYNC_INT; + break; + case 5: + priv->mode = UB953_MODE_DVP; + break; + default: + return dev_err_probe(dev, -EIO, + "Invalid mode in mode register\n"); + } + + dev_dbg(dev, "mode from %s: %#x\n", mode_override ? "reg" : "strap", + priv->mode); + + if (priv->mode != UB953_MODE_SYNC && + priv->mode != UB953_MODE_NONSYNC_EXT) + return dev_err_probe(dev, -ENODEV, + "Unsupported mode selected: %u\n", + priv->mode); + + if (priv->mode == UB953_MODE_NONSYNC_EXT && !priv->clkin) + return dev_err_probe(dev, -EINVAL, + "clkin required for non-sync ext mode\n"); + + ret = ub953_read(priv, UB953_REG_REV_MASK_ID, &v, NULL); + if (ret) + return dev_err_probe(dev, ret, "Failed to read revision"); + + dev_info(dev, "Found %s rev/mask %#04x\n", priv->hw_data->model, v); + + ret = ub953_read(priv, UB953_REG_GENERAL_CFG, &v, NULL); + if (ret) + return ret; + + dev_dbg(dev, "i2c strap setting %s V\n", + (v & UB953_REG_GENERAL_CFG_I2C_STRAP_MODE) ? "1.8" : "3.3"); + + ret = ub953_i2c_master_init(priv); + if (ret) + return dev_err_probe(dev, ret, "i2c init failed\n"); + + v = 0; + v |= priv->non_continous_clk ? 0 : UB953_REG_GENERAL_CFG_CONT_CLK; + v |= (priv->num_data_lanes - 1) << + UB953_REG_GENERAL_CFG_CSI_LANE_SEL_SHIFT; + v |= UB953_REG_GENERAL_CFG_CRC_TX_GEN_ENABLE; + + ret = ub953_write(priv, UB953_REG_GENERAL_CFG, v, NULL); + if (ret) + return ret; + + v = 1U << UB953_REG_I2C_CONTROL2_SDA_OUTPUT_SETUP_SHIFT; + v |= UB953_REG_I2C_CONTROL2_BUS_SPEEDUP; + + ret = ub953_write(priv, UB953_REG_I2C_CONTROL2, v, NULL); + + return ret; +} + +static int ub953_subdev_init(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + int ret; + + v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub953_subdev_ops); + priv->sd.internal_ops = &ub953_internal_ops; + + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_STREAMS; + priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops = &ub953_entity_ops; + + priv->pads[0].flags = MEDIA_PAD_FL_SINK; + priv->pads[1].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads); + if (ret) + return dev_err_probe(dev, ret, "Failed to init pads\n"); + + ret = v4l2_subdev_init_finalize(&priv->sd); + if (ret) + goto err_entity_cleanup; + + ret = ub953_v4l2_notifier_register(priv); + if (ret) { + dev_err_probe(dev, ret, + "v4l2 subdev notifier register failed\n"); + goto err_free_state; + } + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) { + dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n"); + goto err_unreg_notif; + } + + return 0; + +err_unreg_notif: + ub953_v4l2_notifier_unregister(priv); +err_free_state: + v4l2_subdev_cleanup(&priv->sd); +err_entity_cleanup: + media_entity_cleanup(&priv->sd.entity); + + return ret; +} + +static void ub953_subdev_uninit(struct ub953_data *priv) +{ + v4l2_async_unregister_subdev(&priv->sd); + ub953_v4l2_notifier_unregister(priv); + v4l2_subdev_cleanup(&priv->sd); + media_entity_cleanup(&priv->sd.entity); +} + +static int ub953_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ub953_data *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + + priv->hw_data = device_get_match_data(dev); + + priv->plat_data = dev_get_platdata(&client->dev); + if (!priv->plat_data) + return dev_err_probe(dev, -ENODEV, "Platform data missing\n"); + + mutex_init(&priv->reg_lock); + + /* + * Initialize to invalid values so that the first reg writes will + * configure the target. + */ + priv->current_indirect_target = 0xff; + + priv->regmap = devm_regmap_init_i2c(client, &ub953_regmap_config); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err_probe(dev, ret, "Failed to init regmap\n"); + goto err_mutex_destroy; + } + + priv->clkin = devm_clk_get_optional(dev, "clkin"); + if (IS_ERR(priv->clkin)) { + ret = PTR_ERR(priv->clkin); + dev_err_probe(dev, ret, "failed to parse 'clkin'\n"); + goto err_mutex_destroy; + } + + ret = ub953_parse_dt(priv); + if (ret) + goto err_mutex_destroy; + + ret = ub953_hw_init(priv); + if (ret) + goto err_mutex_destroy; + + ret = ub953_gpiochip_probe(priv); + if (ret) { + dev_err_probe(dev, ret, "Failed to init gpiochip\n"); + goto err_mutex_destroy; + } + + ret = ub953_register_clkout(priv); + if (ret) { + dev_err_probe(dev, ret, "Failed to register clkout\n"); + goto err_gpiochip_remove; + } + + ret = ub953_subdev_init(priv); + if (ret) + goto err_gpiochip_remove; + + ret = ub953_add_i2c_adapter(priv); + if (ret) { + dev_err_probe(dev, ret, "failed to add remote i2c adapter\n"); + goto err_subdev_uninit; + } + + return 0; + +err_subdev_uninit: + ub953_subdev_uninit(priv); +err_gpiochip_remove: + ub953_gpiochip_remove(priv); +err_mutex_destroy: + mutex_destroy(&priv->reg_lock); + + return ret; +} + +static void ub953_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ub953_data *priv = sd_to_ub953(sd); + + i2c_atr_del_adapter(priv->plat_data->atr, priv->plat_data->port); + + ub953_subdev_uninit(priv); + + ub953_gpiochip_remove(priv); + mutex_destroy(&priv->reg_lock); +} + +static const struct ub953_hw_data ds90ub953_hw = { + .model = "ub953", +}; + +static const struct ub953_hw_data ds90ub971_hw = { + .model = "ub971", + .is_ub971 = true, +}; + +static const struct i2c_device_id ub953_id[] = { + { .name = "ds90ub953-q1", .driver_data = (kernel_ulong_t)&ds90ub953_hw }, + { .name = "ds90ub971-q1", .driver_data = (kernel_ulong_t)&ds90ub971_hw }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ub953_id); + +static const struct of_device_id ub953_dt_ids[] = { + { .compatible = "ti,ds90ub953-q1", .data = &ds90ub953_hw }, + { .compatible = "ti,ds90ub971-q1", .data = &ds90ub971_hw }, + {} +}; +MODULE_DEVICE_TABLE(of, ub953_dt_ids); + +static struct i2c_driver ds90ub953_driver = { + .probe = ub953_probe, + .remove = ub953_remove, + .id_table = ub953_id, + .driver = { + .name = "ds90ub953", + .of_match_table = ub953_dt_ids, + }, +}; +module_i2c_driver(ds90ub953_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Texas Instruments FPD-Link III/IV CSI-2 Serializers Driver"); +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_AUTHOR("Tomi Valkeinen "); +MODULE_IMPORT_NS("I2C_ATR"); diff --git a/drivers/media/i2c/ds90ub953.h b/drivers/media/i2c/ds90ub953.h new file mode 100644 index 000000000..97a6b3af3 --- /dev/null +++ b/drivers/media/i2c/ds90ub953.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __MEDIA_I2C_DS90UB953_H__ +#define __MEDIA_I2C_DS90UB953_H__ + +#include + +#define UB953_REG_RESET_CTL 0x01 +#define UB953_REG_RESET_CTL_DIGITAL_RESET_1 BIT(1) +#define UB953_REG_RESET_CTL_DIGITAL_RESET_0 BIT(0) + +#define UB953_REG_GENERAL_CFG 0x02 +#define UB953_REG_GENERAL_CFG_CONT_CLK BIT(6) +#define UB953_REG_GENERAL_CFG_CSI_LANE_SEL_SHIFT 4 +#define UB953_REG_GENERAL_CFG_CSI_LANE_SEL_MASK GENMASK(5, 4) +#define UB953_REG_GENERAL_CFG_CRC_TX_GEN_ENABLE BIT(1) +#define UB953_REG_GENERAL_CFG_I2C_STRAP_MODE BIT(0) + +#define UB953_REG_MODE_SEL 0x03 +#define UB953_REG_MODE_SEL_MODE_DONE BIT(3) +#define UB953_REG_MODE_SEL_MODE_OVERRIDE BIT(4) +#define UB953_REG_MODE_SEL_MODE_MASK GENMASK(2, 0) + +#define UB953_REG_CLKOUT_CTRL0 0x06 +#define UB953_REG_CLKOUT_CTRL1 0x07 + +#define UB953_REG_I2C_CONTROL2 0x0a +#define UB953_REG_I2C_CONTROL2_SDA_OUTPUT_SETUP_SHIFT 4 +#define UB953_REG_I2C_CONTROL2_BUS_SPEEDUP BIT(1) + +#define UB953_REG_SCL_HIGH_TIME 0x0b +#define UB953_REG_SCL_LOW_TIME 0x0c + +#define UB953_REG_LOCAL_GPIO_DATA 0x0d +#define UB953_REG_LOCAL_GPIO_DATA_GPIO_RMTEN(n) BIT(4 + (n)) +#define UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(n) BIT(0 + (n)) + +#define UB953_REG_GPIO_INPUT_CTRL 0x0e +#define UB953_REG_GPIO_INPUT_CTRL_OUT_EN(n) BIT(4 + (n)) +#define UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(n) BIT(0 + (n)) + +#define UB953_REG_BC_CTRL 0x49 +#define UB953_REG_BC_CTRL_CRC_ERR_CLR BIT(3) + +#define UB953_REG_REV_MASK_ID 0x50 +#define UB953_REG_GENERAL_STATUS 0x52 + +#define UB953_REG_GPIO_PIN_STS 0x53 +#define UB953_REG_GPIO_PIN_STS_GPIO_STS(n) BIT(0 + (n)) + +#define UB953_REG_BIST_ERR_CNT 0x54 +#define UB953_REG_CRC_ERR_CNT1 0x55 +#define UB953_REG_CRC_ERR_CNT2 0x56 + +#define UB953_REG_CSI_ERR_CNT 0x5c +#define UB953_REG_CSI_ERR_STATUS 0x5d +#define UB953_REG_CSI_ERR_DLANE01 0x5e +#define UB953_REG_CSI_ERR_DLANE23 0x5f +#define UB953_REG_CSI_ERR_CLK_LANE 0x60 +#define UB953_REG_CSI_PKT_HDR_VC_ID 0x61 +#define UB953_REG_PKT_HDR_WC_LSB 0x62 +#define UB953_REG_PKT_HDR_WC_MSB 0x63 +#define UB953_REG_CSI_ECC 0x64 + +#define UB953_REG_IND_ACC_CTL 0xb0 +#define UB953_REG_IND_ACC_ADDR 0xb1 +#define UB953_REG_IND_ACC_DATA 0xb2 + +#define UB953_REG_FPD3_RX_ID(n) (0xf0 + (n)) +#define UB953_REG_FPD3_RX_ID_LEN 6 + +/* Indirect register blocks */ +#define UB953_IND_TARGET_PAT_GEN 0x00 +#define UB953_IND_TARGET_ANALOG 0x01 +#define UB953_IND_TARGET_DIE_ID 0x02 + +#define UB953_IND_PGEN_CTL 0x01 +#define UB953_IND_PGEN_CTL_PGEN_ENABLE BIT(0) +#define UB953_IND_PGEN_CFG 0x02 +#define UB953_IND_PGEN_CSI_DI 0x03 +#define UB953_IND_PGEN_LINE_SIZE1 0x04 +#define UB953_IND_PGEN_LINE_SIZE0 0x05 +#define UB953_IND_PGEN_BAR_SIZE1 0x06 +#define UB953_IND_PGEN_BAR_SIZE0 0x07 +#define UB953_IND_PGEN_ACT_LPF1 0x08 +#define UB953_IND_PGEN_ACT_LPF0 0x09 +#define UB953_IND_PGEN_TOT_LPF1 0x0a +#define UB953_IND_PGEN_TOT_LPF0 0x0b +#define UB953_IND_PGEN_LINE_PD1 0x0c +#define UB953_IND_PGEN_LINE_PD0 0x0d +#define UB953_IND_PGEN_VBP 0x0e +#define UB953_IND_PGEN_VFP 0x0f +#define UB953_IND_PGEN_COLOR(n) (0x10 + (n)) /* n <= 15 */ + +#define UB953_IND_ANA_TEMP_DYNAMIC_CFG 0x4b +#define UB953_IND_ANA_TEMP_DYNAMIC_CFG_OV BIT(5) +#define UB953_IND_ANA_TEMP_STATIC_CFG 0x4c +#define UB953_IND_ANA_TEMP_STATIC_CFG_MASK GENMASK(6, 4) + +/* UB971 Registers */ + +#define UB971_ENH_BC_CHK 0x4b + +#endif /* __MEDIA_I2C_DS90UB953_H__ */ diff --git a/drivers/media/i2c/ds90ub960.c b/drivers/media/i2c/ds90ub960.c new file mode 100644 index 000000000..15a9797b4 --- /dev/null +++ b/drivers/media/i2c/ds90ub960.c @@ -0,0 +1,5299 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Texas Instruments DS90UB960-Q1 video deserializer + * + * Copyright (c) 2019 Luca Ceresoli + * Copyright (c) 2023 Tomi Valkeinen + */ + +/* + * (Possible) TODOs: + * + * - PM for serializer and remote peripherals. We need to manage: + * - VPOC + * - Power domain? Regulator? Somehow any remote device should be able to + * cause the VPOC to be turned on. + * - Link between the deserializer and the serializer + * - Related to VPOC management. We probably always want to turn on the VPOC + * and then enable the link. + * - Serializer's services: i2c, gpios, power + * - The serializer needs to resume before the remote peripherals can + * e.g. use the i2c. + * - How to handle gpios? Reserving a gpio essentially keeps the provider + * (serializer) always powered on. + * - Do we need a new bus for the FPD-Link? At the moment the serializers + * are children of the same i2c-adapter where the deserializer resides. + * - i2c-atr could be made embeddable instead of allocatable. + */ + +#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 "ds90ub953.h" + +#define MHZ(v) ((u32)((v) * HZ_PER_MHZ)) + +/* + * If this is defined, the i2c addresses from UB960_DEBUG_I2C_RX_ID to + * UB960_DEBUG_I2C_RX_ID + 3 can be used to access the paged RX port registers + * directly. + * + * Only for debug purposes. + */ +/* #define UB960_DEBUG_I2C_RX_ID 0x40 */ + +#define UB960_POLL_TIME_MS 500 + +#define UB960_MAX_RX_NPORTS 4 +#define UB960_MAX_TX_NPORTS 2 +#define UB960_MAX_NPORTS (UB960_MAX_RX_NPORTS + UB960_MAX_TX_NPORTS) + +#define UB960_MAX_PORT_ALIASES 8 + +#define UB960_NUM_BC_GPIOS 4 + +/* + * Register map + * + * 0x00-0x32 Shared (UB960_SR) + * 0x33-0x3a CSI-2 TX (per-port paged on DS90UB960, shared on 954) (UB960_TR) + * 0x4c Shared (UB960_SR) + * 0x4d-0x7f FPD-Link RX, per-port paged (UB960_RR) + * 0xb0-0xbf Shared (UB960_SR) + * 0xd0-0xdf FPD-Link RX, per-port paged (UB960_RR) + * 0xf0-0xf5 Shared (UB960_SR) + * 0xf8-0xfb Shared (UB960_SR) + * All others Reserved + * + * Register prefixes: + * UB960_SR_* = Shared register + * UB960_RR_* = FPD-Link RX, per-port paged register + * UB960_TR_* = CSI-2 TX, per-port paged register + * UB960_XR_* = Reserved register + * UB960_IR_* = Indirect register + */ + +#define UB960_SR_I2C_DEV_ID 0x00 +#define UB960_SR_RESET 0x01 +#define UB960_SR_RESET_DIGITAL_RESET1 BIT(1) +#define UB960_SR_RESET_DIGITAL_RESET0 BIT(0) +#define UB960_SR_RESET_GPIO_LOCK_RELEASE BIT(5) + +#define UB960_SR_GEN_CONFIG 0x02 +#define UB960_SR_REV_MASK 0x03 +#define UB960_SR_DEVICE_STS 0x04 +#define UB960_SR_PAR_ERR_THOLD_HI 0x05 +#define UB960_SR_PAR_ERR_THOLD_LO 0x06 +#define UB960_SR_BCC_WDOG_CTL 0x07 +#define UB960_SR_I2C_CTL1 0x08 +#define UB960_SR_I2C_CTL2 0x09 +#define UB960_SR_SCL_HIGH_TIME 0x0a +#define UB960_SR_SCL_LOW_TIME 0x0b +#define UB960_SR_RX_PORT_CTL 0x0c +#define UB960_SR_IO_CTL 0x0d +#define UB960_SR_GPIO_PIN_STS 0x0e +#define UB960_SR_GPIO_INPUT_CTL 0x0f +#define UB960_SR_GPIO_PIN_CTL(n) (0x10 + (n)) /* n < UB960_NUM_GPIOS */ +#define UB960_SR_GPIO_PIN_CTL_GPIO_OUT_SEL 5 +#define UB960_SR_GPIO_PIN_CTL_GPIO_OUT_SRC_SHIFT 2 +#define UB960_SR_GPIO_PIN_CTL_GPIO_OUT_EN BIT(0) + +#define UB960_SR_FS_CTL 0x18 +#define UB960_SR_FS_HIGH_TIME_1 0x19 +#define UB960_SR_FS_HIGH_TIME_0 0x1a +#define UB960_SR_FS_LOW_TIME_1 0x1b +#define UB960_SR_FS_LOW_TIME_0 0x1c +#define UB960_SR_MAX_FRM_HI 0x1d +#define UB960_SR_MAX_FRM_LO 0x1e +#define UB960_SR_CSI_PLL_CTL 0x1f + +#define UB960_SR_FWD_CTL1 0x20 +#define UB960_SR_FWD_CTL1_PORT_DIS(n) BIT((n) + 4) + +#define UB960_SR_FWD_CTL2 0x21 +#define UB960_SR_FWD_STS 0x22 + +#define UB960_SR_INTERRUPT_CTL 0x23 +#define UB960_SR_INTERRUPT_CTL_INT_EN BIT(7) +#define UB960_SR_INTERRUPT_CTL_IE_CSI_TX0 BIT(4) +#define UB960_SR_INTERRUPT_CTL_IE_RX(n) BIT((n)) /* rxport[n] IRQ */ + +#define UB960_SR_INTERRUPT_STS 0x24 +#define UB960_SR_INTERRUPT_STS_INT BIT(7) +#define UB960_SR_INTERRUPT_STS_IS_CSI_TX(n) BIT(4 + (n)) /* txport[n] IRQ */ +#define UB960_SR_INTERRUPT_STS_IS_RX(n) BIT((n)) /* rxport[n] IRQ */ + +#define UB960_SR_TS_CONFIG 0x25 +#define UB960_SR_TS_CONTROL 0x26 +#define UB960_SR_TS_LINE_HI 0x27 +#define UB960_SR_TS_LINE_LO 0x28 +#define UB960_SR_TS_STATUS 0x29 +#define UB960_SR_TIMESTAMP_P0_HI 0x2a +#define UB960_SR_TIMESTAMP_P0_LO 0x2b +#define UB960_SR_TIMESTAMP_P1_HI 0x2c +#define UB960_SR_TIMESTAMP_P1_LO 0x2d + +#define UB960_SR_CSI_PORT_SEL 0x32 + +#define UB960_TR_CSI_CTL 0x33 +#define UB960_TR_CSI_CTL_CSI_CAL_EN BIT(6) +#define UB960_TR_CSI_CTL_CSI_CONTS_CLOCK BIT(1) +#define UB960_TR_CSI_CTL_CSI_ENABLE BIT(0) + +#define UB960_TR_CSI_CTL2 0x34 +#define UB960_TR_CSI_STS 0x35 +#define UB960_TR_CSI_TX_ICR 0x36 + +#define UB960_TR_CSI_TX_ISR 0x37 +#define UB960_TR_CSI_TX_ISR_IS_CSI_SYNC_ERROR BIT(3) +#define UB960_TR_CSI_TX_ISR_IS_CSI_PASS_ERROR BIT(1) + +#define UB960_TR_CSI_TEST_CTL 0x38 +#define UB960_TR_CSI_TEST_PATT_HI 0x39 +#define UB960_TR_CSI_TEST_PATT_LO 0x3a + +#define UB960_XR_SFILTER_CFG 0x41 +#define UB960_XR_SFILTER_CFG_SFILTER_MAX_SHIFT 4 +#define UB960_XR_SFILTER_CFG_SFILTER_MIN_SHIFT 0 + +#define UB960_XR_AEQ_CTL1 0x42 +#define UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_FPD_CLK BIT(6) +#define UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_ENCODING BIT(5) +#define UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_PARITY BIT(4) +#define UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_MASK \ + (UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_FPD_CLK | \ + UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_ENCODING | \ + UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_PARITY) +#define UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN BIT(0) + +#define UB960_XR_AEQ_ERR_THOLD 0x43 + +#define UB960_RR_BCC_ERR_CTL 0x46 +#define UB960_RR_BCC_STATUS 0x47 +#define UB960_RR_BCC_STATUS_SEQ_ERROR BIT(5) +#define UB960_RR_BCC_STATUS_MASTER_ERR BIT(4) +#define UB960_RR_BCC_STATUS_MASTER_TO BIT(3) +#define UB960_RR_BCC_STATUS_SLAVE_ERR BIT(2) +#define UB960_RR_BCC_STATUS_SLAVE_TO BIT(1) +#define UB960_RR_BCC_STATUS_RESP_ERR BIT(0) +#define UB960_RR_BCC_STATUS_ERROR_MASK \ + (UB960_RR_BCC_STATUS_SEQ_ERROR | UB960_RR_BCC_STATUS_MASTER_ERR | \ + UB960_RR_BCC_STATUS_MASTER_TO | UB960_RR_BCC_STATUS_SLAVE_ERR | \ + UB960_RR_BCC_STATUS_SLAVE_TO | UB960_RR_BCC_STATUS_RESP_ERR) + +#define UB960_RR_FPD3_CAP 0x4a +#define UB960_RR_RAW_EMBED_DTYPE 0x4b +#define UB960_RR_RAW_EMBED_DTYPE_LINES_SHIFT 6 + +#define UB960_SR_FPD3_PORT_SEL 0x4c + +#define UB960_RR_RX_PORT_STS1 0x4d +#define UB960_RR_RX_PORT_STS1_BCC_CRC_ERROR BIT(5) +#define UB960_RR_RX_PORT_STS1_LOCK_STS_CHG BIT(4) +#define UB960_RR_RX_PORT_STS1_BCC_SEQ_ERROR BIT(3) +#define UB960_RR_RX_PORT_STS1_PARITY_ERROR BIT(2) +#define UB960_RR_RX_PORT_STS1_PORT_PASS BIT(1) +#define UB960_RR_RX_PORT_STS1_LOCK_STS BIT(0) +#define UB960_RR_RX_PORT_STS1_ERROR_MASK \ + (UB960_RR_RX_PORT_STS1_BCC_CRC_ERROR | \ + UB960_RR_RX_PORT_STS1_BCC_SEQ_ERROR | \ + UB960_RR_RX_PORT_STS1_PARITY_ERROR) + +#define UB960_RR_RX_PORT_STS2 0x4e +#define UB960_RR_RX_PORT_STS2_LINE_LEN_UNSTABLE BIT(7) +#define UB960_RR_RX_PORT_STS2_LINE_LEN_CHG BIT(6) +#define UB960_RR_RX_PORT_STS2_FPD3_ENCODE_ERROR BIT(5) +#define UB960_RR_RX_PORT_STS2_BUFFER_ERROR BIT(4) +#define UB960_RR_RX_PORT_STS2_CSI_ERROR BIT(3) +#define UB960_RR_RX_PORT_STS2_FREQ_STABLE BIT(2) +#define UB960_RR_RX_PORT_STS2_CABLE_FAULT BIT(1) +#define UB960_RR_RX_PORT_STS2_LINE_CNT_CHG BIT(0) +#define UB960_RR_RX_PORT_STS2_ERROR_MASK \ + UB960_RR_RX_PORT_STS2_BUFFER_ERROR + +#define UB960_RR_RX_FREQ_HIGH 0x4f +#define UB960_RR_RX_FREQ_LOW 0x50 +#define UB960_RR_SENSOR_STS_0 0x51 +#define UB960_RR_SENSOR_STS_1 0x52 +#define UB960_RR_SENSOR_STS_2 0x53 +#define UB960_RR_SENSOR_STS_3 0x54 +#define UB960_RR_RX_PAR_ERR_HI 0x55 +#define UB960_RR_RX_PAR_ERR_LO 0x56 +#define UB960_RR_BIST_ERR_COUNT 0x57 + +#define UB960_RR_BCC_CONFIG 0x58 +#define UB960_RR_BCC_CONFIG_BC_ALWAYS_ON BIT(4) +#define UB960_RR_BCC_CONFIG_AUTO_ACK_ALL BIT(5) +#define UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH BIT(6) +#define UB960_RR_BCC_CONFIG_BC_FREQ_SEL_MASK GENMASK(2, 0) + +#define UB960_RR_DATAPATH_CTL1 0x59 +#define UB960_RR_DATAPATH_CTL2 0x5a +#define UB960_RR_SER_ID 0x5b +#define UB960_RR_SER_ID_FREEZE_DEVICE_ID BIT(0) +#define UB960_RR_SER_ALIAS_ID 0x5c +#define UB960_RR_SER_ALIAS_ID_AUTO_ACK BIT(0) + +/* For these two register sets: n < UB960_MAX_PORT_ALIASES */ +#define UB960_RR_SLAVE_ID(n) (0x5d + (n)) +#define UB960_RR_SLAVE_ALIAS(n) (0x65 + (n)) + +#define UB960_RR_PORT_CONFIG 0x6d +#define UB960_RR_PORT_CONFIG_FPD3_MODE_MASK GENMASK(1, 0) + +#define UB960_RR_BC_GPIO_CTL(n) (0x6e + (n)) /* n < 2 */ +#define UB960_RR_RAW10_ID 0x70 +#define UB960_RR_RAW10_ID_VC_SHIFT 6 +#define UB960_RR_RAW10_ID_DT_SHIFT 0 + +#define UB960_RR_RAW12_ID 0x71 +#define UB960_RR_CSI_VC_MAP 0x72 +#define UB960_RR_CSI_VC_MAP_SHIFT(x) ((x) * 2) + +#define UB960_RR_LINE_COUNT_HI 0x73 +#define UB960_RR_LINE_COUNT_LO 0x74 +#define UB960_RR_LINE_LEN_1 0x75 +#define UB960_RR_LINE_LEN_0 0x76 +#define UB960_RR_FREQ_DET_CTL 0x77 +#define UB960_RR_MAILBOX_1 0x78 +#define UB960_RR_MAILBOX_2 0x79 + +#define UB960_RR_CSI_RX_STS 0x7a +#define UB960_RR_CSI_RX_STS_LENGTH_ERR BIT(3) +#define UB960_RR_CSI_RX_STS_CKSUM_ERR BIT(2) +#define UB960_RR_CSI_RX_STS_ECC2_ERR BIT(1) +#define UB960_RR_CSI_RX_STS_ECC1_ERR BIT(0) +#define UB960_RR_CSI_RX_STS_ERROR_MASK \ + (UB960_RR_CSI_RX_STS_LENGTH_ERR | UB960_RR_CSI_RX_STS_CKSUM_ERR | \ + UB960_RR_CSI_RX_STS_ECC2_ERR | UB960_RR_CSI_RX_STS_ECC1_ERR) + +#define UB960_RR_CSI_ERR_COUNTER 0x7b +#define UB960_RR_PORT_CONFIG2 0x7c +#define UB960_RR_PORT_CONFIG2_RAW10_8BIT_CTL_MASK GENMASK(7, 6) +#define UB960_RR_PORT_CONFIG2_RAW10_8BIT_CTL_SHIFT 6 + +#define UB960_RR_PORT_CONFIG2_LV_POL_LOW BIT(1) +#define UB960_RR_PORT_CONFIG2_FV_POL_LOW BIT(0) + +#define UB960_RR_PORT_PASS_CTL 0x7d +#define UB960_RR_SEN_INT_RISE_CTL 0x7e +#define UB960_RR_SEN_INT_FALL_CTL 0x7f + +#define UB960_SR_CSI_FRAME_COUNT_HI(n) (0x90 + 8 * (n)) +#define UB960_SR_CSI_FRAME_COUNT_LO(n) (0x91 + 8 * (n)) +#define UB960_SR_CSI_FRAME_ERR_COUNT_HI(n) (0x92 + 8 * (n)) +#define UB960_SR_CSI_FRAME_ERR_COUNT_LO(n) (0x93 + 8 * (n)) +#define UB960_SR_CSI_LINE_COUNT_HI(n) (0x94 + 8 * (n)) +#define UB960_SR_CSI_LINE_COUNT_LO(n) (0x95 + 8 * (n)) +#define UB960_SR_CSI_LINE_ERR_COUNT_HI(n) (0x96 + 8 * (n)) +#define UB960_SR_CSI_LINE_ERR_COUNT_LO(n) (0x97 + 8 * (n)) + +#define UB960_XR_REFCLK_FREQ 0xa5 /* UB960 */ + +#define UB960_SR_IND_ACC_CTL 0xb0 +#define UB960_SR_IND_ACC_CTL_IA_AUTO_INC BIT(1) + +#define UB960_SR_IND_ACC_ADDR 0xb1 +#define UB960_SR_IND_ACC_DATA 0xb2 +#define UB960_SR_BIST_CONTROL 0xb3 +#define UB960_SR_MODE_IDX_STS 0xb8 +#define UB960_SR_LINK_ERROR_COUNT 0xb9 +#define UB960_SR_FPD3_ENC_CTL 0xba +#define UB960_SR_FV_MIN_TIME 0xbc +#define UB960_SR_GPIO_PD_CTL 0xbe + +#define UB960_RR_PORT_DEBUG 0xd0 +#define UB960_RR_AEQ_CTL2 0xd2 +#define UB960_RR_AEQ_CTL2_SET_AEQ_FLOOR BIT(2) + +#define UB960_RR_AEQ_STATUS 0xd3 +#define UB960_RR_AEQ_STATUS_STATUS_2 GENMASK(5, 3) +#define UB960_RR_AEQ_STATUS_STATUS_1 GENMASK(2, 0) + +#define UB960_RR_AEQ_BYPASS 0xd4 +#define UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT 5 +#define UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK GENMASK(7, 5) +#define UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT 1 +#define UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK GENMASK(3, 1) +#define UB960_RR_AEQ_BYPASS_ENABLE BIT(0) + +#define UB960_RR_AEQ_MIN_MAX 0xd5 +#define UB960_RR_AEQ_MIN_MAX_AEQ_MAX_SHIFT 4 +#define UB960_RR_AEQ_MIN_MAX_AEQ_FLOOR_SHIFT 0 + +#define UB960_RR_SFILTER_STS_0 0xd6 +#define UB960_RR_SFILTER_STS_1 0xd7 +#define UB960_RR_PORT_ICR_HI 0xd8 +#define UB960_RR_PORT_ICR_LO 0xd9 +#define UB960_RR_PORT_ISR_HI 0xda +#define UB960_RR_PORT_ISR_LO 0xdb +#define UB960_RR_FC_GPIO_STS 0xdc +#define UB960_RR_FC_GPIO_ICR 0xdd +#define UB960_RR_SEN_INT_RISE_STS 0xde +#define UB960_RR_SEN_INT_FALL_STS 0xdf + + +#define UB960_SR_FPD3_RX_ID(n) (0xf0 + (n)) +#define UB960_SR_FPD3_RX_ID_LEN 6 + +#define UB960_SR_I2C_RX_ID(n) (0xf8 + (n)) + +/* Indirect register blocks */ +#define UB960_IND_TARGET_PAT_GEN 0x00 +#define UB960_IND_TARGET_RX_ANA(n) (0x01 + (n)) +#define UB960_IND_TARGET_CSI_ANA 0x07 + +/* UB960_IR_PGEN_*: Indirect Registers for Test Pattern Generator */ + +#define UB960_IR_PGEN_CTL 0x01 +#define UB960_IR_PGEN_CTL_PGEN_ENABLE BIT(0) + +#define UB960_IR_PGEN_CFG 0x02 +#define UB960_IR_PGEN_CSI_DI 0x03 +#define UB960_IR_PGEN_LINE_SIZE1 0x04 +#define UB960_IR_PGEN_LINE_SIZE0 0x05 +#define UB960_IR_PGEN_BAR_SIZE1 0x06 +#define UB960_IR_PGEN_BAR_SIZE0 0x07 +#define UB960_IR_PGEN_ACT_LPF1 0x08 +#define UB960_IR_PGEN_ACT_LPF0 0x09 +#define UB960_IR_PGEN_TOT_LPF1 0x0a +#define UB960_IR_PGEN_TOT_LPF0 0x0b +#define UB960_IR_PGEN_LINE_PD1 0x0c +#define UB960_IR_PGEN_LINE_PD0 0x0d +#define UB960_IR_PGEN_VBP 0x0e +#define UB960_IR_PGEN_VFP 0x0f +#define UB960_IR_PGEN_COLOR(n) (0x10 + (n)) /* n < 15 */ + +#define UB960_IR_RX_ANA_STROBE_SET_CLK 0x08 +#define UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY BIT(3) +#define UB960_IR_RX_ANA_STROBE_SET_CLK_DELAY_MASK GENMASK(2, 0) + +#define UB960_IR_RX_ANA_STROBE_SET_DATA 0x09 +#define UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY BIT(3) +#define UB960_IR_RX_ANA_STROBE_SET_DATA_DELAY_MASK GENMASK(2, 0) + +#define UB954_IR_RX_ANA_STROBE_SET_CLK_DATA 0x08 +#define UB954_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY BIT(3) +#define UB954_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY BIT(7) +#define UB954_IR_RX_ANA_STROBE_SET_CLK_DELAY_MASK GENMASK(2, 0) +#define UB954_IR_RX_ANA_STROBE_SET_DATA_DELAY_MASK GENMASK(4, 6) +#define UB954_IR_RX_ANA_STROBE_SET_DATA_DELAY_SHIFT 4 + +/* UB9702 Registers */ + +#define UB9702_SR_CSI_EXCLUSIVE_FWD2 0x3c +#define UB9702_SR_REFCLK_FREQ 0x3d +#define UB9702_RR_RX_CTL_1 0x80 +#define UB9702_RR_RX_CTL_2 0x87 +#define UB9702_RR_VC_ID_MAP(x) (0xa0 + (x)) +#define UB9702_SR_FPD_RATE_CFG 0xc2 +#define UB9702_SR_CSI_PLL_DIV 0xc9 +#define UB9702_RR_RX_SM_SEL_2 0xd4 +#define UB9702_RR_CHANNEL_MODE 0xe4 + +#define UB9702_IND_TARGET_SAR_ADC 0x0a + +#define UB9702_IR_RX_ANA_FPD_BC_CTL0 0x04 +#define UB9702_IR_RX_ANA_FPD_BC_CTL1 0x0d +#define UB9702_IR_RX_ANA_FPD_BC_CTL2 0x1b +#define UB9702_IR_RX_ANA_SYSTEM_INIT_REG0 0x21 +#define UB9702_IR_RX_ANA_AEQ_ALP_SEL6 0x27 +#define UB9702_IR_RX_ANA_AEQ_ALP_SEL7 0x28 +#define UB9702_IR_RX_ANA_AEQ_ALP_SEL10 0x2b +#define UB9702_IR_RX_ANA_AEQ_ALP_SEL11 0x2c +#define UB9702_IR_RX_ANA_EQ_ADAPT_CTRL 0x2e +#define UB9702_IR_RX_ANA_AEQ_CFG_1 0x34 +#define UB9702_IR_RX_ANA_AEQ_CFG_2 0x4d +#define UB9702_IR_RX_ANA_GAIN_CTRL_0 0x71 +#define UB9702_IR_RX_ANA_GAIN_CTRL_0 0x71 +#define UB9702_IR_RX_ANA_VGA_CTRL_SEL_1 0x72 +#define UB9702_IR_RX_ANA_VGA_CTRL_SEL_2 0x73 +#define UB9702_IR_RX_ANA_VGA_CTRL_SEL_3 0x74 +#define UB9702_IR_RX_ANA_VGA_CTRL_SEL_6 0x77 +#define UB9702_IR_RX_ANA_AEQ_CFG_3 0x79 +#define UB9702_IR_RX_ANA_AEQ_CFG_4 0x85 +#define UB9702_IR_RX_ANA_EQ_CTRL_SEL_15 0x87 +#define UB9702_IR_RX_ANA_EQ_CTRL_SEL_24 0x90 +#define UB9702_IR_RX_ANA_EQ_CTRL_SEL_38 0x9e +#define UB9702_IR_RX_ANA_FPD3_CDR_CTRL_SEL_5 0xa5 +#define UB9702_IR_RX_ANA_FPD3_AEQ_CTRL_SEL_1 0xa8 +#define UB9702_IR_RX_ANA_EQ_OVERRIDE_CTRL 0xf0 +#define UB9702_IR_RX_ANA_VGA_CTRL_SEL_8 0xf1 + +#define UB9702_IR_CSI_ANA_CSIPLL_REG_1 0x92 + +/* EQ related */ + +#define UB960_MIN_AEQ_STROBE_POS -7 +#define UB960_MAX_AEQ_STROBE_POS 7 + +#define UB960_MANUAL_STROBE_EXTRA_DELAY 6 + +#define UB960_MIN_MANUAL_STROBE_POS -(7 + UB960_MANUAL_STROBE_EXTRA_DELAY) +#define UB960_MAX_MANUAL_STROBE_POS (7 + UB960_MANUAL_STROBE_EXTRA_DELAY) +#define UB960_NUM_MANUAL_STROBE_POS (UB960_MAX_MANUAL_STROBE_POS - UB960_MIN_MANUAL_STROBE_POS + 1) + +#define UB960_MIN_EQ_LEVEL 0 +#define UB960_MAX_EQ_LEVEL 14 +#define UB960_NUM_EQ_LEVELS (UB960_MAX_EQ_LEVEL - UB960_MIN_EQ_LEVEL + 1) + +enum chip_type { + UB954, + UB960, + UB9702, +}; + +enum chip_family { + FAMILY_FPD3, + FAMILY_FPD4, +}; + +struct ub960_hw_data { + const char *model; + enum chip_type chip_type; + enum chip_family chip_family; + u8 num_rxports; + u8 num_txports; +}; + +enum ub960_rxport_mode { + RXPORT_MODE_RAW10 = 0, + RXPORT_MODE_RAW12_HF = 1, + RXPORT_MODE_RAW12_LF = 2, + RXPORT_MODE_CSI2_SYNC = 3, + RXPORT_MODE_CSI2_NONSYNC = 4, + RXPORT_MODE_LAST = RXPORT_MODE_CSI2_NONSYNC, +}; + +enum ub960_rxport_cdr { + RXPORT_CDR_FPD3 = 0, + RXPORT_CDR_FPD4 = 1, + RXPORT_CDR_LAST = RXPORT_CDR_FPD4, +}; + +struct ub960_rxport { + struct ub960_data *priv; + u8 nport; /* RX port number, and index in priv->rxport[] */ + + struct { + struct v4l2_subdev *sd; + u16 pad; + struct fwnode_handle *ep_fwnode; + } source; + + /* Serializer */ + struct { + struct fwnode_handle *fwnode; + struct i2c_client *client; + unsigned short alias; /* I2C alias (lower 7 bits) */ + short addr; /* Local I2C address (lower 7 bits) */ + struct ds90ub9xx_platform_data pdata; + struct regmap *regmap; + } ser; + + enum ub960_rxport_mode rx_mode; + enum ub960_rxport_cdr cdr_mode; + + u8 lv_fv_pol; /* LV and FV polarities */ + + struct regulator *vpoc; + + /* EQ settings */ + struct { + bool manual_eq; + + s8 strobe_pos; + + union { + struct { + u8 eq_level_min; + u8 eq_level_max; + } aeq; + + struct { + u8 eq_level; + } manual; + }; + } eq; + + /* lock for aliased_addrs and associated registers */ + struct mutex aliased_addrs_lock; + u16 aliased_addrs[UB960_MAX_PORT_ALIASES]; +}; + +struct ub960_asd { + struct v4l2_async_connection base; + struct ub960_rxport *rxport; +}; + +static inline struct ub960_asd *to_ub960_asd(struct v4l2_async_connection *asd) +{ + return container_of(asd, struct ub960_asd, base); +} + +struct ub960_txport { + struct ub960_data *priv; + u8 nport; /* TX port number, and index in priv->txport[] */ + + u32 num_data_lanes; + bool non_continous_clk; +}; + +struct ub960_data { + const struct ub960_hw_data *hw_data; + struct i2c_client *client; /* for shared local registers */ + struct regmap *regmap; + + /* lock for register access */ + struct mutex reg_lock; + + struct clk *refclk; + + struct regulator *vddio; + + struct gpio_desc *pd_gpio; + struct delayed_work poll_work; + struct ub960_rxport *rxports[UB960_MAX_RX_NPORTS]; + struct ub960_txport *txports[UB960_MAX_TX_NPORTS]; + + struct v4l2_subdev sd; + struct media_pad pads[UB960_MAX_NPORTS]; + + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_async_notifier notifier; + + u32 tx_data_rate; /* Nominal data rate (Gb/s) */ + s64 tx_link_freq[1]; + + struct i2c_atr *atr; + + struct { + u8 rxport; + u8 txport; + u8 indirect_target; + } reg_current; + + bool streaming; + + u8 stored_fwd_ctl; + + u64 stream_enable_mask[UB960_MAX_NPORTS]; + + /* These are common to all ports */ + struct { + bool manual; + + s8 min; + s8 max; + } strobe; +}; + +static inline struct ub960_data *sd_to_ub960(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ub960_data, sd); +} + +static inline bool ub960_pad_is_sink(struct ub960_data *priv, u32 pad) +{ + return pad < priv->hw_data->num_rxports; +} + +static inline bool ub960_pad_is_source(struct ub960_data *priv, u32 pad) +{ + return pad >= priv->hw_data->num_rxports; +} + +static inline unsigned int ub960_pad_to_port(struct ub960_data *priv, u32 pad) +{ + if (ub960_pad_is_sink(priv, pad)) + return pad; + else + return pad - priv->hw_data->num_rxports; +} + +struct ub960_format_info { + u32 code; + u32 bpp; + u8 datatype; + bool meta; +}; + +static const struct ub960_format_info ub960_formats[] = { + { .code = MEDIA_BUS_FMT_RGB888_1X24, .bpp = 24, .datatype = MIPI_CSI2_DT_RGB888, }, + + { .code = MEDIA_BUS_FMT_YUYV8_1X16, .bpp = 16, .datatype = MIPI_CSI2_DT_YUV422_8B, }, + { .code = MEDIA_BUS_FMT_UYVY8_1X16, .bpp = 16, .datatype = MIPI_CSI2_DT_YUV422_8B, }, + { .code = MEDIA_BUS_FMT_VYUY8_1X16, .bpp = 16, .datatype = MIPI_CSI2_DT_YUV422_8B, }, + { .code = MEDIA_BUS_FMT_YVYU8_1X16, .bpp = 16, .datatype = MIPI_CSI2_DT_YUV422_8B, }, + + { .code = MEDIA_BUS_FMT_SBGGR8_1X8, .bpp = 8, .datatype = MIPI_CSI2_DT_RAW8, }, + { .code = MEDIA_BUS_FMT_SGBRG8_1X8, .bpp = 8, .datatype = MIPI_CSI2_DT_RAW8, }, + { .code = MEDIA_BUS_FMT_SGRBG8_1X8, .bpp = 8, .datatype = MIPI_CSI2_DT_RAW8, }, + { .code = MEDIA_BUS_FMT_SRGGB8_1X8, .bpp = 8, .datatype = MIPI_CSI2_DT_RAW8, }, + + { .code = MEDIA_BUS_FMT_SBGGR10_1X10, .bpp = 10, .datatype = MIPI_CSI2_DT_RAW10, }, + { .code = MEDIA_BUS_FMT_SGBRG10_1X10, .bpp = 10, .datatype = MIPI_CSI2_DT_RAW10, }, + { .code = MEDIA_BUS_FMT_SGRBG10_1X10, .bpp = 10, .datatype = MIPI_CSI2_DT_RAW10, }, + { .code = MEDIA_BUS_FMT_SRGGB10_1X10, .bpp = 10, .datatype = MIPI_CSI2_DT_RAW10, }, + + { .code = MEDIA_BUS_FMT_SBGGR12_1X12, .bpp = 12, .datatype = MIPI_CSI2_DT_RAW12, }, + { .code = MEDIA_BUS_FMT_SGBRG12_1X12, .bpp = 12, .datatype = MIPI_CSI2_DT_RAW12, }, + { .code = MEDIA_BUS_FMT_SGRBG12_1X12, .bpp = 12, .datatype = MIPI_CSI2_DT_RAW12, }, + { .code = MEDIA_BUS_FMT_SRGGB12_1X12, .bpp = 12, .datatype = MIPI_CSI2_DT_RAW12, }, +}; + +static const struct ub960_format_info *ub960_find_format(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ub960_formats); i++) { + if (ub960_formats[i].code == code) + return &ub960_formats[i]; + } + + return NULL; +} + +struct ub960_rxport_iter { + unsigned int nport; + struct ub960_rxport *rxport; +}; + +enum ub960_iter_flags { + UB960_ITER_ACTIVE_ONLY = BIT(0), + UB960_ITER_FPD4_ONLY = BIT(1), +}; + +static struct ub960_rxport_iter ub960_iter_rxport(struct ub960_data *priv, + struct ub960_rxport_iter it, + enum ub960_iter_flags flags) +{ + for (; it.nport < priv->hw_data->num_rxports; it.nport++) { + it.rxport = priv->rxports[it.nport]; + + if ((flags & UB960_ITER_ACTIVE_ONLY) && !it.rxport) + continue; + + if ((flags & UB960_ITER_FPD4_ONLY) && + it.rxport->cdr_mode != RXPORT_CDR_FPD4) + continue; + + return it; + } + + it.rxport = NULL; + + return it; +} + +#define for_each_rxport(priv, it) \ + for (struct ub960_rxport_iter it = \ + ub960_iter_rxport(priv, (struct ub960_rxport_iter){ 0 }, \ + 0); \ + it.nport < (priv)->hw_data->num_rxports; \ + it.nport++, it = ub960_iter_rxport(priv, it, 0)) + +#define for_each_active_rxport(priv, it) \ + for (struct ub960_rxport_iter it = \ + ub960_iter_rxport(priv, (struct ub960_rxport_iter){ 0 }, \ + UB960_ITER_ACTIVE_ONLY); \ + it.nport < (priv)->hw_data->num_rxports; \ + it.nport++, it = ub960_iter_rxport(priv, it, \ + UB960_ITER_ACTIVE_ONLY)) + +#define for_each_active_rxport_fpd4(priv, it) \ + for (struct ub960_rxport_iter it = \ + ub960_iter_rxport(priv, (struct ub960_rxport_iter){ 0 }, \ + UB960_ITER_ACTIVE_ONLY | \ + UB960_ITER_FPD4_ONLY); \ + it.nport < (priv)->hw_data->num_rxports; \ + it.nport++, it = ub960_iter_rxport(priv, it, \ + UB960_ITER_ACTIVE_ONLY | \ + UB960_ITER_FPD4_ONLY)) + +/* ----------------------------------------------------------------------------- + * Basic device access + */ + +static int ub960_read(struct ub960_data *priv, u8 reg, u8 *val, int *err) +{ + struct device *dev = &priv->client->dev; + unsigned int v; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = regmap_read(priv->regmap, reg, &v); + if (ret) { + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n", + __func__, reg, ret); + goto out_unlock; + } + + *val = v; + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_write(struct ub960_data *priv, u8 reg, u8 val, int *err) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = regmap_write(priv->regmap, reg, val); + if (ret) + dev_err(dev, "%s: cannot write register 0x%02x (%d)!\n", + __func__, reg, ret); + + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_update_bits(struct ub960_data *priv, u8 reg, u8 mask, u8 val, + int *err) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + if (ret) + dev_err(dev, "%s: cannot update register 0x%02x (%d)!\n", + __func__, reg, ret); + + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_read16(struct ub960_data *priv, u8 reg, u16 *val, int *err) +{ + struct device *dev = &priv->client->dev; + __be16 __v; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = regmap_bulk_read(priv->regmap, reg, &__v, sizeof(__v)); + if (ret) { + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n", + __func__, reg, ret); + goto out_unlock; + } + + *val = be16_to_cpu(__v); + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_rxport_select(struct ub960_data *priv, u8 nport) +{ + struct device *dev = &priv->client->dev; + int ret; + + lockdep_assert_held(&priv->reg_lock); + + if (priv->reg_current.rxport == nport) + return 0; + + ret = regmap_write(priv->regmap, UB960_SR_FPD3_PORT_SEL, + (nport << 4) | BIT(nport)); + if (ret) { + dev_err(dev, "%s: cannot select rxport %d (%d)!\n", __func__, + nport, ret); + return ret; + } + + priv->reg_current.rxport = nport; + + return 0; +} + +static int ub960_rxport_read(struct ub960_data *priv, u8 nport, u8 reg, + u8 *val, int *err) +{ + struct device *dev = &priv->client->dev; + unsigned int v; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = ub960_rxport_select(priv, nport); + if (ret) + goto out_unlock; + + ret = regmap_read(priv->regmap, reg, &v); + if (ret) { + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n", + __func__, reg, ret); + goto out_unlock; + } + + *val = v; + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_rxport_write(struct ub960_data *priv, u8 nport, u8 reg, + u8 val, int *err) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = ub960_rxport_select(priv, nport); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, reg, val); + if (ret) + dev_err(dev, "%s: cannot write register 0x%02x (%d)!\n", + __func__, reg, ret); + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_rxport_update_bits(struct ub960_data *priv, u8 nport, u8 reg, + u8 mask, u8 val, int *err) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = ub960_rxport_select(priv, nport); + if (ret) + goto out_unlock; + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + if (ret) + dev_err(dev, "%s: cannot update register 0x%02x (%d)!\n", + __func__, reg, ret); + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_rxport_read16(struct ub960_data *priv, u8 nport, u8 reg, + u16 *val, int *err) +{ + struct device *dev = &priv->client->dev; + __be16 __v; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = ub960_rxport_select(priv, nport); + if (ret) + goto out_unlock; + + ret = regmap_bulk_read(priv->regmap, reg, &__v, sizeof(__v)); + if (ret) { + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n", + __func__, reg, ret); + goto out_unlock; + } + + *val = be16_to_cpu(__v); + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_txport_select(struct ub960_data *priv, u8 nport) +{ + struct device *dev = &priv->client->dev; + int ret; + + lockdep_assert_held(&priv->reg_lock); + + /* UB954 has only 1 CSI TX. Hence, no need to select */ + if (priv->hw_data->chip_type == UB954) + return 0; + + if (priv->reg_current.txport == nport) + return 0; + + ret = regmap_write(priv->regmap, UB960_SR_CSI_PORT_SEL, + (nport << 4) | BIT(nport)); + if (ret) { + dev_err(dev, "%s: cannot select tx port %d (%d)!\n", __func__, + nport, ret); + return ret; + } + + priv->reg_current.txport = nport; + + return 0; +} + +static int ub960_txport_read(struct ub960_data *priv, u8 nport, u8 reg, + u8 *val, int *err) +{ + struct device *dev = &priv->client->dev; + unsigned int v; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = ub960_txport_select(priv, nport); + if (ret) + goto out_unlock; + + ret = regmap_read(priv->regmap, reg, &v); + if (ret) { + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n", + __func__, reg, ret); + goto out_unlock; + } + + *val = v; + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_txport_write(struct ub960_data *priv, u8 nport, u8 reg, + u8 val, int *err) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = ub960_txport_select(priv, nport); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, reg, val); + if (ret) + dev_err(dev, "%s: cannot write register 0x%02x (%d)!\n", + __func__, reg, ret); + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_txport_update_bits(struct ub960_data *priv, u8 nport, u8 reg, + u8 mask, u8 val, int *err) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = ub960_txport_select(priv, nport); + if (ret) + goto out_unlock; + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + if (ret) + dev_err(dev, "%s: cannot update register 0x%02x (%d)!\n", + __func__, reg, ret); + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_select_ind_reg_block(struct ub960_data *priv, u8 block) +{ + struct device *dev = &priv->client->dev; + int ret; + + lockdep_assert_held(&priv->reg_lock); + + if (priv->reg_current.indirect_target == block) + return 0; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_CTL, block << 2); + if (ret) { + dev_err(dev, "%s: cannot select indirect target %u (%d)!\n", + __func__, block, ret); + return ret; + } + + priv->reg_current.indirect_target = block; + + return 0; +} + +static int ub960_read_ind(struct ub960_data *priv, u8 block, u8 reg, u8 *val, + int *err) +{ + struct device *dev = &priv->client->dev; + unsigned int v; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = ub960_select_ind_reg_block(priv, block); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) { + dev_err(dev, + "Write to IND_ACC_ADDR failed when reading %u:%x02x: %d\n", + block, reg, ret); + goto out_unlock; + } + + ret = regmap_read(priv->regmap, UB960_SR_IND_ACC_DATA, &v); + if (ret) { + dev_err(dev, + "Write to IND_ACC_DATA failed when reading %u:%x02x: %d\n", + block, reg, ret); + goto out_unlock; + } + + *val = v; + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_write_ind(struct ub960_data *priv, u8 block, u8 reg, u8 val, + int *err) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = ub960_select_ind_reg_block(priv, block); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) { + dev_err(dev, + "Write to IND_ACC_ADDR failed when writing %u:%x02x: %d\n", + block, reg, ret); + goto out_unlock; + } + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, val); + if (ret) { + dev_err(dev, + "Write to IND_ACC_DATA failed when writing %u:%x02x: %d\n", + block, reg, ret); + goto out_unlock; + } + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_ind_update_bits(struct ub960_data *priv, u8 block, u8 reg, + u8 mask, u8 val, int *err) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (err && *err) + return *err; + + mutex_lock(&priv->reg_lock); + + ret = ub960_select_ind_reg_block(priv, block); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) { + dev_err(dev, + "Write to IND_ACC_ADDR failed when updating %u:%x02x: %d\n", + block, reg, ret); + goto out_unlock; + } + + ret = regmap_update_bits(priv->regmap, UB960_SR_IND_ACC_DATA, mask, + val); + if (ret) { + dev_err(dev, + "Write to IND_ACC_DATA failed when updating %u:%x02x: %d\n", + block, reg, ret); + goto out_unlock; + } + +out_unlock: + mutex_unlock(&priv->reg_lock); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_reset(struct ub960_data *priv, bool reset_regs) +{ + struct device *dev = &priv->client->dev; + unsigned int v; + int ret; + u8 bit; + + bit = reset_regs ? UB960_SR_RESET_DIGITAL_RESET1 : + UB960_SR_RESET_DIGITAL_RESET0; + + ret = ub960_write(priv, UB960_SR_RESET, bit, NULL); + if (ret) + return ret; + + mutex_lock(&priv->reg_lock); + + ret = regmap_read_poll_timeout(priv->regmap, UB960_SR_RESET, v, + (v & bit) == 0, 2000, 100000); + + mutex_unlock(&priv->reg_lock); + + if (ret) + dev_err(dev, "reset failed: %d\n", ret); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * I2C-ATR (address translator) + */ + +static int ub960_atr_attach_addr(struct i2c_atr *atr, u32 chan_id, + u16 addr, u16 alias) +{ + struct ub960_data *priv = i2c_atr_get_driver_data(atr); + struct ub960_rxport *rxport = priv->rxports[chan_id]; + struct device *dev = &priv->client->dev; + unsigned int reg_idx; + int ret = 0; + + guard(mutex)(&rxport->aliased_addrs_lock); + + for (reg_idx = 0; reg_idx < ARRAY_SIZE(rxport->aliased_addrs); reg_idx++) { + if (!rxport->aliased_addrs[reg_idx]) + break; + } + + if (reg_idx == ARRAY_SIZE(rxport->aliased_addrs)) { + dev_err(dev, "rx%u: alias pool exhausted\n", rxport->nport); + return -EADDRNOTAVAIL; + } + + rxport->aliased_addrs[reg_idx] = addr; + + ub960_rxport_write(priv, chan_id, UB960_RR_SLAVE_ID(reg_idx), + addr << 1, &ret); + ub960_rxport_write(priv, chan_id, UB960_RR_SLAVE_ALIAS(reg_idx), + alias << 1, &ret); + + if (ret) + return ret; + + dev_dbg(dev, "rx%u: client 0x%02x assigned alias 0x%02x at slot %u\n", + rxport->nport, addr, alias, reg_idx); + + return 0; +} + +static void ub960_atr_detach_addr(struct i2c_atr *atr, u32 chan_id, + u16 addr) +{ + struct ub960_data *priv = i2c_atr_get_driver_data(atr); + struct ub960_rxport *rxport = priv->rxports[chan_id]; + struct device *dev = &priv->client->dev; + unsigned int reg_idx; + int ret; + + guard(mutex)(&rxport->aliased_addrs_lock); + + for (reg_idx = 0; reg_idx < ARRAY_SIZE(rxport->aliased_addrs); reg_idx++) { + if (rxport->aliased_addrs[reg_idx] == addr) + break; + } + + if (reg_idx == ARRAY_SIZE(rxport->aliased_addrs)) { + dev_err(dev, "rx%u: client 0x%02x is not mapped!\n", + rxport->nport, addr); + return; + } + + rxport->aliased_addrs[reg_idx] = 0; + + ret = ub960_rxport_write(priv, chan_id, UB960_RR_SLAVE_ALIAS(reg_idx), + 0, NULL); + if (ret) { + dev_err(dev, "rx%u: unable to fully unmap client 0x%02x: %d\n", + rxport->nport, addr, ret); + return; + } + + dev_dbg(dev, "rx%u: client 0x%02x released at slot %u\n", rxport->nport, + addr, reg_idx); +} + +static const struct i2c_atr_ops ub960_atr_ops = { + .attach_addr = ub960_atr_attach_addr, + .detach_addr = ub960_atr_detach_addr, +}; + +static int ub960_init_atr(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + struct i2c_adapter *parent_adap = priv->client->adapter; + + priv->atr = i2c_atr_new(parent_adap, dev, &ub960_atr_ops, + priv->hw_data->num_rxports, 0); + if (IS_ERR(priv->atr)) + return PTR_ERR(priv->atr); + + i2c_atr_set_driver_data(priv->atr, priv); + + return 0; +} + +static void ub960_uninit_atr(struct ub960_data *priv) +{ + i2c_atr_delete(priv->atr); + priv->atr = NULL; +} + +/* ----------------------------------------------------------------------------- + * TX ports + */ + +static int ub960_parse_dt_txport(struct ub960_data *priv, + struct fwnode_handle *ep_fwnode, + u8 nport) +{ + struct device *dev = &priv->client->dev; + struct v4l2_fwnode_endpoint vep = {}; + struct ub960_txport *txport; + int ret; + + txport = kzalloc_obj(*txport); + if (!txport) + return -ENOMEM; + + txport->priv = priv; + txport->nport = nport; + + vep.bus_type = V4L2_MBUS_CSI2_DPHY; + ret = v4l2_fwnode_endpoint_alloc_parse(ep_fwnode, &vep); + if (ret) { + dev_err(dev, "tx%u: failed to parse endpoint data\n", nport); + goto err_free_txport; + } + + txport->non_continous_clk = vep.bus.mipi_csi2.flags & + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK; + + txport->num_data_lanes = vep.bus.mipi_csi2.num_data_lanes; + + if (vep.nr_of_link_frequencies != 1) { + ret = -EINVAL; + goto err_free_vep; + } + + priv->tx_link_freq[0] = vep.link_frequencies[0]; + priv->tx_data_rate = priv->tx_link_freq[0] * 2; + + if ((priv->tx_data_rate != MHZ(1600) && + priv->tx_data_rate != MHZ(1200) && + priv->tx_data_rate != MHZ(800) && + priv->tx_data_rate != MHZ(400)) || + (priv->hw_data->chip_type == UB954 && priv->tx_data_rate == MHZ(1200))) { + dev_err(dev, "tx%u: invalid 'link-frequencies' value\n", nport); + ret = -EINVAL; + goto err_free_vep; + } + + v4l2_fwnode_endpoint_free(&vep); + + priv->txports[nport] = txport; + + return 0; + +err_free_vep: + v4l2_fwnode_endpoint_free(&vep); +err_free_txport: + kfree(txport); + + return ret; +} + +static int ub960_csi_handle_events(struct ub960_data *priv, u8 nport) +{ + struct device *dev = &priv->client->dev; + u8 csi_tx_isr; + int ret; + + ret = ub960_txport_read(priv, nport, UB960_TR_CSI_TX_ISR, &csi_tx_isr, + NULL); + if (ret) + return ret; + + if (csi_tx_isr & UB960_TR_CSI_TX_ISR_IS_CSI_SYNC_ERROR) + dev_warn(dev, "TX%u: CSI_SYNC_ERROR\n", nport); + + if (csi_tx_isr & UB960_TR_CSI_TX_ISR_IS_CSI_PASS_ERROR) + dev_warn(dev, "TX%u: CSI_PASS_ERROR\n", nport); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * RX ports + */ + +static int ub960_rxport_enable_vpocs(struct ub960_data *priv) +{ + unsigned int failed_nport; + int ret; + + for_each_active_rxport(priv, it) { + if (!it.rxport->vpoc) + continue; + + ret = regulator_enable(it.rxport->vpoc); + if (ret) { + failed_nport = it.nport; + goto err_disable_vpocs; + } + } + + return 0; + +err_disable_vpocs: + while (failed_nport--) { + struct ub960_rxport *rxport = priv->rxports[failed_nport]; + + if (!rxport || !rxport->vpoc) + continue; + + regulator_disable(rxport->vpoc); + } + + return ret; +} + +static void ub960_rxport_disable_vpocs(struct ub960_data *priv) +{ + for_each_active_rxport(priv, it) { + if (!it.rxport->vpoc) + continue; + + regulator_disable(it.rxport->vpoc); + } +} + +static int ub960_rxport_clear_errors(struct ub960_data *priv, + unsigned int nport) +{ + int ret = 0; + u8 v; + + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, &v, &ret); + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, &v, &ret); + ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS, &v, &ret); + ub960_rxport_read(priv, nport, UB960_RR_BCC_STATUS, &v, &ret); + + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v, &ret); + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v, &ret); + + ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER, &v, &ret); + + return ret; +} + +static int ub960_clear_rx_errors(struct ub960_data *priv) +{ + int ret; + + for_each_rxport(priv, it) { + ret = ub960_rxport_clear_errors(priv, it.nport); + if (ret) + return ret; + } + + return 0; +} + +static int ub960_rxport_get_strobe_pos(struct ub960_data *priv, + unsigned int nport, s8 *strobe_pos) +{ + u8 v; + u8 clk_delay, data_delay; + int ret; + + if (priv->hw_data->chip_type == UB954) { + ret = ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB954_IR_RX_ANA_STROBE_SET_CLK_DATA, &v, NULL); + if (ret) + return ret; + + clk_delay = (v & UB954_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY) ? + 0 : UB960_MANUAL_STROBE_EXTRA_DELAY; + + data_delay = (v & UB954_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY) ? + 0 : UB960_MANUAL_STROBE_EXTRA_DELAY; + } else { + ret = ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_CLK, &v, NULL); + if (ret) + return ret; + + clk_delay = (v & UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY) ? + 0 : UB960_MANUAL_STROBE_EXTRA_DELAY; + + ret = ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_DATA, &v, NULL); + if (ret) + return ret; + + data_delay = (v & UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY) ? + 0 : UB960_MANUAL_STROBE_EXTRA_DELAY; + } + + ret = ub960_rxport_read(priv, nport, UB960_RR_SFILTER_STS_0, &v, NULL); + if (ret) + return ret; + + clk_delay += v & UB960_IR_RX_ANA_STROBE_SET_CLK_DELAY_MASK; + + ret = ub960_rxport_read(priv, nport, UB960_RR_SFILTER_STS_1, &v, NULL); + if (ret) + return ret; + + data_delay += v & UB960_IR_RX_ANA_STROBE_SET_DATA_DELAY_MASK; + + *strobe_pos = data_delay - clk_delay; + + return 0; +} + +static int ub960_rxport_set_strobe_pos(struct ub960_data *priv, + unsigned int nport, s8 strobe_pos) +{ + int ret = 0; + + if (priv->hw_data->chip_type == UB954) { + u8 clk_data_delay; + + clk_data_delay = UB954_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY | + UB954_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY; + + if (strobe_pos < UB960_MIN_AEQ_STROBE_POS) + clk_data_delay = abs(strobe_pos) - UB960_MANUAL_STROBE_EXTRA_DELAY; + else if (strobe_pos > UB960_MAX_AEQ_STROBE_POS) + clk_data_delay = (strobe_pos - UB960_MANUAL_STROBE_EXTRA_DELAY) << + UB954_IR_RX_ANA_STROBE_SET_DATA_DELAY_SHIFT; + else if (strobe_pos < 0) + clk_data_delay = abs(strobe_pos) | + UB954_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY; + else if (strobe_pos > 0) + clk_data_delay = (strobe_pos | + UB954_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY) << + UB954_IR_RX_ANA_STROBE_SET_DATA_DELAY_SHIFT; + + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB954_IR_RX_ANA_STROBE_SET_CLK_DATA, clk_data_delay, &ret); + } else { + u8 clk_delay, data_delay; + + clk_delay = UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY; + data_delay = UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY; + + if (strobe_pos < UB960_MIN_AEQ_STROBE_POS) + clk_delay = abs(strobe_pos) - UB960_MANUAL_STROBE_EXTRA_DELAY; + else if (strobe_pos > UB960_MAX_AEQ_STROBE_POS) + data_delay = strobe_pos - UB960_MANUAL_STROBE_EXTRA_DELAY; + else if (strobe_pos < 0) + clk_delay = abs(strobe_pos) | UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY; + else if (strobe_pos > 0) + data_delay = strobe_pos | UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY; + + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_CLK, clk_delay, &ret); + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_DATA, data_delay, &ret); + } + + return ret; +} + +static int ub960_rxport_set_strobe_range(struct ub960_data *priv, s8 strobe_min, + s8 strobe_max) +{ + /* Convert the signed strobe pos to positive zero based value */ + strobe_min -= UB960_MIN_AEQ_STROBE_POS; + strobe_max -= UB960_MIN_AEQ_STROBE_POS; + + return ub960_write(priv, UB960_XR_SFILTER_CFG, + ((u8)strobe_min << UB960_XR_SFILTER_CFG_SFILTER_MIN_SHIFT) | + ((u8)strobe_max << UB960_XR_SFILTER_CFG_SFILTER_MAX_SHIFT), + NULL); +} + +static int ub960_rxport_get_eq_level(struct ub960_data *priv, + unsigned int nport, u8 *eq_level) +{ + int ret; + u8 v; + + ret = ub960_rxport_read(priv, nport, UB960_RR_AEQ_STATUS, &v, NULL); + if (ret) + return ret; + + *eq_level = (v & UB960_RR_AEQ_STATUS_STATUS_1) + + (v & UB960_RR_AEQ_STATUS_STATUS_2); + + return 0; +} + +static int ub960_rxport_set_eq_level(struct ub960_data *priv, + unsigned int nport, u8 eq_level) +{ + u8 eq_stage_1_select_value, eq_stage_2_select_value; + const unsigned int eq_stage_max = 7; + int ret; + u8 v; + + if (eq_level <= eq_stage_max) { + eq_stage_1_select_value = eq_level; + eq_stage_2_select_value = 0; + } else { + eq_stage_1_select_value = eq_stage_max; + eq_stage_2_select_value = eq_level - eq_stage_max; + } + + ret = ub960_rxport_read(priv, nport, UB960_RR_AEQ_BYPASS, &v, NULL); + if (ret) + return ret; + + v &= ~(UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK | + UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK); + v |= eq_stage_1_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT; + v |= eq_stage_2_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT; + v |= UB960_RR_AEQ_BYPASS_ENABLE; + + ret = ub960_rxport_write(priv, nport, UB960_RR_AEQ_BYPASS, v, NULL); + if (ret) + return ret; + + return 0; +} + +static int ub960_rxport_set_eq_range(struct ub960_data *priv, + unsigned int nport, u8 eq_min, u8 eq_max) +{ + int ret = 0; + + ub960_rxport_write(priv, nport, UB960_RR_AEQ_MIN_MAX, + (eq_min << UB960_RR_AEQ_MIN_MAX_AEQ_FLOOR_SHIFT) | + (eq_max << UB960_RR_AEQ_MIN_MAX_AEQ_MAX_SHIFT), + &ret); + + /* Enable AEQ min setting */ + ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_CTL2, + UB960_RR_AEQ_CTL2_SET_AEQ_FLOOR, + UB960_RR_AEQ_CTL2_SET_AEQ_FLOOR, &ret); + + return ret; +} + +static int ub960_rxport_config_eq(struct ub960_data *priv, unsigned int nport) +{ + struct ub960_rxport *rxport = priv->rxports[nport]; + int ret; + + /* We also set common settings here. Should be moved elsewhere. */ + + if (priv->strobe.manual) { + /* Disable AEQ_SFILTER_EN */ + ret = ub960_update_bits(priv, UB960_XR_AEQ_CTL1, + UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN, 0, + NULL); + if (ret) + return ret; + } else { + /* Enable SFILTER and error control */ + ret = ub960_write(priv, UB960_XR_AEQ_CTL1, + UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_MASK | + UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN, + NULL); + + if (ret) + return ret; + + /* Set AEQ strobe range */ + ret = ub960_rxport_set_strobe_range(priv, priv->strobe.min, + priv->strobe.max); + if (ret) + return ret; + } + + /* The rest are port specific */ + + if (priv->strobe.manual) + ret = ub960_rxport_set_strobe_pos(priv, nport, + rxport->eq.strobe_pos); + else + ret = ub960_rxport_set_strobe_pos(priv, nport, 0); + + if (ret) + return ret; + + if (rxport->eq.manual_eq) { + ret = ub960_rxport_set_eq_level(priv, nport, + rxport->eq.manual.eq_level); + if (ret) + return ret; + + /* Enable AEQ Bypass */ + ret = ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS, + UB960_RR_AEQ_BYPASS_ENABLE, + UB960_RR_AEQ_BYPASS_ENABLE, + NULL); + if (ret) + return ret; + } else { + ret = ub960_rxport_set_eq_range(priv, nport, + rxport->eq.aeq.eq_level_min, + rxport->eq.aeq.eq_level_max); + if (ret) + return ret; + + /* Disable AEQ Bypass */ + ret = ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS, + UB960_RR_AEQ_BYPASS_ENABLE, 0, + NULL); + if (ret) + return ret; + } + + return 0; +} + +static int ub960_rxport_link_ok(struct ub960_data *priv, unsigned int nport, + bool *ok) +{ + u8 rx_port_sts1, rx_port_sts2; + u16 parity_errors; + u8 csi_rx_sts; + u8 csi_err_cnt; + u8 bcc_sts; + int ret; + bool errors; + + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, + &rx_port_sts1, NULL); + if (ret) + return ret; + + if (!(rx_port_sts1 & UB960_RR_RX_PORT_STS1_LOCK_STS)) { + *ok = false; + return 0; + } + + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, + &rx_port_sts2, NULL); + if (ret) + return ret; + + ret = ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS, &csi_rx_sts, + NULL); + if (ret) + return ret; + + ret = ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER, + &csi_err_cnt, NULL); + if (ret) + return ret; + + ret = ub960_rxport_read(priv, nport, UB960_RR_BCC_STATUS, &bcc_sts, + NULL); + if (ret) + return ret; + + ret = ub960_rxport_read16(priv, nport, UB960_RR_RX_PAR_ERR_HI, + &parity_errors, NULL); + if (ret) + return ret; + + errors = (rx_port_sts1 & UB960_RR_RX_PORT_STS1_ERROR_MASK) || + (rx_port_sts2 & UB960_RR_RX_PORT_STS2_ERROR_MASK) || + (bcc_sts & UB960_RR_BCC_STATUS_ERROR_MASK) || + (csi_rx_sts & UB960_RR_CSI_RX_STS_ERROR_MASK) || csi_err_cnt || + parity_errors; + + *ok = !errors; + + return 0; +} + +static int ub960_rxport_lockup_wa_ub9702(struct ub960_data *priv) +{ + int ret; + + /* Toggle PI_MODE to avoid possible FPD RX lockup */ + + ret = ub960_update_bits(priv, UB9702_RR_CHANNEL_MODE, GENMASK(4, 3), + 2 << 3, NULL); + if (ret) + return ret; + + usleep_range(1000, 5000); + + return ub960_update_bits(priv, UB9702_RR_CHANNEL_MODE, GENMASK(4, 3), + 0, NULL); +} + +/* + * Wait for the RX ports to lock, have no errors and have stable strobe position + * and EQ level. + */ +static int ub960_rxport_wait_locks(struct ub960_data *priv, + unsigned long port_mask, + unsigned int *lock_mask) +{ + struct device *dev = &priv->client->dev; + unsigned long timeout; + unsigned int link_ok_mask; + unsigned int missing; + unsigned int loops; + u8 nport; + int ret; + + if (port_mask == 0) { + if (lock_mask) + *lock_mask = 0; + return 0; + } + + if (port_mask >= BIT(priv->hw_data->num_rxports)) + return -EINVAL; + + timeout = jiffies + msecs_to_jiffies(1000); + loops = 0; + link_ok_mask = 0; + + while (time_before(jiffies, timeout)) { + bool fpd4_wa = false; + missing = 0; + + for_each_set_bit(nport, &port_mask, + priv->hw_data->num_rxports) { + struct ub960_rxport *rxport = priv->rxports[nport]; + bool ok; + + if (!rxport) + continue; + + ret = ub960_rxport_link_ok(priv, nport, &ok); + if (ret) + return ret; + + if (!ok && rxport->cdr_mode == RXPORT_CDR_FPD4) + fpd4_wa = true; + + /* + * We want the link to be ok for two consecutive loops, + * as a link could get established just before our test + * and drop soon after. + */ + if (!ok || !(link_ok_mask & BIT(nport))) + missing++; + + if (ok) + link_ok_mask |= BIT(nport); + else + link_ok_mask &= ~BIT(nport); + } + + loops++; + + if (missing == 0) + break; + + if (fpd4_wa) { + ret = ub960_rxport_lockup_wa_ub9702(priv); + if (ret) + return ret; + } + + /* + * The sleep time of 10 ms was found by testing to give a lock + * with a few iterations. It can be decreased if on some setups + * the lock can be achieved much faster. + */ + fsleep(10 * USEC_PER_MSEC); + } + + if (lock_mask) + *lock_mask = link_ok_mask; + + dev_dbg(dev, "Wait locks done in %u loops\n", loops); + for_each_set_bit(nport, &port_mask, priv->hw_data->num_rxports) { + struct ub960_rxport *rxport = priv->rxports[nport]; + s8 strobe_pos, eq_level; + u16 v; + + if (!rxport) + continue; + + if (!(link_ok_mask & BIT(nport))) { + dev_dbg(dev, "\trx%u: not locked\n", nport); + continue; + } + + ret = ub960_rxport_read16(priv, nport, UB960_RR_RX_FREQ_HIGH, + &v, NULL); + + if (ret) + return ret; + + if (priv->hw_data->chip_type == UB9702) { + dev_dbg(dev, "\trx%u: locked, freq %llu Hz\n", + nport, ((u64)v * HZ_PER_MHZ) >> 8); + } else { + ret = ub960_rxport_get_strobe_pos(priv, nport, + &strobe_pos); + if (ret) + return ret; + + ret = ub960_rxport_get_eq_level(priv, nport, &eq_level); + if (ret) + return ret; + + dev_dbg(dev, + "\trx%u: locked, SP: %d, EQ: %u, freq %llu Hz\n", + nport, strobe_pos, eq_level, + ((u64)v * HZ_PER_MHZ) >> 8); + } + } + + return 0; +} + +static unsigned long ub960_calc_bc_clk_rate_ub960(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int mult; + unsigned int div; + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + mult = 1; + div = 10; + break; + + case RXPORT_MODE_CSI2_SYNC: + mult = 2; + div = 1; + break; + + case RXPORT_MODE_CSI2_NONSYNC: + mult = 2; + div = 5; + break; + + default: + return 0; + } + + return clk_get_rate(priv->refclk) * mult / div; +} + +static unsigned long ub960_calc_bc_clk_rate_ub9702(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + return 2359400; + + case RXPORT_MODE_CSI2_SYNC: + return 47187500; + + case RXPORT_MODE_CSI2_NONSYNC: + return 9437500; + + default: + return 0; + } +} + +static int ub960_rxport_serializer_write(struct ub960_rxport *rxport, u8 reg, + u8 val, int *err) +{ + struct ub960_data *priv = rxport->priv; + struct device *dev = &priv->client->dev; + union i2c_smbus_data data; + int ret; + + if (err && *err) + return *err; + + data.byte = val; + + ret = i2c_smbus_xfer(priv->client->adapter, rxport->ser.alias, 0, + I2C_SMBUS_WRITE, reg, I2C_SMBUS_BYTE_DATA, &data); + if (ret) + dev_err(dev, + "rx%u: cannot write serializer register 0x%02x (%d)!\n", + rxport->nport, reg, ret); + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_rxport_serializer_read(struct ub960_rxport *rxport, u8 reg, + u8 *val, int *err) +{ + struct ub960_data *priv = rxport->priv; + struct device *dev = &priv->client->dev; + union i2c_smbus_data data = { 0 }; + int ret; + + if (err && *err) + return *err; + + ret = i2c_smbus_xfer(priv->client->adapter, rxport->ser.alias, + priv->client->flags, I2C_SMBUS_READ, reg, + I2C_SMBUS_BYTE_DATA, &data); + if (ret) + dev_err(dev, + "rx%u: cannot read serializer register 0x%02x (%d)!\n", + rxport->nport, reg, ret); + else + *val = data.byte; + + if (ret && err) + *err = ret; + + return ret; +} + +static int ub960_serializer_temp_ramp(struct ub960_rxport *rxport) +{ + struct ub960_data *priv = rxport->priv; + short temp_dynamic_offset[] = {-1, -1, 0, 0, 1, 1, 1, 3}; + u8 temp_dynamic_cfg; + u8 nport = rxport->nport; + u8 ser_temp_code; + int ret = 0; + + /* Configure temp ramp only on UB953 */ + if (!fwnode_device_is_compatible(rxport->ser.fwnode, "ti,ds90ub953-q1")) + return 0; + + /* Read current serializer die temperature */ + ub960_rxport_read(priv, nport, UB960_RR_SENSOR_STS_2, &ser_temp_code, + &ret); + + /* Enable I2C passthrough on back channel */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH, &ret); + + if (ret) + return ret; + + /* Select indirect page for analog regs on the serializer */ + ub960_rxport_serializer_write(rxport, UB953_REG_IND_ACC_CTL, + UB953_IND_TARGET_ANALOG << 2, &ret); + + /* Set temperature ramp dynamic and static config */ + ub960_rxport_serializer_write(rxport, UB953_REG_IND_ACC_ADDR, + UB953_IND_ANA_TEMP_DYNAMIC_CFG, &ret); + ub960_rxport_serializer_read(rxport, UB953_REG_IND_ACC_DATA, + &temp_dynamic_cfg, &ret); + + if (ret) + return ret; + + temp_dynamic_cfg |= UB953_IND_ANA_TEMP_DYNAMIC_CFG_OV; + temp_dynamic_cfg += temp_dynamic_offset[ser_temp_code]; + + /* Update temp static config */ + ub960_rxport_serializer_write(rxport, UB953_REG_IND_ACC_ADDR, + UB953_IND_ANA_TEMP_STATIC_CFG, &ret); + ub960_rxport_serializer_write(rxport, UB953_REG_IND_ACC_DATA, + UB953_IND_ANA_TEMP_STATIC_CFG_MASK, &ret); + + /* Update temperature ramp dynamic config */ + ub960_rxport_serializer_write(rxport, UB953_REG_IND_ACC_ADDR, + UB953_IND_ANA_TEMP_DYNAMIC_CFG, &ret); + + /* Enable I2C auto ack on BC before we set dynamic cfg and reset */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_AUTO_ACK_ALL, + UB960_RR_BCC_CONFIG_AUTO_ACK_ALL, &ret); + + ub960_rxport_serializer_write(rxport, UB953_REG_IND_ACC_DATA, + temp_dynamic_cfg, &ret); + + if (ret) + return ret; + + /* Soft reset to apply PLL updates */ + ub960_rxport_serializer_write(rxport, UB953_REG_RESET_CTL, + UB953_REG_RESET_CTL_DIGITAL_RESET_0, + &ret); + msleep(20); + + /* Disable I2C passthrough and auto-ack on BC */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH | + UB960_RR_BCC_CONFIG_AUTO_ACK_ALL, + 0x0, &ret); + + return ret; +} + +static int ub960_rxport_bc_ser_config(struct ub960_rxport *rxport) +{ + struct ub960_data *priv = rxport->priv; + struct device *dev = &priv->client->dev; + u8 nport = rxport->nport; + int ret = 0; + + /* Skip port if serializer's address is not known */ + if (rxport->ser.addr < 0) { + dev_dbg(dev, + "rx%u: serializer address missing, skip configuration\n", + nport); + return 0; + } + + /* + * Note: the code here probably only works for CSI-2 serializers in + * sync mode. To support other serializers the BC related configuration + * should be done before calling this function. + */ + + /* Enable I2C passthrough and auto-ack on BC */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH | + UB960_RR_BCC_CONFIG_AUTO_ACK_ALL, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH | + UB960_RR_BCC_CONFIG_AUTO_ACK_ALL, + &ret); + + if (ret) + return ret; + + /* Disable BC alternate mode auto detect */ + ub960_rxport_serializer_write(rxport, UB971_ENH_BC_CHK, 0x02, &ret); + /* Decrease link detect timer */ + ub960_rxport_serializer_write(rxport, UB953_REG_BC_CTRL, 0x06, &ret); + + /* Disable I2C passthrough and auto-ack on BC */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH | + UB960_RR_BCC_CONFIG_AUTO_ACK_ALL, + 0x0, &ret); + + return ret; +} + +static int ub960_rxport_add_serializer(struct ub960_data *priv, u8 nport) +{ + struct ub960_rxport *rxport = priv->rxports[nport]; + struct device *dev = &priv->client->dev; + struct ds90ub9xx_platform_data *ser_pdata = &rxport->ser.pdata; + struct i2c_board_info ser_info = { + .fwnode = rxport->ser.fwnode, + .platform_data = ser_pdata, + }; + + ser_pdata->port = nport; + ser_pdata->atr = priv->atr; + if (priv->hw_data->chip_type == UB9702) + ser_pdata->bc_rate = ub960_calc_bc_clk_rate_ub9702(priv, rxport); + else + ser_pdata->bc_rate = ub960_calc_bc_clk_rate_ub960(priv, rxport); + + /* + * The serializer is added under the same i2c adapter as the + * deserializer. This is not quite right, as the serializer is behind + * the FPD-Link. + */ + ser_info.addr = rxport->ser.alias; + rxport->ser.client = + i2c_new_client_device(priv->client->adapter, &ser_info); + if (IS_ERR(rxport->ser.client)) { + dev_err(dev, "rx%u: cannot add %s i2c device", nport, + ser_info.type); + return PTR_ERR(rxport->ser.client); + } + + dev_dbg(dev, "rx%u: remote serializer at alias 0x%02x (%u-%04x)\n", + nport, rxport->ser.client->addr, + rxport->ser.client->adapter->nr, rxport->ser.client->addr); + + return 0; +} + +static void ub960_rxport_remove_serializer(struct ub960_data *priv, u8 nport) +{ + struct ub960_rxport *rxport = priv->rxports[nport]; + + i2c_unregister_device(rxport->ser.client); + rxport->ser.client = NULL; +} + +/* Add serializer i2c devices for all initialized ports */ +static int ub960_rxport_add_serializers(struct ub960_data *priv) +{ + unsigned int failed_nport; + int ret; + + for_each_active_rxport(priv, it) { + ret = ub960_rxport_add_serializer(priv, it.nport); + if (ret) { + failed_nport = it.nport; + goto err_remove_sers; + } + } + + return 0; + +err_remove_sers: + while (failed_nport--) { + struct ub960_rxport *rxport = priv->rxports[failed_nport]; + + if (!rxport) + continue; + + ub960_rxport_remove_serializer(priv, failed_nport); + } + + return ret; +} + +static void ub960_rxport_remove_serializers(struct ub960_data *priv) +{ + for_each_active_rxport(priv, it) + ub960_rxport_remove_serializer(priv, it.nport); +} + +static int ub960_init_tx_port(struct ub960_data *priv, + struct ub960_txport *txport) +{ + unsigned int nport = txport->nport; + u8 csi_ctl = 0; + + /* + * From the datasheet: "initial CSI Skew-Calibration + * sequence [...] should be set when operating at 1.6 Gbps" + */ + if (priv->tx_data_rate == MHZ(1600)) + csi_ctl |= UB960_TR_CSI_CTL_CSI_CAL_EN; + + csi_ctl |= (4 - txport->num_data_lanes) << 4; + + if (!txport->non_continous_clk) + csi_ctl |= UB960_TR_CSI_CTL_CSI_CONTS_CLOCK; + + return ub960_txport_write(priv, nport, UB960_TR_CSI_CTL, csi_ctl, NULL); +} + +static int ub960_init_tx_ports_ub960(struct ub960_data *priv) +{ + u8 speed_select; + + switch (priv->tx_data_rate) { + case MHZ(400): + speed_select = 3; + break; + case MHZ(800): + speed_select = 2; + break; + case MHZ(1200): + speed_select = 1; + break; + case MHZ(1600): + default: + speed_select = 0; + break; + } + + return ub960_write(priv, UB960_SR_CSI_PLL_CTL, speed_select, NULL); +} + +static int ub960_init_tx_ports_ub9702(struct ub960_data *priv) +{ + u8 speed_select; + u8 ana_pll_div; + u8 pll_div; + int ret = 0; + + switch (priv->tx_data_rate) { + case MHZ(400): + speed_select = 3; + pll_div = 0x10; + ana_pll_div = 0xa2; + break; + case MHZ(800): + speed_select = 2; + pll_div = 0x10; + ana_pll_div = 0x92; + break; + case MHZ(1200): + speed_select = 1; + pll_div = 0x18; + ana_pll_div = 0x90; + break; + case MHZ(1500): + speed_select = 0; + pll_div = 0x0f; + ana_pll_div = 0x82; + break; + case MHZ(1600): + default: + speed_select = 0; + pll_div = 0x10; + ana_pll_div = 0x82; + break; + case MHZ(2500): + speed_select = 0x10; + pll_div = 0x19; + ana_pll_div = 0x80; + break; + } + + ub960_write(priv, UB960_SR_CSI_PLL_CTL, speed_select, &ret); + ub960_write(priv, UB9702_SR_CSI_PLL_DIV, pll_div, &ret); + ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, + UB9702_IR_CSI_ANA_CSIPLL_REG_1, ana_pll_div, &ret); + + return ret; +} + +static int ub960_init_tx_ports(struct ub960_data *priv) +{ + int ret; + + if (priv->hw_data->chip_type == UB9702) + ret = ub960_init_tx_ports_ub9702(priv); + else + ret = ub960_init_tx_ports_ub960(priv); + + if (ret) + return ret; + + for (unsigned int nport = 0; nport < priv->hw_data->num_txports; + nport++) { + struct ub960_txport *txport = priv->txports[nport]; + + if (!txport) + continue; + + ret = ub960_init_tx_port(priv, txport); + if (ret) + return ret; + } + + return 0; +} + +static int ub960_init_rx_port_ub960(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int nport = rxport->nport; + u32 bc_freq_val; + int ret = 0; + + /* + * Back channel frequency select. + * Override FREQ_SELECT from the strap. + * 0 - 2.5 Mbps (DS90UB913A-Q1 / DS90UB933-Q1) + * 2 - 10 Mbps + * 6 - 50 Mbps (DS90UB953-Q1) + * + * Note that changing this setting will result in some errors on the back + * channel for a short period of time. + */ + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + bc_freq_val = 0; + break; + + case RXPORT_MODE_CSI2_NONSYNC: + bc_freq_val = 2; + break; + + case RXPORT_MODE_CSI2_SYNC: + bc_freq_val = 6; + break; + + default: + return -EINVAL; + } + + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_BC_FREQ_SEL_MASK, + bc_freq_val, &ret); + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + /* FPD3_MODE = RAW10 Mode (DS90UB913A-Q1 / DS90UB933-Q1 compatible) */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG, + UB960_RR_PORT_CONFIG_FPD3_MODE_MASK, + 0x3, &ret); + + /* + * RAW10_8BIT_CTL = 0b10 : 8-bit processing using upper 8 bits + */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, + UB960_RR_PORT_CONFIG2_RAW10_8BIT_CTL_MASK, + 0x2 << UB960_RR_PORT_CONFIG2_RAW10_8BIT_CTL_SHIFT, + &ret); + + break; + + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + /* Not implemented */ + return -EINVAL; + + case RXPORT_MODE_CSI2_SYNC: + case RXPORT_MODE_CSI2_NONSYNC: + /* CSI-2 Mode (DS90UB953-Q1 compatible) */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG, 0x3, + 0x0, &ret); + + break; + } + + /* LV_POLARITY & FV_POLARITY */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, 0x3, + rxport->lv_fv_pol, &ret); + + /* Enable all interrupt sources from this port */ + ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_HI, 0x07, &ret); + ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_LO, 0x7f, &ret); + + /* Enable I2C_PASS_THROUGH */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH, &ret); + + /* Enable I2C communication to the serializer via the alias addr */ + ub960_rxport_write(priv, nport, UB960_RR_SER_ALIAS_ID, + rxport->ser.alias << 1, &ret); + + /* Configure EQ related settings */ + ub960_rxport_config_eq(priv, nport); + + /* Enable RX port */ + ub960_update_bits(priv, UB960_SR_RX_PORT_CTL, BIT(nport), BIT(nport), + &ret); + + return ret; +} + +static int ub960_init_rx_ports_ub960(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + unsigned int port_lock_mask; + unsigned int port_mask; + int ret; + + for_each_active_rxport(priv, it) { + ret = ub960_init_rx_port_ub960(priv, it.rxport); + if (ret) + return ret; + } + + ret = ub960_reset(priv, false); + if (ret) + return ret; + + port_mask = 0; + + for_each_active_rxport(priv, it) + port_mask |= BIT(it.nport); + + ret = ub960_rxport_wait_locks(priv, port_mask, &port_lock_mask); + if (ret) + return ret; + + if (port_mask != port_lock_mask) { + ret = -EIO; + dev_err_probe(dev, ret, "Failed to lock all RX ports\n"); + return ret; + } + + /* Set temperature ramp on serializer */ + for_each_active_rxport(priv, it) { + ret = ub960_serializer_temp_ramp(it.rxport); + if (ret) + return ret; + + ub960_rxport_update_bits(priv, it.nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH, + &ret); + if (ret) + return ret; + } + + /* + * Clear any errors caused by switching the RX port settings while + * probing. + */ + ret = ub960_clear_rx_errors(priv); + if (ret) + return ret; + + return 0; +} + +/* + * UB9702 specific initial RX port configuration + */ + +static int ub960_turn_off_rxport_ub9702(struct ub960_data *priv, + unsigned int nport) +{ + int ret = 0; + + /* Disable RX port */ + ub960_update_bits(priv, UB960_SR_RX_PORT_CTL, BIT(nport), 0, &ret); + + /* Disable FPD Rx and FPD BC CMR */ + ub960_rxport_write(priv, nport, UB9702_RR_RX_CTL_2, 0x1b, &ret); + + /* Disable FPD BC Tx */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, BIT(4), 0, + &ret); + + /* Disable internal RX blocks */ + ub960_rxport_write(priv, nport, UB9702_RR_RX_CTL_1, 0x15, &ret); + + /* Disable AEQ */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_AEQ_CFG_2, 0x03, &ret); + + /* PI disabled and oDAC disabled */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_AEQ_CFG_4, 0x09, &ret); + + /* AEQ configured for disabled link */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_AEQ_CFG_1, 0x20, &ret); + + /* disable AEQ clock and DFE */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_AEQ_CFG_3, 0x45, &ret); + + /* Powerdown FPD3 CDR */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_FPD3_CDR_CTRL_SEL_5, 0x82, &ret); + + return ret; +} + +static int ub960_set_bc_drv_config_ub9702(struct ub960_data *priv, + unsigned int nport) +{ + u8 fpd_bc_ctl0; + u8 fpd_bc_ctl1; + u8 fpd_bc_ctl2; + int ret = 0; + + if (priv->rxports[nport]->cdr_mode == RXPORT_CDR_FPD4) { + /* Set FPD PBC drv into FPD IV mode */ + + fpd_bc_ctl0 = 0; + fpd_bc_ctl1 = 0; + fpd_bc_ctl2 = 0; + } else { + /* Set FPD PBC drv into FPD III mode */ + + fpd_bc_ctl0 = 2; + fpd_bc_ctl1 = 1; + fpd_bc_ctl2 = 5; + } + + ub960_ind_update_bits(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_FPD_BC_CTL0, GENMASK(7, 5), + fpd_bc_ctl0 << 5, &ret); + + ub960_ind_update_bits(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_FPD_BC_CTL1, BIT(6), + fpd_bc_ctl1 << 6, &ret); + + ub960_ind_update_bits(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_FPD_BC_CTL2, GENMASK(6, 3), + fpd_bc_ctl2 << 3, &ret); + + return ret; +} + +static int ub960_set_fpd4_sync_mode_ub9702(struct ub960_data *priv, + unsigned int nport) +{ + int ret = 0; + + /* FPD4 Sync Mode */ + ub960_rxport_write(priv, nport, UB9702_RR_CHANNEL_MODE, 0x0, &ret); + + /* BC_FREQ_SELECT = (PLL_FREQ/3200) Mbps */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_BC_FREQ_SEL_MASK, 6, &ret); + + if (ret) + return ret; + + ret = ub960_set_bc_drv_config_ub9702(priv, nport); + if (ret) + return ret; + + /* Set AEQ timer to 400us/step */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_SYSTEM_INIT_REG0, 0x2f, &ret); + + /* Disable FPD4 Auto Recovery */ + ub960_update_bits(priv, UB9702_SR_CSI_EXCLUSIVE_FWD2, GENMASK(5, 4), 0, + &ret); + + /* Enable RX port */ + ub960_update_bits(priv, UB960_SR_RX_PORT_CTL, BIT(nport), BIT(nport), + &ret); + + /* Enable FPD4 Auto Recovery */ + ub960_update_bits(priv, UB9702_SR_CSI_EXCLUSIVE_FWD2, GENMASK(5, 4), + BIT(4), &ret); + + return ret; +} + +static int ub960_set_fpd4_async_mode_ub9702(struct ub960_data *priv, + unsigned int nport) +{ + int ret = 0; + + /* FPD4 ASync Mode */ + ub960_rxport_write(priv, nport, UB9702_RR_CHANNEL_MODE, 0x1, &ret); + + /* 10Mbps w/ BC enabled */ + /* BC_FREQ_SELECT=(PLL_FREQ/3200) Mbps */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_BC_FREQ_SEL_MASK, 2, &ret); + + if (ret) + return ret; + + ret = ub960_set_bc_drv_config_ub9702(priv, nport); + if (ret) + return ret; + + /* Set AEQ timer to 400us/step */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_SYSTEM_INIT_REG0, 0x2f, &ret); + + /* Disable FPD4 Auto Recover */ + ub960_update_bits(priv, UB9702_SR_CSI_EXCLUSIVE_FWD2, GENMASK(5, 4), 0, + &ret); + + /* Enable RX port */ + ub960_update_bits(priv, UB960_SR_RX_PORT_CTL, BIT(nport), BIT(nport), + &ret); + + /* Enable FPD4 Auto Recovery */ + ub960_update_bits(priv, UB9702_SR_CSI_EXCLUSIVE_FWD2, GENMASK(5, 4), + BIT(4), &ret); + + return ret; +} + +static int ub960_set_fpd3_sync_mode_ub9702(struct ub960_data *priv, + unsigned int nport) +{ + int ret = 0; + + /* FPD3 Sync Mode */ + ub960_rxport_write(priv, nport, UB9702_RR_CHANNEL_MODE, 0x2, &ret); + + /* BC_FREQ_SELECT=(PLL_FREQ/3200) Mbps */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_BC_FREQ_SEL_MASK, 6, &ret); + + /* Set AEQ_LOCK_MODE = 1 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_FPD3_AEQ_CTRL_SEL_1, BIT(7), &ret); + + if (ret) + return ret; + + ret = ub960_set_bc_drv_config_ub9702(priv, nport); + if (ret) + return ret; + + /* Enable RX port */ + ub960_update_bits(priv, UB960_SR_RX_PORT_CTL, BIT(nport), BIT(nport), + &ret); + + return ret; +} + +static int ub960_set_raw10_dvp_mode_ub9702(struct ub960_data *priv, + unsigned int nport) +{ + int ret = 0; + + /* FPD3 RAW10 Mode */ + ub960_rxport_write(priv, nport, UB9702_RR_CHANNEL_MODE, 0x5, &ret); + + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_BC_FREQ_SEL_MASK, 0, &ret); + + /* Set AEQ_LOCK_MODE = 1 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_FPD3_AEQ_CTRL_SEL_1, BIT(7), &ret); + + /* + * RAW10_8BIT_CTL = 0b11 : 8-bit processing using lower 8 bits + * 0b10 : 8-bit processing using upper 8 bits + */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, 0x3 << 6, + 0x2 << 6, &ret); + + /* LV_POLARITY & FV_POLARITY */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, 0x3, + priv->rxports[nport]->lv_fv_pol, &ret); + + if (ret) + return ret; + + ret = ub960_set_bc_drv_config_ub9702(priv, nport); + if (ret) + return ret; + + /* Enable RX port */ + ub960_update_bits(priv, UB960_SR_RX_PORT_CTL, BIT(nport), BIT(nport), + &ret); + + return ret; +} + +static int ub960_configure_rx_port_ub9702(struct ub960_data *priv, + unsigned int nport) +{ + struct device *dev = &priv->client->dev; + struct ub960_rxport *rxport = priv->rxports[nport]; + int ret; + + if (!rxport) { + ret = ub960_turn_off_rxport_ub9702(priv, nport); + if (ret) + return ret; + + dev_dbg(dev, "rx%u: disabled\n", nport); + return 0; + } + + switch (rxport->cdr_mode) { + case RXPORT_CDR_FPD4: + switch (rxport->rx_mode) { + case RXPORT_MODE_CSI2_SYNC: + ret = ub960_set_fpd4_sync_mode_ub9702(priv, nport); + if (ret) + return ret; + + dev_dbg(dev, "rx%u: FPD-Link IV SYNC mode\n", nport); + break; + case RXPORT_MODE_CSI2_NONSYNC: + ret = ub960_set_fpd4_async_mode_ub9702(priv, nport); + if (ret) + return ret; + + dev_dbg(dev, "rx%u: FPD-Link IV ASYNC mode\n", nport); + break; + default: + dev_err(dev, "rx%u: unsupported FPD4 mode %u\n", nport, + rxport->rx_mode); + return -EINVAL; + } + break; + + case RXPORT_CDR_FPD3: + switch (rxport->rx_mode) { + case RXPORT_MODE_CSI2_SYNC: + ret = ub960_set_fpd3_sync_mode_ub9702(priv, nport); + if (ret) + return ret; + + dev_dbg(dev, "rx%u: FPD-Link III SYNC mode\n", nport); + break; + case RXPORT_MODE_RAW10: + ret = ub960_set_raw10_dvp_mode_ub9702(priv, nport); + if (ret) + return ret; + + dev_dbg(dev, "rx%u: FPD-Link III RAW10 DVP mode\n", + nport); + break; + default: + dev_err(&priv->client->dev, + "rx%u: unsupported FPD3 mode %u\n", nport, + rxport->rx_mode); + return -EINVAL; + } + break; + + default: + dev_err(&priv->client->dev, "rx%u: unsupported CDR mode %u\n", + nport, rxport->cdr_mode); + return -EINVAL; + } + + return 0; +} + +static int ub960_lock_recovery_ub9702(struct ub960_data *priv, + unsigned int nport) +{ + struct device *dev = &priv->client->dev; + /* Assumption that max AEQ should be under 16 */ + const u8 rx_aeq_limit = 16; + u8 prev_aeq = 0xff; + bool rx_lock; + + for (unsigned int retry = 0; retry < 3; ++retry) { + u8 port_sts1; + u8 rx_aeq; + int ret; + + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, + &port_sts1, NULL); + if (ret) + return ret; + + rx_lock = port_sts1 & UB960_RR_RX_PORT_STS1_PORT_PASS; + + if (!rx_lock) { + ret = ub960_rxport_lockup_wa_ub9702(priv); + if (ret) + return ret; + + /* Restart AEQ by changing max to 0 --> 0x23 */ + ret = ub960_write_ind(priv, + UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_AEQ_ALP_SEL7, 0, + NULL); + if (ret) + return ret; + + msleep(20); + + /* AEQ Restart */ + ret = ub960_write_ind(priv, + UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_AEQ_ALP_SEL7, + 0x23, NULL); + + if (ret) + return ret; + + msleep(20); + dev_dbg(dev, "rx%u: no lock, retry = %u\n", nport, + retry); + + continue; + } + + ret = ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_AEQ_ALP_SEL11, &rx_aeq, + NULL); + if (ret) + return ret; + + if (rx_aeq < rx_aeq_limit) { + dev_dbg(dev, + "rx%u: locked and AEQ normal before setting AEQ window\n", + nport); + return 0; + } + + if (rx_aeq != prev_aeq) { + ret = ub960_rxport_lockup_wa_ub9702(priv); + if (ret) + return ret; + + /* Restart AEQ by changing max to 0 --> 0x23 */ + ret = ub960_write_ind(priv, + UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_AEQ_ALP_SEL7, + 0, NULL); + if (ret) + return ret; + + msleep(20); + + /* AEQ Restart */ + ret = ub960_write_ind(priv, + UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_AEQ_ALP_SEL7, + 0x23, NULL); + if (ret) + return ret; + + msleep(20); + + dev_dbg(dev, + "rx%u: high AEQ at initial check recovery loop, retry=%u\n", + nport, retry); + + prev_aeq = rx_aeq; + } else { + dev_dbg(dev, + "rx%u: lossy cable detected, RX_AEQ %#x, RX_AEQ_LIMIT %#x, retry %u\n", + nport, rx_aeq, rx_aeq_limit, retry); + dev_dbg(dev, + "rx%u: will continue with initiation sequence but high AEQ\n", + nport); + return 0; + } + } + + dev_err(dev, "rx%u: max number of retries: %s\n", nport, + rx_lock ? "unstable AEQ" : "no lock"); + + return -EIO; +} + +static int ub960_enable_aeq_lms_ub9702(struct ub960_data *priv, + unsigned int nport) +{ + struct device *dev = &priv->client->dev; + u8 read_aeq_init; + int ret; + + ret = ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_AEQ_ALP_SEL11, &read_aeq_init, + NULL); + if (ret) + return ret; + + dev_dbg(dev, "rx%u: initial AEQ = %#x\n", nport, read_aeq_init); + + /* Set AEQ Min */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_AEQ_ALP_SEL6, read_aeq_init, &ret); + /* Set AEQ Max */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_AEQ_ALP_SEL7, read_aeq_init + 1, &ret); + /* Set AEQ offset to 0 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_AEQ_ALP_SEL10, 0x0, &ret); + + /* Enable AEQ tap2 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_EQ_CTRL_SEL_38, 0x00, &ret); + /* Set VGA Gain 1 Gain 2 override to 0 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_VGA_CTRL_SEL_8, 0x00, &ret); + /* Set VGA Initial Sweep Gain to 0 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_VGA_CTRL_SEL_6, 0x80, &ret); + /* Set VGA_Adapt (VGA Gain) override to 0 (thermometer encoded) */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_VGA_CTRL_SEL_3, 0x00, &ret); + /* Enable VGA_SWEEP */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_EQ_ADAPT_CTRL, 0x40, &ret); + /* Disable VGA_SWEEP_GAIN_OV, disable VGA_TUNE_OV */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_EQ_OVERRIDE_CTRL, 0x00, &ret); + + /* Set VGA HIGH Threshold to 43 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_VGA_CTRL_SEL_1, 0x2b, &ret); + /* Set VGA LOW Threshold to 18 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_VGA_CTRL_SEL_2, 0x12, &ret); + /* Set vga_sweep_th to 32 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_EQ_CTRL_SEL_15, 0x20, &ret); + /* Set AEQ timer to 400us/step and parity threshold to 7 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_SYSTEM_INIT_REG0, 0xef, &ret); + + if (ret) + return ret; + + dev_dbg(dev, "rx%u: enable FPD-Link IV AEQ LMS\n", nport); + + return 0; +} + +static int ub960_enable_dfe_lms_ub9702(struct ub960_data *priv, + unsigned int nport) +{ + struct device *dev = &priv->client->dev; + int ret = 0; + + /* Enable DFE LMS */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_EQ_CTRL_SEL_24, 0x40, &ret); + /* Disable VGA Gain1 override */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_GAIN_CTRL_0, 0x20, &ret); + + if (ret) + return ret; + + usleep_range(1000, 5000); + + /* Disable VGA Gain2 override */ + ret = ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB9702_IR_RX_ANA_GAIN_CTRL_0, 0x00, NULL); + if (ret) + return ret; + + dev_dbg(dev, "rx%u: enabled FPD-Link IV DFE LMS", nport); + + return 0; +} + +static int ub960_init_rx_ports_ub9702(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + unsigned int port_lock_mask; + unsigned int port_mask = 0; + bool have_fpd4 = false; + int ret; + + for_each_active_rxport(priv, it) { + ret = ub960_rxport_update_bits(priv, it.nport, + UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_BC_ALWAYS_ON, + UB960_RR_BCC_CONFIG_BC_ALWAYS_ON, + NULL); + if (ret) + return ret; + } + + /* Disable FPD4 Auto Recovery */ + ret = ub960_write(priv, UB9702_SR_CSI_EXCLUSIVE_FWD2, 0x0f, NULL); + if (ret) + return ret; + + for_each_active_rxport(priv, it) { + if (it.rxport->ser.addr >= 0) { + /* + * Set serializer's I2C address if set in the dts file, + * and freeze it to prevent updates from the FC. + */ + ub960_rxport_write(priv, it.nport, UB960_RR_SER_ID, + it.rxport->ser.addr << 1 | + UB960_RR_SER_ID_FREEZE_DEVICE_ID, + &ret); + } + + /* Set serializer I2C alias with auto-ack */ + ub960_rxport_write(priv, it.nport, UB960_RR_SER_ALIAS_ID, + it.rxport->ser.alias << 1 | + UB960_RR_SER_ALIAS_ID_AUTO_ACK, &ret); + + if (ret) + return ret; + } + + for_each_active_rxport(priv, it) { + if (fwnode_device_is_compatible(it.rxport->ser.fwnode, + "ti,ds90ub971-q1")) { + ret = ub960_rxport_bc_ser_config(it.rxport); + if (ret) + return ret; + } + } + + for_each_active_rxport_fpd4(priv, it) { + /* Hold state machine in reset */ + ub960_rxport_write(priv, it.nport, UB9702_RR_RX_SM_SEL_2, 0x10, + &ret); + + /* Set AEQ max to 0 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(it.nport), + UB9702_IR_RX_ANA_AEQ_ALP_SEL7, 0, &ret); + + if (ret) + return ret; + + dev_dbg(dev, + "rx%u: holding state machine and adjusting AEQ max to 0", + it.nport); + } + + for_each_active_rxport(priv, it) { + port_mask |= BIT(it.nport); + + if (it.rxport->cdr_mode == RXPORT_CDR_FPD4) + have_fpd4 = true; + } + + for_each_rxport(priv, it) { + ret = ub960_configure_rx_port_ub9702(priv, it.nport); + if (ret) + return ret; + } + + ret = ub960_reset(priv, false); + if (ret) + return ret; + + if (have_fpd4) { + for_each_active_rxport_fpd4(priv, it) { + /* Release state machine */ + ret = ub960_rxport_write(priv, it.nport, + UB9702_RR_RX_SM_SEL_2, 0x0, + NULL); + if (ret) + return ret; + + dev_dbg(dev, "rx%u: state machine released\n", + it.nport); + } + + /* Wait for SM to resume */ + fsleep(5000); + + for_each_active_rxport_fpd4(priv, it) { + ret = ub960_write_ind(priv, + UB960_IND_TARGET_RX_ANA(it.nport), + UB9702_IR_RX_ANA_AEQ_ALP_SEL7, + 0x23, NULL); + if (ret) + return ret; + + dev_dbg(dev, "rx%u: AEQ restart\n", it.nport); + } + + /* Wait for lock */ + fsleep(20000); + + for_each_active_rxport_fpd4(priv, it) { + ret = ub960_lock_recovery_ub9702(priv, it.nport); + if (ret) + return ret; + } + + for_each_active_rxport_fpd4(priv, it) { + ret = ub960_enable_aeq_lms_ub9702(priv, it.nport); + if (ret) + return ret; + } + + for_each_active_rxport_fpd4(priv, it) { + /* Hold state machine in reset */ + ret = ub960_rxport_write(priv, it.nport, + UB9702_RR_RX_SM_SEL_2, 0x10, + NULL); + if (ret) + return ret; + } + + ret = ub960_reset(priv, false); + if (ret) + return ret; + + for_each_active_rxport_fpd4(priv, it) { + /* Release state machine */ + ret = ub960_rxport_write(priv, it.nport, + UB9702_RR_RX_SM_SEL_2, 0, + NULL); + if (ret) + return ret; + } + } + + /* Wait time for stable lock */ + fsleep(15000); + + /* Set temperature ramp on serializer */ + for_each_active_rxport(priv, it) { + ret = ub960_serializer_temp_ramp(it.rxport); + if (ret) + return ret; + } + + for_each_active_rxport_fpd4(priv, it) { + ret = ub960_enable_dfe_lms_ub9702(priv, it.nport); + if (ret) + return ret; + } + + /* Wait for DFE and LMS to adapt */ + fsleep(5000); + + ret = ub960_rxport_wait_locks(priv, port_mask, &port_lock_mask); + if (ret) + return ret; + + if (port_mask != port_lock_mask) { + ret = -EIO; + dev_err_probe(dev, ret, "Failed to lock all RX ports\n"); + return ret; + } + + for_each_active_rxport(priv, it) { + /* Enable all interrupt sources from this port */ + ub960_rxport_write(priv, it.nport, UB960_RR_PORT_ICR_HI, 0x07, + &ret); + ub960_rxport_write(priv, it.nport, UB960_RR_PORT_ICR_LO, 0x7f, + &ret); + + /* Clear serializer I2C alias auto-ack */ + ub960_rxport_update_bits(priv, it.nport, UB960_RR_SER_ALIAS_ID, + UB960_RR_SER_ALIAS_ID_AUTO_ACK, 0, + &ret); + + /* Enable I2C_PASS_THROUGH */ + ub960_rxport_update_bits(priv, it.nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH, + &ret); + + if (ret) + return ret; + } + + /* Enable FPD4 Auto Recovery, Recovery loop active */ + ret = ub960_write(priv, UB9702_SR_CSI_EXCLUSIVE_FWD2, 0x18, NULL); + if (ret) + return ret; + + for_each_active_rxport_fpd4(priv, it) { + u8 final_aeq; + + ret = ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(it.nport), + UB9702_IR_RX_ANA_AEQ_ALP_SEL11, &final_aeq, + NULL); + if (ret) + return ret; + + dev_dbg(dev, "rx%u: final AEQ = %#x\n", it.nport, final_aeq); + } + + /* + * Clear any errors caused by switching the RX port settings while + * probing. + */ + + ret = ub960_clear_rx_errors(priv); + if (ret) + return ret; + + return 0; +} + +static int ub960_rxport_handle_events(struct ub960_data *priv, u8 nport) +{ + struct device *dev = &priv->client->dev; + u8 rx_port_sts1; + u8 rx_port_sts2; + u8 csi_rx_sts; + u8 bcc_sts; + int ret = 0; + + /* Read interrupts (also clears most of them) */ + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, &rx_port_sts1, + &ret); + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, &rx_port_sts2, + &ret); + ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS, &csi_rx_sts, &ret); + ub960_rxport_read(priv, nport, UB960_RR_BCC_STATUS, &bcc_sts, &ret); + + if (ret) + return ret; + + if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_PARITY_ERROR) { + u16 v; + + ret = ub960_rxport_read16(priv, nport, UB960_RR_RX_PAR_ERR_HI, + &v, NULL); + if (!ret) + dev_err(dev, "rx%u parity errors: %u\n", nport, v); + } + + if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_BCC_CRC_ERROR) + dev_err(dev, "rx%u BCC CRC error\n", nport); + + if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_BCC_SEQ_ERROR) + dev_err(dev, "rx%u BCC SEQ error\n", nport); + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_LINE_LEN_UNSTABLE) + dev_err(dev, "rx%u line length unstable\n", nport); + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_FPD3_ENCODE_ERROR) + dev_err(dev, "rx%u FPD3 encode error\n", nport); + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_BUFFER_ERROR) + dev_err(dev, "rx%u buffer error\n", nport); + + if (csi_rx_sts) + dev_err(dev, "rx%u CSI error: %#02x\n", nport, csi_rx_sts); + + if (csi_rx_sts & UB960_RR_CSI_RX_STS_ECC1_ERR) + dev_err(dev, "rx%u CSI ECC1 error\n", nport); + + if (csi_rx_sts & UB960_RR_CSI_RX_STS_ECC2_ERR) + dev_err(dev, "rx%u CSI ECC2 error\n", nport); + + if (csi_rx_sts & UB960_RR_CSI_RX_STS_CKSUM_ERR) + dev_err(dev, "rx%u CSI checksum error\n", nport); + + if (csi_rx_sts & UB960_RR_CSI_RX_STS_LENGTH_ERR) + dev_err(dev, "rx%u CSI length error\n", nport); + + if (bcc_sts) + dev_err(dev, "rx%u BCC error: %#02x\n", nport, bcc_sts); + + if (bcc_sts & UB960_RR_BCC_STATUS_RESP_ERR) + dev_err(dev, "rx%u BCC response error", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_SLAVE_TO) + dev_err(dev, "rx%u BCC slave timeout", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_SLAVE_ERR) + dev_err(dev, "rx%u BCC slave error", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_MASTER_TO) + dev_err(dev, "rx%u BCC master timeout", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_MASTER_ERR) + dev_err(dev, "rx%u BCC master error", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_SEQ_ERROR) + dev_err(dev, "rx%u BCC sequence error", nport); + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_LINE_LEN_CHG) { + u16 v; + + ret = ub960_rxport_read16(priv, nport, UB960_RR_LINE_LEN_1, + &v, NULL); + if (!ret) + dev_dbg(dev, "rx%u line len changed: %u\n", nport, v); + } + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_LINE_CNT_CHG) { + u16 v; + + ret = ub960_rxport_read16(priv, nport, UB960_RR_LINE_COUNT_HI, + &v, NULL); + if (!ret) + dev_dbg(dev, "rx%u line count changed: %u\n", nport, v); + } + + if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_LOCK_STS_CHG) { + dev_dbg(dev, "rx%u: %s, %s, %s, %s\n", nport, + (rx_port_sts1 & UB960_RR_RX_PORT_STS1_LOCK_STS) ? + "locked" : + "unlocked", + (rx_port_sts1 & UB960_RR_RX_PORT_STS1_PORT_PASS) ? + "passed" : + "not passed", + (rx_port_sts2 & UB960_RR_RX_PORT_STS2_CABLE_FAULT) ? + "no clock" : + "clock ok", + (rx_port_sts2 & UB960_RR_RX_PORT_STS2_FREQ_STABLE) ? + "stable freq" : + "unstable freq"); + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 + */ + +/* + * The current implementation only supports a simple VC mapping, where all VCs + * from a one RX port will be mapped to the same VC. Also, the hardware + * dictates that all streams from an RX port must go to a single TX port. + * + * This function decides the target VC numbers for each RX port with a simple + * algorithm, so that for each TX port, we get VC numbers starting from 0, + * and counting up. + * + * E.g. if all four RX ports are in use, of which the first two go to the + * first TX port and the secont two go to the second TX port, we would get + * the following VCs for the four RX ports: 0, 1, 0, 1. + * + * TODO: implement a more sophisticated VC mapping. As the driver cannot know + * what VCs the sinks expect (say, an FPGA with hardcoded VC routing), this + * probably needs to be somehow configurable. Device tree? + */ +static void ub960_get_vc_maps(struct ub960_data *priv, + struct v4l2_subdev_state *state, u8 *vc) +{ + u8 cur_vc[UB960_MAX_TX_NPORTS] = {}; + struct v4l2_subdev_route *route; + u8 handled_mask = 0; + + for_each_active_route(&state->routing, route) { + unsigned int rx, tx; + + rx = ub960_pad_to_port(priv, route->sink_pad); + if (BIT(rx) & handled_mask) + continue; + + tx = ub960_pad_to_port(priv, route->source_pad); + + vc[rx] = cur_vc[tx]++; + handled_mask |= BIT(rx); + } +} + +static int ub960_enable_tx_port(struct ub960_data *priv, unsigned int nport) +{ + struct device *dev = &priv->client->dev; + + dev_dbg(dev, "enable TX port %u\n", nport); + + return ub960_txport_update_bits(priv, nport, UB960_TR_CSI_CTL, + UB960_TR_CSI_CTL_CSI_ENABLE, + UB960_TR_CSI_CTL_CSI_ENABLE, NULL); +} + +static int ub960_disable_tx_port(struct ub960_data *priv, unsigned int nport) +{ + struct device *dev = &priv->client->dev; + + dev_dbg(dev, "disable TX port %u\n", nport); + + return ub960_txport_update_bits(priv, nport, UB960_TR_CSI_CTL, + UB960_TR_CSI_CTL_CSI_ENABLE, 0, NULL); +} + +static int ub960_enable_rx_port(struct ub960_data *priv, unsigned int nport) +{ + struct device *dev = &priv->client->dev; + + dev_dbg(dev, "enable RX port %u\n", nport); + + /* Enable forwarding */ + return ub960_update_bits(priv, UB960_SR_FWD_CTL1, + UB960_SR_FWD_CTL1_PORT_DIS(nport), 0, NULL); +} + +static int ub960_disable_rx_port(struct ub960_data *priv, unsigned int nport) +{ + struct device *dev = &priv->client->dev; + + dev_dbg(dev, "disable RX port %u\n", nport); + + /* Disable forwarding */ + return ub960_update_bits(priv, UB960_SR_FWD_CTL1, + UB960_SR_FWD_CTL1_PORT_DIS(nport), + UB960_SR_FWD_CTL1_PORT_DIS(nport), NULL); +} + +/* + * The driver only supports using a single VC for each source. This function + * checks that each source only provides streams using a single VC. + */ +static int ub960_validate_stream_vcs(struct ub960_data *priv) +{ + for_each_active_rxport(priv, it) { + struct v4l2_mbus_frame_desc desc; + int ret; + u8 vc; + + ret = v4l2_subdev_call(it.rxport->source.sd, pad, + get_frame_desc, it.rxport->source.pad, + &desc); + if (ret) + return ret; + + if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) + continue; + + if (desc.num_entries == 0) + continue; + + vc = desc.entry[0].bus.csi2.vc; + + for (unsigned int i = 1; i < desc.num_entries; i++) { + if (vc == desc.entry[i].bus.csi2.vc) + continue; + + dev_err(&priv->client->dev, + "rx%u: source with multiple virtual-channels is not supported\n", + it.nport); + return -ENODEV; + } + } + + return 0; +} + +static int ub960_configure_ports_for_streaming(struct ub960_data *priv, + struct v4l2_subdev_state *state) +{ + u8 fwd_ctl; + struct { + u32 num_streams; + u8 pixel_dt; + u8 meta_dt; + u32 meta_lines; + u32 tx_port; + } rx_data[UB960_MAX_RX_NPORTS] = {}; + u8 vc_map[UB960_MAX_RX_NPORTS] = {}; + struct v4l2_subdev_route *route; + int ret; + + ret = ub960_validate_stream_vcs(priv); + if (ret) + return ret; + + ub960_get_vc_maps(priv, state, vc_map); + + for_each_active_route(&state->routing, route) { + struct ub960_rxport *rxport; + struct ub960_txport *txport; + struct v4l2_mbus_framefmt *fmt; + const struct ub960_format_info *ub960_fmt; + unsigned int nport; + + nport = ub960_pad_to_port(priv, route->sink_pad); + + rxport = priv->rxports[nport]; + if (!rxport) + return -EINVAL; + + txport = priv->txports[ub960_pad_to_port(priv, route->source_pad)]; + if (!txport) + return -EINVAL; + + rx_data[nport].tx_port = ub960_pad_to_port(priv, route->source_pad); + + rx_data[nport].num_streams++; + + /* For the rest, we are only interested in parallel busses */ + if (rxport->rx_mode == RXPORT_MODE_CSI2_SYNC || + rxport->rx_mode == RXPORT_MODE_CSI2_NONSYNC) + continue; + + if (rx_data[nport].num_streams > 2) + return -EPIPE; + + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); + if (!fmt) + return -EPIPE; + + ub960_fmt = ub960_find_format(fmt->code); + if (!ub960_fmt) + return -EPIPE; + + if (ub960_fmt->meta) { + if (fmt->height > 3) { + dev_err(&priv->client->dev, + "rx%u: unsupported metadata height %u\n", + nport, fmt->height); + return -EPIPE; + } + + rx_data[nport].meta_dt = ub960_fmt->datatype; + rx_data[nport].meta_lines = fmt->height; + } else { + rx_data[nport].pixel_dt = ub960_fmt->datatype; + } + } + + /* Configure RX ports */ + + /* + * Keep all port forwardings disabled by default. Forwarding will be + * enabled in ub960_enable_rx_port. + */ + fwd_ctl = GENMASK(7, 4); + + for_each_active_rxport(priv, it) { + unsigned long nport = it.nport; + + u8 vc = vc_map[nport]; + + if (rx_data[nport].num_streams == 0) + continue; + + switch (it.rxport->rx_mode) { + case RXPORT_MODE_RAW10: + ub960_rxport_write(priv, nport, UB960_RR_RAW10_ID, + rx_data[nport].pixel_dt | (vc << UB960_RR_RAW10_ID_VC_SHIFT), + &ret); + + ub960_rxport_write(priv, nport, + UB960_RR_RAW_EMBED_DTYPE, + (rx_data[nport].meta_lines << UB960_RR_RAW_EMBED_DTYPE_LINES_SHIFT) | + rx_data[nport].meta_dt, &ret); + + break; + + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + /* Not implemented */ + break; + + case RXPORT_MODE_CSI2_SYNC: + case RXPORT_MODE_CSI2_NONSYNC: + if (priv->hw_data->chip_type == UB960 || + priv->hw_data->chip_type == UB954) { + /* Map all VCs from this port to the same VC */ + ub960_rxport_write(priv, nport, UB960_RR_CSI_VC_MAP, + (vc << UB960_RR_CSI_VC_MAP_SHIFT(3)) | + (vc << UB960_RR_CSI_VC_MAP_SHIFT(2)) | + (vc << UB960_RR_CSI_VC_MAP_SHIFT(1)) | + (vc << UB960_RR_CSI_VC_MAP_SHIFT(0)), + &ret); + } else { + unsigned int i; + + /* Map all VCs from this port to VC(nport) */ + for (i = 0; i < 8; i++) + ub960_rxport_write(priv, nport, + UB9702_RR_VC_ID_MAP(i), + (nport << 4) | nport, + &ret); + } + + break; + } + + if (rx_data[nport].tx_port == 1) + fwd_ctl |= BIT(nport); /* forward to TX1 */ + else + fwd_ctl &= ~BIT(nport); /* forward to TX0 */ + } + + ub960_write(priv, UB960_SR_FWD_CTL1, fwd_ctl, &ret); + + return ret; +} + +static void ub960_update_streaming_status(struct ub960_data *priv) +{ + unsigned int i; + + for (i = 0; i < UB960_MAX_NPORTS; i++) { + if (priv->stream_enable_mask[i]) + break; + } + + priv->streaming = i < UB960_MAX_NPORTS; +} + +static int ub960_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 source_pad, + u64 source_streams_mask) +{ + struct ub960_data *priv = sd_to_ub960(sd); + struct device *dev = &priv->client->dev; + u64 sink_streams[UB960_MAX_RX_NPORTS] = {}; + struct v4l2_subdev_route *route; + unsigned int failed_port; + int ret; + + if (!priv->streaming) { + dev_dbg(dev, "Prepare for streaming\n"); + ret = ub960_configure_ports_for_streaming(priv, state); + if (ret) + return ret; + } + + /* Enable TX port if not yet enabled */ + if (!priv->stream_enable_mask[source_pad]) { + ret = ub960_enable_tx_port(priv, + ub960_pad_to_port(priv, source_pad)); + if (ret) + return ret; + } + + priv->stream_enable_mask[source_pad] |= source_streams_mask; + + /* Collect sink streams per pad which we need to enable */ + for_each_active_route(&state->routing, route) { + unsigned int nport; + + if (route->source_pad != source_pad) + continue; + + if (!(source_streams_mask & BIT_ULL(route->source_stream))) + continue; + + nport = ub960_pad_to_port(priv, route->sink_pad); + + sink_streams[nport] |= BIT_ULL(route->sink_stream); + } + + for_each_rxport(priv, it) { + unsigned int nport = it.nport; + + if (!sink_streams[nport]) + continue; + + /* Enable the RX port if not yet enabled */ + if (!priv->stream_enable_mask[nport]) { + ret = ub960_enable_rx_port(priv, nport); + if (ret) { + failed_port = nport; + goto err; + } + } + + priv->stream_enable_mask[nport] |= sink_streams[nport]; + + dev_dbg(dev, "enable RX port %u streams %#llx\n", nport, + sink_streams[nport]); + + ret = v4l2_subdev_enable_streams( + priv->rxports[nport]->source.sd, + priv->rxports[nport]->source.pad, + sink_streams[nport]); + if (ret) { + priv->stream_enable_mask[nport] &= ~sink_streams[nport]; + + if (!priv->stream_enable_mask[nport]) + ub960_disable_rx_port(priv, nport); + + failed_port = nport; + goto err; + } + } + + priv->streaming = true; + + return 0; + +err: + for (unsigned int nport = 0; nport < failed_port; nport++) { + if (!sink_streams[nport]) + continue; + + dev_dbg(dev, "disable RX port %u streams %#llx\n", nport, + sink_streams[nport]); + + ret = v4l2_subdev_disable_streams( + priv->rxports[nport]->source.sd, + priv->rxports[nport]->source.pad, + sink_streams[nport]); + if (ret) + dev_err(dev, "Failed to disable streams: %d\n", ret); + + priv->stream_enable_mask[nport] &= ~sink_streams[nport]; + + /* Disable RX port if no active streams */ + if (!priv->stream_enable_mask[nport]) + ub960_disable_rx_port(priv, nport); + } + + priv->stream_enable_mask[source_pad] &= ~source_streams_mask; + + if (!priv->stream_enable_mask[source_pad]) + ub960_disable_tx_port(priv, + ub960_pad_to_port(priv, source_pad)); + + ub960_update_streaming_status(priv); + + return ret; +} + +static int ub960_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 source_pad, u64 source_streams_mask) +{ + struct ub960_data *priv = sd_to_ub960(sd); + struct device *dev = &priv->client->dev; + u64 sink_streams[UB960_MAX_RX_NPORTS] = {}; + struct v4l2_subdev_route *route; + int ret; + + /* Collect sink streams per pad which we need to disable */ + for_each_active_route(&state->routing, route) { + unsigned int nport; + + if (route->source_pad != source_pad) + continue; + + if (!(source_streams_mask & BIT_ULL(route->source_stream))) + continue; + + nport = ub960_pad_to_port(priv, route->sink_pad); + + sink_streams[nport] |= BIT_ULL(route->sink_stream); + } + + for_each_rxport(priv, it) { + unsigned int nport = it.nport; + + if (!sink_streams[nport]) + continue; + + dev_dbg(dev, "disable RX port %u streams %#llx\n", nport, + sink_streams[nport]); + + ret = v4l2_subdev_disable_streams( + priv->rxports[nport]->source.sd, + priv->rxports[nport]->source.pad, + sink_streams[nport]); + if (ret) + dev_err(dev, "Failed to disable streams: %d\n", ret); + + priv->stream_enable_mask[nport] &= ~sink_streams[nport]; + + /* Disable RX port if no active streams */ + if (!priv->stream_enable_mask[nport]) + ub960_disable_rx_port(priv, nport); + } + + /* Disable TX port if no active streams */ + + priv->stream_enable_mask[source_pad] &= ~source_streams_mask; + + if (!priv->stream_enable_mask[source_pad]) + ub960_disable_tx_port(priv, + ub960_pad_to_port(priv, source_pad)); + + ub960_update_streaming_status(priv); + + return 0; +} + +static int _ub960_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + static const struct v4l2_mbus_framefmt format = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .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, + }; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 | + V4L2_SUBDEV_ROUTING_NO_SINK_STREAM_MIX); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); + if (ret) + return ret; + + return 0; +} + +static int ub960_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct ub960_data *priv = sd_to_ub960(sd); + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->streaming) + return -EBUSY; + + return _ub960_set_routing(sd, state, routing); +} + +static int ub960_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct ub960_data *priv = sd_to_ub960(sd); + struct v4l2_subdev_route *route; + struct v4l2_subdev_state *state; + int ret = 0; + struct device *dev = &priv->client->dev; + u8 vc_map[UB960_MAX_RX_NPORTS] = {}; + + if (!ub960_pad_is_source(priv, pad)) + return -EINVAL; + + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + + state = v4l2_subdev_lock_and_get_active_state(&priv->sd); + + ub960_get_vc_maps(priv, state, vc_map); + + for_each_active_route(&state->routing, route) { + struct v4l2_mbus_frame_desc_entry *source_entry = NULL; + struct v4l2_mbus_frame_desc source_fd; + unsigned int nport; + unsigned int i; + + if (route->source_pad != pad) + continue; + + nport = ub960_pad_to_port(priv, route->sink_pad); + + ret = v4l2_subdev_call(priv->rxports[nport]->source.sd, pad, + get_frame_desc, + priv->rxports[nport]->source.pad, + &source_fd); + if (ret) { + dev_err(dev, + "Failed to get source frame desc for pad %u\n", + route->sink_pad); + goto out_unlock; + } + + for (i = 0; i < source_fd.num_entries; i++) { + if (source_fd.entry[i].stream == route->sink_stream) { + source_entry = &source_fd.entry[i]; + break; + } + } + + if (!source_entry) { + dev_err(dev, + "Failed to find stream from source frame desc\n"); + ret = -EPIPE; + goto out_unlock; + } + + fd->entry[fd->num_entries].stream = route->source_stream; + fd->entry[fd->num_entries].flags = source_entry->flags; + fd->entry[fd->num_entries].length = source_entry->length; + fd->entry[fd->num_entries].pixelcode = source_entry->pixelcode; + + fd->entry[fd->num_entries].bus.csi2.vc = vc_map[nport]; + + if (source_fd.type == V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { + fd->entry[fd->num_entries].bus.csi2.dt = + source_entry->bus.csi2.dt; + } else { + const struct ub960_format_info *ub960_fmt; + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(state, pad, + route->source_stream); + + if (!fmt) { + ret = -EINVAL; + goto out_unlock; + } + + ub960_fmt = ub960_find_format(fmt->code); + if (!ub960_fmt) { + dev_err(dev, "Unable to find format\n"); + ret = -EINVAL; + goto out_unlock; + } + + fd->entry[fd->num_entries].bus.csi2.dt = + ub960_fmt->datatype; + } + + fd->num_entries++; + } + +out_unlock: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int ub960_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct ub960_data *priv = sd_to_ub960(sd); + struct v4l2_mbus_framefmt *fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->streaming) + return -EBUSY; + + /* No transcoding, source and sink formats must match. */ + if (ub960_pad_is_source(priv, format->pad)) + return v4l2_subdev_get_fmt(sd, state, format); + + /* + * Default to the first format if the requested media bus code isn't + * supported. + */ + if (!ub960_find_format(format->format.code)) + format->format.code = ub960_formats[0].code; + + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + return 0; +} + +static int ub960_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct ub960_data *priv = sd_to_ub960(sd); + + struct v4l2_subdev_route routes[] = { + { + .sink_pad = 0, + .sink_stream = 0, + .source_pad = priv->hw_data->num_rxports, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return _ub960_set_routing(sd, state, &routing); +} + +static const struct v4l2_subdev_pad_ops ub960_pad_ops = { + .enable_streams = ub960_enable_streams, + .disable_streams = ub960_disable_streams, + + .set_routing = ub960_set_routing, + .get_frame_desc = ub960_get_frame_desc, + + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = ub960_set_fmt, +}; + +static int ub960_log_status_ub960_sp_eq(struct ub960_data *priv, + unsigned int nport) +{ + struct device *dev = &priv->client->dev; + u8 eq_level; + s8 strobe_pos; + int ret; + u8 v; + + /* Strobe */ + + ret = ub960_read(priv, UB960_XR_AEQ_CTL1, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "\t%s strobe\n", + (v & UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN) ? "Adaptive" : + "Manual"); + + if (v & UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN) { + ret = ub960_read(priv, UB960_XR_SFILTER_CFG, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "\tStrobe range [%d, %d]\n", + ((v >> UB960_XR_SFILTER_CFG_SFILTER_MIN_SHIFT) & 0xf) - 7, + ((v >> UB960_XR_SFILTER_CFG_SFILTER_MAX_SHIFT) & 0xf) - 7); + } + + ret = ub960_rxport_get_strobe_pos(priv, nport, &strobe_pos); + if (ret) + return ret; + + dev_info(dev, "\tStrobe pos %d\n", strobe_pos); + + /* EQ */ + + ret = ub960_rxport_read(priv, nport, UB960_RR_AEQ_BYPASS, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "\t%s EQ\n", + (v & UB960_RR_AEQ_BYPASS_ENABLE) ? "Manual" : + "Adaptive"); + + if (!(v & UB960_RR_AEQ_BYPASS_ENABLE)) { + ret = ub960_rxport_read(priv, nport, UB960_RR_AEQ_MIN_MAX, &v, + NULL); + if (ret) + return ret; + + dev_info(dev, "\tEQ range [%u, %u]\n", + (v >> UB960_RR_AEQ_MIN_MAX_AEQ_FLOOR_SHIFT) & 0xf, + (v >> UB960_RR_AEQ_MIN_MAX_AEQ_MAX_SHIFT) & 0xf); + } + + ret = ub960_rxport_get_eq_level(priv, nport, &eq_level); + if (ret) + return ret; + + dev_info(dev, "\tEQ level %u\n", eq_level); + + return 0; +} + +static int ub960_log_status(struct v4l2_subdev *sd) +{ + struct ub960_data *priv = sd_to_ub960(sd); + struct device *dev = &priv->client->dev; + struct v4l2_subdev_state *state; + u16 v16 = 0; + u8 v = 0; + u8 id[UB960_SR_FPD3_RX_ID_LEN]; + int ret = 0; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + for (unsigned int i = 0; i < sizeof(id); i++) { + ret = ub960_read(priv, UB960_SR_FPD3_RX_ID(i), &id[i], NULL); + if (ret) + return ret; + } + + dev_info(dev, "ID '%.*s'\n", (int)sizeof(id), id); + + for (unsigned int nport = 0; nport < priv->hw_data->num_txports; + nport++) { + struct ub960_txport *txport = priv->txports[nport]; + + dev_info(dev, "TX %u\n", nport); + + if (!txport) { + dev_info(dev, "\tNot initialized\n"); + continue; + } + + ret = ub960_txport_read(priv, nport, UB960_TR_CSI_STS, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "\tsync %u, pass %u\n", v & (u8)BIT(1), + v & (u8)BIT(0)); + + /* + * Frame counter, frame error counter, line counter and line error counter + * registers are marked as reserved in the UB954 datasheet. Hence restrict + * the following register reads only for UB960 and UB9702. + */ + if (priv->hw_data->chip_type == UB960 || priv->hw_data->chip_type == UB9702) { + ret = ub960_read16(priv, UB960_SR_CSI_FRAME_COUNT_HI(nport), + &v16, NULL); + if (ret) + return ret; + + dev_info(dev, "\tframe counter %u\n", v16); + + ret = ub960_read16(priv, UB960_SR_CSI_FRAME_ERR_COUNT_HI(nport), + &v16, NULL); + if (ret) + return ret; + + dev_info(dev, "\tframe error counter %u\n", v16); + + ret = ub960_read16(priv, UB960_SR_CSI_LINE_COUNT_HI(nport), + &v16, NULL); + if (ret) + return ret; + + dev_info(dev, "\tline counter %u\n", v16); + + ret = ub960_read16(priv, UB960_SR_CSI_LINE_ERR_COUNT_HI(nport), + &v16, NULL); + if (ret) + return ret; + + dev_info(dev, "\tline error counter %u\n", v16); + } + } + + for_each_rxport(priv, it) { + unsigned int nport = it.nport; + + dev_info(dev, "RX %u\n", nport); + + if (!it.rxport) { + dev_info(dev, "\tNot initialized\n"); + continue; + } + + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, &v, + NULL); + if (ret) + return ret; + + if (v & UB960_RR_RX_PORT_STS1_LOCK_STS) + dev_info(dev, "\tLocked\n"); + else + dev_info(dev, "\tNot locked\n"); + + dev_info(dev, "\trx_port_sts1 %#02x\n", v); + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, &v, + NULL); + if (ret) + return ret; + + dev_info(dev, "\trx_port_sts2 %#02x\n", v); + + ret = ub960_rxport_read16(priv, nport, UB960_RR_RX_FREQ_HIGH, + &v16, NULL); + if (ret) + return ret; + + dev_info(dev, "\tlink freq %llu Hz\n", ((u64)v16 * HZ_PER_MHZ) >> 8); + + ret = ub960_rxport_read16(priv, nport, UB960_RR_RX_PAR_ERR_HI, + &v16, NULL); + if (ret) + return ret; + + dev_info(dev, "\tparity errors %u\n", v16); + + ret = ub960_rxport_read16(priv, nport, UB960_RR_LINE_COUNT_HI, + &v16, NULL); + if (ret) + return ret; + + dev_info(dev, "\tlines per frame %u\n", v16); + + ret = ub960_rxport_read16(priv, nport, UB960_RR_LINE_LEN_1, + &v16, NULL); + if (ret) + return ret; + + dev_info(dev, "\tbytes per line %u\n", v16); + + ret = ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER, + &v, NULL); + if (ret) + return ret; + + dev_info(dev, "\tcsi_err_counter %u\n", v); + + if (priv->hw_data->chip_type == UB960 || priv->hw_data->chip_type == UB954) { + ret = ub960_log_status_ub960_sp_eq(priv, nport); + if (ret) + return ret; + } + + /* GPIOs */ + for (unsigned int i = 0; i < UB960_NUM_BC_GPIOS; i++) { + u8 ctl_reg; + u8 ctl_shift; + + ctl_reg = UB960_RR_BC_GPIO_CTL(i / 2); + ctl_shift = (i % 2) * 4; + + ret = ub960_rxport_read(priv, nport, ctl_reg, &v, NULL); + if (ret) + return ret; + + dev_info(dev, "\tGPIO%u: mode %u\n", i, + (v >> ctl_shift) & 0xf); + } + } + + v4l2_subdev_unlock_state(state); + + return 0; +} + +static const struct v4l2_subdev_core_ops ub960_subdev_core_ops = { + .log_status = ub960_log_status, +}; + +static const struct v4l2_subdev_internal_ops ub960_internal_ops = { + .init_state = ub960_init_state, +}; + +static const struct v4l2_subdev_ops ub960_subdev_ops = { + .core = &ub960_subdev_core_ops, + .pad = &ub960_pad_ops, +}; + +static const struct media_entity_operations ub960_entity_ops = { + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, + .link_validate = v4l2_subdev_link_validate, + .has_pad_interdep = v4l2_subdev_has_pad_interdep, +}; + +/* ----------------------------------------------------------------------------- + * Core + */ + +static irqreturn_t ub960_handle_events(int irq, void *arg) +{ + struct ub960_data *priv = arg; + u8 int_sts; + u8 fwd_sts; + int ret; + + ret = ub960_read(priv, UB960_SR_INTERRUPT_STS, &int_sts, NULL); + if (ret || !int_sts) + return IRQ_NONE; + + dev_dbg(&priv->client->dev, "INTERRUPT_STS %x\n", int_sts); + + ret = ub960_read(priv, UB960_SR_FWD_STS, &fwd_sts, NULL); + if (ret) + return IRQ_NONE; + + dev_dbg(&priv->client->dev, "FWD_STS %#02x\n", fwd_sts); + + for (unsigned int i = 0; i < priv->hw_data->num_txports; i++) { + if (int_sts & UB960_SR_INTERRUPT_STS_IS_CSI_TX(i)) { + ret = ub960_csi_handle_events(priv, i); + if (ret) + return IRQ_NONE; + } + } + + for_each_active_rxport(priv, it) { + if (int_sts & UB960_SR_INTERRUPT_STS_IS_RX(it.nport)) { + ret = ub960_rxport_handle_events(priv, it.nport); + if (ret) + return IRQ_NONE; + } + } + + return IRQ_HANDLED; +} + +static void ub960_handler_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct ub960_data *priv = + container_of(dwork, struct ub960_data, poll_work); + + ub960_handle_events(0, priv); + + schedule_delayed_work(&priv->poll_work, + msecs_to_jiffies(UB960_POLL_TIME_MS)); +} + +static void ub960_txport_free_ports(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport = 0; nport < priv->hw_data->num_txports; nport++) { + struct ub960_txport *txport = priv->txports[nport]; + + if (!txport) + continue; + + kfree(txport); + priv->txports[nport] = NULL; + } +} + +static void ub960_rxport_free_ports(struct ub960_data *priv) +{ + for_each_active_rxport(priv, it) { + fwnode_handle_put(it.rxport->source.ep_fwnode); + fwnode_handle_put(it.rxport->ser.fwnode); + + mutex_destroy(&it.rxport->aliased_addrs_lock); + + kfree(it.rxport); + priv->rxports[it.nport] = NULL; + } +} + +static int +ub960_parse_dt_rxport_link_properties(struct ub960_data *priv, + struct fwnode_handle *link_fwnode, + struct ub960_rxport *rxport) +{ + struct device *dev = &priv->client->dev; + unsigned int nport = rxport->nport; + u32 rx_mode; + u32 cdr_mode; + s32 strobe_pos; + u32 eq_level; + u32 ser_i2c_alias; + u32 ser_i2c_addr; + int ret; + + cdr_mode = RXPORT_CDR_FPD3; + + ret = fwnode_property_read_u32(link_fwnode, "ti,cdr-mode", &cdr_mode); + if (ret < 0 && ret != -EINVAL) { + dev_err(dev, "rx%u: failed to read '%s': %d\n", nport, + "ti,cdr-mode", ret); + return ret; + } + + if (cdr_mode > RXPORT_CDR_LAST) { + dev_err(dev, "rx%u: bad 'ti,cdr-mode' %u\n", nport, cdr_mode); + return -EINVAL; + } + + if (priv->hw_data->chip_family != FAMILY_FPD4 && cdr_mode == RXPORT_CDR_FPD4) { + dev_err(dev, "rx%u: FPD-Link 4 CDR not supported\n", nport); + return -EINVAL; + } + + rxport->cdr_mode = cdr_mode; + + ret = fwnode_property_read_u32(link_fwnode, "ti,rx-mode", &rx_mode); + if (ret < 0) { + dev_err(dev, "rx%u: failed to read '%s': %d\n", nport, + "ti,rx-mode", ret); + return ret; + } + + if (rx_mode > RXPORT_MODE_LAST) { + dev_err(dev, "rx%u: bad 'ti,rx-mode' %u\n", nport, rx_mode); + return -EINVAL; + } + + switch (rx_mode) { + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + dev_err(dev, "rx%u: unsupported 'ti,rx-mode' %u\n", nport, + rx_mode); + return -EINVAL; + default: + break; + } + + rxport->rx_mode = rx_mode; + + /* EQ & Strobe related */ + + /* Defaults */ + rxport->eq.manual_eq = false; + rxport->eq.aeq.eq_level_min = UB960_MIN_EQ_LEVEL; + rxport->eq.aeq.eq_level_max = UB960_MAX_EQ_LEVEL; + + ret = fwnode_property_read_u32(link_fwnode, "ti,strobe-pos", + &strobe_pos); + if (ret) { + if (ret != -EINVAL) { + dev_err(dev, "rx%u: failed to read '%s': %d\n", nport, + "ti,strobe-pos", ret); + return ret; + } + } else { + if (strobe_pos < UB960_MIN_MANUAL_STROBE_POS || + strobe_pos > UB960_MAX_MANUAL_STROBE_POS) { + dev_err(dev, "rx%u: illegal 'strobe-pos' value: %d\n", + nport, strobe_pos); + return -EINVAL; + } + + /* NOTE: ignored unless global manual strobe pos is also set */ + rxport->eq.strobe_pos = strobe_pos; + if (!priv->strobe.manual) + dev_warn(dev, + "rx%u: 'ti,strobe-pos' ignored as 'ti,manual-strobe' not set\n", + nport); + } + + ret = fwnode_property_read_u32(link_fwnode, "ti,eq-level", &eq_level); + if (ret) { + if (ret != -EINVAL) { + dev_err(dev, "rx%u: failed to read '%s': %d\n", nport, + "ti,eq-level", ret); + return ret; + } + } else { + if (eq_level > UB960_MAX_EQ_LEVEL) { + dev_err(dev, "rx%u: illegal 'ti,eq-level' value: %d\n", + nport, eq_level); + return -EINVAL; + } + + rxport->eq.manual_eq = true; + rxport->eq.manual.eq_level = eq_level; + } + + ret = fwnode_property_read_u32(link_fwnode, "i2c-alias", + &ser_i2c_alias); + if (ret) { + dev_err(dev, "rx%u: failed to read '%s': %d\n", nport, + "i2c-alias", ret); + return ret; + } + rxport->ser.alias = ser_i2c_alias; + + rxport->ser.fwnode = fwnode_get_named_child_node(link_fwnode, "serializer"); + if (!rxport->ser.fwnode) { + dev_err(dev, "rx%u: missing 'serializer' node\n", nport); + return -EINVAL; + } + + ret = fwnode_property_read_u32(rxport->ser.fwnode, "reg", + &ser_i2c_addr); + if (ret) + rxport->ser.addr = -EINVAL; + else + rxport->ser.addr = ser_i2c_addr; + + return 0; +} + +static int ub960_parse_dt_rxport_ep_properties(struct ub960_data *priv, + struct fwnode_handle *ep_fwnode, + struct ub960_rxport *rxport) +{ + struct device *dev = &priv->client->dev; + struct v4l2_fwnode_endpoint vep = {}; + unsigned int nport = rxport->nport; + bool hsync_hi; + bool vsync_hi; + int ret; + + rxport->source.ep_fwnode = fwnode_graph_get_remote_endpoint(ep_fwnode); + if (!rxport->source.ep_fwnode) { + dev_err(dev, "rx%u: no remote endpoint\n", nport); + return -ENODEV; + } + + /* We currently have properties only for RAW modes */ + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + break; + default: + return 0; + } + + vep.bus_type = V4L2_MBUS_PARALLEL; + ret = v4l2_fwnode_endpoint_parse(ep_fwnode, &vep); + if (ret) { + dev_err(dev, "rx%u: failed to parse endpoint data\n", nport); + goto err_put_source_ep_fwnode; + } + + hsync_hi = !!(vep.bus.parallel.flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH); + vsync_hi = !!(vep.bus.parallel.flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH); + + /* LineValid and FrameValid are inverse to the h/vsync active */ + rxport->lv_fv_pol = (hsync_hi ? UB960_RR_PORT_CONFIG2_LV_POL_LOW : 0) | + (vsync_hi ? UB960_RR_PORT_CONFIG2_FV_POL_LOW : 0); + + return 0; + +err_put_source_ep_fwnode: + fwnode_handle_put(rxport->source.ep_fwnode); + return ret; +} + +static int ub960_parse_dt_rxport(struct ub960_data *priv, unsigned int nport, + struct fwnode_handle *link_fwnode, + struct fwnode_handle *ep_fwnode) +{ + static const char *vpoc_names[UB960_MAX_RX_NPORTS] = { + "vpoc0", "vpoc1", "vpoc2", "vpoc3" + }; + struct device *dev = &priv->client->dev; + struct ub960_rxport *rxport; + int ret; + + rxport = kzalloc_obj(*rxport); + if (!rxport) + return -ENOMEM; + + priv->rxports[nport] = rxport; + + rxport->nport = nport; + rxport->priv = priv; + + ret = ub960_parse_dt_rxport_link_properties(priv, link_fwnode, rxport); + if (ret) + goto err_free_rxport; + + rxport->vpoc = devm_regulator_get_optional(dev, vpoc_names[nport]); + if (IS_ERR(rxport->vpoc)) { + ret = PTR_ERR(rxport->vpoc); + if (ret == -ENODEV) { + rxport->vpoc = NULL; + } else { + dev_err(dev, "rx%u: failed to get VPOC supply: %d\n", + nport, ret); + goto err_put_remote_fwnode; + } + } + + ret = ub960_parse_dt_rxport_ep_properties(priv, ep_fwnode, rxport); + if (ret) + goto err_put_remote_fwnode; + + mutex_init(&rxport->aliased_addrs_lock); + + return 0; + +err_put_remote_fwnode: + fwnode_handle_put(rxport->ser.fwnode); +err_free_rxport: + priv->rxports[nport] = NULL; + kfree(rxport); + return ret; +} + +static struct fwnode_handle * +ub960_fwnode_get_link_by_regs(struct fwnode_handle *links_fwnode, + unsigned int nport) +{ + struct fwnode_handle *link_fwnode; + int ret; + + fwnode_for_each_child_node(links_fwnode, link_fwnode) { + u32 link_num; + + if (!str_has_prefix(fwnode_get_name(link_fwnode), "link@")) + continue; + + ret = fwnode_property_read_u32(link_fwnode, "reg", &link_num); + if (ret) { + fwnode_handle_put(link_fwnode); + return NULL; + } + + if (nport == link_num) + return link_fwnode; + } + + return NULL; +} + +static int ub960_parse_dt_rxports(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + struct fwnode_handle *links_fwnode; + int ret; + + links_fwnode = fwnode_get_named_child_node(dev_fwnode(dev), "links"); + if (!links_fwnode) { + dev_err(dev, "'links' node missing\n"); + return -ENODEV; + } + + /* Defaults, recommended by TI */ + priv->strobe.min = 2; + priv->strobe.max = 3; + + priv->strobe.manual = fwnode_property_read_bool(links_fwnode, "ti,manual-strobe"); + + for_each_rxport(priv, it) { + struct fwnode_handle *link_fwnode; + struct fwnode_handle *ep_fwnode; + unsigned int nport = it.nport; + + link_fwnode = ub960_fwnode_get_link_by_regs(links_fwnode, nport); + if (!link_fwnode) + continue; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + nport, 0, 0); + if (!ep_fwnode) { + fwnode_handle_put(link_fwnode); + continue; + } + + ret = ub960_parse_dt_rxport(priv, nport, link_fwnode, + ep_fwnode); + + fwnode_handle_put(link_fwnode); + fwnode_handle_put(ep_fwnode); + + if (ret) { + dev_err(dev, "rx%u: failed to parse RX port\n", nport); + goto err_put_links; + } + } + + fwnode_handle_put(links_fwnode); + + return 0; + +err_put_links: + fwnode_handle_put(links_fwnode); + + return ret; +} + +static int ub960_parse_dt_txports(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + u32 nport; + int ret; + + for (nport = 0; nport < priv->hw_data->num_txports; nport++) { + unsigned int port = nport + priv->hw_data->num_rxports; + struct fwnode_handle *ep_fwnode; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + port, 0, 0); + if (!ep_fwnode) + continue; + + ret = ub960_parse_dt_txport(priv, ep_fwnode, nport); + + fwnode_handle_put(ep_fwnode); + + if (ret) + break; + } + + return 0; +} + +static int ub960_parse_dt(struct ub960_data *priv) +{ + int ret; + + ret = ub960_parse_dt_rxports(priv); + if (ret) + return ret; + + ret = ub960_parse_dt_txports(priv); + if (ret) + goto err_free_rxports; + + return 0; + +err_free_rxports: + ub960_rxport_free_ports(priv); + + return ret; +} + +static int ub960_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *asd) +{ + struct ub960_data *priv = sd_to_ub960(notifier->sd); + struct ub960_rxport *rxport = to_ub960_asd(asd)->rxport; + struct device *dev = &priv->client->dev; + u8 nport = rxport->nport; + int ret; + + ret = media_entity_get_fwnode_pad(&subdev->entity, + rxport->source.ep_fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "Failed to find pad for %s\n", subdev->name); + return ret; + } + + rxport->source.sd = subdev; + rxport->source.pad = ret; + + ret = media_create_pad_link(&rxport->source.sd->entity, + rxport->source.pad, &priv->sd.entity, nport, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:%u\n", + rxport->source.sd->name, rxport->source.pad, + priv->sd.name, nport); + return ret; + } + + for_each_active_rxport(priv, it) { + if (!it.rxport->source.sd) { + dev_dbg(dev, "Waiting for more subdevs to be bound\n"); + return 0; + } + } + + return 0; +} + +static void ub960_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *asd) +{ + struct ub960_rxport *rxport = to_ub960_asd(asd)->rxport; + + rxport->source.sd = NULL; +} + +static const struct v4l2_async_notifier_operations ub960_notify_ops = { + .bound = ub960_notify_bound, + .unbind = ub960_notify_unbind, +}; + +static int ub960_v4l2_notifier_register(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + int ret; + + v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd); + + for_each_active_rxport(priv, it) { + struct ub960_asd *asd; + + asd = v4l2_async_nf_add_fwnode(&priv->notifier, + it.rxport->source.ep_fwnode, + struct ub960_asd); + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev for source %u: %pe", + it.nport, asd); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + asd->rxport = it.rxport; + } + + priv->notifier.ops = &ub960_notify_ops; + + ret = v4l2_async_nf_register(&priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void ub960_v4l2_notifier_unregister(struct ub960_data *priv) +{ + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +} + +static int ub960_create_subdev(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + unsigned int i; + int ret; + + v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub960_subdev_ops); + priv->sd.internal_ops = &ub960_internal_ops; + + v4l2_ctrl_handler_init(&priv->ctrl_handler, 1); + priv->sd.ctrl_handler = &priv->ctrl_handler; + + v4l2_ctrl_new_int_menu(&priv->ctrl_handler, NULL, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(priv->tx_link_freq) - 1, 0, + priv->tx_link_freq); + + if (priv->ctrl_handler.error) { + ret = priv->ctrl_handler.error; + goto err_free_ctrl; + } + + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_STREAMS; + priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops = &ub960_entity_ops; + + for (i = 0; i < priv->hw_data->num_rxports + priv->hw_data->num_txports; i++) { + priv->pads[i].flags = ub960_pad_is_sink(priv, i) ? + MEDIA_PAD_FL_SINK : + MEDIA_PAD_FL_SOURCE; + } + + ret = media_entity_pads_init(&priv->sd.entity, + priv->hw_data->num_rxports + + priv->hw_data->num_txports, + priv->pads); + if (ret) + goto err_free_ctrl; + + priv->sd.state_lock = priv->sd.ctrl_handler->lock; + + ret = v4l2_subdev_init_finalize(&priv->sd); + if (ret) + goto err_entity_cleanup; + + ret = ub960_v4l2_notifier_register(priv); + if (ret) { + dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret); + goto err_subdev_cleanup; + } + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) { + dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret); + goto err_unreg_notif; + } + + return 0; + +err_unreg_notif: + ub960_v4l2_notifier_unregister(priv); +err_subdev_cleanup: + v4l2_subdev_cleanup(&priv->sd); +err_entity_cleanup: + media_entity_cleanup(&priv->sd.entity); +err_free_ctrl: + v4l2_ctrl_handler_free(&priv->ctrl_handler); + + return ret; +} + +static void ub960_destroy_subdev(struct ub960_data *priv) +{ + ub960_v4l2_notifier_unregister(priv); + v4l2_async_unregister_subdev(&priv->sd); + + v4l2_subdev_cleanup(&priv->sd); + + media_entity_cleanup(&priv->sd.entity); + v4l2_ctrl_handler_free(&priv->ctrl_handler); +} + +static const struct regmap_config ub960_regmap_config = { + .name = "ds90ub960", + + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + + /* + * We do locking in the driver to cover the TX/RX port selection and the + * indirect register access. + */ + .disable_locking = true, +}; + +static int ub960_get_hw_resources(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + + priv->regmap = devm_regmap_init_i2c(priv->client, &ub960_regmap_config); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->vddio = devm_regulator_get(dev, "vddio"); + if (IS_ERR(priv->vddio)) + return dev_err_probe(dev, PTR_ERR(priv->vddio), + "cannot get VDDIO regulator\n"); + + /* get power-down pin from DT */ + priv->pd_gpio = + devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH); + if (IS_ERR(priv->pd_gpio)) + return dev_err_probe(dev, PTR_ERR(priv->pd_gpio), + "Cannot get powerdown GPIO\n"); + + priv->refclk = devm_clk_get(dev, "refclk"); + if (IS_ERR(priv->refclk)) + return dev_err_probe(dev, PTR_ERR(priv->refclk), + "Cannot get REFCLK\n"); + + return 0; +} + +static int ub960_enable_core_hw(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + u8 rev_mask; + int ret; + u8 dev_sts; + u8 refclk_freq; + + ret = regulator_enable(priv->vddio); + if (ret) + return dev_err_probe(dev, ret, + "failed to enable VDDIO regulator\n"); + + ret = clk_prepare_enable(priv->refclk); + if (ret) { + dev_err_probe(dev, ret, "Failed to enable refclk\n"); + goto err_disable_vddio; + } + + if (priv->pd_gpio) { + gpiod_set_value_cansleep(priv->pd_gpio, 1); + /* wait min 2 ms for reset to complete */ + fsleep(2000); + gpiod_set_value_cansleep(priv->pd_gpio, 0); + /* wait min 2 ms for power up to finish */ + fsleep(2000); + } + + ret = ub960_reset(priv, true); + if (ret) + goto err_pd_gpio; + + /* Runtime check register accessibility */ + ret = ub960_read(priv, UB960_SR_REV_MASK, &rev_mask, NULL); + if (ret) { + dev_err_probe(dev, ret, "Cannot read first register, abort\n"); + goto err_pd_gpio; + } + + dev_dbg(dev, "Found %s (rev/mask %#04x)\n", priv->hw_data->model, + rev_mask); + + ret = ub960_read(priv, UB960_SR_DEVICE_STS, &dev_sts, NULL); + if (ret) + goto err_pd_gpio; + + /* + * UB954 REFCLK_FREQ is not synchronized, so multiple reads are recommended + * by the datasheet. However, a single read is practically seen to be + * sufficient and moreover it is only used for a debug print. + */ + if (priv->hw_data->chip_type == UB9702) + ret = ub960_read(priv, UB9702_SR_REFCLK_FREQ, &refclk_freq, + NULL); + else + ret = ub960_read(priv, UB960_XR_REFCLK_FREQ, &refclk_freq, + NULL); + if (ret) + goto err_pd_gpio; + + dev_dbg(dev, "refclk valid %u freq %u MHz (clk fw freq %lu MHz)\n", + !!(dev_sts & BIT(4)), refclk_freq, + clk_get_rate(priv->refclk) / HZ_PER_MHZ); + + /* Disable all RX ports by default */ + ret = ub960_write(priv, UB960_SR_RX_PORT_CTL, 0, NULL); + if (ret) + goto err_pd_gpio; + + /* release GPIO lock */ + if (priv->hw_data->chip_type == UB9702) { + ret = ub960_update_bits(priv, UB960_SR_RESET, + UB960_SR_RESET_GPIO_LOCK_RELEASE, + UB960_SR_RESET_GPIO_LOCK_RELEASE, + NULL); + if (ret) + goto err_pd_gpio; + } + + return 0; + +err_pd_gpio: + gpiod_set_value_cansleep(priv->pd_gpio, 1); + clk_disable_unprepare(priv->refclk); +err_disable_vddio: + regulator_disable(priv->vddio); + + return ret; +} + +static void ub960_disable_core_hw(struct ub960_data *priv) +{ + gpiod_set_value_cansleep(priv->pd_gpio, 1); + clk_disable_unprepare(priv->refclk); + regulator_disable(priv->vddio); +} + +static int ub960_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ub960_data *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + + priv->hw_data = device_get_match_data(dev); + + mutex_init(&priv->reg_lock); + + INIT_DELAYED_WORK(&priv->poll_work, ub960_handler_work); + + /* + * Initialize these to invalid values so that the first reg writes will + * configure the target. + */ + priv->reg_current.indirect_target = 0xff; + priv->reg_current.rxport = 0xff; + priv->reg_current.txport = 0xff; + + ret = ub960_get_hw_resources(priv); + if (ret) + goto err_mutex_destroy; + + ret = ub960_enable_core_hw(priv); + if (ret) + goto err_mutex_destroy; + + ret = ub960_parse_dt(priv); + if (ret) + goto err_disable_core_hw; + + ret = ub960_init_tx_ports(priv); + if (ret) + goto err_free_ports; + + ret = ub960_rxport_enable_vpocs(priv); + if (ret) + goto err_free_ports; + + if (priv->hw_data->chip_type == UB9702) + ret = ub960_init_rx_ports_ub9702(priv); + else + ret = ub960_init_rx_ports_ub960(priv); + + if (ret) + goto err_disable_vpocs; + + ret = ub960_init_atr(priv); + if (ret) + goto err_disable_vpocs; + + ret = ub960_rxport_add_serializers(priv); + if (ret) + goto err_uninit_atr; + + ret = ub960_create_subdev(priv); + if (ret) + goto err_free_sers; + + if (client->irq) + dev_warn(dev, "irq support not implemented, using polling\n"); + + schedule_delayed_work(&priv->poll_work, + msecs_to_jiffies(UB960_POLL_TIME_MS)); + +#ifdef UB960_DEBUG_I2C_RX_ID + for_each_rxport(priv, it) + ub960_write(priv, UB960_SR_I2C_RX_ID(it.nport), + (UB960_DEBUG_I2C_RX_ID + it.nport) << 1, NULL); +#endif + + return 0; + +err_free_sers: + ub960_rxport_remove_serializers(priv); +err_uninit_atr: + ub960_uninit_atr(priv); +err_disable_vpocs: + ub960_rxport_disable_vpocs(priv); +err_free_ports: + ub960_rxport_free_ports(priv); + ub960_txport_free_ports(priv); +err_disable_core_hw: + ub960_disable_core_hw(priv); +err_mutex_destroy: + mutex_destroy(&priv->reg_lock); + return ret; +} + +static void ub960_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ub960_data *priv = sd_to_ub960(sd); + + cancel_delayed_work_sync(&priv->poll_work); + + ub960_destroy_subdev(priv); + ub960_rxport_remove_serializers(priv); + ub960_uninit_atr(priv); + ub960_rxport_disable_vpocs(priv); + ub960_rxport_free_ports(priv); + ub960_txport_free_ports(priv); + ub960_disable_core_hw(priv); + mutex_destroy(&priv->reg_lock); +} + +static const struct ub960_hw_data ds90ub954_hw = { + .model = "ub954", + .chip_type = UB954, + .chip_family = FAMILY_FPD3, + .num_rxports = 2, + .num_txports = 1, +}; + +static const struct ub960_hw_data ds90ub960_hw = { + .model = "ub960", + .chip_type = UB960, + .chip_family = FAMILY_FPD3, + .num_rxports = 4, + .num_txports = 2, +}; + +static const struct ub960_hw_data ds90ub9702_hw = { + .model = "ub9702", + .chip_type = UB9702, + .chip_family = FAMILY_FPD4, + .num_rxports = 4, + .num_txports = 2, +}; + +static const struct i2c_device_id ub960_id[] = { + { .name = "ds90ub954-q1", .driver_data = (kernel_ulong_t)&ds90ub954_hw }, + { .name = "ds90ub960-q1", .driver_data = (kernel_ulong_t)&ds90ub960_hw }, + { .name = "ds90ub9702-q1", .driver_data = (kernel_ulong_t)&ds90ub9702_hw }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ub960_id); + +static const struct of_device_id ub960_dt_ids[] = { + { .compatible = "ti,ds90ub954-q1", .data = &ds90ub954_hw }, + { .compatible = "ti,ds90ub960-q1", .data = &ds90ub960_hw }, + { .compatible = "ti,ds90ub9702-q1", .data = &ds90ub9702_hw }, + {} +}; +MODULE_DEVICE_TABLE(of, ub960_dt_ids); + +static struct i2c_driver ds90ub960_driver = { + .probe = ub960_probe, + .remove = ub960_remove, + .id_table = ub960_id, + .driver = { + .name = "ds90ub960", + .of_match_table = ub960_dt_ids, + }, +}; +module_i2c_driver(ds90ub960_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Texas Instruments FPD-Link III/IV Deserializers Driver"); +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_AUTHOR("Tomi Valkeinen "); +MODULE_IMPORT_NS("I2C_ATR"); diff --git a/drivers/media/i2c/dw9714.c b/drivers/media/i2c/dw9714.c new file mode 100644 index 000000000..1d686b1d2 --- /dev/null +++ b/drivers/media/i2c/dw9714.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2015--2017 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DW9714_NAME "dw9714" +#define DW9714_MAX_FOCUS_POS 1023 +/* + * This sets the minimum granularity for the focus positions. + * A value of 1 gives maximum accuracy for a desired focus position + */ +#define DW9714_FOCUS_STEPS 1 +/* + * This acts as the minimum granularity of lens movement. + * Keep this value power of 2, so the control steps can be + * uniformly adjusted for gradual lens movement, with desired + * number of control steps. + */ +#define DW9714_CTRL_STEPS 16 +#define DW9714_CTRL_DELAY_US 1000 +/* + * S[3:2] = 0x00, codes per step for "Linear Slope Control" + * S[1:0] = 0x00, step period + */ +#define DW9714_DEFAULT_S 0x0 +#define DW9714_VAL(data, s) ((data) << 4 | (s)) + +/* dw9714 device structure */ +struct dw9714_device { + struct v4l2_ctrl_handler ctrls_vcm; + struct v4l2_subdev sd; + u16 current_val; + struct regulator *vcc; + struct gpio_desc *powerdown_gpio; +}; + +static inline struct dw9714_device *to_dw9714_vcm(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct dw9714_device, ctrls_vcm); +} + +static inline struct dw9714_device *sd_to_dw9714_vcm(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct dw9714_device, sd); +} + +static int dw9714_i2c_write(struct i2c_client *client, u16 data) +{ + int ret; + __be16 val = cpu_to_be16(data); + + ret = i2c_master_send(client, (const char *)&val, sizeof(val)); + if (ret != sizeof(val)) { + dev_err(&client->dev, "I2C write fail\n"); + return -EIO; + } + return 0; +} + +static int dw9714_t_focus_vcm(struct dw9714_device *dw9714_dev, u16 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dw9714_dev->sd); + + dw9714_dev->current_val = val; + + return dw9714_i2c_write(client, DW9714_VAL(val, DW9714_DEFAULT_S)); +} + +static int dw9714_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dw9714_device *dev_vcm = to_dw9714_vcm(ctrl); + + if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) + return dw9714_t_focus_vcm(dev_vcm, ctrl->val); + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops dw9714_vcm_ctrl_ops = { + .s_ctrl = dw9714_set_ctrl, +}; + +static int dw9714_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return pm_runtime_resume_and_get(sd->dev); +} + +static int dw9714_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops dw9714_int_ops = { + .open = dw9714_open, + .close = dw9714_close, +}; + +static const struct v4l2_subdev_core_ops dw9714_core_ops = { + .log_status = v4l2_ctrl_subdev_log_status, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops dw9714_ops = { + .core = &dw9714_core_ops, +}; + +static void dw9714_subdev_cleanup(struct dw9714_device *dw9714_dev) +{ + v4l2_async_unregister_subdev(&dw9714_dev->sd); + v4l2_ctrl_handler_free(&dw9714_dev->ctrls_vcm); + media_entity_cleanup(&dw9714_dev->sd.entity); +} + +static int dw9714_init_controls(struct dw9714_device *dev_vcm) +{ + struct v4l2_ctrl_handler *hdl = &dev_vcm->ctrls_vcm; + const struct v4l2_ctrl_ops *ops = &dw9714_vcm_ctrl_ops; + + v4l2_ctrl_handler_init(hdl, 1); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, + 0, DW9714_MAX_FOCUS_POS, DW9714_FOCUS_STEPS, 0); + + if (hdl->error) + dev_err(dev_vcm->sd.dev, "%s fail error: 0x%x\n", + __func__, hdl->error); + dev_vcm->sd.ctrl_handler = hdl; + return hdl->error; +} + +static int dw9714_power_up(struct dw9714_device *dw9714_dev) +{ + int ret; + + ret = regulator_enable(dw9714_dev->vcc); + if (ret) + return ret; + + gpiod_set_value_cansleep(dw9714_dev->powerdown_gpio, 0); + + usleep_range(12000, 14000); + + return 0; +} + +static int dw9714_power_down(struct dw9714_device *dw9714_dev) +{ + gpiod_set_value_cansleep(dw9714_dev->powerdown_gpio, 1); + + return regulator_disable(dw9714_dev->vcc); +} + +static int dw9714_probe(struct i2c_client *client) +{ + struct dw9714_device *dw9714_dev; + int rval; + + dw9714_dev = devm_kzalloc(&client->dev, sizeof(*dw9714_dev), + GFP_KERNEL); + if (!dw9714_dev) + return -ENOMEM; + + dw9714_dev->vcc = devm_regulator_get(&client->dev, "vcc"); + if (IS_ERR(dw9714_dev->vcc)) + return PTR_ERR(dw9714_dev->vcc); + + dw9714_dev->powerdown_gpio = devm_gpiod_get_optional(&client->dev, + "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(dw9714_dev->powerdown_gpio)) + return dev_err_probe(&client->dev, + PTR_ERR(dw9714_dev->powerdown_gpio), + "could not get powerdown gpio\n"); + + rval = dw9714_power_up(dw9714_dev); + if (rval) + return dev_err_probe(&client->dev, rval, + "failed to power up: %d\n", rval); + + v4l2_i2c_subdev_init(&dw9714_dev->sd, client, &dw9714_ops); + dw9714_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + dw9714_dev->sd.internal_ops = &dw9714_int_ops; + + rval = dw9714_init_controls(dw9714_dev); + if (rval) + goto err_cleanup; + + rval = media_entity_pads_init(&dw9714_dev->sd.entity, 0, NULL); + if (rval < 0) + goto err_cleanup; + + dw9714_dev->sd.entity.function = MEDIA_ENT_F_LENS; + + rval = v4l2_async_register_subdev(&dw9714_dev->sd); + if (rval < 0) + goto err_cleanup; + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +err_cleanup: + dw9714_power_down(dw9714_dev); + v4l2_ctrl_handler_free(&dw9714_dev->ctrls_vcm); + media_entity_cleanup(&dw9714_dev->sd.entity); + + return rval; +} + +static void dw9714_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd); + int ret; + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + ret = dw9714_power_down(dw9714_dev); + if (ret) { + dev_err(&client->dev, + "Failed to power down: %d\n", ret); + } + } + pm_runtime_set_suspended(&client->dev); + dw9714_subdev_cleanup(dw9714_dev); +} + +/* + * This function sets the vcm position, so it consumes least current + * The lens position is gradually moved in units of DW9714_CTRL_STEPS, + * to make the movements smoothly. + */ +static int __maybe_unused dw9714_vcm_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd); + int ret, val; + + if (pm_runtime_suspended(&client->dev)) + return 0; + + for (val = dw9714_dev->current_val & ~(DW9714_CTRL_STEPS - 1); + val >= 0; val -= DW9714_CTRL_STEPS) { + ret = dw9714_i2c_write(client, + DW9714_VAL(val, DW9714_DEFAULT_S)); + if (ret) + dev_err_once(dev, "%s I2C failure: %d", __func__, ret); + usleep_range(DW9714_CTRL_DELAY_US, DW9714_CTRL_DELAY_US + 10); + } + + ret = dw9714_power_down(dw9714_dev); + if (ret) + dev_err(dev, "Failed to power down: %d\n", ret); + + return ret; +} + +/* + * This function sets the vcm position to the value set by the user + * through v4l2_ctrl_ops s_ctrl handler + * The lens position is gradually moved in units of DW9714_CTRL_STEPS, + * to make the movements smoothly. + */ +static int __maybe_unused dw9714_vcm_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd); + int ret, val; + + if (pm_runtime_suspended(&client->dev)) + return 0; + + ret = dw9714_power_up(dw9714_dev); + if (ret) { + dev_err(dev, "Failed to power up: %d\n", ret); + return ret; + } + + for (val = dw9714_dev->current_val % DW9714_CTRL_STEPS; + val < dw9714_dev->current_val + DW9714_CTRL_STEPS - 1; + val += DW9714_CTRL_STEPS) { + ret = dw9714_i2c_write(client, + DW9714_VAL(val, DW9714_DEFAULT_S)); + if (ret) + dev_err_ratelimited(dev, "%s I2C failure: %d", + __func__, ret); + usleep_range(DW9714_CTRL_DELAY_US, DW9714_CTRL_DELAY_US + 10); + } + + return 0; +} + +static const struct i2c_device_id dw9714_id_table[] = { + { .name = DW9714_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, dw9714_id_table); + +static const struct of_device_id dw9714_of_table[] = { + { .compatible = "dongwoon,dw9714" }, + { { 0 } } +}; +MODULE_DEVICE_TABLE(of, dw9714_of_table); + +static const struct dev_pm_ops dw9714_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dw9714_vcm_suspend, dw9714_vcm_resume) + SET_RUNTIME_PM_OPS(dw9714_vcm_suspend, dw9714_vcm_resume, NULL) +}; + +static struct i2c_driver dw9714_i2c_driver = { + .driver = { + .name = DW9714_NAME, + .pm = &dw9714_pm_ops, + .of_match_table = dw9714_of_table, + }, + .probe = dw9714_probe, + .remove = dw9714_remove, + .id_table = dw9714_id_table, +}; + +module_i2c_driver(dw9714_i2c_driver); + +MODULE_AUTHOR("Tianshu Qiu "); +MODULE_AUTHOR("Jian Xu Zheng"); +MODULE_AUTHOR("Yuning Pu"); +MODULE_AUTHOR("Jouni Ukkonen"); +MODULE_AUTHOR("Tommi Franttila"); +MODULE_DESCRIPTION("DW9714 VCM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c new file mode 100644 index 000000000..3b7ba88fd --- /dev/null +++ b/drivers/media/i2c/dw9719.c @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2012 Intel Corporation + +/* + * Based on linux/modules/camera/drivers/media/i2c/imx/dw9719.c from: + * https://github.com/ZenfoneArea/android_kernel_asus_zenfone5 and + * latte-l-oss/drivers/external_drivers/camera/drivers/media/i2c/micam/dw9761.c + * from: https://github.com/MiCode/Xiaomi_Kernel_OpenSource/ + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define DW9719_MAX_FOCUS_POS 1023 +#define DW9719_CTRL_STEPS 16 +#define DW9719_CTRL_DELAY_US 1000 + +#define DW9718S_PD CCI_REG8(0) + +#define DW9718S_CONTROL CCI_REG8(1) +#define DW9718S_CONTROL_SW_LINEAR BIT(0) +#define DW9718S_CONTROL_SAC_SHIFT 1 +#define DW9718S_CONTROL_SAC_MASK 0x7 +#define DW9718S_CONTROL_OCP_DISABLE BIT(4) +#define DW9718S_CONTROL_UVLO_DISABLE BIT(5) +#define DW9718S_DEFAULT_SAC 4 + +#define DW9718S_VCM_CURRENT CCI_REG16(2) + +#define DW9718S_SW CCI_REG8(4) +#define DW9718S_SW_VCM_FREQ_MASK 0xF +#define DW9718S_DEFAULT_VCM_FREQ 0 + +#define DW9718S_SACT CCI_REG8(5) +#define DW9718S_SACT_PERIOD_8_8MS 0x19 + +#define DW9719_INFO CCI_REG8(0) +#define DW9719_ID 0xF1 +#define DW9761_ID 0xF4 + +#define DW9719_CONTROL CCI_REG8(2) +#define DW9719_STANDBY 0x00 +#define DW9719_SHUTDOWN 0x01 +#define DW9719_ENABLE_RINGING 0x02 + +#define DW9719_VCM_CURRENT CCI_REG16(3) + +#define DW9719_STATUS CCI_REG16(5) +#define DW9719_STATUS_BUSY BIT(0) + +#define DW9719_MODE CCI_REG8(6) +#define DW9719_MODE_SAC_SHIFT 4 +#define DW9719_DEFAULT_SAC 4 +#define DW9761_DEFAULT_SAC 6 + +#define DW9719_VCM_FREQ CCI_REG8(7) +#define DW9719_DEFAULT_VCM_FREQ 0x60 +#define DW9761_DEFAULT_VCM_FREQ 0x3E + +#define DW9761_VCM_PRELOAD CCI_REG8(8) +#define DW9761_DEFAULT_VCM_PRELOAD 0x73 + +#define DW9800K_DEFAULT_SAC 1 +#define DW9800K_MODE_SAC_SHIFT 6 +#define DW9800K_DEFAULT_VCM_FREQ 0x10 + +#define to_dw9719_device(x) container_of(x, struct dw9719_device, sd) + +enum dw9719_model { + DW9718S, + DW9719, + DW9761, + DW9800K, +}; + +struct dw9719_device { + struct v4l2_subdev sd; + struct device *dev; + struct regmap *regmap; + struct regulator *regulator; + enum dw9719_model model; + u32 mode_low_bits; + u32 sac_mode; + u32 vcm_freq; + + struct dw9719_v4l2_ctrls { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *focus; + } ctrls; +}; + +static int dw9719_power_down(struct dw9719_device *dw9719) +{ + u32 reg_pwr = dw9719->model == DW9718S ? DW9718S_PD : DW9719_CONTROL; + + /* + * Worth engaging the internal SHUTDOWN mode especially due to the + * regulator being potentially shared with other devices. + */ + if (cci_write(dw9719->regmap, reg_pwr, DW9719_SHUTDOWN, NULL)) + dev_err(dw9719->dev, "Error writing to power register\n"); + return regulator_disable(dw9719->regulator); +} + +static int dw9719_power_up(struct dw9719_device *dw9719, bool detect) +{ + u32 reg_pwr = dw9719->model == DW9718S ? DW9718S_PD : DW9719_CONTROL; + u64 val; + int ret; + int err; + + ret = regulator_enable(dw9719->regulator); + if (ret) + return ret; + + /* + * Need 100us to transition from SHUTDOWN to STANDBY. + * Jiggle the SCL pin to wake up the device (even when the regulator is + * shared) and wait double the time to be sure, as 100us is not enough + * at least on the DW9718S as found on the motorola-nora smartphone, + * then retry the write. + */ + cci_write(dw9719->regmap, reg_pwr, DW9719_STANDBY, NULL); + /* the jiggle is expected to fail, don't even log that as error */ + fsleep(200); + cci_write(dw9719->regmap, reg_pwr, DW9719_STANDBY, &ret); + + if (detect) { + /* These models do not have an INFO register */ + switch (dw9719->model) { + case DW9718S: + dw9719->sac_mode = DW9718S_DEFAULT_SAC; + dw9719->vcm_freq = DW9718S_DEFAULT_VCM_FREQ; + goto props; + case DW9800K: + dw9719->sac_mode = DW9800K_DEFAULT_SAC; + dw9719->vcm_freq = DW9800K_DEFAULT_VCM_FREQ; + goto props; + default: + break; + } + + ret = cci_read(dw9719->regmap, DW9719_INFO, &val, NULL); + if (ret < 0) + return ret; + + switch (val) { + case DW9719_ID: + dw9719->model = DW9719; + dw9719->mode_low_bits = 0x00; + dw9719->sac_mode = DW9719_DEFAULT_SAC; + dw9719->vcm_freq = DW9719_DEFAULT_VCM_FREQ; + break; + case DW9761_ID: + dw9719->model = DW9761; + dw9719->mode_low_bits = 0x01; + dw9719->sac_mode = DW9761_DEFAULT_SAC; + dw9719->vcm_freq = DW9761_DEFAULT_VCM_FREQ; + break; + default: + dev_err(dw9719->dev, + "Error unknown device id 0x%02llx\n", val); + return -ENXIO; + } + +props: + /* Optional indication of SAC mode select */ + device_property_read_u32(dw9719->dev, "dongwoon,sac-mode", + &dw9719->sac_mode); + + /* Optional indication of VCM frequency */ + err = device_property_read_u32(dw9719->dev, "dongwoon,vcm-freq", + &dw9719->vcm_freq); + if (err == 0) + dev_warn(dw9719->dev, "dongwoon,vcm-freq property is deprecated, please use dongwoon,vcm-prescale\n"); + + /* Optional indication of VCM prescale */ + device_property_read_u32(dw9719->dev, "dongwoon,vcm-prescale", + &dw9719->vcm_freq); + } + + switch (dw9719->model) { + case DW9800K: + cci_write(dw9719->regmap, DW9719_CONTROL, DW9719_ENABLE_RINGING, &ret); + cci_write(dw9719->regmap, DW9719_MODE, + dw9719->sac_mode << DW9800K_MODE_SAC_SHIFT, &ret); + cci_write(dw9719->regmap, DW9719_VCM_FREQ, dw9719->vcm_freq, &ret); + break; + case DW9718S: + /* Datasheet says [OCP/UVLO] should be disabled below 2.5V */ + dw9719->sac_mode &= DW9718S_CONTROL_SAC_MASK; + cci_write(dw9719->regmap, DW9718S_CONTROL, + DW9718S_CONTROL_SW_LINEAR | + (dw9719->sac_mode << DW9718S_CONTROL_SAC_SHIFT) | + DW9718S_CONTROL_OCP_DISABLE | + DW9718S_CONTROL_UVLO_DISABLE, &ret); + cci_write(dw9719->regmap, DW9718S_SACT, + DW9718S_SACT_PERIOD_8_8MS, &ret); + cci_write(dw9719->regmap, DW9718S_SW, + dw9719->vcm_freq & DW9718S_SW_VCM_FREQ_MASK, &ret); + break; + case DW9761: + cci_write(dw9719->regmap, DW9761_VCM_PRELOAD, + DW9761_DEFAULT_VCM_PRELOAD, &ret); + fallthrough; + case DW9719: + cci_write(dw9719->regmap, DW9719_CONTROL, DW9719_ENABLE_RINGING, &ret); + cci_write(dw9719->regmap, DW9719_MODE, dw9719->mode_low_bits | + (dw9719->sac_mode << DW9719_MODE_SAC_SHIFT), &ret); + cci_write(dw9719->regmap, DW9719_VCM_FREQ, dw9719->vcm_freq, &ret); + } + + if (ret) + dw9719_power_down(dw9719); + + return ret; +} + +static int dw9719_t_focus_abs(struct dw9719_device *dw9719, s32 value) +{ + u32 reg = dw9719->model == DW9718S ? DW9718S_VCM_CURRENT + : DW9719_VCM_CURRENT; + return cci_write(dw9719->regmap, reg, value, NULL); +} + +static int dw9719_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dw9719_device *dw9719 = container_of(ctrl->handler, + struct dw9719_device, + ctrls.handler); + int ret; + + /* Only apply changes to the controls if the device is powered up */ + if (!pm_runtime_get_if_in_use(dw9719->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_FOCUS_ABSOLUTE: + ret = dw9719_t_focus_abs(dw9719, ctrl->val); + break; + default: + ret = -EINVAL; + } + + pm_runtime_put(dw9719->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops dw9719_ctrl_ops = { + .s_ctrl = dw9719_set_ctrl, +}; + +static int dw9719_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct dw9719_device *dw9719 = to_dw9719_device(sd); + int ret; + int val; + + for (val = dw9719->ctrls.focus->val; val >= 0; + val -= DW9719_CTRL_STEPS) { + ret = dw9719_t_focus_abs(dw9719, val); + if (ret) + return ret; + + usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); + } + + return dw9719_power_down(dw9719); +} + +static int dw9719_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct dw9719_device *dw9719 = to_dw9719_device(sd); + int current_focus = dw9719->ctrls.focus->val; + int ret; + int val; + + ret = dw9719_power_up(dw9719, false); + if (ret) + return ret; + + for (val = current_focus % DW9719_CTRL_STEPS; val < current_focus; + val += DW9719_CTRL_STEPS) { + ret = dw9719_t_focus_abs(dw9719, val); + if (ret) + goto err_power_down; + + usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); + } + + return 0; + +err_power_down: + dw9719_power_down(dw9719); + return ret; +} + +static int dw9719_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return pm_runtime_resume_and_get(sd->dev); +} + +static int dw9719_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put_autosuspend(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops dw9719_internal_ops = { + .open = dw9719_open, + .close = dw9719_close, +}; + +static int dw9719_init_controls(struct dw9719_device *dw9719) +{ + const struct v4l2_ctrl_ops *ops = &dw9719_ctrl_ops; + int ret; + + v4l2_ctrl_handler_init(&dw9719->ctrls.handler, 1); + + dw9719->ctrls.focus = v4l2_ctrl_new_std(&dw9719->ctrls.handler, ops, + V4L2_CID_FOCUS_ABSOLUTE, 0, + DW9719_MAX_FOCUS_POS, 1, 0); + + if (dw9719->ctrls.handler.error) { + dev_err(dw9719->dev, "Error initialising v4l2 ctrls\n"); + ret = dw9719->ctrls.handler.error; + goto err_free_handler; + } + + dw9719->sd.ctrl_handler = &dw9719->ctrls.handler; + return 0; + +err_free_handler: + v4l2_ctrl_handler_free(&dw9719->ctrls.handler); + return ret; +} + +static const struct v4l2_subdev_ops dw9719_ops = { }; + +static int dw9719_probe(struct i2c_client *client) +{ + struct dw9719_device *dw9719; + int ret; + + dw9719 = devm_kzalloc(&client->dev, sizeof(*dw9719), GFP_KERNEL); + if (!dw9719) + return -ENOMEM; + + dw9719->model = (enum dw9719_model)(uintptr_t)i2c_get_match_data(client); + + dw9719->regmap = devm_cci_regmap_init_i2c(client, 8); + if (IS_ERR(dw9719->regmap)) + return PTR_ERR(dw9719->regmap); + + dw9719->dev = &client->dev; + + dw9719->regulator = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(dw9719->regulator)) + return dev_err_probe(&client->dev, PTR_ERR(dw9719->regulator), + "getting regulator\n"); + + v4l2_i2c_subdev_init(&dw9719->sd, client, &dw9719_ops); + dw9719->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + dw9719->sd.internal_ops = &dw9719_internal_ops; + + ret = dw9719_init_controls(dw9719); + if (ret) + return ret; + + ret = media_entity_pads_init(&dw9719->sd.entity, 0, NULL); + if (ret < 0) + goto err_free_ctrl_handler; + + dw9719->sd.entity.function = MEDIA_ENT_F_LENS; + + /* + * We need the driver to work in the event that pm runtime is disable in + * the kernel, so power up and verify the chip now. In the event that + * runtime pm is disabled this will leave the chip on, so that the lens + * will work. + */ + + ret = dw9719_power_up(dw9719, true); + if (ret) + goto err_cleanup_media; + + pm_runtime_set_active(&client->dev); + pm_runtime_get_noresume(&client->dev); + pm_runtime_enable(&client->dev); + + ret = v4l2_async_register_subdev(&dw9719->sd); + if (ret < 0) + goto err_pm_runtime; + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return ret; + +err_pm_runtime: + pm_runtime_disable(&client->dev); + pm_runtime_put_noidle(&client->dev); + dw9719_power_down(dw9719); +err_cleanup_media: + media_entity_cleanup(&dw9719->sd.entity); +err_free_ctrl_handler: + v4l2_ctrl_handler_free(&dw9719->ctrls.handler); + + return ret; +} + +static void dw9719_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9719_device *dw9719 = + container_of(sd, struct dw9719_device, sd); + + v4l2_async_unregister_subdev(sd); + v4l2_ctrl_handler_free(&dw9719->ctrls.handler); + media_entity_cleanup(&dw9719->sd.entity); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + dw9719_power_down(dw9719); + pm_runtime_set_suspended(&client->dev); +} + +static const struct i2c_device_id dw9719_id_table[] = { + { .name = "dw9718s", .driver_data = (kernel_ulong_t)DW9718S }, + { .name = "dw9719", .driver_data = (kernel_ulong_t)DW9719 }, + { .name = "dw9761", .driver_data = (kernel_ulong_t)DW9761 }, + { .name = "dw9800k", .driver_data = (kernel_ulong_t)DW9800K }, + { } +}; +MODULE_DEVICE_TABLE(i2c, dw9719_id_table); + +static const struct of_device_id dw9719_of_table[] = { + { .compatible = "dongwoon,dw9718s", .data = (const void *)DW9718S }, + { .compatible = "dongwoon,dw9719", .data = (const void *)DW9719 }, + { .compatible = "dongwoon,dw9761", .data = (const void *)DW9761 }, + { .compatible = "dongwoon,dw9800k", .data = (const void *)DW9800K }, + { } +}; +MODULE_DEVICE_TABLE(of, dw9719_of_table); + +static DEFINE_RUNTIME_DEV_PM_OPS(dw9719_pm_ops, dw9719_suspend, dw9719_resume, + NULL); + +static struct i2c_driver dw9719_i2c_driver = { + .driver = { + .name = "dw9719", + .pm = pm_sleep_ptr(&dw9719_pm_ops), + .of_match_table = dw9719_of_table, + }, + .probe = dw9719_probe, + .remove = dw9719_remove, + .id_table = dw9719_id_table, +}; +module_i2c_driver(dw9719_i2c_driver); + +MODULE_AUTHOR("Daniel Scally "); +MODULE_DESCRIPTION("DW9719 VCM Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/dw9768.c b/drivers/media/i2c/dw9768.c new file mode 100644 index 000000000..d037d3009 --- /dev/null +++ b/drivers/media/i2c/dw9768.c @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020 MediaTek Inc. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DW9768_NAME "dw9768" +#define DW9768_MAX_FOCUS_POS (1024 - 1) +/* + * This sets the minimum granularity for the focus positions. + * A value of 1 gives maximum accuracy for a desired focus position + */ +#define DW9768_FOCUS_STEPS 1 + +/* + * Ring control and Power control register + * Bit[1] RING_EN + * 0: Direct mode + * 1: AAC mode (ringing control mode) + * Bit[0] PD + * 0: Normal operation mode + * 1: Power down mode + * DW9768 requires waiting time of Topr after PD reset takes place. + */ +#define DW9768_RING_PD_CONTROL_REG 0x02 +#define DW9768_PD_MODE_OFF 0x00 +#define DW9768_PD_MODE_EN BIT(0) +#define DW9768_AAC_MODE_EN BIT(1) + +/* + * DW9768 separates two registers to control the VCM position. + * One for MSB value, another is LSB value. + * DAC_MSB: D[9:8] (ADD: 0x03) + * DAC_LSB: D[7:0] (ADD: 0x04) + * D[9:0] DAC data input: positive output current = D[9:0] / 1023 * 100[mA] + */ +#define DW9768_MSB_ADDR 0x03 +#define DW9768_LSB_ADDR 0x04 +#define DW9768_STATUS_ADDR 0x05 + +/* + * AAC mode control & prescale register + * Bit[7:5] Namely AC[2:0], decide the VCM mode and operation time. + * 001 AAC2 0.48 x Tvib + * 010 AAC3 0.70 x Tvib + * 011 AAC4 0.75 x Tvib + * 101 AAC8 1.13 x Tvib + * Bit[2:0] Namely PRESC[2:0], set the internal clock dividing rate as follow. + * 000 2 + * 001 1 + * 010 1/2 + * 011 1/4 + * 100 8 + * 101 4 + */ +#define DW9768_AAC_PRESC_REG 0x06 +#define DW9768_AAC_MODE_SEL_MASK GENMASK(7, 5) +#define DW9768_CLOCK_PRE_SCALE_SEL_MASK GENMASK(2, 0) + +/* + * VCM period of vibration register + * Bit[5:0] Defined as VCM rising periodic time (Tvib) together with PRESC[2:0] + * Tvib = (6.3ms + AACT[5:0] * 0.1ms) * Dividing Rate + * Dividing Rate is the internal clock dividing rate that is defined at + * PRESCALE register (ADD: 0x06) + */ +#define DW9768_AAC_TIME_REG 0x07 + +/* + * DW9768 requires waiting time (delay time) of t_OPR after power-up, + * or in the case of PD reset taking place. + */ +#define DW9768_T_OPR_US 1000 +#define DW9768_TVIB_MS_BASE10 (64 - 1) +#define DW9768_AAC_MODE_DEFAULT 2 +#define DW9768_AAC_TIME_DEFAULT 0x20 +#define DW9768_CLOCK_PRE_SCALE_DEFAULT 1 + +/* + * This acts as the minimum granularity of lens movement. + * Keep this value power of 2, so the control steps can be + * uniformly adjusted for gradual lens movement, with desired + * number of control steps. + */ +#define DW9768_MOVE_STEPS 16 + +static const char * const dw9768_supply_names[] = { + "vin", /* Digital I/O power */ + "vdd", /* Digital core power */ +}; + +/* dw9768 device structure */ +struct dw9768 { + struct regulator_bulk_data supplies[ARRAY_SIZE(dw9768_supply_names)]; + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *focus; + struct v4l2_subdev sd; + + u32 aac_mode; + u32 aac_timing; + u32 clock_presc; + u32 move_delay_us; +}; + +static inline struct dw9768 *sd_to_dw9768(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct dw9768, sd); +} + +struct dw9768_aac_mode_ot_multi { + u32 aac_mode_enum; + u32 ot_multi_base100; +}; + +struct dw9768_clk_presc_dividing_rate { + u32 clk_presc_enum; + u32 dividing_rate_base100; +}; + +static const struct dw9768_aac_mode_ot_multi aac_mode_ot_multi[] = { + {1, 48}, + {2, 70}, + {3, 75}, + {5, 113}, +}; + +static const struct dw9768_clk_presc_dividing_rate presc_dividing_rate[] = { + {0, 200}, + {1, 100}, + {2, 50}, + {3, 25}, + {4, 800}, + {5, 400}, +}; + +static u32 dw9768_find_ot_multi(u32 aac_mode_param) +{ + u32 cur_ot_multi_base100 = 70; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(aac_mode_ot_multi); i++) { + if (aac_mode_ot_multi[i].aac_mode_enum == aac_mode_param) { + cur_ot_multi_base100 = + aac_mode_ot_multi[i].ot_multi_base100; + } + } + + return cur_ot_multi_base100; +} + +static u32 dw9768_find_dividing_rate(u32 presc_param) +{ + u32 cur_clk_dividing_rate_base100 = 100; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(presc_dividing_rate); i++) { + if (presc_dividing_rate[i].clk_presc_enum == presc_param) { + cur_clk_dividing_rate_base100 = + presc_dividing_rate[i].dividing_rate_base100; + } + } + + return cur_clk_dividing_rate_base100; +} + +/* + * DW9768_AAC_PRESC_REG & DW9768_AAC_TIME_REG determine VCM operation time. + * For current VCM mode: AAC3, Operation Time would be 0.70 x Tvib. + * Tvib = (6.3ms + AACT[5:0] * 0.1MS) * Dividing Rate. + * Below is calculation of the operation delay for each step. + */ +static inline u32 dw9768_cal_move_delay(u32 aac_mode_param, u32 presc_param, + u32 aac_timing_param) +{ + u32 Tvib_us; + u32 ot_multi_base100; + u32 clk_dividing_rate_base100; + + ot_multi_base100 = dw9768_find_ot_multi(aac_mode_param); + + clk_dividing_rate_base100 = dw9768_find_dividing_rate(presc_param); + + Tvib_us = (DW9768_TVIB_MS_BASE10 + aac_timing_param) * + clk_dividing_rate_base100; + + return Tvib_us * ot_multi_base100 / 100; +} + +static int dw9768_mod_reg(struct dw9768 *dw9768, u8 reg, u8 mask, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dw9768->sd); + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + return ret; + + val = ((unsigned char)ret & ~mask) | (val & mask); + + return i2c_smbus_write_byte_data(client, reg, val); +} + +static int dw9768_set_dac(struct dw9768 *dw9768, u16 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dw9768->sd); + + /* Write VCM position to registers */ + return i2c_smbus_write_word_swapped(client, DW9768_MSB_ADDR, val); +} + +static int dw9768_init(struct dw9768 *dw9768) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dw9768->sd); + int ret, val; + + /* Reset DW9768_RING_PD_CONTROL_REG to default status 0x00 */ + ret = i2c_smbus_write_byte_data(client, DW9768_RING_PD_CONTROL_REG, + DW9768_PD_MODE_OFF); + if (ret < 0) + return ret; + + /* + * DW9769 requires waiting delay time of t_OPR + * after PD reset takes place. + */ + usleep_range(DW9768_T_OPR_US, DW9768_T_OPR_US + 100); + + /* Set DW9768_RING_PD_CONTROL_REG to DW9768_AAC_MODE_EN(0x01) */ + ret = i2c_smbus_write_byte_data(client, DW9768_RING_PD_CONTROL_REG, + DW9768_AAC_MODE_EN); + if (ret < 0) + return ret; + + /* Set AAC mode */ + ret = dw9768_mod_reg(dw9768, DW9768_AAC_PRESC_REG, + DW9768_AAC_MODE_SEL_MASK, + dw9768->aac_mode << 5); + if (ret < 0) + return ret; + + /* Set clock presc */ + if (dw9768->clock_presc != DW9768_CLOCK_PRE_SCALE_DEFAULT) { + ret = dw9768_mod_reg(dw9768, DW9768_AAC_PRESC_REG, + DW9768_CLOCK_PRE_SCALE_SEL_MASK, + dw9768->clock_presc); + if (ret < 0) + return ret; + } + + /* Set AAC Timing */ + if (dw9768->aac_timing != DW9768_AAC_TIME_DEFAULT) { + ret = i2c_smbus_write_byte_data(client, DW9768_AAC_TIME_REG, + dw9768->aac_timing); + if (ret < 0) + return ret; + } + + for (val = dw9768->focus->val % DW9768_MOVE_STEPS; + val <= dw9768->focus->val; + val += DW9768_MOVE_STEPS) { + ret = dw9768_set_dac(dw9768, val); + if (ret) { + dev_err(&client->dev, "I2C failure: %d", ret); + return ret; + } + usleep_range(dw9768->move_delay_us, + dw9768->move_delay_us + 1000); + } + + return 0; +} + +static int dw9768_release(struct dw9768 *dw9768) +{ + struct i2c_client *client = v4l2_get_subdevdata(&dw9768->sd); + int ret, val; + + val = round_down(dw9768->focus->val, DW9768_MOVE_STEPS); + for ( ; val >= 0; val -= DW9768_MOVE_STEPS) { + ret = dw9768_set_dac(dw9768, val); + if (ret) { + dev_err(&client->dev, "I2C write fail: %d", ret); + return ret; + } + usleep_range(dw9768->move_delay_us, + dw9768->move_delay_us + 1000); + } + + ret = i2c_smbus_write_byte_data(client, DW9768_RING_PD_CONTROL_REG, + DW9768_PD_MODE_EN); + if (ret < 0) + return ret; + + /* + * DW9769 requires waiting delay time of t_OPR + * after PD reset takes place. + */ + usleep_range(DW9768_T_OPR_US, DW9768_T_OPR_US + 100); + + return 0; +} + +static int dw9768_runtime_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct dw9768 *dw9768 = sd_to_dw9768(sd); + + dw9768_release(dw9768); + regulator_bulk_disable(ARRAY_SIZE(dw9768_supply_names), + dw9768->supplies); + + return 0; +} + +static int dw9768_runtime_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct dw9768 *dw9768 = sd_to_dw9768(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(dw9768_supply_names), + dw9768->supplies); + if (ret < 0) { + dev_err(dev, "failed to enable regulators\n"); + return ret; + } + + /* + * The datasheet refers to t_OPR that needs to be waited before sending + * I2C commands after power-up. + */ + usleep_range(DW9768_T_OPR_US, DW9768_T_OPR_US + 100); + + ret = dw9768_init(dw9768); + if (ret < 0) + goto disable_regulator; + + return 0; + +disable_regulator: + regulator_bulk_disable(ARRAY_SIZE(dw9768_supply_names), + dw9768->supplies); + + return ret; +} + +static int dw9768_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dw9768 *dw9768 = container_of(ctrl->handler, + struct dw9768, ctrls); + + if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) + return dw9768_set_dac(dw9768, ctrl->val); + + return 0; +} + +static const struct v4l2_ctrl_ops dw9768_ctrl_ops = { + .s_ctrl = dw9768_set_ctrl, +}; + +static int dw9768_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return pm_runtime_resume_and_get(sd->dev); +} + +static int dw9768_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put_autosuspend(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops dw9768_int_ops = { + .open = dw9768_open, + .close = dw9768_close, +}; + +static const struct v4l2_subdev_ops dw9768_ops = { }; + +static int dw9768_init_controls(struct dw9768 *dw9768) +{ + struct v4l2_ctrl_handler *hdl = &dw9768->ctrls; + const struct v4l2_ctrl_ops *ops = &dw9768_ctrl_ops; + + v4l2_ctrl_handler_init(hdl, 1); + + dw9768->focus = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, 0, + DW9768_MAX_FOCUS_POS, + DW9768_FOCUS_STEPS, 0); + + if (hdl->error) + return hdl->error; + + dw9768->sd.ctrl_handler = hdl; + + return 0; +} + +static int dw9768_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct dw9768 *dw9768; + bool full_power; + unsigned int i; + int ret; + + dw9768 = devm_kzalloc(dev, sizeof(*dw9768), GFP_KERNEL); + if (!dw9768) + return -ENOMEM; + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&dw9768->sd, client, &dw9768_ops); + + dw9768->aac_mode = DW9768_AAC_MODE_DEFAULT; + dw9768->aac_timing = DW9768_AAC_TIME_DEFAULT; + dw9768->clock_presc = DW9768_CLOCK_PRE_SCALE_DEFAULT; + + /* Optional indication of AAC mode select */ + fwnode_property_read_u32(dev_fwnode(dev), "dongwoon,aac-mode", + &dw9768->aac_mode); + + /* Optional indication of clock pre-scale select */ + fwnode_property_read_u32(dev_fwnode(dev), "dongwoon,clock-presc", + &dw9768->clock_presc); + + /* Optional indication of AAC Timing */ + fwnode_property_read_u32(dev_fwnode(dev), "dongwoon,aac-timing", + &dw9768->aac_timing); + + dw9768->move_delay_us = dw9768_cal_move_delay(dw9768->aac_mode, + dw9768->clock_presc, + dw9768->aac_timing); + + for (i = 0; i < ARRAY_SIZE(dw9768_supply_names); i++) + dw9768->supplies[i].supply = dw9768_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(dw9768_supply_names), + dw9768->supplies); + if (ret) { + dev_err(dev, "failed to get regulators\n"); + return ret; + } + + /* Initialize controls */ + ret = dw9768_init_controls(dw9768); + if (ret) + goto err_free_handler; + + /* Initialize subdev */ + dw9768->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + dw9768->sd.internal_ops = &dw9768_int_ops; + + ret = media_entity_pads_init(&dw9768->sd.entity, 0, NULL); + if (ret < 0) + goto err_free_handler; + + dw9768->sd.entity.function = MEDIA_ENT_F_LENS; + + /* + * Figure out whether we're going to power up the device here. Generally + * this is done if CONFIG_PM is disabled in a DT system or the device is + * to be powered on in an ACPI system. Similarly for power off in + * remove. + */ + full_power = (is_acpi_node(dev_fwnode(dev)) && + acpi_dev_state_d0(dev)) || + (is_of_node(dev_fwnode(dev)) && !IS_ENABLED(CONFIG_PM)); + if (full_power) { + ret = dw9768_runtime_resume(dev); + if (ret < 0) { + dev_err(dev, "failed to power on: %d\n", ret); + goto err_clean_entity; + } + pm_runtime_set_active(dev); + } + + pm_runtime_enable(dev); + ret = v4l2_async_register_subdev(&dw9768->sd); + if (ret < 0) { + dev_err(dev, "failed to register V4L2 subdev: %d", ret); + goto err_power_off; + } + + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + pm_runtime_idle(dev); + + return 0; + +err_power_off: + pm_runtime_disable(dev); + if (full_power) { + dw9768_runtime_suspend(dev); + pm_runtime_set_suspended(dev); + } +err_clean_entity: + media_entity_cleanup(&dw9768->sd.entity); +err_free_handler: + v4l2_ctrl_handler_free(&dw9768->ctrls); + + return ret; +} + +static void dw9768_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9768 *dw9768 = sd_to_dw9768(sd); + struct device *dev = &client->dev; + + v4l2_async_unregister_subdev(&dw9768->sd); + v4l2_ctrl_handler_free(&dw9768->ctrls); + media_entity_cleanup(&dw9768->sd.entity); + pm_runtime_disable(dev); + if ((is_acpi_node(dev_fwnode(dev)) && acpi_dev_state_d0(dev)) || + (is_of_node(dev_fwnode(dev)) && !IS_ENABLED(CONFIG_PM))) { + dw9768_runtime_suspend(dev); + pm_runtime_set_suspended(dev); + } +} + +static const struct of_device_id dw9768_of_table[] = { + { .compatible = "dongwoon,dw9768" }, + { .compatible = "giantec,gt9769" }, + {} +}; +MODULE_DEVICE_TABLE(of, dw9768_of_table); + +static const struct dev_pm_ops dw9768_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(dw9768_runtime_suspend, dw9768_runtime_resume, NULL) +}; + +static struct i2c_driver dw9768_i2c_driver = { + .driver = { + .name = DW9768_NAME, + .pm = &dw9768_pm_ops, + .of_match_table = dw9768_of_table, + }, + .probe = dw9768_probe, + .remove = dw9768_remove, +}; +module_i2c_driver(dw9768_i2c_driver); + +MODULE_AUTHOR("Dongchun Zhu"); +MODULE_DESCRIPTION("DW9768 VCM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/dw9807-vcm.c b/drivers/media/i2c/dw9807-vcm.c new file mode 100644 index 000000000..4148009e0 --- /dev/null +++ b/drivers/media/i2c/dw9807-vcm.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 Intel Corporation + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DW9807_MAX_FOCUS_POS 1023 +/* + * This sets the minimum granularity for the focus positions. + * A value of 1 gives maximum accuracy for a desired focus position. + */ +#define DW9807_FOCUS_STEPS 1 +/* + * This acts as the minimum granularity of lens movement. + * Keep this value power of 2, so the control steps can be + * uniformly adjusted for gradual lens movement, with desired + * number of control steps. + */ +#define DW9807_CTRL_STEPS 16 +#define DW9807_CTRL_DELAY_US 1000 + +#define DW9807_CTL_ADDR 0x02 +/* + * DW9807 separates two registers to control the VCM position. + * One for MSB value, another is LSB value. + */ +#define DW9807_MSB_ADDR 0x03 +#define DW9807_LSB_ADDR 0x04 +#define DW9807_STATUS_ADDR 0x05 +#define DW9807_MODE_ADDR 0x06 +#define DW9807_RESONANCE_ADDR 0x07 + +#define MAX_RETRY 10 + +struct dw9807_device { + struct v4l2_ctrl_handler ctrls_vcm; + struct v4l2_subdev sd; + u16 current_val; +}; + +static inline struct dw9807_device *sd_to_dw9807_vcm( + struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct dw9807_device, sd); +} + +static int dw9807_i2c_check(struct i2c_client *client) +{ + const char status_addr = DW9807_STATUS_ADDR; + char status_result; + int ret; + + ret = i2c_master_send(client, &status_addr, sizeof(status_addr)); + if (ret < 0) { + dev_err(&client->dev, "I2C write STATUS address fail ret = %d\n", + ret); + return ret; + } + + ret = i2c_master_recv(client, &status_result, sizeof(status_result)); + if (ret < 0) { + dev_err(&client->dev, "I2C read STATUS value fail ret = %d\n", + ret); + return ret; + } + + return status_result; +} + +static int dw9807_set_dac(struct i2c_client *client, u16 data) +{ + const char tx_data[3] = { + DW9807_MSB_ADDR, ((data >> 8) & 0x03), (data & 0xff) + }; + int val, ret; + + /* + * According to the datasheet, need to check the bus status before we + * write VCM position. This ensure that we really write the value + * into the register + */ + ret = readx_poll_timeout(dw9807_i2c_check, client, val, val <= 0, + DW9807_CTRL_DELAY_US, MAX_RETRY * DW9807_CTRL_DELAY_US); + + if (ret || val < 0) { + if (ret) { + dev_warn(&client->dev, + "Cannot do the write operation because VCM is busy\n"); + } + + return ret ? -EBUSY : val; + } + + /* Write VCM position to registers */ + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret < 0) { + dev_err(&client->dev, + "I2C write MSB fail ret=%d\n", ret); + + return ret; + } + + return 0; +} + +static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dw9807_device *dev_vcm = container_of(ctrl->handler, + struct dw9807_device, ctrls_vcm); + + if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) { + struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + + dev_vcm->current_val = ctrl->val; + return dw9807_set_dac(client, ctrl->val); + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops dw9807_vcm_ctrl_ops = { + .s_ctrl = dw9807_set_ctrl, +}; + +static int dw9807_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return pm_runtime_resume_and_get(sd->dev); +} + +static int dw9807_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops dw9807_int_ops = { + .open = dw9807_open, + .close = dw9807_close, +}; + +static const struct v4l2_subdev_ops dw9807_ops = { }; + +static void dw9807_subdev_cleanup(struct dw9807_device *dw9807_dev) +{ + v4l2_async_unregister_subdev(&dw9807_dev->sd); + v4l2_ctrl_handler_free(&dw9807_dev->ctrls_vcm); + media_entity_cleanup(&dw9807_dev->sd.entity); +} + +static int dw9807_init_controls(struct dw9807_device *dev_vcm) +{ + struct v4l2_ctrl_handler *hdl = &dev_vcm->ctrls_vcm; + const struct v4l2_ctrl_ops *ops = &dw9807_vcm_ctrl_ops; + struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + + v4l2_ctrl_handler_init(hdl, 1); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, + 0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS, 0); + + dev_vcm->sd.ctrl_handler = hdl; + if (hdl->error) { + dev_err(&client->dev, "%s fail error: 0x%x\n", + __func__, hdl->error); + return hdl->error; + } + + return 0; +} + +static int dw9807_probe(struct i2c_client *client) +{ + struct dw9807_device *dw9807_dev; + int rval; + + dw9807_dev = devm_kzalloc(&client->dev, sizeof(*dw9807_dev), + GFP_KERNEL); + if (dw9807_dev == NULL) + return -ENOMEM; + + v4l2_i2c_subdev_init(&dw9807_dev->sd, client, &dw9807_ops); + dw9807_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + dw9807_dev->sd.internal_ops = &dw9807_int_ops; + + rval = dw9807_init_controls(dw9807_dev); + if (rval) + goto err_cleanup; + + rval = media_entity_pads_init(&dw9807_dev->sd.entity, 0, NULL); + if (rval < 0) + goto err_cleanup; + + dw9807_dev->sd.entity.function = MEDIA_ENT_F_LENS; + + rval = v4l2_async_register_subdev(&dw9807_dev->sd); + if (rval < 0) + goto err_cleanup; + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +err_cleanup: + v4l2_ctrl_handler_free(&dw9807_dev->ctrls_vcm); + media_entity_cleanup(&dw9807_dev->sd.entity); + + return rval; +} + +static void dw9807_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); + + pm_runtime_disable(&client->dev); + + dw9807_subdev_cleanup(dw9807_dev); +} + +/* + * This function sets the vcm position, so it consumes least current + * The lens position is gradually moved in units of DW9807_CTRL_STEPS, + * to make the movements smoothly. + */ +static int __maybe_unused dw9807_vcm_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); + const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 }; + int ret, val; + + for (val = dw9807_dev->current_val & ~(DW9807_CTRL_STEPS - 1); + val >= 0; val -= DW9807_CTRL_STEPS) { + ret = dw9807_set_dac(client, val); + if (ret) + dev_err_once(dev, "%s I2C failure: %d", __func__, ret); + usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); + } + + /* Power down */ + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret < 0) { + dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret); + return ret; + } + + return 0; +} + +/* + * This function sets the vcm position to the value set by the user + * through v4l2_ctrl_ops s_ctrl handler + * The lens position is gradually moved in units of DW9807_CTRL_STEPS, + * to make the movements smoothly. + */ +static int __maybe_unused dw9807_vcm_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); + const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 }; + int ret, val; + + /* Power on */ + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret < 0) { + dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret); + return ret; + } + + for (val = dw9807_dev->current_val % DW9807_CTRL_STEPS; + val < dw9807_dev->current_val + DW9807_CTRL_STEPS - 1; + val += DW9807_CTRL_STEPS) { + ret = dw9807_set_dac(client, val); + if (ret) + dev_err_ratelimited(dev, "%s I2C failure: %d", + __func__, ret); + usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); + } + + return 0; +} + +static const struct of_device_id dw9807_of_table[] = { + { .compatible = "dongwoon,dw9807-vcm" }, + /* Compatibility for older firmware, NEVER USE THIS IN FIRMWARE! */ + { .compatible = "dongwoon,dw9807" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dw9807_of_table); + +static const struct dev_pm_ops dw9807_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dw9807_vcm_suspend, dw9807_vcm_resume) + SET_RUNTIME_PM_OPS(dw9807_vcm_suspend, dw9807_vcm_resume, NULL) +}; + +static struct i2c_driver dw9807_i2c_driver = { + .driver = { + .name = "dw9807", + .pm = &dw9807_pm_ops, + .of_match_table = dw9807_of_table, + }, + .probe = dw9807_probe, + .remove = dw9807_remove, +}; + +module_i2c_driver(dw9807_i2c_driver); + +MODULE_AUTHOR("Chiang, Alan"); +MODULE_DESCRIPTION("DW9807 VCM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/et8ek8/Kconfig b/drivers/media/i2c/et8ek8/Kconfig new file mode 100644 index 000000000..987fc62d5 --- /dev/null +++ b/drivers/media/i2c/et8ek8/Kconfig @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_ET8EK8 + tristate "ET8EK8 camera sensor support" + help + This is a driver for the Toshiba ET8EK8 5 MP camera sensor. + It is used for example in Nokia N900 (RX-51). diff --git a/drivers/media/i2c/et8ek8/Makefile b/drivers/media/i2c/et8ek8/Makefile new file mode 100644 index 000000000..5e06c308c --- /dev/null +++ b/drivers/media/i2c/et8ek8/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +et8ek8-objs += et8ek8_mode.o et8ek8_driver.o +obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8.o diff --git a/drivers/media/i2c/et8ek8/et8ek8_driver.c b/drivers/media/i2c/et8ek8/et8ek8_driver.c new file mode 100644 index 000000000..738e28010 --- /dev/null +++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c @@ -0,0 +1,1512 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * et8ek8_driver.c + * + * Copyright (C) 2008 Nokia Corporation + * + * Contact: Sakari Ailus + * Tuukka Toivonen + * Pavel Machek + * + * Based on code from Toni Leinonen . + * + * This driver is based on the Micron MT9T012 camera imager driver + * (C) Texas Instruments. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "et8ek8_reg.h" + +#define ET8EK8_NAME "et8ek8" +#define ET8EK8_PRIV_MEM_SIZE 128 +#define ET8EK8_MAX_MSG 8 + +struct et8ek8_sensor { + struct v4l2_subdev subdev; + struct media_pad pad; + struct v4l2_mbus_framefmt format; + struct gpio_desc *reset; + struct regulator *vana; + struct clk *ext_clk; + u32 xclk_freq; + + u16 version; + + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *pixel_rate; + struct et8ek8_reglist *current_reglist; + + u8 priv_mem[ET8EK8_PRIV_MEM_SIZE]; + + struct mutex power_lock; + int power_count; +}; + +#define to_et8ek8_sensor(sd) container_of(sd, struct et8ek8_sensor, subdev) + +enum et8ek8_versions { + ET8EK8_REV_1 = 0x0001, + ET8EK8_REV_2, +}; + +/* + * This table describes what should be written to the sensor register + * for each gain value. The gain(index in the table) is in terms of + * 0.1EV, i.e. 10 indexes in the table give 2 time more gain [0] in + * the *analog gain, [1] in the digital gain + * + * Analog gain [dB] = 20*log10(regvalue/32); 0x20..0x100 + */ +static struct et8ek8_gain { + u16 analog; + u16 digital; +} const et8ek8_gain_table[] = { + { 32, 0}, /* x1 */ + { 34, 0}, + { 37, 0}, + { 39, 0}, + { 42, 0}, + { 45, 0}, + { 49, 0}, + { 52, 0}, + { 56, 0}, + { 60, 0}, + { 64, 0}, /* x2 */ + { 69, 0}, + { 74, 0}, + { 79, 0}, + { 84, 0}, + { 91, 0}, + { 97, 0}, + {104, 0}, + {111, 0}, + {119, 0}, + {128, 0}, /* x4 */ + {137, 0}, + {147, 0}, + {158, 0}, + {169, 0}, + {181, 0}, + {194, 0}, + {208, 0}, + {223, 0}, + {239, 0}, + {256, 0}, /* x8 */ + {256, 73}, + {256, 152}, + {256, 236}, + {256, 327}, + {256, 424}, + {256, 528}, + {256, 639}, + {256, 758}, + {256, 886}, + {256, 1023}, /* x16 */ +}; + +/* Register definitions */ +#define REG_REVISION_NUMBER_L 0x1200 +#define REG_REVISION_NUMBER_H 0x1201 + +#define PRIV_MEM_START_REG 0x0008 +#define PRIV_MEM_WIN_SIZE 8 + +#define ET8EK8_I2C_DELAY 3 /* msec delay b/w accesses */ + +#define USE_CRC 1 + +/* + * Register access helpers + * + * Read a 8/16/32-bit i2c register. The value is returned in 'val'. + * Returns zero if successful, or non-zero otherwise. + */ +static int et8ek8_i2c_read_reg(struct i2c_client *client, u16 data_length, + u16 reg, u32 *val) +{ + int r; + struct i2c_msg msg; + unsigned char data[4]; + + if (!client->adapter) + return -ENODEV; + if (data_length != ET8EK8_REG_8BIT && data_length != ET8EK8_REG_16BIT) + return -EINVAL; + + msg.addr = client->addr; + msg.flags = 0; + msg.len = 2; + msg.buf = data; + + /* high byte goes out first */ + data[0] = (u8) (reg >> 8); + data[1] = (u8) (reg & 0xff); + r = i2c_transfer(client->adapter, &msg, 1); + if (r < 0) + goto err; + + msg.len = data_length; + msg.flags = I2C_M_RD; + r = i2c_transfer(client->adapter, &msg, 1); + if (r < 0) + goto err; + + *val = 0; + /* high byte comes first */ + if (data_length == ET8EK8_REG_8BIT) + *val = data[0]; + else + *val = (data[1] << 8) + data[0]; + + return 0; + +err: + dev_err(&client->dev, "read from offset 0x%x error %d\n", reg, r); + + return r; +} + +static void et8ek8_i2c_create_msg(struct i2c_client *client, u16 len, u16 reg, + u32 val, struct i2c_msg *msg, + unsigned char *buf) +{ + msg->addr = client->addr; + msg->flags = 0; /* Write */ + msg->len = 2 + len; + msg->buf = buf; + + /* high byte goes out first */ + buf[0] = (u8) (reg >> 8); + buf[1] = (u8) (reg & 0xff); + + switch (len) { + case ET8EK8_REG_8BIT: + buf[2] = (u8) (val) & 0xff; + break; + case ET8EK8_REG_16BIT: + buf[2] = (u8) (val) & 0xff; + buf[3] = (u8) (val >> 8) & 0xff; + break; + default: + WARN_ONCE(1, ET8EK8_NAME ": %s: invalid message length.\n", + __func__); + } +} + +/* + * A buffered write method that puts the wanted register write + * commands in smaller number of message lists and passes the lists to + * the i2c framework + */ +static int et8ek8_i2c_buffered_write_regs(struct i2c_client *client, + const struct et8ek8_reg *wnext, + int cnt) +{ + struct i2c_msg msg[ET8EK8_MAX_MSG]; + unsigned char data[ET8EK8_MAX_MSG][6]; + int wcnt = 0; + u16 reg, data_length; + u32 val; + int rval; + + /* Create new write messages for all writes */ + while (wcnt < cnt) { + data_length = wnext->type; + reg = wnext->reg; + val = wnext->val; + wnext++; + + et8ek8_i2c_create_msg(client, data_length, reg, + val, &msg[wcnt], &data[wcnt][0]); + + /* Update write count */ + wcnt++; + + if (wcnt < ET8EK8_MAX_MSG) + continue; + + rval = i2c_transfer(client->adapter, msg, wcnt); + if (rval < 0) + return rval; + + cnt -= wcnt; + wcnt = 0; + } + + rval = i2c_transfer(client->adapter, msg, wcnt); + + return rval < 0 ? rval : 0; +} + +/* + * Write a list of registers to i2c device. + * + * The list of registers is terminated by ET8EK8_REG_TERM. + * Returns zero if successful, or non-zero otherwise. + */ +static int et8ek8_i2c_write_regs(struct i2c_client *client, + const struct et8ek8_reg *regs) +{ + int r, cnt = 0; + const struct et8ek8_reg *next; + + if (!client->adapter) + return -ENODEV; + + if (!regs) + return -EINVAL; + + /* Initialize list pointers to the start of the list */ + next = regs; + + do { + /* + * We have to go through the list to figure out how + * many regular writes we have in a row + */ + while (next->type != ET8EK8_REG_TERM && + next->type != ET8EK8_REG_DELAY) { + /* + * Here we check that the actual length fields + * are valid + */ + if (WARN(next->type != ET8EK8_REG_8BIT && + next->type != ET8EK8_REG_16BIT, + "Invalid type = %d", next->type)) { + return -EINVAL; + } + /* + * Increment count of successive writes and + * read pointer + */ + cnt++; + next++; + } + + /* Now we start writing ... */ + r = et8ek8_i2c_buffered_write_regs(client, regs, cnt); + + /* ... and then check that everything was OK */ + if (r < 0) { + dev_err(&client->dev, "i2c transfer error!\n"); + return r; + } + + /* + * If we ran into a sleep statement when going through + * the list, this is where we snooze for the required time + */ + if (next->type == ET8EK8_REG_DELAY) { + msleep(next->val); + /* + * ZZZ ... + * Update list pointers and cnt and start over ... + */ + next++; + regs = next; + cnt = 0; + } + } while (next->type != ET8EK8_REG_TERM); + + return 0; +} + +/* + * Write to a 8/16-bit register. + * Returns zero if successful, or non-zero otherwise. + */ +static int et8ek8_i2c_write_reg(struct i2c_client *client, u16 data_length, + u16 reg, u32 val) +{ + int r; + struct i2c_msg msg; + unsigned char data[6]; + + if (!client->adapter) + return -ENODEV; + if (data_length != ET8EK8_REG_8BIT && data_length != ET8EK8_REG_16BIT) + return -EINVAL; + + et8ek8_i2c_create_msg(client, data_length, reg, val, &msg, data); + + r = i2c_transfer(client->adapter, &msg, 1); + if (r < 0) { + dev_err(&client->dev, + "wrote 0x%x to offset 0x%x error %d\n", val, reg, r); + return r; + } + + return 0; +} + +static struct et8ek8_reglist *et8ek8_reglist_find_type( + struct et8ek8_meta_reglist *meta, + u16 type) +{ + struct et8ek8_reglist **next = &meta->reglist[0].ptr; + + while (*next) { + if ((*next)->type == type) + return *next; + + next++; + } + + return NULL; +} + +static int et8ek8_i2c_reglist_find_write(struct i2c_client *client, + struct et8ek8_meta_reglist *meta, + u16 type) +{ + struct et8ek8_reglist *reglist; + + reglist = et8ek8_reglist_find_type(meta, type); + if (!reglist) + return -EINVAL; + + return et8ek8_i2c_write_regs(client, reglist->regs); +} + +static struct et8ek8_reglist **et8ek8_reglist_first( + struct et8ek8_meta_reglist *meta) +{ + return &meta->reglist[0].ptr; +} + +static void et8ek8_reglist_to_mbus(const struct et8ek8_reglist *reglist, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = reglist->mode.window_width; + fmt->height = reglist->mode.window_height; + fmt->code = reglist->mode.bus_format; +} + +static struct et8ek8_reglist *et8ek8_reglist_find_mode_fmt( + struct et8ek8_meta_reglist *meta, + struct v4l2_mbus_framefmt *fmt) +{ + struct et8ek8_reglist **list = et8ek8_reglist_first(meta); + struct et8ek8_reglist *best_match = NULL; + struct et8ek8_reglist *best_other = NULL; + struct v4l2_mbus_framefmt format; + unsigned int max_dist_match = (unsigned int)-1; + unsigned int max_dist_other = (unsigned int)-1; + + /* + * Find the mode with the closest image size. The distance between + * image sizes is the size in pixels of the non-overlapping regions + * between the requested size and the frame-specified size. + * + * Store both the closest mode that matches the requested format, and + * the closest mode for all other formats. The best match is returned + * if found, otherwise the best mode with a non-matching format is + * returned. + */ + for (; *list; list++) { + unsigned int dist; + + if ((*list)->type != ET8EK8_REGLIST_MODE) + continue; + + et8ek8_reglist_to_mbus(*list, &format); + + dist = min(fmt->width, format.width) + * min(fmt->height, format.height); + dist = format.width * format.height + + fmt->width * fmt->height - 2 * dist; + + + if (fmt->code == format.code) { + if (dist < max_dist_match || !best_match) { + best_match = *list; + max_dist_match = dist; + } + } else { + if (dist < max_dist_other || !best_other) { + best_other = *list; + max_dist_other = dist; + } + } + } + + return best_match ? best_match : best_other; +} + +#define TIMEPERFRAME_AVG_FPS(t) \ + (((t).denominator + ((t).numerator >> 1)) / (t).numerator) + +static struct et8ek8_reglist *et8ek8_reglist_find_mode_ival( + struct et8ek8_meta_reglist *meta, + struct et8ek8_reglist *current_reglist, + struct v4l2_fract *timeperframe) +{ + int fps = TIMEPERFRAME_AVG_FPS(*timeperframe); + struct et8ek8_reglist **list = et8ek8_reglist_first(meta); + struct et8ek8_mode *current_mode = ¤t_reglist->mode; + + for (; *list; list++) { + struct et8ek8_mode *mode = &(*list)->mode; + + if ((*list)->type != ET8EK8_REGLIST_MODE) + continue; + + if (mode->window_width != current_mode->window_width || + mode->window_height != current_mode->window_height) + continue; + + if (TIMEPERFRAME_AVG_FPS(mode->timeperframe) == fps) + return *list; + } + + return NULL; +} + +static int et8ek8_reglist_cmp(const void *a, const void *b) +{ + const struct et8ek8_reglist **list1 = (const struct et8ek8_reglist **)a, + **list2 = (const struct et8ek8_reglist **)b; + + /* Put real modes in the beginning. */ + if ((*list1)->type == ET8EK8_REGLIST_MODE && + (*list2)->type != ET8EK8_REGLIST_MODE) + return -1; + if ((*list1)->type != ET8EK8_REGLIST_MODE && + (*list2)->type == ET8EK8_REGLIST_MODE) + return 1; + + /* Descending width. */ + if ((*list1)->mode.window_width > (*list2)->mode.window_width) + return -1; + if ((*list1)->mode.window_width < (*list2)->mode.window_width) + return 1; + + if ((*list1)->mode.window_height > (*list2)->mode.window_height) + return -1; + if ((*list1)->mode.window_height < (*list2)->mode.window_height) + return 1; + + return 0; +} + +static int et8ek8_reglist_import(struct i2c_client *client, + struct et8ek8_meta_reglist *meta) +{ + int nlists = 0, i; + + dev_info(&client->dev, "meta_reglist version %s\n", meta->version); + + while (meta->reglist[nlists].ptr) + nlists++; + + if (!nlists) + return -EINVAL; + + sort(&meta->reglist[0].ptr, nlists, sizeof(meta->reglist[0].ptr), + et8ek8_reglist_cmp, NULL); + + i = nlists; + nlists = 0; + + while (i--) { + struct et8ek8_reglist *list; + + list = meta->reglist[nlists].ptr; + + dev_dbg(&client->dev, + "%s: type %d\tw %d\th %d\tfmt %x\tival %d/%d\tptr %p\n", + __func__, + list->type, + list->mode.window_width, list->mode.window_height, + list->mode.bus_format, + list->mode.timeperframe.numerator, + list->mode.timeperframe.denominator, + (void *)meta->reglist[nlists].ptr); + + nlists++; + } + + return 0; +} + +/* Called to change the V4L2 gain control value. This function + * rounds and clamps the given value and updates the V4L2 control value. + * If power is on, also updates the sensor analog and digital gains. + * gain is in 0.1 EV (exposure value) units. + */ +static int et8ek8_set_gain(struct et8ek8_sensor *sensor, s32 gain) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + struct et8ek8_gain new; + int r; + + new = et8ek8_gain_table[gain]; + + /* FIXME: optimise I2C writes! */ + r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, + 0x124a, new.analog >> 8); + if (r) + return r; + r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, + 0x1249, new.analog & 0xff); + if (r) + return r; + + r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, + 0x124d, new.digital >> 8); + if (r) + return r; + r = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, + 0x124c, new.digital & 0xff); + + return r; +} + +static int et8ek8_set_test_pattern(struct et8ek8_sensor *sensor, s32 mode) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int cbh_mode, cbv_mode, tp_mode, din_sw, r1420, rval; + + /* Values for normal mode */ + cbh_mode = 0; + cbv_mode = 0; + tp_mode = 0; + din_sw = 0x00; + r1420 = 0xF0; + + if (mode) { + /* Test pattern mode */ + if (mode < 5) { + cbh_mode = 1; + cbv_mode = 1; + tp_mode = mode + 3; + } else { + cbh_mode = 0; + cbv_mode = 0; + tp_mode = mode - 4 + 3; + } + + din_sw = 0x01; + r1420 = 0xE0; + } + + rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x111B, + tp_mode << 4); + if (rval) + return rval; + + rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1121, + cbh_mode << 7); + if (rval) + return rval; + + rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1124, + cbv_mode << 7); + if (rval) + return rval; + + rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x112C, din_sw); + if (rval) + return rval; + + return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1420, r1420); +} + +/* ----------------------------------------------------------------------------- + * V4L2 controls + */ + +static int et8ek8_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct et8ek8_sensor *sensor = + container_of(ctrl->handler, struct et8ek8_sensor, ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_GAIN: + return et8ek8_set_gain(sensor, ctrl->val); + + case V4L2_CID_EXPOSURE: + { + struct i2c_client *client = + v4l2_get_subdevdata(&sensor->subdev); + + return et8ek8_i2c_write_reg(client, ET8EK8_REG_16BIT, 0x1243, + ctrl->val); + } + + case V4L2_CID_TEST_PATTERN: + return et8ek8_set_test_pattern(sensor, ctrl->val); + + case V4L2_CID_PIXEL_RATE: + return 0; + + default: + return -EINVAL; + } +} + +static const struct v4l2_ctrl_ops et8ek8_ctrl_ops = { + .s_ctrl = et8ek8_set_ctrl, +}; + +static const char * const et8ek8_test_pattern_menu[] = { + "Normal", + "Vertical colorbar", + "Horizontal colorbar", + "Scale", + "Ramp", + "Small vertical colorbar", + "Small horizontal colorbar", + "Small scale", + "Small ramp", +}; + +static int et8ek8_init_controls(struct et8ek8_sensor *sensor) +{ + s32 max_rows; + + v4l2_ctrl_handler_init(&sensor->ctrl_handler, 4); + + /* V4L2_CID_GAIN */ + v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops, + V4L2_CID_GAIN, 0, ARRAY_SIZE(et8ek8_gain_table) - 1, + 1, 0); + + max_rows = sensor->current_reglist->mode.max_exp; + { + u32 min = 1, max = max_rows; + + sensor->exposure = + v4l2_ctrl_new_std(&sensor->ctrl_handler, + &et8ek8_ctrl_ops, V4L2_CID_EXPOSURE, + min, max, min, max); + } + + /* V4L2_CID_PIXEL_RATE */ + sensor->pixel_rate = + v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops, + V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1); + + /* V4L2_CID_TEST_PATTERN */ + v4l2_ctrl_new_std_menu_items(&sensor->ctrl_handler, + &et8ek8_ctrl_ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(et8ek8_test_pattern_menu) - 1, + 0, 0, et8ek8_test_pattern_menu); + + if (sensor->ctrl_handler.error) + return sensor->ctrl_handler.error; + + sensor->subdev.ctrl_handler = &sensor->ctrl_handler; + + return 0; +} + +static void et8ek8_update_controls(struct et8ek8_sensor *sensor) +{ + struct v4l2_ctrl *ctrl; + struct et8ek8_mode *mode = &sensor->current_reglist->mode; + + u32 min, max, pixel_rate; + static const int S = 8; + + ctrl = sensor->exposure; + + min = 1; + max = mode->max_exp; + + /* + * Calculate average pixel clock per line. Assume buffers can spread + * the data over horizontal blanking time. Rounding upwards. + * Formula taken from stock Nokia N900 kernel. + */ + pixel_rate = ((mode->pixel_clock + (1 << S) - 1) >> S) + mode->width; + pixel_rate = mode->window_width * (pixel_rate - 1) / mode->width; + + __v4l2_ctrl_modify_range(ctrl, min, max, min, max); + __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate, pixel_rate << S); +} + +static int et8ek8_configure(struct et8ek8_sensor *sensor) +{ + struct v4l2_subdev *subdev = &sensor->subdev; + struct i2c_client *client = v4l2_get_subdevdata(subdev); + int rval; + + rval = et8ek8_i2c_write_regs(client, sensor->current_reglist->regs); + if (rval) + goto fail; + + /* Controls set while the power to the sensor is turned off are saved + * but not applied to the hardware. Now that we're about to start + * streaming apply all the current values to the hardware. + */ + rval = v4l2_ctrl_handler_setup(&sensor->ctrl_handler); + if (rval) + goto fail; + + return 0; + +fail: + dev_err(&client->dev, "sensor configuration failed\n"); + + return rval; +} + +static int et8ek8_stream_on(struct et8ek8_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + + return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1252, 0xb0); +} + +static int et8ek8_stream_off(struct et8ek8_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + + return et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1252, 0x30); +} + +static int et8ek8_s_stream(struct v4l2_subdev *subdev, int streaming) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + int ret; + + if (!streaming) + return et8ek8_stream_off(sensor); + + ret = et8ek8_configure(sensor); + if (ret < 0) + return ret; + + return et8ek8_stream_on(sensor); +} + +/* -------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static int et8ek8_power_off(struct et8ek8_sensor *sensor) +{ + gpiod_set_value(sensor->reset, 0); + udelay(1); + + clk_disable_unprepare(sensor->ext_clk); + + return regulator_disable(sensor->vana); +} + +static int et8ek8_power_on(struct et8ek8_sensor *sensor) +{ + struct v4l2_subdev *subdev = &sensor->subdev; + struct i2c_client *client = v4l2_get_subdevdata(subdev); + int val, rval; + + rval = regulator_enable(sensor->vana); + if (rval) { + dev_err(&client->dev, "failed to enable vana regulator\n"); + return rval; + } + + rval = clk_prepare_enable(sensor->ext_clk); + if (rval < 0) { + dev_err(&client->dev, "failed to enable extclk\n"); + goto out; + } + + if (rval) + goto out; + + udelay(10); /* I wish this is a good value */ + + /* + * Note: Misinterpretation of reset assertion - do not re-use this code. + * The reset pin is using incorrect (for a reset signal) logical level. + */ + gpiod_set_value(sensor->reset, 1); + + msleep(5000 * 1000 / sensor->xclk_freq + 1); /* Wait 5000 cycles */ + + rval = et8ek8_i2c_reglist_find_write(client, &meta_reglist, + ET8EK8_REGLIST_POWERON); + if (rval) + goto out; + +#ifdef USE_CRC + rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT, 0x1263, &val); + if (rval) + goto out; +#if USE_CRC /* TODO get crc setting from DT */ + val |= BIT(4); +#else + val &= ~BIT(4); +#endif + rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x1263, val); + if (rval) + goto out; +#endif + +out: + if (rval) + et8ek8_power_off(sensor); + + return rval; +} + +/* -------------------------------------------------------------------------- + * V4L2 subdev video operations + */ +#define MAX_FMTS 4 +static int et8ek8_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct et8ek8_reglist **list = + et8ek8_reglist_first(&meta_reglist); + u32 pixelformat[MAX_FMTS]; + int npixelformat = 0; + + if (code->index >= MAX_FMTS) + return -EINVAL; + + for (; *list; list++) { + struct et8ek8_mode *mode = &(*list)->mode; + int i; + + if ((*list)->type != ET8EK8_REGLIST_MODE) + continue; + + for (i = 0; i < npixelformat; i++) { + if (pixelformat[i] == mode->bus_format) + break; + } + if (i != npixelformat) + continue; + + if (code->index == npixelformat) { + code->code = mode->bus_format; + return 0; + } + + pixelformat[npixelformat] = mode->bus_format; + npixelformat++; + } + + return -EINVAL; +} + +static int et8ek8_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct et8ek8_reglist **list = + et8ek8_reglist_first(&meta_reglist); + struct v4l2_mbus_framefmt format; + int cmp_width = INT_MAX; + int cmp_height = INT_MAX; + int index = fse->index; + + for (; *list; list++) { + if ((*list)->type != ET8EK8_REGLIST_MODE) + continue; + + et8ek8_reglist_to_mbus(*list, &format); + if (fse->code != format.code) + continue; + + /* Assume that the modes are grouped by frame size. */ + if (format.width == cmp_width && format.height == cmp_height) + continue; + + cmp_width = format.width; + cmp_height = format.height; + + if (index-- == 0) { + fse->min_width = format.width; + fse->min_height = format.height; + fse->max_width = format.width; + fse->max_height = format.height; + return 0; + } + } + + return -EINVAL; +} + +static int et8ek8_enum_frame_ival(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct et8ek8_reglist **list = + et8ek8_reglist_first(&meta_reglist); + struct v4l2_mbus_framefmt format; + int index = fie->index; + + for (; *list; list++) { + struct et8ek8_mode *mode = &(*list)->mode; + + if ((*list)->type != ET8EK8_REGLIST_MODE) + continue; + + et8ek8_reglist_to_mbus(*list, &format); + if (fie->code != format.code) + continue; + + if (fie->width != format.width || fie->height != format.height) + continue; + + if (index-- == 0) { + fie->interval = mode->timeperframe; + return 0; + } + } + + return -EINVAL; +} + +static struct v4l2_mbus_framefmt * +__et8ek8_get_pad_format(struct et8ek8_sensor *sensor, + struct v4l2_subdev_state *sd_state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_state_get_format(sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &sensor->format; + default: + return NULL; + } +} + +static int et8ek8_get_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + struct v4l2_mbus_framefmt *format; + + format = __et8ek8_get_pad_format(sensor, sd_state, fmt->pad, + fmt->which); + if (!format) + return -EINVAL; + + fmt->format = *format; + + return 0; +} + +static int et8ek8_set_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + struct v4l2_mbus_framefmt *format; + struct et8ek8_reglist *reglist; + + format = __et8ek8_get_pad_format(sensor, sd_state, fmt->pad, + fmt->which); + if (!format) + return -EINVAL; + + reglist = et8ek8_reglist_find_mode_fmt(&meta_reglist, &fmt->format); + et8ek8_reglist_to_mbus(reglist, &fmt->format); + *format = fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + sensor->current_reglist = reglist; + et8ek8_update_controls(sensor); + } + + return 0; +} + +static int et8ek8_get_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval *fi) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + + /* + * FIXME: Implement support for V4L2_SUBDEV_FORMAT_TRY, using the V4L2 + * subdev active state API. + */ + if (fi->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + memset(fi, 0, sizeof(*fi)); + fi->interval = sensor->current_reglist->mode.timeperframe; + + return 0; +} + +static int et8ek8_set_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval *fi) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + struct et8ek8_reglist *reglist; + + /* + * FIXME: Implement support for V4L2_SUBDEV_FORMAT_TRY, using the V4L2 + * subdev active state API. + */ + if (fi->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + reglist = et8ek8_reglist_find_mode_ival(&meta_reglist, + sensor->current_reglist, + &fi->interval); + + if (!reglist) + return -EINVAL; + + sensor->current_reglist = reglist; + et8ek8_update_controls(sensor); + + return 0; +} + +static int et8ek8_g_priv_mem(struct v4l2_subdev *subdev) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + unsigned int length = ET8EK8_PRIV_MEM_SIZE; + unsigned int offset = 0; + u8 *ptr = sensor->priv_mem; + int rval = 0; + + /* Read the EEPROM window-by-window, each window 8 bytes */ + do { + u8 buffer[PRIV_MEM_WIN_SIZE]; + struct i2c_msg msg; + int bytes, i; + int ofs; + + /* Set the current window */ + rval = et8ek8_i2c_write_reg(client, ET8EK8_REG_8BIT, 0x0001, + 0xe0 | (offset >> 3)); + if (rval < 0) + return rval; + + /* Wait for status bit */ + for (i = 0; i < 1000; ++i) { + u32 status; + + rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT, + 0x0003, &status); + if (rval < 0) + return rval; + if (!(status & 0x08)) + break; + usleep_range(1000, 2000); + } + + if (i == 1000) + return -EIO; + + /* Read window, 8 bytes at once, and copy to user space */ + ofs = offset & 0x07; /* Offset within this window */ + bytes = length + ofs > 8 ? 8-ofs : length; + msg.addr = client->addr; + msg.flags = 0; + msg.len = 2; + msg.buf = buffer; + ofs += PRIV_MEM_START_REG; + buffer[0] = (u8)(ofs >> 8); + buffer[1] = (u8)(ofs & 0xFF); + + rval = i2c_transfer(client->adapter, &msg, 1); + if (rval < 0) + return rval; + + mdelay(ET8EK8_I2C_DELAY); + msg.addr = client->addr; + msg.len = bytes; + msg.flags = I2C_M_RD; + msg.buf = buffer; + memset(buffer, 0, sizeof(buffer)); + + rval = i2c_transfer(client->adapter, &msg, 1); + if (rval < 0) + return rval; + + rval = 0; + memcpy(ptr, buffer, bytes); + + length -= bytes; + offset += bytes; + ptr += bytes; + } while (length > 0); + + return rval; +} + +static int et8ek8_dev_init(struct v4l2_subdev *subdev) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + int rval, rev_l, rev_h; + + rval = et8ek8_power_on(sensor); + if (rval) { + dev_err(&client->dev, "could not power on\n"); + return rval; + } + + rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT, + REG_REVISION_NUMBER_L, &rev_l); + if (!rval) + rval = et8ek8_i2c_read_reg(client, ET8EK8_REG_8BIT, + REG_REVISION_NUMBER_H, &rev_h); + if (rval) { + dev_err(&client->dev, "no et8ek8 sensor detected\n"); + goto out_poweroff; + } + + sensor->version = (rev_h << 8) + rev_l; + if (sensor->version != ET8EK8_REV_1 && sensor->version != ET8EK8_REV_2) + dev_info(&client->dev, + "unknown version 0x%x detected, continuing anyway\n", + sensor->version); + + rval = et8ek8_reglist_import(client, &meta_reglist); + if (rval) { + dev_err(&client->dev, + "invalid register list %s, import failed\n", + ET8EK8_NAME); + goto out_poweroff; + } + + sensor->current_reglist = et8ek8_reglist_find_type(&meta_reglist, + ET8EK8_REGLIST_MODE); + if (!sensor->current_reglist) { + dev_err(&client->dev, + "invalid register list %s, no mode found\n", + ET8EK8_NAME); + rval = -ENODEV; + goto out_poweroff; + } + + et8ek8_reglist_to_mbus(sensor->current_reglist, &sensor->format); + + rval = et8ek8_i2c_reglist_find_write(client, &meta_reglist, + ET8EK8_REGLIST_POWERON); + if (rval) { + dev_err(&client->dev, + "invalid register list %s, no POWERON mode found\n", + ET8EK8_NAME); + goto out_poweroff; + } + rval = et8ek8_stream_on(sensor); /* Needed to be able to read EEPROM */ + if (rval) + goto out_poweroff; + rval = et8ek8_g_priv_mem(subdev); + if (rval) + dev_warn(&client->dev, + "can not read OTP (EEPROM) memory from sensor\n"); + rval = et8ek8_stream_off(sensor); + if (rval) + goto out_poweroff; + + rval = et8ek8_power_off(sensor); + if (rval) + goto out_poweroff; + + return 0; + +out_poweroff: + et8ek8_power_off(sensor); + + return rval; +} + +/* -------------------------------------------------------------------------- + * sysfs attributes + */ +static ssize_t +priv_mem_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct v4l2_subdev *subdev = dev_get_drvdata(dev); + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + +#if PAGE_SIZE < ET8EK8_PRIV_MEM_SIZE +#error PAGE_SIZE too small! +#endif + + memcpy(buf, sensor->priv_mem, ET8EK8_PRIV_MEM_SIZE); + + return ET8EK8_PRIV_MEM_SIZE; +} +static DEVICE_ATTR_RO(priv_mem); + +/* -------------------------------------------------------------------------- + * V4L2 subdev core operations + */ + +static int +et8ek8_registered(struct v4l2_subdev *subdev) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + int rval; + + dev_dbg(&client->dev, "registered!"); + + rval = device_create_file(&client->dev, &dev_attr_priv_mem); + if (rval) { + dev_err(&client->dev, "could not register sysfs entry\n"); + return rval; + } + + rval = et8ek8_dev_init(subdev); + if (rval) + goto err_file; + + rval = et8ek8_init_controls(sensor); + if (rval) { + dev_err(&client->dev, "controls initialization failed\n"); + goto err_file; + } + + __et8ek8_get_pad_format(sensor, NULL, 0, V4L2_SUBDEV_FORMAT_ACTIVE); + + return 0; + +err_file: + device_remove_file(&client->dev, &dev_attr_priv_mem); + + return rval; +} + +static int __et8ek8_set_power(struct et8ek8_sensor *sensor, bool on) +{ + return on ? et8ek8_power_on(sensor) : et8ek8_power_off(sensor); +} + +static int et8ek8_set_power(struct v4l2_subdev *subdev, int on) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + int ret = 0; + + mutex_lock(&sensor->power_lock); + + /* If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (sensor->power_count == !on) { + ret = __et8ek8_set_power(sensor, !!on); + if (ret < 0) + goto done; + } + + /* Update the power count. */ + sensor->power_count += on ? 1 : -1; + WARN_ON(sensor->power_count < 0); + +done: + mutex_unlock(&sensor->power_lock); + + return ret; +} + +static int et8ek8_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct et8ek8_sensor *sensor = to_et8ek8_sensor(sd); + struct v4l2_mbus_framefmt *format; + struct et8ek8_reglist *reglist; + + reglist = et8ek8_reglist_find_type(&meta_reglist, ET8EK8_REGLIST_MODE); + format = __et8ek8_get_pad_format(sensor, fh->state, 0, + V4L2_SUBDEV_FORMAT_TRY); + et8ek8_reglist_to_mbus(reglist, format); + + return et8ek8_set_power(sd, true); +} + +static int et8ek8_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return et8ek8_set_power(sd, false); +} + +static const struct v4l2_subdev_video_ops et8ek8_video_ops = { + .s_stream = et8ek8_s_stream, +}; + +static const struct v4l2_subdev_core_ops et8ek8_core_ops = { + .s_power = et8ek8_set_power, +}; + +static const struct v4l2_subdev_pad_ops et8ek8_pad_ops = { + .enum_mbus_code = et8ek8_enum_mbus_code, + .enum_frame_size = et8ek8_enum_frame_size, + .enum_frame_interval = et8ek8_enum_frame_ival, + .get_fmt = et8ek8_get_pad_format, + .set_fmt = et8ek8_set_pad_format, + .get_frame_interval = et8ek8_get_frame_interval, + .set_frame_interval = et8ek8_set_frame_interval, +}; + +static const struct v4l2_subdev_ops et8ek8_ops = { + .core = &et8ek8_core_ops, + .video = &et8ek8_video_ops, + .pad = &et8ek8_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops et8ek8_internal_ops = { + .registered = et8ek8_registered, + .open = et8ek8_open, + .close = et8ek8_close, +}; + +/* -------------------------------------------------------------------------- + * I2C driver + */ +static int __maybe_unused et8ek8_suspend(struct device *dev) +{ + struct v4l2_subdev *subdev = dev_get_drvdata(dev); + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + + if (!sensor->power_count) + return 0; + + return __et8ek8_set_power(sensor, false); +} + +static int __maybe_unused et8ek8_resume(struct device *dev) +{ + struct v4l2_subdev *subdev = dev_get_drvdata(dev); + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + + if (!sensor->power_count) + return 0; + + return __et8ek8_set_power(sensor, true); +} + +static int et8ek8_probe(struct i2c_client *client) +{ + struct et8ek8_sensor *sensor; + struct device *dev = &client->dev; + int ret; + + sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(sensor->reset)) { + dev_dbg(&client->dev, "could not request reset gpio\n"); + return PTR_ERR(sensor->reset); + } + + sensor->vana = devm_regulator_get(dev, "vana"); + if (IS_ERR(sensor->vana)) { + dev_err(&client->dev, "could not get regulator for vana\n"); + return PTR_ERR(sensor->vana); + } + + sensor->ext_clk = devm_v4l2_sensor_clk_get_legacy(dev, NULL, true, + 9600000); + if (IS_ERR(sensor->ext_clk)) + return dev_err_probe(&client->dev, PTR_ERR(sensor->ext_clk), + "could not get clock\n"); + + sensor->xclk_freq = clk_get_rate(sensor->ext_clk); + + mutex_init(&sensor->power_lock); + + v4l2_i2c_subdev_init(&sensor->subdev, client, &et8ek8_ops); + sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->subdev.internal_ops = &et8ek8_internal_ops; + + sensor->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sensor->subdev.entity, 1, &sensor->pad); + if (ret < 0) { + dev_err(&client->dev, "media entity init failed!\n"); + goto err_mutex; + } + + ret = v4l2_async_register_subdev_sensor(&sensor->subdev); + if (ret < 0) + goto err_entity; + + dev_dbg(dev, "initialized!\n"); + + return 0; + +err_entity: + media_entity_cleanup(&sensor->subdev.entity); +err_mutex: + mutex_destroy(&sensor->power_lock); + return ret; +} + +static void et8ek8_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct et8ek8_sensor *sensor = to_et8ek8_sensor(subdev); + + if (sensor->power_count) { + WARN_ON(1); + et8ek8_power_off(sensor); + sensor->power_count = 0; + } + + v4l2_device_unregister_subdev(&sensor->subdev); + device_remove_file(&client->dev, &dev_attr_priv_mem); + v4l2_ctrl_handler_free(&sensor->ctrl_handler); + v4l2_async_unregister_subdev(&sensor->subdev); + media_entity_cleanup(&sensor->subdev.entity); + mutex_destroy(&sensor->power_lock); +} + +static const struct of_device_id et8ek8_of_table[] = { + { .compatible = "toshiba,et8ek8" }, + { }, +}; +MODULE_DEVICE_TABLE(of, et8ek8_of_table); + +static const struct i2c_device_id et8ek8_id_table[] = { + { .name = ET8EK8_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, et8ek8_id_table); + +static const struct dev_pm_ops et8ek8_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(et8ek8_suspend, et8ek8_resume) +}; + +static struct i2c_driver et8ek8_i2c_driver = { + .driver = { + .name = ET8EK8_NAME, + .pm = &et8ek8_pm_ops, + .of_match_table = et8ek8_of_table, + }, + .probe = et8ek8_probe, + .remove = et8ek8_remove, + .id_table = et8ek8_id_table, +}; + +module_i2c_driver(et8ek8_i2c_driver); + +MODULE_AUTHOR("Sakari Ailus , Pavel Machek + * Tuukka Toivonen + */ + +#include "et8ek8_reg.h" + +/* + * Stingray sensor mode settings for Scooby + */ + +/* Mode1_poweron_Mode2_16VGA_2592x1968_12.07fps */ +static struct et8ek8_reglist mode1_poweron_mode2_16vga_2592x1968_12_07fps = { +/* (without the +1) + * SPCK = 80 MHz + * CCP2 = 640 MHz + * VCO = 640 MHz + * VCOUNT = 84 (2016) + * HCOUNT = 137 (3288) + * CKREF_DIV = 2 + * CKVAR_DIV = 200 + * VCO_DIV = 0 + * SPCK_DIV = 7 + * MRCK_DIV = 7 + * LVDSCK_DIV = 0 + */ + .type = ET8EK8_REGLIST_POWERON, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 3288, + .height = 2016, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 2592, + .window_height = 1968, + .pixel_clock = 80000000, + .timeperframe = { + .numerator = 100, + .denominator = 1207 + }, + .max_exp = 2012, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_1X10, + .sensitivity = 65536 + }, + .regs = { + /* Need to set firstly */ + { ET8EK8_REG_8BIT, 0x126C, 0xCC }, + /* Strobe and Data of CCP2 delay are minimized. */ + { ET8EK8_REG_8BIT, 0x1269, 0x00 }, + /* Refined value of Min H_COUNT */ + { ET8EK8_REG_8BIT, 0x1220, 0x89 }, + /* Frequency of SPCK setting (SPCK=MRCK) */ + { ET8EK8_REG_8BIT, 0x123A, 0x07 }, + { ET8EK8_REG_8BIT, 0x1241, 0x94 }, + { ET8EK8_REG_8BIT, 0x1242, 0x02 }, + { ET8EK8_REG_8BIT, 0x124B, 0x00 }, + { ET8EK8_REG_8BIT, 0x1255, 0xFF }, + { ET8EK8_REG_8BIT, 0x1256, 0x9F }, + { ET8EK8_REG_8BIT, 0x1258, 0x00 }, + /* From parallel out to serial out */ + { ET8EK8_REG_8BIT, 0x125D, 0x88 }, + /* From w/ embedded data to w/o embedded data */ + { ET8EK8_REG_8BIT, 0x125E, 0xC0 }, + /* CCP2 out is from STOP to ACTIVE */ + { ET8EK8_REG_8BIT, 0x1263, 0x98 }, + { ET8EK8_REG_8BIT, 0x1268, 0xC6 }, + { ET8EK8_REG_8BIT, 0x1434, 0x00 }, + { ET8EK8_REG_8BIT, 0x1163, 0x44 }, + { ET8EK8_REG_8BIT, 0x1166, 0x29 }, + { ET8EK8_REG_8BIT, 0x1140, 0x02 }, + { ET8EK8_REG_8BIT, 0x1011, 0x24 }, + { ET8EK8_REG_8BIT, 0x1151, 0x80 }, + { ET8EK8_REG_8BIT, 0x1152, 0x23 }, + /* Initial setting for improvement2 of lower frequency noise */ + { ET8EK8_REG_8BIT, 0x1014, 0x05 }, + { ET8EK8_REG_8BIT, 0x1033, 0x06 }, + { ET8EK8_REG_8BIT, 0x1034, 0x79 }, + { ET8EK8_REG_8BIT, 0x1423, 0x3F }, + { ET8EK8_REG_8BIT, 0x1424, 0x3F }, + { ET8EK8_REG_8BIT, 0x1426, 0x00 }, + /* Switch of Preset-White-balance (0d:disable / 1d:enable) */ + { ET8EK8_REG_8BIT, 0x1439, 0x00 }, + /* Switch of blemish correction (0d:disable / 1d:enable) */ + { ET8EK8_REG_8BIT, 0x161F, 0x60 }, + /* Switch of auto noise correction (0d:disable / 1d:enable) */ + { ET8EK8_REG_8BIT, 0x1634, 0x00 }, + { ET8EK8_REG_8BIT, 0x1646, 0x00 }, + { ET8EK8_REG_8BIT, 0x1648, 0x00 }, + { ET8EK8_REG_8BIT, 0x113E, 0x01 }, + { ET8EK8_REG_8BIT, 0x113F, 0x22 }, + { ET8EK8_REG_8BIT, 0x1239, 0x64 }, + { ET8EK8_REG_8BIT, 0x1238, 0x02 }, + { ET8EK8_REG_8BIT, 0x123B, 0x70 }, + { ET8EK8_REG_8BIT, 0x123A, 0x07 }, + { ET8EK8_REG_8BIT, 0x121B, 0x64 }, + { ET8EK8_REG_8BIT, 0x121D, 0x64 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1220, 0x89 }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0x54 }, + { ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/ */ + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode1_16VGA_2592x1968_13.12fps_DPCM10-8 */ +static struct et8ek8_reglist mode1_16vga_2592x1968_13_12fps_dpcm10_8 = { +/* (without the +1) + * SPCK = 80 MHz + * CCP2 = 560 MHz + * VCO = 560 MHz + * VCOUNT = 84 (2016) + * HCOUNT = 128 (3072) + * CKREF_DIV = 2 + * CKVAR_DIV = 175 + * VCO_DIV = 0 + * SPCK_DIV = 6 + * MRCK_DIV = 7 + * LVDSCK_DIV = 0 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 3072, + .height = 2016, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 2592, + .window_height = 1968, + .pixel_clock = 80000000, + .timeperframe = { + .numerator = 100, + .denominator = 1292 + }, + .max_exp = 2012, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x57 }, + { ET8EK8_REG_8BIT, 0x1238, 0x82 }, + { ET8EK8_REG_8BIT, 0x123B, 0x70 }, + { ET8EK8_REG_8BIT, 0x123A, 0x06 }, + { ET8EK8_REG_8BIT, 0x121B, 0x64 }, + { ET8EK8_REG_8BIT, 0x121D, 0x64 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1220, 0x80 }, /* <-changed to v14 7E->80 */ + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0x54 }, + { ET8EK8_REG_8BIT, 0x125D, 0x83 }, /* CCP_LVDS_MODE/ */ + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode3_4VGA_1296x984_29.99fps_DPCM10-8 */ +static struct et8ek8_reglist mode3_4vga_1296x984_29_99fps_dpcm10_8 = { +/* (without the +1) + * SPCK = 96.5333333333333 MHz + * CCP2 = 579.2 MHz + * VCO = 579.2 MHz + * VCOUNT = 84 (2016) + * HCOUNT = 133 (3192) + * CKREF_DIV = 2 + * CKVAR_DIV = 181 + * VCO_DIV = 0 + * SPCK_DIV = 5 + * MRCK_DIV = 7 + * LVDSCK_DIV = 0 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 3192, + .height = 1008, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 1296, + .window_height = 984, + .pixel_clock = 96533333, + .timeperframe = { + .numerator = 100, + .denominator = 3000 + }, + .max_exp = 1004, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x5A }, + { ET8EK8_REG_8BIT, 0x1238, 0x82 }, + { ET8EK8_REG_8BIT, 0x123B, 0x70 }, + { ET8EK8_REG_8BIT, 0x123A, 0x05 }, + { ET8EK8_REG_8BIT, 0x121B, 0x63 }, + { ET8EK8_REG_8BIT, 0x1220, 0x85 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0x54 }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x121D, 0x63 }, + { ET8EK8_REG_8BIT, 0x125D, 0x83 }, /* CCP_LVDS_MODE/ */ + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode4_SVGA_864x656_29.88fps */ +static struct et8ek8_reglist mode4_svga_864x656_29_88fps = { +/* (without the +1) + * SPCK = 80 MHz + * CCP2 = 320 MHz + * VCO = 640 MHz + * VCOUNT = 84 (2016) + * HCOUNT = 166 (3984) + * CKREF_DIV = 2 + * CKVAR_DIV = 200 + * VCO_DIV = 0 + * SPCK_DIV = 7 + * MRCK_DIV = 7 + * LVDSCK_DIV = 1 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 3984, + .height = 672, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 864, + .window_height = 656, + .pixel_clock = 80000000, + .timeperframe = { + .numerator = 100, + .denominator = 2988 + }, + .max_exp = 668, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_1X10, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x64 }, + { ET8EK8_REG_8BIT, 0x1238, 0x02 }, + { ET8EK8_REG_8BIT, 0x123B, 0x71 }, + { ET8EK8_REG_8BIT, 0x123A, 0x07 }, + { ET8EK8_REG_8BIT, 0x121B, 0x62 }, + { ET8EK8_REG_8BIT, 0x121D, 0x62 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1220, 0xA6 }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0x54 }, + { ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/ */ + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode5_VGA_648x492_29.93fps */ +static struct et8ek8_reglist mode5_vga_648x492_29_93fps = { +/* (without the +1) + * SPCK = 80 MHz + * CCP2 = 320 MHz + * VCO = 640 MHz + * VCOUNT = 84 (2016) + * HCOUNT = 221 (5304) + * CKREF_DIV = 2 + * CKVAR_DIV = 200 + * VCO_DIV = 0 + * SPCK_DIV = 7 + * MRCK_DIV = 7 + * LVDSCK_DIV = 1 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 5304, + .height = 504, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 648, + .window_height = 492, + .pixel_clock = 80000000, + .timeperframe = { + .numerator = 100, + .denominator = 2993 + }, + .max_exp = 500, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_1X10, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x64 }, + { ET8EK8_REG_8BIT, 0x1238, 0x02 }, + { ET8EK8_REG_8BIT, 0x123B, 0x71 }, + { ET8EK8_REG_8BIT, 0x123A, 0x07 }, + { ET8EK8_REG_8BIT, 0x121B, 0x61 }, + { ET8EK8_REG_8BIT, 0x121D, 0x61 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1220, 0xDD }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0x54 }, + { ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/ */ + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode2_16VGA_2592x1968_3.99fps */ +static struct et8ek8_reglist mode2_16vga_2592x1968_3_99fps = { +/* (without the +1) + * SPCK = 80 MHz + * CCP2 = 640 MHz + * VCO = 640 MHz + * VCOUNT = 254 (6096) + * HCOUNT = 137 (3288) + * CKREF_DIV = 2 + * CKVAR_DIV = 200 + * VCO_DIV = 0 + * SPCK_DIV = 7 + * MRCK_DIV = 7 + * LVDSCK_DIV = 0 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 3288, + .height = 6096, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 2592, + .window_height = 1968, + .pixel_clock = 80000000, + .timeperframe = { + .numerator = 100, + .denominator = 399 + }, + .max_exp = 6092, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_1X10, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x64 }, + { ET8EK8_REG_8BIT, 0x1238, 0x02 }, + { ET8EK8_REG_8BIT, 0x123B, 0x70 }, + { ET8EK8_REG_8BIT, 0x123A, 0x07 }, + { ET8EK8_REG_8BIT, 0x121B, 0x64 }, + { ET8EK8_REG_8BIT, 0x121D, 0x64 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1220, 0x89 }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0xFE }, + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode_648x492_5fps */ +static struct et8ek8_reglist mode_648x492_5fps = { +/* (without the +1) + * SPCK = 13.3333333333333 MHz + * CCP2 = 53.3333333333333 MHz + * VCO = 640 MHz + * VCOUNT = 84 (2016) + * HCOUNT = 221 (5304) + * CKREF_DIV = 2 + * CKVAR_DIV = 200 + * VCO_DIV = 5 + * SPCK_DIV = 7 + * MRCK_DIV = 7 + * LVDSCK_DIV = 1 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 5304, + .height = 504, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 648, + .window_height = 492, + .pixel_clock = 13333333, + .timeperframe = { + .numerator = 100, + .denominator = 499 + }, + .max_exp = 500, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_1X10, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x64 }, + { ET8EK8_REG_8BIT, 0x1238, 0x02 }, + { ET8EK8_REG_8BIT, 0x123B, 0x71 }, + { ET8EK8_REG_8BIT, 0x123A, 0x57 }, + { ET8EK8_REG_8BIT, 0x121B, 0x61 }, + { ET8EK8_REG_8BIT, 0x121D, 0x61 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1220, 0xDD }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0x54 }, + { ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/ */ + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode3_4VGA_1296x984_5fps */ +static struct et8ek8_reglist mode3_4vga_1296x984_5fps = { +/* (without the +1) + * SPCK = 49.4 MHz + * CCP2 = 395.2 MHz + * VCO = 790.4 MHz + * VCOUNT = 250 (6000) + * HCOUNT = 137 (3288) + * CKREF_DIV = 2 + * CKVAR_DIV = 247 + * VCO_DIV = 1 + * SPCK_DIV = 7 + * MRCK_DIV = 7 + * LVDSCK_DIV = 0 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 3288, + .height = 3000, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 1296, + .window_height = 984, + .pixel_clock = 49400000, + .timeperframe = { + .numerator = 100, + .denominator = 501 + }, + .max_exp = 2996, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_1X10, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x7B }, + { ET8EK8_REG_8BIT, 0x1238, 0x82 }, + { ET8EK8_REG_8BIT, 0x123B, 0x70 }, + { ET8EK8_REG_8BIT, 0x123A, 0x17 }, + { ET8EK8_REG_8BIT, 0x121B, 0x63 }, + { ET8EK8_REG_8BIT, 0x121D, 0x63 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1220, 0x89 }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0xFA }, + { ET8EK8_REG_8BIT, 0x125D, 0x88 }, /* CCP_LVDS_MODE/ */ + { ET8EK8_REG_TERM, 0, 0} + } +}; + +/* Mode_4VGA_1296x984_25fps_DPCM10-8 */ +static struct et8ek8_reglist mode_4vga_1296x984_25fps_dpcm10_8 = { +/* (without the +1) + * SPCK = 84.2666666666667 MHz + * CCP2 = 505.6 MHz + * VCO = 505.6 MHz + * VCOUNT = 88 (2112) + * HCOUNT = 133 (3192) + * CKREF_DIV = 2 + * CKVAR_DIV = 158 + * VCO_DIV = 0 + * SPCK_DIV = 5 + * MRCK_DIV = 7 + * LVDSCK_DIV = 0 + */ + .type = ET8EK8_REGLIST_MODE, + .mode = { + .sensor_width = 2592, + .sensor_height = 1968, + .sensor_window_origin_x = 0, + .sensor_window_origin_y = 0, + .sensor_window_width = 2592, + .sensor_window_height = 1968, + .width = 3192, + .height = 1056, + .window_origin_x = 0, + .window_origin_y = 0, + .window_width = 1296, + .window_height = 984, + .pixel_clock = 84266667, + .timeperframe = { + .numerator = 100, + .denominator = 2500 + }, + .max_exp = 1052, + /* .max_gain = 0, */ + .bus_format = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, + .sensitivity = 65536 + }, + .regs = { + { ET8EK8_REG_8BIT, 0x1239, 0x4F }, + { ET8EK8_REG_8BIT, 0x1238, 0x02 }, + { ET8EK8_REG_8BIT, 0x123B, 0x70 }, + { ET8EK8_REG_8BIT, 0x123A, 0x05 }, + { ET8EK8_REG_8BIT, 0x121B, 0x63 }, + { ET8EK8_REG_8BIT, 0x1220, 0x85 }, + { ET8EK8_REG_8BIT, 0x1221, 0x00 }, + { ET8EK8_REG_8BIT, 0x1222, 0x58 }, + { ET8EK8_REG_8BIT, 0x1223, 0x00 }, + { ET8EK8_REG_8BIT, 0x121D, 0x63 }, + { ET8EK8_REG_8BIT, 0x125D, 0x83 }, + { ET8EK8_REG_TERM, 0, 0} + } +}; + +struct et8ek8_meta_reglist meta_reglist = { + .version = "V14 03-June-2008", + .reglist = { + { .ptr = &mode1_poweron_mode2_16vga_2592x1968_12_07fps }, + { .ptr = &mode1_16vga_2592x1968_13_12fps_dpcm10_8 }, + { .ptr = &mode3_4vga_1296x984_29_99fps_dpcm10_8 }, + { .ptr = &mode4_svga_864x656_29_88fps }, + { .ptr = &mode5_vga_648x492_29_93fps }, + { .ptr = &mode2_16vga_2592x1968_3_99fps }, + { .ptr = &mode_648x492_5fps }, + { .ptr = &mode3_4vga_1296x984_5fps }, + { .ptr = &mode_4vga_1296x984_25fps_dpcm10_8 }, + { .ptr = NULL } + } +}; diff --git a/drivers/media/i2c/et8ek8/et8ek8_reg.h b/drivers/media/i2c/et8ek8/et8ek8_reg.h new file mode 100644 index 000000000..3305986c7 --- /dev/null +++ b/drivers/media/i2c/et8ek8/et8ek8_reg.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * et8ek8_reg.h + * + * Copyright (C) 2008 Nokia Corporation + * + * Contact: Sakari Ailus + * Tuukka Toivonen + */ + +#ifndef ET8EK8REGS_H +#define ET8EK8REGS_H + +#include +#include +#include +#include + +struct v4l2_mbus_framefmt; +struct v4l2_subdev_pad_mbus_code_enum; + +struct et8ek8_mode { + /* Physical sensor resolution and current image window */ + u16 sensor_width; + u16 sensor_height; + u16 sensor_window_origin_x; + u16 sensor_window_origin_y; + u16 sensor_window_width; + u16 sensor_window_height; + + /* Image data coming from sensor (after scaling) */ + u16 width; + u16 height; + u16 window_origin_x; + u16 window_origin_y; + u16 window_width; + u16 window_height; + + u32 pixel_clock; /* in Hz */ + struct v4l2_fract timeperframe; + u32 max_exp; /* Maximum exposure value */ + u32 bus_format; /* MEDIA_BUS_FMT_ */ + u32 sensitivity; /* 16.16 fixed point */ +}; + +#define ET8EK8_REG_8BIT 1 +#define ET8EK8_REG_16BIT 2 +#define ET8EK8_REG_DELAY 100 +#define ET8EK8_REG_TERM 0xff +struct et8ek8_reg { + u16 type; + u16 reg; /* 16-bit offset */ + u32 val; /* 8/16/32-bit value */ +}; + +/* Possible struct smia_reglist types. */ +#define ET8EK8_REGLIST_STANDBY 0 +#define ET8EK8_REGLIST_POWERON 1 +#define ET8EK8_REGLIST_RESUME 2 +#define ET8EK8_REGLIST_STREAMON 3 +#define ET8EK8_REGLIST_STREAMOFF 4 +#define ET8EK8_REGLIST_DISABLED 5 + +#define ET8EK8_REGLIST_MODE 10 + +#define ET8EK8_REGLIST_LSC_ENABLE 100 +#define ET8EK8_REGLIST_LSC_DISABLE 101 +#define ET8EK8_REGLIST_ANR_ENABLE 102 +#define ET8EK8_REGLIST_ANR_DISABLE 103 + +struct et8ek8_reglist { + u32 type; + struct et8ek8_mode mode; + struct et8ek8_reg regs[]; +}; + +#define ET8EK8_MAX_LEN 32 +struct et8ek8_meta_reglist { + char version[ET8EK8_MAX_LEN]; + union { + struct et8ek8_reglist *ptr; + } reglist[]; +}; + +extern struct et8ek8_meta_reglist meta_reglist; + +#endif /* ET8EK8REGS */ diff --git a/drivers/media/i2c/gc0308.c b/drivers/media/i2c/gc0308.c new file mode 100644 index 000000000..15900d541 --- /dev/null +++ b/drivers/media/i2c/gc0308.c @@ -0,0 +1,1443 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the GalaxyCore GC0308 camera sensor. + * + * Copyright (c) 2023 Sebastian Reichel + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* Analog & CISCTL*/ +#define GC0308_CHIP_ID CCI_REG8(0x000) +#define GC0308_HBLANK CCI_REG8(0x001) +#define GC0308_VBLANK CCI_REG8(0x002) +#define GC0308_EXP CCI_REG16(0x003) +#define GC0308_ROW_START CCI_REG16(0x005) +#define GC0308_COL_START CCI_REG16(0x007) +#define GC0308_WIN_HEIGHT CCI_REG16(0x009) +#define GC0308_WIN_WIDTH CCI_REG16(0x00b) +#define GC0308_VS_START_TIME CCI_REG8(0x00d) /* in rows */ +#define GC0308_VS_END_TIME CCI_REG8(0x00e) /* in rows */ +#define GC0308_VB_HB CCI_REG8(0x00f) +#define GC0308_RSH_WIDTH CCI_REG8(0x010) +#define GC0308_TSP_WIDTH CCI_REG8(0x011) +#define GC0308_SAMPLE_HOLD_DELAY CCI_REG8(0x012) +#define GC0308_ROW_TAIL_WIDTH CCI_REG8(0x013) +#define GC0308_CISCTL_MODE1 CCI_REG8(0x014) +#define GC0308_CISCTL_MODE2 CCI_REG8(0x015) +#define GC0308_CISCTL_MODE3 CCI_REG8(0x016) +#define GC0308_CISCTL_MODE4 CCI_REG8(0x017) +#define GC0308_ANALOG_MODE1 CCI_REG8(0x01a) +#define GC0308_ANALOG_MODE2 CCI_REG8(0x01b) +#define GC0308_HRST_RSG_V18 CCI_REG8(0x01c) +#define GC0308_VREF_V25 CCI_REG8(0x01d) +#define GC0308_ADC_R CCI_REG8(0x01e) +#define GC0308_PAD_DRV CCI_REG8(0x01f) +#define GC0308_SOFT_RESET CCI_REG8(0x0fe) + +/* ISP */ +#define GC0308_BLOCK_EN1 CCI_REG8(0x020) +#define GC0308_BLOCK_EN2 CCI_REG8(0x021) +#define GC0308_AAAA_EN CCI_REG8(0x022) +#define GC0308_SPECIAL_EFFECT CCI_REG8(0x023) +#define GC0308_OUT_FORMAT CCI_REG8(0x024) +#define GC0308_OUT_EN CCI_REG8(0x025) +#define GC0308_SYNC_MODE CCI_REG8(0x026) +#define GC0308_CLK_DIV_MODE CCI_REG8(0x028) +#define GC0308_BYPASS_MODE CCI_REG8(0x029) +#define GC0308_CLK_GATING CCI_REG8(0x02a) +#define GC0308_DITHER_MODE CCI_REG8(0x02b) +#define GC0308_DITHER_BIT CCI_REG8(0x02c) +#define GC0308_DEBUG_MODE1 CCI_REG8(0x02d) +#define GC0308_DEBUG_MODE2 CCI_REG8(0x02e) +#define GC0308_DEBUG_MODE3 CCI_REG8(0x02f) +#define GC0308_CROP_WIN_MODE CCI_REG8(0x046) +#define GC0308_CROP_WIN_Y1 CCI_REG8(0x047) +#define GC0308_CROP_WIN_X1 CCI_REG8(0x048) +#define GC0308_CROP_WIN_HEIGHT CCI_REG16(0x049) +#define GC0308_CROP_WIN_WIDTH CCI_REG16(0x04b) + +/* BLK */ +#define GC0308_BLK_MODE CCI_REG8(0x030) +#define GC0308_BLK_LIMIT_VAL CCI_REG8(0x031) +#define GC0308_GLOBAL_OFF CCI_REG8(0x032) +#define GC0308_CURRENT_R_OFF CCI_REG8(0x033) +#define GC0308_CURRENT_G_OFF CCI_REG8(0x034) +#define GC0308_CURRENT_B_OFF CCI_REG8(0x035) +#define GC0308_CURRENT_R_DARK_CURRENT CCI_REG8(0x036) +#define GC0308_CURRENT_G_DARK_CURRENT CCI_REG8(0x037) +#define GC0308_CURRENT_B_DARK_CURRENT CCI_REG8(0x038) +#define GC0308_EXP_RATE_DARKC CCI_REG8(0x039) +#define GC0308_OFF_SUBMODE CCI_REG8(0x03a) +#define GC0308_DARKC_SUBMODE CCI_REG8(0x03b) +#define GC0308_MANUAL_G1_OFF CCI_REG8(0x03c) +#define GC0308_MANUAL_R1_OFF CCI_REG8(0x03d) +#define GC0308_MANUAL_B2_OFF CCI_REG8(0x03e) +#define GC0308_MANUAL_G2_OFF CCI_REG8(0x03f) + +/* PREGAIN */ +#define GC0308_GLOBAL_GAIN CCI_REG8(0x050) +#define GC0308_AUTO_PREGAIN CCI_REG8(0x051) +#define GC0308_AUTO_POSTGAIN CCI_REG8(0x052) +#define GC0308_CHANNEL_GAIN_G1 CCI_REG8(0x053) +#define GC0308_CHANNEL_GAIN_R CCI_REG8(0x054) +#define GC0308_CHANNEL_GAIN_B CCI_REG8(0x055) +#define GC0308_CHANNEL_GAIN_G2 CCI_REG8(0x056) +#define GC0308_R_RATIO CCI_REG8(0x057) +#define GC0308_G_RATIO CCI_REG8(0x058) +#define GC0308_B_RATIO CCI_REG8(0x059) +#define GC0308_AWB_R_GAIN CCI_REG8(0x05a) +#define GC0308_AWB_G_GAIN CCI_REG8(0x05b) +#define GC0308_AWB_B_GAIN CCI_REG8(0x05c) +#define GC0308_LSC_DEC_LVL1 CCI_REG8(0x05d) +#define GC0308_LSC_DEC_LVL2 CCI_REG8(0x05e) +#define GC0308_LSC_DEC_LVL3 CCI_REG8(0x05f) + +/* DNDD */ +#define GC0308_DN_MODE_EN CCI_REG8(0x060) +#define GC0308_DN_MODE_RATIO CCI_REG8(0x061) +#define GC0308_DN_BILAT_B_BASE CCI_REG8(0x062) +#define GC0308_DN_B_INCR CCI_REG8(0x063) +#define GC0308_DN_BILAT_N_BASE CCI_REG8(0x064) +#define GC0308_DN_N_INCR CCI_REG8(0x065) +#define GC0308_DD_DARK_BRIGHT_TH CCI_REG8(0x066) +#define GC0308_DD_FLAT_TH CCI_REG8(0x067) +#define GC0308_DD_LIMIT CCI_REG8(0x068) + +/* ASDE - Auto Saturation De-noise and Edge-Enhancement */ +#define GC0308_ASDE_GAIN_TRESH CCI_REG8(0x069) +#define GC0308_ASDE_GAIN_MODE CCI_REG8(0x06a) +#define GC0308_ASDE_DN_SLOPE CCI_REG8(0x06b) +#define GC0308_ASDE_DD_BRIGHT CCI_REG8(0x06c) +#define GC0308_ASDE_DD_LIMIT CCI_REG8(0x06d) +#define GC0308_ASDE_AUTO_EE1 CCI_REG8(0x06e) +#define GC0308_ASDE_AUTO_EE2 CCI_REG8(0x06f) +#define GC0308_ASDE_AUTO_SAT_DEC_SLOPE CCI_REG8(0x070) +#define GC0308_ASDE_AUTO_SAT_LOW_LIMIT CCI_REG8(0x071) + +/* INTPEE - Interpolation and Edge-Enhancement */ +#define GC0308_EEINTP_MODE_1 CCI_REG8(0x072) +#define GC0308_EEINTP_MODE_2 CCI_REG8(0x073) +#define GC0308_DIRECTION_TH1 CCI_REG8(0x074) +#define GC0308_DIRECTION_TH2 CCI_REG8(0x075) +#define GC0308_DIFF_HV_TI_TH CCI_REG8(0x076) +#define GC0308_EDGE12_EFFECT CCI_REG8(0x077) +#define GC0308_EDGE_POS_RATIO CCI_REG8(0x078) +#define GC0308_EDGE1_MINMAX CCI_REG8(0x079) +#define GC0308_EDGE2_MINMAX CCI_REG8(0x07a) +#define GC0308_EDGE12_TH CCI_REG8(0x07b) +#define GC0308_EDGE_MAX CCI_REG8(0x07c) + +/* ABB - Auto Black Balance */ +#define GC0308_ABB_MODE CCI_REG8(0x080) +#define GC0308_ABB_TARGET_AVGH CCI_REG8(0x081) +#define GC0308_ABB_TARGET_AVGL CCI_REG8(0x082) +#define GC0308_ABB_LIMIT_VAL CCI_REG8(0x083) +#define GC0308_ABB_SPEED CCI_REG8(0x084) +#define GC0308_CURR_R_BLACK_LVL CCI_REG8(0x085) +#define GC0308_CURR_G_BLACK_LVL CCI_REG8(0x086) +#define GC0308_CURR_B_BLACK_LVL CCI_REG8(0x087) +#define GC0308_CURR_R_BLACK_FACTOR CCI_REG8(0x088) +#define GC0308_CURR_G_BLACK_FACTOR CCI_REG8(0x089) +#define GC0308_CURR_B_BLACK_FACTOR CCI_REG8(0x08a) + +/* LSC - Lens Shading Correction */ +#define GC0308_LSC_RED_B2 CCI_REG8(0x08b) +#define GC0308_LSC_GREEN_B2 CCI_REG8(0x08c) +#define GC0308_LSC_BLUE_B2 CCI_REG8(0x08d) +#define GC0308_LSC_RED_B4 CCI_REG8(0x08e) +#define GC0308_LSC_GREEN_B4 CCI_REG8(0x08f) +#define GC0308_LSC_BLUE_B4 CCI_REG8(0x090) +#define GC0308_LSC_ROW_CENTER CCI_REG8(0x091) +#define GC0308_LSC_COL_CENTER CCI_REG8(0x092) + +/* CC - Channel Coefficient */ +#define GC0308_CC_MATRIX_C11 CCI_REG8(0x093) +#define GC0308_CC_MATRIX_C12 CCI_REG8(0x094) +#define GC0308_CC_MATRIX_C13 CCI_REG8(0x095) +#define GC0308_CC_MATRIX_C21 CCI_REG8(0x096) +#define GC0308_CC_MATRIX_C22 CCI_REG8(0x097) +#define GC0308_CC_MATRIX_C23 CCI_REG8(0x098) +#define GC0308_CC_MATRIX_C41 CCI_REG8(0x09c) +#define GC0308_CC_MATRIX_C42 CCI_REG8(0x09d) +#define GC0308_CC_MATRIX_C43 CCI_REG8(0x09e) + +/* GAMMA */ +#define GC0308_GAMMA_OUT0 CCI_REG8(0x09f) +#define GC0308_GAMMA_OUT1 CCI_REG8(0x0a0) +#define GC0308_GAMMA_OUT2 CCI_REG8(0x0a1) +#define GC0308_GAMMA_OUT3 CCI_REG8(0x0a2) +#define GC0308_GAMMA_OUT4 CCI_REG8(0x0a3) +#define GC0308_GAMMA_OUT5 CCI_REG8(0x0a4) +#define GC0308_GAMMA_OUT6 CCI_REG8(0x0a5) +#define GC0308_GAMMA_OUT7 CCI_REG8(0x0a6) +#define GC0308_GAMMA_OUT8 CCI_REG8(0x0a7) +#define GC0308_GAMMA_OUT9 CCI_REG8(0x0a8) +#define GC0308_GAMMA_OUT10 CCI_REG8(0x0a9) +#define GC0308_GAMMA_OUT11 CCI_REG8(0x0aa) +#define GC0308_GAMMA_OUT12 CCI_REG8(0x0ab) +#define GC0308_GAMMA_OUT13 CCI_REG8(0x0ac) +#define GC0308_GAMMA_OUT14 CCI_REG8(0x0ad) +#define GC0308_GAMMA_OUT15 CCI_REG8(0x0ae) +#define GC0308_GAMMA_OUT16 CCI_REG8(0x0af) + +/* YCP */ +#define GC0308_GLOBAL_SATURATION CCI_REG8(0x0b0) +#define GC0308_SATURATION_CB CCI_REG8(0x0b1) +#define GC0308_SATURATION_CR CCI_REG8(0x0b2) +#define GC0308_LUMA_CONTRAST CCI_REG8(0x0b3) +#define GC0308_CONTRAST_CENTER CCI_REG8(0x0b4) +#define GC0308_LUMA_OFFSET CCI_REG8(0x0b5) +#define GC0308_SKIN_CB_CENTER CCI_REG8(0x0b6) +#define GC0308_SKIN_CR_CENTER CCI_REG8(0x0b7) +#define GC0308_SKIN_RADIUS_SQUARE CCI_REG8(0x0b8) +#define GC0308_SKIN_BRIGHTNESS CCI_REG8(0x0b9) +#define GC0308_FIXED_CB CCI_REG8(0x0ba) +#define GC0308_FIXED_CR CCI_REG8(0x0bb) +#define GC0308_EDGE_DEC_SA CCI_REG8(0x0bd) +#define GC0308_AUTO_GRAY_MODE CCI_REG8(0x0be) +#define GC0308_SATURATION_SUB_STRENGTH CCI_REG8(0x0bf) +#define GC0308_Y_GAMMA_OUT0 CCI_REG8(0x0c0) +#define GC0308_Y_GAMMA_OUT1 CCI_REG8(0x0c1) +#define GC0308_Y_GAMMA_OUT2 CCI_REG8(0x0c2) +#define GC0308_Y_GAMMA_OUT3 CCI_REG8(0x0c3) +#define GC0308_Y_GAMMA_OUT4 CCI_REG8(0x0c4) +#define GC0308_Y_GAMMA_OUT5 CCI_REG8(0x0c5) +#define GC0308_Y_GAMMA_OUT6 CCI_REG8(0x0c6) +#define GC0308_Y_GAMMA_OUT7 CCI_REG8(0x0c7) +#define GC0308_Y_GAMMA_OUT8 CCI_REG8(0x0c8) +#define GC0308_Y_GAMMA_OUT9 CCI_REG8(0x0c9) +#define GC0308_Y_GAMMA_OUT10 CCI_REG8(0x0ca) +#define GC0308_Y_GAMMA_OUT11 CCI_REG8(0x0cb) +#define GC0308_Y_GAMMA_OUT12 CCI_REG8(0x0cc) + +/* AEC - Automatic Exposure Control */ +#define GC0308_AEC_MODE1 CCI_REG8(0x0d0) +#define GC0308_AEC_MODE2 CCI_REG8(0x0d1) +#define GC0308_AEC_MODE3 CCI_REG8(0x0d2) +#define GC0308_AEC_TARGET_Y CCI_REG8(0x0d3) +#define GC0308_Y_AVG CCI_REG8(0x0d4) +#define GC0308_AEC_HIGH_LOW_RANGE CCI_REG8(0x0d5) +#define GC0308_AEC_IGNORE CCI_REG8(0x0d6) +#define GC0308_AEC_LIMIT_HIGH_RANGE CCI_REG8(0x0d7) +#define GC0308_AEC_R_OFFSET CCI_REG8(0x0d9) +#define GC0308_AEC_GB_OFFSET CCI_REG8(0x0da) +#define GC0308_AEC_SLOW_MARGIN CCI_REG8(0x0db) +#define GC0308_AEC_FAST_MARGIN CCI_REG8(0x0dc) +#define GC0308_AEC_EXP_CHANGE_GAIN CCI_REG8(0x0dd) +#define GC0308_AEC_STEP2_SUNLIGHT CCI_REG8(0x0de) +#define GC0308_AEC_I_FRAMES CCI_REG8(0x0df) +#define GC0308_AEC_I_STOP_L_MARGIN CCI_REG8(0x0e0) +#define GC0308_AEC_I_STOP_MARGIN CCI_REG8(0x0e1) +#define GC0308_ANTI_FLICKER_STEP CCI_REG16(0x0e2) +#define GC0308_EXP_LVL_1 CCI_REG16(0x0e4) +#define GC0308_EXP_LVL_2 CCI_REG16(0x0e6) +#define GC0308_EXP_LVL_3 CCI_REG16(0x0e8) +#define GC0308_EXP_LVL_4 CCI_REG16(0x0ea) +#define GC0308_MAX_EXP_LVL CCI_REG8(0x0ec) +#define GC0308_EXP_MIN_L CCI_REG8(0x0ed) +#define GC0308_MAX_POST_DF_GAIN CCI_REG8(0x0ee) +#define GC0308_MAX_PRE_DG_GAIN CCI_REG8(0x0ef) + +/* ABS */ +#define GC0308_ABS_RANGE_COMP CCI_REG8(0x0f0) +#define GC0308_ABS_STOP_MARGIN CCI_REG8(0x0f1) +#define GC0308_Y_S_COMP CCI_REG8(0x0f2) +#define GC0308_Y_STRETCH_LIMIT CCI_REG8(0x0f3) +#define GC0308_Y_TILT CCI_REG8(0x0f4) +#define GC0308_Y_STRETCH CCI_REG8(0x0f5) + +/* Measure Window */ +#define GC0308_BIG_WIN_X0 CCI_REG8(0x0f7) +#define GC0308_BIG_WIN_Y0 CCI_REG8(0x0f8) +#define GC0308_BIG_WIN_X1 CCI_REG8(0x0f9) +#define GC0308_BIG_WIN_Y1 CCI_REG8(0x0fa) +#define GC0308_DIFF_Y_BIG_THD CCI_REG8(0x0fb) + +/* OUT Module (P1) */ +#define GC0308_CLOSE_FRAME_EN CCI_REG8(0x150) +#define GC0308_CLOSE_FRAME_NUM1 CCI_REG8(0x151) +#define GC0308_CLOSE_FRAME_NUM2 CCI_REG8(0x152) +#define GC0308_BAYER_MODE CCI_REG8(0x153) +#define GC0308_SUBSAMPLE CCI_REG8(0x154) +#define GC0308_SUBMODE CCI_REG8(0x155) +#define GC0308_SUB_ROW_N1 CCI_REG8(0x156) +#define GC0308_SUB_ROW_N2 CCI_REG8(0x157) +#define GC0308_SUB_COL_N1 CCI_REG8(0x158) +#define GC0308_SUB_COL_N2 CCI_REG8(0x159) + +/* AWB (P1) - Auto White Balance */ +#define GC0308_AWB_RGB_HIGH_LOW CCI_REG8(0x100) +#define GC0308_AWB_Y_TO_C_DIFF2 CCI_REG8(0x102) +#define GC0308_AWB_C_MAX CCI_REG8(0x104) +#define GC0308_AWB_C_INTER CCI_REG8(0x105) +#define GC0308_AWB_C_INTER2 CCI_REG8(0x106) +#define GC0308_AWB_C_MAX_BIG CCI_REG8(0x108) +#define GC0308_AWB_Y_HIGH CCI_REG8(0x109) +#define GC0308_AWB_NUMBER_LIMIT CCI_REG8(0x10a) +#define GC0308_KWIN_RATIO CCI_REG8(0x10b) +#define GC0308_KWIN_THD CCI_REG8(0x10c) +#define GC0308_LIGHT_GAIN_RANGE CCI_REG8(0x10d) +#define GC0308_SMALL_WIN_WIDTH_STEP CCI_REG8(0x10e) +#define GC0308_SMALL_WIN_HEIGHT_STEP CCI_REG8(0x10f) +#define GC0308_AWB_YELLOW_TH CCI_REG8(0x110) +#define GC0308_AWB_MODE CCI_REG8(0x111) +#define GC0308_AWB_ADJUST_SPEED CCI_REG8(0x112) +#define GC0308_AWB_EVERY_N CCI_REG8(0x113) +#define GC0308_R_AVG_USE CCI_REG8(0x1d0) +#define GC0308_G_AVG_USE CCI_REG8(0x1d1) +#define GC0308_B_AVG_USE CCI_REG8(0x1d2) + +#define GC0308_HBLANK_MIN 0x021 +#define GC0308_HBLANK_MAX 0xfff +#define GC0308_HBLANK_DEF 0x040 + +#define GC0308_VBLANK_MIN 0x000 +#define GC0308_VBLANK_MAX 0xfff +#define GC0308_VBLANK_DEF 0x020 + +#define GC0308_PIXEL_RATE 24000000 + +/* + * frame_time = (BT + height + 8) * row_time + * width = 640 (driver does not change window size) + * height = 480 (driver does not change window size) + * row_time = HBLANK + SAMPLE_HOLD_DELAY + width + 8 + 4 + * + * When EXP_TIME > (BT + height): + * BT = EXP_TIME - height - 8 - VS_START_TIME + VS_END_TIME + * else: + * BT = VBLANK + VS_START_TIME + VS_END_TIME + * + * max is 30 FPS + * + * In my tests frame rate mostly depends on exposure time. Unfortuantely + * it's unclear how this is calculated exactly. Also since we enable AEC, + * the frame times vary depending on ambient light conditions. + */ +#define GC0308_FRAME_RATE_MAX 30 + +enum gc0308_exp_val { + GC0308_EXP_M4 = 0, + GC0308_EXP_M3, + GC0308_EXP_M2, + GC0308_EXP_M1, + GC0308_EXP_0, + GC0308_EXP_P1, + GC0308_EXP_P2, + GC0308_EXP_P3, + GC0308_EXP_P4, +}; + +static const s64 gc0308_exposure_menu[] = { + -4, -3, -2, -1, 0, 1, 2, 3, 4 +}; + +struct gc0308_exposure { + u8 luma_offset; + u8 aec_target_y; +}; + +#define GC0308_EXPOSURE(luma_offset_reg, aec_target_y_reg) \ + { .luma_offset = luma_offset_reg, .aec_target_y = aec_target_y_reg } + +static const struct gc0308_exposure gc0308_exposure_values[] = { + [GC0308_EXP_M4] = GC0308_EXPOSURE(0xc0, 0x30), + [GC0308_EXP_M3] = GC0308_EXPOSURE(0xd0, 0x38), + [GC0308_EXP_M2] = GC0308_EXPOSURE(0xe0, 0x40), + [GC0308_EXP_M1] = GC0308_EXPOSURE(0xf0, 0x48), + [GC0308_EXP_0] = GC0308_EXPOSURE(0x08, 0x50), + [GC0308_EXP_P1] = GC0308_EXPOSURE(0x10, 0x5c), + [GC0308_EXP_P2] = GC0308_EXPOSURE(0x20, 0x60), + [GC0308_EXP_P3] = GC0308_EXPOSURE(0x30, 0x68), + [GC0308_EXP_P4] = GC0308_EXPOSURE(0x40, 0x70), +}; + +struct gc0308_awb_gains { + u8 r; + u8 g; + u8 b; +}; + +#define GC0308_AWB_GAINS(red, green, blue) \ + { .r = red, .g = green, .b = blue } + +static const struct gc0308_awb_gains gc0308_awb_gains[] = { + [V4L2_WHITE_BALANCE_AUTO] = GC0308_AWB_GAINS(0x56, 0x40, 0x4a), + [V4L2_WHITE_BALANCE_CLOUDY] = GC0308_AWB_GAINS(0x8c, 0x50, 0x40), + [V4L2_WHITE_BALANCE_DAYLIGHT] = GC0308_AWB_GAINS(0x74, 0x52, 0x40), + [V4L2_WHITE_BALANCE_INCANDESCENT] = GC0308_AWB_GAINS(0x48, 0x40, 0x5c), + [V4L2_WHITE_BALANCE_FLUORESCENT] = GC0308_AWB_GAINS(0x40, 0x42, 0x50), +}; + +struct gc0308_format { + u32 code; + u8 regval; +}; + +#define GC0308_FORMAT(v4l2_code, gc0308_regval) \ + { .code = v4l2_code, .regval = gc0308_regval } + +static const struct gc0308_format gc0308_formats[] = { + GC0308_FORMAT(MEDIA_BUS_FMT_UYVY8_2X8, 0x00), + GC0308_FORMAT(MEDIA_BUS_FMT_VYUY8_2X8, 0x01), + GC0308_FORMAT(MEDIA_BUS_FMT_YUYV8_2X8, 0x02), + GC0308_FORMAT(MEDIA_BUS_FMT_YVYU8_2X8, 0x03), + GC0308_FORMAT(MEDIA_BUS_FMT_RGB565_2X8_BE, 0x06), + GC0308_FORMAT(MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, 0x07), + GC0308_FORMAT(MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE, 0x09), +}; + +struct gc0308_frame_size { + u8 subsample; + u32 width; + u32 height; +}; + +#define GC0308_FRAME_SIZE(s, w, h) \ + { .subsample = s, .width = w, .height = h } + +static const struct gc0308_frame_size gc0308_frame_sizes[] = { + GC0308_FRAME_SIZE(0x11, 640, 480), + GC0308_FRAME_SIZE(0x22, 320, 240), + GC0308_FRAME_SIZE(0x44, 160, 120), +}; + +struct gc0308_mode_registers { + u8 out_format; + u8 subsample; + u16 width; + u16 height; +}; + +struct gc0308 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct media_pad pad; + struct device *dev; + struct clk *clk; + struct regmap *regmap; + struct regulator *vdd; + struct gpio_desc *pwdn_gpio; + struct gpio_desc *reset_gpio; + unsigned int mbus_config; + struct gc0308_mode_registers mode; + struct { + /* mirror cluster */ + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + }; + struct { + /* blanking cluster */ + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + }; +}; + +static inline struct gc0308 *to_gc0308(struct v4l2_subdev *sd) +{ + return container_of(sd, struct gc0308, sd); +} + +static const struct regmap_range_cfg gc0308_ranges[] = { + { + .range_min = 0x0000, + .range_max = 0x01ff, + .selector_reg = 0xfe, + .selector_mask = 0x01, + .selector_shift = 0x00, + .window_start = 0x00, + .window_len = 0x100, + }, +}; + +static const struct regmap_config gc0308_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .max_register = 0x1ff, + .ranges = gc0308_ranges, + .num_ranges = ARRAY_SIZE(gc0308_ranges), + .disable_locking = true, +}; + +static const struct cci_reg_sequence sensor_default_regs[] = { + {GC0308_VB_HB, 0x00}, + {GC0308_HBLANK, 0x40}, + {GC0308_VBLANK, 0x20}, + {GC0308_EXP, 0x0258}, + {GC0308_AWB_R_GAIN, 0x56}, + {GC0308_AWB_G_GAIN, 0x40}, + {GC0308_AWB_B_GAIN, 0x4a}, + {GC0308_ANTI_FLICKER_STEP, 0x0078}, + {GC0308_EXP_LVL_1, 0x0258}, + {GC0308_EXP_LVL_2, 0x0258}, + {GC0308_EXP_LVL_3, 0x0258}, + {GC0308_EXP_LVL_4, 0x0ea6}, + {GC0308_MAX_EXP_LVL, 0x20}, + {GC0308_ROW_START, 0x0000}, + {GC0308_COL_START, 0x0000}, + {GC0308_WIN_HEIGHT, 488}, + {GC0308_WIN_WIDTH, 648}, + {GC0308_VS_START_TIME, 0x02}, + {GC0308_VS_END_TIME, 0x02}, + {GC0308_RSH_WIDTH, 0x22}, + {GC0308_TSP_WIDTH, 0x0d}, + {GC0308_SAMPLE_HOLD_DELAY, 0x50}, + {GC0308_ROW_TAIL_WIDTH, 0x0f}, + {GC0308_CISCTL_MODE1, 0x10}, + {GC0308_CISCTL_MODE2, 0x0a}, + {GC0308_CISCTL_MODE3, 0x05}, + {GC0308_CISCTL_MODE4, 0x01}, + {CCI_REG8(0x018), 0x44}, /* undocumented */ + {CCI_REG8(0x019), 0x44}, /* undocumented */ + {GC0308_ANALOG_MODE1, 0x2a}, + {GC0308_ANALOG_MODE2, 0x00}, + {GC0308_HRST_RSG_V18, 0x49}, + {GC0308_VREF_V25, 0x9a}, + {GC0308_ADC_R, 0x61}, + {GC0308_PAD_DRV, 0x01}, /* drv strength: pclk=4mA */ + {GC0308_BLOCK_EN1, 0x7f}, + {GC0308_BLOCK_EN2, 0xfa}, + {GC0308_AAAA_EN, 0x57}, + {GC0308_OUT_FORMAT, 0xa2}, /* YCbYCr */ + {GC0308_OUT_EN, 0x0f}, + {GC0308_SYNC_MODE, 0x03}, + {GC0308_CLK_DIV_MODE, 0x00}, + {GC0308_DEBUG_MODE1, 0x0a}, + {GC0308_DEBUG_MODE2, 0x00}, + {GC0308_DEBUG_MODE3, 0x01}, + {GC0308_BLK_MODE, 0xf7}, + {GC0308_BLK_LIMIT_VAL, 0x50}, + {GC0308_GLOBAL_OFF, 0x00}, + {GC0308_CURRENT_R_OFF, 0x28}, + {GC0308_CURRENT_G_OFF, 0x2a}, + {GC0308_CURRENT_B_OFF, 0x28}, + {GC0308_EXP_RATE_DARKC, 0x04}, + {GC0308_OFF_SUBMODE, 0x20}, + {GC0308_DARKC_SUBMODE, 0x20}, + {GC0308_MANUAL_G1_OFF, 0x00}, + {GC0308_MANUAL_R1_OFF, 0x00}, + {GC0308_MANUAL_B2_OFF, 0x00}, + {GC0308_MANUAL_G2_OFF, 0x00}, + {GC0308_GLOBAL_GAIN, 0x14}, + {GC0308_AUTO_POSTGAIN, 0x41}, + {GC0308_CHANNEL_GAIN_G1, 0x80}, + {GC0308_CHANNEL_GAIN_R, 0x80}, + {GC0308_CHANNEL_GAIN_B, 0x80}, + {GC0308_CHANNEL_GAIN_G2, 0x80}, + {GC0308_LSC_RED_B2, 0x20}, + {GC0308_LSC_GREEN_B2, 0x20}, + {GC0308_LSC_BLUE_B2, 0x20}, + {GC0308_LSC_RED_B4, 0x14}, + {GC0308_LSC_GREEN_B4, 0x10}, + {GC0308_LSC_BLUE_B4, 0x14}, + {GC0308_LSC_ROW_CENTER, 0x3c}, + {GC0308_LSC_COL_CENTER, 0x50}, + {GC0308_LSC_DEC_LVL1, 0x12}, + {GC0308_LSC_DEC_LVL2, 0x1a}, + {GC0308_LSC_DEC_LVL3, 0x24}, + {GC0308_DN_MODE_EN, 0x07}, + {GC0308_DN_MODE_RATIO, 0x15}, + {GC0308_DN_BILAT_B_BASE, 0x08}, + {GC0308_DN_BILAT_N_BASE, 0x03}, + {GC0308_DD_DARK_BRIGHT_TH, 0xe8}, + {GC0308_DD_FLAT_TH, 0x86}, + {GC0308_DD_LIMIT, 0x82}, + {GC0308_ASDE_GAIN_TRESH, 0x18}, + {GC0308_ASDE_GAIN_MODE, 0x0f}, + {GC0308_ASDE_DN_SLOPE, 0x00}, + {GC0308_ASDE_DD_BRIGHT, 0x5f}, + {GC0308_ASDE_DD_LIMIT, 0x8f}, + {GC0308_ASDE_AUTO_EE1, 0x55}, + {GC0308_ASDE_AUTO_EE2, 0x38}, + {GC0308_ASDE_AUTO_SAT_DEC_SLOPE, 0x15}, + {GC0308_ASDE_AUTO_SAT_LOW_LIMIT, 0x33}, + {GC0308_EEINTP_MODE_1, 0xdc}, + {GC0308_EEINTP_MODE_2, 0x00}, + {GC0308_DIRECTION_TH1, 0x02}, + {GC0308_DIRECTION_TH2, 0x3f}, + {GC0308_DIFF_HV_TI_TH, 0x02}, + {GC0308_EDGE12_EFFECT, 0x38}, + {GC0308_EDGE_POS_RATIO, 0x88}, + {GC0308_EDGE1_MINMAX, 0x81}, + {GC0308_EDGE2_MINMAX, 0x81}, + {GC0308_EDGE12_TH, 0x22}, + {GC0308_EDGE_MAX, 0xff}, + {GC0308_CC_MATRIX_C11, 0x48}, + {GC0308_CC_MATRIX_C12, 0x02}, + {GC0308_CC_MATRIX_C13, 0x07}, + {GC0308_CC_MATRIX_C21, 0xe0}, + {GC0308_CC_MATRIX_C22, 0x40}, + {GC0308_CC_MATRIX_C23, 0xf0}, + {GC0308_SATURATION_CB, 0x40}, + {GC0308_SATURATION_CR, 0x40}, + {GC0308_LUMA_CONTRAST, 0x40}, + {GC0308_SKIN_CB_CENTER, 0xe0}, + {GC0308_EDGE_DEC_SA, 0x38}, + {GC0308_AUTO_GRAY_MODE, 0x36}, + {GC0308_AEC_MODE1, 0xcb}, + {GC0308_AEC_MODE2, 0x10}, + {GC0308_AEC_MODE3, 0x90}, + {GC0308_AEC_TARGET_Y, 0x48}, + {GC0308_AEC_HIGH_LOW_RANGE, 0xf2}, + {GC0308_AEC_IGNORE, 0x16}, + {GC0308_AEC_SLOW_MARGIN, 0x92}, + {GC0308_AEC_FAST_MARGIN, 0xa5}, + {GC0308_AEC_I_FRAMES, 0x23}, + {GC0308_AEC_R_OFFSET, 0x00}, + {GC0308_AEC_GB_OFFSET, 0x00}, + {GC0308_AEC_I_STOP_L_MARGIN, 0x09}, + {GC0308_EXP_MIN_L, 0x04}, + {GC0308_MAX_POST_DF_GAIN, 0xa0}, + {GC0308_MAX_PRE_DG_GAIN, 0x40}, + {GC0308_ABB_MODE, 0x03}, + {GC0308_GAMMA_OUT0, 0x10}, + {GC0308_GAMMA_OUT1, 0x20}, + {GC0308_GAMMA_OUT2, 0x38}, + {GC0308_GAMMA_OUT3, 0x4e}, + {GC0308_GAMMA_OUT4, 0x63}, + {GC0308_GAMMA_OUT5, 0x76}, + {GC0308_GAMMA_OUT6, 0x87}, + {GC0308_GAMMA_OUT7, 0xa2}, + {GC0308_GAMMA_OUT8, 0xb8}, + {GC0308_GAMMA_OUT9, 0xca}, + {GC0308_GAMMA_OUT10, 0xd8}, + {GC0308_GAMMA_OUT11, 0xe3}, + {GC0308_GAMMA_OUT12, 0xeb}, + {GC0308_GAMMA_OUT13, 0xf0}, + {GC0308_GAMMA_OUT14, 0xf8}, + {GC0308_GAMMA_OUT15, 0xfd}, + {GC0308_GAMMA_OUT16, 0xff}, + {GC0308_Y_GAMMA_OUT0, 0x00}, + {GC0308_Y_GAMMA_OUT1, 0x10}, + {GC0308_Y_GAMMA_OUT2, 0x1c}, + {GC0308_Y_GAMMA_OUT3, 0x30}, + {GC0308_Y_GAMMA_OUT4, 0x43}, + {GC0308_Y_GAMMA_OUT5, 0x54}, + {GC0308_Y_GAMMA_OUT6, 0x65}, + {GC0308_Y_GAMMA_OUT7, 0x75}, + {GC0308_Y_GAMMA_OUT8, 0x93}, + {GC0308_Y_GAMMA_OUT9, 0xb0}, + {GC0308_Y_GAMMA_OUT10, 0xcb}, + {GC0308_Y_GAMMA_OUT11, 0xe6}, + {GC0308_Y_GAMMA_OUT12, 0xff}, + {GC0308_ABS_RANGE_COMP, 0x02}, + {GC0308_ABS_STOP_MARGIN, 0x01}, + {GC0308_Y_S_COMP, 0x02}, + {GC0308_Y_STRETCH_LIMIT, 0x30}, + {GC0308_BIG_WIN_X0, 0x12}, + {GC0308_BIG_WIN_Y0, 0x0a}, + {GC0308_BIG_WIN_X1, 0x9f}, + {GC0308_BIG_WIN_Y1, 0x78}, + {GC0308_AWB_RGB_HIGH_LOW, 0xf5}, + {GC0308_AWB_Y_TO_C_DIFF2, 0x20}, + {GC0308_AWB_C_MAX, 0x10}, + {GC0308_AWB_C_INTER, 0x08}, + {GC0308_AWB_C_INTER2, 0x20}, + {GC0308_AWB_C_MAX_BIG, 0x0a}, + {GC0308_AWB_NUMBER_LIMIT, 0xa0}, + {GC0308_KWIN_RATIO, 0x60}, + {GC0308_KWIN_THD, 0x08}, + {GC0308_SMALL_WIN_WIDTH_STEP, 0x44}, + {GC0308_SMALL_WIN_HEIGHT_STEP, 0x32}, + {GC0308_AWB_YELLOW_TH, 0x41}, + {GC0308_AWB_MODE, 0x37}, + {GC0308_AWB_ADJUST_SPEED, 0x22}, + {GC0308_AWB_EVERY_N, 0x19}, + {CCI_REG8(0x114), 0x44}, /* AWB set1 */ + {CCI_REG8(0x115), 0x44}, /* AWB set1 */ + {CCI_REG8(0x116), 0xc2}, /* AWB set1 */ + {CCI_REG8(0x117), 0xa8}, /* AWB set1 */ + {CCI_REG8(0x118), 0x18}, /* AWB set1 */ + {CCI_REG8(0x119), 0x50}, /* AWB set1 */ + {CCI_REG8(0x11a), 0xd8}, /* AWB set1 */ + {CCI_REG8(0x11b), 0xf5}, /* AWB set1 */ + {CCI_REG8(0x170), 0x40}, /* AWB set2 */ + {CCI_REG8(0x171), 0x58}, /* AWB set2 */ + {CCI_REG8(0x172), 0x30}, /* AWB set2 */ + {CCI_REG8(0x173), 0x48}, /* AWB set2 */ + {CCI_REG8(0x174), 0x20}, /* AWB set2 */ + {CCI_REG8(0x175), 0x60}, /* AWB set2 */ + {CCI_REG8(0x177), 0x20}, /* AWB set2 */ + {CCI_REG8(0x178), 0x32}, /* AWB set2 */ + {CCI_REG8(0x130), 0x03}, /* undocumented */ + {CCI_REG8(0x131), 0x40}, /* undocumented */ + {CCI_REG8(0x132), 0x10}, /* undocumented */ + {CCI_REG8(0x133), 0xe0}, /* undocumented */ + {CCI_REG8(0x134), 0xe0}, /* undocumented */ + {CCI_REG8(0x135), 0x00}, /* undocumented */ + {CCI_REG8(0x136), 0x80}, /* undocumented */ + {CCI_REG8(0x137), 0x00}, /* undocumented */ + {CCI_REG8(0x138), 0x04}, /* undocumented */ + {CCI_REG8(0x139), 0x09}, /* undocumented */ + {CCI_REG8(0x13a), 0x12}, /* undocumented */ + {CCI_REG8(0x13b), 0x1c}, /* undocumented */ + {CCI_REG8(0x13c), 0x28}, /* undocumented */ + {CCI_REG8(0x13d), 0x31}, /* undocumented */ + {CCI_REG8(0x13e), 0x44}, /* undocumented */ + {CCI_REG8(0x13f), 0x57}, /* undocumented */ + {CCI_REG8(0x140), 0x6c}, /* undocumented */ + {CCI_REG8(0x141), 0x81}, /* undocumented */ + {CCI_REG8(0x142), 0x94}, /* undocumented */ + {CCI_REG8(0x143), 0xa7}, /* undocumented */ + {CCI_REG8(0x144), 0xb8}, /* undocumented */ + {CCI_REG8(0x145), 0xd6}, /* undocumented */ + {CCI_REG8(0x146), 0xee}, /* undocumented */ + {CCI_REG8(0x147), 0x0d}, /* undocumented */ + {CCI_REG8(0x162), 0xf7}, /* undocumented */ + {CCI_REG8(0x163), 0x68}, /* undocumented */ + {CCI_REG8(0x164), 0xd3}, /* undocumented */ + {CCI_REG8(0x165), 0xd3}, /* undocumented */ + {CCI_REG8(0x166), 0x60}, /* undocumented */ +}; + +struct gc0308_colormode { + u8 special_effect; + u8 dbg_mode1; + u8 block_en1; + u8 aec_mode3; + u8 eeintp_mode_2; + u8 edge12_effect; + u8 luma_contrast; + u8 contrast_center; + u8 fixed_cb; + u8 fixed_cr; +}; + +#define GC0308_COLOR_FX(reg_special_effect, reg_dbg_mode1, reg_block_en1, \ + reg_aec_mode3, reg_eeintp_mode_2, reg_edge12_effect, \ + reg_luma_contrast, reg_contrast_center, \ + reg_fixed_cb, reg_fixed_cr) \ + { \ + .special_effect = reg_special_effect, \ + .dbg_mode1 = reg_dbg_mode1, \ + .block_en1 = reg_block_en1, \ + .aec_mode3 = reg_aec_mode3, \ + .eeintp_mode_2 = reg_eeintp_mode_2, \ + .edge12_effect = reg_edge12_effect, \ + .luma_contrast = reg_luma_contrast, \ + .contrast_center = reg_contrast_center, \ + .fixed_cb = reg_fixed_cb, \ + .fixed_cr = reg_fixed_cr, \ + } + +static const struct gc0308_colormode gc0308_colormodes[] = { + [V4L2_COLORFX_NONE] = + GC0308_COLOR_FX(0x00, 0x0a, 0xff, 0x90, 0x00, + 0x54, 0x3c, 0x80, 0x00, 0x00), + [V4L2_COLORFX_BW] = + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x00, + 0x54, 0x40, 0x80, 0x00, 0x00), + [V4L2_COLORFX_SEPIA] = + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x00, + 0x38, 0x40, 0x80, 0xd0, 0x28), + [V4L2_COLORFX_NEGATIVE] = + GC0308_COLOR_FX(0x01, 0x0a, 0xff, 0x90, 0x00, + 0x38, 0x40, 0x80, 0x00, 0x00), + [V4L2_COLORFX_EMBOSS] = + GC0308_COLOR_FX(0x02, 0x0a, 0xbf, 0x10, 0x01, + 0x38, 0x40, 0x80, 0x00, 0x00), + [V4L2_COLORFX_SKETCH] = + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x10, 0x80, + 0x38, 0x80, 0x90, 0x00, 0x00), + [V4L2_COLORFX_SKY_BLUE] = + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x00, + 0x38, 0x40, 0x80, 0x50, 0xe0), + [V4L2_COLORFX_GRASS_GREEN] = + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x01, + 0x38, 0x40, 0x80, 0xc0, 0xc0), + [V4L2_COLORFX_SKIN_WHITEN] = + GC0308_COLOR_FX(0x02, 0x0a, 0xbf, 0x10, 0x01, + 0x38, 0x60, 0x40, 0x00, 0x00), +}; + +static int gc0308_power_on(struct device *dev) +{ + struct gc0308 *gc0308 = dev_get_drvdata(dev); + int ret; + + ret = regulator_enable(gc0308->vdd); + if (ret) + return ret; + + ret = clk_prepare_enable(gc0308->clk); + if (ret) + goto clk_fail; + + gpiod_set_value_cansleep(gc0308->pwdn_gpio, 0); + usleep_range(10000, 20000); + + gpiod_set_value_cansleep(gc0308->reset_gpio, 1); + usleep_range(10000, 20000); + gpiod_set_value_cansleep(gc0308->reset_gpio, 0); + msleep(30); + + return 0; + +clk_fail: + regulator_disable(gc0308->vdd); + return ret; +} + +static int gc0308_power_off(struct device *dev) +{ + struct gc0308 *gc0308 = dev_get_drvdata(dev); + + gpiod_set_value_cansleep(gc0308->pwdn_gpio, 1); + clk_disable_unprepare(gc0308->clk); + regulator_disable(gc0308->vdd); + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int gc0308_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct gc0308 *gc0308 = to_gc0308(sd); + + return cci_read(gc0308->regmap, CCI_REG8(reg->reg), ®->val, NULL); +} + +static int gc0308_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct gc0308 *gc0308 = to_gc0308(sd); + + return cci_write(gc0308->regmap, CCI_REG8(reg->reg), reg->val, NULL); +} +#endif + +static int gc0308_set_exposure(struct gc0308 *gc0308, enum gc0308_exp_val exp) +{ + const struct gc0308_exposure *regs = &gc0308_exposure_values[exp]; + struct cci_reg_sequence exposure_reg_seq[] = { + {GC0308_LUMA_OFFSET, regs->luma_offset}, + {GC0308_AEC_TARGET_Y, regs->aec_target_y}, + }; + + return cci_multi_reg_write(gc0308->regmap, exposure_reg_seq, + ARRAY_SIZE(exposure_reg_seq), NULL); +} + +static int gc0308_set_awb_mode(struct gc0308 *gc0308, + enum v4l2_auto_n_preset_white_balance val) +{ + const struct gc0308_awb_gains *regs = &gc0308_awb_gains[val]; + struct cci_reg_sequence awb_reg_seq[] = { + {GC0308_AWB_R_GAIN, regs->r}, + {GC0308_AWB_G_GAIN, regs->g}, + {GC0308_AWB_B_GAIN, regs->b}, + }; + int ret; + + ret = cci_update_bits(gc0308->regmap, GC0308_AAAA_EN, + BIT(1), val == V4L2_WHITE_BALANCE_AUTO, NULL); + ret = cci_multi_reg_write(gc0308->regmap, awb_reg_seq, + ARRAY_SIZE(awb_reg_seq), &ret); + + return ret; +} + +static int gc0308_set_colormode(struct gc0308 *gc0308, enum v4l2_colorfx mode) +{ + const struct gc0308_colormode *regs = &gc0308_colormodes[mode]; + struct cci_reg_sequence colormode_reg_seq[] = { + {GC0308_SPECIAL_EFFECT, regs->special_effect}, + {GC0308_DEBUG_MODE1, regs->dbg_mode1}, + {GC0308_BLOCK_EN1, regs->block_en1}, + {GC0308_AEC_MODE3, regs->aec_mode3}, + {GC0308_EEINTP_MODE_2, regs->eeintp_mode_2}, + {GC0308_EDGE12_EFFECT, regs->edge12_effect}, + {GC0308_LUMA_CONTRAST, regs->luma_contrast}, + {GC0308_CONTRAST_CENTER, regs->contrast_center}, + {GC0308_FIXED_CB, regs->fixed_cb}, + {GC0308_FIXED_CR, regs->fixed_cr}, + }; + + return cci_multi_reg_write(gc0308->regmap, colormode_reg_seq, + ARRAY_SIZE(colormode_reg_seq), NULL); +} + +static int gc0308_set_power_line_freq(struct gc0308 *gc0308, int frequency) +{ + static const struct cci_reg_sequence pwr_line_50hz[] = { + {GC0308_ANTI_FLICKER_STEP, 0x0078}, + {GC0308_EXP_LVL_1, 0x0258}, + {GC0308_EXP_LVL_2, 0x0348}, + {GC0308_EXP_LVL_3, 0x04b0}, + {GC0308_EXP_LVL_4, 0x05a0}, + }; + static const struct cci_reg_sequence pwr_line_60hz[] = { + {GC0308_ANTI_FLICKER_STEP, 0x0064}, + {GC0308_EXP_LVL_1, 0x0258}, + {GC0308_EXP_LVL_2, 0x0384}, + {GC0308_EXP_LVL_3, 0x04b0}, + {GC0308_EXP_LVL_4, 0x05dc}, + }; + + switch (frequency) { + case V4L2_CID_POWER_LINE_FREQUENCY_60HZ: + return cci_multi_reg_write(gc0308->regmap, pwr_line_60hz, + ARRAY_SIZE(pwr_line_60hz), NULL); + case V4L2_CID_POWER_LINE_FREQUENCY_50HZ: + return cci_multi_reg_write(gc0308->regmap, pwr_line_50hz, + ARRAY_SIZE(pwr_line_50hz), NULL); + } + + return -EINVAL; +} + +static int gc0308_update_mirror(struct gc0308 *gc0308) +{ + u8 regval = 0x00; + + if (gc0308->vflip->val) + regval |= BIT(1); + + if (gc0308->hflip->val) + regval |= BIT(0); + + return cci_update_bits(gc0308->regmap, GC0308_CISCTL_MODE1, + GENMASK(1, 0), regval, NULL); +} + +static int gc0308_update_blanking(struct gc0308 *gc0308) +{ + u16 vblank = gc0308->vblank->val; + u16 hblank = gc0308->hblank->val; + u8 vbhb = ((vblank >> 4) & 0xf0) | ((hblank >> 8) & 0x0f); + int ret = 0; + + cci_write(gc0308->regmap, GC0308_VB_HB, vbhb, &ret); + cci_write(gc0308->regmap, GC0308_HBLANK, hblank & 0xff, &ret); + cci_write(gc0308->regmap, GC0308_VBLANK, vblank & 0xff, &ret); + + return ret; +} + +static int _gc0308_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct gc0308 *gc0308 = container_of(ctrl->handler, struct gc0308, hdl); + u8 flipval = ctrl->val ? 0xff : 0x00; + + switch (ctrl->id) { + case V4L2_CID_HBLANK: + case V4L2_CID_VBLANK: + return gc0308_update_blanking(gc0308); + case V4L2_CID_VFLIP: + case V4L2_CID_HFLIP: + return gc0308_update_mirror(gc0308); + case V4L2_CID_AUTO_WHITE_BALANCE: + return cci_update_bits(gc0308->regmap, GC0308_AAAA_EN, + BIT(1), flipval, NULL); + case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE: + return gc0308_set_awb_mode(gc0308, ctrl->val); + case V4L2_CID_POWER_LINE_FREQUENCY: + return gc0308_set_power_line_freq(gc0308, ctrl->val); + case V4L2_CID_COLORFX: + return gc0308_set_colormode(gc0308, ctrl->val); + case V4L2_CID_TEST_PATTERN: + return cci_update_bits(gc0308->regmap, GC0308_DEBUG_MODE2, + GENMASK(1, 0), ctrl->val, NULL); + case V4L2_CID_AUTO_EXPOSURE_BIAS: + return gc0308_set_exposure(gc0308, ctrl->val); + } + + return -EINVAL; +} + +static int gc0308_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct gc0308 *gc0308 = container_of(ctrl->handler, struct gc0308, hdl); + int ret; + + if (!pm_runtime_get_if_in_use(gc0308->dev)) + return 0; + + ret = _gc0308_s_ctrl(ctrl); + if (ret) + dev_err(gc0308->dev, "failed to set control: %d\n", ret); + + pm_runtime_put_autosuspend(gc0308->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops gc0308_ctrl_ops = { + .s_ctrl = gc0308_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops gc0308_core_ops = { + .log_status = v4l2_ctrl_subdev_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = gc0308_g_register, + .s_register = gc0308_s_register, +#endif +}; + +static int gc0308_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(gc0308_formats)) + return -EINVAL; + + code->code = gc0308_formats[code->index].code; + + return 0; +} + +static int gc0308_get_format_idx(u32 code) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(gc0308_formats); i++) { + if (gc0308_formats[i].code == code) + return i; + } + + return -1; +} + +static int gc0308_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(gc0308_frame_sizes)) + return -EINVAL; + + if (gc0308_get_format_idx(fse->code) < 0) + return -EINVAL; + + fse->min_width = gc0308_frame_sizes[fse->index].width; + fse->max_width = gc0308_frame_sizes[fse->index].width; + fse->min_height = gc0308_frame_sizes[fse->index].height; + fse->max_height = gc0308_frame_sizes[fse->index].height; + + return 0; +} + +static void gc0308_update_pad_format(const struct gc0308_frame_size *mode, + struct v4l2_mbus_framefmt *fmt, u32 code) +{ + fmt->width = mode->width; + fmt->height = mode->height; + fmt->code = code; + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +static int gc0308_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct gc0308 *gc0308 = to_gc0308(sd); + const struct gc0308_frame_size *mode; + int i = gc0308_get_format_idx(fmt->format.code); + + if (i < 0) + i = 0; + + mode = v4l2_find_nearest_size(gc0308_frame_sizes, + ARRAY_SIZE(gc0308_frame_sizes), width, + height, fmt->format.width, + fmt->format.height); + + gc0308_update_pad_format(mode, &fmt->format, gc0308_formats[i].code); + *v4l2_subdev_state_get_format(sd_state, 0) = fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + + gc0308->mode.out_format = gc0308_formats[i].regval; + gc0308->mode.subsample = mode->subsample; + gc0308->mode.width = mode->width; + gc0308->mode.height = mode->height; + + return 0; +} + +static int gc0308_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_mbus_framefmt *format = + v4l2_subdev_state_get_format(sd_state, 0); + + format->width = 640; + format->height = 480; + format->code = gc0308_formats[0].code; + format->colorspace = V4L2_COLORSPACE_SRGB; + format->field = V4L2_FIELD_NONE; + format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + format->quantization = V4L2_QUANTIZATION_DEFAULT; + format->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + return 0; +} + +static const struct v4l2_subdev_pad_ops gc0308_pad_ops = { + .enum_mbus_code = gc0308_enum_mbus_code, + .enum_frame_size = gc0308_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = gc0308_set_format, +}; + +static int gc0308_set_resolution(struct gc0308 *gc0308, int *ret) +{ + struct cci_reg_sequence resolution_regs[] = { + {GC0308_SUBSAMPLE, gc0308->mode.subsample}, + {GC0308_SUBMODE, 0x03}, + {GC0308_SUB_ROW_N1, 0x00}, + {GC0308_SUB_ROW_N2, 0x00}, + {GC0308_SUB_COL_N1, 0x00}, + {GC0308_SUB_COL_N2, 0x00}, + {GC0308_CROP_WIN_MODE, 0x80}, + {GC0308_CROP_WIN_Y1, 0x00}, + {GC0308_CROP_WIN_X1, 0x00}, + {GC0308_CROP_WIN_HEIGHT, gc0308->mode.height}, + {GC0308_CROP_WIN_WIDTH, gc0308->mode.width}, + }; + + return cci_multi_reg_write(gc0308->regmap, resolution_regs, + ARRAY_SIZE(resolution_regs), ret); +} + +static int gc0308_start_stream(struct gc0308 *gc0308) +{ + int ret, sync_mode; + + ret = pm_runtime_resume_and_get(gc0308->dev); + if (ret < 0) + return ret; + + cci_multi_reg_write(gc0308->regmap, sensor_default_regs, + ARRAY_SIZE(sensor_default_regs), &ret); + cci_update_bits(gc0308->regmap, GC0308_OUT_FORMAT, + GENMASK(4, 0), gc0308->mode.out_format, &ret); + gc0308_set_resolution(gc0308, &ret); + + if (ret) { + dev_err(gc0308->dev, "failed to update registers: %d\n", ret); + goto disable_pm; + } + + ret = __v4l2_ctrl_handler_setup(&gc0308->hdl); + if (ret) { + dev_err(gc0308->dev, "failed to setup controls\n"); + goto disable_pm; + } + + /* HSYNC/VSYNC polarity */ + sync_mode = 0x3; + if (gc0308->mbus_config & V4L2_MBUS_VSYNC_ACTIVE_LOW) + sync_mode &= ~BIT(0); + if (gc0308->mbus_config & V4L2_MBUS_HSYNC_ACTIVE_LOW) + sync_mode &= ~BIT(1); + ret = cci_write(gc0308->regmap, GC0308_SYNC_MODE, sync_mode, NULL); + if (ret) + goto disable_pm; + + return 0; + +disable_pm: + pm_runtime_put_autosuspend(gc0308->dev); + return ret; +} + +static int gc0308_stop_stream(struct gc0308 *gc0308) +{ + pm_runtime_put_autosuspend(gc0308->dev); + return 0; +} + +static int gc0308_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct gc0308 *gc0308 = to_gc0308(sd); + struct v4l2_subdev_state *sd_state; + int ret; + + sd_state = v4l2_subdev_lock_and_get_active_state(sd); + + if (enable) + ret = gc0308_start_stream(gc0308); + else + ret = gc0308_stop_stream(gc0308); + + v4l2_subdev_unlock_state(sd_state); + return ret; +} + +static const struct v4l2_subdev_video_ops gc0308_video_ops = { + .s_stream = gc0308_s_stream, +}; + +static const struct v4l2_subdev_ops gc0308_subdev_ops = { + .core = &gc0308_core_ops, + .pad = &gc0308_pad_ops, + .video = &gc0308_video_ops, +}; + +static const struct v4l2_subdev_internal_ops gc0308_internal_ops = { + .init_state = gc0308_init_state, +}; + +static int gc0308_bus_config(struct gc0308 *gc0308) +{ + struct device *dev = gc0308->dev; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_PARALLEL + }; + struct fwnode_handle *ep; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); + if (!ep) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + gc0308->mbus_config = bus_cfg.bus.parallel.flags; + + return 0; +} + +static const char * const gc0308_test_pattern_menu[] = { + "Disabled", + "Test Image 1", + "Test Image 2", +}; + +static int gc0308_init_controls(struct gc0308 *gc0308) +{ + int ret; + + v4l2_ctrl_handler_init(&gc0308->hdl, 11); + gc0308->hblank = v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops, + V4L2_CID_HBLANK, GC0308_HBLANK_MIN, + GC0308_HBLANK_MAX, 1, + GC0308_HBLANK_DEF); + gc0308->vblank = v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops, + V4L2_CID_VBLANK, GC0308_VBLANK_MIN, + GC0308_VBLANK_MAX, 1, + GC0308_VBLANK_DEF); + gc0308->hflip = v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + gc0308->vflip = v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops, V4L2_CID_PIXEL_RATE, + GC0308_PIXEL_RATE, GC0308_PIXEL_RATE, 1, + GC0308_PIXEL_RATE); + v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); + v4l2_ctrl_new_std_menu_items(&gc0308->hdl, &gc0308_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(gc0308_test_pattern_menu) - 1, + 0, 0, gc0308_test_pattern_menu); + v4l2_ctrl_new_std_menu(&gc0308->hdl, &gc0308_ctrl_ops, + V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, + 8, ~0x14e, V4L2_WHITE_BALANCE_AUTO); + v4l2_ctrl_new_std_menu(&gc0308->hdl, &gc0308_ctrl_ops, + V4L2_CID_COLORFX, 8, 0, V4L2_COLORFX_NONE); + v4l2_ctrl_new_std_menu(&gc0308->hdl, &gc0308_ctrl_ops, + V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_60HZ, + ~0x6, V4L2_CID_POWER_LINE_FREQUENCY_50HZ); + v4l2_ctrl_new_int_menu(&gc0308->hdl, &gc0308_ctrl_ops, + V4L2_CID_AUTO_EXPOSURE_BIAS, + ARRAY_SIZE(gc0308_exposure_menu) - 1, + ARRAY_SIZE(gc0308_exposure_menu) / 2, + gc0308_exposure_menu); + + gc0308->sd.ctrl_handler = &gc0308->hdl; + if (gc0308->hdl.error) { + ret = gc0308->hdl.error; + v4l2_ctrl_handler_free(&gc0308->hdl); + return ret; + } + + v4l2_ctrl_cluster(2, &gc0308->hflip); + v4l2_ctrl_cluster(2, &gc0308->hblank); + + return 0; +} + +static int gc0308_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct gc0308 *gc0308; + unsigned long clkrate; + u64 regval; + int ret; + + gc0308 = devm_kzalloc(dev, sizeof(*gc0308), GFP_KERNEL); + if (!gc0308) + return -ENOMEM; + + gc0308->dev = dev; + dev_set_drvdata(dev, gc0308); + + ret = gc0308_bus_config(gc0308); + if (ret) + return dev_err_probe(dev, ret, "failed to get bus config\n"); + + gc0308->clk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(gc0308->clk)) + return dev_err_probe(dev, PTR_ERR(gc0308->clk), + "could not get clk\n"); + + gc0308->vdd = devm_regulator_get(dev, "vdd28"); + if (IS_ERR(gc0308->vdd)) + return dev_err_probe(dev, PTR_ERR(gc0308->vdd), + "failed to get vdd28 regulator\n"); + + gc0308->pwdn_gpio = devm_gpiod_get(dev, "powerdown", GPIOD_OUT_LOW); + if (IS_ERR(gc0308->pwdn_gpio)) + return dev_err_probe(dev, PTR_ERR(gc0308->pwdn_gpio), + "failed to get powerdown gpio\n"); + + gc0308->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(gc0308->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(gc0308->reset_gpio), + "failed to get reset gpio\n"); + + /* + * This is not using devm_cci_regmap_init_i2c(), because the driver + * makes use of regmap's pagination feature. The chosen settings are + * compatible with the CCI helpers. + */ + gc0308->regmap = devm_regmap_init_i2c(client, &gc0308_regmap_config); + if (IS_ERR(gc0308->regmap)) + return dev_err_probe(dev, PTR_ERR(gc0308->regmap), + "failed to init regmap\n"); + + v4l2_i2c_subdev_init(&gc0308->sd, client, &gc0308_subdev_ops); + gc0308->sd.internal_ops = &gc0308_internal_ops; + gc0308->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + ret = gc0308_init_controls(gc0308); + if (ret) + return dev_err_probe(dev, ret, "failed to init controls\n"); + + gc0308->sd.state_lock = gc0308->hdl.lock; + gc0308->pad.flags = MEDIA_PAD_FL_SOURCE; + gc0308->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&gc0308->sd.entity, 1, &gc0308->pad); + if (ret < 0) + goto fail_ctrl_hdl_cleanup; + + ret = v4l2_subdev_init_finalize(&gc0308->sd); + if (ret) + goto fail_media_entity_cleanup; + + ret = gc0308_power_on(dev); + if (ret) + goto fail_subdev_cleanup; + + if (gc0308->clk) { + clkrate = clk_get_rate(gc0308->clk); + if (clkrate != 24000000) + dev_warn(dev, "unexpected clock rate: %lu\n", clkrate); + } + + ret = cci_read(gc0308->regmap, GC0308_CHIP_ID, ®val, NULL); + if (ret < 0) { + dev_err_probe(dev, ret, "failed to read chip ID\n"); + goto fail_power_off; + } + + if (regval != 0x9b) { + ret = -EINVAL; + dev_err_probe(dev, ret, "invalid chip ID (%02llx)\n", regval); + goto fail_power_off; + } + + /* + * Enable runtime PM with autosuspend. As the device has been powered + * manually, mark it as active, and increase the usage count without + * resuming the device. + */ + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + + ret = v4l2_async_register_subdev(&gc0308->sd); + if (ret) { + dev_err_probe(dev, ret, "failed to register v4l subdev\n"); + goto fail_rpm; + } + + return 0; + +fail_rpm: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); +fail_power_off: + gc0308_power_off(dev); +fail_subdev_cleanup: + v4l2_subdev_cleanup(&gc0308->sd); +fail_media_entity_cleanup: + media_entity_cleanup(&gc0308->sd.entity); +fail_ctrl_hdl_cleanup: + v4l2_ctrl_handler_free(&gc0308->hdl); + return ret; +} + +static void gc0308_remove(struct i2c_client *client) +{ + struct gc0308 *gc0308 = i2c_get_clientdata(client); + struct device *dev = &client->dev; + + v4l2_async_unregister_subdev(&gc0308->sd); + v4l2_ctrl_handler_free(&gc0308->hdl); + media_entity_cleanup(&gc0308->sd.entity); + + pm_runtime_disable(dev); + if (!pm_runtime_status_suspended(dev)) + gc0308_power_off(dev); + pm_runtime_set_suspended(dev); +} + +static const struct dev_pm_ops gc0308_pm_ops = { + SET_RUNTIME_PM_OPS(gc0308_power_off, gc0308_power_on, NULL) +}; + +static const struct of_device_id gc0308_of_match[] = { + { .compatible = "galaxycore,gc0308" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, gc0308_of_match); + +static struct i2c_driver gc0308_i2c_driver = { + .driver = { + .name = "gc0308", + .pm = &gc0308_pm_ops, + .of_match_table = gc0308_of_match, + }, + .probe = gc0308_probe, + .remove = gc0308_remove, +}; +module_i2c_driver(gc0308_i2c_driver); + +MODULE_DESCRIPTION("GalaxyCore GC0308 Camera Driver"); +MODULE_AUTHOR("Sebastian Reichel "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/gc0310.c b/drivers/media/i2c/gc0310.c new file mode 100644 index 000000000..754e82ad5 --- /dev/null +++ b/drivers/media/i2c/gc0310.c @@ -0,0 +1,781 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Support for GalaxyCore GC0310 VGA camera sensor. + * + * Copyright (c) 2013 Intel Corporation. All Rights Reserved. + * Copyright (c) 2023-2025 Hans de Goede + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define GC0310_NATIVE_WIDTH 656 +#define GC0310_NATIVE_HEIGHT 496 + +/* + * The actual PLL output rate is unknown, the datasheet + * says that the formula for the frame-time in pixels is: + * rowtime = win-width + hblank + sh-delay + 4 + * frametime = rowtime * (win-height + vblank) + * Filling this in and multiplying by 30 fps gives: + * pixelrate = (660 + 178 + 42 + 4) * (498 + 27) * 30 = 13923000 + */ +#define GC0310_PIXELRATE 13923000 +/* single lane, bus-format is 8 bpp, CSI-2 is double data rate */ +#define GC0310_LINK_FREQ (GC0310_PIXELRATE * 8 / 2) +#define GC0310_MCLK_FREQ 19200000 +#define GC0310_FPS 30 +#define GC0310_SKIP_FRAMES 3 + +#define GC0310_ID 0xa310 + +#define GC0310_RESET_RELATED_REG CCI_REG8(0xfe) +#define GC0310_REGISTER_PAGE_0 0x0 +#define GC0310_REGISTER_PAGE_3 0x3 + +/* + * GC0310 System control registers + */ +#define GC0310_SW_STREAM_REG CCI_REG8(0x10) + +#define GC0310_START_STREAMING 0x94 /* 8-bit enable */ +#define GC0310_STOP_STREAMING 0x0 /* 8-bit disable */ + +#define GC0310_SC_CMMN_CHIP_ID_REG CCI_REG16(0xf0) + +#define GC0310_AEC_PK_EXPO_REG CCI_REG16(0x03) +#define GC0310_AGC_ADJ_REG CCI_REG8(0x48) +#define GC0310_DGC_ADJ_REG CCI_REG8(0x71) + +#define GC0310_H_CROP_START_REG CCI_REG16(0x09) +#define GC0310_V_CROP_START_REG CCI_REG16(0x0b) +#define GC0310_H_OUTSIZE_REG CCI_REG16(0x0f) +#define GC0310_V_OUTSIZE_REG CCI_REG16(0x0d) + +#define GC0310_H_BLANKING_REG CCI_REG16(0x05) +/* Hblank-register + sh-delay + H-crop + 4 (from hw) */ +#define GC0310_H_BLANK_DEFAULT (178 + 42 + 4 + 4) + +#define GC0310_V_BLANKING_REG CCI_REG16(0x07) +/* Vblank needs an offset compensate for the small V-crop done */ +#define GC0310_V_BLANK_OFFSET 2 +/* Vsync start time + 1 row vsync + vsync end time + offset */ +#define GC0310_V_BLANK_MIN (9 + 1 + 4 + GC0310_V_BLANK_OFFSET) +#define GC0310_V_BLANK_DEFAULT (27 + GC0310_V_BLANK_OFFSET) +#define GC0310_V_BLANK_MAX (4095 - GC0310_NATIVE_HEIGHT) + +#define GC0310_SH_DELAY_REG CCI_REG8(0x11) +#define GC0310_VS_START_TIME_REG CCI_REG8(0x12) +#define GC0310_VS_END_TIME_REG CCI_REG8(0x13) + +#define to_gc0310_sensor(x) container_of(x, struct gc0310_device, sd) + +struct gc0310_device { + struct clk *clk; + + struct v4l2_subdev sd; + struct media_pad pad; + + struct regmap *regmap; + struct gpio_desc *reset; + struct gpio_desc *powerdown; + + struct gc0310_ctrls { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *gain; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + } ctrls; +}; + +struct gc0310_reg { + u8 reg; + u8 val; +}; + +static const struct reg_sequence gc0310_reset_register[] = { + /* System registers */ + { 0xfe, 0xf0 }, + { 0xfe, 0xf0 }, + { 0xfe, 0x00 }, + + { 0xfc, 0x0e }, /* 4e */ + { 0xfc, 0x0e }, /* 16//4e // [0]apwd [6]regf_clk_gate */ + { 0xf2, 0x80 }, /* sync output */ + { 0xf3, 0x00 }, /* 1f//01 data output */ + { 0xf7, 0x33 }, /* f9 */ + { 0xf8, 0x05 }, /* 00 */ + { 0xf9, 0x0e }, /* 0x8e //0f */ + { 0xfa, 0x11 }, + + /* MIPI */ + { 0xfe, 0x03 }, + { 0x01, 0x03 }, /* mipi 1lane */ + { 0x02, 0x22 }, /* 0x33 */ + { 0x03, 0x94 }, + { 0x04, 0x01 }, /* fifo_prog */ + { 0x05, 0x00 }, /* fifo_prog */ + { 0x06, 0x80 }, /* b0 //YUV ISP data */ + { 0x11, 0x2a }, /* 1e //LDI set YUV422 */ + { 0x12, 0x90 }, /* 00 //04 //00 //04//00 //LWC[7:0] */ + { 0x13, 0x02 }, /* 05 //05 //LWC[15:8] */ + { 0x15, 0x12 }, /* 0x10 //DPHYY_MODE read_ready */ + { 0x17, 0x01 }, + { 0x40, 0x08 }, + { 0x41, 0x00 }, + { 0x42, 0x00 }, + { 0x43, 0x00 }, + { 0x21, 0x02 }, /* 0x01 */ + { 0x22, 0x02 }, /* 0x01 */ + { 0x23, 0x01 }, /* 0x05 //Nor:0x05 DOU:0x06 */ + { 0x29, 0x00 }, + { 0x2A, 0x25 }, /* 0x05 //data zero 0x7a de */ + { 0x2B, 0x02 }, + + { 0xfe, 0x00 }, + + /* CISCTL */ + { 0x00, 0x2f }, /* 2f//0f//02//01 */ + { 0x01, 0x0f }, /* 06 */ + { 0x02, 0x04 }, + { 0x4f, 0x00 }, /* AEC 0FF */ + { 0x03, 0x01 }, /* 0x03 //04 */ + { 0x04, 0xc0 }, /* 0xe8 //58 */ + { 0x05, 0x00 }, + { 0x06, 0xb2 }, /* 0x0a //HB */ + /* Vblank (reg 0x07 + 0x08) gets set by the vblank ctrl */ + { 0x09, 0x00 }, /* row start */ + { 0x0a, 0x00 }, + { 0x0b, 0x00 }, /* col start */ + { 0x0c, 0x00 }, + { 0x0d, 0x01 }, /* height */ + { 0x0e, 0xf2 }, /* 0xf7 //height */ + { 0x0f, 0x02 }, /* width */ + { 0x10, 0x94 }, /* 0xa0 //height */ + { 0x17, 0x14 }, + { 0x18, 0x1a }, /* 0a//[4]double reset */ + { 0x19, 0x14 }, /* AD pipeline */ + { 0x1b, 0x48 }, + { 0x1e, 0x6b }, /* 3b//col bias */ + { 0x1f, 0x28 }, /* 20//00//08//txlow */ + { 0x20, 0x89 }, /* 88//0c//[3:2]DA15 */ + { 0x21, 0x49 }, /* 48//[3] txhigh */ + { 0x22, 0xb0 }, + { 0x23, 0x04 }, /* [1:0]vcm_r */ + { 0x24, 0x16 }, /* 15 */ + { 0x34, 0x20 }, /* [6:4] rsg high//range */ + + /* BLK */ + { 0x26, 0x23 }, /* [1]dark_current_en [0]offset_en */ + { 0x28, 0xff }, /* BLK_limie_value */ + { 0x29, 0x00 }, /* global offset */ + { 0x33, 0x18 }, /* offset_ratio */ + { 0x37, 0x20 }, /* dark_current_ratio */ + { 0x2a, 0x00 }, + { 0x2b, 0x00 }, + { 0x2c, 0x00 }, + { 0x2d, 0x00 }, + { 0x2e, 0x00 }, + { 0x2f, 0x00 }, + { 0x30, 0x00 }, + { 0x31, 0x00 }, + { 0x47, 0x80 }, /* a7 */ + { 0x4e, 0x66 }, /* select_row */ + { 0xa8, 0x02 }, /* win_width_dark, same with crop_win_width */ + { 0xa9, 0x80 }, + + /* ISP */ + { 0x40, 0x06 }, /* 0xff //ff //48 */ + { 0x41, 0x00 }, /* 0x21 //00//[0]curve_en */ + { 0x42, 0x04 }, /* 0xcf //0a//[1]awn_en */ + { 0x44, 0x18 }, /* 0x18 //02 */ + { 0x46, 0x02 }, /* 0x03 //sync */ + { 0x49, 0x03 }, + { 0x4c, 0x20 }, /* 00[5]pretect exp */ + { 0x50, 0x01 }, /* crop enable */ + { 0x51, 0x00 }, + { 0x52, 0x00 }, + { 0x53, 0x00 }, + { 0x54, 0x01 }, + { 0x55, 0x01 }, /* crop window height */ + { 0x56, 0xf0 }, + { 0x57, 0x02 }, /* crop window width */ + { 0x58, 0x90 }, + + /* Gain */ + { 0x70, 0x70 }, /* 70 //80//global gain */ + { 0x71, 0x20 }, /* pregain gain */ + { 0x72, 0x40 }, /* post gain */ + { 0x5a, 0x84 }, /* 84//analog gain 0 */ + { 0x5b, 0xc9 }, /* c9 */ + { 0x5c, 0xed }, /* ed//not use pga gain highest level */ + { 0x77, 0x40 }, /* R gain 0x74 //awb gain */ + { 0x78, 0x40 }, /* G gain */ + { 0x79, 0x40 }, /* B gain 0x5f */ + + { 0x48, 0x00 }, + { 0xfe, 0x01 }, + { 0x0a, 0x45 }, /* [7]col gain mode */ + + { 0x3e, 0x40 }, + { 0x3f, 0x5c }, + { 0x40, 0x7b }, + { 0x41, 0xbd }, + { 0x42, 0xf6 }, + { 0x43, 0x63 }, + { 0x03, 0x60 }, + { 0x44, 0x03 }, + + /* Dark / Sun mode related */ + { 0xfe, 0x01 }, + { 0x45, 0xa4 }, /* 0xf7 */ + { 0x46, 0xf0 }, /* 0xff //f0//sun value th */ + { 0x48, 0x03 }, /* sun mode */ + { 0x4f, 0x60 }, /* sun_clamp */ + { 0xfe, 0x00 }, +}; + +static const struct reg_sequence gc0310_VGA_30fps[] = { + { 0xfe, 0x00 }, + { 0x0d, 0x01 }, /* height */ + { 0x0e, 0xf2 }, /* 0xf7 //height */ + { 0x0f, 0x02 }, /* width */ + { 0x10, 0x94 }, /* 0xa0 //height */ + + { 0x50, 0x01 }, /* crop enable */ + { 0x51, 0x00 }, + { 0x52, 0x00 }, + { 0x53, 0x00 }, + { 0x54, 0x01 }, + { 0x55, 0x01 }, /* crop window height */ + { 0x56, 0xf0 }, + { 0x57, 0x02 }, /* crop window width */ + { 0x58, 0x90 }, + + { 0xfe, 0x03 }, + { 0x12, 0x90 }, /* 00 //04 //00 //04//00 //LWC[7:0] */ + { 0x13, 0x02 }, /* 05 //05 //LWC[15:8] */ + + { 0xfe, 0x00 }, +}; + +static const s64 link_freq_menu_items[] = { + GC0310_LINK_FREQ, +}; + +static int gc0310_gain_set(struct gc0310_device *sensor, u32 gain) +{ + u8 again, dgain; + int ret = 0; + + /* Taken from original driver, this never sets dgain lower then 32? */ + + /* Change 0 - 95 to 32 - 127 */ + gain += 32; + + if (gain < 64) { + again = 0x0; /* sqrt(2) */ + dgain = gain; + } else { + again = 0x2; /* 2 * sqrt(2) */ + dgain = gain / 2; + } + + cci_write(sensor->regmap, GC0310_AGC_ADJ_REG, again, &ret); + cci_write(sensor->regmap, GC0310_DGC_ADJ_REG, dgain, &ret); + return ret; +} + +static int gc0310_exposure_update_range(struct gc0310_device *sensor) +{ + int exp_max = GC0310_NATIVE_HEIGHT + sensor->ctrls.vblank->val; + + return __v4l2_ctrl_modify_range(sensor->ctrls.exposure, 0, exp_max, + 1, exp_max); +} + +static int gc0310_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct gc0310_device *sensor = + container_of(ctrl->handler, struct gc0310_device, ctrls.handler); + int ret; + + /* Update exposure range on vblank changes */ + if (ctrl->id == V4L2_CID_VBLANK) { + ret = gc0310_exposure_update_range(sensor); + if (ret) + return ret; + } + + /* Only apply changes to the controls if the device is powered up */ + if (!pm_runtime_get_if_in_use(sensor->sd.dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ret = cci_write(sensor->regmap, GC0310_AEC_PK_EXPO_REG, + ctrl->val, NULL); + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = gc0310_gain_set(sensor, ctrl->val); + break; + case V4L2_CID_VBLANK: + ret = cci_write(sensor->regmap, GC0310_V_BLANKING_REG, + ctrl->val - GC0310_V_BLANK_OFFSET, + NULL); + break; + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(sensor->sd.dev); + return ret; +} + +static const struct v4l2_ctrl_ops ctrl_ops = { + .s_ctrl = gc0310_s_ctrl, +}; + +/* The GC0310 currently only supports 1 fixed fmt */ +static void gc0310_fill_format(struct v4l2_mbus_framefmt *fmt) +{ + memset(fmt, 0, sizeof(*fmt)); + fmt->width = GC0310_NATIVE_WIDTH; + fmt->height = GC0310_NATIVE_HEIGHT; + fmt->field = V4L2_FIELD_NONE; + fmt->code = MEDIA_BUS_FMT_SGRBG8_1X8; +} + +static int gc0310_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + /* Only the single fixed 656x496 mode is supported, without croping */ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = GC0310_NATIVE_WIDTH; + sel->r.height = GC0310_NATIVE_HEIGHT; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int gc0310_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct gc0310_device *sensor = to_gc0310_sensor(sd); + + gpiod_set_value_cansleep(sensor->powerdown, 1); + gpiod_set_value_cansleep(sensor->reset, 1); + return 0; +} + +static int gc0310_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct gc0310_device *sensor = to_gc0310_sensor(sd); + + fsleep(10 * USEC_PER_MSEC); + gpiod_set_value_cansleep(sensor->reset, 0); + fsleep(10 * USEC_PER_MSEC); + gpiod_set_value_cansleep(sensor->powerdown, 0); + fsleep(10 * USEC_PER_MSEC); + + return 0; +} + +static int gc0310_detect(struct gc0310_device *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); + u64 val; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + ret = cci_read(sensor->regmap, GC0310_SC_CMMN_CHIP_ID_REG, &val, NULL); + if (ret < 0) { + dev_err(&client->dev, "read sensor_id failed: %d\n", ret); + return -ENODEV; + } + + dev_dbg(&client->dev, "sensor ID = 0x%llx\n", val); + + if (val != GC0310_ID) { + dev_err(&client->dev, "sensor ID error, read id = 0x%llx, target id = 0x%x\n", + val, GC0310_ID); + return -ENODEV; + } + + dev_dbg(&client->dev, "detect gc0310 success\n"); + + return 0; +} + +static int gc0310_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct gc0310_device *sensor = to_gc0310_sensor(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + ret = pm_runtime_resume_and_get(&client->dev); + if (ret) + return ret; + + ret = regmap_multi_reg_write(sensor->regmap, + gc0310_reset_register, + ARRAY_SIZE(gc0310_reset_register)); + if (ret) + goto error_power_down; + + ret = regmap_multi_reg_write(sensor->regmap, + gc0310_VGA_30fps, + ARRAY_SIZE(gc0310_VGA_30fps)); + if (ret) + goto error_power_down; + + /* restore value of all ctrls */ + ret = __v4l2_ctrl_handler_setup(&sensor->ctrls.handler); + + /* enable per frame MIPI and sensor ctrl reset */ + cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, 0x30, &ret); + + cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, + GC0310_REGISTER_PAGE_3, &ret); + cci_write(sensor->regmap, GC0310_SW_STREAM_REG, + GC0310_START_STREAMING, &ret); + cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, + GC0310_REGISTER_PAGE_0, &ret); + +error_power_down: + if (ret) + pm_runtime_put(&client->dev); + + return ret; +} + +static int gc0310_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct gc0310_device *sensor = to_gc0310_sensor(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, + GC0310_REGISTER_PAGE_3, &ret); + cci_write(sensor->regmap, GC0310_SW_STREAM_REG, + GC0310_STOP_STREAMING, &ret); + cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, + GC0310_REGISTER_PAGE_0, &ret); + + pm_runtime_put(&client->dev); + return ret; +} + +static int gc0310_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* We support only a single format */ + if (code->index) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG8_1X8; + return 0; +} + +static int gc0310_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + /* We support only a single resolution */ + if (fse->index) + return -EINVAL; + + fse->min_width = GC0310_NATIVE_WIDTH; + fse->max_width = GC0310_NATIVE_WIDTH; + fse->min_height = GC0310_NATIVE_HEIGHT; + fse->max_height = GC0310_NATIVE_HEIGHT; + + return 0; +} + +static const struct v4l2_subdev_video_ops gc0310_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops gc0310_pad_ops = { + .enum_mbus_code = gc0310_enum_mbus_code, + .enum_frame_size = gc0310_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = v4l2_subdev_get_fmt, /* Only 1 fixed mode supported */ + .get_selection = gc0310_get_selection, + .set_selection = gc0310_get_selection, + .enable_streams = gc0310_enable_streams, + .disable_streams = gc0310_disable_streams, +}; + +static const struct v4l2_subdev_ops gc0310_ops = { + .video = &gc0310_video_ops, + .pad = &gc0310_pad_ops, +}; + +static int gc0310_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + gc0310_fill_format(v4l2_subdev_state_get_format(sd_state, 0)); + return 0; +} + +static const struct v4l2_subdev_internal_ops gc0310_internal_ops = { + .init_state = gc0310_init_state, +}; + +static int gc0310_init_controls(struct gc0310_device *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); + struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler; + struct v4l2_fwnode_device_properties props; + int exp_max, ret; + + v4l2_ctrl_handler_init(hdl, 8); + + /* Use the same lock for controls as for everything else */ + sensor->sd.ctrl_handler = hdl; + + exp_max = GC0310_NATIVE_HEIGHT + GC0310_V_BLANK_DEFAULT; + sensor->ctrls.exposure = + v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_EXPOSURE, 0, + exp_max, 1, exp_max); + + /* 32 steps at base gain 1 + 64 half steps at base gain 2 */ + sensor->ctrls.gain = + v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_ANALOGUE_GAIN, 0, 95, 1, 31); + + sensor->ctrls.link_freq = + v4l2_ctrl_new_int_menu(hdl, NULL, V4L2_CID_LINK_FREQ, + 0, 0, link_freq_menu_items); + sensor->ctrls.pixel_rate = + v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_PIXEL_RATE, 0, + GC0310_PIXELRATE, 1, GC0310_PIXELRATE); + + sensor->ctrls.vblank = + v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_VBLANK, + GC0310_V_BLANK_MIN, + GC0310_V_BLANK_MAX, 1, + GC0310_V_BLANK_DEFAULT); + + sensor->ctrls.hblank = + v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_HBLANK, + GC0310_H_BLANK_DEFAULT, + GC0310_H_BLANK_DEFAULT, 1, + GC0310_H_BLANK_DEFAULT); + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + return ret; + + v4l2_ctrl_new_fwnode_properties(hdl, &ctrl_ops, &props); + + if (hdl->error) + return hdl->error; + + sensor->ctrls.pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; + sensor->ctrls.link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + sensor->ctrls.hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + return 0; +} + +static void gc0310_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct gc0310_device *sensor = to_gc0310_sensor(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sensor->sd.entity); + v4l2_ctrl_handler_free(&sensor->ctrls.handler); + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + gc0310_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + } +} + +static int gc0310_check_hwcfg(struct device *dev) +{ + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct fwnode_handle *ep_fwnode; + unsigned long link_freq_bitmap; + int ret; + + /* + * Sometimes the fwnode graph is initialized by the bridge driver. + * Bridge drivers doing this may also add GPIO mappings, wait for this. + */ + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); + if (!ep_fwnode) + return dev_err_probe(dev, -EPROBE_DEFER, + "waiting for fwnode graph endpoint\n"); + + ret = v4l2_fwnode_endpoint_alloc_parse(ep_fwnode, &bus_cfg); + fwnode_handle_put(ep_fwnode); + if (ret) + return dev_err_probe(dev, ret, "parsing endpoint failed\n"); + + ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, + link_freq_menu_items, + ARRAY_SIZE(link_freq_menu_items), + &link_freq_bitmap); + + if (ret == 0 && bus_cfg.bus.mipi_csi2.num_data_lanes != 1) + ret = dev_err_probe(dev, -EINVAL, + "number of CSI2 data lanes %u is not supported\n", + bus_cfg.bus.mipi_csi2.num_data_lanes); + + v4l2_fwnode_endpoint_free(&bus_cfg); + return ret; +} + +static int gc0310_probe(struct i2c_client *client) +{ + struct gc0310_device *sensor; + unsigned long freq; + int ret; + + ret = gc0310_check_hwcfg(&client->dev); + if (ret) + return ret; + + sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->clk = devm_v4l2_sensor_clk_get(&client->dev, NULL); + if (IS_ERR(sensor->clk)) + return dev_err_probe(&client->dev, PTR_ERR(sensor->clk), + "failed to get clock\n"); + + freq = clk_get_rate(sensor->clk); + if (freq != GC0310_MCLK_FREQ) + return dev_err_probe(&client->dev, -EINVAL, + "external clock %lu is not supported\n", freq); + + sensor->reset = devm_gpiod_get(&client->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(sensor->reset)) { + return dev_err_probe(&client->dev, PTR_ERR(sensor->reset), + "getting reset GPIO\n"); + } + + sensor->powerdown = devm_gpiod_get(&client->dev, "powerdown", GPIOD_OUT_HIGH); + if (IS_ERR(sensor->powerdown)) { + return dev_err_probe(&client->dev, PTR_ERR(sensor->powerdown), + "getting powerdown GPIO\n"); + } + + v4l2_i2c_subdev_init(&sensor->sd, client, &gc0310_ops); + + sensor->regmap = devm_cci_regmap_init_i2c(client, 8); + if (IS_ERR(sensor->regmap)) + return PTR_ERR(sensor->regmap); + + gc0310_power_on(&client->dev); + + pm_runtime_set_active(&client->dev); + pm_runtime_get_noresume(&client->dev); + pm_runtime_enable(&client->dev); + + ret = gc0310_detect(sensor); + if (ret) + goto err_power_down; + + sensor->sd.internal_ops = &gc0310_internal_ops; + sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + ret = gc0310_init_controls(sensor); + if (ret) + goto err_power_down; + + ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); + if (ret) + goto err_power_down; + + sensor->sd.state_lock = sensor->ctrls.handler.lock; + ret = v4l2_subdev_init_finalize(&sensor->sd); + if (ret) + goto err_power_down; + + ret = v4l2_async_register_subdev_sensor(&sensor->sd); + if (ret) + goto err_power_down; + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return 0; + +err_power_down: + pm_runtime_put_noidle(&client->dev); + gc0310_remove(client); + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(gc0310_pm_ops, + gc0310_power_off, gc0310_power_on, NULL); + +static const struct acpi_device_id gc0310_acpi_match[] = { + {"INT0310"}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, gc0310_acpi_match); + +static struct i2c_driver gc0310_driver = { + .driver = { + .name = "gc0310", + .pm = pm_sleep_ptr(&gc0310_pm_ops), + .acpi_match_table = gc0310_acpi_match, + }, + .probe = gc0310_probe, + .remove = gc0310_remove, +}; +module_i2c_driver(gc0310_driver); + +MODULE_AUTHOR("Lai, Angie "); +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("A low-level driver for GalaxyCore GC0310 sensors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/gc05a2.c b/drivers/media/i2c/gc05a2.c new file mode 100644 index 000000000..7cf7cde1f --- /dev/null +++ b/drivers/media/i2c/gc05a2.c @@ -0,0 +1,1346 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for GalaxyCore gc05a2 image sensor + * + * Copyright 2024 MediaTek + * + * Zhi Mao + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define GC05A2_REG_TEST_PATTERN_EN CCI_REG8(0x008c) +#define GC05A2_REG_TEST_PATTERN_IDX CCI_REG8(0x008d) +#define GC05A2_TEST_PATTERN_EN 0x01 + +#define GC05A2_STREAMING_REG CCI_REG8(0x0100) + +#define GC05A2_FLIP_REG CCI_REG8(0x0101) +#define GC05A2_FLIP_H_MASK BIT(0) +#define GC05A2_FLIP_V_MASK BIT(1) + +#define GC05A2_EXP_REG CCI_REG16(0x0202) +#define GC05A2_EXP_MARGIN 16 +#define GC05A2_EXP_MIN 4 +#define GC05A2_EXP_STEP 1 + +#define GC05A2_AGAIN_REG CCI_REG16(0x0204) +#define GC05A2_AGAIN_MIN 1024 +#define GC05A2_AGAIN_MAX (1024 * 16) +#define GC05A2_AGAIN_STEP 1 + +#define GC05A2_FRAME_LENGTH_REG CCI_REG16(0x0340) +#define GC05A2_VTS_MAX 0xffff + +#define GC05A2_REG_CHIP_ID CCI_REG16(0x03f0) +#define GC05A2_CHIP_ID 0x05a2 + +#define GC05A2_NATIVE_WIDTH 2592 +#define GC05A2_NATIVE_HEIGHT 1944 + +#define GC05A2_DEFAULT_CLK_FREQ (24 * HZ_PER_MHZ) +#define GC05A2_MBUS_CODE MEDIA_BUS_FMT_SGRBG10_1X10 +#define GC05A2_DATA_LANES 2 +#define GC05A2_RGB_DEPTH 10 +#define GC05A2_SLEEP_US (2 * USEC_PER_MSEC) + +static const char *const gc05a2_test_pattern_menu[] = { + "No Pattern", "Fade_to_gray_Color Bar", "Color Bar", + "PN9", "Horizontal_gradient", "Checkboard Pattern", + "Slant", "Resolution", "Solid Black", + "Solid White", +}; + +static const s64 gc05a2_link_freq_menu_items[] = { + (448 * HZ_PER_MHZ), + (224 * HZ_PER_MHZ), +}; + +static const char *const gc05a2_supply_name[] = { + "avdd", + "dvdd", + "dovdd", +}; + +struct gc05a2 { + struct device *dev; + struct v4l2_subdev sd; + struct media_pad pad; + + struct clk *xclk; + struct regulator_bulk_data supplies[ARRAY_SIZE(gc05a2_supply_name)]; + struct gpio_desc *reset_gpio; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + + struct regmap *regmap; + unsigned long link_freq_bitmap; + + /* True if the device has been identified */ + bool identified; + const struct gc05a2_mode *cur_mode; +}; + +struct gc05a2_reg_list { + u32 num_of_regs; + const struct cci_reg_sequence *regs; +}; + +static const struct cci_reg_sequence mode_2592x1944[] = { + /* system */ + { CCI_REG8(0x0135), 0x01 }, + { CCI_REG8(0x0084), 0x21 }, + { CCI_REG8(0x0d05), 0xcc }, + { CCI_REG8(0x0218), 0x00 }, + { CCI_REG8(0x005e), 0x48 }, + { CCI_REG8(0x0d06), 0x01 }, + { CCI_REG8(0x0007), 0x16 }, + { CCI_REG8(0x0101), 0x00 }, + + /* analog */ + { CCI_REG8(0x0342), 0x07 }, + { CCI_REG8(0x0343), 0x28 }, + { CCI_REG8(0x0220), 0x07 }, + { CCI_REG8(0x0221), 0xd0 }, + { CCI_REG8(0x0202), 0x07 }, + { CCI_REG8(0x0203), 0x32 }, + { CCI_REG8(0x0340), 0x07 }, + { CCI_REG8(0x0341), 0xf0 }, + { CCI_REG8(0x0219), 0x00 }, + { CCI_REG8(0x0346), 0x00 }, + { CCI_REG8(0x0347), 0x04 }, + { CCI_REG8(0x0d14), 0x00 }, + { CCI_REG8(0x0d13), 0x05 }, + { CCI_REG8(0x0d16), 0x05 }, + { CCI_REG8(0x0d15), 0x1d }, + { CCI_REG8(0x00c0), 0x0a }, + { CCI_REG8(0x00c1), 0x30 }, + { CCI_REG8(0x034a), 0x07 }, + { CCI_REG8(0x034b), 0xa8 }, + { CCI_REG8(0x0e0a), 0x00 }, + { CCI_REG8(0x0e0b), 0x00 }, + { CCI_REG8(0x0e0e), 0x03 }, + { CCI_REG8(0x0e0f), 0x00 }, + { CCI_REG8(0x0e06), 0x0a }, + { CCI_REG8(0x0e23), 0x15 }, + { CCI_REG8(0x0e24), 0x15 }, + { CCI_REG8(0x0e2a), 0x10 }, + { CCI_REG8(0x0e2b), 0x10 }, + { CCI_REG8(0x0e17), 0x49 }, + { CCI_REG8(0x0e1b), 0x1c }, + { CCI_REG8(0x0e3a), 0x36 }, + { CCI_REG8(0x0d11), 0x84 }, + { CCI_REG8(0x0e52), 0x14 }, + { CCI_REG8(0x000b), 0x10 }, + { CCI_REG8(0x0008), 0x08 }, + { CCI_REG8(0x0223), 0x17 }, + { CCI_REG8(0x0d27), 0x39 }, + { CCI_REG8(0x0d22), 0x00 }, + { CCI_REG8(0x03f6), 0x0d }, + { CCI_REG8(0x0d04), 0x07 }, + { CCI_REG8(0x03f3), 0x72 }, + { CCI_REG8(0x03f4), 0xb8 }, + { CCI_REG8(0x03f5), 0xbc }, + { CCI_REG8(0x0d02), 0x73 }, + + /* auto load start */ + { CCI_REG8(0x00cb), 0x00 }, + + /* OUT 2592*1944 */ + { CCI_REG8(0x0350), 0x01 }, + { CCI_REG8(0x0353), 0x00 }, + { CCI_REG8(0x0354), 0x08 }, + { CCI_REG16(0x034c), 2592 }, /* Width */ + { CCI_REG8(0x021f), 0x14 }, + + /* MIPI */ + { CCI_REG8(0x0107), 0x05 }, + { CCI_REG8(0x0117), 0x01 }, + { CCI_REG8(0x0d81), 0x00 }, + { CCI_REG8(0x0d84), 0x0c }, + { CCI_REG8(0x0d85), 0xa8 }, + { CCI_REG8(0x0d86), 0x06 }, + { CCI_REG8(0x0d87), 0x55 }, + { CCI_REG8(0x0db3), 0x06 }, + { CCI_REG8(0x0db4), 0x08 }, + { CCI_REG8(0x0db5), 0x1e }, + { CCI_REG8(0x0db6), 0x02 }, + { CCI_REG8(0x0db8), 0x12 }, + { CCI_REG8(0x0db9), 0x0a }, + { CCI_REG8(0x0d93), 0x06 }, + { CCI_REG8(0x0d94), 0x09 }, + { CCI_REG8(0x0d95), 0x0d }, + { CCI_REG8(0x0d99), 0x0b }, + { CCI_REG8(0x0084), 0x01 }, + { CCI_REG8(0x0110), 0x01 }, +}; + +static const struct cci_reg_sequence mode_1280x720[] = { + /* system */ + { CCI_REG8(0x0135), 0x05 }, + { CCI_REG8(0x0084), 0x21 }, + { CCI_REG8(0x0d05), 0xcc }, + { CCI_REG8(0x0218), 0x80 }, + { CCI_REG8(0x005e), 0x49 }, + { CCI_REG8(0x0d06), 0x81 }, + { CCI_REG8(0x0007), 0x16 }, + { CCI_REG8(0x0101), 0x00 }, + + /* analog */ + { CCI_REG8(0x0342), 0x07 }, + { CCI_REG8(0x0343), 0x10 }, + { CCI_REG8(0x0220), 0x07 }, + { CCI_REG8(0x0221), 0xd0 }, + { CCI_REG8(0x0202), 0x03 }, + { CCI_REG8(0x0203), 0x32 }, + { CCI_REG8(0x0340), 0x04 }, + { CCI_REG8(0x0341), 0x08 }, + { CCI_REG8(0x0219), 0x00 }, + { CCI_REG8(0x0346), 0x01 }, + { CCI_REG8(0x0347), 0x00 }, + { CCI_REG8(0x0d14), 0x00 }, + { CCI_REG8(0x0d13), 0x05 }, + { CCI_REG8(0x0d16), 0x05 }, + { CCI_REG8(0x0d15), 0x1d }, + { CCI_REG8(0x00c0), 0x0a }, + { CCI_REG8(0x00c1), 0x30 }, + { CCI_REG8(0x034a), 0x05 }, + { CCI_REG8(0x034b), 0xb0 }, + { CCI_REG8(0x0e0a), 0x00 }, + { CCI_REG8(0x0e0b), 0x00 }, + { CCI_REG8(0x0e0e), 0x03 }, + { CCI_REG8(0x0e0f), 0x00 }, + { CCI_REG8(0x0e06), 0x0a }, + { CCI_REG8(0x0e23), 0x15 }, + { CCI_REG8(0x0e24), 0x15 }, + { CCI_REG8(0x0e2a), 0x10 }, + { CCI_REG8(0x0e2b), 0x10 }, + { CCI_REG8(0x0e17), 0x49 }, + { CCI_REG8(0x0e1b), 0x1c }, + { CCI_REG8(0x0e3a), 0x36 }, + { CCI_REG8(0x0d11), 0x84 }, + { CCI_REG8(0x0e52), 0x14 }, + { CCI_REG8(0x000b), 0x0e }, + { CCI_REG8(0x0008), 0x03 }, + { CCI_REG8(0x0223), 0x16 }, + { CCI_REG8(0x0d27), 0x39 }, + { CCI_REG8(0x0d22), 0x00 }, + { CCI_REG8(0x03f6), 0x0d }, + { CCI_REG8(0x0d04), 0x07 }, + { CCI_REG8(0x03f3), 0x72 }, + { CCI_REG8(0x03f4), 0xb8 }, + { CCI_REG8(0x03f5), 0xbc }, + { CCI_REG8(0x0d02), 0x73 }, + + /* auto load start */ + { CCI_REG8(0x00cb), 0xfc }, + + /* OUT 1280x720 */ + { CCI_REG8(0x0350), 0x01 }, + { CCI_REG8(0x0353), 0x00 }, + { CCI_REG8(0x0354), 0x0c }, + { CCI_REG16(0x034c), 1280 }, /* Width */ + { CCI_REG8(0x021f), 0x14 }, + + /* MIPI */ + { CCI_REG8(0x0107), 0x05 }, + { CCI_REG8(0x0117), 0x01 }, + { CCI_REG8(0x0d81), 0x00 }, + { CCI_REG8(0x0d84), 0x06 }, + { CCI_REG8(0x0d85), 0x40 }, + { CCI_REG8(0x0d86), 0x03 }, + { CCI_REG8(0x0d87), 0x21 }, + { CCI_REG8(0x0db3), 0x03 }, + { CCI_REG8(0x0db4), 0x04 }, + { CCI_REG8(0x0db5), 0x0d }, + { CCI_REG8(0x0db6), 0x01 }, + { CCI_REG8(0x0db8), 0x04 }, + { CCI_REG8(0x0db9), 0x06 }, + { CCI_REG8(0x0d93), 0x03 }, + { CCI_REG8(0x0d94), 0x04 }, + { CCI_REG8(0x0d95), 0x05 }, + { CCI_REG8(0x0d99), 0x06 }, + { CCI_REG8(0x0084), 0x01 }, + { CCI_REG8(0x0110), 0x01 }, +}; + +static const struct cci_reg_sequence mode_table_common[] = { + { GC05A2_STREAMING_REG, 0x00 }, + /* system */ + { CCI_REG8(0x0315), 0xd4 }, + { CCI_REG8(0x0d06), 0x01 }, + { CCI_REG8(0x0a70), 0x80 }, + { CCI_REG8(0x031a), 0x00 }, + { CCI_REG8(0x0314), 0x00 }, + { CCI_REG8(0x0130), 0x08 }, + { CCI_REG8(0x0132), 0x01 }, + { CCI_REG8(0x0136), 0x38 }, + { CCI_REG8(0x0137), 0x03 }, + { CCI_REG8(0x0134), 0x5b }, + { CCI_REG8(0x031c), 0xe0 }, + { CCI_REG8(0x0d82), 0x14 }, + { CCI_REG8(0x0dd1), 0x56 }, + { CCI_REG8(0x0af4), 0x01 }, + { CCI_REG8(0x0002), 0x10 }, + { CCI_REG8(0x00c3), 0x34 }, + { CCI_REG8(0x00c4), 0x00 }, + { CCI_REG8(0x00c5), 0x01 }, + { CCI_REG8(0x0af6), 0x00 }, + { CCI_REG8(0x0ba0), 0x17 }, + { CCI_REG8(0x0ba1), 0x00 }, + { CCI_REG8(0x0ba2), 0x00 }, + { CCI_REG8(0x0ba3), 0x00 }, + { CCI_REG8(0x0ba4), 0x03 }, + { CCI_REG8(0x0ba5), 0x00 }, + { CCI_REG8(0x0ba6), 0x00 }, + { CCI_REG8(0x0ba7), 0x00 }, + { CCI_REG8(0x0ba8), 0x40 }, + { CCI_REG8(0x0ba9), 0x00 }, + { CCI_REG8(0x0baa), 0x00 }, + { CCI_REG8(0x0bab), 0x00 }, + { CCI_REG8(0x0bac), 0x40 }, + { CCI_REG8(0x0bad), 0x00 }, + { CCI_REG8(0x0bae), 0x00 }, + { CCI_REG8(0x0baf), 0x00 }, + { CCI_REG8(0x0bb0), 0x02 }, + { CCI_REG8(0x0bb1), 0x00 }, + { CCI_REG8(0x0bb2), 0x00 }, + { CCI_REG8(0x0bb3), 0x00 }, + { CCI_REG8(0x0bb8), 0x02 }, + { CCI_REG8(0x0bb9), 0x00 }, + { CCI_REG8(0x0bba), 0x00 }, + { CCI_REG8(0x0bbb), 0x00 }, + { CCI_REG8(0x0a70), 0x80 }, + { CCI_REG8(0x0a71), 0x00 }, + { CCI_REG8(0x0a72), 0x00 }, + { CCI_REG8(0x0a66), 0x00 }, + { CCI_REG8(0x0a67), 0x80 }, + { CCI_REG8(0x0a4d), 0x4e }, + { CCI_REG8(0x0a50), 0x00 }, + { CCI_REG8(0x0a4f), 0x0c }, + { CCI_REG8(0x0a66), 0x00 }, + { CCI_REG8(0x00ca), 0x00 }, + { CCI_REG8(0x00cc), 0x00 }, + { CCI_REG8(0x00cd), 0x00 }, + { CCI_REG8(0x0aa1), 0x00 }, + { CCI_REG8(0x0aa2), 0xe0 }, + { CCI_REG8(0x0aa3), 0x00 }, + { CCI_REG8(0x0aa4), 0x40 }, + { CCI_REG8(0x0a90), 0x03 }, + { CCI_REG8(0x0a91), 0x0e }, + { CCI_REG8(0x0a94), 0x80 }, + { CCI_REG8(0x0af6), 0x20 }, + { CCI_REG8(0x0b00), 0x91 }, + { CCI_REG8(0x0b01), 0x17 }, + { CCI_REG8(0x0b02), 0x01 }, + { CCI_REG8(0x0b03), 0x00 }, + { CCI_REG8(0x0b04), 0x01 }, + { CCI_REG8(0x0b05), 0x17 }, + { CCI_REG8(0x0b06), 0x01 }, + { CCI_REG8(0x0b07), 0x00 }, + { CCI_REG8(0x0ae9), 0x01 }, + { CCI_REG8(0x0aea), 0x02 }, + { CCI_REG8(0x0ae8), 0x53 }, + { CCI_REG8(0x0ae8), 0x43 }, + { CCI_REG8(0x0af6), 0x30 }, + { CCI_REG8(0x0b00), 0x08 }, + { CCI_REG8(0x0b01), 0x0f }, + { CCI_REG8(0x0b02), 0x00 }, + { CCI_REG8(0x0b04), 0x1c }, + { CCI_REG8(0x0b05), 0x24 }, + { CCI_REG8(0x0b06), 0x00 }, + { CCI_REG8(0x0b08), 0x30 }, + { CCI_REG8(0x0b09), 0x40 }, + { CCI_REG8(0x0b0a), 0x00 }, + { CCI_REG8(0x0b0c), 0x0e }, + { CCI_REG8(0x0b0d), 0x2a }, + { CCI_REG8(0x0b0e), 0x00 }, + { CCI_REG8(0x0b10), 0x0e }, + { CCI_REG8(0x0b11), 0x2b }, + { CCI_REG8(0x0b12), 0x00 }, + { CCI_REG8(0x0b14), 0x0e }, + { CCI_REG8(0x0b15), 0x23 }, + { CCI_REG8(0x0b16), 0x00 }, + { CCI_REG8(0x0b18), 0x0e }, + { CCI_REG8(0x0b19), 0x24 }, + { CCI_REG8(0x0b1a), 0x00 }, + { CCI_REG8(0x0b1c), 0x0c }, + { CCI_REG8(0x0b1d), 0x0c }, + { CCI_REG8(0x0b1e), 0x00 }, + { CCI_REG8(0x0b20), 0x03 }, + { CCI_REG8(0x0b21), 0x03 }, + { CCI_REG8(0x0b22), 0x00 }, + { CCI_REG8(0x0b24), 0x0e }, + { CCI_REG8(0x0b25), 0x0e }, + { CCI_REG8(0x0b26), 0x00 }, + { CCI_REG8(0x0b28), 0x03 }, + { CCI_REG8(0x0b29), 0x03 }, + { CCI_REG8(0x0b2a), 0x00 }, + { CCI_REG8(0x0b2c), 0x12 }, + { CCI_REG8(0x0b2d), 0x12 }, + { CCI_REG8(0x0b2e), 0x00 }, + { CCI_REG8(0x0b30), 0x08 }, + { CCI_REG8(0x0b31), 0x08 }, + { CCI_REG8(0x0b32), 0x00 }, + { CCI_REG8(0x0b34), 0x14 }, + { CCI_REG8(0x0b35), 0x14 }, + { CCI_REG8(0x0b36), 0x00 }, + { CCI_REG8(0x0b38), 0x10 }, + { CCI_REG8(0x0b39), 0x10 }, + { CCI_REG8(0x0b3a), 0x00 }, + { CCI_REG8(0x0b3c), 0x16 }, + { CCI_REG8(0x0b3d), 0x16 }, + { CCI_REG8(0x0b3e), 0x00 }, + { CCI_REG8(0x0b40), 0x10 }, + { CCI_REG8(0x0b41), 0x10 }, + { CCI_REG8(0x0b42), 0x00 }, + { CCI_REG8(0x0b44), 0x19 }, + { CCI_REG8(0x0b45), 0x19 }, + { CCI_REG8(0x0b46), 0x00 }, + { CCI_REG8(0x0b48), 0x16 }, + { CCI_REG8(0x0b49), 0x16 }, + { CCI_REG8(0x0b4a), 0x00 }, + { CCI_REG8(0x0b4c), 0x19 }, + { CCI_REG8(0x0b4d), 0x19 }, + { CCI_REG8(0x0b4e), 0x00 }, + { CCI_REG8(0x0b50), 0x16 }, + { CCI_REG8(0x0b51), 0x16 }, + { CCI_REG8(0x0b52), 0x00 }, + { CCI_REG8(0x0b80), 0x01 }, + { CCI_REG8(0x0b81), 0x00 }, + { CCI_REG8(0x0b82), 0x00 }, + { CCI_REG8(0x0b84), 0x00 }, + { CCI_REG8(0x0b85), 0x00 }, + { CCI_REG8(0x0b86), 0x00 }, + { CCI_REG8(0x0b88), 0x01 }, + { CCI_REG8(0x0b89), 0x6a }, + { CCI_REG8(0x0b8a), 0x00 }, + { CCI_REG8(0x0b8c), 0x00 }, + { CCI_REG8(0x0b8d), 0x01 }, + { CCI_REG8(0x0b8e), 0x00 }, + { CCI_REG8(0x0b90), 0x01 }, + { CCI_REG8(0x0b91), 0xf6 }, + { CCI_REG8(0x0b92), 0x00 }, + { CCI_REG8(0x0b94), 0x00 }, + { CCI_REG8(0x0b95), 0x02 }, + { CCI_REG8(0x0b96), 0x00 }, + { CCI_REG8(0x0b98), 0x02 }, + { CCI_REG8(0x0b99), 0xc4 }, + { CCI_REG8(0x0b9a), 0x00 }, + { CCI_REG8(0x0b9c), 0x00 }, + { CCI_REG8(0x0b9d), 0x03 }, + { CCI_REG8(0x0b9e), 0x00 }, + { CCI_REG8(0x0ba0), 0x03 }, + { CCI_REG8(0x0ba1), 0xd8 }, + { CCI_REG8(0x0ba2), 0x00 }, + { CCI_REG8(0x0ba4), 0x00 }, + { CCI_REG8(0x0ba5), 0x04 }, + { CCI_REG8(0x0ba6), 0x00 }, + { CCI_REG8(0x0ba8), 0x05 }, + { CCI_REG8(0x0ba9), 0x4d }, + { CCI_REG8(0x0baa), 0x00 }, + { CCI_REG8(0x0bac), 0x00 }, + { CCI_REG8(0x0bad), 0x05 }, + { CCI_REG8(0x0bae), 0x00 }, + { CCI_REG8(0x0bb0), 0x07 }, + { CCI_REG8(0x0bb1), 0x3e }, + { CCI_REG8(0x0bb2), 0x00 }, + { CCI_REG8(0x0bb4), 0x00 }, + { CCI_REG8(0x0bb5), 0x06 }, + { CCI_REG8(0x0bb6), 0x00 }, + { CCI_REG8(0x0bb8), 0x0a }, + { CCI_REG8(0x0bb9), 0x1a }, + { CCI_REG8(0x0bba), 0x00 }, + { CCI_REG8(0x0bbc), 0x09 }, + { CCI_REG8(0x0bbd), 0x36 }, + { CCI_REG8(0x0bbe), 0x00 }, + { CCI_REG8(0x0bc0), 0x0e }, + { CCI_REG8(0x0bc1), 0x66 }, + { CCI_REG8(0x0bc2), 0x00 }, + { CCI_REG8(0x0bc4), 0x10 }, + { CCI_REG8(0x0bc5), 0x06 }, + { CCI_REG8(0x0bc6), 0x00 }, + { CCI_REG8(0x02c1), 0xe0 }, + { CCI_REG8(0x0207), 0x04 }, + { CCI_REG8(0x02c2), 0x10 }, + { CCI_REG8(0x02c3), 0x74 }, + { CCI_REG8(0x02c5), 0x09 }, + { CCI_REG8(0x02c1), 0xe0 }, + { CCI_REG8(0x0207), 0x04 }, + { CCI_REG8(0x02c2), 0x10 }, + { CCI_REG8(0x02c5), 0x09 }, + { CCI_REG8(0x02c1), 0xe0 }, + { CCI_REG8(0x0207), 0x04 }, + { CCI_REG8(0x02c2), 0x10 }, + { CCI_REG8(0x02c5), 0x09 }, + { CCI_REG8(0x0aa1), 0x15 }, + { CCI_REG8(0x0aa2), 0x50 }, + { CCI_REG8(0x0aa3), 0x00 }, + { CCI_REG8(0x0aa4), 0x09 }, + { CCI_REG8(0x0a90), 0x25 }, + { CCI_REG8(0x0a91), 0x0e }, + { CCI_REG8(0x0a94), 0x80 }, + + /* ISP */ + { CCI_REG8(0x0050), 0x00 }, + { CCI_REG8(0x0089), 0x83 }, + { CCI_REG8(0x005a), 0x40 }, + { CCI_REG8(0x00c3), 0x35 }, + { CCI_REG8(0x00c4), 0x80 }, + { CCI_REG8(0x0080), 0x10 }, + { CCI_REG8(0x0040), 0x12 }, + { CCI_REG8(0x0053), 0x0a }, + { CCI_REG8(0x0054), 0x44 }, + { CCI_REG8(0x0055), 0x32 }, + { CCI_REG8(0x0058), 0x89 }, + { CCI_REG8(0x004a), 0x03 }, + { CCI_REG8(0x0048), 0xf0 }, + { CCI_REG8(0x0049), 0x0f }, + { CCI_REG8(0x0041), 0x20 }, + { CCI_REG8(0x0043), 0x0a }, + { CCI_REG8(0x009d), 0x08 }, + { CCI_REG8(0x0236), 0x40 }, + { CCI_REG8(0x0204), 0x04 }, + { CCI_REG8(0x0205), 0x00 }, + { CCI_REG8(0x02b3), 0x00 }, + { CCI_REG8(0x02b4), 0x00 }, + { CCI_REG8(0x009e), 0x01 }, + { CCI_REG8(0x009f), 0x94 }, + + /* auto load REG */ + { CCI_REG8(0x0aa1), 0x10 }, + { CCI_REG8(0x0aa2), 0xf8 }, + { CCI_REG8(0x0aa3), 0x00 }, + { CCI_REG8(0x0aa4), 0x1f }, + { CCI_REG8(0x0a90), 0x11 }, + { CCI_REG8(0x0a91), 0x0e }, + { CCI_REG8(0x0a94), 0x80 }, + { CCI_REG8(0x03fe), 0x00 }, + { CCI_REG8(0x0a90), 0x00 }, + { CCI_REG8(0x0a70), 0x00 }, + { CCI_REG8(0x0a67), 0x00 }, + { CCI_REG8(0x0af4), 0x29 }, + + /* DPHY */ + { CCI_REG8(0x0d80), 0x07 }, + { CCI_REG8(0x0dd3), 0x18 }, + + /* CISCTL_Reset */ + { CCI_REG8(0x031c), 0x80 }, + { CCI_REG8(0x03fe), 0x30 }, + { CCI_REG8(0x0d17), 0x06 }, + { CCI_REG8(0x03fe), 0x00 }, + { CCI_REG8(0x0d17), 0x00 }, + { CCI_REG8(0x031c), 0x93 }, + { CCI_REG8(0x03fe), 0x00 }, + { CCI_REG8(0x031c), 0x80 }, + { CCI_REG8(0x03fe), 0x30 }, + { CCI_REG8(0x0d17), 0x06 }, + { CCI_REG8(0x03fe), 0x00 }, + { CCI_REG8(0x0d17), 0x00 }, + { CCI_REG8(0x031c), 0x93 }, +}; + +struct gc05a2_mode { + u32 width; + u32 height; + const struct gc05a2_reg_list reg_list; + + u32 hts; /* Horizontal timining size */ + u32 vts_def; /* Default vertical timining size */ + u32 vts_min; /* Min vertical timining size */ +}; + +/* Declare modes in order, from biggest to smallest height. */ +static const struct gc05a2_mode gc05a2_modes[] = { + { + /* 2592*1944@30fps */ + .width = GC05A2_NATIVE_WIDTH, + .height = GC05A2_NATIVE_HEIGHT, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2592x1944), + .regs = mode_2592x1944, + }, + .hts = 3664, + .vts_def = 2032, + .vts_min = 2032, + }, + { + /* 1280*720@60fps */ + .width = 1280, + .height = 720, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x720), + .regs = mode_1280x720, + }, + .hts = 3616, + .vts_def = 1032, + .vts_min = 1032, + }, +}; + +static inline struct gc05a2 *to_gc05a2(struct v4l2_subdev *sd) +{ + return container_of(sd, struct gc05a2, sd); +} + +static int gc05a2_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct gc05a2 *gc05a2 = to_gc05a2(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(gc05a2_supply_name), + gc05a2->supplies); + if (ret < 0) { + dev_err(gc05a2->dev, "failed to enable regulators: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(gc05a2->xclk); + if (ret < 0) { + regulator_bulk_disable(ARRAY_SIZE(gc05a2_supply_name), + gc05a2->supplies); + dev_err(gc05a2->dev, "clk prepare enable failed\n"); + return ret; + } + + fsleep(GC05A2_SLEEP_US); + + gpiod_set_value_cansleep(gc05a2->reset_gpio, 0); + fsleep(GC05A2_SLEEP_US); + + return 0; +} + +static int gc05a2_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct gc05a2 *gc05a2 = to_gc05a2(sd); + + clk_disable_unprepare(gc05a2->xclk); + gpiod_set_value_cansleep(gc05a2->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(gc05a2_supply_name), + gc05a2->supplies); + + return 0; +} + +static int gc05a2_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = GC05A2_MBUS_CODE; + + return 0; +} + +static int gc05a2_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->code != GC05A2_MBUS_CODE) + return -EINVAL; + + if (fse->index >= ARRAY_SIZE(gc05a2_modes)) + return -EINVAL; + + fse->min_width = gc05a2_modes[fse->index].width; + fse->max_width = gc05a2_modes[fse->index].width; + fse->min_height = gc05a2_modes[fse->index].height; + fse->max_height = gc05a2_modes[fse->index].height; + + return 0; +} + +static int gc05a2_update_cur_mode_controls(struct gc05a2 *gc05a2, + const struct gc05a2_mode *mode) +{ + s64 exposure_max, h_blank; + int ret; + + ret = __v4l2_ctrl_modify_range(gc05a2->vblank, + mode->vts_min - mode->height, + GC05A2_VTS_MAX - mode->height, 1, + mode->vts_def - mode->height); + if (ret) { + dev_err(gc05a2->dev, "VB ctrl range update failed\n"); + return ret; + } + + h_blank = mode->hts - mode->width; + ret = __v4l2_ctrl_modify_range(gc05a2->hblank, h_blank, h_blank, 1, + h_blank); + if (ret) { + dev_err(gc05a2->dev, "HB ctrl range update failed\n"); + return ret; + } + + exposure_max = mode->vts_def - GC05A2_EXP_MARGIN; + ret = __v4l2_ctrl_modify_range(gc05a2->exposure, GC05A2_EXP_MIN, + exposure_max, GC05A2_EXP_STEP, + exposure_max); + if (ret) { + dev_err(gc05a2->dev, "exposure ctrl range update failed\n"); + return ret; + } + + return 0; +} + +static void gc05a2_update_pad_format(struct gc05a2 *gc08a3, + const struct gc05a2_mode *mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = mode->width; + fmt->height = mode->height; + fmt->code = GC05A2_MBUS_CODE; + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_XFER_FUNC_NONE; +} + +static int gc05a2_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct gc05a2 *gc05a2 = to_gc05a2(sd); + struct v4l2_mbus_framefmt *mbus_fmt; + struct v4l2_rect *crop; + const struct gc05a2_mode *mode; + + mode = v4l2_find_nearest_size(gc05a2_modes, ARRAY_SIZE(gc05a2_modes), + width, height, fmt->format.width, + fmt->format.height); + + /* update crop info to subdev state */ + crop = v4l2_subdev_state_get_crop(state, 0); + crop->width = mode->width; + crop->height = mode->height; + + /* update fmt info to subdev state */ + gc05a2_update_pad_format(gc05a2, mode, &fmt->format); + mbus_fmt = v4l2_subdev_state_get_format(state, 0); + *mbus_fmt = fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + gc05a2->cur_mode = mode; + gc05a2_update_cur_mode_controls(gc05a2, mode); + + return 0; +} + +static int gc05a2_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_state_get_crop(state, 0); + break; + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = GC05A2_NATIVE_WIDTH; + sel->r.height = GC05A2_NATIVE_HEIGHT; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int gc05a2_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .pad = 0, + .format = { + .code = GC05A2_MBUS_CODE, + .width = gc05a2_modes[0].width, + .height = gc05a2_modes[0].height, + }, + }; + + gc05a2_set_format(sd, state, &fmt); + + return 0; +} + +static int gc05a2_set_ctrl_hflip(struct gc05a2 *gc05a2, u32 ctrl_val) +{ + int ret; + u64 val; + + ret = cci_read(gc05a2->regmap, GC05A2_FLIP_REG, &val, NULL); + if (ret) { + dev_err(gc05a2->dev, "read hflip register failed: %d\n", ret); + return ret; + } + + return cci_update_bits(gc05a2->regmap, GC05A2_FLIP_REG, + GC05A2_FLIP_H_MASK, + ctrl_val ? GC05A2_FLIP_H_MASK : 0, NULL); +} + +static int gc05a2_set_ctrl_vflip(struct gc05a2 *gc05a2, u32 ctrl_val) +{ + int ret; + u64 val; + + ret = cci_read(gc05a2->regmap, GC05A2_FLIP_REG, &val, NULL); + if (ret) { + dev_err(gc05a2->dev, "read vflip register failed: %d\n", ret); + return ret; + } + + return cci_update_bits(gc05a2->regmap, GC05A2_FLIP_REG, + GC05A2_FLIP_V_MASK, + ctrl_val ? GC05A2_FLIP_V_MASK : 0, NULL); +} + +static int gc05a2_test_pattern(struct gc05a2 *gc05a2, u32 pattern_menu) +{ + u32 pattern; + int ret; + + if (pattern_menu) { + switch (pattern_menu) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + pattern = pattern_menu << 4; + break; + + case 8: + pattern = 0; + break; + + case 9: + pattern = 4; + break; + + default: + /* Set pattern to 0, it's a safe default. */ + pattern = 0; + break; + } + + ret = cci_write(gc05a2->regmap, GC05A2_REG_TEST_PATTERN_IDX, + pattern, NULL); + if (ret) + return ret; + + return cci_write(gc05a2->regmap, GC05A2_REG_TEST_PATTERN_EN, + GC05A2_TEST_PATTERN_EN, NULL); + } else { + return cci_write(gc05a2->regmap, GC05A2_REG_TEST_PATTERN_EN, + 0x00, NULL); + } +} + +static int gc05a2_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct gc05a2 *gc05a2 = + container_of(ctrl->handler, struct gc05a2, ctrls); + int ret = 0; + s64 exposure_max; + struct v4l2_subdev_state *state; + const struct v4l2_mbus_framefmt *format; + + state = v4l2_subdev_get_locked_active_state(&gc05a2->sd); + format = v4l2_subdev_state_get_format(state, 0); + + if (ctrl->id == V4L2_CID_VBLANK) { + /* Update max exposure while meeting expected vblanking */ + exposure_max = format->height + ctrl->val - GC05A2_EXP_MARGIN; + __v4l2_ctrl_modify_range(gc05a2->exposure, + gc05a2->exposure->minimum, + exposure_max, gc05a2->exposure->step, + exposure_max); + } + + /* + * Applying V4L2 control value only happens + * when power is on for streaming. + */ + if (!pm_runtime_get_if_active(gc05a2->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ret = cci_write(gc05a2->regmap, GC05A2_EXP_REG, + ctrl->val, NULL); + break; + + case V4L2_CID_ANALOGUE_GAIN: + ret = cci_write(gc05a2->regmap, GC05A2_AGAIN_REG, + ctrl->val, NULL); + break; + + case V4L2_CID_VBLANK: + ret = cci_write(gc05a2->regmap, GC05A2_FRAME_LENGTH_REG, + gc05a2->cur_mode->height + ctrl->val, NULL); + break; + + case V4L2_CID_HFLIP: + ret = gc05a2_set_ctrl_hflip(gc05a2, ctrl->val); + break; + + case V4L2_CID_VFLIP: + ret = gc05a2_set_ctrl_vflip(gc05a2, ctrl->val); + break; + + case V4L2_CID_TEST_PATTERN: + ret = gc05a2_test_pattern(gc05a2, ctrl->val); + break; + + default: + break; + } + + pm_runtime_put(gc05a2->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops gc05a2_ctrl_ops = { + .s_ctrl = gc05a2_set_ctrl, +}; + +static int gc05a2_identify_module(struct gc05a2 *gc05a2) +{ + u64 val; + int ret; + + if (gc05a2->identified) + return 0; + + ret = cci_read(gc05a2->regmap, GC05A2_REG_CHIP_ID, &val, NULL); + if (ret) + return ret; + + if (val != GC05A2_CHIP_ID) { + dev_err(gc05a2->dev, "chip id mismatch: 0x%x!=0x%llx", + GC05A2_CHIP_ID, val); + return -ENXIO; + } + + gc05a2->identified = true; + + return 0; +} + +static int gc05a2_start_streaming(struct gc05a2 *gc05a2) +{ + const struct gc05a2_mode *mode; + const struct gc05a2_reg_list *reg_list; + int ret; + + ret = pm_runtime_resume_and_get(gc05a2->dev); + if (ret < 0) + return ret; + + ret = gc05a2_identify_module(gc05a2); + if (ret) + goto err_rpm_put; + + ret = cci_multi_reg_write(gc05a2->regmap, + mode_table_common, + ARRAY_SIZE(mode_table_common), NULL); + if (ret) + goto err_rpm_put; + + mode = gc05a2->cur_mode; + reg_list = &mode->reg_list; + + ret = cci_multi_reg_write(gc05a2->regmap, + reg_list->regs, reg_list->num_of_regs, NULL); + if (ret < 0) + goto err_rpm_put; + + ret = __v4l2_ctrl_handler_setup(&gc05a2->ctrls); + if (ret < 0) { + dev_err(gc05a2->dev, "could not sync v4l2 controls\n"); + goto err_rpm_put; + } + + ret = cci_write(gc05a2->regmap, GC05A2_STREAMING_REG, 1, NULL); + if (ret < 0) { + dev_err(gc05a2->dev, "write STREAMING_REG failed: %d\n", ret); + goto err_rpm_put; + } + + return 0; + +err_rpm_put: + pm_runtime_put(gc05a2->dev); + return ret; +} + +static int gc05a2_stop_streaming(struct gc05a2 *gc05a2) +{ + int ret; + + ret = cci_write(gc05a2->regmap, GC05A2_STREAMING_REG, 0, NULL); + if (ret < 0) + dev_err(gc05a2->dev, "could not sent stop streaming %d\n", ret); + + pm_runtime_put(gc05a2->dev); + return ret; +} + +static int gc05a2_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct gc05a2 *gc05a2 = to_gc05a2(subdev); + struct v4l2_subdev_state *state; + int ret; + + state = v4l2_subdev_lock_and_get_active_state(subdev); + + if (enable) + ret = gc05a2_start_streaming(gc05a2); + else + ret = gc05a2_stop_streaming(gc05a2); + + v4l2_subdev_unlock_state(state); + + return ret; +} + +static const struct v4l2_subdev_video_ops gc05a2_video_ops = { + .s_stream = gc05a2_s_stream, +}; + +static const struct v4l2_subdev_pad_ops gc05a2_subdev_pad_ops = { + .enum_mbus_code = gc05a2_enum_mbus_code, + .enum_frame_size = gc05a2_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = gc05a2_set_format, + .get_selection = gc05a2_get_selection, +}; + +static const struct v4l2_subdev_ops gc05a2_subdev_ops = { + .video = &gc05a2_video_ops, + .pad = &gc05a2_subdev_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops gc05a2_internal_ops = { + .init_state = gc05a2_init_state, +}; + +static int gc05a2_get_regulators(struct device *dev, struct gc05a2 *gc05a2) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(gc05a2_supply_name); i++) + gc05a2->supplies[i].supply = gc05a2_supply_name[i]; + + return devm_regulator_bulk_get(dev, ARRAY_SIZE(gc05a2_supply_name), + gc05a2->supplies); +} + +static int gc05a2_parse_fwnode(struct gc05a2 *gc05a2) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + int ret; + struct device *dev = gc05a2->dev; + + endpoint = + fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!endpoint) + return dev_err_probe(dev, -EINVAL, "Missing endpoint node\n"); + + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); + if (ret) { + dev_err_probe(dev, ret, "parsing endpoint node failed\n"); + goto done; + } + + ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, + gc05a2_link_freq_menu_items, + ARRAY_SIZE(gc05a2_link_freq_menu_items), + &gc05a2->link_freq_bitmap); + if (ret) + goto done; + +done: + v4l2_fwnode_endpoint_free(&bus_cfg); + fwnode_handle_put(endpoint); + return ret; +} + +static u64 gc05a2_to_pixel_rate(u32 f_index) +{ + u64 pixel_rate = + gc05a2_link_freq_menu_items[f_index] * 2 * GC05A2_DATA_LANES; + + return div_u64(pixel_rate, GC05A2_RGB_DEPTH); +} + +static int gc05a2_init_controls(struct gc05a2 *gc05a2) +{ + struct i2c_client *client = v4l2_get_subdevdata(&gc05a2->sd); + const struct gc05a2_mode *mode = &gc05a2_modes[0]; + const struct v4l2_ctrl_ops *ops = &gc05a2_ctrl_ops; + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max, h_blank; + int ret; + + ctrl_hdlr = &gc05a2->ctrls; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 9); + if (ret) + return ret; + + gc05a2->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &gc05a2_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + gc05a2->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &gc05a2_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_cluster(2, &gc05a2->hflip); + + gc05a2->link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, + &gc05a2_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(gc05a2_link_freq_menu_items) - 1, + 0, + gc05a2_link_freq_menu_items); + if (gc05a2->link_freq) + gc05a2->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + gc05a2->pixel_rate = + v4l2_ctrl_new_std(ctrl_hdlr, + &gc05a2_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, + gc05a2_to_pixel_rate(0), + 1, + gc05a2_to_pixel_rate(0)); + + gc05a2->vblank = + v4l2_ctrl_new_std(ctrl_hdlr, + &gc05a2_ctrl_ops, V4L2_CID_VBLANK, + mode->vts_min - mode->height, + GC05A2_VTS_MAX - mode->height, 1, + mode->vts_def - mode->height); + + h_blank = mode->hts - mode->width; + gc05a2->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &gc05a2_ctrl_ops, + V4L2_CID_HBLANK, h_blank, h_blank, 1, + h_blank); + if (gc05a2->hblank) + gc05a2->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(ctrl_hdlr, &gc05a2_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, GC05A2_AGAIN_MIN, + GC05A2_AGAIN_MAX, GC05A2_AGAIN_STEP, + GC05A2_AGAIN_MIN); + + exposure_max = mode->vts_def - GC05A2_EXP_MARGIN; + gc05a2->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &gc05a2_ctrl_ops, + V4L2_CID_EXPOSURE, GC05A2_EXP_MIN, + exposure_max, GC05A2_EXP_STEP, + exposure_max); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &gc05a2_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(gc05a2_test_pattern_menu) - 1, + 0, 0, gc05a2_test_pattern_menu); + + /* register properties to fwnode (e.g. rotation, orientation) */ + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error_ctrls; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, ops, &props); + if (ret) + goto error_ctrls; + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + goto error_ctrls; + } + + gc05a2->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error_ctrls: + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; +} + +static int gc05a2_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct gc05a2 *gc05a2; + int ret; + + gc05a2 = devm_kzalloc(dev, sizeof(*gc05a2), GFP_KERNEL); + if (!gc05a2) + return -ENOMEM; + + gc05a2->dev = dev; + + ret = gc05a2_parse_fwnode(gc05a2); + if (ret) + return ret; + + gc05a2->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(gc05a2->regmap)) + return dev_err_probe(dev, PTR_ERR(gc05a2->regmap), + "failed to init CCI\n"); + + gc05a2->xclk = devm_v4l2_sensor_clk_get_legacy(dev, NULL, true, + GC05A2_DEFAULT_CLK_FREQ); + if (IS_ERR(gc05a2->xclk)) + return dev_err_probe(dev, PTR_ERR(gc05a2->xclk), + "failed to get xclk\n"); + + ret = gc05a2_get_regulators(dev, gc05a2); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to get regulators\n"); + + gc05a2->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(gc05a2->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(gc05a2->reset_gpio), + "failed to get gpio\n"); + + v4l2_i2c_subdev_init(&gc05a2->sd, client, &gc05a2_subdev_ops); + gc05a2->sd.internal_ops = &gc05a2_internal_ops; + gc05a2->cur_mode = &gc05a2_modes[0]; + + ret = gc05a2_init_controls(gc05a2); + if (ret) + return dev_err_probe(dev, ret, + "failed to init controls\n"); + + gc05a2->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + gc05a2->pad.flags = MEDIA_PAD_FL_SOURCE; + gc05a2->sd.dev = &client->dev; + gc05a2->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + ret = media_entity_pads_init(&gc05a2->sd.entity, 1, &gc05a2->pad); + if (ret < 0) { + dev_err_probe(dev, ret, "could not register media entity\n"); + goto err_v4l2_ctrl_handler_free; + } + + gc05a2->sd.state_lock = gc05a2->ctrls.lock; + ret = v4l2_subdev_init_finalize(&gc05a2->sd); + if (ret < 0) { + dev_err_probe(dev, ret, "v4l2 subdev init error\n"); + goto err_media_entity_cleanup; + } + + pm_runtime_enable(gc05a2->dev); + pm_runtime_set_autosuspend_delay(gc05a2->dev, 1000); + pm_runtime_use_autosuspend(gc05a2->dev); + pm_runtime_idle(gc05a2->dev); + + ret = v4l2_async_register_subdev_sensor(&gc05a2->sd); + if (ret < 0) { + dev_err_probe(dev, ret, "could not register v4l2 device\n"); + goto err_rpm; + } + + return 0; + +err_rpm: + pm_runtime_disable(gc05a2->dev); + v4l2_subdev_cleanup(&gc05a2->sd); + +err_media_entity_cleanup: + media_entity_cleanup(&gc05a2->sd.entity); + +err_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(&gc05a2->ctrls); + + return ret; +} + +static void gc05a2_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct gc05a2 *gc05a2 = to_gc05a2(sd); + + v4l2_async_unregister_subdev(&gc05a2->sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&gc05a2->sd.entity); + v4l2_ctrl_handler_free(&gc05a2->ctrls); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + gc05a2_power_off(gc05a2->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id gc05a2_of_match[] = { + { .compatible = "galaxycore,gc05a2" }, + {} +}; +MODULE_DEVICE_TABLE(of, gc05a2_of_match); + +static DEFINE_RUNTIME_DEV_PM_OPS(gc05a2_pm_ops, + gc05a2_power_off, + gc05a2_power_on, + NULL); + +static struct i2c_driver gc05a2_i2c_driver = { + .driver = { + .of_match_table = gc05a2_of_match, + .pm = pm_ptr(&gc05a2_pm_ops), + .name = "gc05a2", + }, + .probe = gc05a2_probe, + .remove = gc05a2_remove, +}; +module_i2c_driver(gc05a2_i2c_driver); + +MODULE_DESCRIPTION("GalaxyCore gc05a2 Camera driver"); +MODULE_AUTHOR("Zhi Mao "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/gc08a3.c b/drivers/media/i2c/gc08a3.c new file mode 100644 index 000000000..4144aad8f --- /dev/null +++ b/drivers/media/i2c/gc08a3.c @@ -0,0 +1,1326 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for GalaxyCore gc08a3 image sensor + * + * Copyright 2024 MediaTek + * + * Zhi Mao + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define GC08A3_REG_TEST_PATTERN_EN CCI_REG8(0x008c) +#define GC08A3_REG_TEST_PATTERN_IDX CCI_REG8(0x008d) +#define GC08A3_TEST_PATTERN_EN 0x01 + +#define GC08A3_STREAMING_REG CCI_REG8(0x0100) + +#define GC08A3_FLIP_REG CCI_REG8(0x0101) +#define GC08A3_FLIP_H_MASK BIT(0) +#define GC08A3_FLIP_V_MASK BIT(1) + +#define GC08A3_EXP_REG CCI_REG16(0x0202) +#define GC08A3_EXP_MARGIN 16 +#define GC08A3_EXP_MIN 4 +#define GC08A3_EXP_STEP 1 + +#define GC08A3_AGAIN_REG CCI_REG16(0x0204) +#define GC08A3_AGAIN_MIN 1024 +#define GC08A3_AGAIN_MAX (1024 * 16) +#define GC08A3_AGAIN_STEP 1 + +#define GC08A3_FRAME_LENGTH_REG CCI_REG16(0x0340) +#define GC08A3_VTS_MAX 0xfff0 + +#define GC08A3_REG_CHIP_ID CCI_REG16(0x03f0) +#define GC08A3_CHIP_ID 0x08a3 + +#define GC08A3_NATIVE_WIDTH 3264 +#define GC08A3_NATIVE_HEIGHT 2448 + +#define GC08A3_DEFAULT_CLK_FREQ (24 * HZ_PER_MHZ) +#define GC08A3_MBUS_CODE MEDIA_BUS_FMT_SRGGB10_1X10 +#define GC08A3_DATA_LANES 4 + +#define GC08A3_RGB_DEPTH 10 + +#define GC08A3_SLEEP_US (2 * USEC_PER_MSEC) + +static const char *const gc08a3_test_pattern_menu[] = { + "No Pattern", "Solid Black", "Colour Bar", "Solid White", + "Solid Red", "Solid Green", "Solid Blue", "Solid Yellow", +}; + +static const s64 gc08a3_link_freq_menu_items[] = { + (336 * HZ_PER_MHZ), + (207 * HZ_PER_MHZ), +}; + +static const char *const gc08a3_supply_name[] = { + "avdd", + "dvdd", + "dovdd", +}; + +struct gc08a3 { + struct device *dev; + struct v4l2_subdev sd; + struct media_pad pad; + + struct clk *xclk; + struct regulator_bulk_data supplies[ARRAY_SIZE(gc08a3_supply_name)]; + struct gpio_desc *reset_gpio; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + + struct regmap *regmap; + unsigned long link_freq_bitmap; + const struct gc08a3_mode *cur_mode; +}; + +struct gc08a3_reg_list { + u32 num_of_regs; + const struct cci_reg_sequence *regs; +}; + +static const struct cci_reg_sequence mode_3264x2448[] = { + /* system */ + { CCI_REG8(0x0336), 0x70 }, + { CCI_REG8(0x0383), 0xbb }, + { CCI_REG8(0x0344), 0x00 }, + { CCI_REG8(0x0345), 0x06 }, + { CCI_REG8(0x0346), 0x00 }, + { CCI_REG8(0x0347), 0x04 }, + { CCI_REG8(0x0348), 0x0c }, + { CCI_REG8(0x0349), 0xd0 }, + { CCI_REG8(0x034a), 0x09 }, + { CCI_REG8(0x034b), 0x9c }, + { CCI_REG8(0x0202), 0x09 }, + { CCI_REG8(0x0203), 0x04 }, + { CCI_REG8(0x0340), 0x09 }, + { CCI_REG8(0x0341), 0xf4 }, + { CCI_REG8(0x0342), 0x07 }, + { CCI_REG8(0x0343), 0x1c }, + + { CCI_REG8(0x0226), 0x00 }, + { CCI_REG8(0x0227), 0x28 }, + { CCI_REG8(0x0e38), 0x49 }, + { CCI_REG8(0x0210), 0x13 }, + { CCI_REG8(0x0218), 0x00 }, + { CCI_REG8(0x0241), 0x88 }, + { CCI_REG8(0x0392), 0x60 }, + + /* ISP */ + { CCI_REG8(0x00a2), 0x00 }, + { CCI_REG8(0x00a3), 0x00 }, + { CCI_REG8(0x00ab), 0x00 }, + { CCI_REG8(0x00ac), 0x00 }, + + /* GAIN */ + { CCI_REG8(0x0204), 0x04 }, + { CCI_REG8(0x0205), 0x00 }, + { CCI_REG8(0x0050), 0x5c }, + { CCI_REG8(0x0051), 0x44 }, + + /* out window */ + { CCI_REG8(0x009a), 0x66 }, + { CCI_REG8(0x0351), 0x00 }, + { CCI_REG8(0x0352), 0x06 }, + { CCI_REG8(0x0353), 0x00 }, + { CCI_REG8(0x0354), 0x08 }, + { CCI_REG8(0x034c), 0x0c }, + { CCI_REG8(0x034d), 0xc0 }, + { CCI_REG8(0x034e), 0x09 }, + { CCI_REG8(0x034f), 0x90 }, + + /* MIPI */ + { CCI_REG8(0x0114), 0x03 }, + { CCI_REG8(0x0180), 0x65 }, + { CCI_REG8(0x0181), 0xf0 }, + { CCI_REG8(0x0185), 0x01 }, + { CCI_REG8(0x0115), 0x30 }, + { CCI_REG8(0x011b), 0x12 }, + { CCI_REG8(0x011c), 0x12 }, + { CCI_REG8(0x0121), 0x06 }, + { CCI_REG8(0x0122), 0x06 }, + { CCI_REG8(0x0123), 0x15 }, + { CCI_REG8(0x0124), 0x01 }, + { CCI_REG8(0x0125), 0x0b }, + { CCI_REG8(0x0126), 0x08 }, + { CCI_REG8(0x0129), 0x06 }, + { CCI_REG8(0x012a), 0x08 }, + { CCI_REG8(0x012b), 0x08 }, + + { CCI_REG8(0x0a73), 0x60 }, + { CCI_REG8(0x0a70), 0x11 }, + { CCI_REG8(0x0313), 0x80 }, + { CCI_REG8(0x0aff), 0x00 }, + { CCI_REG8(0x0a70), 0x00 }, + { CCI_REG8(0x00a4), 0x80 }, + { CCI_REG8(0x0316), 0x01 }, + { CCI_REG8(0x0a67), 0x00 }, + { CCI_REG8(0x0084), 0x10 }, + { CCI_REG8(0x0102), 0x09 }, +}; + +static const struct cci_reg_sequence mode_1920x1080[] = { + /* system */ + { CCI_REG8(0x0336), 0x45 }, + { CCI_REG8(0x0383), 0x8b }, + { CCI_REG8(0x0344), 0x02 }, + { CCI_REG8(0x0345), 0xa6 }, + { CCI_REG8(0x0346), 0x02 }, + { CCI_REG8(0x0347), 0xb0 }, + { CCI_REG8(0x0348), 0x07 }, + { CCI_REG8(0x0349), 0x90 }, + { CCI_REG8(0x034a), 0x04 }, + { CCI_REG8(0x034b), 0x44 }, + { CCI_REG8(0x0202), 0x03 }, + { CCI_REG8(0x0203), 0x00 }, + { CCI_REG8(0x0340), 0x04 }, + { CCI_REG8(0x0341), 0xfc }, + { CCI_REG8(0x0342), 0x07 }, + { CCI_REG8(0x0343), 0x1c }, + { CCI_REG8(0x0226), 0x00 }, + { CCI_REG8(0x0227), 0x88 }, + { CCI_REG8(0x0e38), 0x49 }, + { CCI_REG8(0x0210), 0x13 }, + { CCI_REG8(0x0218), 0x00 }, + { CCI_REG8(0x0241), 0x88 }, + { CCI_REG8(0x0392), 0x60 }, + + /* ISP */ + { CCI_REG8(0x00a2), 0xac }, + { CCI_REG8(0x00a3), 0x02 }, + { CCI_REG8(0x00ab), 0xa0 }, + { CCI_REG8(0x00ac), 0x02 }, + + /* GAIN */ + { CCI_REG8(0x0204), 0x04 }, + { CCI_REG8(0x0205), 0x00 }, + { CCI_REG8(0x0050), 0x38 }, + { CCI_REG8(0x0051), 0x20 }, + + /* out window */ + { CCI_REG8(0x009a), 0x66 }, + { CCI_REG8(0x0351), 0x00 }, + { CCI_REG8(0x0352), 0x06 }, + { CCI_REG8(0x0353), 0x00 }, + { CCI_REG8(0x0354), 0x08 }, + { CCI_REG8(0x034c), 0x07 }, + { CCI_REG8(0x034d), 0x80 }, + { CCI_REG8(0x034e), 0x04 }, + { CCI_REG8(0x034f), 0x38 }, + + /* MIPI */ + { CCI_REG8(0x0114), 0x03 }, + { CCI_REG8(0x0180), 0x65 }, + { CCI_REG8(0x0181), 0xf0 }, + { CCI_REG8(0x0185), 0x01 }, + { CCI_REG8(0x0115), 0x30 }, + { CCI_REG8(0x011b), 0x12 }, + { CCI_REG8(0x011c), 0x12 }, + { CCI_REG8(0x0121), 0x02 }, + { CCI_REG8(0x0122), 0x03 }, + { CCI_REG8(0x0123), 0x0c }, + { CCI_REG8(0x0124), 0x00 }, + { CCI_REG8(0x0125), 0x09 }, + { CCI_REG8(0x0126), 0x06 }, + { CCI_REG8(0x0129), 0x04 }, + { CCI_REG8(0x012a), 0x03 }, + { CCI_REG8(0x012b), 0x06 }, + + { CCI_REG8(0x0a73), 0x60 }, + { CCI_REG8(0x0a70), 0x11 }, + { CCI_REG8(0x0313), 0x80 }, + { CCI_REG8(0x0aff), 0x00 }, + { CCI_REG8(0x0a70), 0x00 }, + { CCI_REG8(0x00a4), 0x80 }, + { CCI_REG8(0x0316), 0x01 }, + { CCI_REG8(0x0a67), 0x00 }, + { CCI_REG8(0x0084), 0x10 }, + { CCI_REG8(0x0102), 0x09 }, +}; + +static const struct cci_reg_sequence mode_table_common[] = { + { GC08A3_STREAMING_REG, 0x00 }, + /* system */ + { CCI_REG8(0x031c), 0x60 }, + { CCI_REG8(0x0337), 0x04 }, + { CCI_REG8(0x0335), 0x51 }, + { CCI_REG8(0x0336), 0x70 }, + { CCI_REG8(0x0383), 0xbb }, + { CCI_REG8(0x031a), 0x00 }, + { CCI_REG8(0x0321), 0x10 }, + { CCI_REG8(0x0327), 0x03 }, + { CCI_REG8(0x0325), 0x40 }, + { CCI_REG8(0x0326), 0x23 }, + { CCI_REG8(0x0314), 0x11 }, + { CCI_REG8(0x0315), 0xd6 }, + { CCI_REG8(0x0316), 0x01 }, + { CCI_REG8(0x0334), 0x40 }, + { CCI_REG8(0x0324), 0x42 }, + { CCI_REG8(0x031c), 0x00 }, + { CCI_REG8(0x031c), 0x9f }, + { CCI_REG8(0x039a), 0x13 }, + { CCI_REG8(0x0084), 0x30 }, + { CCI_REG8(0x02b3), 0x08 }, + { CCI_REG8(0x0057), 0x0c }, + { CCI_REG8(0x05c3), 0x50 }, + { CCI_REG8(0x0311), 0x90 }, + { CCI_REG8(0x05a0), 0x02 }, + { CCI_REG8(0x0074), 0x0a }, + { CCI_REG8(0x0059), 0x11 }, + { CCI_REG8(0x0070), 0x05 }, + { CCI_REG8(0x0101), 0x00 }, + + /* analog */ + { CCI_REG8(0x0344), 0x00 }, + { CCI_REG8(0x0345), 0x06 }, + { CCI_REG8(0x0346), 0x00 }, + { CCI_REG8(0x0347), 0x04 }, + { CCI_REG8(0x0348), 0x0c }, + { CCI_REG8(0x0349), 0xd0 }, + { CCI_REG8(0x034a), 0x09 }, + { CCI_REG8(0x034b), 0x9c }, + { CCI_REG8(0x0202), 0x09 }, + { CCI_REG8(0x0203), 0x04 }, + + { CCI_REG8(0x0219), 0x05 }, + { CCI_REG8(0x0226), 0x00 }, + { CCI_REG8(0x0227), 0x28 }, + { CCI_REG8(0x0e0a), 0x00 }, + { CCI_REG8(0x0e0b), 0x00 }, + { CCI_REG8(0x0e24), 0x04 }, + { CCI_REG8(0x0e25), 0x04 }, + { CCI_REG8(0x0e26), 0x00 }, + { CCI_REG8(0x0e27), 0x10 }, + { CCI_REG8(0x0e01), 0x74 }, + { CCI_REG8(0x0e03), 0x47 }, + { CCI_REG8(0x0e04), 0x33 }, + { CCI_REG8(0x0e05), 0x44 }, + { CCI_REG8(0x0e06), 0x44 }, + { CCI_REG8(0x0e0c), 0x1e }, + { CCI_REG8(0x0e17), 0x3a }, + { CCI_REG8(0x0e18), 0x3c }, + { CCI_REG8(0x0e19), 0x40 }, + { CCI_REG8(0x0e1a), 0x42 }, + { CCI_REG8(0x0e28), 0x21 }, + { CCI_REG8(0x0e2b), 0x68 }, + { CCI_REG8(0x0e2c), 0x0d }, + { CCI_REG8(0x0e2d), 0x08 }, + { CCI_REG8(0x0e34), 0xf4 }, + { CCI_REG8(0x0e35), 0x44 }, + { CCI_REG8(0x0e36), 0x07 }, + { CCI_REG8(0x0e38), 0x49 }, + { CCI_REG8(0x0210), 0x13 }, + { CCI_REG8(0x0218), 0x00 }, + { CCI_REG8(0x0241), 0x88 }, + { CCI_REG8(0x0e32), 0x00 }, + { CCI_REG8(0x0e33), 0x18 }, + { CCI_REG8(0x0e42), 0x03 }, + { CCI_REG8(0x0e43), 0x80 }, + { CCI_REG8(0x0e44), 0x04 }, + { CCI_REG8(0x0e45), 0x00 }, + { CCI_REG8(0x0e4f), 0x04 }, + { CCI_REG8(0x057a), 0x20 }, + { CCI_REG8(0x0381), 0x7c }, + { CCI_REG8(0x0382), 0x9b }, + { CCI_REG8(0x0384), 0xfb }, + { CCI_REG8(0x0389), 0x38 }, + { CCI_REG8(0x038a), 0x03 }, + { CCI_REG8(0x0390), 0x6a }, + { CCI_REG8(0x0391), 0x0b }, + { CCI_REG8(0x0392), 0x60 }, + { CCI_REG8(0x0393), 0xc1 }, + { CCI_REG8(0x0396), 0xff }, + { CCI_REG8(0x0398), 0x62 }, + + /* cisctl reset */ + { CCI_REG8(0x031c), 0x80 }, + { CCI_REG8(0x03fe), 0x10 }, + { CCI_REG8(0x03fe), 0x00 }, + { CCI_REG8(0x031c), 0x9f }, + { CCI_REG8(0x03fe), 0x00 }, + { CCI_REG8(0x03fe), 0x00 }, + { CCI_REG8(0x03fe), 0x00 }, + { CCI_REG8(0x03fe), 0x00 }, + { CCI_REG8(0x031c), 0x80 }, + { CCI_REG8(0x03fe), 0x10 }, + { CCI_REG8(0x03fe), 0x00 }, + { CCI_REG8(0x031c), 0x9f }, + { CCI_REG8(0x0360), 0x01 }, + { CCI_REG8(0x0360), 0x00 }, + { CCI_REG8(0x0316), 0x09 }, + { CCI_REG8(0x0a67), 0x80 }, + { CCI_REG8(0x0313), 0x00 }, + { CCI_REG8(0x0a53), 0x0e }, + { CCI_REG8(0x0a65), 0x17 }, + { CCI_REG8(0x0a68), 0xa1 }, + { CCI_REG8(0x0a58), 0x00 }, + { CCI_REG8(0x0ace), 0x0c }, + { CCI_REG8(0x00a4), 0x00 }, + { CCI_REG8(0x00a5), 0x01 }, + { CCI_REG8(0x00a7), 0x09 }, + { CCI_REG8(0x00a8), 0x9c }, + { CCI_REG8(0x00a9), 0x0c }, + { CCI_REG8(0x00aa), 0xd0 }, + { CCI_REG8(0x0a8a), 0x00 }, + { CCI_REG8(0x0a8b), 0xe0 }, + { CCI_REG8(0x0a8c), 0x13 }, + { CCI_REG8(0x0a8d), 0xe8 }, + { CCI_REG8(0x0a90), 0x0a }, + { CCI_REG8(0x0a91), 0x10 }, + { CCI_REG8(0x0a92), 0xf8 }, + { CCI_REG8(0x0a71), 0xf2 }, + { CCI_REG8(0x0a72), 0x12 }, + { CCI_REG8(0x0a73), 0x64 }, + { CCI_REG8(0x0a75), 0x41 }, + { CCI_REG8(0x0a70), 0x07 }, + { CCI_REG8(0x0313), 0x80 }, + + /* ISP */ + { CCI_REG8(0x00a0), 0x01 }, + { CCI_REG8(0x0080), 0xd2 }, + { CCI_REG8(0x0081), 0x3f }, + { CCI_REG8(0x0087), 0x51 }, + { CCI_REG8(0x0089), 0x03 }, + { CCI_REG8(0x009b), 0x40 }, + { CCI_REG8(0x05a0), 0x82 }, + { CCI_REG8(0x05ac), 0x00 }, + { CCI_REG8(0x05ad), 0x01 }, + { CCI_REG8(0x05ae), 0x00 }, + { CCI_REG8(0x0800), 0x0a }, + { CCI_REG8(0x0801), 0x14 }, + { CCI_REG8(0x0802), 0x28 }, + { CCI_REG8(0x0803), 0x34 }, + { CCI_REG8(0x0804), 0x0e }, + { CCI_REG8(0x0805), 0x33 }, + { CCI_REG8(0x0806), 0x03 }, + { CCI_REG8(0x0807), 0x8a }, + { CCI_REG8(0x0808), 0x50 }, + { CCI_REG8(0x0809), 0x00 }, + { CCI_REG8(0x080a), 0x34 }, + { CCI_REG8(0x080b), 0x03 }, + { CCI_REG8(0x080c), 0x26 }, + { CCI_REG8(0x080d), 0x03 }, + { CCI_REG8(0x080e), 0x18 }, + { CCI_REG8(0x080f), 0x03 }, + { CCI_REG8(0x0810), 0x10 }, + { CCI_REG8(0x0811), 0x03 }, + { CCI_REG8(0x0812), 0x00 }, + { CCI_REG8(0x0813), 0x00 }, + { CCI_REG8(0x0814), 0x01 }, + { CCI_REG8(0x0815), 0x00 }, + { CCI_REG8(0x0816), 0x01 }, + { CCI_REG8(0x0817), 0x00 }, + { CCI_REG8(0x0818), 0x00 }, + { CCI_REG8(0x0819), 0x0a }, + { CCI_REG8(0x081a), 0x01 }, + { CCI_REG8(0x081b), 0x6c }, + { CCI_REG8(0x081c), 0x00 }, + { CCI_REG8(0x081d), 0x0b }, + { CCI_REG8(0x081e), 0x02 }, + { CCI_REG8(0x081f), 0x00 }, + { CCI_REG8(0x0820), 0x00 }, + { CCI_REG8(0x0821), 0x0c }, + { CCI_REG8(0x0822), 0x02 }, + { CCI_REG8(0x0823), 0xd9 }, + { CCI_REG8(0x0824), 0x00 }, + { CCI_REG8(0x0825), 0x0d }, + { CCI_REG8(0x0826), 0x03 }, + { CCI_REG8(0x0827), 0xf0 }, + { CCI_REG8(0x0828), 0x00 }, + { CCI_REG8(0x0829), 0x0e }, + { CCI_REG8(0x082a), 0x05 }, + { CCI_REG8(0x082b), 0x94 }, + { CCI_REG8(0x082c), 0x09 }, + { CCI_REG8(0x082d), 0x6e }, + { CCI_REG8(0x082e), 0x07 }, + { CCI_REG8(0x082f), 0xe6 }, + { CCI_REG8(0x0830), 0x10 }, + { CCI_REG8(0x0831), 0x0e }, + { CCI_REG8(0x0832), 0x0b }, + { CCI_REG8(0x0833), 0x2c }, + { CCI_REG8(0x0834), 0x14 }, + { CCI_REG8(0x0835), 0xae }, + { CCI_REG8(0x0836), 0x0f }, + { CCI_REG8(0x0837), 0xc4 }, + { CCI_REG8(0x0838), 0x18 }, + { CCI_REG8(0x0839), 0x0e }, + { CCI_REG8(0x05ac), 0x01 }, + { CCI_REG8(0x059a), 0x00 }, + { CCI_REG8(0x059b), 0x00 }, + { CCI_REG8(0x059c), 0x01 }, + { CCI_REG8(0x0598), 0x00 }, + { CCI_REG8(0x0597), 0x14 }, + { CCI_REG8(0x05ab), 0x09 }, + { CCI_REG8(0x05a4), 0x02 }, + { CCI_REG8(0x05a3), 0x05 }, + { CCI_REG8(0x05a0), 0xc2 }, + { CCI_REG8(0x0207), 0xc4 }, + + /* GAIN */ + { CCI_REG8(0x0208), 0x01 }, + { CCI_REG8(0x0209), 0x72 }, + { CCI_REG8(0x0204), 0x04 }, + { CCI_REG8(0x0205), 0x00 }, + + { CCI_REG8(0x0040), 0x22 }, + { CCI_REG8(0x0041), 0x20 }, + { CCI_REG8(0x0043), 0x10 }, + { CCI_REG8(0x0044), 0x00 }, + { CCI_REG8(0x0046), 0x08 }, + { CCI_REG8(0x0047), 0xf0 }, + { CCI_REG8(0x0048), 0x0f }, + { CCI_REG8(0x004b), 0x0f }, + { CCI_REG8(0x004c), 0x00 }, + { CCI_REG8(0x0050), 0x5c }, + { CCI_REG8(0x0051), 0x44 }, + { CCI_REG8(0x005b), 0x03 }, + { CCI_REG8(0x00c0), 0x00 }, + { CCI_REG8(0x00c1), 0x80 }, + { CCI_REG8(0x00c2), 0x31 }, + { CCI_REG8(0x00c3), 0x00 }, + { CCI_REG8(0x0460), 0x04 }, + { CCI_REG8(0x0462), 0x08 }, + { CCI_REG8(0x0464), 0x0e }, + { CCI_REG8(0x0466), 0x0a }, + { CCI_REG8(0x0468), 0x12 }, + { CCI_REG8(0x046a), 0x12 }, + { CCI_REG8(0x046c), 0x10 }, + { CCI_REG8(0x046e), 0x0c }, + { CCI_REG8(0x0461), 0x03 }, + { CCI_REG8(0x0463), 0x03 }, + { CCI_REG8(0x0465), 0x03 }, + { CCI_REG8(0x0467), 0x03 }, + { CCI_REG8(0x0469), 0x04 }, + { CCI_REG8(0x046b), 0x04 }, + { CCI_REG8(0x046d), 0x04 }, + { CCI_REG8(0x046f), 0x04 }, + { CCI_REG8(0x0470), 0x04 }, + { CCI_REG8(0x0472), 0x10 }, + { CCI_REG8(0x0474), 0x26 }, + { CCI_REG8(0x0476), 0x38 }, + { CCI_REG8(0x0478), 0x20 }, + { CCI_REG8(0x047a), 0x30 }, + { CCI_REG8(0x047c), 0x38 }, + { CCI_REG8(0x047e), 0x60 }, + { CCI_REG8(0x0471), 0x05 }, + { CCI_REG8(0x0473), 0x05 }, + { CCI_REG8(0x0475), 0x05 }, + { CCI_REG8(0x0477), 0x05 }, + { CCI_REG8(0x0479), 0x04 }, + { CCI_REG8(0x047b), 0x04 }, + { CCI_REG8(0x047d), 0x04 }, + { CCI_REG8(0x047f), 0x04 }, +}; + +struct gc08a3_mode { + u32 width; + u32 height; + const struct gc08a3_reg_list reg_list; + + u32 hts; /* Horizontal timining size */ + u32 vts_def; /* Default vertical timining size */ + u32 vts_min; /* Min vertical timining size */ +}; + +/* Declare modes in order, from biggest to smallest height. */ +static const struct gc08a3_mode gc08a3_modes[] = { + { + /* 3264*2448@30fps */ + .width = GC08A3_NATIVE_WIDTH, + .height = GC08A3_NATIVE_HEIGHT, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3264x2448), + .regs = mode_3264x2448, + }, + .hts = 3640, + .vts_def = 2548, + .vts_min = 2548, + }, + { + /* 1920*1080@60fps */ + .width = 1920, + .height = 1080, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1920x1080), + .regs = mode_1920x1080, + }, + .hts = 3640, + .vts_def = 1276, + .vts_min = 1276, + }, +}; + +static inline struct gc08a3 *to_gc08a3(struct v4l2_subdev *sd) +{ + return container_of(sd, struct gc08a3, sd); +} + +static int gc08a3_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct gc08a3 *gc08a3 = to_gc08a3(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(gc08a3_supply_name), + gc08a3->supplies); + if (ret < 0) { + dev_err(gc08a3->dev, "failed to enable regulators: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(gc08a3->xclk); + if (ret < 0) { + regulator_bulk_disable(ARRAY_SIZE(gc08a3_supply_name), + gc08a3->supplies); + dev_err(gc08a3->dev, "clk prepare enable failed\n"); + return ret; + } + + fsleep(GC08A3_SLEEP_US); + + gpiod_set_value_cansleep(gc08a3->reset_gpio, 0); + fsleep(GC08A3_SLEEP_US); + + return 0; +} + +static int gc08a3_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct gc08a3 *gc08a3 = to_gc08a3(sd); + + clk_disable_unprepare(gc08a3->xclk); + gpiod_set_value_cansleep(gc08a3->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(gc08a3_supply_name), + gc08a3->supplies); + + return 0; +} + +static int gc08a3_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = GC08A3_MBUS_CODE; + + return 0; +} + +static int gc08a3_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->code != GC08A3_MBUS_CODE) + return -EINVAL; + + if (fse->index >= ARRAY_SIZE(gc08a3_modes)) + return -EINVAL; + + fse->min_width = gc08a3_modes[fse->index].width; + fse->max_width = gc08a3_modes[fse->index].width; + fse->min_height = gc08a3_modes[fse->index].height; + fse->max_height = gc08a3_modes[fse->index].height; + + return 0; +} + +static int gc08a3_update_cur_mode_controls(struct gc08a3 *gc08a3, + const struct gc08a3_mode *mode) +{ + s64 exposure_max, h_blank; + int ret; + + ret = __v4l2_ctrl_modify_range(gc08a3->vblank, + mode->vts_min - mode->height, + GC08A3_VTS_MAX - mode->height, 1, + mode->vts_def - mode->height); + if (ret) { + dev_err(gc08a3->dev, "VB ctrl range update failed\n"); + return ret; + } + + h_blank = mode->hts - mode->width; + ret = __v4l2_ctrl_modify_range(gc08a3->hblank, h_blank, h_blank, 1, + h_blank); + if (ret) { + dev_err(gc08a3->dev, "HB ctrl range update failed\n"); + return ret; + } + + exposure_max = mode->vts_def - GC08A3_EXP_MARGIN; + ret = __v4l2_ctrl_modify_range(gc08a3->exposure, GC08A3_EXP_MIN, + exposure_max, GC08A3_EXP_STEP, + exposure_max); + if (ret) { + dev_err(gc08a3->dev, "exposure ctrl range update failed\n"); + return ret; + } + + return 0; +} + +static void gc08a3_update_pad_format(struct gc08a3 *gc08a3, + const struct gc08a3_mode *mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = mode->width; + fmt->height = mode->height; + fmt->code = GC08A3_MBUS_CODE; + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_XFER_FUNC_NONE; +} + +static int gc08a3_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct gc08a3 *gc08a3 = to_gc08a3(sd); + struct v4l2_mbus_framefmt *mbus_fmt; + struct v4l2_rect *crop; + const struct gc08a3_mode *mode; + + mode = v4l2_find_nearest_size(gc08a3_modes, ARRAY_SIZE(gc08a3_modes), + width, height, fmt->format.width, + fmt->format.height); + + /* update crop info to subdev state */ + crop = v4l2_subdev_state_get_crop(state, 0); + crop->width = mode->width; + crop->height = mode->height; + + /* update fmt info to subdev state */ + gc08a3_update_pad_format(gc08a3, mode, &fmt->format); + mbus_fmt = v4l2_subdev_state_get_format(state, 0); + *mbus_fmt = fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + + gc08a3->cur_mode = mode; + gc08a3_update_cur_mode_controls(gc08a3, mode); + + return 0; +} + +static int gc08a3_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_state_get_crop(state, 0); + break; + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = GC08A3_NATIVE_WIDTH; + sel->r.height = GC08A3_NATIVE_HEIGHT; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int gc08a3_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .pad = 0, + .format = { + .code = GC08A3_MBUS_CODE, + .width = gc08a3_modes[0].width, + .height = gc08a3_modes[0].height, + }, + }; + + gc08a3_set_format(sd, state, &fmt); + + return 0; +} + +static int gc08a3_set_ctrl_hflip(struct gc08a3 *gc08a3, u32 ctrl_val) +{ + int ret; + u64 val; + + ret = cci_read(gc08a3->regmap, GC08A3_FLIP_REG, &val, NULL); + if (ret) { + dev_err(gc08a3->dev, "read hflip register failed: %d\n", ret); + return ret; + } + + return cci_update_bits(gc08a3->regmap, GC08A3_FLIP_REG, + GC08A3_FLIP_H_MASK, + ctrl_val ? GC08A3_FLIP_H_MASK : 0, NULL); +} + +static int gc08a3_set_ctrl_vflip(struct gc08a3 *gc08a3, u32 ctrl_val) +{ + int ret; + u64 val; + + ret = cci_read(gc08a3->regmap, GC08A3_FLIP_REG, &val, NULL); + if (ret) { + dev_err(gc08a3->dev, "read vflip register failed: %d\n", ret); + return ret; + } + + return cci_update_bits(gc08a3->regmap, GC08A3_FLIP_REG, + GC08A3_FLIP_V_MASK, + ctrl_val ? GC08A3_FLIP_V_MASK : 0, NULL); +} + +static int gc08a3_test_pattern(struct gc08a3 *gc08a3, u32 pattern_menu) +{ + u32 pattern; + int ret; + + if (pattern_menu) { + switch (pattern_menu) { + case 1: + pattern = 0x00; + break; + case 2: + pattern = 0x10; + break; + case 3: + case 4: + case 5: + case 6: + case 7: + pattern = pattern_menu + 1; + break; + default: + pattern = 0x00; + break; + } + + ret = cci_write(gc08a3->regmap, GC08A3_REG_TEST_PATTERN_IDX, + pattern, NULL); + if (ret) + return ret; + + return cci_write(gc08a3->regmap, GC08A3_REG_TEST_PATTERN_EN, + GC08A3_TEST_PATTERN_EN, NULL); + } else { + return cci_write(gc08a3->regmap, GC08A3_REG_TEST_PATTERN_EN, + 0x00, NULL); + } +} + +static int gc08a3_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct gc08a3 *gc08a3 = + container_of(ctrl->handler, struct gc08a3, ctrls); + int ret = 0; + s64 exposure_max; + struct v4l2_subdev_state *state; + const struct v4l2_mbus_framefmt *format; + + state = v4l2_subdev_get_locked_active_state(&gc08a3->sd); + format = v4l2_subdev_state_get_format(state, 0); + + if (ctrl->id == V4L2_CID_VBLANK) { + /* Update max exposure while meeting expected vblanking */ + exposure_max = format->height + ctrl->val - GC08A3_EXP_MARGIN; + __v4l2_ctrl_modify_range(gc08a3->exposure, + gc08a3->exposure->minimum, + exposure_max, gc08a3->exposure->step, + exposure_max); + } + + /* + * Applying V4L2 control value only happens + * when power is on for streaming. + */ + if (!pm_runtime_get_if_active(gc08a3->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ret = cci_write(gc08a3->regmap, GC08A3_EXP_REG, + ctrl->val, NULL); + break; + + case V4L2_CID_ANALOGUE_GAIN: + ret = cci_write(gc08a3->regmap, GC08A3_AGAIN_REG, + ctrl->val, NULL); + break; + + case V4L2_CID_VBLANK: + ret = cci_write(gc08a3->regmap, GC08A3_FRAME_LENGTH_REG, + gc08a3->cur_mode->height + ctrl->val, NULL); + break; + + case V4L2_CID_HFLIP: + ret = gc08a3_set_ctrl_hflip(gc08a3, ctrl->val); + break; + + case V4L2_CID_VFLIP: + ret = gc08a3_set_ctrl_vflip(gc08a3, ctrl->val); + break; + + case V4L2_CID_TEST_PATTERN: + ret = gc08a3_test_pattern(gc08a3, ctrl->val); + break; + + default: + break; + } + + pm_runtime_put(gc08a3->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops gc08a3_ctrl_ops = { + .s_ctrl = gc08a3_set_ctrl, +}; + +static int gc08a3_start_streaming(struct gc08a3 *gc08a3) +{ + const struct gc08a3_mode *mode; + const struct gc08a3_reg_list *reg_list; + int ret; + + ret = pm_runtime_resume_and_get(gc08a3->dev); + if (ret < 0) + return ret; + + ret = cci_multi_reg_write(gc08a3->regmap, + mode_table_common, + ARRAY_SIZE(mode_table_common), NULL); + if (ret) + goto err_rpm_put; + + mode = gc08a3->cur_mode; + reg_list = &mode->reg_list; + ret = cci_multi_reg_write(gc08a3->regmap, + reg_list->regs, reg_list->num_of_regs, NULL); + if (ret < 0) + goto err_rpm_put; + + ret = __v4l2_ctrl_handler_setup(&gc08a3->ctrls); + if (ret < 0) { + dev_err(gc08a3->dev, "could not sync v4l2 controls\n"); + goto err_rpm_put; + } + + ret = cci_write(gc08a3->regmap, GC08A3_STREAMING_REG, 1, NULL); + if (ret < 0) { + dev_err(gc08a3->dev, "write STREAMING_REG failed: %d\n", ret); + goto err_rpm_put; + } + + return 0; + +err_rpm_put: + pm_runtime_put(gc08a3->dev); + return ret; +} + +static int gc08a3_stop_streaming(struct gc08a3 *gc08a3) +{ + int ret; + + ret = cci_write(gc08a3->regmap, GC08A3_STREAMING_REG, 0, NULL); + if (ret < 0) + dev_err(gc08a3->dev, "could not sent stop streaming %d\n", ret); + + pm_runtime_put(gc08a3->dev); + return ret; +} + +static int gc08a3_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct gc08a3 *gc08a3 = to_gc08a3(subdev); + struct v4l2_subdev_state *state; + int ret; + + state = v4l2_subdev_lock_and_get_active_state(subdev); + + if (enable) + ret = gc08a3_start_streaming(gc08a3); + else + ret = gc08a3_stop_streaming(gc08a3); + + v4l2_subdev_unlock_state(state); + + return ret; +} + +static const struct v4l2_subdev_video_ops gc08a3_video_ops = { + .s_stream = gc08a3_s_stream, +}; + +static const struct v4l2_subdev_pad_ops gc08a3_subdev_pad_ops = { + .enum_mbus_code = gc08a3_enum_mbus_code, + .enum_frame_size = gc08a3_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = gc08a3_set_format, + .get_selection = gc08a3_get_selection, +}; + +static const struct v4l2_subdev_ops gc08a3_subdev_ops = { + .video = &gc08a3_video_ops, + .pad = &gc08a3_subdev_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops gc08a3_internal_ops = { + .init_state = gc08a3_init_state, +}; + +static int gc08a3_get_regulators(struct device *dev, struct gc08a3 *gc08a3) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(gc08a3_supply_name); i++) + gc08a3->supplies[i].supply = gc08a3_supply_name[i]; + + return devm_regulator_bulk_get(dev, ARRAY_SIZE(gc08a3_supply_name), + gc08a3->supplies); +} + +static int gc08a3_parse_fwnode(struct gc08a3 *gc08a3) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + int ret; + struct device *dev = gc08a3->dev; + + endpoint = + fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); + if (ret) { + dev_err(dev, "parsing endpoint node failed\n"); + goto done; + } + + ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, + gc08a3_link_freq_menu_items, + ARRAY_SIZE(gc08a3_link_freq_menu_items), + &gc08a3->link_freq_bitmap); + if (ret) + goto done; + +done: + v4l2_fwnode_endpoint_free(&bus_cfg); + fwnode_handle_put(endpoint); + return ret; +} + +static u64 gc08a3_to_pixel_rate(u32 f_index) +{ + u64 pixel_rate = + gc08a3_link_freq_menu_items[f_index] * 2 * GC08A3_DATA_LANES; + + return div_u64(pixel_rate, GC08A3_RGB_DEPTH); +} + +static int gc08a3_init_controls(struct gc08a3 *gc08a3) +{ + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); + const struct gc08a3_mode *mode = &gc08a3_modes[0]; + const struct v4l2_ctrl_ops *ops = &gc08a3_ctrl_ops; + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max, h_blank; + int ret; + + ctrl_hdlr = &gc08a3->ctrls; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 9); + if (ret) + return ret; + + gc08a3->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + gc08a3->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_cluster(2, &gc08a3->hflip); + + gc08a3->link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, + &gc08a3_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(gc08a3_link_freq_menu_items) - 1, + 0, + gc08a3_link_freq_menu_items); + if (gc08a3->link_freq) + gc08a3->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + gc08a3->pixel_rate = + v4l2_ctrl_new_std(ctrl_hdlr, + &gc08a3_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, + gc08a3_to_pixel_rate(0), + 1, + gc08a3_to_pixel_rate(0)); + + gc08a3->vblank = + v4l2_ctrl_new_std(ctrl_hdlr, + &gc08a3_ctrl_ops, V4L2_CID_VBLANK, + mode->vts_min - mode->height, + GC08A3_VTS_MAX - mode->height, 1, + mode->vts_def - mode->height); + + h_blank = mode->hts - mode->width; + gc08a3->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, + V4L2_CID_HBLANK, h_blank, h_blank, 1, + h_blank); + if (gc08a3->hblank) + gc08a3->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, GC08A3_AGAIN_MIN, + GC08A3_AGAIN_MAX, GC08A3_AGAIN_STEP, + GC08A3_AGAIN_MIN); + + exposure_max = mode->vts_def - GC08A3_EXP_MARGIN; + gc08a3->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, + V4L2_CID_EXPOSURE, GC08A3_EXP_MIN, + exposure_max, GC08A3_EXP_STEP, + exposure_max); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &gc08a3_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(gc08a3_test_pattern_menu) - 1, + 0, 0, gc08a3_test_pattern_menu); + + /* register properties to fwnode (e.g. rotation, orientation) */ + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error_ctrls; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, ops, &props); + if (ret) + goto error_ctrls; + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + goto error_ctrls; + } + + gc08a3->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error_ctrls: + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; +} + +static int gc08a3_identify_module(struct gc08a3 *gc08a3) +{ + u64 val; + int ret; + + ret = cci_read(gc08a3->regmap, GC08A3_REG_CHIP_ID, &val, NULL); + if (ret) { + dev_err(gc08a3->dev, "failed to read chip id"); + return ret; + } + + if (val != GC08A3_CHIP_ID) { + dev_err(gc08a3->dev, "chip id mismatch: 0x%x!=0x%llx", + GC08A3_CHIP_ID, val); + return -ENXIO; + } + + return 0; +} + +static int gc08a3_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct gc08a3 *gc08a3; + int ret; + + gc08a3 = devm_kzalloc(dev, sizeof(*gc08a3), GFP_KERNEL); + if (!gc08a3) + return -ENOMEM; + + gc08a3->dev = dev; + + ret = gc08a3_parse_fwnode(gc08a3); + if (ret) + return ret; + + gc08a3->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(gc08a3->regmap)) + return dev_err_probe(dev, PTR_ERR(gc08a3->regmap), + "failed to init CCI\n"); + + gc08a3->xclk = devm_v4l2_sensor_clk_get_legacy(dev, NULL, true, + GC08A3_DEFAULT_CLK_FREQ); + if (IS_ERR(gc08a3->xclk)) + return dev_err_probe(dev, PTR_ERR(gc08a3->xclk), + "failed to get xclk\n"); + + ret = gc08a3_get_regulators(dev, gc08a3); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to get regulators\n"); + + gc08a3->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(gc08a3->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(gc08a3->reset_gpio), + "failed to get gpio\n"); + + v4l2_i2c_subdev_init(&gc08a3->sd, client, &gc08a3_subdev_ops); + gc08a3->sd.internal_ops = &gc08a3_internal_ops; + gc08a3->cur_mode = &gc08a3_modes[0]; + + ret = gc08a3_power_on(gc08a3->dev); + if (ret) + return dev_err_probe(dev, ret, + "failed to sensor power on\n"); + + ret = gc08a3_identify_module(gc08a3); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d\n", ret); + goto err_power_off; + } + + ret = gc08a3_init_controls(gc08a3); + if (ret) { + dev_err(&client->dev, "failed to init controls: %d", ret); + goto err_power_off; + } + + gc08a3->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + gc08a3->pad.flags = MEDIA_PAD_FL_SOURCE; + gc08a3->sd.dev = &client->dev; + gc08a3->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + ret = media_entity_pads_init(&gc08a3->sd.entity, 1, &gc08a3->pad); + if (ret < 0) { + dev_err(dev, "could not register media entity\n"); + goto err_v4l2_ctrl_handler_free; + } + + gc08a3->sd.state_lock = gc08a3->ctrls.lock; + ret = v4l2_subdev_init_finalize(&gc08a3->sd); + if (ret < 0) { + dev_err(dev, "v4l2 subdev init error: %d\n", ret); + goto err_media_entity_cleanup; + } + + pm_runtime_set_active(gc08a3->dev); + pm_runtime_enable(gc08a3->dev); + pm_runtime_set_autosuspend_delay(gc08a3->dev, 1000); + pm_runtime_use_autosuspend(gc08a3->dev); + pm_runtime_idle(gc08a3->dev); + + ret = v4l2_async_register_subdev_sensor(&gc08a3->sd); + if (ret < 0) { + dev_err(dev, "could not register v4l2 device\n"); + goto err_rpm; + } + + return 0; + +err_rpm: + pm_runtime_disable(gc08a3->dev); + v4l2_subdev_cleanup(&gc08a3->sd); + +err_media_entity_cleanup: + media_entity_cleanup(&gc08a3->sd.entity); + +err_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(gc08a3->sd.ctrl_handler); + +err_power_off: + gc08a3_power_off(gc08a3->dev); + + return ret; +} + +static void gc08a3_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct gc08a3 *gc08a3 = to_gc08a3(sd); + + v4l2_async_unregister_subdev(&gc08a3->sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&gc08a3->sd.entity); + v4l2_ctrl_handler_free(&gc08a3->ctrls); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + gc08a3_power_off(gc08a3->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id gc08a3_of_match[] = { + { .compatible = "galaxycore,gc08a3" }, + {} +}; +MODULE_DEVICE_TABLE(of, gc08a3_of_match); + +static DEFINE_RUNTIME_DEV_PM_OPS(gc08a3_pm_ops, + gc08a3_power_off, + gc08a3_power_on, + NULL); + +static struct i2c_driver gc08a3_i2c_driver = { + .driver = { + .of_match_table = gc08a3_of_match, + .pm = pm_ptr(&gc08a3_pm_ops), + .name = "gc08a3", + }, + .probe = gc08a3_probe, + .remove = gc08a3_remove, +}; +module_i2c_driver(gc08a3_i2c_driver); + +MODULE_DESCRIPTION("GalaxyCore gc08a3 Camera driver"); +MODULE_AUTHOR("Zhi Mao "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/gc2145.c b/drivers/media/i2c/gc2145.c new file mode 100644 index 000000000..b215963a2 --- /dev/null +++ b/drivers/media/i2c/gc2145.c @@ -0,0 +1,1484 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Galaxycore GC2145 camera. + * Copyright (C) 2023, STMicroelectronics SA + * + * Inspired by the imx219.c driver + * + * Datasheet v1.0 available at http://files.pine64.org/doc/datasheet/PinebookPro/GC2145%20CSP%20DataSheet%20release%20V1.0_20131201.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* Chip ID */ +#define GC2145_CHIP_ID 0x2145 + +/* Page 0 */ +#define GC2145_REG_EXPOSURE CCI_REG16(0x03) +#define GC2145_REG_HBLANK CCI_REG16(0x05) +#define GC2145_REG_VBLANK CCI_REG16(0x07) +#define GC2145_REG_ROW_START CCI_REG16(0x09) +#define GC2145_REG_COL_START CCI_REG16(0x0b) +#define GC2145_REG_WIN_HEIGHT CCI_REG16(0x0d) +#define GC2145_REG_WIN_WIDTH CCI_REG16(0x0f) +#define GC2145_REG_ANALOG_MODE1 CCI_REG8(0x17) +#define GC2145_REG_OUTPUT_FMT CCI_REG8(0x84) +#define GC2145_REG_SYNC_MODE CCI_REG8(0x86) +#define GC2145_SYNC_MODE_COL_SWITCH BIT(4) +#define GC2145_SYNC_MODE_ROW_SWITCH BIT(5) +#define GC2145_REG_BYPASS_MODE CCI_REG8(0x89) +#define GC2145_BYPASS_MODE_SWITCH BIT(5) +#define GC2145_REG_DEBUG_MODE2 CCI_REG8(0x8c) +#define GC2145_REG_DEBUG_MODE3 CCI_REG8(0x8d) +#define GC2145_REG_CROP_ENABLE CCI_REG8(0x90) +#define GC2145_REG_CROP_Y CCI_REG16(0x91) +#define GC2145_REG_CROP_X CCI_REG16(0x93) +#define GC2145_REG_CROP_HEIGHT CCI_REG16(0x95) +#define GC2145_REG_CROP_WIDTH CCI_REG16(0x97) +#define GC2145_REG_GLOBAL_GAIN CCI_REG8(0xb0) +#define GC2145_REG_CHIP_ID CCI_REG16(0xf0) +#define GC2145_REG_PAD_IO CCI_REG8(0xf2) +#define GC2145_REG_PAGE_SELECT CCI_REG8(0xfe) +/* Page 3 */ +#define GC2145_REG_DPHY_ANALOG_MODE1 CCI_REG8(0x01) +#define GC2145_DPHY_MODE_PHY_CLK_EN BIT(0) +#define GC2145_DPHY_MODE_PHY_LANE0_EN BIT(1) +#define GC2145_DPHY_MODE_PHY_LANE1_EN BIT(2) +#define GC2145_DPHY_MODE_PHY_CLK_LANE_P2S_SEL BIT(7) +#define GC2145_REG_DPHY_ANALOG_MODE2 CCI_REG8(0x02) +#define GC2145_DPHY_CLK_DIFF(a) ((a) & 0x07) +#define GC2145_DPHY_LANE0_DIFF(a) (((a) & 0x07) << 4) +#define GC2145_REG_DPHY_ANALOG_MODE3 CCI_REG8(0x03) +#define GC2145_DPHY_LANE1_DIFF(a) ((a) & 0x07) +#define GC2145_DPHY_CLK_DELAY BIT(4) +#define GC2145_DPHY_LANE0_DELAY BIT(5) +#define GC2145_DPHY_LANE1_DELAY BIT(6) +#define GC2145_REG_FIFO_FULL_LVL CCI_REG16_LE(0x04) +#define GC2145_REG_FIFO_MODE CCI_REG8(0x06) +#define GC2145_FIFO_MODE_READ_GATE BIT(3) +#define GC2145_FIFO_MODE_MIPI_CLK_MODULE BIT(7) +#define GC2145_REG_BUF_CSI2_MODE CCI_REG8(0x10) +#define GC2145_CSI2_MODE_DOUBLE BIT(0) +#define GC2145_CSI2_MODE_RAW8 BIT(2) +#define GC2145_CSI2_MODE_MIPI_EN BIT(4) +#define GC2145_CSI2_MODE_EN BIT(7) +#define GC2145_REG_MIPI_DT CCI_REG8(0x11) +#define GC2145_REG_LWC CCI_REG16_LE(0x12) +#define GC2145_REG_DPHY_MODE CCI_REG8(0x15) +#define GC2145_DPHY_MODE_TRIGGER_PROG BIT(4) +#define GC2145_REG_FIFO_GATE_MODE CCI_REG8(0x17) +#define GC2145_REG_T_LPX CCI_REG8(0x21) +#define GC2145_REG_T_CLK_HS_PREPARE CCI_REG8(0x22) +#define GC2145_REG_T_CLK_ZERO CCI_REG8(0x23) +#define GC2145_REG_T_CLK_PRE CCI_REG8(0x24) +#define GC2145_REG_T_CLK_POST CCI_REG8(0x25) +#define GC2145_REG_T_CLK_TRAIL CCI_REG8(0x26) +#define GC2145_REG_T_HS_EXIT CCI_REG8(0x27) +#define GC2145_REG_T_WAKEUP CCI_REG8(0x28) +#define GC2145_REG_T_HS_PREPARE CCI_REG8(0x29) +#define GC2145_REG_T_HS_ZERO CCI_REG8(0x2a) +#define GC2145_REG_T_HS_TRAIL CCI_REG8(0x2b) + +/* External clock frequency is 24.0MHz */ +#define GC2145_XCLK_FREQ (24 * HZ_PER_MHZ) + +#define GC2145_NATIVE_WIDTH 1616U +#define GC2145_NATIVE_HEIGHT 1232U + +/** + * struct gc2145_mode - GC2145 mode description + * @width: frame width (in pixels) + * @height: frame height (in pixels) + * @reg_seq: registers config sequence to enter into the mode + * @reg_seq_size: size of the sequence + * @pixel_rate: pixel rate associated with the mode + * @crop: window area captured + * @hblank: default horizontal blanking + * @vblank: default vertical blanking + * @link_freq_index: index within the link frequency menu + */ +struct gc2145_mode { + unsigned int width; + unsigned int height; + const struct cci_reg_sequence *reg_seq; + size_t reg_seq_size; + unsigned long pixel_rate; + struct v4l2_rect crop; + unsigned int hblank; + unsigned int vblank; + unsigned int link_freq_index; +}; + +#define GC2145_DEFAULT_EXPOSURE 0x04e2 +#define GC2145_DEFAULT_GLOBAL_GAIN 0x55 +static const struct cci_reg_sequence gc2145_common_regs[] = { + {GC2145_REG_PAGE_SELECT, 0x00}, + /* SH Delay */ + {CCI_REG8(0x12), 0x2e}, + /* Flip */ + {GC2145_REG_ANALOG_MODE1, 0x14}, + /* Analog Conf */ + {CCI_REG8(0x18), 0x22}, {CCI_REG8(0x19), 0x0e}, {CCI_REG8(0x1a), 0x01}, + {CCI_REG8(0x1b), 0x4b}, {CCI_REG8(0x1c), 0x07}, {CCI_REG8(0x1d), 0x10}, + {CCI_REG8(0x1e), 0x88}, {CCI_REG8(0x1f), 0x78}, {CCI_REG8(0x20), 0x03}, + {CCI_REG8(0x21), 0x40}, {CCI_REG8(0x22), 0xa0}, {CCI_REG8(0x24), 0x16}, + {CCI_REG8(0x25), 0x01}, {CCI_REG8(0x26), 0x10}, {CCI_REG8(0x2d), 0x60}, + {CCI_REG8(0x30), 0x01}, {CCI_REG8(0x31), 0x90}, {CCI_REG8(0x33), 0x06}, + {CCI_REG8(0x34), 0x01}, + /* ISP related */ + {CCI_REG8(0x80), 0x7f}, {CCI_REG8(0x81), 0x26}, {CCI_REG8(0x82), 0xfa}, + {CCI_REG8(0x83), 0x00}, {CCI_REG8(0x84), 0x02}, {CCI_REG8(0x86), 0x02}, + {CCI_REG8(0x88), 0x03}, + {GC2145_REG_BYPASS_MODE, 0x03}, + {CCI_REG8(0x85), 0x08}, {CCI_REG8(0x8a), 0x00}, {CCI_REG8(0x8b), 0x00}, + {GC2145_REG_GLOBAL_GAIN, GC2145_DEFAULT_GLOBAL_GAIN}, + {CCI_REG8(0xc3), 0x00}, {CCI_REG8(0xc4), 0x80}, {CCI_REG8(0xc5), 0x90}, + {CCI_REG8(0xc6), 0x3b}, {CCI_REG8(0xc7), 0x46}, + /* BLK */ + {GC2145_REG_PAGE_SELECT, 0x00}, + {CCI_REG8(0x40), 0x42}, {CCI_REG8(0x41), 0x00}, {CCI_REG8(0x43), 0x5b}, + {CCI_REG8(0x5e), 0x00}, {CCI_REG8(0x5f), 0x00}, {CCI_REG8(0x60), 0x00}, + {CCI_REG8(0x61), 0x00}, {CCI_REG8(0x62), 0x00}, {CCI_REG8(0x63), 0x00}, + {CCI_REG8(0x64), 0x00}, {CCI_REG8(0x65), 0x00}, {CCI_REG8(0x66), 0x20}, + {CCI_REG8(0x67), 0x20}, {CCI_REG8(0x68), 0x20}, {CCI_REG8(0x69), 0x20}, + {CCI_REG8(0x76), 0x00}, {CCI_REG8(0x6a), 0x08}, {CCI_REG8(0x6b), 0x08}, + {CCI_REG8(0x6c), 0x08}, {CCI_REG8(0x6d), 0x08}, {CCI_REG8(0x6e), 0x08}, + {CCI_REG8(0x6f), 0x08}, {CCI_REG8(0x70), 0x08}, {CCI_REG8(0x71), 0x08}, + {CCI_REG8(0x76), 0x00}, {CCI_REG8(0x72), 0xf0}, {CCI_REG8(0x7e), 0x3c}, + {CCI_REG8(0x7f), 0x00}, + {GC2145_REG_PAGE_SELECT, 0x02}, + {CCI_REG8(0x48), 0x15}, {CCI_REG8(0x49), 0x00}, {CCI_REG8(0x4b), 0x0b}, + /* AEC */ + {GC2145_REG_PAGE_SELECT, 0x00}, + {GC2145_REG_EXPOSURE, GC2145_DEFAULT_EXPOSURE}, + {GC2145_REG_PAGE_SELECT, 0x01}, + {CCI_REG8(0x01), 0x04}, {CCI_REG8(0x02), 0xc0}, {CCI_REG8(0x03), 0x04}, + {CCI_REG8(0x04), 0x90}, {CCI_REG8(0x05), 0x30}, {CCI_REG8(0x06), 0x90}, + {CCI_REG8(0x07), 0x30}, {CCI_REG8(0x08), 0x80}, {CCI_REG8(0x09), 0x00}, + {CCI_REG8(0x0a), 0x82}, {CCI_REG8(0x0b), 0x11}, {CCI_REG8(0x0c), 0x10}, + {CCI_REG8(0x11), 0x10}, {CCI_REG8(0x13), 0x7b}, {CCI_REG8(0x17), 0x00}, + {CCI_REG8(0x1c), 0x11}, {CCI_REG8(0x1e), 0x61}, {CCI_REG8(0x1f), 0x35}, + {CCI_REG8(0x20), 0x40}, {CCI_REG8(0x22), 0x40}, {CCI_REG8(0x23), 0x20}, + {GC2145_REG_PAGE_SELECT, 0x02}, + {CCI_REG8(0x0f), 0x04}, + {GC2145_REG_PAGE_SELECT, 0x01}, + {CCI_REG8(0x12), 0x35}, {CCI_REG8(0x15), 0xb0}, {CCI_REG8(0x10), 0x31}, + {CCI_REG8(0x3e), 0x28}, {CCI_REG8(0x3f), 0xb0}, {CCI_REG8(0x40), 0x90}, + {CCI_REG8(0x41), 0x0f}, + /* INTPEE */ + {GC2145_REG_PAGE_SELECT, 0x02}, + {CCI_REG8(0x90), 0x6c}, {CCI_REG8(0x91), 0x03}, {CCI_REG8(0x92), 0xcb}, + {CCI_REG8(0x94), 0x33}, {CCI_REG8(0x95), 0x84}, {CCI_REG8(0x97), 0x65}, + {CCI_REG8(0xa2), 0x11}, + /* DNDD */ + {GC2145_REG_PAGE_SELECT, 0x02}, + {CCI_REG8(0x80), 0xc1}, {CCI_REG8(0x81), 0x08}, {CCI_REG8(0x82), 0x05}, + {CCI_REG8(0x83), 0x08}, {CCI_REG8(0x84), 0x0a}, {CCI_REG8(0x86), 0xf0}, + {CCI_REG8(0x87), 0x50}, {CCI_REG8(0x88), 0x15}, {CCI_REG8(0x89), 0xb0}, + {CCI_REG8(0x8a), 0x30}, {CCI_REG8(0x8b), 0x10}, + /* ASDE */ + {GC2145_REG_PAGE_SELECT, 0x01}, + {CCI_REG8(0x21), 0x04}, + {GC2145_REG_PAGE_SELECT, 0x02}, + {CCI_REG8(0xa3), 0x50}, {CCI_REG8(0xa4), 0x20}, {CCI_REG8(0xa5), 0x40}, + {CCI_REG8(0xa6), 0x80}, {CCI_REG8(0xab), 0x40}, {CCI_REG8(0xae), 0x0c}, + {CCI_REG8(0xb3), 0x46}, {CCI_REG8(0xb4), 0x64}, {CCI_REG8(0xb6), 0x38}, + {CCI_REG8(0xb7), 0x01}, {CCI_REG8(0xb9), 0x2b}, {CCI_REG8(0x3c), 0x04}, + {CCI_REG8(0x3d), 0x15}, {CCI_REG8(0x4b), 0x06}, {CCI_REG8(0x4c), 0x20}, + /* Gamma */ + {GC2145_REG_PAGE_SELECT, 0x02}, + {CCI_REG8(0x10), 0x09}, {CCI_REG8(0x11), 0x0d}, {CCI_REG8(0x12), 0x13}, + {CCI_REG8(0x13), 0x19}, {CCI_REG8(0x14), 0x27}, {CCI_REG8(0x15), 0x37}, + {CCI_REG8(0x16), 0x45}, {CCI_REG8(0x17), 0x53}, {CCI_REG8(0x18), 0x69}, + {CCI_REG8(0x19), 0x7d}, {CCI_REG8(0x1a), 0x8f}, {CCI_REG8(0x1b), 0x9d}, + {CCI_REG8(0x1c), 0xa9}, {CCI_REG8(0x1d), 0xbd}, {CCI_REG8(0x1e), 0xcd}, + {CCI_REG8(0x1f), 0xd9}, {CCI_REG8(0x20), 0xe3}, {CCI_REG8(0x21), 0xea}, + {CCI_REG8(0x22), 0xef}, {CCI_REG8(0x23), 0xf5}, {CCI_REG8(0x24), 0xf9}, + {CCI_REG8(0x25), 0xff}, + {GC2145_REG_PAGE_SELECT, 0x00}, + {CCI_REG8(0xc6), 0x20}, {CCI_REG8(0xc7), 0x2b}, + /* Gamma 2 */ + {GC2145_REG_PAGE_SELECT, 0x02}, + {CCI_REG8(0x26), 0x0f}, {CCI_REG8(0x27), 0x14}, {CCI_REG8(0x28), 0x19}, + {CCI_REG8(0x29), 0x1e}, {CCI_REG8(0x2a), 0x27}, {CCI_REG8(0x2b), 0x33}, + {CCI_REG8(0x2c), 0x3b}, {CCI_REG8(0x2d), 0x45}, {CCI_REG8(0x2e), 0x59}, + {CCI_REG8(0x2f), 0x69}, {CCI_REG8(0x30), 0x7c}, {CCI_REG8(0x31), 0x89}, + {CCI_REG8(0x32), 0x98}, {CCI_REG8(0x33), 0xae}, {CCI_REG8(0x34), 0xc0}, + {CCI_REG8(0x35), 0xcf}, {CCI_REG8(0x36), 0xda}, {CCI_REG8(0x37), 0xe2}, + {CCI_REG8(0x38), 0xe9}, {CCI_REG8(0x39), 0xf3}, {CCI_REG8(0x3a), 0xf9}, + {CCI_REG8(0x3b), 0xff}, + /* YCP */ + {GC2145_REG_PAGE_SELECT, 0x02}, + {CCI_REG8(0xd1), 0x32}, {CCI_REG8(0xd2), 0x32}, {CCI_REG8(0xd3), 0x40}, + {CCI_REG8(0xd6), 0xf0}, {CCI_REG8(0xd7), 0x10}, {CCI_REG8(0xd8), 0xda}, + {CCI_REG8(0xdd), 0x14}, {CCI_REG8(0xde), 0x86}, {CCI_REG8(0xed), 0x80}, + {CCI_REG8(0xee), 0x00}, {CCI_REG8(0xef), 0x3f}, {CCI_REG8(0xd8), 0xd8}, + /* ABS */ + {GC2145_REG_PAGE_SELECT, 0x01}, + {CCI_REG8(0x9f), 0x40}, + /* LSC */ + {GC2145_REG_PAGE_SELECT, 0x01}, + {CCI_REG8(0xc2), 0x14}, {CCI_REG8(0xc3), 0x0d}, {CCI_REG8(0xc4), 0x0c}, + {CCI_REG8(0xc8), 0x15}, {CCI_REG8(0xc9), 0x0d}, {CCI_REG8(0xca), 0x0a}, + {CCI_REG8(0xbc), 0x24}, {CCI_REG8(0xbd), 0x10}, {CCI_REG8(0xbe), 0x0b}, + {CCI_REG8(0xb6), 0x25}, {CCI_REG8(0xb7), 0x16}, {CCI_REG8(0xb8), 0x15}, + {CCI_REG8(0xc5), 0x00}, {CCI_REG8(0xc6), 0x00}, {CCI_REG8(0xc7), 0x00}, + {CCI_REG8(0xcb), 0x00}, {CCI_REG8(0xcc), 0x00}, {CCI_REG8(0xcd), 0x00}, + {CCI_REG8(0xbf), 0x07}, {CCI_REG8(0xc0), 0x00}, {CCI_REG8(0xc1), 0x00}, + {CCI_REG8(0xb9), 0x00}, {CCI_REG8(0xba), 0x00}, {CCI_REG8(0xbb), 0x00}, + {CCI_REG8(0xaa), 0x01}, {CCI_REG8(0xab), 0x01}, {CCI_REG8(0xac), 0x00}, + {CCI_REG8(0xad), 0x05}, {CCI_REG8(0xae), 0x06}, {CCI_REG8(0xaf), 0x0e}, + {CCI_REG8(0xb0), 0x0b}, {CCI_REG8(0xb1), 0x07}, {CCI_REG8(0xb2), 0x06}, + {CCI_REG8(0xb3), 0x17}, {CCI_REG8(0xb4), 0x0e}, {CCI_REG8(0xb5), 0x0e}, + {CCI_REG8(0xd0), 0x09}, {CCI_REG8(0xd1), 0x00}, {CCI_REG8(0xd2), 0x00}, + {CCI_REG8(0xd6), 0x08}, {CCI_REG8(0xd7), 0x00}, {CCI_REG8(0xd8), 0x00}, + {CCI_REG8(0xd9), 0x00}, {CCI_REG8(0xda), 0x00}, {CCI_REG8(0xdb), 0x00}, + {CCI_REG8(0xd3), 0x0a}, {CCI_REG8(0xd4), 0x00}, {CCI_REG8(0xd5), 0x00}, + {CCI_REG8(0xa4), 0x00}, {CCI_REG8(0xa5), 0x00}, {CCI_REG8(0xa6), 0x77}, + {CCI_REG8(0xa7), 0x77}, {CCI_REG8(0xa8), 0x77}, {CCI_REG8(0xa9), 0x77}, + {CCI_REG8(0xa1), 0x80}, {CCI_REG8(0xa2), 0x80}, + {GC2145_REG_PAGE_SELECT, 0x01}, + {CCI_REG8(0xdf), 0x0d}, {CCI_REG8(0xdc), 0x25}, {CCI_REG8(0xdd), 0x30}, + {CCI_REG8(0xe0), 0x77}, {CCI_REG8(0xe1), 0x80}, {CCI_REG8(0xe2), 0x77}, + {CCI_REG8(0xe3), 0x90}, {CCI_REG8(0xe6), 0x90}, {CCI_REG8(0xe7), 0xa0}, + {CCI_REG8(0xe8), 0x90}, {CCI_REG8(0xe9), 0xa0}, + /* AWB */ + /* measure window */ + {GC2145_REG_PAGE_SELECT, 0x00}, + {CCI_REG8(0xec), 0x06}, {CCI_REG8(0xed), 0x04}, {CCI_REG8(0xee), 0x60}, + {CCI_REG8(0xef), 0x90}, {CCI_REG8(0xb6), 0x01}, + {GC2145_REG_PAGE_SELECT, 0x01}, + {CCI_REG8(0x4f), 0x00}, {CCI_REG8(0x4f), 0x00}, {CCI_REG8(0x4b), 0x01}, + {CCI_REG8(0x4f), 0x00}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x71}, {CCI_REG8(0x4e), 0x01}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x91}, {CCI_REG8(0x4e), 0x01}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x70}, {CCI_REG8(0x4e), 0x01}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x90}, {CCI_REG8(0x4e), 0x02}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xb0}, {CCI_REG8(0x4e), 0x02}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x8f}, {CCI_REG8(0x4e), 0x02}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x6f}, {CCI_REG8(0x4e), 0x02}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xaf}, {CCI_REG8(0x4e), 0x02}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xd0}, {CCI_REG8(0x4e), 0x02}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xf0}, {CCI_REG8(0x4e), 0x02}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xcf}, {CCI_REG8(0x4e), 0x02}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xef}, {CCI_REG8(0x4e), 0x02}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x6e}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x8e}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xae}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xce}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x4d}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x6d}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x8d}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xad}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xcd}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x4c}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x6c}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x8c}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xac}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xcc}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xcb}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x4b}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x6b}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x8b}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xab}, {CCI_REG8(0x4e), 0x03}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x8a}, {CCI_REG8(0x4e), 0x04}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xaa}, {CCI_REG8(0x4e), 0x04}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xca}, {CCI_REG8(0x4e), 0x04}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xca}, {CCI_REG8(0x4e), 0x04}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xc9}, {CCI_REG8(0x4e), 0x04}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x8a}, {CCI_REG8(0x4e), 0x04}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0x89}, {CCI_REG8(0x4e), 0x04}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xa9}, {CCI_REG8(0x4e), 0x04}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0x0b}, {CCI_REG8(0x4e), 0x05}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0x0a}, {CCI_REG8(0x4e), 0x05}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xeb}, {CCI_REG8(0x4e), 0x05}, + {CCI_REG8(0x4c), 0x01}, {CCI_REG8(0x4d), 0xea}, {CCI_REG8(0x4e), 0x05}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0x09}, {CCI_REG8(0x4e), 0x05}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0x29}, {CCI_REG8(0x4e), 0x05}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0x2a}, {CCI_REG8(0x4e), 0x05}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0x4a}, {CCI_REG8(0x4e), 0x05}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0x8a}, {CCI_REG8(0x4e), 0x06}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0x49}, {CCI_REG8(0x4e), 0x06}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0x69}, {CCI_REG8(0x4e), 0x06}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0x89}, {CCI_REG8(0x4e), 0x06}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0xa9}, {CCI_REG8(0x4e), 0x06}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0x48}, {CCI_REG8(0x4e), 0x06}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0x68}, {CCI_REG8(0x4e), 0x06}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0x69}, {CCI_REG8(0x4e), 0x06}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0xca}, {CCI_REG8(0x4e), 0x07}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0xc9}, {CCI_REG8(0x4e), 0x07}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0xe9}, {CCI_REG8(0x4e), 0x07}, + {CCI_REG8(0x4c), 0x03}, {CCI_REG8(0x4d), 0x09}, {CCI_REG8(0x4e), 0x07}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0xc8}, {CCI_REG8(0x4e), 0x07}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0xe8}, {CCI_REG8(0x4e), 0x07}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0xa7}, {CCI_REG8(0x4e), 0x07}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0xc7}, {CCI_REG8(0x4e), 0x07}, + {CCI_REG8(0x4c), 0x02}, {CCI_REG8(0x4d), 0xe7}, {CCI_REG8(0x4e), 0x07}, + {CCI_REG8(0x4c), 0x03}, {CCI_REG8(0x4d), 0x07}, {CCI_REG8(0x4e), 0x07}, + {CCI_REG8(0x4f), 0x01}, + {CCI_REG8(0x50), 0x80}, {CCI_REG8(0x51), 0xa8}, {CCI_REG8(0x52), 0x47}, + {CCI_REG8(0x53), 0x38}, {CCI_REG8(0x54), 0xc7}, {CCI_REG8(0x56), 0x0e}, + {CCI_REG8(0x58), 0x08}, {CCI_REG8(0x5b), 0x00}, {CCI_REG8(0x5c), 0x74}, + {CCI_REG8(0x5d), 0x8b}, {CCI_REG8(0x61), 0xdb}, {CCI_REG8(0x62), 0xb8}, + {CCI_REG8(0x63), 0x86}, {CCI_REG8(0x64), 0xc0}, {CCI_REG8(0x65), 0x04}, + {CCI_REG8(0x67), 0xa8}, {CCI_REG8(0x68), 0xb0}, {CCI_REG8(0x69), 0x00}, + {CCI_REG8(0x6a), 0xa8}, {CCI_REG8(0x6b), 0xb0}, {CCI_REG8(0x6c), 0xaf}, + {CCI_REG8(0x6d), 0x8b}, {CCI_REG8(0x6e), 0x50}, {CCI_REG8(0x6f), 0x18}, + {CCI_REG8(0x73), 0xf0}, {CCI_REG8(0x70), 0x0d}, {CCI_REG8(0x71), 0x60}, + {CCI_REG8(0x72), 0x80}, {CCI_REG8(0x74), 0x01}, {CCI_REG8(0x75), 0x01}, + {CCI_REG8(0x7f), 0x0c}, {CCI_REG8(0x76), 0x70}, {CCI_REG8(0x77), 0x58}, + {CCI_REG8(0x78), 0xa0}, {CCI_REG8(0x79), 0x5e}, {CCI_REG8(0x7a), 0x54}, + {CCI_REG8(0x7b), 0x58}, + /* CC */ + {GC2145_REG_PAGE_SELECT, 0x02}, + {CCI_REG8(0xc0), 0x01}, {CCI_REG8(0xc1), 0x44}, {CCI_REG8(0xc2), 0xfd}, + {CCI_REG8(0xc3), 0x04}, {CCI_REG8(0xc4), 0xf0}, {CCI_REG8(0xc5), 0x48}, + {CCI_REG8(0xc6), 0xfd}, {CCI_REG8(0xc7), 0x46}, {CCI_REG8(0xc8), 0xfd}, + {CCI_REG8(0xc9), 0x02}, {CCI_REG8(0xca), 0xe0}, {CCI_REG8(0xcb), 0x45}, + {CCI_REG8(0xcc), 0xec}, {CCI_REG8(0xcd), 0x48}, {CCI_REG8(0xce), 0xf0}, + {CCI_REG8(0xcf), 0xf0}, {CCI_REG8(0xe3), 0x0c}, {CCI_REG8(0xe4), 0x4b}, + {CCI_REG8(0xe5), 0xe0}, + /* ABS */ + {GC2145_REG_PAGE_SELECT, 0x01}, + {CCI_REG8(0x9f), 0x40}, + /* Dark sun */ + {GC2145_REG_PAGE_SELECT, 0x02}, + {CCI_REG8(0x40), 0xbf}, {CCI_REG8(0x46), 0xcf}, +}; + +#define GC2145_640_480_PIXELRATE 30000000 +#define GC2145_640_480_LINKFREQ 120000000 +#define GC2145_640_480_HBLANK 0x0130 +#define GC2145_640_480_VBLANK 0x000c +static const struct cci_reg_sequence gc2145_mode_640_480_regs[] = { + {GC2145_REG_PAGE_SELECT, 0xf0}, {GC2145_REG_PAGE_SELECT, 0xf0}, + {GC2145_REG_PAGE_SELECT, 0xf0}, {CCI_REG8(0xfc), 0x06}, + {CCI_REG8(0xf6), 0x00}, {CCI_REG8(0xf7), 0x1d}, {CCI_REG8(0xf8), 0x86}, + {CCI_REG8(0xfa), 0x00}, {CCI_REG8(0xf9), 0x8e}, + /* Disable PAD IO */ + {GC2145_REG_PAD_IO, 0x00}, + {GC2145_REG_PAGE_SELECT, 0x00}, + /* Row/Col start - 0/0 */ + {GC2145_REG_ROW_START, 0x0000}, + {GC2145_REG_COL_START, 0x0000}, + /* Window size 1216/1618 */ + {GC2145_REG_WIN_HEIGHT, 0x04c0}, + {GC2145_REG_WIN_WIDTH, 0x0652}, + /* Scalar more */ + {CCI_REG8(0xfd), 0x01}, {CCI_REG8(0xfa), 0x00}, + /* Crop 640-480@0-0 */ + {GC2145_REG_CROP_ENABLE, 0x01}, + {GC2145_REG_CROP_Y, 0x0000}, + {GC2145_REG_CROP_X, 0x0000}, + {GC2145_REG_CROP_HEIGHT, 0x01e0}, + {GC2145_REG_CROP_WIDTH, 0x0280}, + /* Subsampling configuration */ + {CCI_REG8(0x99), 0x55}, {CCI_REG8(0x9a), 0x06}, {CCI_REG8(0x9b), 0x01}, + {CCI_REG8(0x9c), 0x23}, {CCI_REG8(0x9d), 0x00}, {CCI_REG8(0x9e), 0x00}, + {CCI_REG8(0x9f), 0x01}, {CCI_REG8(0xa0), 0x23}, {CCI_REG8(0xa1), 0x00}, + {CCI_REG8(0xa2), 0x00}, + {GC2145_REG_PAGE_SELECT, 0x01}, + /* AEC anti-flicker */ + {CCI_REG16(0x25), 0x0175}, + /* AEC exposure level 1-5 */ + {CCI_REG16(0x27), 0x045f}, {CCI_REG16(0x29), 0x045f}, + {CCI_REG16(0x2b), 0x045f}, {CCI_REG16(0x2d), 0x045f}, +}; + +#define GC2145_1280_720_PIXELRATE 48000000 +#define GC2145_1280_720_LINKFREQ 192000000 +#define GC2145_1280_720_HBLANK 0x0156 +#define GC2145_1280_720_VBLANK 0x0011 +static const struct cci_reg_sequence gc2145_mode_1280_720_regs[] = { + {GC2145_REG_PAGE_SELECT, 0xf0}, {GC2145_REG_PAGE_SELECT, 0xf0}, + {GC2145_REG_PAGE_SELECT, 0xf0}, {CCI_REG8(0xfc), 0x06}, + {CCI_REG8(0xf6), 0x00}, {CCI_REG8(0xf7), 0x1d}, {CCI_REG8(0xf8), 0x83}, + {CCI_REG8(0xfa), 0x00}, {CCI_REG8(0xf9), 0x8e}, + /* Disable PAD IO */ + {GC2145_REG_PAD_IO, 0x00}, + {GC2145_REG_PAGE_SELECT, 0x00}, + /* Row/Col start - 240/160 */ + {GC2145_REG_ROW_START, 0x00f0}, + {GC2145_REG_COL_START, 0x00a0}, + /* Window size 736/1296 */ + {GC2145_REG_WIN_HEIGHT, 0x02e0}, + {GC2145_REG_WIN_WIDTH, 0x0510}, + /* Crop 1280-720@0-0 */ + {GC2145_REG_CROP_ENABLE, 0x01}, + {GC2145_REG_CROP_Y, 0x0000}, + {GC2145_REG_CROP_X, 0x0000}, + {GC2145_REG_CROP_HEIGHT, 0x02d0}, + {GC2145_REG_CROP_WIDTH, 0x0500}, + {GC2145_REG_PAGE_SELECT, 0x01}, + /* AEC anti-flicker */ + {CCI_REG16(0x25), 0x00e6}, + /* AEC exposure level 1-5 */ + {CCI_REG16(0x27), 0x02b2}, {CCI_REG16(0x29), 0x02b2}, + {CCI_REG16(0x2b), 0x02b2}, {CCI_REG16(0x2d), 0x02b2}, +}; + +#define GC2145_1600_1200_PIXELRATE 60000000 +#define GC2145_1600_1200_LINKFREQ 240000000 +#define GC2145_1600_1200_HBLANK 0x0156 +#define GC2145_1600_1200_VBLANK 0x0010 +static const struct cci_reg_sequence gc2145_mode_1600_1200_regs[] = { + {GC2145_REG_PAGE_SELECT, 0xf0}, {GC2145_REG_PAGE_SELECT, 0xf0}, + {GC2145_REG_PAGE_SELECT, 0xf0}, {CCI_REG8(0xfc), 0x06}, + {CCI_REG8(0xf6), 0x00}, {CCI_REG8(0xf7), 0x1d}, {CCI_REG8(0xf8), 0x84}, + {CCI_REG8(0xfa), 0x00}, {CCI_REG8(0xf9), 0x8e}, + /* Disable PAD IO */ + {GC2145_REG_PAD_IO, 0x00}, + {GC2145_REG_PAGE_SELECT, 0x00}, + /* Row/Col start - 0/0 */ + {GC2145_REG_ROW_START, 0x0000}, + {GC2145_REG_COL_START, 0x0000}, + /* Window size: 1216/1618 */ + {GC2145_REG_WIN_HEIGHT, 0x04c0}, + {GC2145_REG_WIN_WIDTH, 0x0652}, + /* Crop 1600-1200@0-0 */ + {GC2145_REG_CROP_ENABLE, 0x01}, + {GC2145_REG_CROP_Y, 0x0000}, + {GC2145_REG_CROP_X, 0x0000}, + {GC2145_REG_CROP_HEIGHT, 0x04b0}, + {GC2145_REG_CROP_WIDTH, 0x0640}, + {GC2145_REG_PAGE_SELECT, 0x01}, + /* AEC anti-flicker */ + {CCI_REG16(0x25), 0x00fa}, + /* AEC exposure level 1-5 */ + {CCI_REG16(0x27), 0x04e2}, {CCI_REG16(0x29), 0x04e2}, + {CCI_REG16(0x2b), 0x04e2}, {CCI_REG16(0x2d), 0x04e2}, +}; + +static const s64 gc2145_link_freq_menu[] = { + GC2145_640_480_LINKFREQ, + GC2145_1280_720_LINKFREQ, + GC2145_1600_1200_LINKFREQ, +}; + +/* Regulators supplies */ +static const char * const gc2145_supply_name[] = { + "iovdd", /* Digital I/O (1.7-3V) suppply */ + "avdd", /* Analog (2.7-3V) supply */ + "dvdd", /* Digital Core (1.7-1.9V) supply */ +}; + +#define GC2145_NUM_SUPPLIES ARRAY_SIZE(gc2145_supply_name) + +/* Mode configs */ +#define GC2145_MODE_640X480 0 +#define GC2145_MODE_1280X720 1 +#define GC2145_MODE_1600X1200 2 +static const struct gc2145_mode supported_modes[] = { + { + /* 640x480 30fps mode */ + .width = 640, + .height = 480, + .reg_seq = gc2145_mode_640_480_regs, + .reg_seq_size = ARRAY_SIZE(gc2145_mode_640_480_regs), + .pixel_rate = GC2145_640_480_PIXELRATE, + .crop = { + .top = 0, + .left = 0, + .width = 640, + .height = 480, + }, + .hblank = GC2145_640_480_HBLANK, + .vblank = GC2145_640_480_VBLANK, + .link_freq_index = GC2145_MODE_640X480, + }, + { + /* 1280x720 30fps mode */ + .width = 1280, + .height = 720, + .reg_seq = gc2145_mode_1280_720_regs, + .reg_seq_size = ARRAY_SIZE(gc2145_mode_1280_720_regs), + .pixel_rate = GC2145_1280_720_PIXELRATE, + .crop = { + .top = 160, + .left = 240, + .width = 1280, + .height = 720, + }, + .hblank = GC2145_1280_720_HBLANK, + .vblank = GC2145_1280_720_VBLANK, + .link_freq_index = GC2145_MODE_1280X720, + }, + { + /* 1600x1200 20fps mode */ + .width = 1600, + .height = 1200, + .reg_seq = gc2145_mode_1600_1200_regs, + .reg_seq_size = ARRAY_SIZE(gc2145_mode_1600_1200_regs), + .pixel_rate = GC2145_1600_1200_PIXELRATE, + .crop = { + .top = 0, + .left = 0, + .width = 1600, + .height = 1200, + }, + .hblank = GC2145_1600_1200_HBLANK, + .vblank = GC2145_1600_1200_VBLANK, + .link_freq_index = GC2145_MODE_1600X1200, + }, +}; + +/** + * struct gc2145_format - GC2145 pixel format description + * @code: media bus (MBUS) associated code + * @colorspace: V4L2 colorspace + * @datatype: MIPI CSI2 data type + * @output_fmt: GC2145 output format + * @switch_bit: GC2145 first/second switch + * @row_col_switch: GC2145 switch row and/or column + */ +struct gc2145_format { + unsigned int code; + unsigned int colorspace; + unsigned char datatype; + unsigned char output_fmt; + bool switch_bit; + unsigned char row_col_switch; +}; + +/* All supported formats */ +static const struct gc2145_format supported_formats[] = { + { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .colorspace = V4L2_COLORSPACE_SRGB, + .datatype = MIPI_CSI2_DT_YUV422_8B, + .output_fmt = 0x00, + }, + { + .code = MEDIA_BUS_FMT_VYUY8_1X16, + .colorspace = V4L2_COLORSPACE_SRGB, + .datatype = MIPI_CSI2_DT_YUV422_8B, + .output_fmt = 0x01, + }, + { + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .colorspace = V4L2_COLORSPACE_SRGB, + .datatype = MIPI_CSI2_DT_YUV422_8B, + .output_fmt = 0x02, + }, + { + .code = MEDIA_BUS_FMT_YVYU8_1X16, + .colorspace = V4L2_COLORSPACE_SRGB, + .datatype = MIPI_CSI2_DT_YUV422_8B, + .output_fmt = 0x03, + }, + { + .code = MEDIA_BUS_FMT_RGB565_1X16, + .colorspace = V4L2_COLORSPACE_SRGB, + .datatype = MIPI_CSI2_DT_RGB565, + .output_fmt = 0x06, + .switch_bit = true, + }, + { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .colorspace = V4L2_COLORSPACE_RAW, + .datatype = MIPI_CSI2_DT_RAW8, + .output_fmt = 0x17, + .row_col_switch = GC2145_SYNC_MODE_COL_SWITCH, + }, + { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .colorspace = V4L2_COLORSPACE_RAW, + .datatype = MIPI_CSI2_DT_RAW8, + .output_fmt = 0x17, + .row_col_switch = GC2145_SYNC_MODE_COL_SWITCH | GC2145_SYNC_MODE_ROW_SWITCH, + }, + { + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .colorspace = V4L2_COLORSPACE_RAW, + .datatype = MIPI_CSI2_DT_RAW8, + .output_fmt = 0x17, + .row_col_switch = 0, + }, + { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .colorspace = V4L2_COLORSPACE_RAW, + .datatype = MIPI_CSI2_DT_RAW8, + .output_fmt = 0x17, + .row_col_switch = GC2145_SYNC_MODE_ROW_SWITCH, + }, +}; + +struct gc2145_ctrls { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *test_pattern; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; +}; + +struct gc2145 { + struct v4l2_subdev sd; + struct media_pad pad; + + struct regmap *regmap; + struct clk *xclk; + + struct gpio_desc *reset_gpio; + struct gpio_desc *powerdown_gpio; + struct regulator_bulk_data supplies[GC2145_NUM_SUPPLIES]; + + /* V4L2 controls */ + struct gc2145_ctrls ctrls; + + /* Current mode */ + const struct gc2145_mode *mode; +}; + +static inline struct gc2145 *to_gc2145(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct gc2145, sd); +} + +static inline struct v4l2_subdev *gc2145_ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct gc2145, + ctrls.handler)->sd; +} + +static const struct gc2145_format * +gc2145_get_format_code(struct gc2145 *gc2145, u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(supported_formats); i++) { + if (supported_formats[i].code == code) + break; + } + + if (i >= ARRAY_SIZE(supported_formats)) + i = 0; + + return &supported_formats[i]; +} + +static void gc2145_update_pad_format(struct gc2145 *gc2145, + const struct gc2145_mode *mode, + struct v4l2_mbus_framefmt *fmt, u32 code, + u32 colorspace) +{ + fmt->code = code; + fmt->width = mode->width; + fmt->height = mode->height; + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->quantization = V4L2_QUANTIZATION_DEFAULT; + fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int gc2145_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct gc2145 *gc2145 = to_gc2145(sd); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + + /* Initialize pad format */ + format = v4l2_subdev_state_get_format(state, 0); + gc2145_update_pad_format(gc2145, &supported_modes[0], format, + MEDIA_BUS_FMT_RGB565_1X16, + V4L2_COLORSPACE_SRGB); + + /* Initialize crop rectangle. */ + crop = v4l2_subdev_state_get_crop(state, 0); + *crop = supported_modes[0].crop; + + return 0; +} + +static int gc2145_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_state_get_crop(sd_state, 0); + return 0; + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = GC2145_NATIVE_WIDTH; + sel->r.height = GC2145_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = 1600; + sel->r.height = 1200; + + return 0; + } + + return -EINVAL; +} + +static int gc2145_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(supported_formats)) + return -EINVAL; + + code->code = supported_formats[code->index].code; + return 0; +} + +static int gc2145_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct gc2145 *gc2145 = to_gc2145(sd); + const struct gc2145_format *gc2145_format; + u32 code; + + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + gc2145_format = gc2145_get_format_code(gc2145, fse->code); + code = gc2145_format->code; + if (fse->code != code) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int gc2145_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct gc2145 *gc2145 = to_gc2145(sd); + const struct gc2145_mode *mode; + const struct gc2145_format *gc2145_fmt; + struct v4l2_mbus_framefmt *framefmt; + struct gc2145_ctrls *ctrls = &gc2145->ctrls; + struct v4l2_rect *crop; + + gc2145_fmt = gc2145_get_format_code(gc2145, fmt->format.code); + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, fmt->format.height); + + /* In RAW mode, VGA is not possible so use 720p instead */ + if (gc2145_fmt->colorspace == V4L2_COLORSPACE_RAW && + mode == &supported_modes[GC2145_MODE_640X480]) + mode = &supported_modes[GC2145_MODE_1280X720]; + + gc2145_update_pad_format(gc2145, mode, &fmt->format, gc2145_fmt->code, + gc2145_fmt->colorspace); + framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + gc2145->mode = mode; + /* Update pixel_rate based on the mode */ + __v4l2_ctrl_s_ctrl_int64(ctrls->pixel_rate, mode->pixel_rate); + /* Update link_freq based on the mode */ + __v4l2_ctrl_s_ctrl(ctrls->link_freq, mode->link_freq_index); + /* Update hblank/vblank based on the mode */ + __v4l2_ctrl_s_ctrl(ctrls->hblank, mode->hblank); + __v4l2_ctrl_s_ctrl(ctrls->vblank, mode->vblank); + } + *framefmt = fmt->format; + crop = v4l2_subdev_state_get_crop(sd_state, fmt->pad); + *crop = mode->crop; + + return 0; +} + +static const struct cci_reg_sequence gc2145_common_mipi_regs[] = { + {GC2145_REG_PAGE_SELECT, 0x03}, + {GC2145_REG_DPHY_ANALOG_MODE1, GC2145_DPHY_MODE_PHY_CLK_EN | + GC2145_DPHY_MODE_PHY_LANE0_EN | + GC2145_DPHY_MODE_PHY_LANE1_EN | + GC2145_DPHY_MODE_PHY_CLK_LANE_P2S_SEL}, + {GC2145_REG_DPHY_ANALOG_MODE2, GC2145_DPHY_CLK_DIFF(2) | + GC2145_DPHY_LANE0_DIFF(2)}, + {GC2145_REG_DPHY_ANALOG_MODE3, GC2145_DPHY_LANE1_DIFF(0) | + GC2145_DPHY_CLK_DELAY}, + {GC2145_REG_FIFO_MODE, GC2145_FIFO_MODE_READ_GATE | + GC2145_FIFO_MODE_MIPI_CLK_MODULE}, + {GC2145_REG_DPHY_MODE, GC2145_DPHY_MODE_TRIGGER_PROG}, + /* Clock & Data lanes timing */ + {GC2145_REG_T_LPX, 0x10}, + {GC2145_REG_T_CLK_HS_PREPARE, 0x04}, {GC2145_REG_T_CLK_ZERO, 0x10}, + {GC2145_REG_T_CLK_PRE, 0x10}, {GC2145_REG_T_CLK_POST, 0x10}, + {GC2145_REG_T_CLK_TRAIL, 0x05}, + {GC2145_REG_T_HS_PREPARE, 0x03}, {GC2145_REG_T_HS_ZERO, 0x0a}, + {GC2145_REG_T_HS_TRAIL, 0x06}, +}; + +static int gc2145_config_mipi_mode(struct gc2145 *gc2145, + const struct gc2145_format *gc2145_format) +{ + u16 lwc, fifo_full_lvl; + int ret = 0; + + /* Common MIPI settings */ + cci_multi_reg_write(gc2145->regmap, gc2145_common_mipi_regs, + ARRAY_SIZE(gc2145_common_mipi_regs), &ret); + + /* + * Adjust the MIPI buffer settings. + * For YUV/RGB, LWC = image width * 2 + * For RAW8, LWC = image width + * For RAW10, LWC = image width * 1.25 + */ + if (gc2145_format->colorspace != V4L2_COLORSPACE_RAW) + lwc = gc2145->mode->width * 2; + else + lwc = gc2145->mode->width; + + cci_write(gc2145->regmap, GC2145_REG_LWC, lwc, &ret); + + /* + * Adjust the MIPI FIFO Full Level + * 640x480 RGB: 0x0190 + * 1280x720 / 1600x1200 (aka no scaler) non RAW: 0x0001 + * 1600x1200 RAW: 0x0190 + */ + if (gc2145_format->colorspace != V4L2_COLORSPACE_RAW) { + if (gc2145->mode->width == 1280 || gc2145->mode->width == 1600) + fifo_full_lvl = 0x0001; + else + fifo_full_lvl = 0x0190; + } else { + fifo_full_lvl = 0x0190; + } + + cci_write(gc2145->regmap, GC2145_REG_FIFO_FULL_LVL, + fifo_full_lvl, &ret); + + /* + * Set the FIFO gate mode / MIPI wdiv set: + * 0xf1 in case of RAW mode and 0xf0 otherwise + */ + cci_write(gc2145->regmap, GC2145_REG_FIFO_GATE_MODE, + gc2145_format->colorspace == V4L2_COLORSPACE_RAW ? + 0xf1 : 0xf0, &ret); + + /* Set the MIPI data type */ + cci_write(gc2145->regmap, GC2145_REG_MIPI_DT, + gc2145_format->datatype, &ret); + + /* Configure mode and enable CSI */ + cci_write(gc2145->regmap, GC2145_REG_BUF_CSI2_MODE, + GC2145_CSI2_MODE_RAW8 | GC2145_CSI2_MODE_DOUBLE | + GC2145_CSI2_MODE_EN | GC2145_CSI2_MODE_MIPI_EN, &ret); + + return ret; +} + +static int gc2145_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct gc2145 *gc2145 = to_gc2145(sd); + struct i2c_client *client = v4l2_get_subdevdata(&gc2145->sd); + const struct gc2145_format *gc2145_format; + struct v4l2_mbus_framefmt *fmt; + int ret; + + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + return ret; + + /* Apply default values of current mode */ + cci_multi_reg_write(gc2145->regmap, gc2145->mode->reg_seq, + gc2145->mode->reg_seq_size, &ret); + cci_multi_reg_write(gc2145->regmap, gc2145_common_regs, + ARRAY_SIZE(gc2145_common_regs), &ret); + if (ret) { + dev_err(&client->dev, "%s failed to write regs\n", __func__); + goto err_rpm_put; + } + + fmt = v4l2_subdev_state_get_format(state, 0); + gc2145_format = gc2145_get_format_code(gc2145, fmt->code); + + /* Set the output format */ + cci_write(gc2145->regmap, GC2145_REG_PAGE_SELECT, 0x00, &ret); + + cci_write(gc2145->regmap, GC2145_REG_OUTPUT_FMT, + gc2145_format->output_fmt, &ret); + cci_update_bits(gc2145->regmap, GC2145_REG_BYPASS_MODE, + GC2145_BYPASS_MODE_SWITCH, + gc2145_format->switch_bit ? GC2145_BYPASS_MODE_SWITCH + : 0, &ret); + cci_update_bits(gc2145->regmap, GC2145_REG_SYNC_MODE, + GC2145_SYNC_MODE_COL_SWITCH | + GC2145_SYNC_MODE_ROW_SWITCH, + gc2145_format->row_col_switch, &ret); + if (ret) { + dev_err(&client->dev, "%s failed to write regs\n", __func__); + goto err_rpm_put; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(&gc2145->ctrls.handler); + if (ret) { + dev_err(&client->dev, "%s failed to apply ctrls\n", __func__); + goto err_rpm_put; + } + + /* Perform MIPI specific configuration */ + ret = gc2145_config_mipi_mode(gc2145, gc2145_format); + if (ret) { + dev_err(&client->dev, "%s failed to write mipi conf\n", + __func__); + goto err_rpm_put; + } + + cci_write(gc2145->regmap, GC2145_REG_PAGE_SELECT, 0x00, &ret); + + return 0; + +err_rpm_put: + pm_runtime_put_autosuspend(&client->dev); + return ret; +} + +static int gc2145_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct gc2145 *gc2145 = to_gc2145(sd); + struct i2c_client *client = v4l2_get_subdevdata(&gc2145->sd); + int ret = 0; + + /* Disable lanes & mipi streaming */ + cci_write(gc2145->regmap, GC2145_REG_PAGE_SELECT, 0x03, &ret); + cci_update_bits(gc2145->regmap, GC2145_REG_BUF_CSI2_MODE, + GC2145_CSI2_MODE_EN | GC2145_CSI2_MODE_MIPI_EN, 0, + &ret); + cci_write(gc2145->regmap, GC2145_REG_PAGE_SELECT, 0x00, &ret); + if (ret) + dev_err(&client->dev, "%s failed to write regs\n", __func__); + + pm_runtime_put_autosuspend(&client->dev); + + return ret; +} + +/* Power/clock management functions */ +static int gc2145_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct gc2145 *gc2145 = to_gc2145(sd); + int ret; + + ret = regulator_bulk_enable(GC2145_NUM_SUPPLIES, gc2145->supplies); + if (ret) { + dev_err(dev, "failed to enable regulators\n"); + return ret; + } + + ret = clk_prepare_enable(gc2145->xclk); + if (ret) { + dev_err(dev, "failed to enable clock\n"); + goto reg_off; + } + + gpiod_set_value_cansleep(gc2145->powerdown_gpio, 0); + gpiod_set_value_cansleep(gc2145->reset_gpio, 0); + + /* + * Datasheet doesn't mention timing between PWDN/RESETB control and + * i2c access however, experimentation shows that a rather big delay is + * needed. + */ + msleep(41); + + return 0; + +reg_off: + regulator_bulk_disable(GC2145_NUM_SUPPLIES, gc2145->supplies); + + return ret; +} + +static int gc2145_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct gc2145 *gc2145 = to_gc2145(sd); + + gpiod_set_value_cansleep(gc2145->powerdown_gpio, 1); + gpiod_set_value_cansleep(gc2145->reset_gpio, 1); + clk_disable_unprepare(gc2145->xclk); + regulator_bulk_disable(GC2145_NUM_SUPPLIES, gc2145->supplies); + + return 0; +} + +static int gc2145_get_regulators(struct gc2145 *gc2145) +{ + struct i2c_client *client = v4l2_get_subdevdata(&gc2145->sd); + unsigned int i; + + for (i = 0; i < GC2145_NUM_SUPPLIES; i++) + gc2145->supplies[i].supply = gc2145_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, GC2145_NUM_SUPPLIES, + gc2145->supplies); +} + +/* Verify chip ID */ +static int gc2145_identify_module(struct gc2145 *gc2145) +{ + struct i2c_client *client = v4l2_get_subdevdata(&gc2145->sd); + int ret; + u64 chip_id; + + ret = cci_read(gc2145->regmap, GC2145_REG_CHIP_ID, &chip_id, NULL); + if (ret) { + dev_err(&client->dev, "failed to read chip id (%d)\n", ret); + return ret; + } + + if (chip_id != GC2145_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%llx\n", + GC2145_CHIP_ID, chip_id); + return -EIO; + } + + return 0; +} + +static const char * const test_pattern_menu[] = { + "Disabled", + "Colored patterns", + "Uniform white", + "Uniform yellow", + "Uniform cyan", + "Uniform green", + "Uniform magenta", + "Uniform red", + "Uniform black", +}; + +#define GC2145_TEST_PATTERN_ENABLE BIT(0) +#define GC2145_TEST_PATTERN_UXGA BIT(3) + +#define GC2145_TEST_UNIFORM BIT(3) +#define GC2145_TEST_WHITE (4 << 4) +#define GC2145_TEST_YELLOW (8 << 4) +#define GC2145_TEST_CYAN (9 << 4) +#define GC2145_TEST_GREEN (6 << 4) +#define GC2145_TEST_MAGENTA (10 << 4) +#define GC2145_TEST_RED (5 << 4) +#define GC2145_TEST_BLACK (0) + +static const u8 test_pattern_val[] = { + 0, + GC2145_TEST_PATTERN_ENABLE, + GC2145_TEST_UNIFORM | GC2145_TEST_WHITE, + GC2145_TEST_UNIFORM | GC2145_TEST_YELLOW, + GC2145_TEST_UNIFORM | GC2145_TEST_CYAN, + GC2145_TEST_UNIFORM | GC2145_TEST_GREEN, + GC2145_TEST_UNIFORM | GC2145_TEST_MAGENTA, + GC2145_TEST_UNIFORM | GC2145_TEST_RED, + GC2145_TEST_UNIFORM | GC2145_TEST_BLACK, +}; + +static const struct v4l2_subdev_video_ops gc2145_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops gc2145_pad_ops = { + .enum_mbus_code = gc2145_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = gc2145_set_pad_format, + .get_selection = gc2145_get_selection, + .enum_frame_size = gc2145_enum_frame_size, + .enable_streams = gc2145_enable_streams, + .disable_streams = gc2145_disable_streams, +}; + +static const struct v4l2_subdev_ops gc2145_subdev_ops = { + .video = &gc2145_video_ops, + .pad = &gc2145_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops gc2145_subdev_internal_ops = { + .init_state = gc2145_init_state, +}; + +static int gc2145_set_ctrl_test_pattern(struct gc2145 *gc2145, int value) +{ + int ret = 0; + + if (!value) { + /* Disable test pattern */ + cci_write(gc2145->regmap, GC2145_REG_DEBUG_MODE2, 0, &ret); + return cci_write(gc2145->regmap, GC2145_REG_DEBUG_MODE3, 0, + &ret); + } + + /* Enable test pattern, colored or uniform */ + cci_write(gc2145->regmap, GC2145_REG_DEBUG_MODE2, + GC2145_TEST_PATTERN_ENABLE | GC2145_TEST_PATTERN_UXGA, &ret); + + if (!(test_pattern_val[value] & GC2145_TEST_UNIFORM)) + return cci_write(gc2145->regmap, GC2145_REG_DEBUG_MODE3, 0, + &ret); + + /* Uniform */ + return cci_write(gc2145->regmap, GC2145_REG_DEBUG_MODE3, + test_pattern_val[value], &ret); +} + +static int gc2145_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = gc2145_ctrl_to_sd(ctrl); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct gc2145 *gc2145 = to_gc2145(sd); + int ret; + + if (pm_runtime_get_if_in_use(&client->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_HBLANK: + ret = cci_write(gc2145->regmap, GC2145_REG_HBLANK, ctrl->val, + NULL); + break; + case V4L2_CID_VBLANK: + ret = cci_write(gc2145->regmap, GC2145_REG_VBLANK, ctrl->val, + NULL); + break; + case V4L2_CID_TEST_PATTERN: + ret = gc2145_set_ctrl_test_pattern(gc2145, ctrl->val); + break; + case V4L2_CID_HFLIP: + ret = cci_update_bits(gc2145->regmap, GC2145_REG_ANALOG_MODE1, + BIT(0), (ctrl->val ? BIT(0) : 0), NULL); + break; + case V4L2_CID_VFLIP: + ret = cci_update_bits(gc2145->regmap, GC2145_REG_ANALOG_MODE1, + BIT(1), (ctrl->val ? BIT(1) : 0), NULL); + break; + default: + ret = -EINVAL; + break; + } + + pm_runtime_put_autosuspend(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops gc2145_ctrl_ops = { + .s_ctrl = gc2145_s_ctrl, +}; + +/* Initialize control handlers */ +static int gc2145_init_controls(struct gc2145 *gc2145) +{ + struct i2c_client *client = v4l2_get_subdevdata(&gc2145->sd); + const struct v4l2_ctrl_ops *ops = &gc2145_ctrl_ops; + struct gc2145_ctrls *ctrls = &gc2145->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + struct v4l2_fwnode_device_properties props; + int ret; + + ret = v4l2_ctrl_handler_init(hdl, 12); + if (ret) + return ret; + + ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, + GC2145_640_480_PIXELRATE, + GC2145_1600_1200_PIXELRATE, 1, + supported_modes[0].pixel_rate); + + ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(gc2145_link_freq_menu) - 1, + 0, gc2145_link_freq_menu); + if (ctrls->link_freq) + ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ctrls->hblank = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, + 0, 0xfff, 1, GC2145_640_480_HBLANK); + + ctrls->vblank = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, + 0, 0x1fff, 1, GC2145_640_480_VBLANK); + + ctrls->test_pattern = + v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(test_pattern_menu) - 1, + 0, 0, test_pattern_menu); + ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, + 0, 1, 1, 0); + ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, + 0, 1, 1, 0); + + if (hdl->error) { + ret = hdl->error; + dev_err(&client->dev, "control init failed (%d)\n", ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(hdl, &gc2145_ctrl_ops, + &props); + if (ret) + goto error; + + gc2145->sd.ctrl_handler = hdl; + + return 0; + +error: + v4l2_ctrl_handler_free(hdl); + + return ret; +} + +static int gc2145_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg); + fwnode_handle_put(endpoint); + if (ret) + return ret; + + /* Check the number of MIPI CSI2 data lanes */ + if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) { + dev_err(dev, "only 2 data lanes are currently supported\n"); + ret = -EINVAL; + goto out; + } + + /* Check the link frequency set in device tree */ + if (!ep_cfg.nr_of_link_frequencies) { + dev_err(dev, "link-frequency property not found in DT\n"); + ret = -EINVAL; + goto out; + } + + if (ep_cfg.nr_of_link_frequencies != 3 || + ep_cfg.link_frequencies[0] != GC2145_640_480_LINKFREQ || + ep_cfg.link_frequencies[1] != GC2145_1280_720_LINKFREQ || + ep_cfg.link_frequencies[2] != GC2145_1600_1200_LINKFREQ) { + dev_err(dev, "Invalid link-frequencies provided\n"); + ret = -EINVAL; + } + +out: + v4l2_fwnode_endpoint_free(&ep_cfg); + + return ret; +} + +static int gc2145_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + unsigned int xclk_freq; + struct gc2145 *gc2145; + int ret; + + gc2145 = devm_kzalloc(&client->dev, sizeof(*gc2145), GFP_KERNEL); + if (!gc2145) + return -ENOMEM; + + v4l2_i2c_subdev_init(&gc2145->sd, client, &gc2145_subdev_ops); + gc2145->sd.internal_ops = &gc2145_subdev_internal_ops; + + /* Check the hardware configuration in device tree */ + if (gc2145_check_hwcfg(dev)) + return -EINVAL; + + /* Get system clock (xclk) */ + gc2145->xclk = devm_v4l2_sensor_clk_get(dev, NULL); + if (IS_ERR(gc2145->xclk)) + return dev_err_probe(dev, PTR_ERR(gc2145->xclk), + "failed to get xclk\n"); + + xclk_freq = clk_get_rate(gc2145->xclk); + if (xclk_freq != GC2145_XCLK_FREQ) { + dev_err(dev, "xclk frequency not supported: %d Hz\n", + xclk_freq); + return -EINVAL; + } + + ret = gc2145_get_regulators(gc2145); + if (ret) + return dev_err_probe(dev, ret, + "failed to get regulators\n"); + + /* Request optional reset pin */ + gc2145->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(gc2145->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(gc2145->reset_gpio), + "failed to get reset_gpio\n"); + + /* Request optional powerdown pin */ + gc2145->powerdown_gpio = devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(gc2145->powerdown_gpio)) + return dev_err_probe(dev, PTR_ERR(gc2145->powerdown_gpio), + "failed to get powerdown_gpio\n"); + + /* Initialise the regmap for further cci access */ + gc2145->regmap = devm_cci_regmap_init_i2c(client, 8); + if (IS_ERR(gc2145->regmap)) + return dev_err_probe(dev, PTR_ERR(gc2145->regmap), + "failed to get cci regmap\n"); + + /* + * The sensor must be powered for gc2145_identify_module() + * to be able to read the CHIP_ID register + */ + ret = gc2145_power_on(dev); + if (ret) + return ret; + + ret = gc2145_identify_module(gc2145); + if (ret) + goto error_power_off; + + /* Set default mode */ + gc2145->mode = &supported_modes[0]; + + ret = gc2145_init_controls(gc2145); + if (ret) + goto error_power_off; + + /* Initialize subdev */ + gc2145->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + gc2145->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + gc2145->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&gc2145->sd.entity, 1, &gc2145->pad); + if (ret) { + dev_err(dev, "failed to init entity pads: %d\n", ret); + goto error_handler_free; + } + + gc2145->sd.state_lock = gc2145->ctrls.handler.lock; + ret = v4l2_subdev_init_finalize(&gc2145->sd); + if (ret < 0) { + dev_err(dev, "subdev init error: %d\n", ret); + goto error_media_entity; + } + + /* Enable runtime PM and turn off the device */ + pm_runtime_set_active(dev); + pm_runtime_get_noresume(&client->dev); + pm_runtime_enable(dev); + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + ret = v4l2_async_register_subdev_sensor(&gc2145->sd); + if (ret < 0) { + dev_err(dev, "failed to register sensor sub-device: %d\n", ret); + goto error_subdev_cleanup; + } + + return 0; + +error_subdev_cleanup: + v4l2_subdev_cleanup(&gc2145->sd); + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + +error_media_entity: + media_entity_cleanup(&gc2145->sd.entity); + +error_handler_free: + v4l2_ctrl_handler_free(&gc2145->ctrls.handler); + +error_power_off: + gc2145_power_off(dev); + + return ret; +} + +static void gc2145_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct gc2145 *gc2145 = to_gc2145(sd); + + v4l2_subdev_cleanup(sd); + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&gc2145->ctrls.handler); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + gc2145_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id gc2145_dt_ids[] = { + { .compatible = "galaxycore,gc2145" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, gc2145_dt_ids); + +static const struct dev_pm_ops gc2145_pm_ops = { + RUNTIME_PM_OPS(gc2145_power_off, gc2145_power_on, NULL) +}; + +static struct i2c_driver gc2145_i2c_driver = { + .driver = { + .name = "gc2145", + .of_match_table = gc2145_dt_ids, + .pm = pm_ptr(&gc2145_pm_ops), + }, + .probe = gc2145_probe, + .remove = gc2145_remove, +}; + +module_i2c_driver(gc2145_i2c_driver); + +MODULE_AUTHOR("Alain Volmat "); +MODULE_DESCRIPTION("GalaxyCore GC2145 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c new file mode 100644 index 000000000..de573cee4 --- /dev/null +++ b/drivers/media/i2c/hi556.c @@ -0,0 +1,1455 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define HI556_REG_VALUE_08BIT 1 +#define HI556_REG_VALUE_16BIT 2 +#define HI556_REG_VALUE_24BIT 3 + +#define HI556_LINK_FREQ_437MHZ 437000000ULL +#define HI556_MCLK 19200000 +#define HI556_DATA_LANES 2 +#define HI556_RGB_DEPTH 10 + +#define HI556_REG_CHIP_ID 0x0f16 +#define HI556_CHIP_ID 0x0556 + +#define HI556_REG_MODE_SELECT 0x0a00 +#define HI556_MODE_STANDBY 0x0000 +#define HI556_MODE_STREAMING 0x0100 + +/* vertical-timings from sensor */ +#define HI556_REG_FLL 0x0006 +#define HI556_FLL_30FPS 0x0814 +#define HI556_FLL_30FPS_MIN 0x0814 +#define HI556_FLL_MAX 0x7fff + +/* horizontal-timings from sensor */ +#define HI556_REG_LLP 0x0008 + +/* Exposure controls from sensor */ +#define HI556_REG_EXPOSURE 0x0074 +#define HI556_EXPOSURE_MIN 6 +#define HI556_EXPOSURE_MAX_MARGIN 2 +#define HI556_EXPOSURE_STEP 1 + +/* Analog gain controls from sensor */ +#define HI556_REG_ANALOG_GAIN 0x0077 +#define HI556_ANAL_GAIN_MIN 0 +#define HI556_ANAL_GAIN_MAX 240 +#define HI556_ANAL_GAIN_STEP 1 + +/* Digital gain controls from sensor */ +#define HI556_REG_MWB_GR_GAIN 0x0078 +#define HI556_REG_MWB_GB_GAIN 0x007a +#define HI556_REG_MWB_R_GAIN 0x007c +#define HI556_REG_MWB_B_GAIN 0x007e +#define HI556_DGTL_GAIN_MIN 0 +#define HI556_DGTL_GAIN_MAX 2048 +#define HI556_DGTL_GAIN_STEP 1 +#define HI556_DGTL_GAIN_DEFAULT 256 + +/* Test Pattern Control */ +#define HI556_REG_ISP 0X0a05 +#define HI556_REG_ISP_TPG_EN 0x01 +#define HI556_REG_TEST_PATTERN 0x0201 + +/* HI556 native and active pixel array size. */ +#define HI556_NATIVE_WIDTH 2592U +#define HI556_NATIVE_HEIGHT 1944U +#define HI556_PIXEL_ARRAY_LEFT 0U +#define HI556_PIXEL_ARRAY_TOP 0U +#define HI556_PIXEL_ARRAY_WIDTH 2592U +#define HI556_PIXEL_ARRAY_HEIGHT 1944U + +enum { + HI556_LINK_FREQ_437MHZ_INDEX, +}; + +struct hi556_reg { + u16 address; + u16 val; +}; + +struct hi556_reg_list { + u32 num_of_regs; + const struct hi556_reg *regs; +}; + +struct hi556_link_freq_config { + const struct hi556_reg_list reg_list; +}; + +struct hi556_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + /* Analog crop rectangle. */ + struct v4l2_rect crop; + + /* Horizontal timining size */ + u32 llp; + + /* Default vertical timining size */ + u32 fll_def; + + /* Min vertical timining size */ + u32 fll_min; + + /* Link frequency needed for this resolution */ + u32 link_freq_index; + + /* Sensor register settings for this resolution */ + const struct hi556_reg_list reg_list; +}; + +#define to_hi556(_sd) container_of(_sd, struct hi556, sd) + +//SENSOR_INITIALIZATION +static const struct hi556_reg mipi_data_rate_874mbps[] = { + {0x0e00, 0x0102}, + {0x0e02, 0x0102}, + {0x0e0c, 0x0100}, + {0x2000, 0x7400}, + {0x2002, 0x001c}, + {0x2004, 0x0242}, + {0x2006, 0x0942}, + {0x2008, 0x7007}, + {0x200a, 0x0fd9}, + {0x200c, 0x0259}, + {0x200e, 0x7008}, + {0x2010, 0x160e}, + {0x2012, 0x0047}, + {0x2014, 0x2118}, + {0x2016, 0x0041}, + {0x2018, 0x00d8}, + {0x201a, 0x0145}, + {0x201c, 0x0006}, + {0x201e, 0x0181}, + {0x2020, 0x13cc}, + {0x2022, 0x2057}, + {0x2024, 0x7001}, + {0x2026, 0x0fca}, + {0x2028, 0x00cb}, + {0x202a, 0x009f}, + {0x202c, 0x7002}, + {0x202e, 0x13cc}, + {0x2030, 0x019b}, + {0x2032, 0x014d}, + {0x2034, 0x2987}, + {0x2036, 0x2766}, + {0x2038, 0x0020}, + {0x203a, 0x2060}, + {0x203c, 0x0e5d}, + {0x203e, 0x181d}, + {0x2040, 0x2066}, + {0x2042, 0x20c4}, + {0x2044, 0x5000}, + {0x2046, 0x0005}, + {0x2048, 0x0000}, + {0x204a, 0x01db}, + {0x204c, 0x025a}, + {0x204e, 0x00c0}, + {0x2050, 0x0005}, + {0x2052, 0x0006}, + {0x2054, 0x0ad9}, + {0x2056, 0x0259}, + {0x2058, 0x0618}, + {0x205a, 0x0258}, + {0x205c, 0x2266}, + {0x205e, 0x20c8}, + {0x2060, 0x2060}, + {0x2062, 0x707b}, + {0x2064, 0x0fdd}, + {0x2066, 0x81b8}, + {0x2068, 0x5040}, + {0x206a, 0x0020}, + {0x206c, 0x5060}, + {0x206e, 0x3143}, + {0x2070, 0x5081}, + {0x2072, 0x025c}, + {0x2074, 0x7800}, + {0x2076, 0x7400}, + {0x2078, 0x001c}, + {0x207a, 0x0242}, + {0x207c, 0x0942}, + {0x207e, 0x0bd9}, + {0x2080, 0x0259}, + {0x2082, 0x7008}, + {0x2084, 0x160e}, + {0x2086, 0x0047}, + {0x2088, 0x2118}, + {0x208a, 0x0041}, + {0x208c, 0x00d8}, + {0x208e, 0x0145}, + {0x2090, 0x0006}, + {0x2092, 0x0181}, + {0x2094, 0x13cc}, + {0x2096, 0x2057}, + {0x2098, 0x7001}, + {0x209a, 0x0fca}, + {0x209c, 0x00cb}, + {0x209e, 0x009f}, + {0x20a0, 0x7002}, + {0x20a2, 0x13cc}, + {0x20a4, 0x019b}, + {0x20a6, 0x014d}, + {0x20a8, 0x2987}, + {0x20aa, 0x2766}, + {0x20ac, 0x0020}, + {0x20ae, 0x2060}, + {0x20b0, 0x0e5d}, + {0x20b2, 0x181d}, + {0x20b4, 0x2066}, + {0x20b6, 0x20c4}, + {0x20b8, 0x50a0}, + {0x20ba, 0x0005}, + {0x20bc, 0x0000}, + {0x20be, 0x01db}, + {0x20c0, 0x025a}, + {0x20c2, 0x00c0}, + {0x20c4, 0x0005}, + {0x20c6, 0x0006}, + {0x20c8, 0x0ad9}, + {0x20ca, 0x0259}, + {0x20cc, 0x0618}, + {0x20ce, 0x0258}, + {0x20d0, 0x2266}, + {0x20d2, 0x20c8}, + {0x20d4, 0x2060}, + {0x20d6, 0x707b}, + {0x20d8, 0x0fdd}, + {0x20da, 0x86b8}, + {0x20dc, 0x50e0}, + {0x20de, 0x0020}, + {0x20e0, 0x5100}, + {0x20e2, 0x3143}, + {0x20e4, 0x5121}, + {0x20e6, 0x7800}, + {0x20e8, 0x3140}, + {0x20ea, 0x01c4}, + {0x20ec, 0x01c1}, + {0x20ee, 0x01c0}, + {0x20f0, 0x01c4}, + {0x20f2, 0x2700}, + {0x20f4, 0x3d40}, + {0x20f6, 0x7800}, + {0x20f8, 0xffff}, + {0x27fe, 0xe000}, + {0x3000, 0x60f8}, + {0x3002, 0x187f}, + {0x3004, 0x7060}, + {0x3006, 0x0114}, + {0x3008, 0x60b0}, + {0x300a, 0x1473}, + {0x300c, 0x0013}, + {0x300e, 0x140f}, + {0x3010, 0x0040}, + {0x3012, 0x100f}, + {0x3014, 0x60f8}, + {0x3016, 0x187f}, + {0x3018, 0x7060}, + {0x301a, 0x0114}, + {0x301c, 0x60b0}, + {0x301e, 0x1473}, + {0x3020, 0x0013}, + {0x3022, 0x140f}, + {0x3024, 0x0040}, + {0x3026, 0x000f}, + + {0x0b00, 0x0000}, + {0x0b02, 0x0045}, + {0x0b04, 0xb405}, + {0x0b06, 0xc403}, + {0x0b08, 0x0081}, + {0x0b0a, 0x8252}, + {0x0b0c, 0xf814}, + {0x0b0e, 0xc618}, + {0x0b10, 0xa828}, + {0x0b12, 0x004c}, + {0x0b14, 0x4068}, + {0x0b16, 0x0000}, + {0x0f30, 0x5b15}, + {0x0f32, 0x7067}, + {0x0954, 0x0009}, + {0x0956, 0x0000}, + {0x0958, 0xbb80}, + {0x095a, 0x5140}, + {0x0c00, 0x1110}, + {0x0c02, 0x0011}, + {0x0c04, 0x0000}, + {0x0c06, 0x0200}, + {0x0c10, 0x0040}, + {0x0c12, 0x0040}, + {0x0c14, 0x0040}, + {0x0c16, 0x0040}, + {0x0a10, 0x4000}, + {0x3068, 0xf800}, + {0x306a, 0xf876}, + {0x006c, 0x0000}, + {0x005e, 0x0200}, + {0x000e, 0x0100}, + {0x0e0a, 0x0001}, + {0x004a, 0x0100}, + {0x004c, 0x0000}, + {0x004e, 0x0100}, + {0x000c, 0x0022}, + {0x0008, 0x0b00}, + {0x005a, 0x0202}, + {0x0012, 0x000e}, + {0x0018, 0x0a33}, + {0x0022, 0x0008}, + {0x0028, 0x0017}, + {0x0024, 0x0028}, + {0x002a, 0x002d}, + {0x0026, 0x0030}, + {0x002c, 0x07c9}, + {0x002e, 0x1111}, + {0x0030, 0x1111}, + {0x0032, 0x1111}, + {0x0006, 0x07bc}, + {0x0a22, 0x0000}, + {0x0a12, 0x0a20}, + {0x0a14, 0x0798}, + {0x003e, 0x0000}, + {0x0074, 0x080e}, + {0x0070, 0x0407}, + {0x0002, 0x0000}, + {0x0a02, 0x0100}, + {0x0a24, 0x0100}, + {0x0046, 0x0000}, + {0x0076, 0x0000}, + {0x0060, 0x0000}, + {0x0062, 0x0530}, + {0x0064, 0x0500}, + {0x0066, 0x0530}, + {0x0068, 0x0500}, + {0x0122, 0x0300}, + {0x015a, 0xff08}, + {0x0804, 0x0300}, + {0x0806, 0x0100}, + {0x005c, 0x0102}, + {0x0a1a, 0x0800}, +}; + +static const struct hi556_reg mode_2592x1944_regs[] = { + {0x0a00, 0x0000}, + {0x0b0a, 0x8252}, + {0x0f30, 0x5b15}, + {0x0f32, 0x7067}, + {0x004a, 0x0100}, + {0x004c, 0x0000}, + {0x004e, 0x0100}, + {0x000c, 0x0022}, + {0x0008, 0x0b00}, + {0x005a, 0x0202}, + {0x0012, 0x000e}, + {0x0018, 0x0a33}, + {0x0022, 0x0008}, + {0x0028, 0x0017}, + {0x0024, 0x0028}, + {0x002a, 0x002d}, + {0x0026, 0x0030}, + {0x002c, 0x07c9}, + {0x002e, 0x1111}, + {0x0030, 0x1111}, + {0x0032, 0x1111}, + {0x0006, 0x0814}, + {0x0a22, 0x0000}, + {0x0a12, 0x0a20}, + {0x0a14, 0x0798}, + {0x003e, 0x0000}, + {0x0074, 0x0812}, + {0x0070, 0x0409}, + {0x0804, 0x0300}, + {0x0806, 0x0100}, + {0x0a04, 0x014a}, + {0x090c, 0x0fdc}, + {0x090e, 0x002d}, + + {0x0902, 0x4319}, + {0x0914, 0xc10a}, + {0x0916, 0x071f}, + {0x0918, 0x0408}, + {0x091a, 0x0c0d}, + {0x091c, 0x0f09}, + {0x091e, 0x0a00}, + {0x0958, 0xbb80}, +}; + +static const struct hi556_reg mode_2592x1444_regs[] = { + {0x0a00, 0x0000}, + {0x0b0a, 0x8252}, + {0x0f30, 0xe545}, + {0x0f32, 0x7067}, + {0x004a, 0x0100}, + {0x004c, 0x0000}, + {0x000c, 0x0022}, + {0x0008, 0x0b00}, + {0x005a, 0x0202}, + {0x0012, 0x000e}, + {0x0018, 0x0a33}, + {0x0022, 0x0008}, + {0x0028, 0x0017}, + {0x0024, 0x0122}, + {0x002a, 0x0127}, + {0x0026, 0x012a}, + {0x002c, 0x06cf}, + {0x002e, 0x1111}, + {0x0030, 0x1111}, + {0x0032, 0x1111}, + {0x0006, 0x0821}, + {0x0a22, 0x0000}, + {0x0a12, 0x0a20}, + {0x0a14, 0x05a4}, + {0x003e, 0x0000}, + {0x0074, 0x081f}, + {0x0070, 0x040f}, + {0x0804, 0x0300}, + {0x0806, 0x0100}, + {0x0a04, 0x014a}, + {0x090c, 0x0fdc}, + {0x090e, 0x002d}, + {0x0902, 0x4319}, + {0x0914, 0xc10a}, + {0x0916, 0x071f}, + {0x0918, 0x0408}, + {0x091a, 0x0c0d}, + {0x091c, 0x0f09}, + {0x091e, 0x0a00}, + {0x0958, 0xbb80}, +}; + +static const struct hi556_reg mode_1296x972_regs[] = { + {0x0a00, 0x0000}, + {0x0b0a, 0x8259}, + {0x0f30, 0x5b15}, + {0x0f32, 0x7167}, + {0x004a, 0x0100}, + {0x004c, 0x0000}, + {0x004e, 0x0100}, + {0x000c, 0x0122}, + {0x0008, 0x0b00}, + {0x005a, 0x0404}, + {0x0012, 0x000c}, + {0x0018, 0x0a33}, + {0x0022, 0x0008}, + {0x0028, 0x0017}, + {0x0024, 0x0022}, + {0x002a, 0x002b}, + {0x0026, 0x0030}, + {0x002c, 0x07c9}, + {0x002e, 0x3311}, + {0x0030, 0x3311}, + {0x0032, 0x3311}, + {0x0006, 0x0814}, + {0x0a22, 0x0000}, + {0x0a12, 0x0510}, + {0x0a14, 0x03cc}, + {0x003e, 0x0000}, + {0x0074, 0x0812}, + {0x0070, 0x0409}, + {0x0804, 0x0308}, + {0x0806, 0x0100}, + {0x0a04, 0x016a}, + {0x090e, 0x0010}, + {0x090c, 0x09c0}, + + {0x0902, 0x4319}, + {0x0914, 0xc106}, + {0x0916, 0x040e}, + {0x0918, 0x0304}, + {0x091a, 0x0708}, + {0x091c, 0x0e06}, + {0x091e, 0x0300}, + {0x0958, 0xbb80}, +}; + +static const struct hi556_reg mode_1296x722_regs[] = { + {0x0a00, 0x0000}, + {0x0b0a, 0x8259}, + {0x0f30, 0x5b15}, + {0x0f32, 0x7167}, + {0x004a, 0x0100}, + {0x004c, 0x0000}, + {0x004e, 0x0100}, + {0x000c, 0x0122}, + {0x0008, 0x0b00}, + {0x005a, 0x0404}, + {0x0012, 0x000c}, + {0x0018, 0x0a33}, + {0x0022, 0x0008}, + {0x0028, 0x0017}, + {0x0024, 0x0022}, + {0x002a, 0x002b}, + {0x0026, 0x012a}, + {0x002c, 0x06cf}, + {0x002e, 0x3311}, + {0x0030, 0x3311}, + {0x0032, 0x3311}, + {0x0006, 0x0814}, + {0x0a22, 0x0000}, + {0x0a12, 0x0510}, + {0x0a14, 0x02d2}, + {0x003e, 0x0000}, + {0x0074, 0x0812}, + {0x0070, 0x0409}, + {0x0804, 0x0308}, + {0x0806, 0x0100}, + {0x0a04, 0x016a}, + {0x090c, 0x09c0}, + {0x090e, 0x0010}, + {0x0902, 0x4319}, + {0x0914, 0xc106}, + {0x0916, 0x040e}, + {0x0918, 0x0304}, + {0x091a, 0x0708}, + {0x091c, 0x0e06}, + {0x091e, 0x0300}, + {0x0958, 0xbb80}, +}; + +static const char * const hi556_test_pattern_menu[] = { + "Disabled", + "Solid Colour", + "100% Colour Bars", + "Fade To Grey Colour Bars", + "PN9", + "Gradient Horizontal", + "Gradient Vertical", + "Check Board", + "Slant Pattern", +}; + +static const s64 link_freq_menu_items[] = { + HI556_LINK_FREQ_437MHZ, +}; + +static const struct hi556_link_freq_config link_freq_configs[] = { + [HI556_LINK_FREQ_437MHZ_INDEX] = { + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_874mbps), + .regs = mipi_data_rate_874mbps, + } + } +}; + +static const struct hi556_mode supported_modes[] = { + { + .width = HI556_PIXEL_ARRAY_WIDTH, + .height = HI556_PIXEL_ARRAY_HEIGHT, + .crop = { + .left = HI556_PIXEL_ARRAY_LEFT, + .top = HI556_PIXEL_ARRAY_TOP, + .width = HI556_PIXEL_ARRAY_WIDTH, + .height = HI556_PIXEL_ARRAY_HEIGHT + }, + .fll_def = HI556_FLL_30FPS, + .fll_min = HI556_FLL_30FPS_MIN, + .llp = 0x0b00, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2592x1944_regs), + .regs = mode_2592x1944_regs, + }, + .link_freq_index = HI556_LINK_FREQ_437MHZ_INDEX, + }, + { + .width = HI556_PIXEL_ARRAY_WIDTH, + .height = 1444, + .crop = { + .left = HI556_PIXEL_ARRAY_LEFT, + .top = 250, + .width = HI556_PIXEL_ARRAY_WIDTH, + .height = 1444 + }, + .fll_def = 0x821, + .fll_min = 0x821, + .llp = 0x0b00, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2592x1444_regs), + .regs = mode_2592x1444_regs, + }, + .link_freq_index = HI556_LINK_FREQ_437MHZ_INDEX, + }, + { + .width = 1296, + .height = 972, + .crop = { + .left = HI556_PIXEL_ARRAY_LEFT, + .top = HI556_PIXEL_ARRAY_TOP, + .width = HI556_PIXEL_ARRAY_WIDTH, + .height = HI556_PIXEL_ARRAY_HEIGHT + }, + .fll_def = HI556_FLL_30FPS, + .fll_min = HI556_FLL_30FPS_MIN, + .llp = 0x0b00, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1296x972_regs), + .regs = mode_1296x972_regs, + }, + .link_freq_index = HI556_LINK_FREQ_437MHZ_INDEX, + }, + { + .width = 1296, + .height = 722, + .crop = { + .left = HI556_PIXEL_ARRAY_LEFT, + .top = 250, + .width = HI556_PIXEL_ARRAY_WIDTH, + .height = 1444 + }, + .fll_def = HI556_FLL_30FPS, + .fll_min = HI556_FLL_30FPS_MIN, + .llp = 0x0b00, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1296x722_regs), + .regs = mode_1296x722_regs, + }, + .link_freq_index = HI556_LINK_FREQ_437MHZ_INDEX, + }, +}; + +static const char * const hi556_supply_names[] = { + "dovdd", /* Digital I/O power */ + "avdd", /* Analog power */ + "dvdd", /* Digital core power */ +}; + +struct hi556 { + struct device *dev; + + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + /* GPIOs, clocks, etc. */ + struct gpio_desc *reset_gpio; + struct clk *clk; + struct regulator_bulk_data supplies[ARRAY_SIZE(hi556_supply_names)]; + + /* Current mode */ + const struct hi556_mode *cur_mode; + + /* To serialize asynchronous callbacks */ + struct mutex mutex; + + /* True if the device has been identified */ + bool identified; +}; + +static u64 to_pixel_rate(u32 f_index) +{ + u64 pixel_rate = link_freq_menu_items[f_index] * 2 * HI556_DATA_LANES; + + do_div(pixel_rate, HI556_RGB_DEPTH); + + return pixel_rate; +} + +static int hi556_read_reg(struct hi556 *hi556, u16 reg, u16 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi556->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2]; + u8 data_buf[4] = {0}; + int ret; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, addr_buf); + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(addr_buf); + msgs[0].buf = addr_buf; + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +static int hi556_write_reg(struct hi556 *hi556, u16 reg, u16 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi556->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << 8 * (4 - len), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +static int hi556_write_reg_list(struct hi556 *hi556, + const struct hi556_reg_list *r_list) +{ + unsigned int i; + int ret; + + for (i = 0; i < r_list->num_of_regs; i++) { + ret = hi556_write_reg(hi556, r_list->regs[i].address, + HI556_REG_VALUE_16BIT, + r_list->regs[i].val); + if (ret) { + dev_err_ratelimited(hi556->dev, + "failed to write reg 0x%4.4x. error = %d\n", + r_list->regs[i].address, ret); + return ret; + } + } + + return 0; +} + +static int hi556_update_digital_gain(struct hi556 *hi556, u32 d_gain) +{ + int ret; + + ret = hi556_write_reg(hi556, HI556_REG_MWB_GR_GAIN, + HI556_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + ret = hi556_write_reg(hi556, HI556_REG_MWB_GB_GAIN, + HI556_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + ret = hi556_write_reg(hi556, HI556_REG_MWB_R_GAIN, + HI556_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + return hi556_write_reg(hi556, HI556_REG_MWB_B_GAIN, + HI556_REG_VALUE_16BIT, d_gain); +} + +static int hi556_test_pattern(struct hi556 *hi556, u32 pattern) +{ + int ret; + u32 val; + + ret = hi556_read_reg(hi556, HI556_REG_ISP, + HI556_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + val = pattern ? (val | HI556_REG_ISP_TPG_EN) : + (val & ~HI556_REG_ISP_TPG_EN); + + ret = hi556_write_reg(hi556, HI556_REG_ISP, + HI556_REG_VALUE_08BIT, val); + if (ret) + return ret; + + val = pattern ? BIT(pattern - 1) : 0; + + return hi556_write_reg(hi556, HI556_REG_TEST_PATTERN, + HI556_REG_VALUE_08BIT, val); +} + +static int hi556_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct hi556 *hi556 = container_of(ctrl->handler, + struct hi556, ctrl_handler); + s64 exposure_max; + int ret = 0; + + /* Propagate change of current control to all related controls */ + if (ctrl->id == V4L2_CID_VBLANK) { + /* Update max exposure while meeting expected vblanking */ + exposure_max = hi556->cur_mode->height + ctrl->val - + HI556_EXPOSURE_MAX_MARGIN; + __v4l2_ctrl_modify_range(hi556->exposure, + hi556->exposure->minimum, + exposure_max, hi556->exposure->step, + exposure_max); + } + + /* V4L2 controls values will be applied only when power is already up */ + if (!pm_runtime_get_if_in_use(hi556->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = hi556_write_reg(hi556, HI556_REG_ANALOG_GAIN, + HI556_REG_VALUE_16BIT, ctrl->val); + break; + + case V4L2_CID_DIGITAL_GAIN: + ret = hi556_update_digital_gain(hi556, ctrl->val); + break; + + case V4L2_CID_EXPOSURE: + ret = hi556_write_reg(hi556, HI556_REG_EXPOSURE, + HI556_REG_VALUE_16BIT, ctrl->val); + break; + + case V4L2_CID_VBLANK: + /* Update FLL that meets expected vertical blanking */ + ret = hi556_write_reg(hi556, HI556_REG_FLL, + HI556_REG_VALUE_16BIT, + hi556->cur_mode->height + ctrl->val); + break; + + case V4L2_CID_TEST_PATTERN: + ret = hi556_test_pattern(hi556, ctrl->val); + break; + + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(hi556->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops hi556_ctrl_ops = { + .s_ctrl = hi556_set_ctrl, +}; + +static int hi556_init_controls(struct hi556 *hi556) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max, h_blank; + int ret; + + ctrl_hdlr = &hi556->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); + if (ret) + return ret; + + ctrl_hdlr->lock = &hi556->mutex; + hi556->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &hi556_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, + 0, link_freq_menu_items); + if (hi556->link_freq) + hi556->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + hi556->pixel_rate = v4l2_ctrl_new_std + (ctrl_hdlr, &hi556_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, + to_pixel_rate(HI556_LINK_FREQ_437MHZ_INDEX), + 1, + to_pixel_rate(HI556_LINK_FREQ_437MHZ_INDEX)); + hi556->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &hi556_ctrl_ops, + V4L2_CID_VBLANK, + hi556->cur_mode->fll_min - + hi556->cur_mode->height, + HI556_FLL_MAX - + hi556->cur_mode->height, 1, + hi556->cur_mode->fll_def - + hi556->cur_mode->height); + + h_blank = hi556->cur_mode->llp - hi556->cur_mode->width; + + hi556->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &hi556_ctrl_ops, + V4L2_CID_HBLANK, h_blank, h_blank, 1, + h_blank); + if (hi556->hblank) + hi556->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(ctrl_hdlr, &hi556_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + HI556_ANAL_GAIN_MIN, HI556_ANAL_GAIN_MAX, + HI556_ANAL_GAIN_STEP, HI556_ANAL_GAIN_MIN); + v4l2_ctrl_new_std(ctrl_hdlr, &hi556_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + HI556_DGTL_GAIN_MIN, HI556_DGTL_GAIN_MAX, + HI556_DGTL_GAIN_STEP, HI556_DGTL_GAIN_DEFAULT); + exposure_max = hi556->cur_mode->fll_def - HI556_EXPOSURE_MAX_MARGIN; + hi556->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &hi556_ctrl_ops, + V4L2_CID_EXPOSURE, + HI556_EXPOSURE_MIN, exposure_max, + HI556_EXPOSURE_STEP, + exposure_max); + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &hi556_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(hi556_test_pattern_menu) - 1, + 0, 0, hi556_test_pattern_menu); + if (ctrl_hdlr->error) + return ctrl_hdlr->error; + + hi556->sd.ctrl_handler = ctrl_hdlr; + + return 0; +} + +static void hi556_assign_pad_format(const struct hi556_mode *mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = mode->width; + fmt->height = mode->height; + fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->field = V4L2_FIELD_NONE; +} + +static int hi556_identify_module(struct hi556 *hi556) +{ + int ret; + u32 val; + + if (hi556->identified) + return 0; + + ret = hi556_read_reg(hi556, HI556_REG_CHIP_ID, + HI556_REG_VALUE_16BIT, &val); + if (ret) + return ret; + + if (val != HI556_CHIP_ID) { + dev_err(hi556->dev, "chip id mismatch: %x!=%x\n", + HI556_CHIP_ID, val); + return -ENXIO; + } + + hi556->identified = true; + + return 0; +} + +static const struct v4l2_rect * +__hi556_get_pad_crop(struct hi556 *hi556, + struct v4l2_subdev_state *sd_state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_state_get_crop(sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &hi556->cur_mode->crop; + } + + return NULL; +} + +static int hi556_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct hi556 *hi556 = to_hi556(sd); + + mutex_lock(&hi556->mutex); + sel->r = *__hi556_get_pad_crop(hi556, sd_state, sel->pad, + sel->which); + mutex_unlock(&hi556->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = HI556_NATIVE_WIDTH; + sel->r.height = HI556_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = HI556_PIXEL_ARRAY_TOP; + sel->r.left = HI556_PIXEL_ARRAY_LEFT; + sel->r.width = HI556_PIXEL_ARRAY_WIDTH; + sel->r.height = HI556_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + +static int hi556_start_streaming(struct hi556 *hi556) +{ + const struct hi556_reg_list *reg_list; + int link_freq_index, ret; + + ret = hi556_identify_module(hi556); + if (ret) + return ret; + + link_freq_index = hi556->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + ret = hi556_write_reg_list(hi556, reg_list); + if (ret) { + dev_err(hi556->dev, "failed to set plls\n"); + return ret; + } + + reg_list = &hi556->cur_mode->reg_list; + ret = hi556_write_reg_list(hi556, reg_list); + if (ret) { + dev_err(hi556->dev, "failed to set mode\n"); + return ret; + } + + ret = __v4l2_ctrl_handler_setup(hi556->sd.ctrl_handler); + if (ret) + return ret; + + ret = hi556_write_reg(hi556, HI556_REG_MODE_SELECT, + HI556_REG_VALUE_16BIT, HI556_MODE_STREAMING); + + if (ret) { + dev_err(hi556->dev, "failed to set stream\n"); + return ret; + } + + return 0; +} + +static void hi556_stop_streaming(struct hi556 *hi556) +{ + if (hi556_write_reg(hi556, HI556_REG_MODE_SELECT, + HI556_REG_VALUE_16BIT, HI556_MODE_STANDBY)) + dev_err(hi556->dev, "failed to set stream\n"); +} + +static int hi556_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct hi556 *hi556 = to_hi556(sd); + int ret = 0; + + mutex_lock(&hi556->mutex); + if (enable) { + ret = pm_runtime_resume_and_get(hi556->dev); + if (ret < 0) { + mutex_unlock(&hi556->mutex); + return ret; + } + + ret = hi556_start_streaming(hi556); + if (ret) { + hi556_stop_streaming(hi556); + pm_runtime_put(hi556->dev); + } + } else { + hi556_stop_streaming(hi556); + pm_runtime_put(hi556->dev); + } + + mutex_unlock(&hi556->mutex); + + return ret; +} + +static int hi556_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct hi556 *hi556 = to_hi556(sd); + const struct hi556_mode *mode; + s32 vblank_def, h_blank; + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), width, + height, fmt->format.width, + fmt->format.height); + + mutex_lock(&hi556->mutex); + hi556_assign_pad_format(mode, &fmt->format); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; + } else { + hi556->cur_mode = mode; + __v4l2_ctrl_s_ctrl(hi556->link_freq, mode->link_freq_index); + __v4l2_ctrl_s_ctrl_int64(hi556->pixel_rate, + to_pixel_rate(mode->link_freq_index)); + + /* Update limits and set FPS to default */ + vblank_def = mode->fll_def - mode->height; + __v4l2_ctrl_modify_range(hi556->vblank, + mode->fll_min - mode->height, + HI556_FLL_MAX - mode->height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(hi556->vblank, vblank_def); + + h_blank = hi556->cur_mode->llp - hi556->cur_mode->width; + + __v4l2_ctrl_modify_range(hi556->hblank, h_blank, h_blank, 1, + h_blank); + } + + mutex_unlock(&hi556->mutex); + + return 0; +} + +static int hi556_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct hi556 *hi556 = to_hi556(sd); + + mutex_lock(&hi556->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_state_get_format(sd_state, + fmt->pad); + else + hi556_assign_pad_format(hi556->cur_mode, &fmt->format); + + mutex_unlock(&hi556->mutex); + + return 0; +} + +static int hi556_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int hi556_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int hi556_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct hi556 *hi556 = to_hi556(sd); + struct v4l2_rect *try_crop; + + mutex_lock(&hi556->mutex); + hi556_assign_pad_format(&supported_modes[0], + v4l2_subdev_state_get_format(fh->state, 0)); + + /* Initialize try_crop rectangle. */ + try_crop = v4l2_subdev_state_get_crop(fh->state, 0); + try_crop->top = HI556_PIXEL_ARRAY_TOP; + try_crop->left = HI556_PIXEL_ARRAY_LEFT; + try_crop->width = HI556_PIXEL_ARRAY_WIDTH; + try_crop->height = HI556_PIXEL_ARRAY_HEIGHT; + + mutex_unlock(&hi556->mutex); + + return 0; +} + +static const struct v4l2_subdev_video_ops hi556_video_ops = { + .s_stream = hi556_set_stream, +}; + +static const struct v4l2_subdev_pad_ops hi556_pad_ops = { + .set_fmt = hi556_set_format, + .get_fmt = hi556_get_format, + .get_selection = hi556_get_selection, + .enum_mbus_code = hi556_enum_mbus_code, + .enum_frame_size = hi556_enum_frame_size, +}; + +static const struct v4l2_subdev_ops hi556_subdev_ops = { + .video = &hi556_video_ops, + .pad = &hi556_pad_ops, +}; + +static const struct media_entity_operations hi556_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops hi556_internal_ops = { + .open = hi556_open, +}; + +static int hi556_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret = 0; + unsigned int i, j; + + /* + * Sometimes the fwnode graph is initialized by the bridge driver, + * wait for this. + */ + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return dev_err_probe(dev, -EPROBE_DEFER, + "waiting for fwnode graph endpoint\n"); + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return dev_err_probe(dev, ret, "parsing endpoint failed\n"); + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != 2) { + dev_err(dev, "number of CSI2 data lanes %d is not supported\n", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto check_hwcfg_error; + } + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequencies defined\n"); + ret = -EINVAL; + goto check_hwcfg_error; + } + + for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) { + for (j = 0; j < bus_cfg.nr_of_link_frequencies; j++) { + if (link_freq_menu_items[i] == + bus_cfg.link_frequencies[j]) + break; + } + + if (j == bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequency %lld supported\n", + link_freq_menu_items[i]); + ret = -EINVAL; + goto check_hwcfg_error; + } + } + +check_hwcfg_error: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static void hi556_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct hi556 *hi556 = to_hi556(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + pm_runtime_disable(hi556->dev); + mutex_destroy(&hi556->mutex); +} + +static int hi556_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct hi556 *hi556 = to_hi556(sd); + + gpiod_set_value_cansleep(hi556->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(hi556_supply_names), + hi556->supplies); + clk_disable_unprepare(hi556->clk); + return 0; +} + +static int hi556_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct hi556 *hi556 = to_hi556(sd); + int ret; + + ret = clk_prepare_enable(hi556->clk); + if (ret) + return ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(hi556_supply_names), + hi556->supplies); + if (ret) { + dev_err(dev, "failed to enable regulators: %d", ret); + clk_disable_unprepare(hi556->clk); + return ret; + } + + if (hi556->reset_gpio) { + /* Assert reset for at least 2ms on back to back off-on */ + usleep_range(2000, 2200); + gpiod_set_value_cansleep(hi556->reset_gpio, 0); + } + + usleep_range(5000, 5500); + return 0; +} + +static int hi556_probe(struct i2c_client *client) +{ + struct hi556 *hi556; + unsigned long freq; + bool full_power; + int i, ret; + + ret = hi556_check_hwcfg(&client->dev); + if (ret) + return ret; + + hi556 = devm_kzalloc(&client->dev, sizeof(*hi556), GFP_KERNEL); + if (!hi556) + return -ENOMEM; + + hi556->dev = &client->dev; + + v4l2_i2c_subdev_init(&hi556->sd, client, &hi556_subdev_ops); + + hi556->reset_gpio = devm_gpiod_get_optional(hi556->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(hi556->reset_gpio)) + return dev_err_probe(hi556->dev, PTR_ERR(hi556->reset_gpio), + "failed to get reset GPIO\n"); + + hi556->clk = devm_v4l2_sensor_clk_get(hi556->dev, "clk"); + if (IS_ERR(hi556->clk)) + return dev_err_probe(hi556->dev, PTR_ERR(hi556->clk), + "failed to get clock\n"); + + freq = clk_get_rate(hi556->clk); + if (freq != HI556_MCLK) + return dev_err_probe(hi556->dev, -EINVAL, + "external clock %lu is not supported\n", + freq); + + for (i = 0; i < ARRAY_SIZE(hi556_supply_names); i++) + hi556->supplies[i].supply = hi556_supply_names[i]; + + ret = devm_regulator_bulk_get(hi556->dev, + ARRAY_SIZE(hi556_supply_names), + hi556->supplies); + if (ret) + return dev_err_probe(hi556->dev, ret, + "failed to get regulators\n"); + + full_power = acpi_dev_state_d0(hi556->dev); + if (full_power) { + /* Ensure non ACPI managed resources are enabled */ + ret = hi556_resume(hi556->dev); + if (ret) + return dev_err_probe(hi556->dev, ret, + "failed to power on sensor\n"); + + ret = hi556_identify_module(hi556); + if (ret) { + dev_err(hi556->dev, "failed to find sensor: %d\n", ret); + goto probe_error_power_off; + } + } + + mutex_init(&hi556->mutex); + hi556->cur_mode = &supported_modes[0]; + ret = hi556_init_controls(hi556); + if (ret) { + dev_err(hi556->dev, "failed to init controls: %d\n", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + hi556->sd.internal_ops = &hi556_internal_ops; + hi556->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + hi556->sd.entity.ops = &hi556_subdev_entity_ops; + hi556->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + hi556->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&hi556->sd.entity, 1, &hi556->pad); + if (ret) { + dev_err(hi556->dev, "failed to init entity pads: %d\n", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&hi556->sd); + if (ret < 0) { + dev_err(hi556->dev, "failed to register V4L2 subdev: %d\n", + ret); + goto probe_error_media_entity_cleanup; + } + + /* Set the device's state to active if it's in D0 state. */ + if (full_power) + pm_runtime_set_active(hi556->dev); + pm_runtime_enable(hi556->dev); + pm_runtime_idle(hi556->dev); + + return 0; + +probe_error_media_entity_cleanup: + media_entity_cleanup(&hi556->sd.entity); + +probe_error_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(hi556->sd.ctrl_handler); + mutex_destroy(&hi556->mutex); + +probe_error_power_off: + if (full_power) + hi556_suspend(hi556->dev); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(hi556_pm_ops, hi556_suspend, hi556_resume, + NULL); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id hi556_acpi_ids[] = { + {"INT3537"}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, hi556_acpi_ids); +#endif + +static struct i2c_driver hi556_i2c_driver = { + .driver = { + .name = "hi556", + .acpi_match_table = ACPI_PTR(hi556_acpi_ids), + .pm = pm_sleep_ptr(&hi556_pm_ops), + }, + .probe = hi556_probe, + .remove = hi556_remove, + .flags = I2C_DRV_ACPI_WAIVE_D0_PROBE, +}; + +module_i2c_driver(hi556_i2c_driver); + +MODULE_AUTHOR("Shawn Tu"); +MODULE_DESCRIPTION("Hynix HI556 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/hi846.c b/drivers/media/i2c/hi846.c new file mode 100644 index 000000000..a3f77b843 --- /dev/null +++ b/drivers/media/i2c/hi846.c @@ -0,0 +1,2176 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2021 Purism SPC + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HI846_MEDIA_BUS_FORMAT MEDIA_BUS_FMT_SGBRG10_1X10 +#define HI846_RGB_DEPTH 10 + +/* Frame length lines / vertical timings */ +#define HI846_REG_FLL 0x0006 +#define HI846_FLL_MAX 0xffff + +/* Horizontal timing */ +#define HI846_REG_LLP 0x0008 +#define HI846_LINE_LENGTH 3800 + +#define HI846_REG_BINNING_MODE 0x000c + +#define HI846_REG_IMAGE_ORIENTATION 0x000e + +#define HI846_REG_UNKNOWN_0022 0x0022 + +#define HI846_REG_Y_ADDR_START_VACT_H 0x0026 +#define HI846_REG_Y_ADDR_START_VACT_L 0x0027 +#define HI846_REG_UNKNOWN_0028 0x0028 + +#define HI846_REG_Y_ADDR_END_VACT_H 0x002c +#define HI846_REG_Y_ADDR_END_VACT_L 0x002d + +#define HI846_REG_Y_ODD_INC_FOBP 0x002e +#define HI846_REG_Y_EVEN_INC_FOBP 0x002f + +#define HI846_REG_Y_ODD_INC_VACT 0x0032 +#define HI846_REG_Y_EVEN_INC_VACT 0x0033 + +#define HI846_REG_GROUPED_PARA_HOLD 0x0046 + +#define HI846_REG_TG_ENABLE 0x004c + +#define HI846_REG_UNKNOWN_005C 0x005c + +#define HI846_REG_UNKNOWN_006A 0x006a + +/* + * Long exposure time. Actually, exposure is a 20 bit value that + * includes the lower 4 bits of 0x0073 too. Only 16 bits are used + * right now + */ +#define HI846_REG_EXPOSURE 0x0074 +#define HI846_EXPOSURE_MIN 6 +#define HI846_EXPOSURE_MAX_MARGIN 2 +#define HI846_EXPOSURE_STEP 1 + +/* Analog gain controls from sensor */ +#define HI846_REG_ANALOG_GAIN 0x0077 +#define HI846_ANAL_GAIN_MIN 0 +#define HI846_ANAL_GAIN_MAX 240 +#define HI846_ANAL_GAIN_STEP 8 + +/* Digital gain controls from sensor */ +#define HI846_REG_MWB_GR_GAIN_H 0x0078 +#define HI846_REG_MWB_GR_GAIN_L 0x0079 +#define HI846_REG_MWB_GB_GAIN_H 0x007a +#define HI846_REG_MWB_GB_GAIN_L 0x007b +#define HI846_REG_MWB_R_GAIN_H 0x007c +#define HI846_REG_MWB_R_GAIN_L 0x007d +#define HI846_REG_MWB_B_GAIN_H 0x007e +#define HI846_REG_MWB_B_GAIN_L 0x007f +#define HI846_DGTL_GAIN_MIN 512 +#define HI846_DGTL_GAIN_MAX 8191 +#define HI846_DGTL_GAIN_STEP 1 +#define HI846_DGTL_GAIN_DEFAULT 512 + +#define HI846_REG_X_ADDR_START_HACT_H 0x0120 +#define HI846_REG_X_ADDR_END_HACT_H 0x0122 + +#define HI846_REG_UNKNOWN_012A 0x012a + +#define HI846_REG_UNKNOWN_0200 0x0200 + +#define HI846_REG_UNKNOWN_021C 0x021c +#define HI846_REG_UNKNOWN_021E 0x021e + +#define HI846_REG_UNKNOWN_0402 0x0402 +#define HI846_REG_UNKNOWN_0404 0x0404 +#define HI846_REG_UNKNOWN_0408 0x0408 +#define HI846_REG_UNKNOWN_0410 0x0410 +#define HI846_REG_UNKNOWN_0412 0x0412 +#define HI846_REG_UNKNOWN_0414 0x0414 + +#define HI846_REG_UNKNOWN_0418 0x0418 + +#define HI846_REG_UNKNOWN_051E 0x051e + +/* Formatter */ +#define HI846_REG_X_START_H 0x0804 +#define HI846_REG_X_START_L 0x0805 + +/* MIPI */ +#define HI846_REG_UNKNOWN_0900 0x0900 +#define HI846_REG_MIPI_TX_OP_EN 0x0901 +#define HI846_REG_MIPI_TX_OP_MODE 0x0902 +#define HI846_RAW8 BIT(5) + +#define HI846_REG_UNKNOWN_090C 0x090c +#define HI846_REG_UNKNOWN_090E 0x090e + +#define HI846_REG_UNKNOWN_0914 0x0914 +#define HI846_REG_TLPX 0x0915 +#define HI846_REG_TCLK_PREPARE 0x0916 +#define HI846_REG_TCLK_ZERO 0x0917 +#define HI846_REG_UNKNOWN_0918 0x0918 +#define HI846_REG_THS_PREPARE 0x0919 +#define HI846_REG_THS_ZERO 0x091a +#define HI846_REG_THS_TRAIL 0x091b +#define HI846_REG_TCLK_POST 0x091c +#define HI846_REG_TCLK_TRAIL_MIN 0x091d +#define HI846_REG_UNKNOWN_091E 0x091e + +#define HI846_REG_UNKNOWN_0954 0x0954 +#define HI846_REG_UNKNOWN_0956 0x0956 +#define HI846_REG_UNKNOWN_0958 0x0958 +#define HI846_REG_UNKNOWN_095A 0x095a + +/* ISP Common */ +#define HI846_REG_MODE_SELECT 0x0a00 +#define HI846_MODE_STANDBY 0x00 +#define HI846_MODE_STREAMING 0x01 +#define HI846_REG_FAST_STANDBY_MODE 0x0a02 +#define HI846_REG_ISP_EN_H 0x0a04 + +/* Test Pattern Control */ +#define HI846_REG_ISP 0x0a05 +#define HI846_REG_ISP_TPG_EN 0x01 +#define HI846_REG_TEST_PATTERN 0x020a /* 1-9 */ + +#define HI846_REG_UNKNOWN_0A0C 0x0a0c + +/* Windowing */ +#define HI846_REG_X_OUTPUT_SIZE_H 0x0a12 +#define HI846_REG_X_OUTPUT_SIZE_L 0x0a13 +#define HI846_REG_Y_OUTPUT_SIZE_H 0x0a14 +#define HI846_REG_Y_OUTPUT_SIZE_L 0x0a15 + +/* ISP Common */ +#define HI846_REG_PEDESTAL_EN 0x0a1a + +#define HI846_REG_UNKNOWN_0A1E 0x0a1e + +/* Horizontal Binning Mode */ +#define HI846_REG_HBIN_MODE 0x0a22 + +#define HI846_REG_UNKNOWN_0A24 0x0a24 +#define HI846_REG_UNKNOWN_0B02 0x0b02 +#define HI846_REG_UNKNOWN_0B10 0x0b10 +#define HI846_REG_UNKNOWN_0B12 0x0b12 +#define HI846_REG_UNKNOWN_0B14 0x0b14 + +/* BLC (Black Level Calibration) */ +#define HI846_REG_BLC_CTL0 0x0c00 + +#define HI846_REG_UNKNOWN_0C06 0x0c06 +#define HI846_REG_UNKNOWN_0C10 0x0c10 +#define HI846_REG_UNKNOWN_0C12 0x0c12 +#define HI846_REG_UNKNOWN_0C14 0x0c14 +#define HI846_REG_UNKNOWN_0C16 0x0c16 + +#define HI846_REG_UNKNOWN_0E04 0x0e04 + +#define HI846_REG_CHIP_ID_L 0x0f16 +#define HI846_REG_CHIP_ID_H 0x0f17 +#define HI846_CHIP_ID_L 0x46 +#define HI846_CHIP_ID_H 0x08 + +#define HI846_REG_UNKNOWN_0F04 0x0f04 +#define HI846_REG_UNKNOWN_0F08 0x0f08 + +/* PLL */ +#define HI846_REG_PLL_CFG_MIPI2_H 0x0f2a +#define HI846_REG_PLL_CFG_MIPI2_L 0x0f2b + +#define HI846_REG_UNKNOWN_0F30 0x0f30 +#define HI846_REG_PLL_CFG_RAMP1_H 0x0f32 +#define HI846_REG_UNKNOWN_0F36 0x0f36 +#define HI846_REG_PLL_CFG_MIPI1_H 0x0f38 + +#define HI846_REG_UNKNOWN_2008 0x2008 +#define HI846_REG_UNKNOWN_326E 0x326e + +struct hi846_reg { + u16 address; + u16 val; +}; + +struct hi846_reg_list { + u32 num_of_regs; + const struct hi846_reg *regs; +}; + +struct hi846_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + /* Horizontal timing size */ + u32 llp; + + /* Link frequency needed for this resolution */ + u8 link_freq_index; + + u16 fps; + + /* Vertical timining size */ + u16 frame_len; + + const struct hi846_reg_list reg_list_config; + const struct hi846_reg_list reg_list_2lane; + const struct hi846_reg_list reg_list_4lane; + + /* Position inside of the 3264x2448 pixel array */ + struct v4l2_rect crop; +}; + +static const struct hi846_reg hi846_init_2lane[] = { + {HI846_REG_MODE_SELECT, 0x0000}, + /* regs below are unknown */ + {0x2000, 0x100a}, + {0x2002, 0x00ff}, + {0x2004, 0x0007}, + {0x2006, 0x3fff}, + {0x2008, 0x3fff}, + {0x200a, 0xc216}, + {0x200c, 0x1292}, + {0x200e, 0xc01a}, + {0x2010, 0x403d}, + {0x2012, 0x000e}, + {0x2014, 0x403e}, + {0x2016, 0x0b80}, + {0x2018, 0x403f}, + {0x201a, 0x82ae}, + {0x201c, 0x1292}, + {0x201e, 0xc00c}, + {0x2020, 0x4130}, + {0x2022, 0x43e2}, + {0x2024, 0x0180}, + {0x2026, 0x4130}, + {0x2028, 0x7400}, + {0x202a, 0x5000}, + {0x202c, 0x0253}, + {0x202e, 0x0ad1}, + {0x2030, 0x2360}, + {0x2032, 0x0009}, + {0x2034, 0x5020}, + {0x2036, 0x000b}, + {0x2038, 0x0002}, + {0x203a, 0x0044}, + {0x203c, 0x0016}, + {0x203e, 0x1792}, + {0x2040, 0x7002}, + {0x2042, 0x154f}, + {0x2044, 0x00d5}, + {0x2046, 0x000b}, + {0x2048, 0x0019}, + {0x204a, 0x1698}, + {0x204c, 0x000e}, + {0x204e, 0x099a}, + {0x2050, 0x0058}, + {0x2052, 0x7000}, + {0x2054, 0x1799}, + {0x2056, 0x0310}, + {0x2058, 0x03c3}, + {0x205a, 0x004c}, + {0x205c, 0x064a}, + {0x205e, 0x0001}, + {0x2060, 0x0007}, + {0x2062, 0x0bc7}, + {0x2064, 0x0055}, + {0x2066, 0x7000}, + {0x2068, 0x1550}, + {0x206a, 0x158a}, + {0x206c, 0x0004}, + {0x206e, 0x1488}, + {0x2070, 0x7010}, + {0x2072, 0x1508}, + {0x2074, 0x0004}, + {0x2076, 0x0016}, + {0x2078, 0x03d5}, + {0x207a, 0x0055}, + {0x207c, 0x08ca}, + {0x207e, 0x2019}, + {0x2080, 0x0007}, + {0x2082, 0x7057}, + {0x2084, 0x0fc7}, + {0x2086, 0x5041}, + {0x2088, 0x12c8}, + {0x208a, 0x5060}, + {0x208c, 0x5080}, + {0x208e, 0x2084}, + {0x2090, 0x12c8}, + {0x2092, 0x7800}, + {0x2094, 0x0802}, + {0x2096, 0x040f}, + {0x2098, 0x1007}, + {0x209a, 0x0803}, + {0x209c, 0x080b}, + {0x209e, 0x3803}, + {0x20a0, 0x0807}, + {0x20a2, 0x0404}, + {0x20a4, 0x0400}, + {0x20a6, 0xffff}, + {0x20a8, 0xf0b2}, + {0x20aa, 0xffef}, + {0x20ac, 0x0a84}, + {0x20ae, 0x1292}, + {0x20b0, 0xc02e}, + {0x20b2, 0x4130}, + {0x23fe, 0xc056}, + {0x3232, 0xfc0c}, + {0x3236, 0xfc22}, + {0x3248, 0xfca8}, + {0x326a, 0x8302}, + {0x326c, 0x830a}, + {0x326e, 0x0000}, + {0x32ca, 0xfc28}, + {0x32cc, 0xc3bc}, + {0x32ce, 0xc34c}, + {0x32d0, 0xc35a}, + {0x32d2, 0xc368}, + {0x32d4, 0xc376}, + {0x32d6, 0xc3c2}, + {0x32d8, 0xc3e6}, + {0x32da, 0x0003}, + {0x32dc, 0x0003}, + {0x32de, 0x00c7}, + {0x32e0, 0x0031}, + {0x32e2, 0x0031}, + {0x32e4, 0x0031}, + {0x32e6, 0xfc28}, + {0x32e8, 0xc3bc}, + {0x32ea, 0xc384}, + {0x32ec, 0xc392}, + {0x32ee, 0xc3a0}, + {0x32f0, 0xc3ae}, + {0x32f2, 0xc3c4}, + {0x32f4, 0xc3e6}, + {0x32f6, 0x0003}, + {0x32f8, 0x0003}, + {0x32fa, 0x00c7}, + {0x32fc, 0x0031}, + {0x32fe, 0x0031}, + {0x3300, 0x0031}, + {0x3302, 0x82ca}, + {0x3304, 0xc164}, + {0x3306, 0x82e6}, + {0x3308, 0xc19c}, + {0x330a, 0x001f}, + {0x330c, 0x001a}, + {0x330e, 0x0034}, + {0x3310, 0x0000}, + {0x3312, 0x0000}, + {0x3314, 0xfc94}, + {0x3316, 0xc3d8}, + /* regs above are unknown */ + {HI846_REG_MODE_SELECT, 0x0000}, + {HI846_REG_UNKNOWN_0E04, 0x0012}, + {HI846_REG_Y_ODD_INC_FOBP, 0x1111}, + {HI846_REG_Y_ODD_INC_VACT, 0x1111}, + {HI846_REG_UNKNOWN_0022, 0x0008}, + {HI846_REG_Y_ADDR_START_VACT_H, 0x0040}, + {HI846_REG_UNKNOWN_0028, 0x0017}, + {HI846_REG_Y_ADDR_END_VACT_H, 0x09cf}, + {HI846_REG_UNKNOWN_005C, 0x2101}, + {HI846_REG_FLL, 0x09de}, + {HI846_REG_LLP, 0x0ed8}, + {HI846_REG_IMAGE_ORIENTATION, 0x0100}, + {HI846_REG_BINNING_MODE, 0x0022}, + {HI846_REG_HBIN_MODE, 0x0000}, + {HI846_REG_UNKNOWN_0A24, 0x0000}, + {HI846_REG_X_START_H, 0x0000}, + {HI846_REG_X_OUTPUT_SIZE_H, 0x0cc0}, + {HI846_REG_Y_OUTPUT_SIZE_H, 0x0990}, + {HI846_REG_EXPOSURE, 0x09d8}, + {HI846_REG_ANALOG_GAIN, 0x0000}, + {HI846_REG_GROUPED_PARA_HOLD, 0x0000}, + {HI846_REG_UNKNOWN_051E, 0x0000}, + {HI846_REG_UNKNOWN_0200, 0x0400}, + {HI846_REG_PEDESTAL_EN, 0x0c00}, + {HI846_REG_UNKNOWN_0A0C, 0x0010}, + {HI846_REG_UNKNOWN_0A1E, 0x0ccf}, + {HI846_REG_UNKNOWN_0402, 0x0110}, + {HI846_REG_UNKNOWN_0404, 0x00f4}, + {HI846_REG_UNKNOWN_0408, 0x0000}, + {HI846_REG_UNKNOWN_0410, 0x008d}, + {HI846_REG_UNKNOWN_0412, 0x011a}, + {HI846_REG_UNKNOWN_0414, 0x864c}, + {HI846_REG_UNKNOWN_021C, 0x0003}, + {HI846_REG_UNKNOWN_021E, 0x0235}, + {HI846_REG_BLC_CTL0, 0x9150}, + {HI846_REG_UNKNOWN_0C06, 0x0021}, + {HI846_REG_UNKNOWN_0C10, 0x0040}, + {HI846_REG_UNKNOWN_0C12, 0x0040}, + {HI846_REG_UNKNOWN_0C14, 0x0040}, + {HI846_REG_UNKNOWN_0C16, 0x0040}, + {HI846_REG_FAST_STANDBY_MODE, 0x0100}, + {HI846_REG_ISP_EN_H, 0x014a}, + {HI846_REG_UNKNOWN_0418, 0x0000}, + {HI846_REG_UNKNOWN_012A, 0x03b4}, + {HI846_REG_X_ADDR_START_HACT_H, 0x0046}, + {HI846_REG_X_ADDR_END_HACT_H, 0x0376}, + {HI846_REG_UNKNOWN_0B02, 0xe04d}, + {HI846_REG_UNKNOWN_0B10, 0x6821}, + {HI846_REG_UNKNOWN_0B12, 0x0120}, + {HI846_REG_UNKNOWN_0B14, 0x0001}, + {HI846_REG_UNKNOWN_2008, 0x38fd}, + {HI846_REG_UNKNOWN_326E, 0x0000}, + {HI846_REG_UNKNOWN_0900, 0x0320}, + {HI846_REG_MIPI_TX_OP_MODE, 0xc31a}, + {HI846_REG_UNKNOWN_0914, 0xc109}, + {HI846_REG_TCLK_PREPARE, 0x061a}, + {HI846_REG_UNKNOWN_0918, 0x0306}, + {HI846_REG_THS_ZERO, 0x0b09}, + {HI846_REG_TCLK_POST, 0x0c07}, + {HI846_REG_UNKNOWN_091E, 0x0a00}, + {HI846_REG_UNKNOWN_090C, 0x042a}, + {HI846_REG_UNKNOWN_090E, 0x006b}, + {HI846_REG_UNKNOWN_0954, 0x0089}, + {HI846_REG_UNKNOWN_0956, 0x0000}, + {HI846_REG_UNKNOWN_0958, 0xca00}, + {HI846_REG_UNKNOWN_095A, 0x9240}, + {HI846_REG_UNKNOWN_0F08, 0x2f04}, + {HI846_REG_UNKNOWN_0F30, 0x001f}, + {HI846_REG_UNKNOWN_0F36, 0x001f}, + {HI846_REG_UNKNOWN_0F04, 0x3a00}, + {HI846_REG_PLL_CFG_RAMP1_H, 0x025a}, + {HI846_REG_PLL_CFG_MIPI1_H, 0x025a}, + {HI846_REG_PLL_CFG_MIPI2_H, 0x0024}, + {HI846_REG_UNKNOWN_006A, 0x0100}, + {HI846_REG_TG_ENABLE, 0x0100}, +}; + +static const struct hi846_reg hi846_init_4lane[] = { + {0x2000, 0x987a}, + {0x2002, 0x00ff}, + {0x2004, 0x0047}, + {0x2006, 0x3fff}, + {0x2008, 0x3fff}, + {0x200a, 0xc216}, + {0x200c, 0x1292}, + {0x200e, 0xc01a}, + {0x2010, 0x403d}, + {0x2012, 0x000e}, + {0x2014, 0x403e}, + {0x2016, 0x0b80}, + {0x2018, 0x403f}, + {0x201a, 0x82ae}, + {0x201c, 0x1292}, + {0x201e, 0xc00c}, + {0x2020, 0x4130}, + {0x2022, 0x43e2}, + {0x2024, 0x0180}, + {0x2026, 0x4130}, + {0x2028, 0x7400}, + {0x202a, 0x5000}, + {0x202c, 0x0253}, + {0x202e, 0x0ad1}, + {0x2030, 0x2360}, + {0x2032, 0x0009}, + {0x2034, 0x5020}, + {0x2036, 0x000b}, + {0x2038, 0x0002}, + {0x203a, 0x0044}, + {0x203c, 0x0016}, + {0x203e, 0x1792}, + {0x2040, 0x7002}, + {0x2042, 0x154f}, + {0x2044, 0x00d5}, + {0x2046, 0x000b}, + {0x2048, 0x0019}, + {0x204a, 0x1698}, + {0x204c, 0x000e}, + {0x204e, 0x099a}, + {0x2050, 0x0058}, + {0x2052, 0x7000}, + {0x2054, 0x1799}, + {0x2056, 0x0310}, + {0x2058, 0x03c3}, + {0x205a, 0x004c}, + {0x205c, 0x064a}, + {0x205e, 0x0001}, + {0x2060, 0x0007}, + {0x2062, 0x0bc7}, + {0x2064, 0x0055}, + {0x2066, 0x7000}, + {0x2068, 0x1550}, + {0x206a, 0x158a}, + {0x206c, 0x0004}, + {0x206e, 0x1488}, + {0x2070, 0x7010}, + {0x2072, 0x1508}, + {0x2074, 0x0004}, + {0x2076, 0x0016}, + {0x2078, 0x03d5}, + {0x207a, 0x0055}, + {0x207c, 0x08ca}, + {0x207e, 0x2019}, + {0x2080, 0x0007}, + {0x2082, 0x7057}, + {0x2084, 0x0fc7}, + {0x2086, 0x5041}, + {0x2088, 0x12c8}, + {0x208a, 0x5060}, + {0x208c, 0x5080}, + {0x208e, 0x2084}, + {0x2090, 0x12c8}, + {0x2092, 0x7800}, + {0x2094, 0x0802}, + {0x2096, 0x040f}, + {0x2098, 0x1007}, + {0x209a, 0x0803}, + {0x209c, 0x080b}, + {0x209e, 0x3803}, + {0x20a0, 0x0807}, + {0x20a2, 0x0404}, + {0x20a4, 0x0400}, + {0x20a6, 0xffff}, + {0x20a8, 0xf0b2}, + {0x20aa, 0xffef}, + {0x20ac, 0x0a84}, + {0x20ae, 0x1292}, + {0x20b0, 0xc02e}, + {0x20b2, 0x4130}, + {0x20b4, 0xf0b2}, + {0x20b6, 0xffbf}, + {0x20b8, 0x2004}, + {0x20ba, 0x403f}, + {0x20bc, 0x00c3}, + {0x20be, 0x4fe2}, + {0x20c0, 0x8318}, + {0x20c2, 0x43cf}, + {0x20c4, 0x0000}, + {0x20c6, 0x9382}, + {0x20c8, 0xc314}, + {0x20ca, 0x2003}, + {0x20cc, 0x12b0}, + {0x20ce, 0xcab0}, + {0x20d0, 0x4130}, + {0x20d2, 0x12b0}, + {0x20d4, 0xc90a}, + {0x20d6, 0x4130}, + {0x20d8, 0x42d2}, + {0x20da, 0x8318}, + {0x20dc, 0x00c3}, + {0x20de, 0x9382}, + {0x20e0, 0xc314}, + {0x20e2, 0x2009}, + {0x20e4, 0x120b}, + {0x20e6, 0x120a}, + {0x20e8, 0x1209}, + {0x20ea, 0x1208}, + {0x20ec, 0x1207}, + {0x20ee, 0x1206}, + {0x20f0, 0x4030}, + {0x20f2, 0xc15e}, + {0x20f4, 0x4130}, + {0x20f6, 0x1292}, + {0x20f8, 0xc008}, + {0x20fa, 0x4130}, + {0x20fc, 0x42d2}, + {0x20fe, 0x82a1}, + {0x2100, 0x00c2}, + {0x2102, 0x1292}, + {0x2104, 0xc040}, + {0x2106, 0x4130}, + {0x2108, 0x1292}, + {0x210a, 0xc006}, + {0x210c, 0x42a2}, + {0x210e, 0x7324}, + {0x2110, 0x9382}, + {0x2112, 0xc314}, + {0x2114, 0x2011}, + {0x2116, 0x425f}, + {0x2118, 0x82a1}, + {0x211a, 0xf25f}, + {0x211c, 0x00c1}, + {0x211e, 0xf35f}, + {0x2120, 0x2406}, + {0x2122, 0x425f}, + {0x2124, 0x00c0}, + {0x2126, 0xf37f}, + {0x2128, 0x522f}, + {0x212a, 0x4f82}, + {0x212c, 0x7324}, + {0x212e, 0x425f}, + {0x2130, 0x82d4}, + {0x2132, 0xf35f}, + {0x2134, 0x4fc2}, + {0x2136, 0x01b3}, + {0x2138, 0x93c2}, + {0x213a, 0x829f}, + {0x213c, 0x2421}, + {0x213e, 0x403e}, + {0x2140, 0xfffe}, + {0x2142, 0x40b2}, + {0x2144, 0xec78}, + {0x2146, 0x831c}, + {0x2148, 0x40b2}, + {0x214a, 0xec78}, + {0x214c, 0x831e}, + {0x214e, 0x40b2}, + {0x2150, 0xec78}, + {0x2152, 0x8320}, + {0x2154, 0xb3d2}, + {0x2156, 0x008c}, + {0x2158, 0x2405}, + {0x215a, 0x4e0f}, + {0x215c, 0x503f}, + {0x215e, 0xffd8}, + {0x2160, 0x4f82}, + {0x2162, 0x831c}, + {0x2164, 0x90f2}, + {0x2166, 0x0003}, + {0x2168, 0x008c}, + {0x216a, 0x2401}, + {0x216c, 0x4130}, + {0x216e, 0x421f}, + {0x2170, 0x831c}, + {0x2172, 0x5e0f}, + {0x2174, 0x4f82}, + {0x2176, 0x831e}, + {0x2178, 0x5e0f}, + {0x217a, 0x4f82}, + {0x217c, 0x8320}, + {0x217e, 0x3ff6}, + {0x2180, 0x432e}, + {0x2182, 0x3fdf}, + {0x2184, 0x421f}, + {0x2186, 0x7100}, + {0x2188, 0x4f0e}, + {0x218a, 0x503e}, + {0x218c, 0xffd8}, + {0x218e, 0x4e82}, + {0x2190, 0x7a04}, + {0x2192, 0x421e}, + {0x2194, 0x831c}, + {0x2196, 0x5f0e}, + {0x2198, 0x4e82}, + {0x219a, 0x7a06}, + {0x219c, 0x0b00}, + {0x219e, 0x7304}, + {0x21a0, 0x0050}, + {0x21a2, 0x40b2}, + {0x21a4, 0xd081}, + {0x21a6, 0x0b88}, + {0x21a8, 0x421e}, + {0x21aa, 0x831e}, + {0x21ac, 0x5f0e}, + {0x21ae, 0x4e82}, + {0x21b0, 0x7a0e}, + {0x21b2, 0x521f}, + {0x21b4, 0x8320}, + {0x21b6, 0x4f82}, + {0x21b8, 0x7a10}, + {0x21ba, 0x0b00}, + {0x21bc, 0x7304}, + {0x21be, 0x007a}, + {0x21c0, 0x40b2}, + {0x21c2, 0x0081}, + {0x21c4, 0x0b88}, + {0x21c6, 0x4392}, + {0x21c8, 0x7a0a}, + {0x21ca, 0x0800}, + {0x21cc, 0x7a0c}, + {0x21ce, 0x0b00}, + {0x21d0, 0x7304}, + {0x21d2, 0x022b}, + {0x21d4, 0x40b2}, + {0x21d6, 0xd081}, + {0x21d8, 0x0b88}, + {0x21da, 0x0b00}, + {0x21dc, 0x7304}, + {0x21de, 0x0255}, + {0x21e0, 0x40b2}, + {0x21e2, 0x0081}, + {0x21e4, 0x0b88}, + {0x21e6, 0x4130}, + {0x23fe, 0xc056}, + {0x3232, 0xfc0c}, + {0x3236, 0xfc22}, + {0x3238, 0xfcfc}, + {0x323a, 0xfd84}, + {0x323c, 0xfd08}, + {0x3246, 0xfcd8}, + {0x3248, 0xfca8}, + {0x324e, 0xfcb4}, + {0x326a, 0x8302}, + {0x326c, 0x830a}, + {0x326e, 0x0000}, + {0x32ca, 0xfc28}, + {0x32cc, 0xc3bc}, + {0x32ce, 0xc34c}, + {0x32d0, 0xc35a}, + {0x32d2, 0xc368}, + {0x32d4, 0xc376}, + {0x32d6, 0xc3c2}, + {0x32d8, 0xc3e6}, + {0x32da, 0x0003}, + {0x32dc, 0x0003}, + {0x32de, 0x00c7}, + {0x32e0, 0x0031}, + {0x32e2, 0x0031}, + {0x32e4, 0x0031}, + {0x32e6, 0xfc28}, + {0x32e8, 0xc3bc}, + {0x32ea, 0xc384}, + {0x32ec, 0xc392}, + {0x32ee, 0xc3a0}, + {0x32f0, 0xc3ae}, + {0x32f2, 0xc3c4}, + {0x32f4, 0xc3e6}, + {0x32f6, 0x0003}, + {0x32f8, 0x0003}, + {0x32fa, 0x00c7}, + {0x32fc, 0x0031}, + {0x32fe, 0x0031}, + {0x3300, 0x0031}, + {0x3302, 0x82ca}, + {0x3304, 0xc164}, + {0x3306, 0x82e6}, + {0x3308, 0xc19c}, + {0x330a, 0x001f}, + {0x330c, 0x001a}, + {0x330e, 0x0034}, + {0x3310, 0x0000}, + {0x3312, 0x0000}, + {0x3314, 0xfc94}, + {0x3316, 0xc3d8}, + + {0x0a00, 0x0000}, + {0x0e04, 0x0012}, + {0x002e, 0x1111}, + {0x0032, 0x1111}, + {0x0022, 0x0008}, + {0x0026, 0x0040}, + {0x0028, 0x0017}, + {0x002c, 0x09cf}, + {0x005c, 0x2101}, + {0x0006, 0x09de}, + {0x0008, 0x0ed8}, + {0x000e, 0x0100}, + {0x000c, 0x0022}, + {0x0a22, 0x0000}, + {0x0a24, 0x0000}, + {0x0804, 0x0000}, + {0x0a12, 0x0cc0}, + {0x0a14, 0x0990}, + {0x0074, 0x09d8}, + {0x0076, 0x0000}, + {0x051e, 0x0000}, + {0x0200, 0x0400}, + {0x0a1a, 0x0c00}, + {0x0a0c, 0x0010}, + {0x0a1e, 0x0ccf}, + {0x0402, 0x0110}, + {0x0404, 0x00f4}, + {0x0408, 0x0000}, + {0x0410, 0x008d}, + {0x0412, 0x011a}, + {0x0414, 0x864c}, + /* for OTP */ + {0x021c, 0x0003}, + {0x021e, 0x0235}, + /* for OTP */ + {0x0c00, 0x9950}, + {0x0c06, 0x0021}, + {0x0c10, 0x0040}, + {0x0c12, 0x0040}, + {0x0c14, 0x0040}, + {0x0c16, 0x0040}, + {0x0a02, 0x0100}, + {0x0a04, 0x015a}, + {0x0418, 0x0000}, + {0x0128, 0x0028}, + {0x012a, 0xffff}, + {0x0120, 0x0046}, + {0x0122, 0x0376}, + {0x012c, 0x0020}, + {0x012e, 0xffff}, + {0x0124, 0x0040}, + {0x0126, 0x0378}, + {0x0746, 0x0050}, + {0x0748, 0x01d5}, + {0x074a, 0x022b}, + {0x074c, 0x03b0}, + {0x0756, 0x043f}, + {0x0758, 0x3f1d}, + {0x0b02, 0xe04d}, + {0x0b10, 0x6821}, + {0x0b12, 0x0120}, + {0x0b14, 0x0001}, + {0x2008, 0x38fd}, + {0x326e, 0x0000}, + {0x0900, 0x0300}, + {0x0902, 0xc319}, + {0x0914, 0xc109}, + {0x0916, 0x061a}, + {0x0918, 0x0407}, + {0x091a, 0x0a0b}, + {0x091c, 0x0e08}, + {0x091e, 0x0a00}, + {0x090c, 0x0427}, + {0x090e, 0x0059}, + {0x0954, 0x0089}, + {0x0956, 0x0000}, + {0x0958, 0xca80}, + {0x095a, 0x9240}, + {0x0f08, 0x2f04}, + {0x0f30, 0x001f}, + {0x0f36, 0x001f}, + {0x0f04, 0x3a00}, + {0x0f32, 0x025a}, + {0x0f38, 0x025a}, + {0x0f2a, 0x4124}, + {0x006a, 0x0100}, + {0x004c, 0x0100}, + {0x0044, 0x0001}, +}; + +static const struct hi846_reg mode_640x480_config[] = { + {HI846_REG_MODE_SELECT, 0x0000}, + {HI846_REG_Y_ODD_INC_FOBP, 0x7711}, + {HI846_REG_Y_ODD_INC_VACT, 0x7711}, + {HI846_REG_Y_ADDR_START_VACT_H, 0x0148}, + {HI846_REG_Y_ADDR_END_VACT_H, 0x08c7}, + {HI846_REG_UNKNOWN_005C, 0x4404}, + {HI846_REG_FLL, 0x0277}, + {HI846_REG_LLP, 0x0ed8}, + {HI846_REG_BINNING_MODE, 0x0322}, + {HI846_REG_HBIN_MODE, 0x0200}, + {HI846_REG_UNKNOWN_0A24, 0x0000}, + {HI846_REG_X_START_H, 0x0058}, + {HI846_REG_X_OUTPUT_SIZE_H, 0x0280}, + {HI846_REG_Y_OUTPUT_SIZE_H, 0x01e0}, + + /* For OTP */ + {HI846_REG_UNKNOWN_021C, 0x0003}, + {HI846_REG_UNKNOWN_021E, 0x0235}, + + {HI846_REG_ISP_EN_H, 0x016a}, + {HI846_REG_UNKNOWN_0418, 0x0210}, + {HI846_REG_UNKNOWN_0B02, 0xe04d}, + {HI846_REG_UNKNOWN_0B10, 0x7021}, + {HI846_REG_UNKNOWN_0B12, 0x0120}, + {HI846_REG_UNKNOWN_0B14, 0x0001}, + {HI846_REG_UNKNOWN_2008, 0x38fd}, + {HI846_REG_UNKNOWN_326E, 0x0000}, +}; + +static const struct hi846_reg mode_640x480_mipi_2lane[] = { + {HI846_REG_UNKNOWN_0900, 0x0300}, + {HI846_REG_MIPI_TX_OP_MODE, 0x4319}, + {HI846_REG_UNKNOWN_0914, 0xc105}, + {HI846_REG_TCLK_PREPARE, 0x030c}, + {HI846_REG_UNKNOWN_0918, 0x0304}, + {HI846_REG_THS_ZERO, 0x0708}, + {HI846_REG_TCLK_POST, 0x0b04}, + {HI846_REG_UNKNOWN_091E, 0x0500}, + {HI846_REG_UNKNOWN_090C, 0x0208}, + {HI846_REG_UNKNOWN_090E, 0x009a}, + {HI846_REG_UNKNOWN_0954, 0x0089}, + {HI846_REG_UNKNOWN_0956, 0x0000}, + {HI846_REG_UNKNOWN_0958, 0xca80}, + {HI846_REG_UNKNOWN_095A, 0x9240}, + {HI846_REG_PLL_CFG_MIPI2_H, 0x4924}, + {HI846_REG_TG_ENABLE, 0x0100}, +}; + +static const struct hi846_reg mode_1280x720_config[] = { + {HI846_REG_MODE_SELECT, 0x0000}, + {HI846_REG_Y_ODD_INC_FOBP, 0x3311}, + {HI846_REG_Y_ODD_INC_VACT, 0x3311}, + {HI846_REG_Y_ADDR_START_VACT_H, 0x0238}, + {HI846_REG_Y_ADDR_END_VACT_H, 0x07d7}, + {HI846_REG_UNKNOWN_005C, 0x4202}, + {HI846_REG_FLL, 0x034a}, + {HI846_REG_LLP, 0x0ed8}, + {HI846_REG_BINNING_MODE, 0x0122}, + {HI846_REG_HBIN_MODE, 0x0100}, + {HI846_REG_UNKNOWN_0A24, 0x0000}, + {HI846_REG_X_START_H, 0x00b0}, + {HI846_REG_X_OUTPUT_SIZE_H, 0x0500}, + {HI846_REG_Y_OUTPUT_SIZE_H, 0x02d0}, + {HI846_REG_EXPOSURE, 0x0344}, + + /* For OTP */ + {HI846_REG_UNKNOWN_021C, 0x0003}, + {HI846_REG_UNKNOWN_021E, 0x0235}, + + {HI846_REG_ISP_EN_H, 0x016a}, + {HI846_REG_UNKNOWN_0418, 0x0410}, + {HI846_REG_UNKNOWN_0B02, 0xe04d}, + {HI846_REG_UNKNOWN_0B10, 0x6c21}, + {HI846_REG_UNKNOWN_0B12, 0x0120}, + {HI846_REG_UNKNOWN_0B14, 0x0005}, + {HI846_REG_UNKNOWN_2008, 0x38fd}, + {HI846_REG_UNKNOWN_326E, 0x0000}, +}; + +static const struct hi846_reg mode_1280x720_mipi_2lane[] = { + {HI846_REG_UNKNOWN_0900, 0x0300}, + {HI846_REG_MIPI_TX_OP_MODE, 0x4319}, + {HI846_REG_UNKNOWN_0914, 0xc109}, + {HI846_REG_TCLK_PREPARE, 0x061a}, + {HI846_REG_UNKNOWN_0918, 0x0407}, + {HI846_REG_THS_ZERO, 0x0a0b}, + {HI846_REG_TCLK_POST, 0x0e08}, + {HI846_REG_UNKNOWN_091E, 0x0a00}, + {HI846_REG_UNKNOWN_090C, 0x0427}, + {HI846_REG_UNKNOWN_090E, 0x0145}, + {HI846_REG_UNKNOWN_0954, 0x0089}, + {HI846_REG_UNKNOWN_0956, 0x0000}, + {HI846_REG_UNKNOWN_0958, 0xca80}, + {HI846_REG_UNKNOWN_095A, 0x9240}, + {HI846_REG_PLL_CFG_MIPI2_H, 0x4124}, + {HI846_REG_TG_ENABLE, 0x0100}, +}; + +static const struct hi846_reg mode_1280x720_mipi_4lane[] = { + /* 360Mbps */ + {HI846_REG_UNKNOWN_0900, 0x0300}, + {HI846_REG_MIPI_TX_OP_MODE, 0xc319}, + {HI846_REG_UNKNOWN_0914, 0xc105}, + {HI846_REG_TCLK_PREPARE, 0x030c}, + {HI846_REG_UNKNOWN_0918, 0x0304}, + {HI846_REG_THS_ZERO, 0x0708}, + {HI846_REG_TCLK_POST, 0x0b04}, + {HI846_REG_UNKNOWN_091E, 0x0500}, + {HI846_REG_UNKNOWN_090C, 0x0208}, + {HI846_REG_UNKNOWN_090E, 0x008a}, + {HI846_REG_UNKNOWN_0954, 0x0089}, + {HI846_REG_UNKNOWN_0956, 0x0000}, + {HI846_REG_UNKNOWN_0958, 0xca80}, + {HI846_REG_UNKNOWN_095A, 0x9240}, + {HI846_REG_PLL_CFG_MIPI2_H, 0x4924}, + {HI846_REG_TG_ENABLE, 0x0100}, +}; + +static const struct hi846_reg mode_1632x1224_config[] = { + {HI846_REG_MODE_SELECT, 0x0000}, + {HI846_REG_Y_ODD_INC_FOBP, 0x3311}, + {HI846_REG_Y_ODD_INC_VACT, 0x3311}, + {HI846_REG_Y_ADDR_START_VACT_H, 0x0040}, + {HI846_REG_Y_ADDR_END_VACT_H, 0x09cf}, + {HI846_REG_UNKNOWN_005C, 0x4202}, + {HI846_REG_FLL, 0x09de}, + {HI846_REG_LLP, 0x0ed8}, + {HI846_REG_BINNING_MODE, 0x0122}, + {HI846_REG_HBIN_MODE, 0x0100}, + {HI846_REG_UNKNOWN_0A24, 0x0000}, + {HI846_REG_X_START_H, 0x0000}, + {HI846_REG_X_OUTPUT_SIZE_H, 0x0660}, + {HI846_REG_Y_OUTPUT_SIZE_H, 0x04c8}, + {HI846_REG_EXPOSURE, 0x09d8}, + + /* For OTP */ + {HI846_REG_UNKNOWN_021C, 0x0003}, + {HI846_REG_UNKNOWN_021E, 0x0235}, + + {HI846_REG_ISP_EN_H, 0x016a}, + {HI846_REG_UNKNOWN_0418, 0x0000}, + {HI846_REG_UNKNOWN_0B02, 0xe04d}, + {HI846_REG_UNKNOWN_0B10, 0x6c21}, + {HI846_REG_UNKNOWN_0B12, 0x0120}, + {HI846_REG_UNKNOWN_0B14, 0x0005}, + {HI846_REG_UNKNOWN_2008, 0x38fd}, + {HI846_REG_UNKNOWN_326E, 0x0000}, +}; + +static const struct hi846_reg mode_1632x1224_mipi_2lane[] = { + {HI846_REG_UNKNOWN_0900, 0x0300}, + {HI846_REG_MIPI_TX_OP_MODE, 0x4319}, + {HI846_REG_UNKNOWN_0914, 0xc109}, + {HI846_REG_TCLK_PREPARE, 0x061a}, + {HI846_REG_UNKNOWN_0918, 0x0407}, + {HI846_REG_THS_ZERO, 0x0a0b}, + {HI846_REG_TCLK_POST, 0x0e08}, + {HI846_REG_UNKNOWN_091E, 0x0a00}, + {HI846_REG_UNKNOWN_090C, 0x0427}, + {HI846_REG_UNKNOWN_090E, 0x0069}, + {HI846_REG_UNKNOWN_0954, 0x0089}, + {HI846_REG_UNKNOWN_0956, 0x0000}, + {HI846_REG_UNKNOWN_0958, 0xca80}, + {HI846_REG_UNKNOWN_095A, 0x9240}, + {HI846_REG_PLL_CFG_MIPI2_H, 0x4124}, + {HI846_REG_TG_ENABLE, 0x0100}, +}; + +static const struct hi846_reg mode_1632x1224_mipi_4lane[] = { + {HI846_REG_UNKNOWN_0900, 0x0300}, + {HI846_REG_MIPI_TX_OP_MODE, 0xc319}, + {HI846_REG_UNKNOWN_0914, 0xc105}, + {HI846_REG_TCLK_PREPARE, 0x030c}, + {HI846_REG_UNKNOWN_0918, 0x0304}, + {HI846_REG_THS_ZERO, 0x0708}, + {HI846_REG_TCLK_POST, 0x0b04}, + {HI846_REG_UNKNOWN_091E, 0x0500}, + {HI846_REG_UNKNOWN_090C, 0x0208}, + {HI846_REG_UNKNOWN_090E, 0x001c}, + {HI846_REG_UNKNOWN_0954, 0x0089}, + {HI846_REG_UNKNOWN_0956, 0x0000}, + {HI846_REG_UNKNOWN_0958, 0xca80}, + {HI846_REG_UNKNOWN_095A, 0x9240}, + {HI846_REG_PLL_CFG_MIPI2_H, 0x4924}, + {HI846_REG_TG_ENABLE, 0x0100}, +}; + +static const char * const hi846_test_pattern_menu[] = { + "Disabled", + "Solid Colour", + "100% Colour Bars", + "Fade To Grey Colour Bars", + "PN9", + "Gradient Horizontal", + "Gradient Vertical", + "Check Board", + "Slant Pattern", + "Resolution Pattern", +}; + +#define FREQ_INDEX_640 0 +#define FREQ_INDEX_1280 1 +static const s64 hi846_link_freqs[] = { + [FREQ_INDEX_640] = 80000000, + [FREQ_INDEX_1280] = 200000000, +}; + +static const struct hi846_reg_list hi846_init_regs_list_2lane = { + .num_of_regs = ARRAY_SIZE(hi846_init_2lane), + .regs = hi846_init_2lane, +}; + +static const struct hi846_reg_list hi846_init_regs_list_4lane = { + .num_of_regs = ARRAY_SIZE(hi846_init_4lane), + .regs = hi846_init_4lane, +}; + +static const struct hi846_mode supported_modes[] = { + { + .width = 640, + .height = 480, + .link_freq_index = FREQ_INDEX_640, + .fps = 120, + .frame_len = 631, + .llp = HI846_LINE_LENGTH, + .reg_list_config = { + .num_of_regs = ARRAY_SIZE(mode_640x480_config), + .regs = mode_640x480_config, + }, + .reg_list_2lane = { + .num_of_regs = ARRAY_SIZE(mode_640x480_mipi_2lane), + .regs = mode_640x480_mipi_2lane, + }, + .reg_list_4lane = { + .num_of_regs = 0, + }, + .crop = { + .left = 0x58, + .top = 0x148, + .width = 640 * 4, + .height = 480 * 4, + }, + }, + { + .width = 1280, + .height = 720, + .link_freq_index = FREQ_INDEX_1280, + .fps = 90, + .frame_len = 842, + .llp = HI846_LINE_LENGTH, + .reg_list_config = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_config), + .regs = mode_1280x720_config, + }, + .reg_list_2lane = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_mipi_2lane), + .regs = mode_1280x720_mipi_2lane, + }, + .reg_list_4lane = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_mipi_4lane), + .regs = mode_1280x720_mipi_4lane, + }, + .crop = { + .left = 0xb0, + .top = 0x238, + .width = 1280 * 2, + .height = 720 * 2, + }, + }, + { + .width = 1632, + .height = 1224, + .link_freq_index = FREQ_INDEX_1280, + .fps = 30, + .frame_len = 2526, + .llp = HI846_LINE_LENGTH, + .reg_list_config = { + .num_of_regs = ARRAY_SIZE(mode_1632x1224_config), + .regs = mode_1632x1224_config, + }, + .reg_list_2lane = { + .num_of_regs = ARRAY_SIZE(mode_1632x1224_mipi_2lane), + .regs = mode_1632x1224_mipi_2lane, + }, + .reg_list_4lane = { + .num_of_regs = ARRAY_SIZE(mode_1632x1224_mipi_4lane), + .regs = mode_1632x1224_mipi_4lane, + }, + .crop = { + .left = 0x0, + .top = 0x0, + .width = 1632 * 2, + .height = 1224 * 2, + }, + } +}; + +struct hi846_datafmt { + u32 code; + enum v4l2_colorspace colorspace; +}; + +static const char * const hi846_supply_names[] = { + "vddio", /* Digital I/O (1.8V or 2.8V) */ + "vdda", /* Analog (2.8V) */ + "vddd", /* Digital Core (1.2V) */ +}; + +#define HI846_NUM_SUPPLIES ARRAY_SIZE(hi846_supply_names) + +struct hi846 { + struct gpio_desc *rst_gpio; + struct gpio_desc *shutdown_gpio; + struct regulator_bulk_data supplies[HI846_NUM_SUPPLIES]; + struct clk *clock; + const struct hi846_datafmt *fmt; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + u8 nr_lanes; + + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + struct mutex mutex; /* protect cur_mode, streaming and chip access */ + const struct hi846_mode *cur_mode; + bool streaming; +}; + +static inline struct hi846 *to_hi846(struct v4l2_subdev *sd) +{ + return container_of(sd, struct hi846, sd); +} + +static const struct hi846_datafmt hi846_colour_fmts[] = { + { HI846_MEDIA_BUS_FORMAT, V4L2_COLORSPACE_RAW }, +}; + +static const struct hi846_datafmt *hi846_find_datafmt(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(hi846_colour_fmts); i++) + if (hi846_colour_fmts[i].code == code) + return &hi846_colour_fmts[i]; + + return NULL; +} + +static inline u8 hi846_get_link_freq_index(struct hi846 *hi846) +{ + return hi846->cur_mode->link_freq_index; +} + +static u64 hi846_get_link_freq(struct hi846 *hi846) +{ + u8 index = hi846_get_link_freq_index(hi846); + + return hi846_link_freqs[index]; +} + +static u64 hi846_calc_pixel_rate(struct hi846 *hi846) +{ + u64 link_freq = hi846_get_link_freq(hi846); + u64 pixel_rate = link_freq * 2 * hi846->nr_lanes; + + do_div(pixel_rate, HI846_RGB_DEPTH); + + return pixel_rate; +} + +static int hi846_read_reg(struct hi846 *hi846, u16 reg, u8 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2]; + u8 data_buf[1] = {0}; + int ret; + + put_unaligned_be16(reg, addr_buf); + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(addr_buf); + msgs[0].buf = addr_buf; + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = 1; + msgs[1].buf = data_buf; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) { + dev_err(&client->dev, "i2c read error: %d\n", ret); + return -EIO; + } + + *val = data_buf[0]; + + return 0; +} + +static int hi846_write_reg(struct hi846 *hi846, u16 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + u8 buf[3] = { reg >> 8, reg & 0xff, val }; + struct i2c_msg msg[] = { + { .addr = client->addr, .flags = 0, + .len = ARRAY_SIZE(buf), .buf = buf }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(&client->dev, "i2c write error\n"); + return -EIO; + } + + return 0; +} + +static void hi846_write_reg_16(struct hi846 *hi846, u16 reg, u16 val, int *err) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + u8 buf[4]; + int ret; + + if (*err < 0) + return; + + put_unaligned_be16(reg, buf); + put_unaligned_be16(val, buf + 2); + ret = i2c_master_send(client, buf, sizeof(buf)); + if (ret != sizeof(buf)) { + dev_err(&client->dev, "i2c_master_send != %zu: %d\n", + sizeof(buf), ret); + *err = -EIO; + } +} + +static int hi846_write_reg_list(struct hi846 *hi846, + const struct hi846_reg_list *r_list) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + unsigned int i; + int ret = 0; + + for (i = 0; i < r_list->num_of_regs; i++) { + hi846_write_reg_16(hi846, r_list->regs[i].address, + r_list->regs[i].val, &ret); + if (ret) { + dev_err_ratelimited(&client->dev, + "failed to write reg 0x%4.4x: %d", + r_list->regs[i].address, ret); + return ret; + } + } + + return 0; +} + +static int hi846_update_digital_gain(struct hi846 *hi846, u16 d_gain) +{ + int ret = 0; + + hi846_write_reg_16(hi846, HI846_REG_MWB_GR_GAIN_H, d_gain, &ret); + hi846_write_reg_16(hi846, HI846_REG_MWB_GB_GAIN_H, d_gain, &ret); + hi846_write_reg_16(hi846, HI846_REG_MWB_R_GAIN_H, d_gain, &ret); + hi846_write_reg_16(hi846, HI846_REG_MWB_B_GAIN_H, d_gain, &ret); + + return ret; +} + +static int hi846_test_pattern(struct hi846 *hi846, u32 pattern) +{ + int ret; + u8 val; + + if (pattern) { + ret = hi846_read_reg(hi846, HI846_REG_ISP, &val); + if (ret) + return ret; + + ret = hi846_write_reg(hi846, HI846_REG_ISP, + val | HI846_REG_ISP_TPG_EN); + if (ret) + return ret; + } + + return hi846_write_reg(hi846, HI846_REG_TEST_PATTERN, pattern); +} + +static int hi846_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct hi846 *hi846 = container_of(ctrl->handler, + struct hi846, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + s64 exposure_max; + int ret = 0; + u32 shutter, frame_len; + + /* Propagate change of current control to all related controls */ + if (ctrl->id == V4L2_CID_VBLANK) { + /* Update max exposure while meeting expected vblanking */ + exposure_max = hi846->cur_mode->height + ctrl->val - + HI846_EXPOSURE_MAX_MARGIN; + __v4l2_ctrl_modify_range(hi846->exposure, + hi846->exposure->minimum, + exposure_max, hi846->exposure->step, + exposure_max); + } + + ret = pm_runtime_get_if_in_use(&client->dev); + if (!ret || ret == -EAGAIN) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = hi846_write_reg(hi846, HI846_REG_ANALOG_GAIN, ctrl->val); + break; + + case V4L2_CID_DIGITAL_GAIN: + ret = hi846_update_digital_gain(hi846, ctrl->val); + break; + + case V4L2_CID_EXPOSURE: + shutter = ctrl->val; + frame_len = hi846->cur_mode->frame_len; + + if (shutter > frame_len - 6) { /* margin */ + frame_len = shutter + 6; + if (frame_len > 0xffff) { /* max frame len */ + frame_len = 0xffff; + } + } + + if (shutter < 6) + shutter = 6; + if (shutter > (0xffff - 6)) + shutter = 0xffff - 6; + + hi846_write_reg_16(hi846, HI846_REG_FLL, frame_len, &ret); + hi846_write_reg_16(hi846, HI846_REG_EXPOSURE, shutter, &ret); + break; + + case V4L2_CID_VBLANK: + /* Update FLL that meets expected vertical blanking */ + hi846_write_reg_16(hi846, HI846_REG_FLL, + hi846->cur_mode->height + ctrl->val, &ret); + break; + case V4L2_CID_TEST_PATTERN: + ret = hi846_test_pattern(hi846, ctrl->val); + break; + + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops hi846_ctrl_ops = { + .s_ctrl = hi846_set_ctrl, +}; + +static int hi846_init_controls(struct hi846 *hi846) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max, h_blank; + int ret; + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + struct v4l2_fwnode_device_properties props; + + ctrl_hdlr = &hi846->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); + if (ret) + return ret; + + ctrl_hdlr->lock = &hi846->mutex; + + hi846->link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, &hi846_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(hi846_link_freqs) - 1, + 0, hi846_link_freqs); + if (hi846->link_freq) + hi846->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + hi846->pixel_rate = + v4l2_ctrl_new_std(ctrl_hdlr, &hi846_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, + hi846_calc_pixel_rate(hi846), 1, + hi846_calc_pixel_rate(hi846)); + hi846->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &hi846_ctrl_ops, + V4L2_CID_VBLANK, + hi846->cur_mode->frame_len - + hi846->cur_mode->height, + HI846_FLL_MAX - + hi846->cur_mode->height, 1, + hi846->cur_mode->frame_len - + hi846->cur_mode->height); + + h_blank = hi846->cur_mode->llp - hi846->cur_mode->width; + + hi846->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &hi846_ctrl_ops, + V4L2_CID_HBLANK, h_blank, h_blank, 1, + h_blank); + if (hi846->hblank) + hi846->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(ctrl_hdlr, &hi846_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + HI846_ANAL_GAIN_MIN, HI846_ANAL_GAIN_MAX, + HI846_ANAL_GAIN_STEP, HI846_ANAL_GAIN_MIN); + v4l2_ctrl_new_std(ctrl_hdlr, &hi846_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + HI846_DGTL_GAIN_MIN, HI846_DGTL_GAIN_MAX, + HI846_DGTL_GAIN_STEP, HI846_DGTL_GAIN_DEFAULT); + exposure_max = hi846->cur_mode->frame_len - HI846_EXPOSURE_MAX_MARGIN; + hi846->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &hi846_ctrl_ops, + V4L2_CID_EXPOSURE, + HI846_EXPOSURE_MIN, exposure_max, + HI846_EXPOSURE_STEP, + exposure_max); + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &hi846_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(hi846_test_pattern_menu) - 1, + 0, 0, hi846_test_pattern_menu); + if (ctrl_hdlr->error) { + dev_err(&client->dev, "v4l ctrl handler error: %d\n", + ctrl_hdlr->error); + ret = ctrl_hdlr->error; + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &hi846_ctrl_ops, + &props); + if (ret) + goto error; + + hi846->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + return ret; +} + +static int hi846_set_video_mode(struct hi846 *hi846, int fps) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + u64 frame_length; + int ret = 0; + int dummy_lines; + u64 link_freq = hi846_get_link_freq(hi846); + + dev_dbg(&client->dev, "%s: link freq: %llu\n", __func__, + hi846_get_link_freq(hi846)); + + do_div(link_freq, fps); + frame_length = link_freq; + do_div(frame_length, HI846_LINE_LENGTH); + + dummy_lines = (frame_length > hi846->cur_mode->frame_len) ? + (frame_length - hi846->cur_mode->frame_len) : 0; + + frame_length = hi846->cur_mode->frame_len + dummy_lines; + + dev_dbg(&client->dev, "%s: frame length calculated: %llu\n", __func__, + frame_length); + + hi846_write_reg_16(hi846, HI846_REG_FLL, frame_length & 0xFFFF, &ret); + hi846_write_reg_16(hi846, HI846_REG_LLP, + HI846_LINE_LENGTH & 0xFFFF, &ret); + + return ret; +} + +static int hi846_start_streaming(struct hi846 *hi846) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + int ret = 0; + u8 val; + + if (hi846->nr_lanes == 2) + ret = hi846_write_reg_list(hi846, &hi846_init_regs_list_2lane); + else + ret = hi846_write_reg_list(hi846, &hi846_init_regs_list_4lane); + if (ret) { + dev_err(&client->dev, "failed to set plls: %d\n", ret); + return ret; + } + + ret = hi846_write_reg_list(hi846, &hi846->cur_mode->reg_list_config); + if (ret) { + dev_err(&client->dev, "failed to set mode: %d\n", ret); + return ret; + } + + if (hi846->nr_lanes == 2) + ret = hi846_write_reg_list(hi846, + &hi846->cur_mode->reg_list_2lane); + else + ret = hi846_write_reg_list(hi846, + &hi846->cur_mode->reg_list_4lane); + if (ret) { + dev_err(&client->dev, "failed to set mipi mode: %d\n", ret); + return ret; + } + + hi846_set_video_mode(hi846, hi846->cur_mode->fps); + + ret = __v4l2_ctrl_handler_setup(hi846->sd.ctrl_handler); + if (ret) + return ret; + + /* + * Reading 0x0034 is purely done for debugging reasons: It is not + * documented in the DS but only mentioned once: + * "If 0x0034[2] bit is disabled , Visible pixel width and height is 0." + * So even though that sounds like we won't see anything, we don't + * know more about this, so in that case only inform the user but do + * nothing more. + */ + ret = hi846_read_reg(hi846, 0x0034, &val); + if (ret) + return ret; + if (!(val & BIT(2))) + dev_info(&client->dev, "visible pixel width and height is 0\n"); + + ret = hi846_write_reg(hi846, HI846_REG_MODE_SELECT, + HI846_MODE_STREAMING); + if (ret) { + dev_err(&client->dev, "failed to start stream"); + return ret; + } + + hi846->streaming = 1; + + dev_dbg(&client->dev, "%s: started streaming successfully\n", __func__); + + return ret; +} + +static void hi846_stop_streaming(struct hi846 *hi846) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + + if (hi846_write_reg(hi846, HI846_REG_MODE_SELECT, HI846_MODE_STANDBY)) + dev_err(&client->dev, "failed to stop stream"); + + hi846->streaming = 0; +} + +static int hi846_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct hi846 *hi846 = to_hi846(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&hi846->mutex); + + if (enable) { + ret = pm_runtime_resume_and_get(&client->dev); + if (ret) + goto out; + + ret = hi846_start_streaming(hi846); + } + + if (!enable || ret) { + hi846_stop_streaming(hi846); + pm_runtime_put(&client->dev); + } + +out: + mutex_unlock(&hi846->mutex); + + return ret; +} + +static int hi846_power_on(struct hi846 *hi846) +{ + int ret; + + ret = regulator_bulk_enable(HI846_NUM_SUPPLIES, hi846->supplies); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(hi846->clock); + if (ret < 0) + goto err_reg; + + if (hi846->shutdown_gpio) + gpiod_set_value_cansleep(hi846->shutdown_gpio, 0); + + /* 30us = 2400 cycles at 80Mhz */ + usleep_range(30, 60); + if (hi846->rst_gpio) + gpiod_set_value_cansleep(hi846->rst_gpio, 0); + usleep_range(30, 60); + + return 0; + +err_reg: + regulator_bulk_disable(HI846_NUM_SUPPLIES, hi846->supplies); + + return ret; +} + +static int hi846_power_off(struct hi846 *hi846) +{ + if (hi846->rst_gpio) + gpiod_set_value_cansleep(hi846->rst_gpio, 1); + + if (hi846->shutdown_gpio) + gpiod_set_value_cansleep(hi846->shutdown_gpio, 1); + + clk_disable_unprepare(hi846->clock); + return regulator_bulk_disable(HI846_NUM_SUPPLIES, hi846->supplies); +} + +static int __maybe_unused hi846_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct hi846 *hi846 = to_hi846(sd); + + return hi846_power_off(hi846); +} + +static int __maybe_unused hi846_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct hi846 *hi846 = to_hi846(sd); + + return hi846_power_on(hi846); +} + +static int hi846_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct hi846 *hi846 = to_hi846(sd); + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + const struct hi846_datafmt *fmt = hi846_find_datafmt(mf->code); + u32 tgt_fps; + s32 vblank_def, h_blank; + + if (!fmt) { + mf->code = hi846_colour_fmts[0].code; + mf->colorspace = hi846_colour_fmts[0].colorspace; + fmt = &hi846_colour_fmts[0]; + } + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_state_get_format(sd_state, format->pad) = *mf; + return 0; + } + + if (hi846->nr_lanes == 2) { + if (!hi846->cur_mode->reg_list_2lane.num_of_regs) { + dev_err(&client->dev, + "this mode is not supported for 2 lanes\n"); + return -EINVAL; + } + } else { + if (!hi846->cur_mode->reg_list_4lane.num_of_regs) { + dev_err(&client->dev, + "this mode is not supported for 4 lanes\n"); + return -EINVAL; + } + } + + mutex_lock(&hi846->mutex); + + if (hi846->streaming) { + mutex_unlock(&hi846->mutex); + return -EBUSY; + } + + hi846->fmt = fmt; + + hi846->cur_mode = + v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, mf->width, mf->height); + dev_dbg(&client->dev, "%s: found mode: %dx%d\n", __func__, + hi846->cur_mode->width, hi846->cur_mode->height); + + tgt_fps = hi846->cur_mode->fps; + dev_dbg(&client->dev, "%s: target fps: %d\n", __func__, tgt_fps); + + mf->width = hi846->cur_mode->width; + mf->height = hi846->cur_mode->height; + mf->code = HI846_MEDIA_BUS_FORMAT; + mf->field = V4L2_FIELD_NONE; + + __v4l2_ctrl_s_ctrl(hi846->link_freq, hi846_get_link_freq_index(hi846)); + __v4l2_ctrl_s_ctrl_int64(hi846->pixel_rate, + hi846_calc_pixel_rate(hi846)); + + /* Update limits and set FPS to default */ + vblank_def = hi846->cur_mode->frame_len - hi846->cur_mode->height; + __v4l2_ctrl_modify_range(hi846->vblank, + hi846->cur_mode->frame_len - + hi846->cur_mode->height, + HI846_FLL_MAX - hi846->cur_mode->height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(hi846->vblank, vblank_def); + + h_blank = hi846->cur_mode->llp - hi846->cur_mode->width; + + __v4l2_ctrl_modify_range(hi846->hblank, h_blank, h_blank, 1, + h_blank); + + dev_dbg(&client->dev, "Set fmt w=%d h=%d code=0x%x colorspace=0x%x\n", + mf->width, mf->height, + fmt->code, fmt->colorspace); + + mutex_unlock(&hi846->mutex); + + return 0; +} + +static int hi846_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct hi846 *hi846 = to_hi846(sd); + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + format->format = *v4l2_subdev_state_get_format(sd_state, + format->pad); + return 0; + } + + mutex_lock(&hi846->mutex); + mf->code = HI846_MEDIA_BUS_FORMAT; + mf->colorspace = V4L2_COLORSPACE_RAW; + mf->field = V4L2_FIELD_NONE; + mf->width = hi846->cur_mode->width; + mf->height = hi846->cur_mode->height; + mutex_unlock(&hi846->mutex); + dev_dbg(&client->dev, + "Get format w=%d h=%d code=0x%x colorspace=0x%x\n", + mf->width, mf->height, mf->code, mf->colorspace); + + return 0; +} + +static int hi846_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index > 0) + return -EINVAL; + + code->code = HI846_MEDIA_BUS_FORMAT; + + return 0; +} + +static int hi846_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (fse->pad || fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != HI846_MEDIA_BUS_FORMAT) { + dev_err(&client->dev, "frame size enum not matching\n"); + return -EINVAL; + } + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = supported_modes[fse->index].width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = supported_modes[fse->index].height; + + dev_dbg(&client->dev, "%s: max width: %d max height: %d\n", __func__, + fse->max_width, fse->max_height); + + return 0; +} + +static int hi846_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct hi846 *hi846 = to_hi846(sd); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + mutex_lock(&hi846->mutex); + switch (sel->which) { + case V4L2_SUBDEV_FORMAT_TRY: + sel->r = *v4l2_subdev_state_get_crop(sd_state, sel->pad); + break; + case V4L2_SUBDEV_FORMAT_ACTIVE: + sel->r = hi846->cur_mode->crop; + break; + } + mutex_unlock(&hi846->mutex); + return 0; + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = 3264; + sel->r.height = 2448; + return 0; + default: + return -EINVAL; + } +} + +static int hi846_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct hi846 *hi846 = to_hi846(sd); + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_state_get_format(sd_state, 0); + + mutex_lock(&hi846->mutex); + mf->code = HI846_MEDIA_BUS_FORMAT; + mf->colorspace = V4L2_COLORSPACE_RAW; + mf->field = V4L2_FIELD_NONE; + mf->width = hi846->cur_mode->width; + mf->height = hi846->cur_mode->height; + mutex_unlock(&hi846->mutex); + + return 0; +} + +static const struct v4l2_subdev_video_ops hi846_video_ops = { + .s_stream = hi846_set_stream, +}; + +static const struct v4l2_subdev_pad_ops hi846_pad_ops = { + .enum_frame_size = hi846_enum_frame_size, + .enum_mbus_code = hi846_enum_mbus_code, + .set_fmt = hi846_set_format, + .get_fmt = hi846_get_format, + .get_selection = hi846_get_selection, +}; + +static const struct v4l2_subdev_ops hi846_subdev_ops = { + .video = &hi846_video_ops, + .pad = &hi846_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops hi846_internal_ops = { + .init_state = hi846_init_state, +}; + +static const struct media_entity_operations hi846_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int hi846_identify_module(struct hi846 *hi846) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + int ret; + u8 hi, lo; + + ret = hi846_read_reg(hi846, HI846_REG_CHIP_ID_L, &lo); + if (ret) + return ret; + + if (lo != HI846_CHIP_ID_L) { + dev_err(&client->dev, "wrong chip id low byte: %x", lo); + return -ENXIO; + } + + ret = hi846_read_reg(hi846, HI846_REG_CHIP_ID_H, &hi); + if (ret) + return ret; + + if (hi != HI846_CHIP_ID_H) { + dev_err(&client->dev, "wrong chip id high byte: %x", hi); + return -ENXIO; + } + + dev_info(&client->dev, "chip id %02X %02X using %d mipi lanes\n", + hi, lo, hi846->nr_lanes); + + return 0; +} + +static s64 hi846_check_link_freqs(struct hi846 *hi846, + struct v4l2_fwnode_endpoint *ep) +{ + const s64 *freqs = hi846_link_freqs; + int freqs_count = ARRAY_SIZE(hi846_link_freqs); + int i, j; + + for (i = 0; i < freqs_count; i++) { + for (j = 0; j < ep->nr_of_link_frequencies; j++) + if (freqs[i] == ep->link_frequencies[j]) + break; + if (j == ep->nr_of_link_frequencies) + return freqs[i]; + } + + return 0; +} + +static int hi846_parse_dt(struct hi846 *hi846, struct device *dev) +{ + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret; + s64 fq; + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) { + dev_err(dev, "unable to find endpoint node\n"); + return -ENXIO; + } + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) { + dev_err(dev, "failed to parse endpoint node: %d\n", ret); + return ret; + } + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != 2 && + bus_cfg.bus.mipi_csi2.num_data_lanes != 4) { + dev_err(dev, "number of CSI2 data lanes %d is not supported", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto check_hwcfg_error; + } + + hi846->nr_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "link-frequency property not found in DT\n"); + ret = -EINVAL; + goto check_hwcfg_error; + } + + /* Check that link frequences for all the modes are in device tree */ + fq = hi846_check_link_freqs(hi846, &bus_cfg); + if (fq) { + dev_err(dev, "Link frequency of %lld is not supported\n", fq); + ret = -EINVAL; + goto check_hwcfg_error; + } + + v4l2_fwnode_endpoint_free(&bus_cfg); + + hi846->rst_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(hi846->rst_gpio)) { + dev_err(dev, "failed to get reset gpio: %pe\n", + hi846->rst_gpio); + return PTR_ERR(hi846->rst_gpio); + } + + hi846->shutdown_gpio = devm_gpiod_get_optional(dev, "shutdown", + GPIOD_OUT_LOW); + if (IS_ERR(hi846->shutdown_gpio)) { + dev_err(dev, "failed to get shutdown gpio: %pe\n", + hi846->shutdown_gpio); + return PTR_ERR(hi846->shutdown_gpio); + } + + return 0; + +check_hwcfg_error: + v4l2_fwnode_endpoint_free(&bus_cfg); + return ret; +} + +static int hi846_probe(struct i2c_client *client) +{ + struct hi846 *hi846; + int ret; + int i; + u32 mclk_freq; + + hi846 = devm_kzalloc(&client->dev, sizeof(*hi846), GFP_KERNEL); + if (!hi846) + return -ENOMEM; + + ret = hi846_parse_dt(hi846, &client->dev); + if (ret) { + dev_err(&client->dev, "failed to check HW configuration: %d", + ret); + return ret; + } + + hi846->clock = devm_v4l2_sensor_clk_get(&client->dev, NULL); + if (IS_ERR(hi846->clock)) + return dev_err_probe(&client->dev, PTR_ERR(hi846->clock), + "failed to get clock: %pe\n", + hi846->clock); + + mclk_freq = clk_get_rate(hi846->clock); + if (mclk_freq != 25000000) + dev_warn(&client->dev, + "External clock freq should be 25000000, not %u.\n", + mclk_freq); + + for (i = 0; i < HI846_NUM_SUPPLIES; i++) + hi846->supplies[i].supply = hi846_supply_names[i]; + + ret = devm_regulator_bulk_get(&client->dev, HI846_NUM_SUPPLIES, + hi846->supplies); + if (ret < 0) + return ret; + + v4l2_i2c_subdev_init(&hi846->sd, client, &hi846_subdev_ops); + hi846->sd.internal_ops = &hi846_internal_ops; + + mutex_init(&hi846->mutex); + + ret = hi846_power_on(hi846); + if (ret) + goto err_mutex; + + ret = hi846_identify_module(hi846); + if (ret) + goto err_power_off; + + hi846->cur_mode = &supported_modes[0]; + + ret = hi846_init_controls(hi846); + if (ret) { + dev_err(&client->dev, "failed to init controls: %d", ret); + goto err_power_off; + } + + hi846->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + hi846->sd.entity.ops = &hi846_subdev_entity_ops; + hi846->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + hi846->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&hi846->sd.entity, 1, &hi846->pad); + if (ret) { + dev_err(&client->dev, "failed to init entity pads: %d", ret); + goto err_v4l2_ctrl_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&hi846->sd); + if (ret < 0) { + dev_err(&client->dev, "failed to register V4L2 subdev: %d", + ret); + goto err_media_entity_cleanup; + } + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +err_media_entity_cleanup: + media_entity_cleanup(&hi846->sd.entity); + +err_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(hi846->sd.ctrl_handler); + +err_power_off: + hi846_power_off(hi846); + +err_mutex: + mutex_destroy(&hi846->mutex); + + return ret; +} + +static void hi846_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct hi846 *hi846 = to_hi846(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + hi846_suspend(&client->dev); + pm_runtime_set_suspended(&client->dev); + + mutex_destroy(&hi846->mutex); +} + +static const struct dev_pm_ops hi846_pm_ops = { + SET_RUNTIME_PM_OPS(hi846_suspend, hi846_resume, NULL) +}; + +static const struct of_device_id hi846_of_match[] = { + { .compatible = "hynix,hi846", }, + {}, +}; +MODULE_DEVICE_TABLE(of, hi846_of_match); + +static struct i2c_driver hi846_i2c_driver = { + .driver = { + .name = "hi846", + .pm = &hi846_pm_ops, + .of_match_table = hi846_of_match, + }, + .probe = hi846_probe, + .remove = hi846_remove, +}; + +module_i2c_driver(hi846_i2c_driver); + +MODULE_AUTHOR("Angus Ainslie "); +MODULE_AUTHOR("Martin Kepplinger "); +MODULE_DESCRIPTION("Hynix HI846 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/hi847.c b/drivers/media/i2c/hi847.c new file mode 100644 index 000000000..def01aa07 --- /dev/null +++ b/drivers/media/i2c/hi847.c @@ -0,0 +1,2956 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define HI847_REG_VALUE_08BIT 1 +#define HI847_REG_VALUE_16BIT 2 +#define HI847_REG_VALUE_24BIT 3 + +#define HI847_LINK_FREQ_400MHZ 400000000ULL +#define HI847_LINK_FREQ_200MHZ 200000000ULL +#define HI847_SCLK 72000000ULL +#define HI847_MCLK 19200000 +#define HI847_DATA_LANES 4 +#define HI847_RGB_DEPTH 10 + +#define HI847_REG_CHIP_ID 0x0716 +#define HI847_CHIP_ID 0x0847 + +#define HI847_REG_MODE_SELECT 0x0B00 +#define HI847_MODE_STANDBY 0x0000 +#define HI847_MODE_STREAMING 0x0100 + +#define HI847_REG_MODE_TG 0x027E +#define HI847_REG_MODE_TG_ENABLE 0x0100 +#define HI847_REG_MODE_TG_DISABLE 0x0000 + +/* vertical-timings from sensor */ +#define HI847_REG_FLL 0x020E +#define HI847_FLL_30FPS 0x0B51 +#define HI847_FLL_30FPS_MIN 0x0B51 +#define HI847_FLL_60FPS 0x05A9 +#define HI847_FLL_60FPS_MIN 0x05A9 +#define HI847_FLL_MAX 0x7fff + +/* horizontal-timings from sensor */ +#define HI847_REG_LLP 0x0206 + +/* Exposure controls from sensor */ +#define HI847_REG_EXPOSURE 0x020A +#define HI847_EXPOSURE_MIN 4 +#define HI847_EXPOSURE_MAX_MARGIN 4 +#define HI847_EXPOSURE_STEP 1 + +/* Analog gain controls from sensor */ +#define HI847_REG_ANALOG_GAIN 0x0212 +#define HI847_ANAL_GAIN_MIN 0 +#define HI847_ANAL_GAIN_MAX 240 +#define HI847_ANAL_GAIN_STEP 1 + +/* Digital gain controls from sensor */ +#define HI847_REG_MWB_GR_GAIN 0x0214 +#define HI847_REG_MWB_GB_GAIN 0x0216 +#define HI847_REG_MWB_R_GAIN 0x0218 +#define HI847_REG_MWB_B_GAIN 0x021A +#define HI847_DGTL_GAIN_MIN 1 +#define HI847_DGTL_GAIN_MAX 8191 +#define HI847_DGTL_GAIN_STEP 1 +#define HI847_DGTL_GAIN_DEFAULT 512 + +/* Test Pattern Control */ +#define HI847_REG_ISP 0X0B04 +#define HI847_REG_ISP_TPG_EN 0x0001 +#define HI847_REG_TEST_PATTERN 0x0C0A + +/* Flip Mirror Controls from sensor */ +#define HI847_REG_MIRROR_FLIP 0x0202 + +#define HI847_REG_FORMAT_X 0x0F04 +#define HI847_REG_FORMAT_Y 0x0F06 + +enum { + HI847_LINK_FREQ_400MHZ_INDEX, + HI847_LINK_FREQ_200MHZ_INDEX, +}; + +struct hi847_reg { + u16 address; + u16 val; +}; + +struct hi847_reg_list { + u32 num_of_regs; + const struct hi847_reg *regs; +}; + +struct hi847_link_freq_config { + const struct hi847_reg_list reg_list; +}; + +struct hi847_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + /* Horizontal timining size */ + u32 llp; + + /* Default vertical timining size */ + u32 fll_def; + + /* Min vertical timining size */ + u32 fll_min; + + /* Link frequency needed for this resolution */ + u32 link_freq_index; + + /* Sensor register settings for this resolution */ + const struct hi847_reg_list reg_list; +}; + +#define to_hi847(_sd) container_of(_sd, struct hi847, sd) + +//SENSOR_INITIALIZATION +static const struct hi847_reg mipi_data_rate_lane_4[] = { + {0x0790, 0x0100}, + {0x2000, 0x0000}, + {0x2002, 0x0058}, + {0x2006, 0x40B2}, + {0x2008, 0xB05C}, + {0x200A, 0x8446}, + {0x200C, 0x40B2}, + {0x200E, 0xB082}, + {0x2010, 0x8450}, + {0x2012, 0x40B2}, + {0x2014, 0xB0AE}, + {0x2016, 0x84C6}, + {0x2018, 0x40B2}, + {0x201A, 0xB11A}, + {0x201C, 0x84BC}, + {0x201E, 0x40B2}, + {0x2020, 0xB34A}, + {0x2022, 0x84B4}, + {0x2024, 0x40B2}, + {0x2026, 0xB386}, + {0x2028, 0x84B0}, + {0x202A, 0x40B2}, + {0x202C, 0xB3B4}, + {0x202E, 0x84B8}, + {0x2030, 0x40B2}, + {0x2032, 0xB0F4}, + {0x2034, 0x8470}, + {0x2036, 0x40B2}, + {0x2038, 0xB3EA}, + {0x203A, 0x847C}, + {0x203C, 0x40B2}, + {0x203E, 0xB658}, + {0x2040, 0x8478}, + {0x2042, 0x40B2}, + {0x2044, 0xB67E}, + {0x2046, 0x847E}, + {0x2048, 0x40B2}, + {0x204A, 0xB78E}, + {0x204C, 0x843A}, + {0x204E, 0x40B2}, + {0x2050, 0xB980}, + {0x2052, 0x845C}, + {0x2054, 0x40B2}, + {0x2056, 0xB9B0}, + {0x2058, 0x845E}, + {0x205A, 0x4130}, + {0x205C, 0x1292}, + {0x205E, 0xD016}, + {0x2060, 0xB3D2}, + {0x2062, 0x0B00}, + {0x2064, 0x2002}, + {0x2066, 0xD2E2}, + {0x2068, 0x0381}, + {0x206A, 0x93C2}, + {0x206C, 0x0263}, + {0x206E, 0x2001}, + {0x2070, 0x4130}, + {0x2072, 0x422D}, + {0x2074, 0x403E}, + {0x2076, 0x888E}, + {0x2078, 0x403F}, + {0x207A, 0x192A}, + {0x207C, 0x1292}, + {0x207E, 0x843E}, + {0x2080, 0x3FF7}, + {0x2082, 0x422D}, + {0x2084, 0x403E}, + {0x2086, 0x192A}, + {0x2088, 0x403F}, + {0x208A, 0x888E}, + {0x208C, 0x1292}, + {0x208E, 0x843E}, + {0x2090, 0xB3D2}, + {0x2092, 0x0267}, + {0x2094, 0x2403}, + {0x2096, 0xD0F2}, + {0x2098, 0x0040}, + {0x209A, 0x0381}, + {0x209C, 0x90F2}, + {0x209E, 0x0010}, + {0x20A0, 0x0260}, + {0x20A2, 0x2002}, + {0x20A4, 0x1292}, + {0x20A6, 0x84BC}, + {0x20A8, 0x1292}, + {0x20AA, 0xD020}, + {0x20AC, 0x4130}, + {0x20AE, 0x1292}, + {0x20B0, 0x8470}, + {0x20B2, 0x1292}, + {0x20B4, 0x8452}, + {0x20B6, 0x0900}, + {0x20B8, 0x7118}, + {0x20BA, 0x1292}, + {0x20BC, 0x848E}, + {0x20BE, 0x0900}, + {0x20C0, 0x7112}, + {0x20C2, 0x0800}, + {0x20C4, 0x7A20}, + {0x20C6, 0x4292}, + {0x20C8, 0x87DE}, + {0x20CA, 0x7334}, + {0x20CC, 0x0F00}, + {0x20CE, 0x7304}, + {0x20D0, 0x421F}, + {0x20D2, 0x8718}, + {0x20D4, 0x1292}, + {0x20D6, 0x846E}, + {0x20D8, 0x1292}, + {0x20DA, 0x8488}, + {0x20DC, 0x0B00}, + {0x20DE, 0x7114}, + {0x20E0, 0x0002}, + {0x20E2, 0x1292}, + {0x20E4, 0x848C}, + {0x20E6, 0x1292}, + {0x20E8, 0x8454}, + {0x20EA, 0x43C2}, + {0x20EC, 0x86EE}, + {0x20EE, 0x1292}, + {0x20F0, 0x8444}, + {0x20F2, 0x4130}, + {0x20F4, 0x4392}, + {0x20F6, 0x7360}, + {0x20F8, 0xB3D2}, + {0x20FA, 0x0B00}, + {0x20FC, 0x2402}, + {0x20FE, 0xC2E2}, + {0x2100, 0x0381}, + {0x2102, 0x0900}, + {0x2104, 0x732C}, + {0x2106, 0x4382}, + {0x2108, 0x7360}, + {0x210A, 0x422D}, + {0x210C, 0x403E}, + {0x210E, 0x87F0}, + {0x2110, 0x403F}, + {0x2112, 0x87E8}, + {0x2114, 0x1292}, + {0x2116, 0x843E}, + {0x2118, 0x4130}, + {0x211A, 0x120B}, + {0x211C, 0x120A}, + {0x211E, 0x4392}, + {0x2120, 0x87FA}, + {0x2122, 0x4392}, + {0x2124, 0x760E}, + {0x2126, 0x0900}, + {0x2128, 0x760C}, + {0x212A, 0x421B}, + {0x212C, 0x760A}, + {0x212E, 0x903B}, + {0x2130, 0x0201}, + {0x2132, 0x2408}, + {0x2134, 0x903B}, + {0x2136, 0x0102}, + {0x2138, 0x2405}, + {0x213A, 0x4292}, + {0x213C, 0x030A}, + {0x213E, 0x87F8}, + {0x2140, 0x1292}, + {0x2142, 0x849A}, + {0x2144, 0x903B}, + {0x2146, 0x0020}, + {0x2148, 0x2010}, + {0x214A, 0x403B}, + {0x214C, 0x8498}, + {0x214E, 0x422F}, + {0x2150, 0x12AB}, + {0x2152, 0x403F}, + {0x2154, 0x0028}, + {0x2156, 0x12AB}, + {0x2158, 0x403B}, + {0x215A, 0x84C4}, + {0x215C, 0x407F}, + {0x215E, 0xFFAA}, + {0x2160, 0x12AB}, + {0x2162, 0x407F}, + {0x2164, 0x0055}, + {0x2166, 0x12AB}, + {0x2168, 0x3FDC}, + {0x216A, 0x903B}, + {0x216C, 0x0021}, + {0x216E, 0x2890}, + {0x2170, 0x903B}, + {0x2172, 0x0100}, + {0x2174, 0x200D}, + {0x2176, 0x403F}, + {0x2178, 0x0028}, + {0x217A, 0x1292}, + {0x217C, 0x8498}, + {0x217E, 0x425F}, + {0x2180, 0x0306}, + {0x2182, 0x1292}, + {0x2184, 0x84C4}, + {0x2186, 0x4FC2}, + {0x2188, 0x0318}, + {0x218A, 0x0261}, + {0x218C, 0x0000}, + {0x218E, 0x3FC9}, + {0x2190, 0x903B}, + {0x2192, 0x0101}, + {0x2194, 0x2858}, + {0x2196, 0x903B}, + {0x2198, 0x0200}, + {0x219A, 0x2450}, + {0x219C, 0x903B}, + {0x219E, 0x0201}, + {0x21A0, 0x2C47}, + {0x21A2, 0x903B}, + {0x21A4, 0x0102}, + {0x21A6, 0x2041}, + {0x21A8, 0x93E2}, + {0x21AA, 0x0262}, + {0x21AC, 0x240A}, + {0x21AE, 0x425F}, + {0x21B0, 0x0306}, + {0x21B2, 0x1292}, + {0x21B4, 0x84C4}, + {0x21B6, 0x4F4E}, + {0x21B8, 0x4EC2}, + {0x21BA, 0x0318}, + {0x21BC, 0x0260}, + {0x21BE, 0x0000}, + {0x21C0, 0x3FB0}, + {0x21C2, 0x403A}, + {0x21C4, 0x8030}, + {0x21C6, 0x4382}, + {0x21C8, 0x0326}, + {0x21CA, 0x4382}, + {0x21CC, 0x0328}, + {0x21CE, 0x421B}, + {0x21D0, 0x030C}, + {0x21D2, 0x930B}, + {0x21D4, 0x2420}, + {0x21D6, 0x4A5F}, + {0x21D8, 0x0001}, + {0x21DA, 0x1292}, + {0x21DC, 0x84C4}, + {0x21DE, 0x4F4E}, + {0x21E0, 0x4A5F}, + {0x21E2, 0x0001}, + {0x21E4, 0x9F0E}, + {0x21E6, 0x2402}, + {0x21E8, 0x5392}, + {0x21EA, 0x0326}, + {0x21EC, 0x4ECA}, + {0x21EE, 0x0001}, + {0x21F0, 0x533B}, + {0x21F2, 0x2411}, + {0x21F4, 0x4A6F}, + {0x21F6, 0x1292}, + {0x21F8, 0x84C4}, + {0x21FA, 0x4F4E}, + {0x21FC, 0x4A6F}, + {0x21FE, 0x9F0E}, + {0x2200, 0x2402}, + {0x2202, 0x5392}, + {0x2204, 0x0326}, + {0x2206, 0x4ECA}, + {0x2208, 0x0000}, + {0x220A, 0x533B}, + {0x220C, 0x532A}, + {0x220E, 0x0260}, + {0x2210, 0x0000}, + {0x2212, 0x930B}, + {0x2214, 0x23E0}, + {0x2216, 0x40B2}, + {0x2218, 0xAA55}, + {0x221A, 0x0328}, + {0x221C, 0xB0F2}, + {0x221E, 0x0040}, + {0x2220, 0x0381}, + {0x2222, 0x277F}, + {0x2224, 0xD3D2}, + {0x2226, 0x0267}, + {0x2228, 0x3F7C}, + {0x222A, 0x0261}, + {0x222C, 0x0000}, + {0x222E, 0x3F79}, + {0x2230, 0x903B}, + {0x2232, 0x0201}, + {0x2234, 0x23FA}, + {0x2236, 0x1292}, + {0x2238, 0x84C0}, + {0x223A, 0x3F73}, + {0x223C, 0x1292}, + {0x223E, 0x84C0}, + {0x2240, 0x0261}, + {0x2242, 0x0000}, + {0x2244, 0x3F6E}, + {0x2246, 0x903B}, + {0x2248, 0x0040}, + {0x224A, 0x2018}, + {0x224C, 0x422F}, + {0x224E, 0x1292}, + {0x2250, 0x8498}, + {0x2252, 0x12B0}, + {0x2254, 0xF0EA}, + {0x2256, 0x907F}, + {0x2258, 0xFFAA}, + {0x225A, 0x240D}, + {0x225C, 0x5392}, + {0x225E, 0x0312}, + {0x2260, 0x12B0}, + {0x2262, 0xF0EA}, + {0x2264, 0x907F}, + {0x2266, 0x0055}, + {0x2268, 0x2403}, + {0x226A, 0x5392}, + {0x226C, 0x0312}, + {0x226E, 0x3F59}, + {0x2270, 0x5392}, + {0x2272, 0x0310}, + {0x2274, 0x3F56}, + {0x2276, 0x5392}, + {0x2278, 0x0310}, + {0x227A, 0x3FF2}, + {0x227C, 0x903B}, + {0x227E, 0x0080}, + {0x2280, 0x23D4}, + {0x2282, 0x4382}, + {0x2284, 0x0312}, + {0x2286, 0x4382}, + {0x2288, 0x0310}, + {0x228A, 0x0261}, + {0x228C, 0x0000}, + {0x228E, 0x3F49}, + {0x2290, 0x932B}, + {0x2292, 0x2005}, + {0x2294, 0x403F}, + {0x2296, 0x0028}, + {0x2298, 0x1292}, + {0x229A, 0x8498}, + {0x229C, 0x3F42}, + {0x229E, 0x903B}, + {0x22A0, 0x0003}, + {0x22A2, 0x284B}, + {0x22A4, 0x923B}, + {0x22A6, 0x2015}, + {0x22A8, 0x403F}, + {0x22AA, 0x0023}, + {0x22AC, 0x1292}, + {0x22AE, 0x8498}, + {0x22B0, 0x421B}, + {0x22B2, 0x87F8}, + {0x22B4, 0x421F}, + {0x22B6, 0x030C}, + {0x22B8, 0x9F0B}, + {0x22BA, 0x2F33}, + {0x22BC, 0x1292}, + {0x22BE, 0x84BA}, + {0x22C0, 0x930F}, + {0x22C2, 0x2004}, + {0x22C4, 0x5392}, + {0x22C6, 0x0312}, + {0x22C8, 0x531B}, + {0x22CA, 0x3FF4}, + {0x22CC, 0x5392}, + {0x22CE, 0x0310}, + {0x22D0, 0x3FFB}, + {0x22D2, 0x903B}, + {0x22D4, 0x0009}, + {0x22D6, 0x2818}, + {0x22D8, 0x903B}, + {0x22DA, 0x0010}, + {0x22DC, 0x23A6}, + {0x22DE, 0x403F}, + {0x22E0, 0x0027}, + {0x22E2, 0x1292}, + {0x22E4, 0x8498}, + {0x22E6, 0x421B}, + {0x22E8, 0x87F8}, + {0x22EA, 0x421F}, + {0x22EC, 0x030C}, + {0x22EE, 0x9F0B}, + {0x22F0, 0x2F18}, + {0x22F2, 0x1292}, + {0x22F4, 0x84BA}, + {0x22F6, 0x930F}, + {0x22F8, 0x2004}, + {0x22FA, 0x5392}, + {0x22FC, 0x0312}, + {0x22FE, 0x531B}, + {0x2300, 0x3FF4}, + {0x2302, 0x5392}, + {0x2304, 0x0310}, + {0x2306, 0x3FFB}, + {0x2308, 0x922B}, + {0x230A, 0x238F}, + {0x230C, 0x421B}, + {0x230E, 0x87F8}, + {0x2310, 0x421F}, + {0x2312, 0x030C}, + {0x2314, 0x9F0B}, + {0x2316, 0x2C0B}, + {0x2318, 0x1292}, + {0x231A, 0x84C2}, + {0x231C, 0x934F}, + {0x231E, 0x240A}, + {0x2320, 0x5392}, + {0x2322, 0x0312}, + {0x2324, 0x531B}, + {0x2326, 0x421F}, + {0x2328, 0x030C}, + {0x232A, 0x9F0B}, + {0x232C, 0x2BF5}, + {0x232E, 0x0261}, + {0x2330, 0x0000}, + {0x2332, 0x3EF7}, + {0x2334, 0x5392}, + {0x2336, 0x0310}, + {0x2338, 0x3FF5}, + {0x233A, 0x930B}, + {0x233C, 0x277F}, + {0x233E, 0x931B}, + {0x2340, 0x277A}, + {0x2342, 0x3F73}, + {0x2344, 0x413A}, + {0x2346, 0x413B}, + {0x2348, 0x4130}, + {0x234A, 0x4F0C}, + {0x234C, 0x403F}, + {0x234E, 0x0267}, + {0x2350, 0xF0FF}, + {0x2352, 0xFFDF}, + {0x2354, 0x0000}, + {0x2356, 0xF0FF}, + {0x2358, 0xFFEF}, + {0x235A, 0x0000}, + {0x235C, 0x421D}, + {0x235E, 0x84B0}, + {0x2360, 0x403E}, + {0x2362, 0x06F9}, + {0x2364, 0x4C0F}, + {0x2366, 0x1292}, + {0x2368, 0x84AC}, + {0x236A, 0x4F4E}, + {0x236C, 0xB31E}, + {0x236E, 0x2403}, + {0x2370, 0xD0F2}, + {0x2372, 0x0020}, + {0x2374, 0x0267}, + {0x2376, 0xB32E}, + {0x2378, 0x2403}, + {0x237A, 0xD0F2}, + {0x237C, 0x0010}, + {0x237E, 0x0267}, + {0x2380, 0xC3E2}, + {0x2382, 0x0267}, + {0x2384, 0x4130}, + {0x2386, 0x120B}, + {0x2388, 0x120A}, + {0x238A, 0x403A}, + {0x238C, 0x1140}, + {0x238E, 0x1292}, + {0x2390, 0xD080}, + {0x2392, 0x430B}, + {0x2394, 0x4A0F}, + {0x2396, 0x532A}, + {0x2398, 0x1292}, + {0x239A, 0x84A4}, + {0x239C, 0x4F0E}, + {0x239E, 0x430F}, + {0x23A0, 0x5E82}, + {0x23A2, 0x87FC}, + {0x23A4, 0x6F82}, + {0x23A6, 0x87FE}, + {0x23A8, 0x531B}, + {0x23AA, 0x923B}, + {0x23AC, 0x2BF3}, + {0x23AE, 0x413A}, + {0x23B0, 0x413B}, + {0x23B2, 0x4130}, + {0x23B4, 0xF0F2}, + {0x23B6, 0x007F}, + {0x23B8, 0x0267}, + {0x23BA, 0x421D}, + {0x23BC, 0x84B6}, + {0x23BE, 0x403E}, + {0x23C0, 0x01F9}, + {0x23C2, 0x1292}, + {0x23C4, 0x84AC}, + {0x23C6, 0x4F4E}, + {0x23C8, 0xF35F}, + {0x23CA, 0x2403}, + {0x23CC, 0xD0F2}, + {0x23CE, 0xFF80}, + {0x23D0, 0x0267}, + {0x23D2, 0xB36E}, + {0x23D4, 0x2404}, + {0x23D6, 0xD0F2}, + {0x23D8, 0x0040}, + {0x23DA, 0x0267}, + {0x23DC, 0x3C03}, + {0x23DE, 0xF0F2}, + {0x23E0, 0xFFBF}, + {0x23E2, 0x0267}, + {0x23E4, 0xC2E2}, + {0x23E6, 0x0267}, + {0x23E8, 0x4130}, + {0x23EA, 0x120B}, + {0x23EC, 0x120A}, + {0x23EE, 0x8231}, + {0x23F0, 0x430B}, + {0x23F2, 0x93C2}, + {0x23F4, 0x0C0A}, + {0x23F6, 0x2404}, + {0x23F8, 0xB3D2}, + {0x23FA, 0x0B05}, + {0x23FC, 0x2401}, + {0x23FE, 0x431B}, + {0x2400, 0x422D}, + {0x2402, 0x403E}, + {0x2404, 0x192A}, + {0x2406, 0x403F}, + {0x2408, 0x888E}, + {0x240A, 0x1292}, + {0x240C, 0x843E}, + {0x240E, 0x930B}, + {0x2410, 0x20F4}, + {0x2412, 0x93E2}, + {0x2414, 0x0241}, + {0x2416, 0x24EB}, + {0x2418, 0x403A}, + {0x241A, 0x0292}, + {0x241C, 0x4AA2}, + {0x241E, 0x0A00}, + {0x2420, 0xB2E2}, + {0x2422, 0x0361}, + {0x2424, 0x2405}, + {0x2426, 0x4A2F}, + {0x2428, 0x1292}, + {0x242A, 0x8474}, + {0x242C, 0x4F82}, + {0x242E, 0x0A1C}, + {0x2430, 0x93C2}, + {0x2432, 0x0360}, + {0x2434, 0x34CD}, + {0x2436, 0x430C}, + {0x2438, 0x4C0F}, + {0x243A, 0x5F0F}, + {0x243C, 0x4F0D}, + {0x243E, 0x510D}, + {0x2440, 0x4F0E}, + {0x2442, 0x5A0E}, + {0x2444, 0x4E1E}, + {0x2446, 0x0002}, + {0x2448, 0x4F1F}, + {0x244A, 0x192A}, + {0x244C, 0x1202}, + {0x244E, 0xC232}, + {0x2450, 0x4303}, + {0x2452, 0x4E82}, + {0x2454, 0x0130}, + {0x2456, 0x4F82}, + {0x2458, 0x0138}, + {0x245A, 0x421E}, + {0x245C, 0x013A}, + {0x245E, 0x421F}, + {0x2460, 0x013C}, + {0x2462, 0x4132}, + {0x2464, 0x108E}, + {0x2466, 0x108F}, + {0x2468, 0xEF4E}, + {0x246A, 0xEF0E}, + {0x246C, 0xF37F}, + {0x246E, 0xC312}, + {0x2470, 0x100F}, + {0x2472, 0x100E}, + {0x2474, 0x4E8D}, + {0x2476, 0x0000}, + {0x2478, 0x531C}, + {0x247A, 0x922C}, + {0x247C, 0x2BDD}, + {0x247E, 0xB3D2}, + {0x2480, 0x1921}, + {0x2482, 0x2403}, + {0x2484, 0x410F}, + {0x2486, 0x1292}, + {0x2488, 0x847E}, + {0x248A, 0x403B}, + {0x248C, 0x843E}, + {0x248E, 0x422D}, + {0x2490, 0x410E}, + {0x2492, 0x403F}, + {0x2494, 0x1908}, + {0x2496, 0x12AB}, + {0x2498, 0x403D}, + {0x249A, 0x0005}, + {0x249C, 0x403E}, + {0x249E, 0x0292}, + {0x24A0, 0x403F}, + {0x24A2, 0x86E4}, + {0x24A4, 0x12AB}, + {0x24A6, 0x421F}, + {0x24A8, 0x060E}, + {0x24AA, 0x9F82}, + {0x24AC, 0x8720}, + {0x24AE, 0x288D}, + {0x24B0, 0x9382}, + {0x24B2, 0x060E}, + {0x24B4, 0x248A}, + {0x24B6, 0x90BA}, + {0x24B8, 0x0010}, + {0x24BA, 0x0000}, + {0x24BC, 0x2C0B}, + {0x24BE, 0x93C2}, + {0x24C0, 0x86EE}, + {0x24C2, 0x2008}, + {0x24C4, 0x403F}, + {0x24C6, 0x06A7}, + {0x24C8, 0xD0FF}, + {0x24CA, 0x0007}, + {0x24CC, 0x0000}, + {0x24CE, 0xF0FF}, + {0x24D0, 0xFFF8}, + {0x24D2, 0x0000}, + {0x24D4, 0x4392}, + {0x24D6, 0x8720}, + {0x24D8, 0x403F}, + {0x24DA, 0x06A7}, + {0x24DC, 0xD2EF}, + {0x24DE, 0x0000}, + {0x24E0, 0xC2EF}, + {0x24E2, 0x0000}, + {0x24E4, 0x93C2}, + {0x24E6, 0x87D3}, + {0x24E8, 0x2068}, + {0x24EA, 0xB0F2}, + {0x24EC, 0x0040}, + {0x24EE, 0x0B05}, + {0x24F0, 0x2461}, + {0x24F2, 0xD3D2}, + {0x24F4, 0x0410}, + {0x24F6, 0xB3E2}, + {0x24F8, 0x0381}, + {0x24FA, 0x2089}, + {0x24FC, 0x90B2}, + {0x24FE, 0x0030}, + {0x2500, 0x0A00}, + {0x2502, 0x2C52}, + {0x2504, 0x93C2}, + {0x2506, 0x86EE}, + {0x2508, 0x204F}, + {0x250A, 0x430E}, + {0x250C, 0x430C}, + {0x250E, 0x4C0F}, + {0x2510, 0x5F0F}, + {0x2512, 0x5F0F}, + {0x2514, 0x5F0F}, + {0x2516, 0x4F1F}, + {0x2518, 0x8668}, + {0x251A, 0xF03F}, + {0x251C, 0x07FF}, + {0x251E, 0x903F}, + {0x2520, 0x0400}, + {0x2522, 0x343E}, + {0x2524, 0x5F0E}, + {0x2526, 0x531C}, + {0x2528, 0x923C}, + {0x252A, 0x2BF1}, + {0x252C, 0x4E0F}, + {0x252E, 0x930E}, + {0x2530, 0x3834}, + {0x2532, 0x110F}, + {0x2534, 0x110F}, + {0x2536, 0x110F}, + {0x2538, 0x9382}, + {0x253A, 0x86EE}, + {0x253C, 0x2023}, + {0x253E, 0x5F82}, + {0x2540, 0x87D6}, + {0x2542, 0x403B}, + {0x2544, 0x87D6}, + {0x2546, 0x4B2F}, + {0x2548, 0x12B0}, + {0x254A, 0xB624}, + {0x254C, 0x4F8B}, + {0x254E, 0x0000}, + {0x2550, 0x430C}, + {0x2552, 0x4C0D}, + {0x2554, 0x5D0D}, + {0x2556, 0x5D0D}, + {0x2558, 0x5D0D}, + {0x255A, 0x403A}, + {0x255C, 0x87D8}, + {0x255E, 0x421B}, + {0x2560, 0x87D6}, + {0x2562, 0x4B0F}, + {0x2564, 0x8A2F}, + {0x2566, 0x4F0E}, + {0x2568, 0x4E0F}, + {0x256A, 0x5F0F}, + {0x256C, 0x7F0F}, + {0x256E, 0xE33F}, + {0x2570, 0x8E8D}, + {0x2572, 0x8668}, + {0x2574, 0x7F8D}, + {0x2576, 0x866A}, + {0x2578, 0x531C}, + {0x257A, 0x923C}, + {0x257C, 0x2BEA}, + {0x257E, 0x4B8A}, + {0x2580, 0x0000}, + {0x2582, 0x3C45}, + {0x2584, 0x9382}, + {0x2586, 0x86F0}, + {0x2588, 0x2005}, + {0x258A, 0x4382}, + {0x258C, 0x87D6}, + {0x258E, 0x4382}, + {0x2590, 0x87D8}, + {0x2592, 0x3FD7}, + {0x2594, 0x4F82}, + {0x2596, 0x87D6}, + {0x2598, 0x3FD4}, + {0x259A, 0x503F}, + {0x259C, 0x0007}, + {0x259E, 0x3FC9}, + {0x25A0, 0x5F0E}, + {0x25A2, 0x503E}, + {0x25A4, 0xF800}, + {0x25A6, 0x3FBF}, + {0x25A8, 0x430F}, + {0x25AA, 0x12B0}, + {0x25AC, 0xB624}, + {0x25AE, 0x4382}, + {0x25B0, 0x87D6}, + {0x25B2, 0x3C2D}, + {0x25B4, 0xC3D2}, + {0x25B6, 0x0410}, + {0x25B8, 0x3F9E}, + {0x25BA, 0x430D}, + {0x25BC, 0x403E}, + {0x25BE, 0x0050}, + {0x25C0, 0x403F}, + {0x25C2, 0x85C8}, + {0x25C4, 0x1292}, + {0x25C6, 0x844E}, + {0x25C8, 0x3F90}, + {0x25CA, 0x5392}, + {0x25CC, 0x8720}, + {0x25CE, 0x3F84}, + {0x25D0, 0x403B}, + {0x25D2, 0x843E}, + {0x25D4, 0x4A0F}, + {0x25D6, 0x532F}, + {0x25D8, 0x422D}, + {0x25DA, 0x4F0E}, + {0x25DC, 0x403F}, + {0x25DE, 0x0E08}, + {0x25E0, 0x12AB}, + {0x25E2, 0x422D}, + {0x25E4, 0x403E}, + {0x25E6, 0x192A}, + {0x25E8, 0x410F}, + {0x25EA, 0x12AB}, + {0x25EC, 0x3F48}, + {0x25EE, 0x93C2}, + {0x25F0, 0x86EE}, + {0x25F2, 0x2312}, + {0x25F4, 0x403A}, + {0x25F6, 0x86E4}, + {0x25F8, 0x3F11}, + {0x25FA, 0x403D}, + {0x25FC, 0x0200}, + {0x25FE, 0x422E}, + {0x2600, 0x403F}, + {0x2602, 0x192A}, + {0x2604, 0x1292}, + {0x2606, 0x844E}, + {0x2608, 0xC3D2}, + {0x260A, 0x1921}, + {0x260C, 0x3F02}, + {0x260E, 0x422D}, + {0x2610, 0x403E}, + {0x2612, 0x888E}, + {0x2614, 0x403F}, + {0x2616, 0x192A}, + {0x2618, 0x1292}, + {0x261A, 0x843E}, + {0x261C, 0x5231}, + {0x261E, 0x413A}, + {0x2620, 0x413B}, + {0x2622, 0x4130}, + {0x2624, 0x4382}, + {0x2626, 0x052C}, + {0x2628, 0x4F0D}, + {0x262A, 0x930D}, + {0x262C, 0x3402}, + {0x262E, 0xE33D}, + {0x2630, 0x531D}, + {0x2632, 0xF03D}, + {0x2634, 0x07F0}, + {0x2636, 0x4D0E}, + {0x2638, 0xC312}, + {0x263A, 0x100E}, + {0x263C, 0x110E}, + {0x263E, 0x110E}, + {0x2640, 0x110E}, + {0x2642, 0x930F}, + {0x2644, 0x3803}, + {0x2646, 0x4EC2}, + {0x2648, 0x052C}, + {0x264A, 0x3C04}, + {0x264C, 0x4EC2}, + {0x264E, 0x052D}, + {0x2650, 0xE33D}, + {0x2652, 0x531D}, + {0x2654, 0x4D0F}, + {0x2656, 0x4130}, + {0x2658, 0x1292}, + {0x265A, 0xD048}, + {0x265C, 0x93C2}, + {0x265E, 0x86EE}, + {0x2660, 0x200D}, + {0x2662, 0xB0F2}, + {0x2664, 0x0020}, + {0x2666, 0x0381}, + {0x2668, 0x2407}, + {0x266A, 0x9292}, + {0x266C, 0x8722}, + {0x266E, 0x0384}, + {0x2670, 0x2C03}, + {0x2672, 0xD3D2}, + {0x2674, 0x0649}, + {0x2676, 0x4130}, + {0x2678, 0xC3D2}, + {0x267A, 0x0649}, + {0x267C, 0x4130}, + {0x267E, 0x120B}, + {0x2680, 0x120A}, + {0x2682, 0x1209}, + {0x2684, 0x1208}, + {0x2686, 0x1207}, + {0x2688, 0x1206}, + {0x268A, 0x1205}, + {0x268C, 0x1204}, + {0x268E, 0x8231}, + {0x2690, 0x4F81}, + {0x2692, 0x0000}, + {0x2694, 0x4381}, + {0x2696, 0x0002}, + {0x2698, 0x4304}, + {0x269A, 0x411C}, + {0x269C, 0x0002}, + {0x269E, 0x5C0C}, + {0x26A0, 0x4C0F}, + {0x26A2, 0x5F0F}, + {0x26A4, 0x5F0F}, + {0x26A6, 0x5F0F}, + {0x26A8, 0x5F0F}, + {0x26AA, 0x5F0F}, + {0x26AC, 0x503F}, + {0x26AE, 0x1980}, + {0x26B0, 0x440D}, + {0x26B2, 0x5D0D}, + {0x26B4, 0x4D0E}, + {0x26B6, 0x5F0E}, + {0x26B8, 0x4E2E}, + {0x26BA, 0x4D05}, + {0x26BC, 0x5505}, + {0x26BE, 0x5F05}, + {0x26C0, 0x4516}, + {0x26C2, 0x0008}, + {0x26C4, 0x4517}, + {0x26C6, 0x000A}, + {0x26C8, 0x460A}, + {0x26CA, 0x470B}, + {0x26CC, 0xF30A}, + {0x26CE, 0xF32B}, + {0x26D0, 0x4A81}, + {0x26D2, 0x0004}, + {0x26D4, 0x4B81}, + {0x26D6, 0x0006}, + {0x26D8, 0xB03E}, + {0x26DA, 0x2000}, + {0x26DC, 0x2404}, + {0x26DE, 0xF03E}, + {0x26E0, 0x1FFF}, + {0x26E2, 0xE33E}, + {0x26E4, 0x531E}, + {0x26E6, 0xF317}, + {0x26E8, 0x503E}, + {0x26EA, 0x2000}, + {0x26EC, 0x4E0F}, + {0x26EE, 0x5F0F}, + {0x26F0, 0x7F0F}, + {0x26F2, 0xE33F}, + {0x26F4, 0x512C}, + {0x26F6, 0x4C28}, + {0x26F8, 0x4309}, + {0x26FA, 0x4E0A}, + {0x26FC, 0x4F0B}, + {0x26FE, 0x480C}, + {0x2700, 0x490D}, + {0x2702, 0x1202}, + {0x2704, 0xC232}, + {0x2706, 0x12B0}, + {0x2708, 0xFFC0}, + {0x270A, 0x4132}, + {0x270C, 0x108E}, + {0x270E, 0x108F}, + {0x2710, 0xEF4E}, + {0x2712, 0xEF0E}, + {0x2714, 0xF37F}, + {0x2716, 0xC312}, + {0x2718, 0x100F}, + {0x271A, 0x100E}, + {0x271C, 0x4E85}, + {0x271E, 0x0018}, + {0x2720, 0x4F85}, + {0x2722, 0x001A}, + {0x2724, 0x480A}, + {0x2726, 0x490B}, + {0x2728, 0x460C}, + {0x272A, 0x470D}, + {0x272C, 0x1202}, + {0x272E, 0xC232}, + {0x2730, 0x12B0}, + {0x2732, 0xFFC0}, + {0x2734, 0x4132}, + {0x2736, 0x4E0C}, + {0x2738, 0x4F0D}, + {0x273A, 0x108C}, + {0x273C, 0x108D}, + {0x273E, 0xED4C}, + {0x2740, 0xED0C}, + {0x2742, 0xF37D}, + {0x2744, 0xC312}, + {0x2746, 0x100D}, + {0x2748, 0x100C}, + {0x274A, 0x411E}, + {0x274C, 0x0004}, + {0x274E, 0x411F}, + {0x2750, 0x0006}, + {0x2752, 0x5E0E}, + {0x2754, 0x6F0F}, + {0x2756, 0x5E0E}, + {0x2758, 0x6F0F}, + {0x275A, 0x5E0E}, + {0x275C, 0x6F0F}, + {0x275E, 0xDE0C}, + {0x2760, 0xDF0D}, + {0x2762, 0x4C85}, + {0x2764, 0x002C}, + {0x2766, 0x4D85}, + {0x2768, 0x002E}, + {0x276A, 0x5314}, + {0x276C, 0x9224}, + {0x276E, 0x2B95}, + {0x2770, 0x5391}, + {0x2772, 0x0002}, + {0x2774, 0x92A1}, + {0x2776, 0x0002}, + {0x2778, 0x2B8F}, + {0x277A, 0x5231}, + {0x277C, 0x4134}, + {0x277E, 0x4135}, + {0x2780, 0x4136}, + {0x2782, 0x4137}, + {0x2784, 0x4138}, + {0x2786, 0x4139}, + {0x2788, 0x413A}, + {0x278A, 0x413B}, + {0x278C, 0x4130}, + {0x278E, 0x120B}, + {0x2790, 0x120A}, + {0x2792, 0x1209}, + {0x2794, 0x8031}, + {0x2796, 0x000C}, + {0x2798, 0x425F}, + {0x279A, 0x0205}, + {0x279C, 0xC312}, + {0x279E, 0x104F}, + {0x27A0, 0x114F}, + {0x27A2, 0x114F}, + {0x27A4, 0x114F}, + {0x27A6, 0x114F}, + {0x27A8, 0x114F}, + {0x27AA, 0xF37F}, + {0x27AC, 0x4F0B}, + {0x27AE, 0xF31B}, + {0x27B0, 0x5B0B}, + {0x27B2, 0x5B0B}, + {0x27B4, 0x5B0B}, + {0x27B6, 0x503B}, + {0x27B8, 0xD194}, + {0x27BA, 0x4219}, + {0x27BC, 0x0508}, + {0x27BE, 0xF039}, + {0x27C0, 0x2000}, + {0x27C2, 0x4F0A}, + {0x27C4, 0xC312}, + {0x27C6, 0x100A}, + {0x27C8, 0xE31A}, + {0x27CA, 0x421F}, + {0x27CC, 0x87DE}, + {0x27CE, 0x503F}, + {0x27D0, 0xFF60}, + {0x27D2, 0x903F}, + {0x27D4, 0x00C8}, + {0x27D6, 0x2C02}, + {0x27D8, 0x403F}, + {0x27DA, 0x00C8}, + {0x27DC, 0x4F82}, + {0x27DE, 0x7322}, + {0x27E0, 0xB3D2}, + {0x27E2, 0x0381}, + {0x27E4, 0x2009}, + {0x27E6, 0x421F}, + {0x27E8, 0x86F0}, + {0x27EA, 0xD21F}, + {0x27EC, 0x86EE}, + {0x27EE, 0x930F}, + {0x27F0, 0x24B9}, + {0x27F2, 0x40F2}, + {0x27F4, 0xFF80}, + {0x27F6, 0x0619}, + {0x27F8, 0x1292}, + {0x27FA, 0xD00A}, + {0x27FC, 0xB3D2}, + {0x27FE, 0x0385}, + {0x2800, 0x2405}, + {0x2802, 0x421F}, + {0x2804, 0x880A}, + {0x2806, 0x4F92}, + {0x2808, 0x0002}, + {0x280A, 0x8714}, + {0x280C, 0x430D}, + {0x280E, 0x93C2}, + {0x2810, 0x87D0}, + {0x2812, 0x2003}, + {0x2814, 0xB2F2}, + {0x2816, 0x0360}, + {0x2818, 0x2001}, + {0x281A, 0x431D}, + {0x281C, 0x425F}, + {0x281E, 0x87D3}, + {0x2820, 0xD25F}, + {0x2822, 0x87D2}, + {0x2824, 0xF37F}, + {0x2826, 0x5F0F}, + {0x2828, 0x425E}, + {0x282A, 0x87CD}, + {0x282C, 0xDE0F}, + {0x282E, 0x5F0F}, + {0x2830, 0x5B0F}, + {0x2832, 0x4FA2}, + {0x2834, 0x0402}, + {0x2836, 0x930D}, + {0x2838, 0x2007}, + {0x283A, 0x930A}, + {0x283C, 0x248E}, + {0x283E, 0x4F5F}, + {0x2840, 0x0001}, + {0x2842, 0xF37F}, + {0x2844, 0x4FC2}, + {0x2846, 0x0403}, + {0x2848, 0x93C2}, + {0x284A, 0x87CD}, + {0x284C, 0x2483}, + {0x284E, 0xC2F2}, + {0x2850, 0x0400}, + {0x2852, 0xB2E2}, + {0x2854, 0x0265}, + {0x2856, 0x2407}, + {0x2858, 0x421F}, + {0x285A, 0x0508}, + {0x285C, 0xF03F}, + {0x285E, 0xFFDF}, + {0x2860, 0xD90F}, + {0x2862, 0x4F82}, + {0x2864, 0x0508}, + {0x2866, 0xB3D2}, + {0x2868, 0x0383}, + {0x286A, 0x2484}, + {0x286C, 0x403F}, + {0x286E, 0x0508}, + {0x2870, 0x4FB1}, + {0x2872, 0x0000}, + {0x2874, 0x4FB1}, + {0x2876, 0x0002}, + {0x2878, 0x4FB1}, + {0x287A, 0x0004}, + {0x287C, 0x403F}, + {0x287E, 0x0500}, + {0x2880, 0x4FB1}, + {0x2882, 0x0006}, + {0x2884, 0x4FB1}, + {0x2886, 0x0008}, + {0x2888, 0x4FB1}, + {0x288A, 0x000A}, + {0x288C, 0xB3E2}, + {0x288E, 0x0383}, + {0x2890, 0x2412}, + {0x2892, 0xC2E1}, + {0x2894, 0x0002}, + {0x2896, 0xB2E2}, + {0x2898, 0x0383}, + {0x289A, 0x434F}, + {0x289C, 0x634F}, + {0x289E, 0xF37F}, + {0x28A0, 0x4F4E}, + {0x28A2, 0x114E}, + {0x28A4, 0x434E}, + {0x28A6, 0x104E}, + {0x28A8, 0x415F}, + {0x28AA, 0x0007}, + {0x28AC, 0xF07F}, + {0x28AE, 0x007F}, + {0x28B0, 0xDE4F}, + {0x28B2, 0x4FC1}, + {0x28B4, 0x0007}, + {0x28B6, 0xB2F2}, + {0x28B8, 0x0383}, + {0x28BA, 0x2415}, + {0x28BC, 0xF0F1}, + {0x28BE, 0xFFBF}, + {0x28C0, 0x0000}, + {0x28C2, 0xB0F2}, + {0x28C4, 0x0010}, + {0x28C6, 0x0383}, + {0x28C8, 0x434E}, + {0x28CA, 0x634E}, + {0x28CC, 0x5E4E}, + {0x28CE, 0x5E4E}, + {0x28D0, 0x5E4E}, + {0x28D2, 0x5E4E}, + {0x28D4, 0x5E4E}, + {0x28D6, 0x5E4E}, + {0x28D8, 0x415F}, + {0x28DA, 0x0006}, + {0x28DC, 0xF07F}, + {0x28DE, 0xFFBF}, + {0x28E0, 0xDE4F}, + {0x28E2, 0x4FC1}, + {0x28E4, 0x0006}, + {0x28E6, 0xB0F2}, + {0x28E8, 0x0020}, + {0x28EA, 0x0383}, + {0x28EC, 0x2410}, + {0x28EE, 0xF0F1}, + {0x28F0, 0xFFDF}, + {0x28F2, 0x0002}, + {0x28F4, 0xB0F2}, + {0x28F6, 0x0040}, + {0x28F8, 0x0383}, + {0x28FA, 0x434E}, + {0x28FC, 0x634E}, + {0x28FE, 0x5E4E}, + {0x2900, 0x5E4E}, + {0x2902, 0x415F}, + {0x2904, 0x0008}, + {0x2906, 0xC26F}, + {0x2908, 0xDE4F}, + {0x290A, 0x4FC1}, + {0x290C, 0x0008}, + {0x290E, 0x93C2}, + {0x2910, 0x0383}, + {0x2912, 0x3412}, + {0x2914, 0xF0F1}, + {0x2916, 0xFFDF}, + {0x2918, 0x0000}, + {0x291A, 0x425E}, + {0x291C, 0x0382}, + {0x291E, 0xF35E}, + {0x2920, 0x5E4E}, + {0x2922, 0x5E4E}, + {0x2924, 0x5E4E}, + {0x2926, 0x5E4E}, + {0x2928, 0x5E4E}, + {0x292A, 0x415F}, + {0x292C, 0x0006}, + {0x292E, 0xF07F}, + {0x2930, 0xFFDF}, + {0x2932, 0xDE4F}, + {0x2934, 0x4FC1}, + {0x2936, 0x0006}, + {0x2938, 0x410F}, + {0x293A, 0x4FB2}, + {0x293C, 0x0508}, + {0x293E, 0x4FB2}, + {0x2940, 0x050A}, + {0x2942, 0x4FB2}, + {0x2944, 0x050C}, + {0x2946, 0x4FB2}, + {0x2948, 0x0500}, + {0x294A, 0x4FB2}, + {0x294C, 0x0502}, + {0x294E, 0x4FB2}, + {0x2950, 0x0504}, + {0x2952, 0x3C10}, + {0x2954, 0xD2F2}, + {0x2956, 0x0400}, + {0x2958, 0x3F7C}, + {0x295A, 0x4F6F}, + {0x295C, 0xF37F}, + {0x295E, 0x4FC2}, + {0x2960, 0x0402}, + {0x2962, 0x3F72}, + {0x2964, 0x90F2}, + {0x2966, 0x0011}, + {0x2968, 0x0619}, + {0x296A, 0x2B46}, + {0x296C, 0x50F2}, + {0x296E, 0xFFF0}, + {0x2970, 0x0619}, + {0x2972, 0x3F42}, + {0x2974, 0x5031}, + {0x2976, 0x000C}, + {0x2978, 0x4139}, + {0x297A, 0x413A}, + {0x297C, 0x413B}, + {0x297E, 0x4130}, + {0x2980, 0x0900}, + {0x2982, 0x7312}, + {0x2984, 0x421F}, + {0x2986, 0x0A08}, + {0x2988, 0xF03F}, + {0x298A, 0xF7FF}, + {0x298C, 0x4F82}, + {0x298E, 0x0A88}, + {0x2990, 0x0900}, + {0x2992, 0x7312}, + {0x2994, 0x421F}, + {0x2996, 0x0A0E}, + {0x2998, 0xF03F}, + {0x299A, 0x7FFF}, + {0x299C, 0x4F82}, + {0x299E, 0x0A8E}, + {0x29A0, 0x0900}, + {0x29A2, 0x7312}, + {0x29A4, 0x421F}, + {0x29A6, 0x0A1E}, + {0x29A8, 0xC31F}, + {0x29AA, 0x4F82}, + {0x29AC, 0x0A9E}, + {0x29AE, 0x4130}, + {0x29B0, 0x4292}, + {0x29B2, 0x0A08}, + {0x29B4, 0x0A88}, + {0x29B6, 0x0900}, + {0x29B8, 0x7312}, + {0x29BA, 0x4292}, + {0x29BC, 0x0A0E}, + {0x29BE, 0x0A8E}, + {0x29C0, 0x0900}, + {0x29C2, 0x7312}, + {0x29C4, 0x4292}, + {0x29C6, 0x0A1E}, + {0x29C8, 0x0A9E}, + {0x29CA, 0x4130}, + {0x29CC, 0x7400}, + {0x29CE, 0x8058}, + {0x29D0, 0x1807}, + {0x29D2, 0x00E0}, + {0x29D4, 0x7002}, + {0x29D6, 0x17C7}, + {0x29D8, 0x0045}, + {0x29DA, 0x0006}, + {0x29DC, 0x17CC}, + {0x29DE, 0x0015}, + {0x29E0, 0x1512}, + {0x29E2, 0x216F}, + {0x29E4, 0x005B}, + {0x29E6, 0x005D}, + {0x29E8, 0x00DE}, + {0x29EA, 0x00DD}, + {0x29EC, 0x5023}, + {0x29EE, 0x00DE}, + {0x29F0, 0x005B}, + {0x29F2, 0x0410}, + {0x29F4, 0x0091}, + {0x29F6, 0x0015}, + {0x29F8, 0x0040}, + {0x29FA, 0x7023}, + {0x29FC, 0x1653}, + {0x29FE, 0x0156}, + {0x2A00, 0x0001}, + {0x2A02, 0x2081}, + {0x2A04, 0x7020}, + {0x2A06, 0x2F99}, + {0x2A08, 0x005C}, + {0x2A0A, 0x0000}, + {0x2A0C, 0x5040}, + {0x2A0E, 0x0045}, + {0x2A10, 0x213A}, + {0x2A12, 0x0303}, + {0x2A14, 0x0148}, + {0x2A16, 0x0049}, + {0x2A18, 0x0045}, + {0x2A1A, 0x0046}, + {0x2A1C, 0x05DD}, + {0x2A1E, 0x00DE}, + {0x2A20, 0x00DD}, + {0x2A22, 0x00DC}, + {0x2A24, 0x00DE}, + {0x2A26, 0x04D6}, + {0x2A28, 0x2014}, + {0x2A2A, 0x2081}, + {0x2A2C, 0x7087}, + {0x2A2E, 0x2F99}, + {0x2A30, 0x005C}, + {0x2A32, 0x0002}, + {0x2A34, 0x5060}, + {0x2A36, 0x31C0}, + {0x2A38, 0x2122}, + {0x2A3A, 0x7800}, + {0x2A3C, 0xC08C}, + {0x2A3E, 0x0001}, + {0x2A40, 0x9038}, + {0x2A42, 0x59F7}, + {0x2A44, 0x907A}, + {0x2A46, 0x03D8}, + {0x2A48, 0x8D90}, + {0x2A4A, 0x01C0}, + {0x2A4C, 0x7400}, + {0x2A4E, 0x8058}, + {0x2A50, 0x1807}, + {0x2A52, 0x00E0}, + {0x2A54, 0x7002}, + {0x2A56, 0x17C7}, + {0x2A58, 0x0045}, + {0x2A5A, 0x0006}, + {0x2A5C, 0x17CC}, + {0x2A5E, 0x0015}, + {0x2A60, 0x1512}, + {0x2A62, 0x216F}, + {0x2A64, 0x005B}, + {0x2A66, 0x005D}, + {0x2A68, 0x00DE}, + {0x2A6A, 0x00DD}, + {0x2A6C, 0x5023}, + {0x2A6E, 0x00DE}, + {0x2A70, 0x005B}, + {0x2A72, 0x0410}, + {0x2A74, 0x0091}, + {0x2A76, 0x0015}, + {0x2A78, 0x0040}, + {0x2A7A, 0x7023}, + {0x2A7C, 0x1653}, + {0x2A7E, 0x0156}, + {0x2A80, 0x0001}, + {0x2A82, 0x2081}, + {0x2A84, 0x7020}, + {0x2A86, 0x2F99}, + {0x2A88, 0x005C}, + {0x2A8A, 0x0000}, + {0x2A8C, 0x5040}, + {0x2A8E, 0x0045}, + {0x2A90, 0x213A}, + {0x2A92, 0x0303}, + {0x2A94, 0x0148}, + {0x2A96, 0x0049}, + {0x2A98, 0x0045}, + {0x2A9A, 0x0046}, + {0x2A9C, 0x05DD}, + {0x2A9E, 0x00DE}, + {0x2AA0, 0x00DD}, + {0x2AA2, 0x00DC}, + {0x2AA4, 0x00DE}, + {0x2AA6, 0x0296}, + {0x2AA8, 0x2014}, + {0x2AAA, 0x2081}, + {0x2AAC, 0x7087}, + {0x2AAE, 0x2F99}, + {0x2AB0, 0x005C}, + {0x2AB2, 0x0002}, + {0x2AB4, 0x5060}, + {0x2AB6, 0x31C0}, + {0x2AB8, 0x2122}, + {0x2ABA, 0x7800}, + {0x2ABC, 0xC08C}, + {0x2ABE, 0x0001}, + {0x2AC0, 0x9038}, + {0x2AC2, 0x59F7}, + {0x2AC4, 0x907A}, + {0x2AC6, 0x03D8}, + {0x2AC8, 0x8D90}, + {0x2ACA, 0x01C0}, + {0x2ACC, 0x7400}, + {0x2ACE, 0x2002}, + {0x2AD0, 0x70DF}, + {0x2AD2, 0x2F21}, + {0x2AD4, 0x04C1}, + {0x2AD6, 0x0D80}, + {0x2AD8, 0x7800}, + {0x2ADA, 0x0041}, + {0x2ADC, 0x7400}, + {0x2ADE, 0x2004}, + {0x2AE0, 0x70DF}, + {0x2AE2, 0x2F21}, + {0x2AE4, 0x04C2}, + {0x2AE6, 0x0D80}, + {0x2AE8, 0x7800}, + {0x2AEA, 0x7400}, + {0x2AEC, 0x2008}, + {0x2AEE, 0x70DF}, + {0x2AF0, 0x2F21}, + {0x2AF2, 0x04C3}, + {0x2AF4, 0x0D80}, + {0x2AF6, 0x7800}, + {0x2AF8, 0x7400}, + {0x2AFA, 0x0004}, + {0x2AFC, 0x70DF}, + {0x2AFE, 0x2F22}, + {0x2B00, 0x7008}, + {0x2B02, 0x2F1F}, + {0x2B04, 0x7021}, + {0x2B06, 0x2F01}, + {0x2B08, 0x7800}, + {0x2B0A, 0x7400}, + {0x2B0C, 0x0002}, + {0x2B0E, 0x70DF}, + {0x2B10, 0x3F5F}, + {0x2B12, 0x703A}, + {0x2B14, 0x2F01}, + {0x2B16, 0x7800}, + {0x2B18, 0x7400}, + {0x2B1A, 0x2010}, + {0x2B1C, 0x70DF}, + {0x2B1E, 0x3F40}, + {0x2B20, 0x700A}, + {0x2B22, 0x0FC0}, + {0x2B24, 0x7800}, + {0x2B26, 0x7400}, + {0x2B28, 0x2004}, + {0x2B2A, 0x70DF}, + {0x2B2C, 0x2F21}, + {0x2B2E, 0x04C2}, + {0x2B30, 0x0D80}, + {0x2B32, 0x7800}, + {0x2B34, 0x0041}, + {0x2B36, 0x7400}, + {0x2B38, 0x2002}, + {0x2B3A, 0x70DF}, + {0x2B3C, 0x2F22}, + {0x2B3E, 0x04C1}, + {0x2B40, 0x0D80}, + {0x2B42, 0x7800}, + {0x2B44, 0x7400}, + {0x2B46, 0x0001}, + {0x2B48, 0x70DF}, + {0x2B4A, 0x3F5F}, + {0x2B4C, 0x703A}, + {0x2B4E, 0x2F01}, + {0x2B50, 0x7800}, + {0x2B52, 0x7400}, + {0x2B54, 0x200A}, + {0x2B56, 0x70DF}, + {0x2B58, 0x3F40}, + {0x2B5A, 0x700A}, + {0x2B5C, 0x0FC0}, + {0x2B5E, 0x7800}, + {0x2B60, 0x7400}, + {0x2B62, 0x2015}, + {0x2B64, 0x70DF}, + {0x2B66, 0x3F5F}, + {0x2B68, 0x703A}, + {0x2B6A, 0x2F01}, + {0x2B6C, 0x7800}, + {0x2B6E, 0x7400}, + {0x2B70, 0x7800}, + {0x2B72, 0x007F}, + {0x2B74, 0x0000}, + {0x2B76, 0xB9CC}, + {0x2B78, 0x0000}, + {0x2B7A, 0xB9CC}, + {0x2B7C, 0xBA3C}, + {0x2B7E, 0x0002}, + {0x2B80, 0x0000}, + {0x2B82, 0xBA4C}, + {0x2B84, 0x0000}, + {0x2B86, 0xBA4C}, + {0x2B88, 0xBABC}, + {0x2B8A, 0x0002}, + {0x2B8C, 0x0063}, + {0x2B8E, 0xBB26}, + {0x2B90, 0x0063}, + {0x2B92, 0xBB36}, + {0x2B94, 0x0063}, + {0x2B96, 0xBAEA}, + {0x2B98, 0x0063}, + {0x2B9A, 0xBAF8}, + {0x2B9C, 0xBADA}, + {0x2B9E, 0x0004}, + {0x2BA0, 0x0063}, + {0x2BA2, 0xBAEA}, + {0x2BA4, 0x0063}, + {0x2BA6, 0xBB18}, + {0x2BA8, 0x0063}, + {0x2BAA, 0xBB26}, + {0x2BAC, 0x0063}, + {0x2BAE, 0xBB44}, + {0x2BB0, 0xBADA}, + {0x2BB2, 0x0004}, + {0x2BB4, 0x0063}, + {0x2BB6, 0xBACC}, + {0x2BB8, 0x0063}, + {0x2BBA, 0xBADC}, + {0x2BBC, 0x0063}, + {0x2BBE, 0xBAEA}, + {0x2BC0, 0x0063}, + {0x2BC2, 0xBAF8}, + {0x2BC4, 0xBADA}, + {0x2BC6, 0x0004}, + {0x2BC8, 0x0063}, + {0x2BCA, 0xBAEA}, + {0x2BCC, 0x0063}, + {0x2BCE, 0xBB18}, + {0x2BD0, 0x0063}, + {0x2BD2, 0xBACC}, + {0x2BD4, 0x0063}, + {0x2BD6, 0xBB0A}, + {0x2BD8, 0xBADA}, + {0x2BDA, 0x0004}, + {0x2BDC, 0x0063}, + {0x2BDE, 0xBACC}, + {0x2BE0, 0x0063}, + {0x2BE2, 0xBADC}, + {0x2BE4, 0x0063}, + {0x2BE6, 0xBAEA}, + {0x2BE8, 0x0063}, + {0x2BEA, 0xBB18}, + {0x2BEC, 0xBADA}, + {0x2BEE, 0x0004}, + {0x2BF0, 0xFFFF}, + {0x2BF2, 0xBB6E}, + {0x2BF4, 0x0000}, + {0x2BF6, 0x0000}, + {0x2BF8, 0x0000}, + {0x2BFA, 0x0000}, + {0x2BFC, 0x0000}, + {0x2BFE, 0x0000}, + {0x2C00, 0xBB72}, + {0x2C02, 0x0001}, + {0x2C04, 0x0063}, + {0x2C06, 0xBB52}, + {0x2C08, 0x0063}, + {0x2C0A, 0xBB60}, + {0x2C0C, 0x0000}, + {0x2C0E, 0x0000}, + {0x2C10, 0x0000}, + {0x2C12, 0x0000}, + {0x2C14, 0xBADA}, + {0x2C16, 0x0002}, + {0x2C18, 0x0066}, + {0x2C1A, 0x0067}, + {0x2C1C, 0x00AF}, + {0x2C1E, 0x01CF}, + {0x2C20, 0x0087}, + {0x2C22, 0x0083}, + {0x2C24, 0x011B}, + {0x2C26, 0x035A}, + {0x2C28, 0x00FA}, + {0x2C2A, 0x00F2}, + {0x2C2C, 0x00A6}, + {0x2C2E, 0x00A4}, + {0x2C30, 0xFFFF}, + {0x2C32, 0x002C}, + {0x2C34, 0x0058}, + {0x2C36, 0x0000}, + {0x2C38, 0x0000}, + {0x2C3A, 0xBC18}, + {0x2C3C, 0xBB74}, + {0x2C3E, 0xBB80}, + {0x2C40, 0xBC32}, + {0x2C42, 0xBB8C}, + {0x2C44, 0xBBA0}, + {0x2C46, 0xBB8C}, + {0x2C48, 0xBBA0}, + {0x2C4A, 0xBC04}, + {0x2C4C, 0xBC04}, + {0x2C4E, 0xBBF0}, + {0x2C50, 0xBBF0}, + {0x2C52, 0xBBB4}, + {0x2C54, 0xBBC8}, + {0x2C56, 0xBBB4}, + {0x2C58, 0xBBC8}, + {0x2C5A, 0xBC04}, + {0x2C5C, 0xBC04}, + {0x2C5E, 0xBBF0}, + {0x2C60, 0xBBF0}, + {0x2C62, 0xBB8C}, + {0x2C64, 0xBBA0}, + {0x2C66, 0xBB8C}, + {0x2C68, 0xBBA0}, + {0x2C6A, 0xBC04}, + {0x2C6C, 0xBC04}, + {0x2C6E, 0xBBF0}, + {0x2C70, 0xBBF0}, + {0x2C72, 0xBBB4}, + {0x2C74, 0xBBC8}, + {0x2C76, 0xBBB4}, + {0x2C78, 0xBBC8}, + {0x2C7A, 0xBC04}, + {0x2C7C, 0xBC04}, + {0x2C7E, 0xBBF0}, + {0x2C80, 0xBBF0}, + {0x3800, 0x880E}, + {0x3802, 0xBC62}, + {0x3804, 0xBC40}, + {0x3806, 0xD13E}, + {0x3808, 0xBC42}, + {0x380A, 0xBC3C}, + {0x380C, 0x0000}, + {0x380E, 0x0040}, + {0x3810, 0x0040}, + {0x3812, 0x0040}, + {0x3814, 0x0043}, + {0x3816, 0x0046}, + {0x3818, 0x004B}, + {0x381A, 0x004D}, + {0x381C, 0x0051}, + {0x381E, 0x0055}, + {0x3820, 0x005A}, + {0x3822, 0x005E}, + {0x3824, 0x0062}, + {0x3826, 0x0067}, + {0x3828, 0x006C}, + {0x382A, 0x0070}, + {0x382C, 0x0078}, + {0x382E, 0x0086}, + {0x3830, 0x0090}, + {0x3832, 0x0096}, + {0x3834, 0x009D}, + {0x3836, 0x00A5}, + {0x3838, 0x00AD}, + {0x383A, 0x00B4}, + {0x383C, 0x00B9}, + {0x383E, 0x00BE}, + {0x3840, 0x00C3}, + {0x3842, 0x00C8}, + {0x3844, 0x00CD}, + {0x3846, 0x00D2}, + {0x3848, 0x00D7}, + {0x384A, 0x00DC}, + {0x384C, 0x00DC}, + {0x384E, 0x0000}, + {0x3850, 0x0000}, + {0x3852, 0x0000}, + {0x3854, 0x0000}, + {0x3856, 0x0000}, + {0x3858, 0x0000}, + {0x385A, 0x0000}, + {0x385C, 0x0000}, + {0x385E, 0x0000}, + {0x3860, 0x0000}, + {0x3862, 0x0000}, + {0x3864, 0x0000}, + {0x3866, 0x0000}, + {0x3868, 0x0000}, + {0x386A, 0x0000}, + {0x386C, 0x0000}, + {0x386E, 0x0000}, + {0x3870, 0x0000}, + {0x3872, 0x0000}, + {0x3874, 0x0000}, + {0x3876, 0x0000}, + {0x3878, 0x0000}, + {0x387A, 0x0000}, + {0x387C, 0x0000}, + {0x387E, 0x0000}, + {0x3880, 0x0000}, + {0x3882, 0x0000}, + {0x3884, 0x0000}, + {0x3886, 0x0000}, + {0x3888, 0x0000}, + {0x388A, 0x0000}, + {0x388C, 0x0000}, + {0x026A, 0xFFFF}, + {0x026C, 0x00FF}, + {0x026E, 0x0000}, + {0x0360, 0x1E8E}, + {0x040E, 0x01EB}, + {0x0600, 0x1130}, + {0x0602, 0x3112}, + {0x0604, 0x8048}, + {0x0606, 0x00E9}, + {0x067A, 0x0404}, + {0x067C, 0x0404}, + {0x06A8, 0x0240}, + {0x06AA, 0x00CA}, + {0x06AC, 0x0041}, + {0x06B4, 0x3FFF}, + {0x06DE, 0x0404}, + {0x06E0, 0x0404}, + {0x06E2, 0xFF00}, + {0x06E4, 0x8333}, + {0x06E6, 0x8333}, + {0x06E8, 0x8333}, + {0x06EA, 0x8333}, + {0x052A, 0x0000}, + {0x052C, 0x0000}, + {0x0F06, 0x0002}, + {0x0A04, 0xB4C5}, + {0x0A06, 0xC400}, + {0x0A08, 0x988A}, + {0x0A0A, 0xA387}, + {0x0A0E, 0xEEC0}, + {0x0A12, 0x0000}, + {0x0A18, 0x0010}, + {0x0A1C, 0x0040}, + {0x0A20, 0x0015}, + {0x0C00, 0x0021}, + {0x0C16, 0x0002}, + {0x0708, 0x6FC0}, + {0x070C, 0x0000}, + {0x120C, 0x1428}, + {0x121A, 0x0000}, + {0x121C, 0x1896}, + {0x121E, 0x0032}, + {0x1220, 0x0000}, + {0x1222, 0x96FF}, + {0x1244, 0x0000}, + {0x105C, 0x0F0B}, + {0x1958, 0x0000}, + {0x195A, 0x004C}, + {0x195C, 0x0097}, + {0x195E, 0x0221}, + {0x1960, 0x03FE}, + {0x1980, 0x00E0}, + {0x1982, 0x0010}, + {0x1984, 0x2018}, + {0x1986, 0x0008}, + {0x1988, 0x0000}, + {0x198A, 0x0000}, + {0x198C, 0x0880}, + {0x198E, 0x0000}, + {0x1990, 0x1A00}, + {0x1992, 0x0000}, + {0x1994, 0x2800}, + {0x1996, 0x0002}, + {0x1962, 0x0000}, + {0x1964, 0x004C}, + {0x1966, 0x0097}, + {0x1968, 0x0221}, + {0x196A, 0x03FE}, + {0x19C0, 0x00E0}, + {0x19C2, 0x0010}, + {0x19C4, 0x2018}, + {0x19C6, 0x0008}, + {0x19C8, 0x0000}, + {0x19CA, 0x0000}, + {0x19CC, 0x0880}, + {0x19CE, 0x0000}, + {0x19D0, 0x1A00}, + {0x19D2, 0x0000}, + {0x19D4, 0x2800}, + {0x19D6, 0x0002}, + {0x196C, 0x0000}, + {0x196E, 0x004C}, + {0x1970, 0x0097}, + {0x1972, 0x0221}, + {0x1974, 0x03FE}, + {0x1A00, 0x00E0}, + {0x1A02, 0x0010}, + {0x1A04, 0x2018}, + {0x1A06, 0x0008}, + {0x1A08, 0x0000}, + {0x1A0A, 0x0000}, + {0x1A0C, 0x0880}, + {0x1A0E, 0x0000}, + {0x1A10, 0x1A00}, + {0x1A12, 0x0000}, + {0x1A14, 0x2800}, + {0x1A16, 0x0002}, + {0x1976, 0x0000}, + {0x1978, 0x004C}, + {0x197A, 0x0097}, + {0x197C, 0x0221}, + {0x197E, 0x03FE}, + {0x1A40, 0x00E0}, + {0x1A42, 0x0010}, + {0x1A44, 0x2018}, + {0x1A46, 0x0008}, + {0x1A48, 0x0000}, + {0x1A4A, 0x0000}, + {0x1A4C, 0x0880}, + {0x1A4E, 0x0000}, + {0x1A50, 0x1A00}, + {0x1A52, 0x0000}, + {0x1A54, 0x2800}, + {0x1A56, 0x0002}, + {0x192A, 0x0201}, + {0x0384, 0x0001}, + {0x027E, 0x0100}, +}; + +static const struct hi847_reg mode_3264x2448_regs[] = { + {0x0B00, 0x0000}, + {0x0204, 0x0000}, + {0x0206, 0x033C}, + {0x020A, 0x0B4D}, + {0x020E, 0x0B51}, + {0x0214, 0x0200}, + {0x0216, 0x0200}, + {0x0218, 0x0200}, + {0x021A, 0x0200}, + {0x0224, 0x002E}, + {0x022A, 0x0017}, + {0x022C, 0x0E1F}, + {0x022E, 0x09C1}, + {0x0234, 0x1111}, + {0x0236, 0x1111}, + {0x0238, 0x1111}, + {0x023A, 0x1111}, + {0x0250, 0x0000}, + {0x0252, 0x0006}, + {0x0254, 0x0000}, + {0x0256, 0x0000}, + {0x0258, 0x0000}, + {0x025A, 0x0000}, + {0x025C, 0x0000}, + {0x025E, 0x0202}, + {0x0268, 0x00CD}, + {0x0440, 0x0002}, + {0x0F00, 0x0000}, + {0x0F04, 0x0008}, + {0x0F06, 0x0002}, + {0x0B02, 0x0100}, + {0x0B04, 0x00DC}, + {0x0B12, 0x0CC0}, + {0x0B14, 0x0990}, + {0x0B20, 0x0100}, + {0x1100, 0x1100}, + {0x1102, 0x0008}, + {0x1108, 0x0202}, + {0x1118, 0x0000}, + {0x0A10, 0xB040}, + {0x0C14, 0x0008}, + {0x0C18, 0x0CC0}, + {0x0C1A, 0x0990}, + {0x0730, 0x0001}, + {0x0732, 0x0000}, + {0x0734, 0x0300}, + {0x0736, 0x004B}, + {0x0738, 0x0001}, + {0x073C, 0x0900}, + {0x0740, 0x0000}, + {0x0742, 0x0000}, + {0x0744, 0x0300}, + {0x0746, 0x007D}, + {0x0748, 0x0002}, + {0x074A, 0x0900}, + {0x074C, 0x0000}, + {0x074E, 0x0100}, + {0x0750, 0x0000}, + {0x1200, 0x0946}, + {0x1202, 0x1A00}, + {0x120E, 0x6027}, + {0x1210, 0x8027}, + {0x1246, 0x0105}, + {0x1000, 0x0300}, + {0x1002, 0xC311}, + {0x1004, 0x2BB0}, + {0x1010, 0x087B}, + {0x1012, 0x0040}, + {0x1014, 0x0020}, + {0x1016, 0x0020}, + {0x101A, 0x0020}, + {0x1020, 0xC107}, + {0x1022, 0x081E}, + {0x1024, 0x0509}, + {0x1026, 0x0B0A}, + {0x1028, 0x1409}, + {0x102A, 0x0B05}, + {0x102C, 0x1400}, + {0x1038, 0x0000}, + {0x103E, 0x0001}, + {0x1040, 0x0000}, + {0x1042, 0x0008}, + {0x1044, 0x0120}, + {0x1046, 0x01B0}, + {0x1048, 0x0090}, + {0x1066, 0x089C}, + {0x1600, 0x0000}, + {0x1608, 0x0028}, + {0x160A, 0x0C80}, + {0x160C, 0x001A}, + {0x160E, 0x0960}, + {0x0252, 0x0009}, + {0x0202, 0x0000}, +}; + +static const struct hi847_reg mode_1632x1224_regs[] = { + {0x0B00, 0x0000}, + {0x0204, 0x0200}, + {0x0206, 0x033C}, + {0x020A, 0x05A5}, + {0x020E, 0x05A9}, + {0x0214, 0x0200}, + {0x0216, 0x0200}, + {0x0218, 0x0200}, + {0x021A, 0x0200}, + {0x0224, 0x002C}, + {0x022A, 0x0015}, + {0x022C, 0x0E2D}, + {0x022E, 0x09C1}, + {0x0234, 0x3311}, + {0x0236, 0x3311}, + {0x0238, 0x3311}, + {0x023A, 0x2222}, + {0x0250, 0x0000}, + {0x0252, 0x0006}, + {0x0254, 0x0000}, + {0x0256, 0x0000}, + {0x0258, 0x0000}, + {0x025A, 0x0000}, + {0x025C, 0x0000}, + {0x025E, 0x0202}, + {0x0268, 0x00CD}, + {0x0440, 0x0002}, + {0x0F00, 0x0400}, + {0x0F04, 0x0004}, + {0x0F06, 0x0002}, + {0x0B02, 0x0100}, + {0x0B04, 0x00FC}, + {0x0B12, 0x0660}, + {0x0B14, 0x04C8}, + {0x0B20, 0x0200}, + {0x1100, 0x1100}, + {0x1102, 0x0008}, + {0x1108, 0x0402}, + {0x1118, 0x0000}, + {0x0A10, 0xB060}, + {0x0C14, 0x0008}, + {0x0C18, 0x0CC0}, + {0x0C1A, 0x04C8}, + {0x0730, 0x0001}, + {0x0732, 0x0000}, + {0x0734, 0x0300}, + {0x0736, 0x004B}, + {0x0738, 0x0001}, + {0x073C, 0x0900}, + {0x0740, 0x0000}, + {0x0742, 0x0000}, + {0x0744, 0x0300}, + {0x0746, 0x007D}, + {0x0748, 0x0002}, + {0x074A, 0x0900}, + {0x074C, 0x0100}, + {0x074E, 0x0100}, + {0x0750, 0x0000}, + {0x1200, 0x0946}, + {0x1202, 0x1A00}, + {0x120E, 0x6027}, + {0x1210, 0x8027}, + {0x1246, 0x0105}, + {0x1000, 0x0300}, + {0x1002, 0xC311}, + {0x1004, 0x2BB0}, + {0x1010, 0x042B}, + {0x1012, 0x0012}, + {0x1014, 0x0020}, + {0x1016, 0x0020}, + {0x101A, 0x0020}, + {0x1020, 0xC103}, + {0x1022, 0x040F}, + {0x1024, 0x0304}, + {0x1026, 0x0607}, + {0x1028, 0x0D06}, + {0x102A, 0x0605}, + {0x102C, 0x0C00}, + {0x1038, 0x0000}, + {0x103E, 0x0101}, + {0x1040, 0x0000}, + {0x1042, 0x0008}, + {0x1044, 0x0120}, + {0x1046, 0x01B0}, + {0x1048, 0x0090}, + {0x1066, 0x043B}, + {0x1600, 0x0400}, + {0x1608, 0x0028}, + {0x160A, 0x0C80}, + {0x160C, 0x001A}, + {0x160E, 0x0960}, + {0x0252, 0x0009}, + {0x0202, 0x0000}, +}; + +static const char * const hi847_test_pattern_menu[] = { + "No Pattern", + "Solid Colour", + "100% Colour Bars", + "Fade To Grey Colour Bars", + "PN9", + "Horizontal Gradient Pattern", + "Vertical Gradient Pattern", + "Check Board", + "Slant Pattern", +}; + +static const s64 link_freq_menu_items[] = { + HI847_LINK_FREQ_400MHZ, + HI847_LINK_FREQ_200MHZ, +}; + +static const struct hi847_link_freq_config link_freq_configs[] = { + [HI847_LINK_FREQ_400MHZ_INDEX] = { + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_lane_4), + .regs = mipi_data_rate_lane_4, + } + }, + [HI847_LINK_FREQ_200MHZ_INDEX] = { + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_lane_4), + .regs = mipi_data_rate_lane_4, + } + } +}; + +static const struct hi847_mode supported_modes[] = { + { + .width = 3264, + .height = 2448, + .fll_def = HI847_FLL_30FPS, + .fll_min = HI847_FLL_30FPS_MIN, + .llp = 0x033C, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3264x2448_regs), + .regs = mode_3264x2448_regs, + }, + .link_freq_index = HI847_LINK_FREQ_400MHZ_INDEX, + }, + { + .width = 1632, + .height = 1224, + .fll_def = HI847_FLL_60FPS, + .fll_min = HI847_FLL_60FPS_MIN, + .llp = 0x033C, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1632x1224_regs), + .regs = mode_1632x1224_regs, + }, + .link_freq_index = HI847_LINK_FREQ_200MHZ_INDEX, + } +}; + +struct hi847 { + struct device *dev; + struct clk *clk; + + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + + /* Current mode */ + const struct hi847_mode *cur_mode; + + /* To serialize asynchronus callbacks */ + struct mutex mutex; +}; + +static u64 to_pixel_rate(u32 f_index) +{ + u64 pixel_rate = link_freq_menu_items[f_index] * 2 * HI847_DATA_LANES; + + do_div(pixel_rate, HI847_RGB_DEPTH); + + return pixel_rate; +} + +static int hi847_read_reg(struct hi847 *hi847, u16 reg, u16 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi847->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2]; + u8 data_buf[4] = {0}; + int ret; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, addr_buf); + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(addr_buf); + msgs[0].buf = addr_buf; + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +static int hi847_write_reg(struct hi847 *hi847, u16 reg, u16 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi847->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << 8 * (4 - len), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +static int hi847_write_reg_list(struct hi847 *hi847, + const struct hi847_reg_list *r_list) +{ + unsigned int i; + int ret; + + for (i = 0; i < r_list->num_of_regs; i++) { + ret = hi847_write_reg(hi847, r_list->regs[i].address, + HI847_REG_VALUE_16BIT, + r_list->regs[i].val); + if (ret) { + dev_err_ratelimited(hi847->dev, + "failed to write reg 0x%4.4x. error = %d", + r_list->regs[i].address, ret); + return ret; + } + } + + return 0; +} + +static int hi847_update_digital_gain(struct hi847 *hi847, u32 d_gain) +{ + int ret; + + ret = hi847_write_reg(hi847, HI847_REG_MWB_GR_GAIN, + HI847_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + ret = hi847_write_reg(hi847, HI847_REG_MWB_GB_GAIN, + HI847_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + ret = hi847_write_reg(hi847, HI847_REG_MWB_R_GAIN, + HI847_REG_VALUE_16BIT, d_gain); + if (ret) + return ret; + + return hi847_write_reg(hi847, HI847_REG_MWB_B_GAIN, + HI847_REG_VALUE_16BIT, d_gain); +} + +static int hi847_test_pattern(struct hi847 *hi847, u32 pattern) +{ + int ret; + u32 val; + + if (pattern) { + ret = hi847_read_reg(hi847, HI847_REG_ISP, + HI847_REG_VALUE_16BIT, &val); + if (ret) + return ret; + + ret = hi847_write_reg(hi847, HI847_REG_ISP, + HI847_REG_VALUE_16BIT, + val | HI847_REG_ISP_TPG_EN); + if (ret) + return ret; + } + + ret = hi847_read_reg(hi847, HI847_REG_TEST_PATTERN, + HI847_REG_VALUE_16BIT, &val); + if (ret) + return ret; + + return hi847_write_reg(hi847, HI847_REG_TEST_PATTERN, + HI847_REG_VALUE_16BIT, val | pattern << 8); +} + +static int hi847_grbg_shift(struct hi847 *hi847) +{ + int ret; + int hflip, vflip; + + /* regs shift for full size */ + static const u32 FORMAT_X_SHIFT_1[2][2] = { + { 0x0008, 0x0007, }, + { 0x0008, 0x0007, }, + }; + + static const u32 FORMAT_Y_SHIFT_1[2][2] = { + { 0x0002, 0x0002, }, + { 0x0001, 0x0001, }, + }; + + /* regs shift for binning size */ + static const u32 FORMAT_X_SHIFT_2[2][2] = { + { 0x0004, 0x0003, }, + { 0x0004, 0x0003, }, + }; + + static const u32 FORMAT_Y_SHIFT_2[2][2] = { + { 0x0002, 0x0002, }, + { 0x0001, 0x0001, }, + }; + + hflip = hi847->hflip->val; + vflip = hi847->vflip->val; + + if (hi847->cur_mode->width == 3264) { + ret = hi847_write_reg(hi847, HI847_REG_FORMAT_X, + HI847_REG_VALUE_16BIT, + FORMAT_X_SHIFT_1[vflip][hflip]); + if (ret) + return ret; + + return hi847_write_reg(hi847, HI847_REG_FORMAT_Y, + HI847_REG_VALUE_16BIT, + FORMAT_Y_SHIFT_1[vflip][hflip]); + } else { + ret = hi847_write_reg(hi847, HI847_REG_FORMAT_X, + HI847_REG_VALUE_16BIT, + FORMAT_X_SHIFT_2[vflip][hflip]); + if (ret) + return ret; + + return hi847_write_reg(hi847, HI847_REG_FORMAT_Y, + HI847_REG_VALUE_16BIT, + FORMAT_Y_SHIFT_2[vflip][hflip]); + } +} + +static int hi847_set_ctrl_hflip(struct hi847 *hi847, u32 ctrl_val) +{ + int ret; + u32 val; + + ret = hi847_read_reg(hi847, HI847_REG_MIRROR_FLIP, + HI847_REG_VALUE_16BIT, &val); + if (ret) + return ret; + + ret = hi847_grbg_shift(hi847); + if (ret) + return ret; + + return hi847_write_reg(hi847, HI847_REG_MIRROR_FLIP, + HI847_REG_VALUE_16BIT, + ctrl_val ? val | BIT(8) : val & ~BIT(8)); +} + +static int hi847_set_ctrl_vflip(struct hi847 *hi847, u8 ctrl_val) +{ + int ret; + u32 val; + + ret = hi847_read_reg(hi847, HI847_REG_MIRROR_FLIP, + HI847_REG_VALUE_16BIT, &val); + if (ret) + return ret; + + ret = hi847_grbg_shift(hi847); + if (ret) + return ret; + + return hi847_write_reg(hi847, HI847_REG_MIRROR_FLIP, + HI847_REG_VALUE_16BIT, + ctrl_val ? val | BIT(9) : val & ~BIT(9)); +} + +static int hi847_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct hi847 *hi847 = container_of(ctrl->handler, + struct hi847, ctrl_handler); + s64 exposure_max; + int ret = 0; + + /* Propagate change of current control to all related controls */ + if (ctrl->id == V4L2_CID_VBLANK) { + /* Update max exposure while meeting expected vblanking */ + exposure_max = hi847->cur_mode->height + ctrl->val - + HI847_EXPOSURE_MAX_MARGIN; + __v4l2_ctrl_modify_range(hi847->exposure, + hi847->exposure->minimum, + exposure_max, hi847->exposure->step, + exposure_max); + } + + /* V4L2 controls values will be applied only when power is already up */ + if (!pm_runtime_get_if_in_use(hi847->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = hi847_write_reg(hi847, HI847_REG_ANALOG_GAIN, + HI847_REG_VALUE_16BIT, ctrl->val); + break; + + case V4L2_CID_DIGITAL_GAIN: + ret = hi847_update_digital_gain(hi847, ctrl->val); + break; + + case V4L2_CID_EXPOSURE: + ret = hi847_write_reg(hi847, HI847_REG_EXPOSURE, + HI847_REG_VALUE_16BIT, ctrl->val); + break; + + case V4L2_CID_VBLANK: + /* Update FLL that meets expected vertical blanking */ + ret = hi847_write_reg(hi847, HI847_REG_FLL, + HI847_REG_VALUE_16BIT, + hi847->cur_mode->height + ctrl->val); + break; + + case V4L2_CID_TEST_PATTERN: + ret = hi847_test_pattern(hi847, ctrl->val); + break; + + case V4L2_CID_HFLIP: + hi847_set_ctrl_hflip(hi847, ctrl->val); + break; + + case V4L2_CID_VFLIP: + hi847_set_ctrl_vflip(hi847, ctrl->val); + break; + + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(hi847->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops hi847_ctrl_ops = { + .s_ctrl = hi847_set_ctrl, +}; + +static int hi847_init_controls(struct hi847 *hi847) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max, h_blank; + int ret; + + ctrl_hdlr = &hi847->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); + if (ret) + return ret; + + ctrl_hdlr->lock = &hi847->mutex; + hi847->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &hi847_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, + 0, link_freq_menu_items); + if (hi847->link_freq) + hi847->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + hi847->pixel_rate = v4l2_ctrl_new_std + (ctrl_hdlr, &hi847_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, + to_pixel_rate(HI847_LINK_FREQ_400MHZ_INDEX), + 1, + to_pixel_rate(HI847_LINK_FREQ_400MHZ_INDEX)); + hi847->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &hi847_ctrl_ops, + V4L2_CID_VBLANK, + hi847->cur_mode->fll_min - + hi847->cur_mode->height, + HI847_FLL_MAX - + hi847->cur_mode->height, 1, + hi847->cur_mode->fll_def - + hi847->cur_mode->height); + + h_blank = hi847->cur_mode->llp - hi847->cur_mode->width; + + hi847->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &hi847_ctrl_ops, + V4L2_CID_HBLANK, h_blank, h_blank, 1, + h_blank); + if (hi847->hblank) + hi847->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(ctrl_hdlr, &hi847_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + HI847_ANAL_GAIN_MIN, HI847_ANAL_GAIN_MAX, + HI847_ANAL_GAIN_STEP, HI847_ANAL_GAIN_MIN); + v4l2_ctrl_new_std(ctrl_hdlr, &hi847_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + HI847_DGTL_GAIN_MIN, HI847_DGTL_GAIN_MAX, + HI847_DGTL_GAIN_STEP, HI847_DGTL_GAIN_DEFAULT); + exposure_max = hi847->cur_mode->fll_def - HI847_EXPOSURE_MAX_MARGIN; + hi847->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &hi847_ctrl_ops, + V4L2_CID_EXPOSURE, + HI847_EXPOSURE_MIN, exposure_max, + HI847_EXPOSURE_STEP, + exposure_max); + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &hi847_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(hi847_test_pattern_menu) - 1, + 0, 0, hi847_test_pattern_menu); + hi847->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &hi847_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + hi847->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &hi847_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + if (ctrl_hdlr->error) + return ctrl_hdlr->error; + + hi847->sd.ctrl_handler = ctrl_hdlr; + + return 0; +} + +static void hi847_assign_pad_format(const struct hi847_mode *mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = mode->width; + fmt->height = mode->height; + fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->field = V4L2_FIELD_NONE; +} + +static int hi847_start_streaming(struct hi847 *hi847) +{ + const struct hi847_reg_list *reg_list; + int link_freq_index, ret; + + link_freq_index = hi847->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + ret = hi847_write_reg_list(hi847, reg_list); + if (ret) { + dev_err(hi847->dev, "failed to set plls"); + return ret; + } + + reg_list = &hi847->cur_mode->reg_list; + ret = hi847_write_reg_list(hi847, reg_list); + if (ret) { + dev_err(hi847->dev, "failed to set mode"); + return ret; + } + + ret = __v4l2_ctrl_handler_setup(hi847->sd.ctrl_handler); + if (ret) + return ret; + + ret = hi847_write_reg(hi847, HI847_REG_MODE_TG, + HI847_REG_VALUE_16BIT, HI847_REG_MODE_TG_ENABLE); + + ret = hi847_write_reg(hi847, HI847_REG_MODE_SELECT, + HI847_REG_VALUE_16BIT, HI847_MODE_STREAMING); + + if (ret) { + dev_err(hi847->dev, "failed to set stream"); + return ret; + } + + return 0; +} + +static void hi847_stop_streaming(struct hi847 *hi847) +{ + if (hi847_write_reg(hi847, HI847_REG_MODE_TG, + HI847_REG_VALUE_16BIT, HI847_REG_MODE_TG_DISABLE)) + dev_err(hi847->dev, "failed to set stream 0x%x", + HI847_REG_MODE_TG); + + if (hi847_write_reg(hi847, HI847_REG_MODE_SELECT, + HI847_REG_VALUE_16BIT, HI847_MODE_STANDBY)) + dev_err(hi847->dev, "failed to set stream 0x%x", + HI847_REG_MODE_SELECT); +} + +static int hi847_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct hi847 *hi847 = to_hi847(sd); + int ret = 0; + + mutex_lock(&hi847->mutex); + if (enable) { + ret = pm_runtime_resume_and_get(hi847->dev); + if (ret) { + mutex_unlock(&hi847->mutex); + return ret; + } + + ret = hi847_start_streaming(hi847); + if (ret) { + enable = 0; + hi847_stop_streaming(hi847); + pm_runtime_put(hi847->dev); + } + } else { + hi847_stop_streaming(hi847); + pm_runtime_put(hi847->dev); + } + + mutex_unlock(&hi847->mutex); + + return ret; +} + +static int hi847_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct hi847 *hi847 = to_hi847(sd); + const struct hi847_mode *mode; + s32 vblank_def, h_blank; + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), width, + height, fmt->format.width, + fmt->format.height); + + mutex_lock(&hi847->mutex); + hi847_assign_pad_format(mode, &fmt->format); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_state_get_format(sd_state, fmt->pad) = + fmt->format; + } else { + hi847->cur_mode = mode; + __v4l2_ctrl_s_ctrl(hi847->link_freq, mode->link_freq_index); + __v4l2_ctrl_s_ctrl_int64(hi847->pixel_rate, + to_pixel_rate(mode->link_freq_index)); + + /* Update limits and set FPS to default */ + vblank_def = mode->fll_def - mode->height; + __v4l2_ctrl_modify_range(hi847->vblank, + mode->fll_min - mode->height, + HI847_FLL_MAX - mode->height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(hi847->vblank, vblank_def); + + h_blank = hi847->cur_mode->llp - hi847->cur_mode->width; + + __v4l2_ctrl_modify_range(hi847->hblank, h_blank, h_blank, 1, + h_blank); + } + + mutex_unlock(&hi847->mutex); + + return 0; +} + +static int hi847_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct hi847 *hi847 = to_hi847(sd); + + mutex_lock(&hi847->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_state_get_format(sd_state, + fmt->pad); + else + hi847_assign_pad_format(hi847->cur_mode, &fmt->format); + + mutex_unlock(&hi847->mutex); + + return 0; +} + +static int hi847_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int hi847_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int hi847_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct hi847 *hi847 = to_hi847(sd); + + mutex_lock(&hi847->mutex); + hi847_assign_pad_format(&supported_modes[0], + v4l2_subdev_state_get_format(fh->state, 0)); + mutex_unlock(&hi847->mutex); + + return 0; +} + +static const struct v4l2_subdev_video_ops hi847_video_ops = { + .s_stream = hi847_set_stream, +}; + +static const struct v4l2_subdev_pad_ops hi847_pad_ops = { + .set_fmt = hi847_set_format, + .get_fmt = hi847_get_format, + .enum_mbus_code = hi847_enum_mbus_code, + .enum_frame_size = hi847_enum_frame_size, +}; + +static const struct v4l2_subdev_ops hi847_subdev_ops = { + .video = &hi847_video_ops, + .pad = &hi847_pad_ops, +}; + +static const struct media_entity_operations hi847_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops hi847_internal_ops = { + .open = hi847_open, +}; + +static int hi847_identify_module(struct hi847 *hi847) +{ + int ret; + u32 val; + + ret = hi847_read_reg(hi847, HI847_REG_CHIP_ID, + HI847_REG_VALUE_16BIT, &val); + if (ret) + return ret; + + if (val != HI847_CHIP_ID) { + dev_err(hi847->dev, "chip id mismatch: %x!=%x", + HI847_CHIP_ID, val); + return -ENXIO; + } + + return 0; +} + +static int hi847_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret; + unsigned int i, j; + + if (!fwnode) + return -ENXIO; + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return -ENXIO; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != HI847_DATA_LANES) { + dev_err(dev, "number of CSI2 data lanes %d is not supported", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto check_hwcfg_error; + } + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequencies defined"); + ret = -EINVAL; + goto check_hwcfg_error; + } + + for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) { + for (j = 0; j < bus_cfg.nr_of_link_frequencies; j++) { + if (link_freq_menu_items[i] == + bus_cfg.link_frequencies[j]) + break; + } + + if (j == bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequency %lld supported", + link_freq_menu_items[i]); + ret = -EINVAL; + goto check_hwcfg_error; + } + } + +check_hwcfg_error: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static void hi847_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct hi847 *hi847 = to_hi847(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + pm_runtime_disable(hi847->dev); + mutex_destroy(&hi847->mutex); +} + +static int hi847_probe(struct i2c_client *client) +{ + struct hi847 *hi847; + unsigned long freq; + int ret; + + hi847 = devm_kzalloc(&client->dev, sizeof(*hi847), GFP_KERNEL); + if (!hi847) + return -ENOMEM; + + hi847->dev = &client->dev; + + hi847->clk = devm_v4l2_sensor_clk_get(hi847->dev, NULL); + if (IS_ERR(hi847->clk)) + return dev_err_probe(hi847->dev, PTR_ERR(hi847->clk), + "failed to get clock\n"); + + freq = clk_get_rate(hi847->clk); + if (freq != HI847_MCLK) + return dev_err_probe(hi847->dev, -EINVAL, + "external clock %lu is not supported\n", + freq); + + ret = hi847_check_hwcfg(hi847->dev); + if (ret) { + dev_err(hi847->dev, "failed to get HW configuration: %d", + ret); + return ret; + } + + v4l2_i2c_subdev_init(&hi847->sd, client, &hi847_subdev_ops); + ret = hi847_identify_module(hi847); + if (ret) { + dev_err(hi847->dev, "failed to find sensor: %d", ret); + return ret; + } + + mutex_init(&hi847->mutex); + hi847->cur_mode = &supported_modes[0]; + ret = hi847_init_controls(hi847); + if (ret) { + dev_err(hi847->dev, "failed to init controls: %d", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + hi847->sd.internal_ops = &hi847_internal_ops; + hi847->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + hi847->sd.entity.ops = &hi847_subdev_entity_ops; + hi847->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + hi847->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&hi847->sd.entity, 1, &hi847->pad); + if (ret) { + dev_err(hi847->dev, "failed to init entity pads: %d", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&hi847->sd); + if (ret < 0) { + dev_err(hi847->dev, "failed to register V4L2 subdev: %d", + ret); + goto probe_error_media_entity_cleanup; + } + + pm_runtime_set_active(hi847->dev); + pm_runtime_enable(hi847->dev); + pm_runtime_idle(hi847->dev); + + return 0; + +probe_error_media_entity_cleanup: + media_entity_cleanup(&hi847->sd.entity); + +probe_error_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(hi847->sd.ctrl_handler); + mutex_destroy(&hi847->mutex); + + return ret; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id hi847_acpi_ids[] = { + {"HYV0847"}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, hi847_acpi_ids); +#endif + +static struct i2c_driver hi847_i2c_driver = { + .driver = { + .name = "hi847", + .acpi_match_table = ACPI_PTR(hi847_acpi_ids), + }, + .probe = hi847_probe, + .remove = hi847_remove, +}; + +module_i2c_driver(hi847_i2c_driver); + +MODULE_AUTHOR("Shawn Tu"); +MODULE_DESCRIPTION("Hynix HI847 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/imx111.c b/drivers/media/i2c/imx111.c new file mode 100644 index 000000000..8eb919788 --- /dev/null +++ b/drivers/media/i2c/imx111.c @@ -0,0 +1,1610 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/* product information registers */ +#define IMX111_PRODUCT_ID CCI_REG16(0x0000) +#define IMX111_CHIP_ID 0x111 +#define IMX111_REVISION CCI_REG8(0x0002) +#define IMX111_MANUFACTURER_ID CCI_REG8(0x0003) +#define IMX111_FRAME_COUNTER CCI_REG8(0x0005) +#define IMX111_PIXEL_ORDER CCI_REG8(0x0006) + +/* general configuration registers */ +#define IMX111_STREAMING_MODE CCI_REG8(0x0100) +#define IMX111_MODE_STANDBY 0 +#define IMX111_MODE_STREAMING 1 +#define IMX111_IMAGE_ORIENTATION CCI_REG8(0x0101) +#define IMX111_IMAGE_HFLIP BIT(0) +#define IMX111_IMAGE_VFLIP BIT(1) +#define IMX111_SOFTWARE_RESET CCI_REG8(0x0103) +#define IMX111_RESET_ON 1 +#define IMX111_GROUP_WRITE CCI_REG8(0x0104) +#define IMX111_GROUP_WRITE_ON 1 +#define IMX111_FRAME_DROP CCI_REG8(0x0105) +#define IMX111_FRAME_DROP_ON 1 +#define IMX111_CHANNEL_ID CCI_REG8(0x0110) +#define IMX111_SIGNALLING_MODE CCI_REG8(0x0111) +#define IMX111_DATA_DEPTH CCI_REG16(0x0112) +#define IMX111_DATA_DEPTH_RAW8 0x08 +#define IMX111_DATA_DEPTH_RAW10 0x0a + +/* integration time registers */ +#define IMX111_INTEGRATION_TIME CCI_REG16(0x0202) +#define IMX111_INTEGRATION_TIME_MIN 0x1 +#define IMX111_INTEGRATION_TIME_MAX 0xffff +#define IMX111_INTEGRATION_TIME_STEP 1 +#define IMX111_INTEGRATION_TIME_OFFSET 5 + +/* analog gain control */ +#define IMX111_REG_ANALOG_GAIN CCI_REG8(0x0205) +#define IMX111_ANA_GAIN_MIN 0 +#define IMX111_ANA_GAIN_MAX 240 +#define IMX111_ANA_GAIN_STEP 1 +#define IMX111_ANA_GAIN_DEFAULT 0 + +/* digital gain control */ +#define IMX111_REG_DIG_GAIN_GREENR CCI_REG16(0x020e) +#define IMX111_REG_DIG_GAIN_RED CCI_REG16(0x0210) +#define IMX111_REG_DIG_GAIN_BLUE CCI_REG16(0x0212) +#define IMX111_REG_DIG_GAIN_GREENB CCI_REG16(0x0214) +#define IMX111_DGTL_GAIN_MIN 0x0100 +#define IMX111_DGTL_GAIN_MAX 0x0fff +#define IMX111_DGTL_GAIN_DEFAULT 0x0100 +#define IMX111_DGTL_GAIN_STEP 1 + +/* clock configuration registers */ +#define IMX111_PIXEL_CLK_DIVIDER_PLL1 CCI_REG8(0x0301) +#define IMX111_SYSTEM_CLK_DIVIDER_PLL1 CCI_REG8(0x0303) +#define IMX111_PRE_PLL_CLK_DIVIDER_PLL1 CCI_REG8(0x0305) +#define IMX111_PLL_MULTIPLIER_PLL1 CCI_REG8(0x0307) +#define IMX111_PLL_SETTLING_TIME CCI_REG8(0x303c) +#define IMX111_PLL_SETTLING_TIME_DEFAULT 200 +#define IMX111_POST_DIVIDER CCI_REG8(0x30a4) +#define IMX111_POST_DIVIDER_DIV1 2 +#define IMX111_POST_DIVIDER_DIV2 0 +#define IMX111_POST_DIVIDER_DIV4 1 + +/* frame timing registers */ +#define IMX111_VERTICAL_TOTAL_LENGTH CCI_REG16(0x0340) +#define IMX111_VTL_MAX 0x09d8 +#define IMX111_VBLANK_MIN 16 +#define IMX111_HORIZONTAL_TOTAL_LENGTH CCI_REG16(0x0342) +#define IMX111_HTL_MAX 0x0dd0 +#define IMX111_HBLANK_MIN 16 + +/* image size registers */ +#define IMX111_HORIZONTAL_START CCI_REG16(0x0344) +#define IMX111_VERTICAL_START CCI_REG16(0x0346) +#define IMX111_HORIZONTAL_END CCI_REG16(0x0348) +#define IMX111_VERTICAL_END CCI_REG16(0x034a) +#define IMX111_IMAGE_WIDTH CCI_REG16(0x034c) +#define IMX111_IMAGE_HEIGHT CCI_REG16(0x034e) +#define IMX111_H_EVEN_INC CCI_REG8(0x0381) +#define IMX111_H_ODD_INC CCI_REG8(0x0383) +#define IMX111_W_EVEN_INC CCI_REG8(0x0385) +#define IMX111_W_ODD_INC CCI_REG8(0x0387) + +/* test pattern registers */ +#define IMX111_TEST_PATTERN CCI_REG8(0x0601) +#define IMX111_TEST_PATTERN_NONE 0 +#define IMX111_TEST_PATTERN_SOLID 1 +#define IMX111_TEST_PATTERN_BARS 2 +#define IMX111_TEST_PATTERN_FADE 3 +#define IMX111_TEST_PATTERN_PN9 4 +#define IMX111_SOLID_COLOR_RED CCI_REG16(0x0602) +#define IMX111_SOLID_COLOR_GR CCI_REG16(0x0604) +#define IMX111_SOLID_COLOR_BLUE CCI_REG16(0x0606) +#define IMX111_SOLID_COLOR_GB CCI_REG16(0x0608) +#define IMX111_TESTP_COLOUR_MIN 0 +#define IMX111_TESTP_COLOUR_MAX 0x03ff +#define IMX111_TESTP_COLOUR_STEP 1 + +#define IMX111_FRAME_RATE_STEP 5 + +#define IMX111_PIXEL_ARRAY_WIDTH 3280U +#define IMX111_PIXEL_ARRAY_HEIGHT 2464U + +enum { + IMX111_MODE_3280x2464, + IMX111_MODE_3280x1848, + IMX111_MODE_3280x1098, + IMX111_MODE_2100x1200, + IMX111_MODE_1952x1098, + IMX111_MODE_1920x1080, + IMX111_MODE_1640x1232, + IMX111_MODE_1440x1080, + IMX111_MODE_1640x924, + IMX111_MODE_1308x736, + IMX111_MODE_1280x720, + IMX111_MODE_820x614, + IMX111_MODE_640x480, +}; + +static const struct regulator_bulk_data imx111_supplies[] = { + { .supply = "iovdd" }, + { .supply = "dvdd" }, + { .supply = "avdd" }, +}; + +struct imx111_mode { + u32 width; + u32 height; + + /* Default vertical and horizontal total length */ + u32 vtl_def; + u32 htl_def; + + struct { + const struct cci_reg_sequence *regs; + u32 num_of_regs; + } reg_list; +}; + +struct imx111_pll { + u64 extclk_rate; + u8 pre_div; + u8 mult; +}; + +struct imx111 { + struct regmap *regmap; + + struct clk *extclk; + struct gpio_desc *reset; + struct regulator_bulk_data *supplies; + + struct v4l2_fwnode_endpoint bus_cfg; + struct v4l2_subdev sd; + struct media_pad pad; + + /* V4L2 Controls */ + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + + /* Current mode */ + const struct imx111_mode *cur_mode; + const struct imx111_pll *pll; + u32 data_depth; + + u64 pixel_clk_raw; + s64 default_link_freq; +}; + +static const struct imx111_pll imx111_pll[] = { + { .extclk_rate = 6000000, .pre_div = 1, .mult = 113, }, + { .extclk_rate = 12000000, .pre_div = 2, .mult = 113, }, + { .extclk_rate = 13500000, .pre_div = 1, .mult = 50, }, + { .extclk_rate = 18000000, .pre_div = 2, .mult = 75, }, + { .extclk_rate = 24000000, .pre_div = 4, .mult = 113, }, + { .extclk_rate = 27000000, .pre_div = 2, .mult = 50, }, + { .extclk_rate = 36000000, .pre_div = 4, .mult = 75, }, + { .extclk_rate = 54000000, .pre_div = 4, .mult = 50, }, +}; + +/* + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 imx111_mbus_formats[] = { + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SBGGR8_1X8, + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, +}; + +static const struct cci_reg_sequence imx111_global_init[] = { + { CCI_REG8(0x3080), 0x50 }, + { CCI_REG8(0x3087), 0x53 }, + { CCI_REG8(0x309d), 0x94 }, + { CCI_REG8(0x30b1), 0x03 }, + { CCI_REG8(0x30c6), 0x00 }, + { CCI_REG8(0x30c7), 0x00 }, + { CCI_REG8(0x3115), 0x0b }, + { CCI_REG8(0x3118), 0x30 }, + { CCI_REG8(0x311d), 0x25 }, + { CCI_REG8(0x3121), 0x0a }, + { CCI_REG8(0x3212), 0xf2 }, + { CCI_REG8(0x3213), 0x0f }, + { CCI_REG8(0x3215), 0x0f }, + { CCI_REG8(0x3217), 0x0b }, + { CCI_REG8(0x3219), 0x0b }, + { CCI_REG8(0x321b), 0x0d }, + { CCI_REG8(0x321d), 0x0d }, + { CCI_REG8(0x32aa), 0x11 }, + { CCI_REG8(0x3032), 0x40 }, +}; + +static const struct cci_reg_sequence mode_820x614[] = { + { IMX111_GROUP_WRITE, 1 }, + { IMX111_HORIZONTAL_START, 0x0008 }, { IMX111_VERTICAL_START, 0x0034 }, + { IMX111_HORIZONTAL_END, 0x0cd7 }, { IMX111_VERTICAL_END, 0x09cb }, + { IMX111_IMAGE_WIDTH, 0x0334 }, { IMX111_IMAGE_HEIGHT, 0x0266 }, + { IMX111_GROUP_WRITE, 0 }, + { IMX111_H_EVEN_INC, 0x05 }, { IMX111_H_ODD_INC, 0x03 }, + { IMX111_W_EVEN_INC, 0x05 }, { IMX111_W_ODD_INC, 0x03 }, + { CCI_REG8(0x3033), 0x00 }, { CCI_REG8(0x303d), 0x10 }, + { CCI_REG8(0x303e), 0x40 }, { CCI_REG8(0x3040), 0x08 }, + { CCI_REG8(0x3041), 0x97 }, { CCI_REG8(0x3048), 0x01 }, + { CCI_REG8(0x304c), 0x6f }, { CCI_REG8(0x304d), 0x03 }, + { CCI_REG8(0x3064), 0x12 }, { CCI_REG8(0x3073), 0x00 }, + { CCI_REG8(0x3074), 0x11 }, { CCI_REG8(0x3075), 0x11 }, + { CCI_REG8(0x3076), 0x11 }, { CCI_REG8(0x3077), 0x11 }, + { CCI_REG8(0x3079), 0x00 }, { CCI_REG8(0x307a), 0x00 }, + { CCI_REG8(0x309b), 0x28 }, { CCI_REG8(0x309c), 0x13 }, + { CCI_REG8(0x309e), 0x00 }, { CCI_REG8(0x30a0), 0x14 }, + { CCI_REG8(0x30a1), 0x09 }, { CCI_REG8(0x30aa), 0x03 }, + { CCI_REG8(0x30b2), 0x03 }, { CCI_REG8(0x30d5), 0x09 }, + { CCI_REG8(0x30d6), 0x00 }, { CCI_REG8(0x30d7), 0x00 }, + { CCI_REG8(0x30d8), 0x00 }, { CCI_REG8(0x30d9), 0x00 }, + { CCI_REG8(0x30de), 0x04 }, { CCI_REG8(0x30df), 0x20 }, + { CCI_REG8(0x3102), 0x08 }, { CCI_REG8(0x3103), 0x22 }, + { CCI_REG8(0x3104), 0x20 }, { CCI_REG8(0x3105), 0x00 }, + { CCI_REG8(0x3106), 0x87 }, { CCI_REG8(0x3107), 0x00 }, + { CCI_REG8(0x3108), 0x03 }, { CCI_REG8(0x3109), 0x02 }, + { CCI_REG8(0x310a), 0x03 }, { CCI_REG8(0x315c), 0x9c }, + { CCI_REG8(0x315d), 0x9b }, { CCI_REG8(0x316e), 0x9d }, + { CCI_REG8(0x316f), 0x9c }, { CCI_REG8(0x3318), 0x7a }, + { CCI_REG8(0x3348), 0xe0 }, +}; + +static const struct cci_reg_sequence mode_1308x736[] = { + { IMX111_GROUP_WRITE, 1 }, + { IMX111_HORIZONTAL_START, 0x0154 }, { IMX111_VERTICAL_START, 0x0220 }, + { IMX111_HORIZONTAL_END, 0x0b8b }, { IMX111_VERTICAL_END, 0x07df }, + { IMX111_IMAGE_WIDTH, 0x051c }, { IMX111_IMAGE_HEIGHT, 0x02e0 }, + { IMX111_GROUP_WRITE, 0 }, + { IMX111_H_EVEN_INC, 0x01 }, { IMX111_H_ODD_INC, 0x01 }, + { IMX111_W_EVEN_INC, 0x01 }, { IMX111_W_ODD_INC, 0x03 }, + { CCI_REG8(0x3033), 0x84 }, { CCI_REG8(0x303d), 0x10 }, + { CCI_REG8(0x303e), 0x40 }, { CCI_REG8(0x3040), 0x08 }, + { CCI_REG8(0x3041), 0x97 }, { CCI_REG8(0x3048), 0x01 }, + { CCI_REG8(0x304c), 0xd7 }, { CCI_REG8(0x304d), 0x01 }, + { CCI_REG8(0x3064), 0x12 }, { CCI_REG8(0x3073), 0x00 }, + { CCI_REG8(0x3074), 0x11 }, { CCI_REG8(0x3075), 0x11 }, + { CCI_REG8(0x3076), 0x11 }, { CCI_REG8(0x3077), 0x11 }, + { CCI_REG8(0x3079), 0x00 }, { CCI_REG8(0x307a), 0x00 }, + { CCI_REG8(0x309b), 0x48 }, { CCI_REG8(0x309c), 0x12 }, + { CCI_REG8(0x309e), 0x04 }, { CCI_REG8(0x30a0), 0x14 }, + { CCI_REG8(0x30a1), 0x0a }, { CCI_REG8(0x30aa), 0x01 }, + { CCI_REG8(0x30b2), 0x05 }, { CCI_REG8(0x30d5), 0x04 }, + { CCI_REG8(0x30d6), 0x85 }, { CCI_REG8(0x30d7), 0x2a }, + { CCI_REG8(0x30d8), 0x64 }, { CCI_REG8(0x30d9), 0x89 }, + { CCI_REG8(0x30de), 0x00 }, { CCI_REG8(0x30df), 0x20 }, + { CCI_REG8(0x3102), 0x08 }, { CCI_REG8(0x3103), 0x22 }, + { CCI_REG8(0x3104), 0x20 }, { CCI_REG8(0x3105), 0x00 }, + { CCI_REG8(0x3106), 0x87 }, { CCI_REG8(0x3107), 0x00 }, + { CCI_REG8(0x3108), 0x03 }, { CCI_REG8(0x3109), 0x02 }, + { CCI_REG8(0x310a), 0x03 }, { CCI_REG8(0x315c), 0x42 }, + { CCI_REG8(0x315d), 0x41 }, { CCI_REG8(0x316e), 0x43 }, + { CCI_REG8(0x316f), 0x42 }, { CCI_REG8(0x3318), 0x62 }, + { CCI_REG8(0x3348), 0xe0 }, +}; + +static const struct cci_reg_sequence mode_1640x924[] = { + { IMX111_GROUP_WRITE, 1 }, + { IMX111_HORIZONTAL_START, 0x0008 }, { IMX111_VERTICAL_START, 0x0164 }, + { IMX111_HORIZONTAL_END, 0x0cd7 }, { IMX111_VERTICAL_END, 0x089b }, + { IMX111_IMAGE_WIDTH, 0x0668 }, { IMX111_IMAGE_HEIGHT, 0x039c }, + { IMX111_GROUP_WRITE, 0 }, + { IMX111_H_EVEN_INC, 0x01 }, { IMX111_H_ODD_INC, 0x03 }, + { IMX111_W_EVEN_INC, 0x01 }, { IMX111_W_ODD_INC, 0x03 }, + { CCI_REG8(0x3033), 0x00 }, { CCI_REG8(0x303d), 0x10 }, + { CCI_REG8(0x303e), 0x40 }, { CCI_REG8(0x3040), 0x08 }, + { CCI_REG8(0x3041), 0x97 }, { CCI_REG8(0x3048), 0x01 }, + { CCI_REG8(0x304c), 0x6f }, { CCI_REG8(0x304d), 0x03 }, + { CCI_REG8(0x3064), 0x12 }, { CCI_REG8(0x3073), 0x00 }, + { CCI_REG8(0x3074), 0x11 }, { CCI_REG8(0x3075), 0x11 }, + { CCI_REG8(0x3076), 0x11 }, { CCI_REG8(0x3077), 0x11 }, + { CCI_REG8(0x3079), 0x00 }, { CCI_REG8(0x307a), 0x00 }, + { CCI_REG8(0x309b), 0x28 }, { CCI_REG8(0x309c), 0x13 }, + { CCI_REG8(0x309e), 0x00 }, { CCI_REG8(0x30a0), 0x14 }, + { CCI_REG8(0x30a1), 0x09 }, { CCI_REG8(0x30aa), 0x03 }, + { CCI_REG8(0x30b2), 0x05 }, { CCI_REG8(0x30d5), 0x09 }, + { CCI_REG8(0x30d6), 0x01 }, { CCI_REG8(0x30d7), 0x01 }, + { CCI_REG8(0x30d8), 0x64 }, { CCI_REG8(0x30d9), 0x89 }, + { CCI_REG8(0x30de), 0x02 }, { CCI_REG8(0x30df), 0x20 }, + { CCI_REG8(0x3102), 0x08 }, { CCI_REG8(0x3103), 0x22 }, + { CCI_REG8(0x3104), 0x20 }, { CCI_REG8(0x3105), 0x00 }, + { CCI_REG8(0x3106), 0x87 }, { CCI_REG8(0x3107), 0x00 }, + { CCI_REG8(0x3108), 0x03 }, { CCI_REG8(0x3109), 0x02 }, + { CCI_REG8(0x310a), 0x03 }, { CCI_REG8(0x315c), 0x9c }, + { CCI_REG8(0x315d), 0x9b }, { CCI_REG8(0x316e), 0x9d }, + { CCI_REG8(0x316f), 0x9c }, { CCI_REG8(0x3318), 0x72 }, + { CCI_REG8(0x3348), 0xe0 }, +}; + +static const struct cci_reg_sequence mode_1640x1232[] = { + { IMX111_GROUP_WRITE, 1 }, + { IMX111_HORIZONTAL_START, 0x0008 }, { IMX111_VERTICAL_START, 0x0030 }, + { IMX111_HORIZONTAL_END, 0x0cd7 }, { IMX111_VERTICAL_END, 0x09cf }, + { IMX111_IMAGE_WIDTH, 0x0668 }, { IMX111_IMAGE_HEIGHT, 0x04d0 }, + { IMX111_GROUP_WRITE, 0 }, + { IMX111_H_EVEN_INC, 0x01 }, { IMX111_H_ODD_INC, 0x03 }, + { IMX111_W_EVEN_INC, 0x01 }, { IMX111_W_ODD_INC, 0x03 }, + { CCI_REG8(0x3033), 0x00 }, { CCI_REG8(0x303d), 0x10 }, + { CCI_REG8(0x303e), 0x40 }, { CCI_REG8(0x3040), 0x08 }, + { CCI_REG8(0x3041), 0x97 }, { CCI_REG8(0x3048), 0x01 }, + { CCI_REG8(0x304c), 0x6f }, { CCI_REG8(0x304d), 0x03 }, + { CCI_REG8(0x3064), 0x12 }, { CCI_REG8(0x3073), 0x00 }, + { CCI_REG8(0x3074), 0x11 }, { CCI_REG8(0x3075), 0x11 }, + { CCI_REG8(0x3076), 0x11 }, { CCI_REG8(0x3077), 0x11 }, + { CCI_REG8(0x3079), 0x00 }, { CCI_REG8(0x307a), 0x00 }, + { CCI_REG8(0x309b), 0x28 }, { CCI_REG8(0x309c), 0x13 }, + { CCI_REG8(0x309e), 0x00 }, { CCI_REG8(0x30a0), 0x14 }, + { CCI_REG8(0x30a1), 0x09 }, { CCI_REG8(0x30aa), 0x03 }, + { CCI_REG8(0x30b2), 0x05 }, { CCI_REG8(0x30d5), 0x09 }, + { CCI_REG8(0x30d6), 0x01 }, { CCI_REG8(0x30d7), 0x01 }, + { CCI_REG8(0x30d8), 0x64 }, { CCI_REG8(0x30d9), 0x89 }, + { CCI_REG8(0x30de), 0x02 }, { CCI_REG8(0x30df), 0x20 }, + { CCI_REG8(0x3102), 0x08 }, { CCI_REG8(0x3103), 0x22 }, + { CCI_REG8(0x3104), 0x20 }, { CCI_REG8(0x3105), 0x00 }, + { CCI_REG8(0x3106), 0x87 }, { CCI_REG8(0x3107), 0x00 }, + { CCI_REG8(0x3108), 0x03 }, { CCI_REG8(0x3109), 0x02 }, + { CCI_REG8(0x310a), 0x03 }, { CCI_REG8(0x315c), 0x9c }, + { CCI_REG8(0x315d), 0x9b }, { CCI_REG8(0x316e), 0x9d }, + { CCI_REG8(0x316f), 0x9c }, { CCI_REG8(0x3318), 0x72 }, + { CCI_REG8(0x3348), 0xe0 }, +}; + +static const struct cci_reg_sequence mode_1952x1098[] = { + { IMX111_GROUP_WRITE, 1 }, + { IMX111_HORIZONTAL_START, 0x0016 }, { IMX111_VERTICAL_START, 0x016e }, + { IMX111_HORIZONTAL_END, 0x0ccb }, { IMX111_VERTICAL_END, 0x0893 }, + { IMX111_IMAGE_WIDTH, 0x07a0 }, { IMX111_IMAGE_HEIGHT, 0x044a }, + { IMX111_GROUP_WRITE, 0 }, + { IMX111_H_EVEN_INC, 0x01 }, { IMX111_H_ODD_INC, 0x01 }, + { IMX111_W_EVEN_INC, 0x01 }, { IMX111_W_ODD_INC, 0x01 }, + { CCI_REG8(0x3033), 0x00 }, { CCI_REG8(0x303d), 0x10 }, + { CCI_REG8(0x303e), 0x00 }, { CCI_REG8(0x3040), 0x08 }, + { CCI_REG8(0x3041), 0x91 }, { CCI_REG8(0x3048), 0x00 }, + { CCI_REG8(0x304c), 0x67 }, { CCI_REG8(0x304d), 0x03 }, + { CCI_REG8(0x3064), 0x10 }, { CCI_REG8(0x3073), 0xa0 }, + { CCI_REG8(0x3074), 0x12 }, { CCI_REG8(0x3075), 0x12 }, + { CCI_REG8(0x3076), 0x12 }, { CCI_REG8(0x3077), 0x11 }, + { CCI_REG8(0x3079), 0x0a }, { CCI_REG8(0x307a), 0x0a }, + { CCI_REG8(0x309b), 0x60 }, { CCI_REG8(0x309e), 0x04 }, + { CCI_REG8(0x30a0), 0x15 }, { CCI_REG8(0x30a1), 0x08 }, + { CCI_REG8(0x30aa), 0x03 }, { CCI_REG8(0x30b2), 0x05 }, + { CCI_REG8(0x30d5), 0x20 }, { CCI_REG8(0x30d6), 0x85 }, + { CCI_REG8(0x30d7), 0x2a }, { CCI_REG8(0x30d8), 0x64 }, + { CCI_REG8(0x30d9), 0x89 }, { CCI_REG8(0x30de), 0x00 }, + { CCI_REG8(0x30df), 0x21 }, { CCI_REG8(0x3102), 0x08 }, + { CCI_REG8(0x3103), 0x1d }, { CCI_REG8(0x3104), 0x1e }, + { CCI_REG8(0x3105), 0x00 }, { CCI_REG8(0x3106), 0x74 }, + { CCI_REG8(0x3107), 0x00 }, { CCI_REG8(0x3108), 0x03 }, + { CCI_REG8(0x3109), 0x02 }, { CCI_REG8(0x310a), 0x03 }, + { CCI_REG8(0x315c), 0x37 }, { CCI_REG8(0x315d), 0x36 }, + { CCI_REG8(0x316e), 0x38 }, { CCI_REG8(0x316f), 0x37 }, + { CCI_REG8(0x3318), 0x63 }, { CCI_REG8(0x3348), 0xA0 }, +}; + +static const struct cci_reg_sequence mode_2100x1200[] = { + { IMX111_GROUP_WRITE, 1 }, + { IMX111_HORIZONTAL_START, 0x0256 }, { IMX111_VERTICAL_START, 0x02a8 }, + { IMX111_HORIZONTAL_END, 0x0a89 }, { IMX111_VERTICAL_END, 0x0757 }, + { IMX111_IMAGE_WIDTH, 0x0834 }, { IMX111_IMAGE_HEIGHT, 0x04b0 }, + { IMX111_GROUP_WRITE, 0 }, + { IMX111_H_EVEN_INC, 0x01 }, { IMX111_H_ODD_INC, 0x01 }, + { IMX111_W_EVEN_INC, 0x01 }, { IMX111_W_ODD_INC, 0x01 }, + { CCI_REG8(0x3033), 0x00 }, { CCI_REG8(0x303d), 0x10 }, + { CCI_REG8(0x303e), 0x40 }, { CCI_REG8(0x3040), 0x08 }, + { CCI_REG8(0x3041), 0x97 }, { CCI_REG8(0x3048), 0x00 }, + { CCI_REG8(0x304c), 0x6f }, { CCI_REG8(0x304d), 0x03 }, + { CCI_REG8(0x3064), 0x12 }, { CCI_REG8(0x3073), 0x00 }, + { CCI_REG8(0x3074), 0x11 }, { CCI_REG8(0x3075), 0x11 }, + { CCI_REG8(0x3076), 0x11 }, { CCI_REG8(0x3077), 0x11 }, + { CCI_REG8(0x3079), 0x00 }, { CCI_REG8(0x307a), 0x00 }, + { CCI_REG8(0x309b), 0x20 }, { CCI_REG8(0x309c), 0x13 }, + { CCI_REG8(0x309e), 0x00 }, { CCI_REG8(0x30a0), 0x14 }, + { CCI_REG8(0x30a1), 0x08 }, { CCI_REG8(0x30aa), 0x03 }, + { CCI_REG8(0x30b2), 0x07 }, { CCI_REG8(0x30d5), 0x00 }, + { CCI_REG8(0x30d6), 0x85 }, { CCI_REG8(0x30d7), 0x2a }, + { CCI_REG8(0x30d8), 0x64 }, { CCI_REG8(0x30d9), 0x89 }, + { CCI_REG8(0x30de), 0x00 }, { CCI_REG8(0x30df), 0x20 }, + { CCI_REG8(0x3102), 0x08 }, { CCI_REG8(0x3103), 0x22 }, + { CCI_REG8(0x3104), 0x20 }, { CCI_REG8(0x3105), 0x00 }, + { CCI_REG8(0x3106), 0x87 }, { CCI_REG8(0x3107), 0x00 }, + { CCI_REG8(0x3108), 0x03 }, { CCI_REG8(0x3109), 0x02 }, + { CCI_REG8(0x310a), 0x03 }, { CCI_REG8(0x315c), 0x9c }, + { CCI_REG8(0x315d), 0x9b }, { CCI_REG8(0x316e), 0x9d }, + { CCI_REG8(0x316f), 0x9c }, { CCI_REG8(0x3318), 0x62 }, + { CCI_REG8(0x3348), 0xe0 }, +}; + +static const struct cci_reg_sequence mode_3280x1098[] = { + { IMX111_GROUP_WRITE, 1 }, + { IMX111_HORIZONTAL_START, 0x0008 }, { IMX111_VERTICAL_START, 0x01f6 }, + { IMX111_HORIZONTAL_END, 0x0cd7 }, { IMX111_VERTICAL_END, 0x080b }, + { IMX111_IMAGE_WIDTH, 0x0cd0 }, { IMX111_IMAGE_HEIGHT, 0x044a }, + { IMX111_GROUP_WRITE, 0 }, + { IMX111_H_EVEN_INC, 0x01 }, { IMX111_H_ODD_INC, 0x01 }, + { IMX111_W_EVEN_INC, 0x01 }, { IMX111_W_ODD_INC, 0x01 }, + { CCI_REG8(0x3033), 0x00 }, { CCI_REG8(0x303d), 0x10 }, + { CCI_REG8(0x303e), 0x40 }, { CCI_REG8(0x3040), 0x08 }, + { CCI_REG8(0x3041), 0x93 }, { CCI_REG8(0x3048), 0x00 }, + { CCI_REG8(0x304c), 0x67 }, { CCI_REG8(0x304d), 0x03 }, + { CCI_REG8(0x3064), 0x12 }, { CCI_REG8(0x3073), 0xe0 }, + { CCI_REG8(0x3074), 0x12 }, { CCI_REG8(0x3075), 0x12 }, + { CCI_REG8(0x3076), 0x12 }, { CCI_REG8(0x3077), 0x12 }, + { CCI_REG8(0x3079), 0x2a }, { CCI_REG8(0x307a), 0x0a }, + { CCI_REG8(0x309b), 0x60 }, { CCI_REG8(0x309e), 0x04 }, + { CCI_REG8(0x30a0), 0x15 }, { CCI_REG8(0x30a1), 0x08 }, + { CCI_REG8(0x30aa), 0x03 }, { CCI_REG8(0x30b2), 0x05 }, + { CCI_REG8(0x30d5), 0x00 }, { CCI_REG8(0x30d6), 0x85 }, + { CCI_REG8(0x30d7), 0x2a }, { CCI_REG8(0x30d8), 0x64 }, + { CCI_REG8(0x30d9), 0x89 }, { CCI_REG8(0x30de), 0x00 }, + { CCI_REG8(0x30df), 0x20 }, { CCI_REG8(0x3102), 0x08 }, + { CCI_REG8(0x3103), 0x1d }, { CCI_REG8(0x3104), 0x1e }, + { CCI_REG8(0x3105), 0x00 }, { CCI_REG8(0x3106), 0x74 }, + { CCI_REG8(0x3107), 0x00 }, { CCI_REG8(0x3108), 0x03 }, + { CCI_REG8(0x3109), 0x02 }, { CCI_REG8(0x310a), 0x03 }, + { CCI_REG8(0x315c), 0x37 }, { CCI_REG8(0x315d), 0x36 }, + { CCI_REG8(0x316e), 0x38 }, { CCI_REG8(0x316f), 0x37 }, + { CCI_REG8(0x3318), 0x63 }, { CCI_REG8(0x3348), 0xe0 }, +}; + +static const struct cci_reg_sequence mode_3280x1848[] = { + { IMX111_GROUP_WRITE, 1 }, + { IMX111_HORIZONTAL_START, 0x0008 }, { IMX111_VERTICAL_START, 0x0164 }, + { IMX111_HORIZONTAL_END, 0x0cd7 }, { IMX111_VERTICAL_END, 0x089b }, + { IMX111_IMAGE_WIDTH, 0x0cd0 }, { IMX111_IMAGE_HEIGHT, 0x0738 }, + { IMX111_GROUP_WRITE, 0 }, + { IMX111_H_EVEN_INC, 0x01 }, { IMX111_H_ODD_INC, 0x01 }, + { IMX111_W_EVEN_INC, 0x01 }, { IMX111_W_ODD_INC, 0x01 }, + { CCI_REG8(0x3033), 0x00 }, { CCI_REG8(0x303d), 0x00 }, + { CCI_REG8(0x303e), 0x41 }, { CCI_REG8(0x3040), 0x08 }, + { CCI_REG8(0x3041), 0x97 }, { CCI_REG8(0x3048), 0x00 }, + { CCI_REG8(0x304c), 0x6f }, { CCI_REG8(0x304d), 0x03 }, + { CCI_REG8(0x3064), 0x12 }, { CCI_REG8(0x3073), 0x00 }, + { CCI_REG8(0x3074), 0x11 }, { CCI_REG8(0x3075), 0x11 }, + { CCI_REG8(0x3076), 0x11 }, { CCI_REG8(0x3077), 0x11 }, + { CCI_REG8(0x3079), 0x00 }, { CCI_REG8(0x307a), 0x00 }, + { CCI_REG8(0x309b), 0x20 }, { CCI_REG8(0x309c), 0x13 }, + { CCI_REG8(0x309e), 0x00 }, { CCI_REG8(0x30a0), 0x14 }, + { CCI_REG8(0x30a1), 0x08 }, { CCI_REG8(0x30aa), 0x03 }, + { CCI_REG8(0x30b2), 0x07 }, { CCI_REG8(0x30d5), 0x00 }, + { CCI_REG8(0x30d6), 0x85 }, { CCI_REG8(0x30d7), 0x2a }, + { CCI_REG8(0x30d8), 0x64 }, { CCI_REG8(0x30d9), 0x89 }, + { CCI_REG8(0x30de), 0x00 }, { CCI_REG8(0x30df), 0x20 }, + { CCI_REG8(0x3102), 0x10 }, { CCI_REG8(0x3103), 0x44 }, + { CCI_REG8(0x3104), 0x40 }, { CCI_REG8(0x3105), 0x00 }, + { CCI_REG8(0x3106), 0x0d }, { CCI_REG8(0x3107), 0x01 }, + { CCI_REG8(0x3108), 0x09 }, { CCI_REG8(0x3109), 0x08 }, + { CCI_REG8(0x310a), 0x0f }, { CCI_REG8(0x315c), 0x5d }, + { CCI_REG8(0x315d), 0x5c }, { CCI_REG8(0x316e), 0x5e }, + { CCI_REG8(0x316f), 0x5d }, { CCI_REG8(0x3318), 0x60 }, + { CCI_REG8(0x3348), 0xe0 }, +}; + +static const struct cci_reg_sequence mode_3280x2464[] = { + { IMX111_GROUP_WRITE, 1 }, + { IMX111_HORIZONTAL_START, 0x0008 }, { IMX111_VERTICAL_START, 0x0030 }, + { IMX111_HORIZONTAL_END, 0x0cd7 }, { IMX111_VERTICAL_END, 0x09cf }, + { IMX111_IMAGE_WIDTH, 0x0cd0 }, { IMX111_IMAGE_HEIGHT, 0x09a0 }, + { IMX111_GROUP_WRITE, 0 }, + { IMX111_H_EVEN_INC, 0x01 }, { IMX111_H_ODD_INC, 0x01 }, + { IMX111_W_EVEN_INC, 0x01 }, { IMX111_W_ODD_INC, 0x01 }, + { CCI_REG8(0x3033), 0x00 }, { CCI_REG8(0x303d), 0x00 }, + { CCI_REG8(0x303e), 0x41 }, { CCI_REG8(0x3040), 0x08 }, + { CCI_REG8(0x3041), 0x97 }, { CCI_REG8(0x3048), 0x00 }, + { CCI_REG8(0x304c), 0x6f }, { CCI_REG8(0x304d), 0x03 }, + { CCI_REG8(0x3064), 0x12 }, { CCI_REG8(0x3073), 0x00 }, + { CCI_REG8(0x3074), 0x11 }, { CCI_REG8(0x3075), 0x11 }, + { CCI_REG8(0x3076), 0x11 }, { CCI_REG8(0x3077), 0x11 }, + { CCI_REG8(0x3079), 0x00 }, { CCI_REG8(0x307a), 0x00 }, + { CCI_REG8(0x309b), 0x20 }, { CCI_REG8(0x309c), 0x13 }, + { CCI_REG8(0x309e), 0x00 }, { CCI_REG8(0x30a0), 0x14 }, + { CCI_REG8(0x30a1), 0x08 }, { CCI_REG8(0x30aa), 0x03 }, + { CCI_REG8(0x30b2), 0x07 }, { CCI_REG8(0x30d5), 0x00 }, + { CCI_REG8(0x30d6), 0x85 }, { CCI_REG8(0x30d7), 0x2a }, + { CCI_REG8(0x30d8), 0x64 }, { CCI_REG8(0x30d9), 0x89 }, + { CCI_REG8(0x30de), 0x00 }, { CCI_REG8(0x30df), 0x20 }, + { CCI_REG8(0x3102), 0x10 }, { CCI_REG8(0x3103), 0x44 }, + { CCI_REG8(0x3104), 0x40 }, { CCI_REG8(0x3105), 0x00 }, + { CCI_REG8(0x3106), 0x0d }, { CCI_REG8(0x3107), 0x01 }, + { CCI_REG8(0x3108), 0x09 }, { CCI_REG8(0x3109), 0x08 }, + { CCI_REG8(0x310a), 0x0f }, { CCI_REG8(0x315c), 0x5d }, + { CCI_REG8(0x315d), 0x5c }, { CCI_REG8(0x316e), 0x5e }, + { CCI_REG8(0x316f), 0x5d }, { CCI_REG8(0x3318), 0x60 }, + { CCI_REG8(0x3348), 0xe0 }, +}; + +static const struct imx111_mode imx111_modes[] = { + [IMX111_MODE_3280x2464] = { + .width = 3280, + .height = 2464, + .vtl_def = 2490, + .htl_def = 3536, + .reg_list = { + .regs = mode_3280x2464, + .num_of_regs = ARRAY_SIZE(mode_3280x2464), + }, + }, + [IMX111_MODE_3280x1848] = { + .width = 3280, + .height = 1848, + .vtl_def = 1874, + .htl_def = 3536, + .reg_list = { + .regs = mode_3280x1848, + .num_of_regs = ARRAY_SIZE(mode_3280x1848), + }, + }, + [IMX111_MODE_3280x1098] = { + .width = 3280, + .height = 1098, + .vtl_def = 1130, + .htl_def = 3500, + .reg_list = { + .regs = mode_3280x1098, + .num_of_regs = ARRAY_SIZE(mode_3280x1098), + }, + }, + [IMX111_MODE_2100x1200] = { + .width = 2100, + .height = 1200, + .vtl_def = 1260, + .htl_def = 3536, + .reg_list = { + .regs = mode_2100x1200, + .num_of_regs = ARRAY_SIZE(mode_2100x1200), + }, + }, + [IMX111_MODE_1952x1098] = { + .width = 1952, + .height = 1098, + .vtl_def = 1884, + .htl_def = 3500, + .reg_list = { + .regs = mode_1952x1098, + .num_of_regs = ARRAY_SIZE(mode_1952x1098), + }, + }, + [IMX111_MODE_1920x1080] = { + .width = 1920, + .height = 1080, + .vtl_def = 1884, + .htl_def = 3500, + .reg_list = { + .regs = mode_1952x1098, + .num_of_regs = ARRAY_SIZE(mode_1952x1098), + }, + }, + [IMX111_MODE_1640x1232] = { + .width = 1640, + .height = 1232, + .vtl_def = 1254, + .htl_def = 3536, + .reg_list = { + .regs = mode_1640x1232, + .num_of_regs = ARRAY_SIZE(mode_1640x1232), + }, + }, + [IMX111_MODE_1440x1080] = { + .width = 1440, + .height = 1080, + .vtl_def = 1254, + .htl_def = 3536, + .reg_list = { + .regs = mode_1640x1232, + .num_of_regs = ARRAY_SIZE(mode_1640x1232), + }, + }, + [IMX111_MODE_1640x924] = { + .width = 1640, + .height = 924, + .vtl_def = 946, + .htl_def = 3536, + .reg_list = { + .regs = mode_1640x924, + .num_of_regs = ARRAY_SIZE(mode_1640x924), + }, + }, + [IMX111_MODE_1308x736] = { + .width = 1308, + .height = 736, + .vtl_def = 2369, + .htl_def = 1896, + .reg_list = { + .regs = mode_1308x736, + .num_of_regs = ARRAY_SIZE(mode_1308x736), + }, + }, + [IMX111_MODE_1280x720] = { + .width = 1280, + .height = 720, + .vtl_def = 2369, + .htl_def = 1896, + .reg_list = { + .regs = mode_1308x736, + .num_of_regs = ARRAY_SIZE(mode_1308x736), + }, + }, + [IMX111_MODE_820x614] = { + .width = 820, + .height = 614, + .vtl_def = 1260, + .htl_def = 3536, + .reg_list = { + .regs = mode_820x614, + .num_of_regs = ARRAY_SIZE(mode_820x614), + }, + }, + [IMX111_MODE_640x480] = { + .width = 640, + .height = 480, + .vtl_def = 1260, + .htl_def = 3536, + .reg_list = { + .regs = mode_820x614, + .num_of_regs = ARRAY_SIZE(mode_820x614), + }, + }, +}; + +static inline struct imx111 *sd_to_imx111(struct v4l2_subdev *sd) +{ + return container_of_const(sd, struct imx111, sd); +} + +static inline struct imx111 *ctrl_to_imx111(struct v4l2_ctrl *ctrl) +{ + return container_of_const(ctrl->handler, struct imx111, hdl); +} + +static u8 to_settle_delay(u64 extclk_rate) +{ + u64 extclk_mhz = div_u64(extclk_rate, MEGA); + + return DIV_ROUND_UP(IMX111_PLL_SETTLING_TIME_DEFAULT * extclk_mhz - 63, + 64); +} + +static u32 imx111_get_format_code(struct imx111 *sensor, u32 code, bool test) +{ + u32 i; + + for (i = 0; i < ARRAY_SIZE(imx111_mbus_formats); i++) + if (imx111_mbus_formats[i] == code) + break; + + if (i >= ARRAY_SIZE(imx111_mbus_formats)) + i = 0; + + if (test) + return imx111_mbus_formats[i]; + + i = (i & ~3) | (sensor->vflip->val ? 2 : 0) | + (sensor->hflip->val ? 1 : 0); + + return imx111_mbus_formats[i]; +} + +static u32 imx111_get_format_bpp(const struct v4l2_mbus_framefmt *format) +{ + switch (format->code) { + case MEDIA_BUS_FMT_SRGGB8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SBGGR8_1X8: + return 8; + + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + default: + return 10; + } +} + +static int imx111_update_digital_gain(struct imx111 *sensor, u32 val) +{ + int ret = 0; + + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, + IMX111_GROUP_WRITE_ON, &ret); + + cci_write(sensor->regmap, IMX111_REG_DIG_GAIN_GREENR, val, &ret); + cci_write(sensor->regmap, IMX111_REG_DIG_GAIN_RED, val, &ret); + cci_write(sensor->regmap, IMX111_REG_DIG_GAIN_BLUE, val, &ret); + cci_write(sensor->regmap, IMX111_REG_DIG_GAIN_GREENB, val, &ret); + + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, 0, &ret); + + return ret; +} + +static int imx111_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx111 *sensor = ctrl_to_imx111(ctrl); + struct device *dev = regmap_get_device(sensor->regmap); + int ret = 0; + + if (ctrl->id == V4L2_CID_VBLANK) { + s64 max = sensor->cur_mode->height + ctrl->val - + IMX111_INTEGRATION_TIME_OFFSET; + + ret = __v4l2_ctrl_modify_range(sensor->exposure, + sensor->exposure->minimum, + max, sensor->exposure->step, + max); + if (ret) + return ret; + } + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (!pm_runtime_get_if_in_use(dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + cci_write(sensor->regmap, IMX111_REG_ANALOG_GAIN, ctrl->val, + &ret); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = imx111_update_digital_gain(sensor, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, + IMX111_GROUP_WRITE_ON, &ret); + cci_write(sensor->regmap, IMX111_INTEGRATION_TIME, ctrl->val, + &ret); + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, 0, &ret); + break; + case V4L2_CID_HBLANK: + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, + IMX111_GROUP_WRITE_ON, &ret); + cci_write(sensor->regmap, IMX111_HORIZONTAL_TOTAL_LENGTH, + sensor->cur_mode->width + ctrl->val, &ret); + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, 0, &ret); + break; + case V4L2_CID_VBLANK: + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, + IMX111_GROUP_WRITE_ON, &ret); + cci_write(sensor->regmap, IMX111_VERTICAL_TOTAL_LENGTH, + sensor->cur_mode->height + ctrl->val, &ret); + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, 0, &ret); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + cci_write(sensor->regmap, IMX111_IMAGE_ORIENTATION, + sensor->hflip->val | sensor->vflip->val << 1, &ret); + break; + case V4L2_CID_TEST_PATTERN: + cci_write(sensor->regmap, IMX111_TEST_PATTERN, ctrl->val, + &ret); + break; + case V4L2_CID_TEST_PATTERN_RED: + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, + IMX111_GROUP_WRITE_ON, &ret); + cci_write(sensor->regmap, IMX111_SOLID_COLOR_RED, ctrl->val, + &ret); + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, 0, &ret); + break; + case V4L2_CID_TEST_PATTERN_GREENR: + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, + IMX111_GROUP_WRITE_ON, &ret); + cci_write(sensor->regmap, IMX111_SOLID_COLOR_GR, ctrl->val, + &ret); + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, 0, &ret); + break; + case V4L2_CID_TEST_PATTERN_BLUE: + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, + IMX111_GROUP_WRITE_ON, &ret); + cci_write(sensor->regmap, IMX111_SOLID_COLOR_BLUE, ctrl->val, + &ret); + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, 0, &ret); + break; + case V4L2_CID_TEST_PATTERN_GREENB: + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, + IMX111_GROUP_WRITE_ON, &ret); + cci_write(sensor->regmap, IMX111_SOLID_COLOR_GB, ctrl->val, + &ret); + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, 0, &ret); + break; + default: + ret = -EINVAL; + } + + pm_runtime_put(dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx111_ctrl_ops = { + .s_ctrl = imx111_set_ctrl, +}; + +static const char * const test_pattern_menu[] = { + "Disabled", + "Solid Color Fill", + "Standard Color Bars", + "Fade To Grey Color Bars", + "Pseudorandom data", +}; + +static int imx111_init_controls(struct imx111 *sensor) +{ + const struct v4l2_ctrl_ops *ops = &imx111_ctrl_ops; + struct device *dev = regmap_get_device(sensor->regmap); + const struct imx111_mode *mode = sensor->cur_mode; + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *hdl = &sensor->hdl; + s64 pixel_rate_min, pixel_rate_max; + int i, ret; + + ret = v4l2_fwnode_device_parse(dev, &props); + if (ret < 0) + return ret; + + v4l2_ctrl_handler_init(hdl, 15); + + pixel_rate_min = div_u64(sensor->pixel_clk_raw, + 2 * IMX111_DATA_DEPTH_RAW10); + pixel_rate_max = div_u64(sensor->pixel_clk_raw, + 2 * IMX111_DATA_DEPTH_RAW8); + sensor->pixel_rate = v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_PIXEL_RATE, + pixel_rate_min, pixel_rate_max, + 1, + div_u64(sensor->pixel_clk_raw, + 2 * + sensor->data_depth)); + + sensor->link_freq = v4l2_ctrl_new_int_menu(hdl, NULL, + V4L2_CID_LINK_FREQ, 0, 0, + &sensor->default_link_freq); + if (sensor->link_freq) + sensor->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, + IMX111_ANA_GAIN_MIN, IMX111_ANA_GAIN_MAX, + IMX111_ANA_GAIN_STEP, IMX111_ANA_GAIN_DEFAULT); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, + IMX111_DGTL_GAIN_MIN, IMX111_DGTL_GAIN_MAX, + IMX111_DGTL_GAIN_STEP, IMX111_DGTL_GAIN_DEFAULT); + + sensor->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, + 0); + if (sensor->hflip) + sensor->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + sensor->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, + 0); + if (sensor->vflip) + sensor->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + sensor->vblank = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, + IMX111_VBLANK_MIN, + IMX111_VTL_MAX - mode->height, 1, + mode->vtl_def - mode->height); + sensor->hblank = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, + IMX111_HBLANK_MIN, + IMX111_HTL_MAX - mode->width, 1, + mode->htl_def - mode->width); + + /* + * The maximum coarse integration time is the frame length in lines + * minus five. + */ + sensor->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, + IMX111_INTEGRATION_TIME_MIN, + IMX111_PIXEL_ARRAY_HEIGHT - + IMX111_INTEGRATION_TIME_OFFSET, + IMX111_INTEGRATION_TIME_STEP, + IMX111_PIXEL_ARRAY_HEIGHT - + IMX111_INTEGRATION_TIME_OFFSET); + + v4l2_ctrl_new_fwnode_properties(hdl, ops, &props); + + v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(test_pattern_menu) - 1, 0, 0, + test_pattern_menu); + for (i = 0; i < 4; i++) { + /* + * The assumption is that + * TEST_PATTERN_GREENR == TEST_PATTERN_RED + 1 + * TEST_PATTERN_BLUE == TEST_PATTERN_RED + 2 + * TEST_PATTERN_GREENB == TEST_PATTERN_RED + 3 + */ + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_TEST_PATTERN_RED + i, + IMX111_TESTP_COLOUR_MIN, + IMX111_TESTP_COLOUR_MAX, + IMX111_TESTP_COLOUR_STEP, + IMX111_TESTP_COLOUR_MAX); + /* The "Solid color" pattern is white by default */ + } + + if (hdl->error) + return hdl->error; + + sensor->sd.ctrl_handler = hdl; + + return 0; +}; + +static int imx111_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct imx111 *sensor = sd_to_imx111(sd); + struct device *dev = regmap_get_device(sensor->regmap); + const struct imx111_mode *mode = sensor->cur_mode; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + /* Apply default values of current mode */ + ret = cci_multi_reg_write(sensor->regmap, mode->reg_list.regs, + mode->reg_list.num_of_regs, NULL); + if (ret < 0) { + dev_err(dev, "Failed to initialize the sensor\n"); + goto err_rpm_put; + } + + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, + IMX111_GROUP_WRITE_ON, &ret); + cci_write(sensor->regmap, IMX111_DATA_DEPTH, + sensor->data_depth | sensor->data_depth << 8, &ret); + cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, + IMX111_GROUP_WRITE_ON, 0, &ret); + + if (ret) + goto err_rpm_put; + + ret = __v4l2_ctrl_handler_setup(&sensor->hdl); + if (ret) + goto err_rpm_put; + + ret = cci_write(sensor->regmap, IMX111_STREAMING_MODE, + IMX111_MODE_STREAMING, NULL); + if (ret) + dev_err(dev, "failed to start stream"); + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(sensor->vflip, true); + __v4l2_ctrl_grab(sensor->hflip, true); + + msleep(30); + + return 0; + +err_rpm_put: + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int imx111_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct imx111 *sensor = sd_to_imx111(sd); + struct device *dev = regmap_get_device(sensor->regmap); + int ret; + + ret = cci_write(sensor->regmap, IMX111_STREAMING_MODE, + IMX111_MODE_STANDBY, NULL); + if (ret) + dev_err(dev, "failed to stop stream\n"); + + __v4l2_ctrl_grab(sensor->vflip, false); + __v4l2_ctrl_grab(sensor->hflip, false); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int imx111_initialize(struct imx111 *sensor) +{ + struct device *dev = regmap_get_device(sensor->regmap); + int ret = 0; + + /* Configure the PLL. */ + cci_write(sensor->regmap, IMX111_PRE_PLL_CLK_DIVIDER_PLL1, + sensor->pll->pre_div, &ret); + cci_write(sensor->regmap, IMX111_PLL_MULTIPLIER_PLL1, + sensor->pll->mult, &ret); + cci_write(sensor->regmap, IMX111_POST_DIVIDER, + IMX111_POST_DIVIDER_DIV1, &ret); + cci_write(sensor->regmap, IMX111_PLL_SETTLING_TIME, + to_settle_delay(sensor->pll->extclk_rate), &ret); + + cci_multi_reg_write(sensor->regmap, imx111_global_init, + ARRAY_SIZE(imx111_global_init), &ret); + if (ret < 0) { + dev_err(dev, "Failed to initialize the sensor\n"); + return ret; + } + + return 0; +} + +/* ---------------------------------------------------------------------------- + * IMX111 Pad Subdev Init and Operations + */ +static int imx111_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx111 *sensor = sd_to_imx111(sd); + + if (code->index >= ARRAY_SIZE(imx111_mbus_formats) / 4) + return -EINVAL; + + code->code = imx111_get_format_code(sensor, + imx111_mbus_formats[code->index * + 4], false); + + return 0; +} + +static int imx111_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx111 *sensor = sd_to_imx111(sd); + u32 code; + + if (fse->index >= ARRAY_SIZE(imx111_modes)) + return -EINVAL; + + code = imx111_get_format_code(sensor, fse->code, true); + if (fse->code != code) + return -EINVAL; + + fse->min_width = imx111_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = imx111_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int imx111_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct imx111 *sensor = sd_to_imx111(sd); + struct v4l2_mbus_framefmt *mbus_fmt = &format->format; + struct v4l2_mbus_framefmt *fmt; + const struct imx111_mode *mode; + + mode = v4l2_find_nearest_size(imx111_modes, ARRAY_SIZE(imx111_modes), + width, height, + mbus_fmt->width, mbus_fmt->height); + + fmt = v4l2_subdev_state_get_format(state, format->pad); + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + int ret; + + sensor->cur_mode = mode; + sensor->data_depth = imx111_get_format_bpp(fmt); + + ret = __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate, + div_u64(sensor->pixel_clk_raw, + 2 * + sensor->data_depth)); + if (ret) + return ret; + + ret = __v4l2_ctrl_modify_range(sensor->vblank, + IMX111_VBLANK_MIN, + IMX111_VTL_MAX - mode->height, + 1, + mode->vtl_def - mode->height); + if (ret) + return ret; + + ret = __v4l2_ctrl_s_ctrl(sensor->vblank, mode->vtl_def - + mode->height); + if (ret) + return ret; + + ret = __v4l2_ctrl_modify_range(sensor->hblank, + IMX111_HBLANK_MIN, + IMX111_HTL_MAX - mode->width, + 1, + mode->htl_def - mode->width); + if (ret) + return ret; + + ret = __v4l2_ctrl_s_ctrl(sensor->hblank, mode->htl_def - + mode->width); + if (ret) + return ret; + } + + fmt->code = imx111_get_format_code(sensor, mbus_fmt->code, false); + fmt->width = mode->width; + fmt->height = mode->height; + fmt->colorspace = V4L2_COLORSPACE_RAW; + + *mbus_fmt = *fmt; + + return 0; +} + +static int imx111_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct imx111 *sensor = sd_to_imx111(sd); + const struct imx111_mode *mode = sensor->cur_mode; + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, 0); + + fmt->code = MEDIA_BUS_FMT_SGBRG10_1X10; + fmt->width = mode->width; + fmt->height = mode->height; + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_XFER_FUNC_NONE; + + return 0; +} + +static const struct v4l2_subdev_video_ops imx111_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops imx111_pad_ops = { + .enum_mbus_code = imx111_enum_mbus_code, + .enum_frame_size = imx111_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = imx111_set_format, + .enable_streams = imx111_enable_streams, + .disable_streams = imx111_disable_streams, +}; + +static const struct v4l2_subdev_ops imx111_subdev_ops = { + .video = &imx111_video_ops, + .pad = &imx111_pad_ops, +}; + +static const struct media_entity_operations imx111_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops imx111_internal_ops = { + .init_state = imx111_init_state, +}; + +static int imx111_init_subdev(struct imx111 *sensor, struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct v4l2_subdev *sd = &sensor->sd; + struct media_pad *pad = &sensor->pad; + struct v4l2_ctrl_handler *hdl = &sensor->hdl; + int ret; + + /* Initialize the subdev. */ + v4l2_i2c_subdev_init(sd, client, &imx111_subdev_ops); + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->internal_ops = &imx111_internal_ops; + + /* Initialize the media entity. */ + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + sd->entity.ops = &imx111_subdev_entity_ops; + pad->flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&sd->entity, 1, pad); + if (ret < 0) { + dev_err(dev, "failed to init entity pads: %d", ret); + return ret; + } + + /* Initialize the control handler. */ + ret = imx111_init_controls(sensor); + if (ret) + goto error; + + return 0; +error: + v4l2_ctrl_handler_free(hdl); + media_entity_cleanup(&sd->entity); + return ret; +}; + +/* ---------------------------------------------------------------------------- + * Power Management + */ + +static int imx111_power_on(struct imx111 *sensor) +{ + int ret; + + if (sensor->reset) + gpiod_set_value(sensor->reset, 1); + + ret = regulator_bulk_enable(ARRAY_SIZE(imx111_supplies), + sensor->supplies); + if (ret < 0) + return ret; + + usleep_range(500, 600); + + if (sensor->reset) + gpiod_set_value(sensor->reset, 0); + + usleep_range(200, 250); + + ret = clk_prepare_enable(sensor->extclk); + if (ret < 0) + goto error_regulator; + + usleep_range(200, 250); + + return 0; + +error_regulator: + regulator_bulk_disable(ARRAY_SIZE(imx111_supplies), sensor->supplies); + return ret; +} + +static void imx111_power_off(struct imx111 *sensor) +{ + if (sensor->reset) + gpiod_set_value(sensor->reset, 1); + usleep_range(1000, 2000); + + clk_disable_unprepare(sensor->extclk); + regulator_bulk_disable(ARRAY_SIZE(imx111_supplies), sensor->supplies); +} + +static int __maybe_unused imx111_pm_runtime_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct imx111 *sensor = sd_to_imx111(sd); + int ret; + + ret = imx111_power_on(sensor); + if (ret) + return ret; + + ret = imx111_initialize(sensor); + if (ret) { + imx111_power_off(sensor); + return ret; + } + + return 0; +} + +static int __maybe_unused imx111_pm_runtime_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct imx111 *sensor = sd_to_imx111(sd); + + imx111_power_off(sensor); + + return 0; +} + +static const struct dev_pm_ops imx111_pm_ops = { + SET_RUNTIME_PM_OPS(imx111_pm_runtime_suspend, + imx111_pm_runtime_resume, NULL) +}; + +/* ---------------------------------------------------------------------------- + * Probe & Remove + */ + +static int imx111_identify_module(struct imx111 *sensor) +{ + struct device *dev = regmap_get_device(sensor->regmap); + u64 value, revision, manufacturer; + int ret = 0; + + ret = cci_read(sensor->regmap, IMX111_PRODUCT_ID, &value, NULL); + if (ret) + return ret; + + if (value != IMX111_CHIP_ID) { + dev_err(dev, "chip id mismatch: %x!=%04llx", IMX111_CHIP_ID, + value); + return -ENXIO; + } + + cci_read(sensor->regmap, IMX111_REVISION, &revision, &ret); + cci_read(sensor->regmap, IMX111_MANUFACTURER_ID, &manufacturer, &ret); + + dev_dbg(dev, "module IMX%03llx rev. %llu manufacturer %llu\n", + value, revision, manufacturer); + + return ret; +} + +static int imx111_clk_init(struct imx111 *sensor) +{ + struct device *dev = regmap_get_device(sensor->regmap); + u32 ndata_lanes = sensor->bus_cfg.bus.mipi_csi2.num_data_lanes; + u64 extclk_rate, system_clk; + unsigned int i; + + extclk_rate = clk_get_rate(sensor->extclk); + if (!extclk_rate) + return dev_err_probe(dev, -EINVAL, "EXTCLK rate unknown\n"); + + for (i = 0; i < ARRAY_SIZE(imx111_pll); i++) { + if (clk_get_rate(sensor->extclk) == + imx111_pll[i].extclk_rate) { + sensor->pll = &imx111_pll[i]; + break; + } + } + if (!sensor->pll) + return dev_err_probe(dev, -EINVAL, + "Unsupported EXTCLK rate %llu\n", + extclk_rate); + + system_clk = div_u64(extclk_rate, sensor->pll->pre_div) * + sensor->pll->mult; + + /* + * Pixel clock or Logic clock is used for internal image processing is + * generated by dividing into 1/10 or 1/8 frequency according to the + * word length of the CSI2 interface. This clock is designating the + * pixel rate and used as the base of integration time, frame rate etc. + */ + sensor->pixel_clk_raw = system_clk * ndata_lanes; + + /* + * The CSI-2 bus is clocked for 16-bit per pixel, transmitted in DDR + * over n lanes for RAW10 default format. + */ + sensor->default_link_freq = div_u64(sensor->pixel_clk_raw * 8, + 2 * IMX111_DATA_DEPTH_RAW10); + + if (sensor->bus_cfg.nr_of_link_frequencies != 1 || + sensor->bus_cfg.link_frequencies[0] != sensor->default_link_freq) + return dev_err_probe(dev, -EINVAL, + "Invalid link-frequency, expected %llu\n", + sensor->default_link_freq); + + return 0; +} + +static int imx111_parse_dt(struct imx111 *sensor) +{ + struct device *dev = regmap_get_device(sensor->regmap); + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct fwnode_handle *ep; + int ret; + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) { + dev_err(dev, "No endpoint found\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &sensor->bus_cfg); + fwnode_handle_put(ep); + if (ret < 0) { + dev_err(dev, "Failed to parse endpoint\n"); + goto error; + } + + sensor->bus_cfg.bus_type = V4L2_MBUS_CSI2_DPHY; + + /* Check the number of MIPI CSI2 data lanes */ + if (sensor->bus_cfg.bus.mipi_csi2.num_data_lanes > 2) { + dev_err(dev, "number of lanes is more than 2\n"); + ret = -EINVAL; + goto error; + } + + return 0; + +error: + v4l2_fwnode_endpoint_free(&sensor->bus_cfg); + return ret; +} + +static int imx111_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct imx111 *sensor; + int ret; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(sensor->regmap)) + return dev_err_probe(dev, PTR_ERR(sensor->regmap), + "Failed to allocate register map\n"); + + sensor->extclk = devm_v4l2_sensor_clk_get(dev, NULL); + if (IS_ERR(sensor->extclk)) + return dev_err_probe(dev, PTR_ERR(sensor->extclk), + "Failed to get clock\n"); + + sensor->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(sensor->reset)) + return dev_err_probe(dev, PTR_ERR(sensor->reset), + "Failed to get reset GPIO\n"); + + ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(imx111_supplies), + imx111_supplies, + &sensor->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ret = imx111_parse_dt(sensor); + if (ret < 0) + return ret; + + ret = imx111_clk_init(sensor); + if (ret < 0) + goto error_ep_free; + + ret = imx111_power_on(sensor); + if (ret < 0) { + dev_err_probe(dev, ret, "Could not power on the device\n"); + goto error_ep_free; + } + + ret = imx111_identify_module(sensor); + if (ret < 0) { + dev_err_probe(dev, ret, "Could not identify module\n"); + goto error_power_off; + } + + sensor->cur_mode = &imx111_modes[IMX111_MODE_3280x2464]; + sensor->data_depth = IMX111_DATA_DEPTH_RAW10; + + ret = imx111_initialize(sensor); + if (ret < 0) + goto error_power_off; + + ret = imx111_init_subdev(sensor, client); + if (ret < 0) { + dev_err(dev, "failed to init controls: %d", ret); + goto error_v4l2_ctrl_handler_free; + } + + ret = v4l2_subdev_init_finalize(&sensor->sd); + if (ret) + goto error_v4l2_ctrl_handler_free; + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + ret = v4l2_async_register_subdev_sensor(&sensor->sd); + if (ret < 0) { + dev_err(dev, "failed to register V4L2 subdev: %d", ret); + goto error_pm; + } + + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + pm_runtime_idle(dev); + + return 0; + +error_pm: + v4l2_subdev_cleanup(&sensor->sd); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + +error_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(&sensor->hdl); + media_entity_cleanup(&sensor->sd.entity); + +error_power_off: + imx111_power_off(sensor); + +error_ep_free: + v4l2_fwnode_endpoint_free(&sensor->bus_cfg); + + return ret; +} + +static void imx111_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx111 *sensor = sd_to_imx111(sd); + + v4l2_async_unregister_subdev(&sensor->sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sensor->sd.entity); + v4l2_ctrl_handler_free(&sensor->hdl); + v4l2_fwnode_endpoint_free(&sensor->bus_cfg); + + /* + * Disable runtime PM. In case runtime PM is disabled in the kernel, + * make sure to turn power off manually. + */ + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + imx111_power_off(sensor); + pm_runtime_set_suspended(&client->dev); + } +} + +static const struct of_device_id imx111_of_match[] = { + { .compatible = "sony,imx111" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx111_of_match); + +static struct i2c_driver imx111_i2c_driver = { + .driver = { + .name = "imx111", + .of_match_table = imx111_of_match, + .pm = &imx111_pm_ops, + }, + .probe = imx111_probe, + .remove = imx111_remove, +}; +module_i2c_driver(imx111_i2c_driver); + +MODULE_AUTHOR("Svyatoslav Ryhel "); +MODULE_DESCRIPTION("Sony IMX111 CMOS Image Sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/imx208.c b/drivers/media/i2c/imx208.c new file mode 100644 index 000000000..d5350bb46 --- /dev/null +++ b/drivers/media/i2c/imx208.c @@ -0,0 +1,1069 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2021 Intel Corporation + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define IMX208_REG_MODE_SELECT 0x0100 +#define IMX208_MODE_STANDBY 0x00 +#define IMX208_MODE_STREAMING 0x01 + +/* Chip ID */ +#define IMX208_REG_CHIP_ID 0x0000 +#define IMX208_CHIP_ID 0x0208 + +/* V_TIMING internal */ +#define IMX208_REG_VTS 0x0340 +#define IMX208_VTS_60FPS 0x0472 +#define IMX208_VTS_BINNING 0x0239 +#define IMX208_VTS_60FPS_MIN 0x0458 +#define IMX208_VTS_BINNING_MIN 0x0230 +#define IMX208_VTS_MAX 0xffff + +/* HBLANK control - read only */ +#define IMX208_PPL_384MHZ 2248 +#define IMX208_PPL_96MHZ 2248 + +/* Exposure control */ +#define IMX208_REG_EXPOSURE 0x0202 +#define IMX208_EXPOSURE_MIN 4 +#define IMX208_EXPOSURE_STEP 1 +#define IMX208_EXPOSURE_DEFAULT 0x190 +#define IMX208_EXPOSURE_MAX 65535 + +/* Analog gain control */ +#define IMX208_REG_ANALOG_GAIN 0x0204 +#define IMX208_ANA_GAIN_MIN 0 +#define IMX208_ANA_GAIN_MAX 0x00e0 +#define IMX208_ANA_GAIN_STEP 1 +#define IMX208_ANA_GAIN_DEFAULT 0x0 + +/* Digital gain control */ +#define IMX208_REG_GR_DIGITAL_GAIN 0x020e +#define IMX208_REG_R_DIGITAL_GAIN 0x0210 +#define IMX208_REG_B_DIGITAL_GAIN 0x0212 +#define IMX208_REG_GB_DIGITAL_GAIN 0x0214 +#define IMX208_DIGITAL_GAIN_SHIFT 8 + +/* Orientation */ +#define IMX208_REG_ORIENTATION_CONTROL 0x0101 + +/* Test Pattern Control */ +#define IMX208_REG_TEST_PATTERN_MODE 0x0600 +#define IMX208_TEST_PATTERN_DISABLE 0x0 +#define IMX208_TEST_PATTERN_SOLID_COLOR 0x1 +#define IMX208_TEST_PATTERN_COLOR_BARS 0x2 +#define IMX208_TEST_PATTERN_GREY_COLOR 0x3 +#define IMX208_TEST_PATTERN_PN9 0x4 +#define IMX208_TEST_PATTERN_FIX_1 0x100 +#define IMX208_TEST_PATTERN_FIX_2 0x101 +#define IMX208_TEST_PATTERN_FIX_3 0x102 +#define IMX208_TEST_PATTERN_FIX_4 0x103 +#define IMX208_TEST_PATTERN_FIX_5 0x104 +#define IMX208_TEST_PATTERN_FIX_6 0x105 + +/* OTP Access */ +#define IMX208_OTP_BASE 0x3500 +#define IMX208_OTP_SIZE 40 + +struct imx208_reg { + u16 address; + u8 val; +}; + +struct imx208_reg_list { + u32 num_of_regs; + const struct imx208_reg *regs; +}; + +/* Link frequency config */ +struct imx208_link_freq_config { + u32 pixels_per_line; + + /* PLL registers for this link frequency */ + struct imx208_reg_list reg_list; +}; + +/* Mode : resolution and related config&values */ +struct imx208_mode { + /* Frame width */ + u32 width; + /* Frame height */ + u32 height; + + /* V-timing */ + u32 vts_def; + u32 vts_min; + + /* Index of Link frequency config to be used */ + u32 link_freq_index; + /* Default register values */ + struct imx208_reg_list reg_list; +}; + +static const struct imx208_reg pll_ctrl_reg[] = { + {0x0305, 0x02}, + {0x0307, 0x50}, + {0x303C, 0x3C}, +}; + +static const struct imx208_reg mode_1936x1096_60fps_regs[] = { + {0x0340, 0x04}, + {0x0341, 0x72}, + {0x0342, 0x04}, + {0x0343, 0x64}, + {0x034C, 0x07}, + {0x034D, 0x90}, + {0x034E, 0x04}, + {0x034F, 0x48}, + {0x0381, 0x01}, + {0x0383, 0x01}, + {0x0385, 0x01}, + {0x0387, 0x01}, + {0x3048, 0x00}, + {0x3050, 0x01}, + {0x30D5, 0x00}, + {0x3301, 0x00}, + {0x3318, 0x62}, + {0x0202, 0x01}, + {0x0203, 0x90}, + {0x0205, 0x00}, +}; + +static const struct imx208_reg mode_968_548_60fps_regs[] = { + {0x0340, 0x02}, + {0x0341, 0x39}, + {0x0342, 0x08}, + {0x0343, 0xC8}, + {0x034C, 0x03}, + {0x034D, 0xC8}, + {0x034E, 0x02}, + {0x034F, 0x24}, + {0x0381, 0x01}, + {0x0383, 0x03}, + {0x0385, 0x01}, + {0x0387, 0x03}, + {0x3048, 0x01}, + {0x3050, 0x02}, + {0x30D5, 0x03}, + {0x3301, 0x10}, + {0x3318, 0x75}, + {0x0202, 0x01}, + {0x0203, 0x90}, + {0x0205, 0x00}, +}; + +static const s64 imx208_discrete_digital_gain[] = { + 1, 2, 4, 8, 16, +}; + +static const char * const imx208_test_pattern_menu[] = { + "Disabled", + "Solid Color", + "100% Color Bar", + "Fade to Grey Color Bar", + "PN9", + "Fixed Pattern1", + "Fixed Pattern2", + "Fixed Pattern3", + "Fixed Pattern4", + "Fixed Pattern5", + "Fixed Pattern6" +}; + +static const int imx208_test_pattern_val[] = { + IMX208_TEST_PATTERN_DISABLE, + IMX208_TEST_PATTERN_SOLID_COLOR, + IMX208_TEST_PATTERN_COLOR_BARS, + IMX208_TEST_PATTERN_GREY_COLOR, + IMX208_TEST_PATTERN_PN9, + IMX208_TEST_PATTERN_FIX_1, + IMX208_TEST_PATTERN_FIX_2, + IMX208_TEST_PATTERN_FIX_3, + IMX208_TEST_PATTERN_FIX_4, + IMX208_TEST_PATTERN_FIX_5, + IMX208_TEST_PATTERN_FIX_6, +}; + +/* Configurations for supported link frequencies */ +#define IMX208_MHZ (1000 * 1000ULL) +#define IMX208_LINK_FREQ_384MHZ (384ULL * IMX208_MHZ) +#define IMX208_LINK_FREQ_96MHZ (96ULL * IMX208_MHZ) + +#define IMX208_DATA_RATE_DOUBLE 2 +#define IMX208_NUM_OF_LANES 2 +#define IMX208_PIXEL_BITS 10 + +enum { + IMX208_LINK_FREQ_384MHZ_INDEX, + IMX208_LINK_FREQ_96MHZ_INDEX, +}; + +/* + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample + * data rate => double data rate; number of lanes => 2; bits per pixel => 10 + */ +static u64 link_freq_to_pixel_rate(u64 f) +{ + f *= IMX208_DATA_RATE_DOUBLE * IMX208_NUM_OF_LANES; + do_div(f, IMX208_PIXEL_BITS); + + return f; +} + +/* Menu items for LINK_FREQ V4L2 control */ +static const s64 link_freq_menu_items[] = { + [IMX208_LINK_FREQ_384MHZ_INDEX] = IMX208_LINK_FREQ_384MHZ, + [IMX208_LINK_FREQ_96MHZ_INDEX] = IMX208_LINK_FREQ_96MHZ, +}; + +/* Link frequency configs */ +static const struct imx208_link_freq_config link_freq_configs[] = { + [IMX208_LINK_FREQ_384MHZ_INDEX] = { + .pixels_per_line = IMX208_PPL_384MHZ, + .reg_list = { + .num_of_regs = ARRAY_SIZE(pll_ctrl_reg), + .regs = pll_ctrl_reg, + } + }, + [IMX208_LINK_FREQ_96MHZ_INDEX] = { + .pixels_per_line = IMX208_PPL_96MHZ, + .reg_list = { + .num_of_regs = ARRAY_SIZE(pll_ctrl_reg), + .regs = pll_ctrl_reg, + } + }, +}; + +/* Mode configs */ +static const struct imx208_mode supported_modes[] = { + { + .width = 1936, + .height = 1096, + .vts_def = IMX208_VTS_60FPS, + .vts_min = IMX208_VTS_60FPS_MIN, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1936x1096_60fps_regs), + .regs = mode_1936x1096_60fps_regs, + }, + .link_freq_index = IMX208_LINK_FREQ_384MHZ_INDEX, + }, + { + .width = 968, + .height = 548, + .vts_def = IMX208_VTS_BINNING, + .vts_min = IMX208_VTS_BINNING_MIN, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_968_548_60fps_regs), + .regs = mode_968_548_60fps_regs, + }, + .link_freq_index = IMX208_LINK_FREQ_96MHZ_INDEX, + }, +}; + +struct imx208 { + struct device *dev; + struct clk *clk; + + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + + /* Current mode */ + const struct imx208_mode *cur_mode; + + /* + * Mutex for serialized access: + * Protect sensor set pad format and start/stop streaming safely. + * Protect access to sensor v4l2 controls. + */ + struct mutex imx208_mx; + + /* OTP data */ + bool otp_read; + char otp_data[IMX208_OTP_SIZE]; + + /* True if the device has been identified */ + bool identified; +}; + +static inline struct imx208 *to_imx208(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx208, sd); +} + +/* Get bayer order based on flip setting. */ +static u32 imx208_get_format_code(struct imx208 *imx208) +{ + /* + * Only one bayer order is supported. + * It depends on the flip settings. + */ + static const u32 codes[2][2] = { + { MEDIA_BUS_FMT_SRGGB10_1X10, MEDIA_BUS_FMT_SGRBG10_1X10, }, + { MEDIA_BUS_FMT_SGBRG10_1X10, MEDIA_BUS_FMT_SBGGR10_1X10, }, + }; + + return codes[imx208->vflip->val][imx208->hflip->val]; +} + +/* Read registers up to 4 at a time */ +static int imx208_read_reg(struct imx208 *imx208, u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx208->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2] = { reg >> 8, reg & 0xff }; + u8 data_buf[4] = { 0, }; + int ret; + + if (len > 4) + return -EINVAL; + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +/* Write registers up to 4 at a time */ +static int imx208_write_reg(struct imx208 *imx208, u16 reg, u32 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx208->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << (8 * (4 - len)), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int imx208_write_regs(struct imx208 *imx208, + const struct imx208_reg *regs, u32 len) +{ + unsigned int i; + int ret; + + for (i = 0; i < len; i++) { + ret = imx208_write_reg(imx208, regs[i].address, 1, + regs[i].val); + if (ret) { + dev_err_ratelimited(imx208->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +/* Open sub-device */ +static int imx208_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_state_get_format(fh->state, 0); + + /* Initialize try_fmt */ + try_fmt->width = supported_modes[0].width; + try_fmt->height = supported_modes[0].height; + try_fmt->code = MEDIA_BUS_FMT_SRGGB10_1X10; + try_fmt->field = V4L2_FIELD_NONE; + + return 0; +} + +static int imx208_update_digital_gain(struct imx208 *imx208, u32 len, u32 val) +{ + int ret; + + val = imx208_discrete_digital_gain[val] << IMX208_DIGITAL_GAIN_SHIFT; + + ret = imx208_write_reg(imx208, IMX208_REG_GR_DIGITAL_GAIN, 2, val); + if (ret) + return ret; + + ret = imx208_write_reg(imx208, IMX208_REG_GB_DIGITAL_GAIN, 2, val); + if (ret) + return ret; + + ret = imx208_write_reg(imx208, IMX208_REG_R_DIGITAL_GAIN, 2, val); + if (ret) + return ret; + + return imx208_write_reg(imx208, IMX208_REG_B_DIGITAL_GAIN, 2, val); +} + +static int imx208_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx208 *imx208 = + container_of(ctrl->handler, struct imx208, ctrl_handler); + int ret; + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (!pm_runtime_get_if_in_use(imx208->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = imx208_write_reg(imx208, IMX208_REG_ANALOG_GAIN, + 2, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = imx208_write_reg(imx208, IMX208_REG_EXPOSURE, + 2, ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = imx208_update_digital_gain(imx208, 2, ctrl->val); + break; + case V4L2_CID_VBLANK: + /* Update VTS that meets expected vertical blanking */ + ret = imx208_write_reg(imx208, IMX208_REG_VTS, 2, + imx208->cur_mode->height + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = imx208_write_reg(imx208, IMX208_REG_TEST_PATTERN_MODE, + 2, imx208_test_pattern_val[ctrl->val]); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = imx208_write_reg(imx208, IMX208_REG_ORIENTATION_CONTROL, + 1, + imx208->hflip->val | + imx208->vflip->val << 1); + break; + default: + ret = -EINVAL; + dev_err(imx208->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(imx208->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx208_ctrl_ops = { + .s_ctrl = imx208_set_ctrl, +}; + +static const struct v4l2_ctrl_config imx208_digital_gain_control = { + .ops = &imx208_ctrl_ops, + .id = V4L2_CID_DIGITAL_GAIN, + .name = "Digital Gain", + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .min = 0, + .max = ARRAY_SIZE(imx208_discrete_digital_gain) - 1, + .step = 0, + .def = 0, + .menu_skip_mask = 0, + .qmenu_int = imx208_discrete_digital_gain, +}; + +static int imx208_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx208 *imx208 = to_imx208(sd); + + if (code->index > 0) + return -EINVAL; + + code->code = imx208_get_format_code(imx208); + + return 0; +} + +static int imx208_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx208 *imx208 = to_imx208(sd); + + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != imx208_get_format_code(imx208)) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static void imx208_mode_to_pad_format(struct imx208 *imx208, + const struct imx208_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = imx208_get_format_code(imx208); + fmt->format.field = V4L2_FIELD_NONE; +} + +static int __imx208_get_pad_format(struct imx208 *imx208, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_state_get_format(sd_state, + fmt->pad); + else + imx208_mode_to_pad_format(imx208, imx208->cur_mode, fmt); + + return 0; +} + +static int imx208_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx208 *imx208 = to_imx208(sd); + int ret; + + mutex_lock(&imx208->imx208_mx); + ret = __imx208_get_pad_format(imx208, sd_state, fmt); + mutex_unlock(&imx208->imx208_mx); + + return ret; +} + +static int imx208_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx208 *imx208 = to_imx208(sd); + const struct imx208_mode *mode; + s32 vblank_def; + s32 vblank_min; + s64 h_blank; + s64 pixel_rate; + s64 link_freq; + + mutex_lock(&imx208->imx208_mx); + + fmt->format.code = imx208_get_format_code(imx208); + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), width, height, + fmt->format.width, fmt->format.height); + imx208_mode_to_pad_format(imx208, mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; + } else { + imx208->cur_mode = mode; + __v4l2_ctrl_s_ctrl(imx208->link_freq, mode->link_freq_index); + link_freq = link_freq_menu_items[mode->link_freq_index]; + pixel_rate = link_freq_to_pixel_rate(link_freq); + __v4l2_ctrl_s_ctrl_int64(imx208->pixel_rate, pixel_rate); + /* Update limits and set FPS to default */ + vblank_def = imx208->cur_mode->vts_def - + imx208->cur_mode->height; + vblank_min = imx208->cur_mode->vts_min - + imx208->cur_mode->height; + __v4l2_ctrl_modify_range(imx208->vblank, vblank_min, + IMX208_VTS_MAX - imx208->cur_mode->height, + 1, vblank_def); + __v4l2_ctrl_s_ctrl(imx208->vblank, vblank_def); + h_blank = + link_freq_configs[mode->link_freq_index].pixels_per_line + - imx208->cur_mode->width; + __v4l2_ctrl_modify_range(imx208->hblank, h_blank, + h_blank, 1, h_blank); + } + + mutex_unlock(&imx208->imx208_mx); + + return 0; +} + +static int imx208_identify_module(struct imx208 *imx208) +{ + int ret; + u32 val; + + if (imx208->identified) + return 0; + + ret = imx208_read_reg(imx208, IMX208_REG_CHIP_ID, + 2, &val); + if (ret) { + dev_err(imx208->dev, "failed to read chip id %x\n", + IMX208_CHIP_ID); + return ret; + } + + if (val != IMX208_CHIP_ID) { + dev_err(imx208->dev, "chip id mismatch: %x!=%x\n", + IMX208_CHIP_ID, val); + return -EIO; + } + + imx208->identified = true; + + return 0; +} + +/* Start streaming */ +static int imx208_start_streaming(struct imx208 *imx208) +{ + const struct imx208_reg_list *reg_list; + int ret, link_freq_index; + + ret = imx208_identify_module(imx208); + if (ret) + return ret; + + /* Setup PLL */ + link_freq_index = imx208->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + ret = imx208_write_regs(imx208, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(imx208->dev, "%s failed to set plls\n", __func__); + return ret; + } + + /* Apply default values of current mode */ + reg_list = &imx208->cur_mode->reg_list; + ret = imx208_write_regs(imx208, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(imx208->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx208->sd.ctrl_handler); + if (ret) + return ret; + + /* set stream on register */ + return imx208_write_reg(imx208, IMX208_REG_MODE_SELECT, + 1, IMX208_MODE_STREAMING); +} + +/* Stop streaming */ +static int imx208_stop_streaming(struct imx208 *imx208) +{ + int ret; + + /* set stream off register */ + ret = imx208_write_reg(imx208, IMX208_REG_MODE_SELECT, + 1, IMX208_MODE_STANDBY); + if (ret) + dev_err(imx208->dev, "%s failed to set stream\n", __func__); + + /* + * Return success even if it was an error, as there is nothing the + * caller can do about it. + */ + return 0; +} + +static int imx208_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx208 *imx208 = to_imx208(sd); + int ret = 0; + + mutex_lock(&imx208->imx208_mx); + + if (enable) { + ret = pm_runtime_resume_and_get(imx208->dev); + if (ret) { + mutex_unlock(&imx208->imx208_mx); + return ret; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx208_start_streaming(imx208); + if (ret) + goto err_rpm_put; + } else { + imx208_stop_streaming(imx208); + pm_runtime_put(imx208->dev); + } + + mutex_unlock(&imx208->imx208_mx); + + /* vflip and hflip cannot change during streaming */ + v4l2_ctrl_grab(imx208->vflip, enable); + v4l2_ctrl_grab(imx208->hflip, enable); + + return ret; + +err_rpm_put: + pm_runtime_put(imx208->dev); + mutex_unlock(&imx208->imx208_mx); + + return ret; +} + +/* Verify chip ID */ +static const struct v4l2_subdev_video_ops imx208_video_ops = { + .s_stream = imx208_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx208_pad_ops = { + .enum_mbus_code = imx208_enum_mbus_code, + .get_fmt = imx208_get_pad_format, + .set_fmt = imx208_set_pad_format, + .enum_frame_size = imx208_enum_frame_size, +}; + +static const struct v4l2_subdev_ops imx208_subdev_ops = { + .video = &imx208_video_ops, + .pad = &imx208_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx208_internal_ops = { + .open = imx208_open, +}; + +static int imx208_read_otp(struct imx208 *imx208) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx208->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2] = { IMX208_OTP_BASE >> 8, IMX208_OTP_BASE & 0xff }; + int ret = 0; + + mutex_lock(&imx208->imx208_mx); + + if (imx208->otp_read) + goto out_unlock; + + ret = pm_runtime_resume_and_get(imx208->dev); + if (ret) + goto out_unlock; + + ret = imx208_identify_module(imx208); + if (ret) + goto out_pm_put; + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from registers */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = sizeof(imx208->otp_data); + msgs[1].buf = imx208->otp_data; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret == ARRAY_SIZE(msgs)) { + imx208->otp_read = true; + ret = 0; + } + +out_pm_put: + pm_runtime_put(imx208->dev); + +out_unlock: + mutex_unlock(&imx208->imx208_mx); + + return ret; +} + +static ssize_t otp_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct i2c_client *client = to_i2c_client(kobj_to_dev(kobj)); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx208 *imx208 = to_imx208(sd); + int ret; + + ret = imx208_read_otp(imx208); + if (ret) + return ret; + + memcpy(buf, &imx208->otp_data[off], count); + return count; +} + +static const BIN_ATTR_RO(otp, IMX208_OTP_SIZE); + +/* Initialize control handlers */ +static int imx208_init_controls(struct imx208 *imx208) +{ + struct v4l2_ctrl_handler *ctrl_hdlr = &imx208->ctrl_handler; + s64 exposure_max; + s64 vblank_def; + s64 vblank_min; + s64 pixel_rate_min; + s64 pixel_rate_max; + int ret; + + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); + if (ret) + return ret; + + mutex_init(&imx208->imx208_mx); + ctrl_hdlr->lock = &imx208->imx208_mx; + imx208->link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, + &imx208_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, + 0, link_freq_menu_items); + + if (imx208->link_freq) + imx208->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + pixel_rate_max = link_freq_to_pixel_rate(link_freq_menu_items[0]); + pixel_rate_min = + link_freq_to_pixel_rate(link_freq_menu_items[ARRAY_SIZE(link_freq_menu_items) - 1]); + /* By default, PIXEL_RATE is read only */ + imx208->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx208_ctrl_ops, + V4L2_CID_PIXEL_RATE, + pixel_rate_min, pixel_rate_max, + 1, pixel_rate_max); + + vblank_def = imx208->cur_mode->vts_def - imx208->cur_mode->height; + vblank_min = imx208->cur_mode->vts_min - imx208->cur_mode->height; + imx208->vblank = + v4l2_ctrl_new_std(ctrl_hdlr, &imx208_ctrl_ops, V4L2_CID_VBLANK, + vblank_min, + IMX208_VTS_MAX - imx208->cur_mode->height, 1, + vblank_def); + + imx208->hblank = + v4l2_ctrl_new_std(ctrl_hdlr, &imx208_ctrl_ops, V4L2_CID_HBLANK, + IMX208_PPL_384MHZ - imx208->cur_mode->width, + IMX208_PPL_384MHZ - imx208->cur_mode->width, + 1, + IMX208_PPL_384MHZ - imx208->cur_mode->width); + + if (imx208->hblank) + imx208->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + exposure_max = imx208->cur_mode->vts_def - 8; + v4l2_ctrl_new_std(ctrl_hdlr, &imx208_ctrl_ops, V4L2_CID_EXPOSURE, + IMX208_EXPOSURE_MIN, exposure_max, + IMX208_EXPOSURE_STEP, IMX208_EXPOSURE_DEFAULT); + + imx208->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx208_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (imx208->hflip) + imx208->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + imx208->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx208_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (imx208->vflip) + imx208->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + v4l2_ctrl_new_std(ctrl_hdlr, &imx208_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX208_ANA_GAIN_MIN, IMX208_ANA_GAIN_MAX, + IMX208_ANA_GAIN_STEP, IMX208_ANA_GAIN_DEFAULT); + + v4l2_ctrl_new_custom(ctrl_hdlr, &imx208_digital_gain_control, NULL); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx208_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx208_test_pattern_menu) - 1, + 0, 0, imx208_test_pattern_menu); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(imx208->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + imx208->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&imx208->imx208_mx); + + return ret; +} + +static void imx208_free_controls(struct imx208 *imx208) +{ + v4l2_ctrl_handler_free(imx208->sd.ctrl_handler); +} + +static int imx208_probe(struct i2c_client *client) +{ + struct imx208 *imx208; + unsigned long freq; + int ret; + bool full_power; + + imx208 = devm_kzalloc(&client->dev, sizeof(*imx208), GFP_KERNEL); + if (!imx208) + return -ENOMEM; + + imx208->dev = &client->dev; + + imx208->clk = devm_v4l2_sensor_clk_get(imx208->dev, NULL); + if (IS_ERR(imx208->clk)) + return dev_err_probe(imx208->dev, PTR_ERR(imx208->clk), + "failed to get clock\n"); + + freq = clk_get_rate(imx208->clk); + if (freq != 19200000) + return dev_err_probe(imx208->dev, -EINVAL, + "external clock %lu is not supported\n", + freq); + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&imx208->sd, client, &imx208_subdev_ops); + + full_power = acpi_dev_state_d0(imx208->dev); + if (full_power) { + /* Check module identity */ + ret = imx208_identify_module(imx208); + if (ret) { + dev_err(imx208->dev, "failed to find sensor: %d", ret); + goto error_probe; + } + } + + /* Set default mode to max resolution */ + imx208->cur_mode = &supported_modes[0]; + + ret = imx208_init_controls(imx208); + if (ret) { + dev_err(imx208->dev, "failed to init controls: %d", ret); + goto error_probe; + } + + /* Initialize subdev */ + imx208->sd.internal_ops = &imx208_internal_ops; + imx208->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + imx208->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + imx208->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&imx208->sd.entity, 1, &imx208->pad); + if (ret) { + dev_err(imx208->dev, "%s failed:%d\n", __func__, ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&imx208->sd); + if (ret < 0) + goto error_media_entity; + + ret = device_create_bin_file(imx208->dev, &bin_attr_otp); + if (ret) { + dev_err(imx208->dev, "sysfs otp creation failed\n"); + goto error_async_subdev; + } + + /* Set the device's state to active if it's in D0 state. */ + if (full_power) + pm_runtime_set_active(imx208->dev); + pm_runtime_enable(imx208->dev); + pm_runtime_idle(imx208->dev); + + return 0; + +error_async_subdev: + v4l2_async_unregister_subdev(&imx208->sd); + +error_media_entity: + media_entity_cleanup(&imx208->sd.entity); + +error_handler_free: + imx208_free_controls(imx208); + +error_probe: + mutex_destroy(&imx208->imx208_mx); + + return ret; +} + +static void imx208_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx208 *imx208 = to_imx208(sd); + + device_remove_bin_file(imx208->dev, &bin_attr_otp); + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + imx208_free_controls(imx208); + + pm_runtime_disable(imx208->dev); + pm_runtime_set_suspended(imx208->dev); + + mutex_destroy(&imx208->imx208_mx); +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id imx208_acpi_ids[] = { + { "INT3478" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(acpi, imx208_acpi_ids); +#endif + +static struct i2c_driver imx208_i2c_driver = { + .driver = { + .name = "imx208", + .acpi_match_table = ACPI_PTR(imx208_acpi_ids), + }, + .probe = imx208_probe, + .remove = imx208_remove, + .flags = I2C_DRV_ACPI_WAIVE_D0_PROBE, +}; + +module_i2c_driver(imx208_i2c_driver); + +MODULE_AUTHOR("Yeh, Andy "); +MODULE_AUTHOR("Chen, Ping-chung "); +MODULE_AUTHOR("Shawn Tu"); +MODULE_DESCRIPTION("Sony IMX208 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/imx214.c b/drivers/media/i2c/imx214.c new file mode 100644 index 000000000..d4945b192 --- /dev/null +++ b/drivers/media/i2c/imx214.c @@ -0,0 +1,1535 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * imx214.c - imx214 sensor driver + * + * Copyright 2018 Qtechnology A/S + * + * Ricardo Ribalda + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ccs-pll.h" + +/* Chip ID */ +#define IMX214_REG_CHIP_ID CCI_REG16(0x0016) +#define IMX214_CHIP_ID 0x0214 + +#define IMX214_REG_MODE_SELECT CCI_REG8(0x0100) +#define IMX214_MODE_STANDBY 0x00 +#define IMX214_MODE_STREAMING 0x01 + +#define IMX214_REG_FAST_STANDBY_CTRL CCI_REG8(0x0106) + +#define IMX214_DEFAULT_LINK_FREQ 600000000 +/* Keep wrong link frequency for backward compatibility */ +#define IMX214_DEFAULT_LINK_FREQ_LEGACY 480000000 +#define IMX214_FPS 30 + +/* V-TIMING internal */ +#define IMX214_REG_FRM_LENGTH_LINES CCI_REG16(0x0340) +#define IMX214_VTS_MAX 0xffff + +#define IMX214_VBLANK_MIN 890 + +/* HBLANK control - read only */ +#define IMX214_PPL_DEFAULT 5008 + +/* Exposure control */ +#define IMX214_REG_EXPOSURE CCI_REG16(0x0202) +#define IMX214_EXPOSURE_OFFSET 10 +#define IMX214_EXPOSURE_MIN 1 +#define IMX214_EXPOSURE_STEP 1 +#define IMX214_EXPOSURE_DEFAULT 3184 +#define IMX214_REG_EXPOSURE_RATIO CCI_REG8(0x0222) +#define IMX214_REG_SHORT_EXPOSURE CCI_REG16(0x0224) + +/* Analog gain control */ +#define IMX214_REG_ANALOG_GAIN CCI_REG16(0x0204) +#define IMX214_REG_SHORT_ANALOG_GAIN CCI_REG16(0x0216) +#define IMX214_ANA_GAIN_MIN 0 +#define IMX214_ANA_GAIN_MAX 448 +#define IMX214_ANA_GAIN_STEP 1 +#define IMX214_ANA_GAIN_DEFAULT 0x0 + +/* Digital gain control */ +#define IMX214_REG_DIG_GAIN_GREENR CCI_REG16(0x020e) +#define IMX214_REG_DIG_GAIN_RED CCI_REG16(0x0210) +#define IMX214_REG_DIG_GAIN_BLUE CCI_REG16(0x0212) +#define IMX214_REG_DIG_GAIN_GREENB CCI_REG16(0x0214) +#define IMX214_DGTL_GAIN_MIN 0x0100 +#define IMX214_DGTL_GAIN_MAX 0x0fff +#define IMX214_DGTL_GAIN_DEFAULT 0x0100 +#define IMX214_DGTL_GAIN_STEP 1 + +#define IMX214_REG_ORIENTATION CCI_REG8(0x0101) + +#define IMX214_REG_MASK_CORR_FRAMES CCI_REG8(0x0105) +#define IMX214_CORR_FRAMES_TRANSMIT 0 +#define IMX214_CORR_FRAMES_MASK 1 + +#define IMX214_REG_CSI_DATA_FORMAT CCI_REG16(0x0112) +#define IMX214_CSI_DATA_FORMAT_RAW8 0x0808 +#define IMX214_CSI_DATA_FORMAT_RAW10 0x0A0A +#define IMX214_CSI_DATA_FORMAT_COMP6 0x0A06 +#define IMX214_CSI_DATA_FORMAT_COMP8 0x0A08 +#define IMX214_BITS_PER_PIXEL_MASK 0xFF + +#define IMX214_REG_CSI_LANE_MODE CCI_REG8(0x0114) +#define IMX214_CSI_2_LANE_MODE 1 +#define IMX214_CSI_4_LANE_MODE 3 + +#define IMX214_REG_EXCK_FREQ CCI_REG16(0x0136) +#define IMX214_EXCK_FREQ(n) ((n) * 256) /* n expressed in MHz */ + +#define IMX214_REG_TEMP_SENSOR_CONTROL CCI_REG8(0x0138) + +#define IMX214_REG_HDR_MODE CCI_REG8(0x0220) +#define IMX214_HDR_MODE_OFF 0 +#define IMX214_HDR_MODE_ON 1 + +#define IMX214_REG_HDR_RES_REDUCTION CCI_REG8(0x0221) +#define IMX214_HDR_RES_REDU_THROUGH 0x11 +#define IMX214_HDR_RES_REDU_2_BINNING 0x22 + +/* PLL settings */ +#define IMX214_REG_VTPXCK_DIV CCI_REG8(0x0301) +#define IMX214_REG_VTSYCK_DIV CCI_REG8(0x0303) +#define IMX214_REG_PREPLLCK_VT_DIV CCI_REG8(0x0305) +#define IMX214_REG_PLL_VT_MPY CCI_REG16(0x0306) +#define IMX214_REG_OPPXCK_DIV CCI_REG8(0x0309) +#define IMX214_REG_OPSYCK_DIV CCI_REG8(0x030b) +#define IMX214_REG_PLL_MULT_DRIV CCI_REG8(0x0310) +#define IMX214_PLL_SINGLE 0 +#define IMX214_PLL_DUAL 1 + +#define IMX214_REG_LINE_LENGTH_PCK CCI_REG16(0x0342) +#define IMX214_REG_X_ADD_STA CCI_REG16(0x0344) +#define IMX214_REG_Y_ADD_STA CCI_REG16(0x0346) +#define IMX214_REG_X_ADD_END CCI_REG16(0x0348) +#define IMX214_REG_Y_ADD_END CCI_REG16(0x034a) +#define IMX214_REG_X_OUTPUT_SIZE CCI_REG16(0x034c) +#define IMX214_REG_Y_OUTPUT_SIZE CCI_REG16(0x034e) +#define IMX214_REG_X_EVEN_INC CCI_REG8(0x0381) +#define IMX214_REG_X_ODD_INC CCI_REG8(0x0383) +#define IMX214_REG_Y_EVEN_INC CCI_REG8(0x0385) +#define IMX214_REG_Y_ODD_INC CCI_REG8(0x0387) + +#define IMX214_REG_SCALE_MODE CCI_REG8(0x0401) +#define IMX214_SCALE_NONE 0 +#define IMX214_SCALE_HORIZONTAL 1 +#define IMX214_SCALE_FULL 2 +#define IMX214_REG_SCALE_M CCI_REG16(0x0404) + +#define IMX214_REG_DIG_CROP_X_OFFSET CCI_REG16(0x0408) +#define IMX214_REG_DIG_CROP_Y_OFFSET CCI_REG16(0x040a) +#define IMX214_REG_DIG_CROP_WIDTH CCI_REG16(0x040c) +#define IMX214_REG_DIG_CROP_HEIGHT CCI_REG16(0x040e) + +#define IMX214_REG_REQ_LINK_BIT_RATE CCI_REG32(0x0820) +#define IMX214_LINK_BIT_RATE_MBPS(n) ((n) << 16) + +/* Binning mode */ +#define IMX214_REG_BINNING_MODE CCI_REG8(0x0900) +#define IMX214_BINNING_NONE 0 +#define IMX214_BINNING_ENABLE 1 +#define IMX214_REG_BINNING_TYPE CCI_REG8(0x0901) +#define IMX214_REG_BINNING_WEIGHTING CCI_REG8(0x0902) +#define IMX214_BINNING_AVERAGE 0x00 +#define IMX214_BINNING_SUMMED 0x01 +#define IMX214_BINNING_BAYER 0x02 + +#define IMX214_REG_SING_DEF_CORR_EN CCI_REG8(0x0b06) +#define IMX214_SING_DEF_CORR_OFF 0 +#define IMX214_SING_DEF_CORR_ON 1 + +/* AWB control */ +#define IMX214_REG_ABS_GAIN_GREENR CCI_REG16(0x0b8e) +#define IMX214_REG_ABS_GAIN_RED CCI_REG16(0x0b90) +#define IMX214_REG_ABS_GAIN_BLUE CCI_REG16(0x0b92) +#define IMX214_REG_ABS_GAIN_GREENB CCI_REG16(0x0b94) + +#define IMX214_REG_RMSC_NR_MODE CCI_REG8(0x3001) +#define IMX214_REG_STATS_OUT_EN CCI_REG8(0x3013) +#define IMX214_STATS_OUT_OFF 0 +#define IMX214_STATS_OUT_ON 1 + +/* Chroma noise reduction */ +#define IMX214_REG_NML_NR_EN CCI_REG8(0x30a2) +#define IMX214_NML_NR_OFF 0 +#define IMX214_NML_NR_ON 1 + +#define IMX214_REG_EBD_SIZE_V CCI_REG8(0x5041) +#define IMX214_EBD_NO 0 +#define IMX214_EBD_4_LINE 4 + +#define IMX214_REG_RG_STATS_LMT CCI_REG16(0x6d12) +#define IMX214_RG_STATS_LMT_10_BIT 0x03FF +#define IMX214_RG_STATS_LMT_14_BIT 0x3FFF + +#define IMX214_REG_ATR_FAST_MOVE CCI_REG8(0x9300) + +/* Test Pattern Control */ +#define IMX214_REG_TEST_PATTERN CCI_REG16(0x0600) +#define IMX214_TEST_PATTERN_DISABLE 0 +#define IMX214_TEST_PATTERN_SOLID_COLOR 1 +#define IMX214_TEST_PATTERN_COLOR_BARS 2 +#define IMX214_TEST_PATTERN_GREY_COLOR 3 +#define IMX214_TEST_PATTERN_PN9 4 + +/* Test pattern colour components */ +#define IMX214_REG_TESTP_RED CCI_REG16(0x0602) +#define IMX214_REG_TESTP_GREENR CCI_REG16(0x0604) +#define IMX214_REG_TESTP_BLUE CCI_REG16(0x0606) +#define IMX214_REG_TESTP_GREENB CCI_REG16(0x0608) +#define IMX214_TESTP_COLOUR_MIN 0 +#define IMX214_TESTP_COLOUR_MAX 0x03ff +#define IMX214_TESTP_COLOUR_STEP 1 + +/* IMX214 native and active pixel array size */ +#define IMX214_NATIVE_WIDTH 4224U +#define IMX214_NATIVE_HEIGHT 3136U +#define IMX214_PIXEL_ARRAY_LEFT 8U +#define IMX214_PIXEL_ARRAY_TOP 8U +#define IMX214_PIXEL_ARRAY_WIDTH 4208U +#define IMX214_PIXEL_ARRAY_HEIGHT 3120U + +static const char * const imx214_supply_name[] = { + "vdda", + "vddd", + "vdddo", +}; + +#define IMX214_NUM_SUPPLIES ARRAY_SIZE(imx214_supply_name) + +/* + * The supported formats. + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 imx214_mbus_formats[] = { + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, +}; + +static const char * const imx214_test_pattern_menu[] = { + "Disabled", + "Color Bars", + "Solid Color", + "Grey Color Bars", + "PN9" +}; + +static const int imx214_test_pattern_val[] = { + IMX214_TEST_PATTERN_DISABLE, + IMX214_TEST_PATTERN_COLOR_BARS, + IMX214_TEST_PATTERN_SOLID_COLOR, + IMX214_TEST_PATTERN_GREY_COLOR, + IMX214_TEST_PATTERN_PN9, +}; + +struct imx214 { + struct device *dev; + struct clk *xclk; + struct regmap *regmap; + + struct ccs_pll pll; + + struct v4l2_fwnode_endpoint bus_cfg; + + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *unit_size; + struct { + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + }; + + struct regulator_bulk_data supplies[IMX214_NUM_SUPPLIES]; + + struct gpio_desc *enable_gpio; +}; + +/*From imx214_mode_tbls.h*/ +static const struct cci_reg_sequence mode_4096x2304[] = { + { IMX214_REG_HDR_MODE, IMX214_HDR_MODE_OFF }, + { IMX214_REG_HDR_RES_REDUCTION, IMX214_HDR_RES_REDU_THROUGH }, + { IMX214_REG_EXPOSURE_RATIO, 1 }, + { IMX214_REG_X_ADD_STA, 56 }, + { IMX214_REG_Y_ADD_STA, 408 }, + { IMX214_REG_X_ADD_END, 4151 }, + { IMX214_REG_Y_ADD_END, 2711 }, + { IMX214_REG_X_EVEN_INC, 1 }, + { IMX214_REG_X_ODD_INC, 1 }, + { IMX214_REG_Y_EVEN_INC, 1 }, + { IMX214_REG_Y_ODD_INC, 1 }, + { IMX214_REG_BINNING_MODE, IMX214_BINNING_NONE }, + { IMX214_REG_BINNING_TYPE, 0 }, + { IMX214_REG_BINNING_WEIGHTING, IMX214_BINNING_AVERAGE }, + { CCI_REG8(0x3000), 0x35 }, + { CCI_REG8(0x3054), 0x01 }, + { CCI_REG8(0x305C), 0x11 }, + + { IMX214_REG_CSI_DATA_FORMAT, IMX214_CSI_DATA_FORMAT_RAW10 }, + { IMX214_REG_X_OUTPUT_SIZE, 4096 }, + { IMX214_REG_Y_OUTPUT_SIZE, 2304 }, + { IMX214_REG_SCALE_MODE, IMX214_SCALE_NONE }, + { IMX214_REG_SCALE_M, 2 }, + { IMX214_REG_DIG_CROP_X_OFFSET, 0 }, + { IMX214_REG_DIG_CROP_Y_OFFSET, 0 }, + { IMX214_REG_DIG_CROP_WIDTH, 4096 }, + { IMX214_REG_DIG_CROP_HEIGHT, 2304 }, + + { CCI_REG8(0x3A03), 0x09 }, + { CCI_REG8(0x3A04), 0x50 }, + { CCI_REG8(0x3A05), 0x01 }, + + { IMX214_REG_SING_DEF_CORR_EN, IMX214_SING_DEF_CORR_ON }, + { IMX214_REG_NML_NR_EN, IMX214_NML_NR_OFF }, + + { CCI_REG8(0x30B4), 0x00 }, + + { CCI_REG8(0x3A02), 0xFF }, + + { CCI_REG8(0x3011), 0x00 }, + { IMX214_REG_STATS_OUT_EN, IMX214_STATS_OUT_ON }, + + { IMX214_REG_SHORT_EXPOSURE, 500 }, + + { CCI_REG8(0x4170), 0x00 }, + { CCI_REG8(0x4171), 0x10 }, + { CCI_REG8(0x4176), 0x00 }, + { CCI_REG8(0x4177), 0x3C }, + { CCI_REG8(0xAE20), 0x04 }, + { CCI_REG8(0xAE21), 0x5C }, +}; + +static const struct cci_reg_sequence mode_1920x1080[] = { + { IMX214_REG_HDR_MODE, IMX214_HDR_MODE_OFF }, + { IMX214_REG_HDR_RES_REDUCTION, IMX214_HDR_RES_REDU_THROUGH }, + { IMX214_REG_EXPOSURE_RATIO, 1 }, + { IMX214_REG_X_ADD_STA, 1144 }, + { IMX214_REG_Y_ADD_STA, 1020 }, + { IMX214_REG_X_ADD_END, 3063 }, + { IMX214_REG_Y_ADD_END, 2099 }, + { IMX214_REG_X_EVEN_INC, 1 }, + { IMX214_REG_X_ODD_INC, 1 }, + { IMX214_REG_Y_EVEN_INC, 1 }, + { IMX214_REG_Y_ODD_INC, 1 }, + { IMX214_REG_BINNING_MODE, IMX214_BINNING_NONE }, + { IMX214_REG_BINNING_TYPE, 0 }, + { IMX214_REG_BINNING_WEIGHTING, IMX214_BINNING_AVERAGE }, + { CCI_REG8(0x3000), 0x35 }, + { CCI_REG8(0x3054), 0x01 }, + { CCI_REG8(0x305C), 0x11 }, + + { IMX214_REG_CSI_DATA_FORMAT, IMX214_CSI_DATA_FORMAT_RAW10 }, + { IMX214_REG_X_OUTPUT_SIZE, 1920 }, + { IMX214_REG_Y_OUTPUT_SIZE, 1080 }, + { IMX214_REG_SCALE_MODE, IMX214_SCALE_NONE }, + { IMX214_REG_SCALE_M, 2 }, + { IMX214_REG_DIG_CROP_X_OFFSET, 0 }, + { IMX214_REG_DIG_CROP_Y_OFFSET, 0 }, + { IMX214_REG_DIG_CROP_WIDTH, 1920 }, + { IMX214_REG_DIG_CROP_HEIGHT, 1080 }, + + { CCI_REG8(0x3A03), 0x04 }, + { CCI_REG8(0x3A04), 0xF8 }, + { CCI_REG8(0x3A05), 0x02 }, + + { IMX214_REG_SING_DEF_CORR_EN, IMX214_SING_DEF_CORR_ON }, + { IMX214_REG_NML_NR_EN, IMX214_NML_NR_OFF }, + + { CCI_REG8(0x30B4), 0x00 }, + + { CCI_REG8(0x3A02), 0xFF }, + + { CCI_REG8(0x3011), 0x00 }, + { IMX214_REG_STATS_OUT_EN, IMX214_STATS_OUT_ON }, + + { IMX214_REG_SHORT_EXPOSURE, 500 }, + + { CCI_REG8(0x4170), 0x00 }, + { CCI_REG8(0x4171), 0x10 }, + { CCI_REG8(0x4176), 0x00 }, + { CCI_REG8(0x4177), 0x3C }, + { CCI_REG8(0xAE20), 0x04 }, + { CCI_REG8(0xAE21), 0x5C }, +}; + +static const struct cci_reg_sequence mode_table_common[] = { + /* software reset */ + + /* software standby settings */ + { IMX214_REG_MODE_SELECT, IMX214_MODE_STANDBY }, + + /* ATR setting */ + { IMX214_REG_ATR_FAST_MOVE, 2 }, + + /* global setting */ + /* basic config */ + { IMX214_REG_MASK_CORR_FRAMES, IMX214_CORR_FRAMES_MASK }, + { IMX214_REG_FAST_STANDBY_CTRL, 1 }, + { IMX214_REG_LINE_LENGTH_PCK, IMX214_PPL_DEFAULT }, + { CCI_REG8(0x4550), 0x02 }, + { CCI_REG8(0x4601), 0x00 }, + { CCI_REG8(0x4642), 0x05 }, + { CCI_REG8(0x6227), 0x11 }, + { CCI_REG8(0x6276), 0x00 }, + { CCI_REG8(0x900E), 0x06 }, + { CCI_REG8(0xA802), 0x90 }, + { CCI_REG8(0xA803), 0x11 }, + { CCI_REG8(0xA804), 0x62 }, + { CCI_REG8(0xA805), 0x77 }, + { CCI_REG8(0xA806), 0xAE }, + { CCI_REG8(0xA807), 0x34 }, + { CCI_REG8(0xA808), 0xAE }, + { CCI_REG8(0xA809), 0x35 }, + { CCI_REG8(0xA80A), 0x62 }, + { CCI_REG8(0xA80B), 0x83 }, + { CCI_REG8(0xAE33), 0x00 }, + + /* analog setting */ + { CCI_REG8(0x4174), 0x00 }, + { CCI_REG8(0x4175), 0x11 }, + { CCI_REG8(0x4612), 0x29 }, + { CCI_REG8(0x461B), 0x12 }, + { CCI_REG8(0x461F), 0x06 }, + { CCI_REG8(0x4635), 0x07 }, + { CCI_REG8(0x4637), 0x30 }, + { CCI_REG8(0x463F), 0x18 }, + { CCI_REG8(0x4641), 0x0D }, + { CCI_REG8(0x465B), 0x12 }, + { CCI_REG8(0x465F), 0x11 }, + { CCI_REG8(0x4663), 0x11 }, + { CCI_REG8(0x4667), 0x0F }, + { CCI_REG8(0x466F), 0x0F }, + { CCI_REG8(0x470E), 0x09 }, + { CCI_REG8(0x4909), 0xAB }, + { CCI_REG8(0x490B), 0x95 }, + { CCI_REG8(0x4915), 0x5D }, + { CCI_REG8(0x4A5F), 0xFF }, + { CCI_REG8(0x4A61), 0xFF }, + { CCI_REG8(0x4A73), 0x62 }, + { CCI_REG8(0x4A85), 0x00 }, + { CCI_REG8(0x4A87), 0xFF }, + + /* embedded data */ + { IMX214_REG_EBD_SIZE_V, IMX214_EBD_4_LINE }, + { CCI_REG8(0x583C), 0x04 }, + { CCI_REG8(0x620E), 0x04 }, + { CCI_REG8(0x6EB2), 0x01 }, + { CCI_REG8(0x6EB3), 0x00 }, + { IMX214_REG_ATR_FAST_MOVE, 2 }, + + /* imagequality */ + /* HDR setting */ + { IMX214_REG_RMSC_NR_MODE, 0x07 }, + { IMX214_REG_RG_STATS_LMT, IMX214_RG_STATS_LMT_14_BIT }, + { CCI_REG8(0x9344), 0x03 }, + { CCI_REG8(0x9706), 0x10 }, + { CCI_REG8(0x9707), 0x03 }, + { CCI_REG8(0x9708), 0x03 }, + { CCI_REG8(0x9E04), 0x01 }, + { CCI_REG8(0x9E05), 0x00 }, + { CCI_REG8(0x9E0C), 0x01 }, + { CCI_REG8(0x9E0D), 0x02 }, + { CCI_REG8(0x9E24), 0x00 }, + { CCI_REG8(0x9E25), 0x8C }, + { CCI_REG8(0x9E26), 0x00 }, + { CCI_REG8(0x9E27), 0x94 }, + { CCI_REG8(0x9E28), 0x00 }, + { CCI_REG8(0x9E29), 0x96 }, + + /* CNR parameter setting */ + { CCI_REG8(0x69DB), 0x01 }, + + /* Moire reduction */ + { CCI_REG8(0x6957), 0x01 }, + + /* image enhancement */ + { CCI_REG8(0x6987), 0x17 }, + { CCI_REG8(0x698A), 0x03 }, + { CCI_REG8(0x698B), 0x03 }, + + /* white balanace */ + { IMX214_REG_ABS_GAIN_GREENR, 0x0100 }, + { IMX214_REG_ABS_GAIN_RED, 0x0100 }, + { IMX214_REG_ABS_GAIN_BLUE, 0x0100 }, + { IMX214_REG_ABS_GAIN_GREENB, 0x0100 }, + + /* ATR setting */ + { CCI_REG8(0x6E50), 0x00 }, + { CCI_REG8(0x6E51), 0x32 }, + { CCI_REG8(0x9340), 0x00 }, + { CCI_REG8(0x9341), 0x3C }, + { CCI_REG8(0x9342), 0x03 }, + { CCI_REG8(0x9343), 0xFF }, +}; + +/* + * Declare modes in order, from biggest + * to smallest height. + */ +static const struct imx214_mode { + u32 width; + u32 height; + + /* V-timing */ + unsigned int vts_def; + + unsigned int num_of_regs; + const struct cci_reg_sequence *reg_table; +} imx214_modes[] = { + { + .width = 4096, + .height = 2304, + .vts_def = 3194, + .num_of_regs = ARRAY_SIZE(mode_4096x2304), + .reg_table = mode_4096x2304, + }, + { + .width = 1920, + .height = 1080, + .vts_def = 3194, + .num_of_regs = ARRAY_SIZE(mode_1920x1080), + .reg_table = mode_1920x1080, + }, +}; + +static inline struct imx214 *to_imx214(struct v4l2_subdev *sd) +{ + return container_of(sd, struct imx214, sd); +} + +static int __maybe_unused imx214_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx214 *imx214 = to_imx214(sd); + int ret; + + ret = regulator_bulk_enable(IMX214_NUM_SUPPLIES, imx214->supplies); + if (ret < 0) { + dev_err(imx214->dev, "failed to enable regulators: %d\n", ret); + return ret; + } + + usleep_range(2000, 3000); + + ret = clk_prepare_enable(imx214->xclk); + if (ret < 0) { + regulator_bulk_disable(IMX214_NUM_SUPPLIES, imx214->supplies); + dev_err(imx214->dev, "clk prepare enable failed\n"); + return ret; + } + + gpiod_set_value_cansleep(imx214->enable_gpio, 1); + usleep_range(12000, 15000); + + return 0; +} + +static int __maybe_unused imx214_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx214 *imx214 = to_imx214(sd); + + gpiod_set_value_cansleep(imx214->enable_gpio, 0); + + clk_disable_unprepare(imx214->xclk); + + regulator_bulk_disable(IMX214_NUM_SUPPLIES, imx214->supplies); + usleep_range(10, 20); + + return 0; +} + +/* Get bayer order based on flip setting. */ +static u32 imx214_get_format_code(struct imx214 *imx214) +{ + unsigned int i; + + i = (imx214->vflip->val ? 2 : 0) | (imx214->hflip->val ? 1 : 0); + + return imx214_mbus_formats[i]; +} + +static void imx214_update_pad_format(struct imx214 *imx214, + const struct imx214_mode *mode, + struct v4l2_mbus_framefmt *fmt, u32 code) +{ + fmt->code = imx214_get_format_code(imx214); + fmt->width = mode->width; + fmt->height = mode->height; + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); +} + +static int imx214_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx214 *imx214 = to_imx214(sd); + + if (code->index >= (ARRAY_SIZE(imx214_mbus_formats) / 4)) + return -EINVAL; + + code->code = imx214_get_format_code(imx214); + + return 0; +} + +static int imx214_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx214 *imx214 = to_imx214(subdev); + u32 code; + + code = imx214_get_format_code(imx214); + if (fse->code != code) + return -EINVAL; + + if (fse->index >= ARRAY_SIZE(imx214_modes)) + return -EINVAL; + + fse->min_width = fse->max_width = imx214_modes[fse->index].width; + fse->min_height = fse->max_height = imx214_modes[fse->index].height; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int imx214_s_register(struct v4l2_subdev *subdev, + const struct v4l2_dbg_register *reg) +{ + struct imx214 *imx214 = container_of(subdev, struct imx214, sd); + + return regmap_write(imx214->regmap, reg->reg, reg->val); +} + +static int imx214_g_register(struct v4l2_subdev *subdev, + struct v4l2_dbg_register *reg) +{ + struct imx214 *imx214 = container_of(subdev, struct imx214, sd); + unsigned int aux; + int ret; + + reg->size = 1; + ret = regmap_read(imx214->regmap, reg->reg, &aux); + reg->val = aux; + + return ret; +} +#endif + +static const struct v4l2_subdev_core_ops imx214_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = imx214_g_register, + .s_register = imx214_s_register, +#endif +}; + +static int imx214_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct imx214 *imx214 = to_imx214(sd); + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + const struct imx214_mode *mode; + + mode = v4l2_find_nearest_size(imx214_modes, + ARRAY_SIZE(imx214_modes), width, height, + format->format.width, + format->format.height); + + imx214_update_pad_format(imx214, mode, &format->format, + format->format.code); + __format = v4l2_subdev_state_get_format(sd_state, 0); + + *__format = format->format; + + __crop = v4l2_subdev_state_get_crop(sd_state, 0); + __crop->width = mode->width; + __crop->height = mode->height; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + int exposure_max; + int exposure_def; + int hblank; + + /* Update blank limits */ + __v4l2_ctrl_modify_range(imx214->vblank, IMX214_VBLANK_MIN, + IMX214_VTS_MAX - mode->height, 2, + mode->vts_def - mode->height); + + /* Update max exposure while meeting expected vblanking */ + exposure_max = mode->vts_def - IMX214_EXPOSURE_OFFSET; + exposure_def = min(exposure_max, IMX214_EXPOSURE_DEFAULT); + __v4l2_ctrl_modify_range(imx214->exposure, + imx214->exposure->minimum, + exposure_max, imx214->exposure->step, + exposure_def); + + /* + * Currently PPL is fixed to IMX214_PPL_DEFAULT, so hblank + * depends on mode->width only, and is not changeable in any + * way other than changing the mode. + */ + hblank = IMX214_PPL_DEFAULT - mode->width; + __v4l2_ctrl_modify_range(imx214->hblank, hblank, hblank, 1, + hblank); + } + + return 0; +} + +static int imx214_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_state_get_crop(sd_state, 0); + return 0; + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = IMX214_NATIVE_WIDTH; + sel->r.height = IMX214_NATIVE_HEIGHT; + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = IMX214_PIXEL_ARRAY_TOP; + sel->r.left = IMX214_PIXEL_ARRAY_LEFT; + sel->r.width = IMX214_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX214_PIXEL_ARRAY_HEIGHT; + return 0; + } + + return -EINVAL; +} + +static int imx214_entity_init_state(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_subdev_format fmt = { }; + + fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + fmt.format.code = MEDIA_BUS_FMT_SRGGB10_1X10; + fmt.format.width = imx214_modes[0].width; + fmt.format.height = imx214_modes[0].height; + + imx214_set_format(subdev, sd_state, &fmt); + + return 0; +} + +static int imx214_configure_pll(struct imx214 *imx214) +{ + int ret = 0; + + cci_write(imx214->regmap, IMX214_REG_VTPXCK_DIV, + imx214->pll.vt_bk.pix_clk_div, &ret); + cci_write(imx214->regmap, IMX214_REG_VTSYCK_DIV, + imx214->pll.vt_bk.sys_clk_div, &ret); + cci_write(imx214->regmap, IMX214_REG_PREPLLCK_VT_DIV, + imx214->pll.vt_fr.pre_pll_clk_div, &ret); + cci_write(imx214->regmap, IMX214_REG_PLL_VT_MPY, + imx214->pll.vt_fr.pll_multiplier, &ret); + cci_write(imx214->regmap, IMX214_REG_OPPXCK_DIV, + imx214->pll.op_bk.pix_clk_div, &ret); + cci_write(imx214->regmap, IMX214_REG_OPSYCK_DIV, + imx214->pll.op_bk.sys_clk_div, &ret); + cci_write(imx214->regmap, IMX214_REG_PLL_MULT_DRIV, + IMX214_PLL_SINGLE, &ret); + cci_write(imx214->regmap, IMX214_REG_EXCK_FREQ, + IMX214_EXCK_FREQ(imx214->pll.ext_clk_freq_hz / 1000000), &ret); + + return ret; +} + +static int imx214_update_digital_gain(struct imx214 *imx214, u32 val) +{ + int ret = 0; + + cci_write(imx214->regmap, IMX214_REG_DIG_GAIN_GREENR, val, &ret); + cci_write(imx214->regmap, IMX214_REG_DIG_GAIN_RED, val, &ret); + cci_write(imx214->regmap, IMX214_REG_DIG_GAIN_BLUE, val, &ret); + cci_write(imx214->regmap, IMX214_REG_DIG_GAIN_GREENB, val, &ret); + + return ret; +} + +static int imx214_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx214 *imx214 = container_of(ctrl->handler, + struct imx214, ctrls); + const struct v4l2_mbus_framefmt *format = NULL; + struct v4l2_subdev_state *state; + int ret = 0; + + if (ctrl->id == V4L2_CID_VBLANK) { + int exposure_max, exposure_def; + + state = v4l2_subdev_get_locked_active_state(&imx214->sd); + format = v4l2_subdev_state_get_format(state, 0); + + /* Update max exposure while meeting expected vblanking */ + exposure_max = + format->height + ctrl->val - IMX214_EXPOSURE_OFFSET; + exposure_def = min(exposure_max, IMX214_EXPOSURE_DEFAULT); + __v4l2_ctrl_modify_range(imx214->exposure, + imx214->exposure->minimum, + exposure_max, imx214->exposure->step, + exposure_def); + } + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (!pm_runtime_get_if_in_use(imx214->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + cci_write(imx214->regmap, IMX214_REG_ANALOG_GAIN, + ctrl->val, &ret); + cci_write(imx214->regmap, IMX214_REG_SHORT_ANALOG_GAIN, + ctrl->val, &ret); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = imx214_update_digital_gain(imx214, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + cci_write(imx214->regmap, IMX214_REG_EXPOSURE, ctrl->val, &ret); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + cci_write(imx214->regmap, IMX214_REG_ORIENTATION, + imx214->hflip->val | imx214->vflip->val << 1, &ret); + break; + case V4L2_CID_VBLANK: + cci_write(imx214->regmap, IMX214_REG_FRM_LENGTH_LINES, + format->height + ctrl->val, &ret); + break; + case V4L2_CID_TEST_PATTERN: + cci_write(imx214->regmap, IMX214_REG_TEST_PATTERN, + imx214_test_pattern_val[ctrl->val], &ret); + break; + case V4L2_CID_TEST_PATTERN_RED: + cci_write(imx214->regmap, IMX214_REG_TESTP_RED, + ctrl->val, &ret); + break; + case V4L2_CID_TEST_PATTERN_GREENR: + cci_write(imx214->regmap, IMX214_REG_TESTP_GREENR, + ctrl->val, &ret); + break; + case V4L2_CID_TEST_PATTERN_BLUE: + cci_write(imx214->regmap, IMX214_REG_TESTP_BLUE, + ctrl->val, &ret); + break; + case V4L2_CID_TEST_PATTERN_GREENB: + cci_write(imx214->regmap, IMX214_REG_TESTP_GREENB, + ctrl->val, &ret); + break; + default: + ret = -EINVAL; + } + + pm_runtime_put(imx214->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx214_ctrl_ops = { + .s_ctrl = imx214_set_ctrl, +}; + +static int imx214_pll_calculate(struct imx214 *imx214, struct ccs_pll *pll, + unsigned int link_freq) +{ + struct ccs_pll_limits limits = { + .min_ext_clk_freq_hz = 6000000, + .max_ext_clk_freq_hz = 27000000, + + .vt_fr = { + .min_pre_pll_clk_div = 1, + .max_pre_pll_clk_div = 15, + /* Value is educated guess as we don't have a spec */ + .min_pll_ip_clk_freq_hz = 6000000, + /* Value is educated guess as we don't have a spec */ + .max_pll_ip_clk_freq_hz = 12000000, + .min_pll_multiplier = 12, + .max_pll_multiplier = 1200, + .min_pll_op_clk_freq_hz = 338000000, + .max_pll_op_clk_freq_hz = 1200000000, + }, + .vt_bk = { + .min_sys_clk_div = 2, + .max_sys_clk_div = 4, + .min_pix_clk_div = 5, + .max_pix_clk_div = 10, + .min_pix_clk_freq_hz = 30000000, + .max_pix_clk_freq_hz = 120000000, + }, + .op_bk = { + .min_sys_clk_div = 1, + .max_sys_clk_div = 2, + .min_pix_clk_div = 6, + .max_pix_clk_div = 10, + .min_pix_clk_freq_hz = 30000000, + .max_pix_clk_freq_hz = 120000000, + }, + + .min_line_length_pck_bin = IMX214_PPL_DEFAULT, + .min_line_length_pck = IMX214_PPL_DEFAULT, + }; + unsigned int num_lanes = imx214->bus_cfg.bus.mipi_csi2.num_data_lanes; + + /* + * There are no documented constraints on the sys clock frequency, for + * either branch. Recover them based on the PLL output clock frequency + * and sys_clk_div limits on one hand, and the pix clock frequency and + * the pix_clk_div limits on the other hand. + */ + limits.vt_bk.min_sys_clk_freq_hz = + max(limits.vt_fr.min_pll_op_clk_freq_hz / limits.vt_bk.max_sys_clk_div, + limits.vt_bk.min_pix_clk_freq_hz * limits.vt_bk.min_pix_clk_div); + limits.vt_bk.max_sys_clk_freq_hz = + min(limits.vt_fr.max_pll_op_clk_freq_hz / limits.vt_bk.min_sys_clk_div, + limits.vt_bk.max_pix_clk_freq_hz * limits.vt_bk.max_pix_clk_div); + + limits.op_bk.min_sys_clk_freq_hz = + max(limits.vt_fr.min_pll_op_clk_freq_hz / limits.op_bk.max_sys_clk_div, + limits.op_bk.min_pix_clk_freq_hz * limits.op_bk.min_pix_clk_div); + limits.op_bk.max_sys_clk_freq_hz = + min(limits.vt_fr.max_pll_op_clk_freq_hz / limits.op_bk.min_sys_clk_div, + limits.op_bk.max_pix_clk_freq_hz * limits.op_bk.max_pix_clk_div); + + memset(pll, 0, sizeof(*pll)); + + pll->bus_type = CCS_PLL_BUS_TYPE_CSI2_DPHY; + pll->op_lanes = num_lanes; + pll->vt_lanes = num_lanes; + pll->csi2.lanes = num_lanes; + + pll->binning_horizontal = 1; + pll->binning_vertical = 1; + pll->scale_m = 1; + pll->scale_n = 1; + pll->bits_per_pixel = + IMX214_CSI_DATA_FORMAT_RAW10 & IMX214_BITS_PER_PIXEL_MASK; + pll->flags = CCS_PLL_FLAG_LANE_SPEED_MODEL; + pll->link_freq = link_freq; + pll->ext_clk_freq_hz = clk_get_rate(imx214->xclk); + + return ccs_pll_calculate(imx214->dev, &limits, pll); +} + +static int imx214_pll_update(struct imx214 *imx214) +{ + u64 link_freq; + int ret; + + link_freq = imx214->bus_cfg.link_frequencies[imx214->link_freq->val]; + ret = imx214_pll_calculate(imx214, &imx214->pll, link_freq); + if (ret) { + dev_err(imx214->dev, "PLL calculations failed: %d\n", ret); + return ret; + } + + ret = v4l2_ctrl_s_ctrl_int64(imx214->pixel_rate, + imx214->pll.pixel_rate_pixel_array); + if (ret) { + dev_err(imx214->dev, "failed to set pixel rate\n"); + return ret; + } + + return 0; +} + +static int imx214_ctrls_init(struct imx214 *imx214) +{ + static const struct v4l2_area unit_size = { + .width = 1120, + .height = 1120, + }; + const struct imx214_mode *mode = &imx214_modes[0]; + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; + int exposure_max, exposure_def; + int hblank; + int i, ret; + + ret = v4l2_fwnode_device_parse(imx214->dev, &props); + if (ret < 0) + return ret; + + ctrl_hdlr = &imx214->ctrls; + ret = v4l2_ctrl_handler_init(&imx214->ctrls, 13); + if (ret) + return ret; + + imx214->pixel_rate = + v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_PIXEL_RATE, 1, + INT_MAX, 1, 1); + + imx214->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, NULL, + V4L2_CID_LINK_FREQ, + imx214->bus_cfg.nr_of_link_frequencies - 1, + 0, imx214->bus_cfg.link_frequencies); + + /* + * WARNING! + * Values obtained reverse engineering blobs and/or devices. + * Ranges and functionality might be wrong. + * + * Sony, please release some register set documentation for the + * device. + * + * Yours sincerely, Ricardo. + */ + + /* Initial vblank/hblank/exposure parameters based on current mode */ + imx214->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx214_ctrl_ops, + V4L2_CID_VBLANK, IMX214_VBLANK_MIN, + IMX214_VTS_MAX - mode->height, 2, + mode->vts_def - mode->height); + + hblank = IMX214_PPL_DEFAULT - mode->width; + imx214->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx214_ctrl_ops, + V4L2_CID_HBLANK, hblank, hblank, + 1, hblank); + exposure_max = mode->vts_def - IMX214_EXPOSURE_OFFSET; + exposure_def = min(exposure_max, IMX214_EXPOSURE_DEFAULT); + imx214->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx214_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX214_EXPOSURE_MIN, + exposure_max, + IMX214_EXPOSURE_STEP, + exposure_def); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx214_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX214_ANA_GAIN_MIN, IMX214_ANA_GAIN_MAX, + IMX214_ANA_GAIN_STEP, IMX214_ANA_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx214_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX214_DGTL_GAIN_MIN, IMX214_DGTL_GAIN_MAX, + IMX214_DGTL_GAIN_STEP, IMX214_DGTL_GAIN_DEFAULT); + + imx214->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx214_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + imx214->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx214_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_cluster(2, &imx214->hflip); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx214_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx214_test_pattern_menu) - 1, + 0, 0, imx214_test_pattern_menu); + for (i = 0; i < 4; i++) { + /* + * The assumption is that + * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1 + * V4L2_CID_TEST_PATTERN_BLUE == V4L2_CID_TEST_PATTERN_RED + 2 + * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3 + */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx214_ctrl_ops, + V4L2_CID_TEST_PATTERN_RED + i, + IMX214_TESTP_COLOUR_MIN, + IMX214_TESTP_COLOUR_MAX, + IMX214_TESTP_COLOUR_STEP, + IMX214_TESTP_COLOUR_MAX); + /* The "Solid color" pattern is white by default */ + } + + imx214->unit_size = v4l2_ctrl_new_std_compound(ctrl_hdlr, + NULL, + V4L2_CID_UNIT_CELL_SIZE, + v4l2_ctrl_ptr_create((void *)&unit_size), + v4l2_ctrl_ptr_create(NULL), + v4l2_ctrl_ptr_create(NULL)); + + v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx214_ctrl_ops, &props); + + ret = ctrl_hdlr->error; + if (ret) { + v4l2_ctrl_handler_free(ctrl_hdlr); + dev_err(imx214->dev, "failed to add controls: %d\n", ret); + return ret; + } + + /* Now that the controls have been properly created, set their flags. */ + imx214->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + imx214->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + imx214->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + imx214->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + ret = imx214_pll_update(imx214); + if (ret < 0) { + v4l2_ctrl_handler_free(ctrl_hdlr); + dev_err(imx214->dev, "failed to update PLL\n"); + return ret; + } + + imx214->sd.ctrl_handler = ctrl_hdlr; + + return 0; +}; + +static int imx214_start_streaming(struct imx214 *imx214) +{ + const struct v4l2_mbus_framefmt *fmt; + struct v4l2_subdev_state *state; + const struct imx214_mode *mode; + int bit_rate_mbps; + int ret; + + ret = cci_multi_reg_write(imx214->regmap, mode_table_common, + ARRAY_SIZE(mode_table_common), NULL); + if (ret < 0) { + dev_err(imx214->dev, "could not sent common table %d\n", ret); + return ret; + } + + ret = imx214_configure_pll(imx214); + if (ret) { + dev_err(imx214->dev, "failed to configure PLL: %d\n", ret); + return ret; + } + + bit_rate_mbps = imx214->pll.pixel_rate_csi / 1000000 + * imx214->pll.bits_per_pixel; + ret = cci_write(imx214->regmap, IMX214_REG_REQ_LINK_BIT_RATE, + IMX214_LINK_BIT_RATE_MBPS(bit_rate_mbps), NULL); + if (ret) { + dev_err(imx214->dev, "failed to configure link bit rate\n"); + return ret; + } + + ret = cci_write(imx214->regmap, IMX214_REG_CSI_LANE_MODE, + IMX214_CSI_4_LANE_MODE, NULL); + if (ret) { + dev_err(imx214->dev, "failed to configure lanes\n"); + return ret; + } + + state = v4l2_subdev_get_locked_active_state(&imx214->sd); + fmt = v4l2_subdev_state_get_format(state, 0); + mode = v4l2_find_nearest_size(imx214_modes, ARRAY_SIZE(imx214_modes), + width, height, fmt->width, fmt->height); + ret = cci_multi_reg_write(imx214->regmap, mode->reg_table, + mode->num_of_regs, NULL); + if (ret < 0) { + dev_err(imx214->dev, "could not sent mode table %d\n", ret); + return ret; + } + + usleep_range(10000, 10500); + + cci_write(imx214->regmap, IMX214_REG_TEMP_SENSOR_CONTROL, 0x01, NULL); + + ret = __v4l2_ctrl_handler_setup(&imx214->ctrls); + if (ret < 0) { + dev_err(imx214->dev, "could not sync v4l2 controls\n"); + return ret; + } + ret = cci_write(imx214->regmap, IMX214_REG_MODE_SELECT, + IMX214_MODE_STREAMING, NULL); + if (ret < 0) + dev_err(imx214->dev, "could not sent start table %d\n", ret); + + return ret; +} + +static int imx214_stop_streaming(struct imx214 *imx214) +{ + int ret; + + ret = cci_write(imx214->regmap, IMX214_REG_MODE_SELECT, + IMX214_MODE_STANDBY, NULL); + if (ret < 0) + dev_err(imx214->dev, "could not sent stop table %d\n", ret); + + return ret; +} + +static int imx214_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct imx214 *imx214 = to_imx214(subdev); + struct v4l2_subdev_state *state; + int ret; + + if (enable) { + ret = pm_runtime_resume_and_get(imx214->dev); + if (ret < 0) + return ret; + + state = v4l2_subdev_lock_and_get_active_state(subdev); + ret = imx214_start_streaming(imx214); + v4l2_subdev_unlock_state(state); + if (ret < 0) + goto err_rpm_put; + } else { + ret = imx214_stop_streaming(imx214); + if (ret < 0) + goto err_rpm_put; + pm_runtime_put(imx214->dev); + } + + return 0; + +err_rpm_put: + pm_runtime_put(imx214->dev); + return ret; +} + +static int imx214_get_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval *fival) +{ + /* + * FIXME: Implement support for V4L2_SUBDEV_FORMAT_TRY, using the V4L2 + * subdev active state API. + */ + if (fival->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + fival->interval.numerator = 1; + fival->interval.denominator = IMX214_FPS; + + return 0; +} + +/* + * Raw sensors should be using the VBLANK and HBLANK controls to determine + * the frame rate. However this driver was initially added using the + * [S|G|ENUM]_FRAME_INTERVAL ioctls with a fixed rate of 30fps. + * Retain the frame_interval ops for backwards compatibility, but they do + * nothing. + */ +static int imx214_enum_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct imx214 *imx214 = to_imx214(subdev); + const struct imx214_mode *mode; + + dev_warn_once(imx214->dev, "frame_interval functions return an unreliable value for compatibility reasons. Use the VBLANK and HBLANK controls to determine the correct frame rate.\n"); + + if (fie->index != 0) + return -EINVAL; + + mode = v4l2_find_nearest_size(imx214_modes, + ARRAY_SIZE(imx214_modes), width, height, + fie->width, fie->height); + + fie->code = imx214_get_format_code(imx214); + fie->width = mode->width; + fie->height = mode->height; + fie->interval.numerator = 1; + fie->interval.denominator = IMX214_FPS; + + return 0; +} + +static const struct v4l2_subdev_video_ops imx214_video_ops = { + .s_stream = imx214_s_stream, +}; + +static const struct v4l2_subdev_pad_ops imx214_subdev_pad_ops = { + .enum_mbus_code = imx214_enum_mbus_code, + .enum_frame_size = imx214_enum_frame_size, + .enum_frame_interval = imx214_enum_frame_interval, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = imx214_set_format, + .get_selection = imx214_get_selection, + .get_frame_interval = imx214_get_frame_interval, + .set_frame_interval = imx214_get_frame_interval, +}; + +static const struct v4l2_subdev_ops imx214_subdev_ops = { + .core = &imx214_core_ops, + .video = &imx214_video_ops, + .pad = &imx214_subdev_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx214_internal_ops = { + .init_state = imx214_entity_init_state, +}; + +static int imx214_get_regulators(struct device *dev, struct imx214 *imx214) +{ + unsigned int i; + + for (i = 0; i < IMX214_NUM_SUPPLIES; i++) + imx214->supplies[i].supply = imx214_supply_name[i]; + + return devm_regulator_bulk_get(dev, IMX214_NUM_SUPPLIES, + imx214->supplies); +} + +/* Verify chip ID */ +static int imx214_identify_module(struct imx214 *imx214) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx214->sd); + int ret; + u64 val; + + ret = cci_read(imx214->regmap, IMX214_REG_CHIP_ID, &val, NULL); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to read chip id %x\n", + IMX214_CHIP_ID); + + if (val != IMX214_CHIP_ID) + return dev_err_probe(&client->dev, -EIO, + "chip id mismatch: %x!=%llx\n", + IMX214_CHIP_ID, val); + + return 0; +} + +static int imx214_parse_fwnode(struct imx214 *imx214) +{ + struct fwnode_handle *endpoint __free(fwnode_handle) = NULL; + struct v4l2_fwnode_endpoint *bus_cfg = &imx214->bus_cfg; + struct device *dev = imx214->dev; + unsigned int i; + int ret; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) + return dev_err_probe(dev, -EINVAL, "endpoint node not found\n"); + + bus_cfg->bus_type = V4L2_MBUS_CSI2_DPHY; + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, bus_cfg); + if (ret) + return dev_err_probe(dev, ret, "parsing endpoint node failed\n"); + + /* Check the number of MIPI CSI2 data lanes */ + if (bus_cfg->bus.mipi_csi2.num_data_lanes != 4) { + ret = dev_err_probe(dev, -EINVAL, + "only 4 data lanes are currently supported\n"); + goto error; + } + + if (bus_cfg->nr_of_link_frequencies != 1) + dev_warn(dev, "Only one link-frequency supported, please review your DT. Continuing anyway\n"); + + for (i = 0; i < bus_cfg->nr_of_link_frequencies; i++) { + u64 freq = bus_cfg->link_frequencies[i]; + struct ccs_pll pll; + + if (freq == IMX214_DEFAULT_LINK_FREQ_LEGACY) { + dev_warn(dev, + "link-frequencies %d not supported, please review your DT. Continuing anyway\n", + IMX214_DEFAULT_LINK_FREQ); + freq = IMX214_DEFAULT_LINK_FREQ; + bus_cfg->link_frequencies[i] = freq; + } + + if (!imx214_pll_calculate(imx214, &pll, freq)) + break; + } + + if (i == bus_cfg->nr_of_link_frequencies) + ret = dev_err_probe(dev, -EINVAL, + "link-frequencies %lld not supported, please review your DT\n", + bus_cfg->nr_of_link_frequencies ? + bus_cfg->link_frequencies[0] : 0); + + return 0; + +error: + v4l2_fwnode_endpoint_free(&imx214->bus_cfg); + return ret; +} + +static int imx214_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct imx214 *imx214; + int ret; + + imx214 = devm_kzalloc(dev, sizeof(*imx214), GFP_KERNEL); + if (!imx214) + return -ENOMEM; + + imx214->dev = dev; + + imx214->xclk = devm_v4l2_sensor_clk_get(dev, NULL); + if (IS_ERR(imx214->xclk)) + return dev_err_probe(dev, PTR_ERR(imx214->xclk), + "failed to get xclk\n"); + + ret = imx214_get_regulators(dev, imx214); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + imx214->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(imx214->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(imx214->enable_gpio), + "failed to get enable gpio\n"); + + imx214->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(imx214->regmap)) + return dev_err_probe(dev, PTR_ERR(imx214->regmap), + "failed to initialize CCI\n"); + + ret = imx214_parse_fwnode(imx214); + if (ret) + return ret; + + v4l2_i2c_subdev_init(&imx214->sd, client, &imx214_subdev_ops); + imx214->sd.internal_ops = &imx214_internal_ops; + + /* + * Enable power initially, to avoid warnings + * from clk_disable on power_off + */ + ret = imx214_power_on(imx214->dev); + if (ret < 0) + goto error_fwnode; + + ret = imx214_identify_module(imx214); + if (ret) + goto error_power_off; + + ret = imx214_ctrls_init(imx214); + if (ret < 0) + goto error_power_off; + + imx214->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + imx214->pad.flags = MEDIA_PAD_FL_SOURCE; + imx214->sd.dev = &client->dev; + imx214->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + ret = media_entity_pads_init(&imx214->sd.entity, 1, &imx214->pad); + if (ret < 0) { + dev_err_probe(dev, ret, "failed to init entity pads\n"); + goto free_ctrl; + } + + imx214->sd.state_lock = imx214->ctrls.lock; + ret = v4l2_subdev_init_finalize(&imx214->sd); + if (ret < 0) { + dev_err_probe(dev, ret, "subdev init error\n"); + goto free_entity; + } + + pm_runtime_set_active(imx214->dev); + pm_runtime_enable(imx214->dev); + + ret = v4l2_async_register_subdev_sensor(&imx214->sd); + if (ret < 0) { + dev_err_probe(dev, ret, + "failed to register sensor sub-device\n"); + goto error_subdev_cleanup; + } + + pm_runtime_idle(imx214->dev); + + return 0; + +error_subdev_cleanup: + pm_runtime_disable(imx214->dev); + pm_runtime_set_suspended(&client->dev); + v4l2_subdev_cleanup(&imx214->sd); + +free_entity: + media_entity_cleanup(&imx214->sd.entity); + +free_ctrl: + v4l2_ctrl_handler_free(&imx214->ctrls); + +error_power_off: + imx214_power_off(imx214->dev); + +error_fwnode: + v4l2_fwnode_endpoint_free(&imx214->bus_cfg); + + return ret; +} + +static void imx214_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx214 *imx214 = to_imx214(sd); + + v4l2_async_unregister_subdev(&imx214->sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&imx214->sd.entity); + v4l2_ctrl_handler_free(&imx214->ctrls); + v4l2_fwnode_endpoint_free(&imx214->bus_cfg); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + imx214_power_off(imx214->dev); + pm_runtime_set_suspended(&client->dev); + } +} + +static const struct of_device_id imx214_of_match[] = { + { .compatible = "sony,imx214" }, + { } +}; +MODULE_DEVICE_TABLE(of, imx214_of_match); + +static const struct dev_pm_ops imx214_pm_ops = { + SET_RUNTIME_PM_OPS(imx214_power_off, imx214_power_on, NULL) +}; + +static struct i2c_driver imx214_i2c_driver = { + .driver = { + .of_match_table = imx214_of_match, + .pm = &imx214_pm_ops, + .name = "imx214", + }, + .probe = imx214_probe, + .remove = imx214_remove, +}; + +module_i2c_driver(imx214_i2c_driver); + +MODULE_DESCRIPTION("Sony IMX214 Camera driver"); +MODULE_AUTHOR("Ricardo Ribalda "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c new file mode 100644 index 000000000..9571f3622 --- /dev/null +++ b/drivers/media/i2c/imx219.c @@ -0,0 +1,1361 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX219 cameras. + * Copyright (C) 2019, Raspberry Pi (Trading) Ltd + * + * Based on Sony imx258 camera driver + * Copyright (C) 2018 Intel Corporation + * + * DT / fwnode changes, and regulator / GPIO control taken from imx214 driver + * Copyright 2018 Qtechnology A/S + * + * Flip handling taken from the Sony IMX319 driver. + * Copyright (C) 2018 Intel Corporation + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* Chip ID */ +#define IMX219_REG_CHIP_ID CCI_REG16(0x0000) +#define IMX219_CHIP_ID 0x0219 + +#define IMX219_REG_MODE_SELECT CCI_REG8(0x0100) +#define IMX219_MODE_STANDBY 0x00 +#define IMX219_MODE_STREAMING 0x01 + +#define IMX219_REG_CSI_LANE_MODE CCI_REG8(0x0114) +#define IMX219_CSI_2_LANE_MODE 0x01 +#define IMX219_CSI_4_LANE_MODE 0x03 + +#define IMX219_REG_DPHY_CTRL CCI_REG8(0x0128) +#define IMX219_DPHY_CTRL_TIMING_AUTO 0 +#define IMX219_DPHY_CTRL_TIMING_MANUAL 1 + +#define IMX219_REG_EXCK_FREQ CCI_REG16(0x012a) +#define IMX219_EXCK_FREQ(n) ((n) * 256) /* n expressed in MHz */ + +/* Analog gain control */ +#define IMX219_REG_ANALOG_GAIN CCI_REG8(0x0157) +#define IMX219_ANA_GAIN_MIN 0 +#define IMX219_ANA_GAIN_MAX 232 +#define IMX219_ANA_GAIN_STEP 1 +#define IMX219_ANA_GAIN_DEFAULT 0x0 + +/* Digital gain control */ +#define IMX219_REG_DIGITAL_GAIN CCI_REG16(0x0158) +#define IMX219_DGTL_GAIN_MIN 0x0100 +#define IMX219_DGTL_GAIN_MAX 0x0fff +#define IMX219_DGTL_GAIN_DEFAULT 0x0100 +#define IMX219_DGTL_GAIN_STEP 1 + +/* Exposure control */ +#define IMX219_REG_EXPOSURE CCI_REG16(0x015a) +#define IMX219_EXPOSURE_MIN 4 +#define IMX219_EXPOSURE_STEP 1 +#define IMX219_EXPOSURE_DEFAULT 0x640 +#define IMX219_EXPOSURE_MAX 65535 +#define IMX219_EXPOSURE_OFFSET 4 + +/* V_TIMING internal */ +#define IMX219_REG_FRM_LENGTH_A CCI_REG16(0x0160) +#define IMX219_FLL_MAX 0xfffe +#define IMX219_VBLANK_MIN 32 +#define IMX219_REG_LINE_LENGTH_A CCI_REG16(0x0162) +#define IMX219_LLP_MIN 0x0d78 +#define IMX219_BINNED_LLP_MIN 0x0de8 +#define IMX219_LLP_MAX 0x7ff0 + +#define IMX219_REG_X_ADD_STA_A CCI_REG16(0x0164) +#define IMX219_REG_X_ADD_END_A CCI_REG16(0x0166) +#define IMX219_REG_Y_ADD_STA_A CCI_REG16(0x0168) +#define IMX219_REG_Y_ADD_END_A CCI_REG16(0x016a) +#define IMX219_REG_X_OUTPUT_SIZE CCI_REG16(0x016c) +#define IMX219_REG_Y_OUTPUT_SIZE CCI_REG16(0x016e) +#define IMX219_REG_X_ODD_INC_A CCI_REG8(0x0170) +#define IMX219_REG_Y_ODD_INC_A CCI_REG8(0x0171) +#define IMX219_REG_ORIENTATION CCI_REG8(0x0172) + +/* Binning Mode */ +#define IMX219_REG_BINNING_MODE_H CCI_REG8(0x0174) +#define IMX219_REG_BINNING_MODE_V CCI_REG8(0x0175) +#define IMX219_BINNING_NONE 0x00 +#define IMX219_BINNING_X2 0x01 +#define IMX219_BINNING_X2_ANALOG 0x03 + +#define IMX219_REG_CSI_DATA_FORMAT_A CCI_REG16(0x018c) + +/* PLL Settings */ +#define IMX219_REG_VTPXCK_DIV CCI_REG8(0x0301) +#define IMX219_REG_VTSYCK_DIV CCI_REG8(0x0303) +#define IMX219_REG_PREPLLCK_VT_DIV CCI_REG8(0x0304) +#define IMX219_REG_PREPLLCK_OP_DIV CCI_REG8(0x0305) +#define IMX219_REG_PLL_VT_MPY CCI_REG16(0x0306) +#define IMX219_REG_OPPXCK_DIV CCI_REG8(0x0309) +#define IMX219_REG_OPSYCK_DIV CCI_REG8(0x030b) +#define IMX219_REG_PLL_OP_MPY CCI_REG16(0x030c) + +/* Test Pattern Control */ +#define IMX219_REG_TEST_PATTERN CCI_REG16(0x0600) +#define IMX219_TEST_PATTERN_DISABLE 0 +#define IMX219_TEST_PATTERN_SOLID_COLOR 1 +#define IMX219_TEST_PATTERN_COLOR_BARS 2 +#define IMX219_TEST_PATTERN_GREY_COLOR 3 +#define IMX219_TEST_PATTERN_PN9 4 +#define IMX219_TEST_PATTERN_16SPLIT_COLOR_BARS 5 +#define IMX219_TEST_PATTERN_16SPLIT_INV_COLOR_BARS 6 +#define IMX219_TEST_PATTERN_COLUMN_COUNTER 7 +#define IMX219_TEST_PATTERN_INV_COLUMN_COUNTER 8 +#define IMX219_TEST_PATTERN_PN31 9 + +/* Test pattern colour components */ +#define IMX219_REG_TESTP_RED CCI_REG16(0x0602) +#define IMX219_REG_TESTP_GREENR CCI_REG16(0x0604) +#define IMX219_REG_TESTP_BLUE CCI_REG16(0x0606) +#define IMX219_REG_TESTP_GREENB CCI_REG16(0x0608) +#define IMX219_TESTP_COLOUR_MIN 0 +#define IMX219_TESTP_COLOUR_MAX 0x03ff +#define IMX219_TESTP_COLOUR_STEP 1 + +#define IMX219_REG_TP_WINDOW_WIDTH CCI_REG16(0x0624) +#define IMX219_REG_TP_WINDOW_HEIGHT CCI_REG16(0x0626) + +/* External clock frequency is 24.0M */ +#define IMX219_XCLK_FREQ 24000000 + +/* Pixel rate is fixed for all the modes */ +#define IMX219_PIXEL_RATE 182400000 +#define IMX219_PIXEL_RATE_4LANE 281600000 + +#define IMX219_DEFAULT_LINK_FREQ 456000000 +#define IMX219_DEFAULT_LINK_FREQ_4LANE_UNSUPPORTED 363000000 +#define IMX219_DEFAULT_LINK_FREQ_4LANE 364000000 + +/* IMX219 native and active pixel array size. */ +#define IMX219_NATIVE_WIDTH 3296U +#define IMX219_NATIVE_HEIGHT 2480U +#define IMX219_ACTIVE_AREA_LEFT 8U +#define IMX219_ACTIVE_AREA_TOP 8U +#define IMX219_ACTIVE_AREA_WIDTH 3280U +#define IMX219_ACTIVE_AREA_HEIGHT 2464U + +/* Mode : resolution and related config&values */ +struct imx219_mode { + /* Frame width */ + unsigned int width; + /* Frame height */ + unsigned int height; + + /* V-timing */ + unsigned int fll_def; +}; + +static const struct cci_reg_sequence imx219_common_regs[] = { + { IMX219_REG_MODE_SELECT, 0x00 }, /* Mode Select */ + + /* To Access Addresses 3000-5fff, send the following commands */ + { CCI_REG8(0x30eb), 0x05 }, + { CCI_REG8(0x30eb), 0x0c }, + { CCI_REG8(0x300a), 0xff }, + { CCI_REG8(0x300b), 0xff }, + { CCI_REG8(0x30eb), 0x05 }, + { CCI_REG8(0x30eb), 0x09 }, + + /* Undocumented registers */ + { CCI_REG8(0x455e), 0x00 }, + { CCI_REG8(0x471e), 0x4b }, + { CCI_REG8(0x4767), 0x0f }, + { CCI_REG8(0x4750), 0x14 }, + { CCI_REG8(0x4540), 0x00 }, + { CCI_REG8(0x47b4), 0x14 }, + { CCI_REG8(0x4713), 0x30 }, + { CCI_REG8(0x478b), 0x10 }, + { CCI_REG8(0x478f), 0x10 }, + { CCI_REG8(0x4793), 0x10 }, + { CCI_REG8(0x4797), 0x0e }, + { CCI_REG8(0x479b), 0x0e }, + + /* Frame Bank Register Group "A" */ + { IMX219_REG_X_ODD_INC_A, 1 }, + { IMX219_REG_Y_ODD_INC_A, 1 }, + + /* Output setup registers */ + { IMX219_REG_DPHY_CTRL, IMX219_DPHY_CTRL_TIMING_AUTO }, + { IMX219_REG_EXCK_FREQ, IMX219_EXCK_FREQ(IMX219_XCLK_FREQ / 1000000) }, +}; + +static const struct cci_reg_sequence imx219_2lane_regs[] = { + /* PLL Clock Table */ + { IMX219_REG_VTPXCK_DIV, 5 }, + { IMX219_REG_VTSYCK_DIV, 1 }, + { IMX219_REG_PREPLLCK_VT_DIV, 3 }, /* 0x03 = AUTO set */ + { IMX219_REG_PREPLLCK_OP_DIV, 3 }, /* 0x03 = AUTO set */ + { IMX219_REG_PLL_VT_MPY, 57 }, + { IMX219_REG_OPSYCK_DIV, 1 }, + { IMX219_REG_PLL_OP_MPY, 114 }, + + /* 2-Lane CSI Mode */ + { IMX219_REG_CSI_LANE_MODE, IMX219_CSI_2_LANE_MODE }, +}; + +static const struct cci_reg_sequence imx219_4lane_regs[] = { + /* PLL Clock Table */ + { IMX219_REG_VTPXCK_DIV, 5 }, + { IMX219_REG_VTSYCK_DIV, 1 }, + { IMX219_REG_PREPLLCK_VT_DIV, 3 }, /* 0x03 = AUTO set */ + { IMX219_REG_PREPLLCK_OP_DIV, 3 }, /* 0x03 = AUTO set */ + { IMX219_REG_PLL_VT_MPY, 88 }, + { IMX219_REG_OPSYCK_DIV, 1 }, + { IMX219_REG_PLL_OP_MPY, 91 }, + + /* 4-Lane CSI Mode */ + { IMX219_REG_CSI_LANE_MODE, IMX219_CSI_4_LANE_MODE }, +}; + +static const s64 imx219_link_freq_menu[] = { + IMX219_DEFAULT_LINK_FREQ, +}; + +static const s64 imx219_link_freq_4lane_menu[] = { + IMX219_DEFAULT_LINK_FREQ_4LANE, + /* + * This will never be advertised to userspace, but will be used for + * v4l2_link_freq_to_bitmap + */ + IMX219_DEFAULT_LINK_FREQ_4LANE_UNSUPPORTED, +}; + +static const char * const imx219_test_pattern_menu[] = { + "Disabled", + "Color Bars", + "Solid Color", + "Grey Color Bars", + "PN9", + "16 Split Color Bars", + "16 Split Inverted Color Bars", + "Column Counter", + "Inverted Column Counter", + "PN31" +}; + +static const int imx219_test_pattern_val[] = { + IMX219_TEST_PATTERN_DISABLE, + IMX219_TEST_PATTERN_COLOR_BARS, + IMX219_TEST_PATTERN_SOLID_COLOR, + IMX219_TEST_PATTERN_GREY_COLOR, + IMX219_TEST_PATTERN_PN9, + IMX219_TEST_PATTERN_16SPLIT_COLOR_BARS, + IMX219_TEST_PATTERN_16SPLIT_INV_COLOR_BARS, + IMX219_TEST_PATTERN_COLUMN_COUNTER, + IMX219_TEST_PATTERN_INV_COLUMN_COUNTER, + IMX219_TEST_PATTERN_PN31 +}; + +/* regulator supplies */ +static const char * const imx219_supply_name[] = { + /* Supplies can be enabled in any order */ + "VANA", /* Analog (2.8V) supply */ + "VDIG", /* Digital Core (1.8V) supply */ + "VDDL", /* IF (1.2V) supply */ +}; + +#define IMX219_NUM_SUPPLIES ARRAY_SIZE(imx219_supply_name) + +/* + * The supported formats. + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 imx219_mbus_formats[] = { + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, + + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SBGGR8_1X8, +}; + +/* + * Initialisation delay between XCLR low->high and the moment when the sensor + * can start capture (i.e. can leave software stanby) must be not less than: + * t4 + max(t5, t6 +