diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml index 2017428d8828e..fe667ac2ccbd3 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml +++ b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml @@ -158,6 +158,8 @@ properties: - hit,tx23d38vm0caa # Innolux AT043TN24 4.3" WQVGA TFT LCD panel - innolux,at043tn24 + # Innolux AT056tN53V1 5.6" VGA (640x480) TFT LCD panel + - innolux,at056tn53v1 # Innolux AT070TN92 7.0" WQVGA TFT LCD panel - innolux,at070tn92 # Innolux G070ACE-L01 7" WVGA (800x480) TFT LCD panel diff --git a/Documentation/devicetree/bindings/eeprom/at24.yaml b/Documentation/devicetree/bindings/eeprom/at24.yaml index 50af7ccf6e21a..e44f83899839d 100644 --- a/Documentation/devicetree/bindings/eeprom/at24.yaml +++ b/Documentation/devicetree/bindings/eeprom/at24.yaml @@ -124,6 +124,7 @@ properties: - items: - enum: - belling,bl24c16a + - belling,bl24c16f - renesas,r1ex24016 - const: atmel,24c16 - items: diff --git a/Documentation/devicetree/bindings/media/i2c/imx708.yaml b/Documentation/devicetree/bindings/media/i2c/imx708.yaml new file mode 100644 index 0000000000000..8ea618fadecbe --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/imx708.yaml @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/imx708.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony 1/2.3-Inch 12Mpixel CMOS Digital Image Sensor + +maintainers: + - Raspberry Pi Kernel Maintenance + +description: |- + The Sony IMX708 is a 1/2.3-inch CMOS active pixel digital image sensor + with an active array size of 4608H x 2592V. It is programmable through + I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet. + Image data is sent through MIPI CSI-2, which is configured as either 2 or + 4 data lanes. + +properties: + compatible: + const: sony,imx708 + + reg: + description: I2C device address + maxItems: 1 + + clocks: + maxItems: 1 + + VDIG-supply: + description: + Digital I/O voltage supply, 1.1 volts + + VANA1-supply: + description: + Analog1 voltage supply, 2.8 volts + + VANA2-supply: + description: + Analog2 voltage supply, 1.8 volts + + VDDL-supply: + description: + Digital core voltage supply, 1.8 volts + + reset-gpios: + description: |- + Reference to the GPIO connected to the xclr pin, if any. + Must be released (set high) after all supplies and INCK are applied. + + # See ../video-interfaces.txt for more details + port: + type: object + properties: + endpoint: + type: object + properties: + data-lanes: + description: |- + The sensor supports either two-lane, or four-lane operation. + For two-lane operation the property must be set to <1 2>. + items: + - const: 1 + - const: 2 + + clock-noncontinuous: + type: boolean + description: |- + MIPI CSI-2 clock is non-continuous if this property is present, + otherwise it's continuous. + + link-frequencies: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint64-array + description: + Allowed data bus frequencies. + + required: + - link-frequencies + +required: + - compatible + - reg + - clocks + - VANA1-supply + - VANA2-supply + - VDIG-supply + - VDDL-supply + - port + +additionalProperties: false + +examples: + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + imx708: sensor@1a { + compatible = "sony,imx708"; + reg = <0x1a>; + clocks = <&imx708_clk>; + VANA1-supply = <&imx708_vana1>; /* 2.8v */ + VANA2-supply = <&imx708_vana2>; /* 1.8v */ + VDIG-supply = <&imx708_vdig>; /* 1.1v */ + VDDL-supply = <&imx708_vddl>; /* 1.8v */ + + port { + imx708_0: endpoint { + remote-endpoint = <&csi1_ep>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = /bits/ 64 <450000000>; + }; + }; + }; + }; + +... diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst index 2a94371448dc0..b716b51f849d3 100644 --- a/Documentation/userspace-api/media/v4l/subdev-formats.rst +++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst @@ -625,6 +625,43 @@ The following tables list existing packed RGB formats. - b\ :sub:`2` - b\ :sub:`1` - b\ :sub:`0` + * .. _MEDIA_BUS_FMT_RGB565_1X24_CPADHI: + + - MEDIA_BUS_FMT_RGB565_1X24_CPADHI + - 0x1022 + - + - + - + - + - + - + - + - + - + - 0 + - 0 + - 0 + - r\ :sub:`4` + - r\ :sub:`3` + - r\ :sub:`2` + - r\ :sub:`1` + - r\ :sub:`0` + - 0 + - 0 + - g\ :sub:`5` + - g\ :sub:`4` + - g\ :sub:`3` + - g\ :sub:`2` + - g\ :sub:`1` + - g\ :sub:`0` + - 0 + - 0 + - 0 + - b\ :sub:`4` + - b\ :sub:`3` + - b\ :sub:`2` + - b\ :sub:`1` + - b\ :sub:`0` * .. _MEDIA-BUS-FMT-BGR565-2X8-BE: - MEDIA_BUS_FMT_BGR565_2X8_BE @@ -913,6 +950,43 @@ The following tables list existing packed RGB formats. - g\ :sub:`5` - g\ :sub:`4` - g\ :sub:`3` + * .. _MEDIA-BUS-FMT-BGR666-1X18: + + - MEDIA_BUS_FMT-BGR666_1X18 + - 0x1023 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - b\ :sub:`5` + - b\ :sub:`4` + - b\ :sub:`3` + - b\ :sub:`2` + - b\ :sub:`1` + - b\ :sub:`0` + - g\ :sub:`5` + - g\ :sub:`4` + - g\ :sub:`3` + - g\ :sub:`2` + - g\ :sub:`1` + - g\ :sub:`0` + - r\ :sub:`5` + - r\ :sub:`4` + - r\ :sub:`3` + - r\ :sub:`2` + - r\ :sub:`1` + - r\ :sub:`0` * .. _MEDIA-BUS-FMT-RGB666-1X18: - MEDIA_BUS_FMT_RGB666_1X18 @@ -1096,6 +1170,43 @@ The following tables list existing packed RGB formats. - g\ :sub:`2` - g\ :sub:`1` - g\ :sub:`0` + * .. _MEDIA-BUS-FMT-BGR666-1X24_CPADHI: + + - MEDIA_BUS_FMT_BGR666_1X24_CPADHI + - 0x1024 + - + - + - + - + - + - + - + - + - + - 0 + - 0 + - b\ :sub:`5` + - b\ :sub:`4` + - b\ :sub:`3` + - b\ :sub:`2` + - b\ :sub:`1` + - b\ :sub:`0` + - 0 + - 0 + - g\ :sub:`5` + - g\ :sub:`4` + - g\ :sub:`3` + - g\ :sub:`2` + - g\ :sub:`1` + - g\ :sub:`0` + - 0 + - 0 + - r\ :sub:`5` + - r\ :sub:`4` + - r\ :sub:`3` + - r\ :sub:`2` + - r\ :sub:`1` + - r\ :sub:`0` * .. _MEDIA-BUS-FMT-RGB666-1X24_CPADHI: - MEDIA_BUS_FMT_RGB666_1X24_CPADHI diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile index de83a99ff195f..07d718288aead 100644 --- a/arch/arm64/boot/dts/qcom/Makefile +++ b/arch/arm64/boot/dts/qcom/Makefile @@ -126,6 +126,9 @@ dtb-$(CONFIG_ARCH_QCOM) += qcm6490-shift-otter.dtb dtb-$(CONFIG_ARCH_QCOM) += qcs404-evb-1000.dtb dtb-$(CONFIG_ARCH_QCOM) += qcs404-evb-4000.dtb dtb-$(CONFIG_ARCH_QCOM) += qcs615-ride.dtb +dtb-$(CONFIG_ARCH_QCOM) += qcs6490-radxa-cm-q64-rpi-cm5-io.dtb +qcs6490-radxa-cm-q64-rpi-cm5-io-kvm-dtbs := qcs6490-radxa-cm-q64-rpi-cm5-io.dtb qcs6490-radxa-cm-q64-rpi-cm5-io-kvm.dtbo +dtb-$(CONFIG_ARCH_QCOM) += qcs6490-radxa-cm-q64-rpi-cm5-io-kvm.dtb dtb-$(CONFIG_ARCH_QCOM) += qcs6490-radxa-dragon-q6a.dtb qcs6490-radxa-dragon-q6a-kvm-dtbs := qcs6490-radxa-dragon-q6a.dtb qcs6490-radxa-dragon-q6a-kvm.dtbo dtb-$(CONFIG_ARCH_QCOM) += qcs6490-radxa-dragon-q6a-kvm.dtb diff --git a/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io-kvm.dtso b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io-kvm.dtso new file mode 100644 index 0000000000000..c77daabe769cc --- /dev/null +++ b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io-kvm.dtso @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2025 Radxa Computer (Shenzhen) Co., Ltd. + * + * This device tree overlay is supposed to be applied by UEFI firmware + * when Hypervisor Override is set to enabled. + */ + +#include + +/dts-v1/; +/plugin/; + +/* Required when Hypervisor Override is set to auto */ +&{/chosen} { + radxa,enable-kvm = <1>; +}; + +/* We can't and don't need to use zap shader in EL2 as linux can zap the gpu on it's own. */ +&gpu_zap_shader { + status = "disabled"; +}; + +&soc { + #address-cells = <2>; + #size-cells = <2>; + + pcie@1c08000 { + #address-cells = <3>; + #size-cells = <2>; + + /* Allow using upper PCIe space */ + ranges = <0x01000000 0x0 0x00000000 0x0 0x40200000 0x0 0x100000>, + <0x02000000 0x0 0x40300000 0x0 0x40300000 0x0 0x1fd00000>, + <0x03000000 0x4 0x00000000 0x4 0x00000000 0x3 0x00000000>; + }; +}; + +&remoteproc_adsp { + qcom,broken-reset; +}; + +&remoteproc_cdsp { + qcom,broken-reset; +}; + +&scm { + qcom,shm-bridge-vmid = ; +}; + +&venus { + iommus = <&apps_smmu 0x2180 0x20>, + <&apps_smmu 0x2184 0x20>; + + video-firmware { + iommus = <&apps_smmu 0x21a2 0x0>; + }; +}; diff --git a/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io.dts b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io.dts new file mode 100644 index 0000000000000..ac5c062f2a6b9 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/qcs6490-radxa-cm-q64-rpi-cm5-io.dts @@ -0,0 +1,1343 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2025 Radxa Computer (Shenzhen) Co., Ltd. + */ + +/dts-v1/; + +#include +#include +#include +#include +#include +#include +#include "sc7280.dtsi" +#include "pm7325.dtsi" +#include "pm8350c.dtsi" /* PM7350C */ +#include "pmk8350.dtsi" /* PMK7325 */ +#include "qcs6490-audioreach.dtsi" + +/delete-node/ &adsp_mem; +/delete-node/ &cdsp_mem; +/delete-node/ &gpu_zap_mem; +/delete-node/ &ipa_fw_mem; +/delete-node/ &mpss_mem; +/delete-node/ &remoteproc_mpss; +/delete-node/ &remoteproc_wpss; +/delete-node/ &rmtfs_mem; +/delete-node/ &video_mem; +/delete-node/ &wifi; +/delete-node/ &wlan_ce_mem; +/delete-node/ &wlan_fw_mem; +/delete-node/ &wpss_mem; + +/ { + model = "Radxa CM-Q64 Raspberry Pi Compute Module 5 IO Board"; + compatible = "radxa,cm-q64-rpi-cm5-io", "radxa,cm-q64", "qcom,qcm6490"; + chassis-type = "embedded"; + + aliases { + mmc0 = &sdhc_1; + mmc1 = &sdhc_2; + serial0 = &uart5; + }; + + wcd938x: audio-codec { + compatible = "qcom,wcd9380-codec"; + + pinctrl-0 = <&wcd_default>; + pinctrl-names = "default"; + + reset-gpios = <&tlmm 83 GPIO_ACTIVE_LOW>; + + vdd-rxtx-supply = <&vreg_l18b_1p8>; + vdd-io-supply = <&vreg_l18b_1p8>; + vdd-buck-supply = <&vreg_l17b_1p8>; + vdd-mic-bias-supply = <&vreg_bob_3p296>; + + qcom,micbias1-microvolt = <1800000>; + qcom,micbias2-microvolt = <1800000>; + qcom,micbias3-microvolt = <1800000>; + qcom,micbias4-microvolt = <1800000>; + qcom,mbhc-buttons-vthreshold-microvolt = <75000 150000 237000 500000 500000 500000 500000 500000>; + qcom,mbhc-headset-vthreshold-microvolt = <1700000>; + qcom,mbhc-headphone-vthreshold-microvolt = <50000>; + qcom,rx-device = <&wcd_rx>; + qcom,tx-device = <&wcd_tx>; + + qcom,hphl-jack-type-normally-closed; + + #sound-dai-cells = <1>; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + usb2_1_con: connector-0 { + compatible = "usb-a-connector"; + vbus-supply = <&vbus>; + + port { + usb2_1_connector: endpoint { + remote-endpoint = <&usb_hub_2_1>; + }; + }; + }; + + usb2_2_con: connector-1 { + compatible = "usb-a-connector"; + vbus-supply = <&vbus>; + + port { + usb2_2_connector: endpoint { + remote-endpoint = <&usb_hub_2_2>; + }; + }; + }; + + usb2_3_con: connector-2 { + compatible = "usb-a-connector"; + vbus-supply = <&vbus>; + + port { + usb2_3_connector: endpoint { + remote-endpoint = <&usb_hub_2_3>; + }; + }; + }; + + usb3_con: connector { + compatible = "usb-a-connector"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + usb3_con_hs_in: endpoint { + remote-endpoint = <&usb_1_dwc3_hs>; + }; + }; + + port@1 { + reg = <1>; + + usb3_con_ss_in: endpoint { + remote-endpoint = <&usb_1_qmpphy_out_usb>; + }; + }; + }; + }; + + pm8350c_gpio9_pwm: pm8350c-gpio9-pwm { + compatible = "pwm-gpio"; + gpios = <&pm8350c_gpios 9 GPIO_ACTIVE_HIGH>; + pinctrl-0 = <&fan_ctr_pwm>; + pinctrl-names = "default"; + #pwm-cells = <3>; + }; + + pwm-fan { + pinctrl-0 = <&fan_tacho>; + pinctrl-names = "default"; + compatible = "pwm-fan"; + pwms = <&pm8350c_gpio9_pwm 0 50000000 0>; + fan-supply = <&vcc_3v3>; + interrupt-parent = <&pm8350c_gpios>; + interrupts = <5 IRQ_TYPE_EDGE_FALLING>; + cooling-levels = <0 120 150 180 210 240 255>; + }; + + hdmi-dp-bridge { + compatible = "radxa,ra620"; + + pinctrl-0 = <&dp_hot_plug_det>; + pinctrl-names = "default"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + hdmi_dp_bridge_in: endpoint { + remote-endpoint = <&usb_1_qmpphy_out_dp>; + }; + }; + + port@1 { + reg = <1>; + + hdmi_dp_bridge_out: endpoint { + remote-endpoint = <&hdmi_dp_connector_in>; + }; + }; + }; + }; + + hdmi-dp-connector { + compatible = "hdmi-connector"; + label = "hdmi"; + type = "a"; + + port { + hdmi_dp_connector_in: endpoint { + remote-endpoint = <&hdmi_dp_bridge_out>; + }; + }; + }; + + // hdmi-edp-connector { + // compatible = "hdmi-connector"; + // label = "hdmi"; + // type = "a"; + + // port { + // hdmi_edp_connector_in: endpoint { + // remote-endpoint = <&mdss_edp_out>; + // }; + // }; + // }; + dp-connector { + compatible = "dp-connector"; + label = "DP"; + type = "mini"; + + port { + hdmi_edp_connector_in: endpoint { + remote-endpoint = <&mdss_edp_out>; + }; + }; + }; + + leds { + compatible = "gpio-leds"; + + pinctrl-0 = <&user_led>; + pinctrl-names = "default"; + + user-led { + color = ; + function = LED_FUNCTION_STATUS; + gpios = <&tlmm 42 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + }; + }; + + reserved-memory { + lpass_ml_mem: lpass-ml@81800000 { + reg = <0x0 0x81800000 0x0 0xf00000>; + no-map; + }; + + cdsp_secure_heap_mem: cdsp-secure-heap@82700000 { + reg = <0x0 0x82700000 0x0 0x10000>; + no-map; + }; + + adsp_mem: adsp@8b800000 { + reg = <0x0 0x8b800000 0x0 0x2800000>; + no-map; + }; + + cdsp_mem: cdsp@8e000000 { + reg = <0x0 0x8e000000 0x0 0x1e00000>; + no-map; + }; + + video_mem: video@8fe00000 { + reg = <0x0 0x8fe00000 0x0 0x500000>; + no-map; + }; + + gpu_zap_mem: zap@90300000 { + reg = <0x0 0x90300000 0x0 0x5000>; + no-map; + }; + + tz_stat_mem: tz-stat@c0000000 { + reg = <0x0 0xc0000000 0x0 0x100000>; + no-map; + }; + + tags_mem: tags@c0100000 { + reg = <0x0 0xc0100000 0x0 0x1200000>; + no-map; + }; + + qtee_mem: qtee@c1300000 { + reg = <0x0 0xc1300000 0x0 0x500000>; + no-map; + }; + + trusted_apps_mem: trusted-apps@c1800000 { + reg = <0x0 0xc1800000 0x0 0x2200000>; + no-map; + }; + + adsp_rpc_remote_heap_mem: adsp-rpc-remote-heap@c6500000 { + reg = <0x0 0xc6500000 0x0 0x800000>; + no-map; + }; + }; + + thermal-zones { + msm-skin-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8350_adc_tm 2>; + }; + + quiet-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8350_adc_tm 1>; + }; + + ufs-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8350_adc_tm 3>; + }; + + xo-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&pmk8350_adc_tm 0>; + }; + }; + + vcc_1v8: regulator-vcc-1v8 { + compatible = "regulator-fixed"; + regulator-name = "vcc_1v8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + vin-supply = <&vreg_l18b_1p8>; + + regulator-boot-on; + regulator-always-on; + }; + + vcc_3v3: regulator-vcc-3v3 { + compatible = "regulator-fixed"; + regulator-name = "vcc_3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + vin-supply = <&vreg_l18b_1p8>; + + regulator-boot-on; + regulator-always-on; + }; + + vcc_3v3s: regulator-vcc-3v3s { + compatible = "regulator-fixed"; + regulator-name = "vcc_3v3s"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + vin-supply = <&vreg_l7b_2p96>; + + regulator-boot-on; + regulator-always-on; + }; + + vbus: regulator-vbus { + compatible = "regulator-fixed"; + regulator-name = "vbus"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + vin-supply = <&vcc_3v3>; + + regulator-boot-on; + regulator-always-on; + }; + + vph_pwr: regulator-vph-pwr { + compatible = "regulator-fixed"; + regulator-name = "vph_pwr"; + regulator-min-microvolt = <3700000>; + regulator-max-microvolt = <3700000>; + + regulator-boot-on; + regulator-always-on; + }; +}; + +&apps_rsc { + regulators-0 { + compatible = "qcom,pm7325-rpmh-regulators"; + qcom,pmic-id = "b"; + + vdd-s1-supply = <&vph_pwr>; + vdd-s2-supply = <&vph_pwr>; + vdd-s3-supply = <&vph_pwr>; + vdd-s4-supply = <&vph_pwr>; + vdd-s5-supply = <&vph_pwr>; + vdd-s6-supply = <&vph_pwr>; + vdd-s7-supply = <&vph_pwr>; + vdd-s8-supply = <&vph_pwr>; + vdd-l1-l4-l12-l15-supply = <&vreg_s7b_0p536>; + vdd-l2-l7-supply = <&vreg_bob_3p296>; + vdd-l6-l9-l10-supply = <&vreg_s8b_1p2>; + vdd-l11-l17-l18-l19-supply = <&vreg_s1b_1p84>; + + vreg_s1b_1p84: smps1 { + regulator-name = "vreg_s1b_1p84"; + regulator-min-microvolt = <1840000>; + regulator-max-microvolt = <2040000>; + }; + + vreg_s7b_0p536: smps7 { + regulator-name = "vreg_s7b_0p536"; + regulator-min-microvolt = <536000>; + regulator-max-microvolt = <1120000>; + }; + + vreg_s8b_1p2: smps8 { + regulator-name = "vreg_s8b_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1496000>; + regulator-initial-mode = ; + }; + + vreg_l1b_0p912: ldo1 { + regulator-name = "vreg_l1b_0p912"; + regulator-min-microvolt = <832000>; + regulator-max-microvolt = <920000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l2b_3p072: ldo2 { + regulator-name = "vreg_l2b_3p072"; + regulator-min-microvolt = <2704000>; + regulator-max-microvolt = <3544000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l6b_1p2: ldo6 { + regulator-name = "vreg_l6b_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1256000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l7b_2p96: ldo7 { + regulator-name = "vreg_l7b_2p96"; + regulator-min-microvolt = <2960000>; + regulator-max-microvolt = <2960000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l9b_1p2: ldo9 { + regulator-name = "vreg_l9b_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1304000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l17b_1p8: ldo17 { + regulator-name = "vreg_l17b_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1896000>; + regulator-initial-mode = ; + }; + + vreg_l18b_1p8: ldo18 { + regulator-name = "vreg_l18b_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <2000000>; + regulator-initial-mode = ; + regulator-always-on; + }; + + vreg_l19b_1p8: ldo19 { + regulator-name = "vreg_l19b_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <2000000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + }; + + regulators-1 { + compatible = "qcom,pm8350c-rpmh-regulators"; + qcom,pmic-id = "c"; + + vdd-s1-supply = <&vph_pwr>; + vdd-s2-supply = <&vph_pwr>; + vdd-s3-supply = <&vph_pwr>; + vdd-s4-supply = <&vph_pwr>; + vdd-s5-supply = <&vph_pwr>; + vdd-s6-supply = <&vph_pwr>; + vdd-s7-supply = <&vph_pwr>; + vdd-s8-supply = <&vph_pwr>; + vdd-s9-supply = <&vph_pwr>; + vdd-s10-supply = <&vph_pwr>; + vdd-l1-l12-supply = <&vreg_s1b_1p84>; + vdd-l6-l9-l11-supply = <&vreg_bob_3p296>; + vdd-l10-supply = <&vreg_s7b_0p536>; + vdd-bob-supply = <&vph_pwr>; + + vreg_l1c_1p8: ldo1 { + regulator-name = "vreg_l1c_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1976000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l6c_2p96: ldo6 { + regulator-name = "vreg_l6c_2p96"; + regulator-min-microvolt = <1650000>; + regulator-max-microvolt = <3544000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l9c_2p96: ldo9 { + regulator-name = "vreg_l9c_2p96"; + regulator-min-microvolt = <2704000>; + regulator-max-microvolt = <3544000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l10c_0p88: ldo10 { + regulator-name = "vreg_l10c_0p88"; + regulator-min-microvolt = <720000>; + regulator-max-microvolt = <1048000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_bob_3p296: bob { + regulator-name = "vreg_bob_3p296"; + regulator-min-microvolt = <3032000>; + regulator-max-microvolt = <3960000>; + }; + }; +}; + +&gcc { + protected-clocks = , + , + , + , + , + , + , + , + , + , + , + , + , + ; +}; + +&gpi_dma0 { + status = "okay"; +}; + +&gpi_dma1 { + status = "okay"; +}; + +&gpu { + status = "okay"; +}; + +&gpu_zap_shader { + firmware-name = "qcom/qcs6490/a660_zap.mbn"; +}; + +&i2c10 { + qcom,enable-gsi-dma; + status = "okay"; + + eeprom: eeprom@50 { + compatible = "belling,bl24c16f", "atmel,24c16"; + reg = <0x50>; + pagesize = <16>; + vcc-supply = <&vcc_3v3>; + }; + + rtc: rtc@68 { + compatible = "st,m41t11"; + reg = <0x68>; + }; +}; + +/* External touchscreen */ +&i2c13 { + qcom,enable-gsi-dma; + status = "okay"; +}; + +&lpass_audiocc { + compatible = "qcom,qcm6490-lpassaudiocc"; + /delete-property/ power-domains; +}; + +&lpass_rx_macro { + status = "okay"; +}; + +&lpass_tx_macro { + status = "okay"; +}; + +&lpass_va_macro { + status = "okay"; +}; + +&mdss { + status = "okay"; +}; + +&mdss_dp { + sound-name-prefix = "Display Port0"; + + status = "okay"; +}; + +&mdss_dp_out { + data-lanes = <0 1>; + remote-endpoint = <&usb_dp_qmpphy_dp_in>; +}; + +&mdss_edp { + status = "okay"; +}; + +&mdss_edp_out { + data-lanes = <0 1 2 3>; + link-frequencies = /bits/ 64 <1620000000 2700000000 5400000000 8100000000>; + + remote-endpoint = <&hdmi_edp_connector_in>; +}; + +&mdss_edp_phy { + status = "okay"; +}; + +&pcie0 { + perst-gpios = <&tlmm 87 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 89 GPIO_ACTIVE_HIGH>; + + pinctrl-0 = <&pcie0_clkreq_n>, <&pcie0_reset_n>, <&pcie0_wake_n>; + pinctrl-names = "default"; + + status = "okay"; +}; + +&pcie0_phy { + vdda-phy-supply = <&vreg_l10c_0p88>; + vdda-pll-supply = <&vreg_l6b_1p2>; + + status = "okay"; +}; + +&pcie1 { + perst-gpios = <&tlmm 2 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 3 GPIO_ACTIVE_HIGH>; + + pinctrl-0 = <&pcie1_clkreq_n>, <&pcie1_reset_n>, <&pcie1_wake_n>; + pinctrl-names = "default"; + + /* + * Support for many different bus topologies + * Tested devices: + * - QPS615 PCIe switch + * - ASM1182e PCIe switch + * - ASM2824 PCIe switch + * - NVIDIA dGPU + * - Chelsio T520-CR + */ + iommu-map = <0x0 &apps_smmu 0x1c80 0x1>, + <0x100 &apps_smmu 0x1c80 0x1>, + <0x101 &apps_smmu 0x1c80 0x1>, + <0x102 &apps_smmu 0x1c80 0x1>, + <0x103 &apps_smmu 0x1c80 0x1>, + <0x104 &apps_smmu 0x1c80 0x1>, + <0x105 &apps_smmu 0x1c80 0x1>, + <0x106 &apps_smmu 0x1c80 0x1>, + <0x200 &apps_smmu 0x1c80 0x1>, + <0x208 &apps_smmu 0x1c80 0x1>, + <0x210 &apps_smmu 0x1c80 0x1>, + <0x218 &apps_smmu 0x1c80 0x1>, + <0x220 &apps_smmu 0x1c80 0x1>, + <0x238 &apps_smmu 0x1c80 0x1>, + <0x240 &apps_smmu 0x1c80 0x1>, + <0x260 &apps_smmu 0x1c80 0x1>, + <0x300 &apps_smmu 0x1c80 0x1>, + <0x400 &apps_smmu 0x1c80 0x1>, + <0x500 &apps_smmu 0x1c80 0x1>, + <0x501 &apps_smmu 0x1c80 0x1>, + <0x600 &apps_smmu 0x1c80 0x1>; + + status = "okay"; +}; + +&pcie1_phy { + vdda-phy-supply = <&vreg_l10c_0p88>; + vdda-pll-supply = <&vreg_l6b_1p2>; + + status = "okay"; +}; + +&pm7325_gpios { + pinctrl-0 = <&hardware_version_sel>; + pinctrl-names = "default"; + gpio-line-names = "", /* GPIO_01 */ + "UFS_THERM", + "Version_ADC", + "", + "", + "KYPD_VOL_UP_N", + "Version_SEL", + "", + "MICRO USB DET", + ""; + + hardware_version_sel: hardware-version-sel-state { + pins = "gpio7"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-high-impedance; + power-source = <1>; + }; +}; + +&pm7325_temp_alarm { + io-channels = <&pmk8350_vadc PM7325_ADC7_DIE_TEMP>; + io-channel-names = "thermal"; +}; + +&pm8350c_gpios { + gpio-line-names = "", /* 1 */ + "", + "", + "", + "FAN_TACHO_IN", + "", + "LCD_BLEN", + "LCD_BLPWM", + "FAN_CTR_PWM"; + + fan_tacho: fan-tacho-state { + pins = "gpio5"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-disable; + power-source = <1>; + }; + + lcd_bl_en: lcd-bl-en-state { + pins = "gpio7"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-disable; + qcom,drive-strength = ; + output-low; + power-source = <1>; + }; + + fan_ctr_pwm: fan-ctr-pwm-state { + pins = "gpio9"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-disable; + qcom,drive-strength = ; + output-low; + power-source = <1>; + }; +}; + +&pm8350c_pwm { + status = "okay"; +}; + +&pmk8350_adc_tm { + status = "okay"; + + xo-therm@0 { + reg = <0>; + io-channels = <&pmk8350_vadc PMK8350_ADC7_AMUX_THM1_100K_PU>; + qcom,ratiometric; + qcom,hw-settle-time-us = <200>; + }; + + quiet-therm@1 { + reg = <1>; + io-channels = <&pmk8350_vadc PM7325_ADC7_AMUX_THM1_100K_PU>; + qcom,ratiometric; + qcom,hw-settle-time-us = <200>; + }; + + msm-skin-therm@2 { + reg = <2>; + io-channels = <&pmk8350_vadc PM7325_ADC7_AMUX_THM3_100K_PU>; + qcom,ratiometric; + qcom,hw-settle-time-us = <200>; + }; + + ufs-therm@3 { + reg = <3>; + io-channels = <&pmk8350_vadc PM7325_ADC7_GPIO1_100K_PU>; + qcom,ratiometric; + qcom,hw-settle-time-us = <200>; + }; +}; + +&pmk8350_vadc { + channel@3 { + reg = ; + label = "pmk7325_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@44 { + reg = ; + label = "xo_therm"; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + qcom,ratiometric; + }; + + channel@103 { + reg = ; + label = "pm7325_die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@10b { + reg = ; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + label = "hardware_version_adc"; + }; + + channel@144 { + reg = ; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + label = "quiet_therm"; + }; + + channel@146 { + reg = ; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + label = "msm_skin_therm"; + }; + + channel@14a { + /* According to datasheet, 0x4a = AMUX1_GPIO = GPIO_02 */ + reg = ; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + label = "ufs_therm"; + }; +}; + +&pon_pwrkey { + status = "okay"; +}; + +&qupv3_id_0 { + firmware-name = "qcom/qcm6490/qupv3fw.elf"; + status = "okay"; +}; + +&qupv3_id_1 { + firmware-name = "qcom/qcm6490/qupv3fw.elf"; + status = "okay"; +}; + +&remoteproc_adsp { + firmware-name = "qcom/qcs6490/radxa/dragon-q6a/adsp.mbn"; + status = "okay"; +}; + +&remoteproc_cdsp { + firmware-name = "qcom/qcs6490/radxa/dragon-q6a/cdsp.mbn"; + status = "okay"; +}; + +&sdhc_1 { + non-removable; + no-sd; + no-sdio; + + vmmc-supply = <&vreg_l7b_2p96>; + vqmmc-supply = <&vreg_l19b_1p8>; + + status = "okay"; +}; + +&sdhc_2 { + pinctrl-0 = <&sdc2_clk>, <&sdc2_cmd>, <&sdc2_data>, <&sd_cd>; + pinctrl-1 = <&sdc2_clk_sleep>, <&sdc2_cmd_sleep>, <&sdc2_data_sleep>, <&sd_cd>; + + vmmc-supply = <&vreg_l9c_2p96>; + vqmmc-supply = <&vreg_l6c_2p96>; + + broken-cd; + // cd-gpios = <&tlmm 91 GPIO_ACTIVE_LOW>; + status = "okay"; +}; + +&sound { + compatible = "qcom,qcs6490-rb3gen2-sndcard"; + model = "QCS6490-Radxa-Dragon-Q6A"; + + audio-routing = "IN1_HPHL", "HPHL_OUT", + "IN2_HPHR", "HPHR_OUT", + "AMIC2", "MIC BIAS2", + "TX SWR_ADC1", "ADC2_OUTPUT"; + + dp0-dai-link { + link-name = "DP0 Playback"; + + codec { + sound-dai = <&mdss_dp>; + }; + + cpu { + sound-dai = <&q6apmbedai DISPLAY_PORT_RX_0>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + + wcd-playback-dai-link { + link-name = "WCD Playback"; + + codec { + sound-dai = <&wcd938x 0>, <&swr0 0>, <&lpass_rx_macro 0>; + }; + + cpu { + sound-dai = <&q6apmbedai RX_CODEC_DMA_RX_0>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + + wcd-capture-dai-link { + link-name = "WCD Capture"; + + codec { + sound-dai = <&wcd938x 1>, <&swr1 0>, <&lpass_tx_macro 0>; + }; + + cpu { + sound-dai = <&q6apmbedai TX_CODEC_DMA_TX_3>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; +}; + +&swr0 { + status = "okay"; + + wcd_rx: codec@0,4 { + compatible = "sdw20217010d00"; + reg = <0 4>; + qcom,rx-port-mapping = <1 2 3 4 5>; + }; +}; + +&swr1 { + status = "okay"; + + wcd_tx: codec@0,3 { + compatible = "sdw20217010d00"; + reg = <0 3>; + qcom,tx-port-mapping = <1 1 2 3>; + }; +}; + +&tlmm { + /* + * 12-17: reserved for QSPI flash + */ + gpio-reserved-ranges = <12 6>; + gpio-line-names = + /* GPIO_0 ~ GPIO_3 */ + "PIN_13", "PIN_15", "", "", + /* GPIO_4 ~ GPIO_7 */ + "", "", "", "", + /* GPIO_8 ~ GPIO_11 */ + "PIN_27", "PIN_28", "", "", + /* GPIO_12 ~ GPIO_15 */ + "", "", "", "", + /* GPIO_16 ~ GPIO_19 */ + "", "", "", "", + /* GPIO_20 ~ GPIO_23 */ + "", "", "PIN_8", "PIN_10", + /* GPIO_24 ~ GPIO_27 */ + "PIN_3", "PIN_5", "PIN_16", "PIN_18", + /* GPIO_28 ~ GPIO_31 */ + "PIN_31", "PIN_11", "PIN_32", "PIN_29", + /* GPIO_32 ~ GPIO_35 */ + "", "", "", "", + /* GPIO_36 ~ GPIO_39 */ + "", "", "", "", + /* GPIO_40 ~ GPIO_43 */ + "", "", "", "", + /* GPIO_44 ~ GPIO_47 */ + "", "", "", "", + /* GPIO_48 ~ GPIO_51 */ + "PIN_21", "PIN_19", "PIN_23", "PIN_24", + /* GPIO_52 ~ GPIO_55 */ + "", "", "", "PIN_26", + /* GPIO_56 ~ GPIO_59 */ + "PIN_33", "PIN_22", "PIN_37", "PIN_36", + /* GPIO_60 ~ GPIO_63 */ + "", "", "", "", + /* GPIO_64 ~ GPIO_67 */ + "", "", "", "", + /* GPIO_68 ~ GPIO_71 */ + "", "", "", "", + /* GPIO_72 ~ GPIO_75 */ + "", "", "", "", + /* GPIO_76 ~ GPIO_79 */ + "", "", "", "", + /* GPIO_80 ~ GPIO_83 */ + "", "", "", "", + /* GPIO_84 ~ GPIO_87 */ + "", "", "", "", + /* GPIO_88 ~ GPIO_91 */ + "", "", "", "", + /* GPIO_92 ~ GPIO_95 */ + "", "", "", "", + /* GPIO_96 ~ GPIO_99 */ + "PIN_7", "PIN_12", "PIN_38", "PIN_40", + /* GPIO_100 ~ GPIO_103 */ + "PIN_35", "", "", "", + /* GPIO_104 ~ GPIO_107 */ + "", "", "", "", + /* GPIO_108 ~ GPIO_111 */ + "", "", "", "", + /* GPIO_112 ~ GPIO_115 */ + "", "", "", "", + /* GPIO_116 ~ GPIO_119 */ + "", "", "", "", + /* GPIO_120 ~ GPIO_123 */ + "", "", "", "", + /* GPIO_124 ~ GPIO_127 */ + "", "", "", "", + /* GPIO_128 ~ GPIO_131 */ + "", "", "", "", + /* GPIO_132 ~ GPIO_135 */ + "", "", "", "", + /* GPIO_136 ~ GPIO_139 */ + "", "", "", "", + /* GPIO_140 ~ GPIO_143 */ + "", "", "", "", + /* GPIO_144 ~ GPIO_147 */ + "", "", "", "", + /* GPIO_148 ~ GPIO_151 */ + "", "", "", "", + /* GPIO_152 ~ GPIO_155 */ + "", "", "", "", + /* GPIO_156 ~ GPIO_159 */ + "", "", "", "", + /* GPIO_160 ~ GPIO_163 */ + "", "", "", "", + /* GPIO_164 ~ GPIO_167 */ + "", "", "", "", + /* GPIO_168 ~ GPIO_171 */ + "", "", "", "", + /* GPIO_172 ~ GPIO_174 */ + "", "", ""; + + pcie0_reset_n: pcie0-reset-n-state { + pins = "gpio87"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + pcie0_wake_n: pcie0-wake-n-state { + pins = "gpio89"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + + pcie1_reset_n: pcie1-reset-n-state { + pins = "gpio2"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + pcie1_wake_n: pcie1-wake-n-state { + pins = "gpio3"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + + sd_cd: sd-cd-state { + pins = "gpio91"; + function = "gpio"; + bias-pull-up; + }; + + ufs_cd: ufs-cd-state { + pins = "gpio7"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + + user_led: user-led-state { + pins = "gpio42"; + function = "gpio"; + bias-pull-up; + }; + + wcd_default: wcd-reset-n-active-state { + pins = "gpio83"; + function = "gpio"; + drive-strength = <16>; + bias-disable; + output-low; + }; +}; + +&uart5 { + status = "okay"; +}; + +&ufs_mem_hc { + cd-gpios = <&tlmm 7 GPIO_ACTIVE_LOW>; + reset-gpios = <&tlmm 175 GPIO_ACTIVE_LOW>; + vcc-supply = <&vreg_l7b_2p96>; + vcc-max-microamp = <800000>; + vccq-supply = <&vreg_l9b_1p2>; + vccq-max-microamp = <900000>; + vccq2-supply = <&vreg_l9b_1p2>; + vccq2-max-microamp = <1300000>; + + pinctrl-0 = <&ufs_cd>; + pinctrl-names = "default"; + + /* Gear-4 Rate-B is unstable due to board */ + /* and UFS module design limitations */ + limit-gear-rate = "rate-a"; + + status = "okay"; +}; + +&ufs_mem_phy { + vdda-phy-supply = <&vreg_l10c_0p88>; + vdda-pll-supply = <&vreg_l6b_1p2>; + + status = "okay"; +}; + +&usb_1 { + dr_mode = "host"; + + status = "okay"; +}; + +&usb_1_dwc3_hs { + remote-endpoint = <&usb3_con_hs_in>; +}; + +&usb_1_hsphy { + vdda-pll-supply = <&vreg_l10c_0p88>; + vdda33-supply = <&vreg_l2b_3p072>; + vdda18-supply = <&vreg_l1c_1p8>; + + status = "okay"; +}; + +&usb_1_qmpphy { + vdda-phy-supply = <&vreg_l6b_1p2>; + vdda-pll-supply = <&vreg_l1b_0p912>; + + /delete-property/ orientation-switch; + + status = "okay"; + + ports { + port@0 { + #address-cells = <1>; + #size-cells = <0>; + + /delete-node/ endpoint; + + /* RX0/TX0 is statically connected to RA620 bridge */ + usb_1_qmpphy_out_dp: endpoint@0 { + reg = <0>; + + data-lanes = <0 1>; + remote-endpoint = <&hdmi_dp_bridge_in>; + }; + + /* RX1/TX1 is statically connected to USB-A port */ + usb_1_qmpphy_out_usb: endpoint@1 { + reg = <1>; + + data-lanes = <2 3>; + remote-endpoint = <&usb3_con_ss_in>; + }; + }; + }; +}; + +&usb_2 { + dr_mode = "host"; + + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + /* Onboard USB 2.0 hub */ + usb_hub_2_x: hub@1 { + compatible = "usb1a86:8091"; + reg = <1>; + vdd-supply = <&vcc_3v3>; + #address-cells = <1>; + #size-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + + usb_hub_2_1: endpoint { + remote-endpoint = <&usb2_1_connector>; + }; + }; + + port@2 { + reg = <2>; + + usb_hub_2_2: endpoint { + remote-endpoint = <&usb2_2_connector>; + }; + }; + + port@3 { + reg = <3>; + + usb_hub_2_3: endpoint { + remote-endpoint = <&usb2_3_connector>; + }; + }; + }; + + /* FCU760K Wi-Fi & Bluetooth module */ + wifi@4 { + compatible = "usba69c,8d80"; + reg = <4>; + }; + }; +}; + +&usb_2_hsphy { + vdda-pll-supply = <&vreg_l10c_0p88>; + vdda33-supply = <&vreg_l2b_3p072>; + vdda18-supply = <&vreg_l1c_1p8>; + + status = "okay"; +}; + +&venus { + status = "okay"; +}; + +/* PINCTRL - additions to nodes defined in sc7280.dtsi */ +&dp_hot_plug_det { + bias-disable; +}; + +/* PINCTRL - additions to nodes defined in sc7280.dtsi */ +&edp_hot_plug_det { + bias-disable; +}; + +&pcie0_clkreq_n { + bias-pull-up; + drive-strength = <2>; +}; + +&pcie1_clkreq_n { + bias-pull-up; + drive-strength = <2>; +}; + +&sdc1_clk { + bias-disable; + drive-strength = <16>; +}; + +&sdc1_cmd { + bias-pull-up; + drive-strength = <10>; +}; + +&sdc1_data { + bias-pull-up; + drive-strength = <10>; +}; + +&sdc1_rclk { + bias-pull-down; +}; + +&sdc2_clk { + bias-disable; + drive-strength = <16>; +}; + +&sdc2_cmd { + bias-pull-up; + drive-strength = <10>; +}; + +&sdc2_data { + bias-pull-up; + drive-strength = <10>; +}; diff --git a/arch/arm64/boot/dts/qcom/qcs6490-radxa-dragon-q6a.dts b/arch/arm64/boot/dts/qcom/qcs6490-radxa-dragon-q6a.dts index 518db4129c985..fb601015973dd 100644 --- a/arch/arm64/boot/dts/qcom/qcs6490-radxa-dragon-q6a.dts +++ b/arch/arm64/boot/dts/qcom/qcs6490-radxa-dragon-q6a.dts @@ -640,10 +640,24 @@ }; &pm7325_gpios { - pm7325_adc_default: adc-default-state { - pins = "gpio2"; + pinctrl-0 = <&hardware_version_sel>; + pinctrl-names = "default"; + gpio-line-names = "", /* GPIO_01 */ + "UFS_THERM", + "Version_ADC", + "", + "", + "KYPD_VOL_UP_N", + "Version_SEL", + "", + "MICRO USB DET", + ""; + + hardware_version_sel: hardware-version-sel-state { + pins = "gpio7"; function = PMIC_GPIO_FUNC_NORMAL; bias-high-impedance; + power-source = <1>; }; }; @@ -685,9 +699,6 @@ }; &pmk8350_vadc { - pinctrl-0 = <&pm7325_adc_default>; - pinctrl-names = "default"; - channel@3 { reg = ; label = "pmk7325_die_temp"; @@ -732,6 +743,14 @@ qcom,pre-scaling = <1 1>; label = "ufs_therm"; }; + + channel@14b { + reg = ; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + label = "hardware_version_adc"; + }; }; &pon_pwrkey { diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index b117e1b58e363..7e9a6c9525580 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -2039,6 +2039,7 @@ static const struct of_device_id qcom_scm_qseecom_allowlist[] __maybe_unused = { { .compatible = "qcom,x1e80100-crd" }, { .compatible = "qcom,x1e80100-qcp" }, { .compatible = "qcom,x1p42100-crd" }, + { .compatible = "radxa,cm-q64" }, { .compatible = "radxa,dragon-q6a" }, { } }; @@ -2318,6 +2319,7 @@ EXPORT_SYMBOL_GPL(qcom_scm_storage_send_cmd); * access on untested platforms. New platforms should be added here after validation. */ static const struct of_device_id qcom_scm_storage_allowlist[] = { + { .compatible = "radxa,cm-q64" }, { .compatible = "radxa,dragon-q6a" }, { } }; diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index c7dc03182e592..3641f0ff47352 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o +obj-$(CONFIG_DRM_LONTIUM_LT8712SX) += lontium-lt8712sx.o obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.o diff --git a/drivers/gpu/drm/bridge/lontium-lt8712sx.c b/drivers/gpu/drm/bridge/lontium-lt8712sx.c new file mode 100644 index 0000000000000..a2da205c7f4e7 --- /dev/null +++ b/drivers/gpu/drm/bridge/lontium-lt8712sx.c @@ -0,0 +1,1067 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Lontium LT8712SX DP/eDP to HDMI bridge driver + * + * The LT8712SX behaves like a mostly transparent bridge once valid + * firmware has been stored in its internal flash. Keep the DRM runtime + * model close to simple-bridge and integrate the optional I2C firmware + * update path into probe(). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define LT8712SX_PAGE_SIZE 256 +#define LT8712SX_MAIN_FW_SIZE SZ_64K +#define LT8712SX_MAIN_FW_PAYLOAD_SIZE (LT8712SX_MAIN_FW_SIZE - 1) +#define LT8712SX_BANK_FW_SIZE (12 * 1024) +#define LT8712SX_FLASH_BLOCK_SIZE 0x8000 +#define LT8712SX_FLASH_BLOCK_COUNT 8 +#define LT8712SX_MAX_FW_SIZE SZ_256K +#define LT8712SX_MAX_BANKS 16 + +#define LT8712SX_DEFAULT_FIRMWARE "LT8712SX.bin" +#define LT8713SX_DEFAULT_FIRMWARE "LT8713SX.bin" + +#define LT8712SX_POWER_ON_DELAY_MS 1000 +#define LT8712SX_MAIN_LOAD_DELAY_MS 200 +#define LT8712SX_BANK_LOAD_DELAY_MS 50 +#define LT8712SX_BLOCK_ERASE_DELAY_MS 100 +#define LT8712SX_STATUS_POLL_DELAY_MS 50 +#define LT8712SX_STATUS_POLL_RETRIES 50 + +struct lt8712sx_info { + unsigned int connector_type; + const char *firmware_name; +}; + +static const struct lt8712sx_info lt8712sx_hdmi_info = { + .connector_type = DRM_MODE_CONNECTOR_HDMIA, +}; + +static const struct lt8712sx_info lt8712sx_info = { + .connector_type = DRM_MODE_CONNECTOR_HDMIA, + .firmware_name = LT8712SX_DEFAULT_FIRMWARE, +}; + +static const struct lt8712sx_info lt8713sx_info = { + .connector_type = DRM_MODE_CONNECTOR_HDMIA, + .firmware_name = LT8713SX_DEFAULT_FIRMWARE, +}; + +struct lt8712sx_firmware { + u8 *data; + size_t size; + u8 main_crc; + u8 bank_crc[LT8712SX_MAX_BANKS]; + unsigned int bank_count; +}; + +struct lt8712sx { + struct drm_bridge bridge; + struct drm_connector connector; + + struct device *dev; + struct regmap *regmap; + const struct lt8712sx_info *info; + struct drm_bridge *next_bridge; + struct regulator *vdd; + struct gpio_desc *power_gpio; + struct gpio_desc *reset_gpio; + struct gpio_desc *enable_gpio; + struct mutex lock; + bool powered; +}; + +static inline struct lt8712sx *bridge_to_lt8712sx(struct drm_bridge *bridge) +{ + return container_of(bridge, struct lt8712sx, bridge); +} + +static inline struct lt8712sx *connector_to_lt8712sx( + struct drm_connector *connector) +{ + return container_of(connector, struct lt8712sx, connector); +} + +static const struct regmap_config lt8712sx_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .cache_type = REGCACHE_NONE, +}; + +static const struct reg_sequence lt8712sx_i2c_enable_seq[] = { + { 0xff, 0xe0 }, + { 0xee, 0x01 }, +}; + +static const struct reg_sequence lt8712sx_i2c_disable_seq[] = { + { 0xff, 0xe0 }, + { 0xee, 0x00 }, +}; + +static const struct reg_sequence lt8712sx_config_seq[] = { + { 0xff, 0xe0 }, + { 0xee, 0x01 }, + { 0x5e, 0xc1 }, + { 0x58, 0x00 }, + { 0x59, 0x50 }, + { 0x5a, 0x10 }, + { 0x5a, 0x00 }, + { 0x58, 0x21 }, +}; + +static const struct reg_sequence lt8712sx_wren_seq[] = { + { 0xff, 0xe1 }, + { 0x03, 0xbf }, + { 0x03, 0xff }, + { 0xff, 0xe0 }, + { 0x5a, 0x04 }, + { 0x5a, 0x00 }, +}; + +static const struct reg_sequence lt8712sx_wrdi_seq[] = { + { 0x5a, 0x08 }, + { 0x5a, 0x00 }, +}; + +static const struct reg_sequence lt8712sx_fifo_reset_seq[] = { + { 0xff, 0xe1 }, + { 0x03, 0xbf }, + { 0x03, 0xff }, +}; + +static const struct reg_sequence lt8712sx_disable_sram_write_seq[] = { + { 0xff, 0xe0 }, + { 0x55, 0x00 }, +}; + +static const struct reg_sequence lt8712sx_sram_to_flash_seq[] = { + { 0x5a, 0x30 }, + { 0x5a, 0x00 }, +}; + +static const struct reg_sequence lt8712sx_i2c_to_sram_seq[] = { + { 0x55, 0x80 }, + { 0x5e, 0xc0 }, + { 0x58, 0x21 }, +}; + +static int lt8712sx_write_seq(struct lt8712sx *lt8712sx, + const struct reg_sequence *seq, + size_t count) +{ + return regmap_multi_reg_write(lt8712sx->regmap, seq, count); +} + +static int lt8712sx_write(struct lt8712sx *lt8712sx, u8 reg, u8 value) +{ + return regmap_write(lt8712sx->regmap, reg, value); +} + +static int lt8712sx_read(struct lt8712sx *lt8712sx, u8 reg, u8 *value) +{ + unsigned int tmp; + int ret; + + ret = regmap_read(lt8712sx->regmap, reg, &tmp); + if (ret) + return ret; + + *value = tmp; + + return 0; +} + +static u8 lt8712sx_crc8_update(u8 crc, u8 value) +{ + int bit; + + crc ^= value; + + for (bit = 0; bit < 8; bit++) + crc = (crc & BIT(7)) ? (crc << 1) ^ 0x31 : crc << 1; + + return crc; +} + +static u8 lt8712sx_crc8_padded(const u8 *data, size_t len, size_t padded_len) +{ + u8 crc = 0; + size_t index; + + for (index = 0; index < len; index++) + crc = lt8712sx_crc8_update(crc, data[index]); + + for (; index < padded_len; index++) + crc = lt8712sx_crc8_update(crc, 0xff); + + return crc; +} + +static int lt8712sx_power_on(struct lt8712sx *lt8712sx) +{ + int ret; + + if (lt8712sx->powered) + return 0; + + if (lt8712sx->vdd) { + ret = regulator_enable(lt8712sx->vdd); + if (ret) + return ret; + } + + if (lt8712sx->power_gpio) + gpiod_set_value_cansleep(lt8712sx->power_gpio, 1); + + if (lt8712sx->reset_gpio) + gpiod_set_value_cansleep(lt8712sx->reset_gpio, 1); + + lt8712sx->powered = true; + + msleep(10); + + return 0; +} + +static void lt8712sx_power_off_action(void *data) +{ + struct lt8712sx *lt8712sx = data; + + mutex_lock(<8712sx->lock); + + if (lt8712sx->enable_gpio) + gpiod_set_value_cansleep(lt8712sx->enable_gpio, 0); + + if (lt8712sx->reset_gpio) + gpiod_set_value_cansleep(lt8712sx->reset_gpio, 0); + + if (lt8712sx->power_gpio) + gpiod_set_value_cansleep(lt8712sx->power_gpio, 0); + + if (lt8712sx->vdd && lt8712sx->powered) + regulator_disable(lt8712sx->vdd); + + lt8712sx->powered = false; + + mutex_unlock(<8712sx->lock); +} + +static int lt8712sx_hw_reset(struct lt8712sx *lt8712sx) +{ + if (!lt8712sx->reset_gpio) + return 0; + + gpiod_set_value_cansleep(lt8712sx->reset_gpio, 1); + msleep(5); + gpiod_set_value_cansleep(lt8712sx->reset_gpio, 0); + msleep(5); + gpiod_set_value_cansleep(lt8712sx->reset_gpio, 1); + msleep(5); + + return 0; +} + +static int lt8712sx_i2c_enable(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_i2c_enable_seq, + ARRAY_SIZE(lt8712sx_i2c_enable_seq)); +} + +static int lt8712sx_i2c_disable(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_i2c_disable_seq, + ARRAY_SIZE(lt8712sx_i2c_disable_seq)); +} + +static int lt8712sx_configure(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_config_seq, + ARRAY_SIZE(lt8712sx_config_seq)); +} + +static int lt8712sx_wren(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_wren_seq, + ARRAY_SIZE(lt8712sx_wren_seq)); +} + +static int lt8712sx_wrdi(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_wrdi_seq, + ARRAY_SIZE(lt8712sx_wrdi_seq)); +} + +static int lt8712sx_fifo_reset(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_fifo_reset_seq, + ARRAY_SIZE(lt8712sx_fifo_reset_seq)); +} + +static int lt8712sx_disable_sram_write(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, + lt8712sx_disable_sram_write_seq, + ARRAY_SIZE(lt8712sx_disable_sram_write_seq)); +} + +static int lt8712sx_sram_to_flash(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_sram_to_flash_seq, + ARRAY_SIZE(lt8712sx_sram_to_flash_seq)); +} + +static int lt8712sx_i2c_to_sram(struct lt8712sx *lt8712sx) +{ + return lt8712sx_write_seq(lt8712sx, lt8712sx_i2c_to_sram_seq, + ARRAY_SIZE(lt8712sx_i2c_to_sram_seq)); +} + +static int lt8712sx_flash_to_fifo(struct lt8712sx *lt8712sx, u32 address) +{ + int ret; + + ret = lt8712sx_write(lt8712sx, 0x5e, 0x40); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5a, 0x20); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5a, 0x00); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5b, FIELD_GET(0xff0000, address)); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5c, FIELD_GET(0x00ff00, address)); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5d, address & 0xff); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5a, 0x10); + if (ret) + return ret; + + return lt8712sx_write(lt8712sx, 0x5a, 0x00); +} + +static int lt8712sx_flash_read_status(struct lt8712sx *lt8712sx, u8 *status) +{ + static const struct reg_sequence seq[] = { + { 0xff, 0xe1 }, + { 0x03, 0x3f }, + { 0x03, 0xff }, + { 0xff, 0xe0 }, + { 0x5e, 0x40 }, + { 0x56, 0x05 }, + { 0x55, 0x25 }, + { 0x55, 0x01 }, + { 0x58, 0x21 }, + }; + int ret; + + ret = lt8712sx_write_seq(lt8712sx, seq, ARRAY_SIZE(seq)); + if (ret) + return ret; + + return lt8712sx_read(lt8712sx, 0x5f, status); +} + +static int lt8712sx_block_erase(struct lt8712sx *lt8712sx) +{ + unsigned int block; + int ret; + + for (block = 0; block < LT8712SX_FLASH_BLOCK_COUNT; block++) { + u32 address = block * LT8712SX_FLASH_BLOCK_SIZE; + u8 status = 0; + unsigned int retry; + + ret = lt8712sx_write(lt8712sx, 0xff, 0xe0); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0xee, 0x01); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5a, 0x04); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5a, 0x00); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5b, + FIELD_GET(0xff0000, address)); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5c, + FIELD_GET(0x00ff00, address)); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5d, address & 0xff); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5a, 0x01); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x5a, 0x00); + if (ret) + return ret; + + msleep(LT8712SX_BLOCK_ERASE_DELAY_MS); + + for (retry = 0; retry < LT8712SX_STATUS_POLL_RETRIES; retry++) { + ret = lt8712sx_flash_read_status(lt8712sx, &status); + if (ret) + return ret; + + if (!(status & BIT(0))) + break; + + msleep(LT8712SX_STATUS_POLL_DELAY_MS); + } + + if (status & BIT(0)) + return -ETIMEDOUT; + } + + return 0; +} + +static int lt8712sx_load_main_fw_to_sram(struct lt8712sx *lt8712sx) +{ + static const struct reg_sequence seq[] = { + { 0xff, 0xe0 }, + { 0xee, 0x01 }, + { 0x68, 0x00 }, + { 0x69, 0x00 }, + { 0x6a, 0x00 }, + { 0x65, 0x00 }, + { 0x66, 0xff }, + { 0x67, 0xff }, + { 0x6b, 0x00 }, + { 0x6c, 0x00 }, + { 0x60, 0x01 }, + }; + int ret; + + ret = lt8712sx_write_seq(lt8712sx, seq, ARRAY_SIZE(seq)); + if (ret) + return ret; + + msleep(LT8712SX_MAIN_LOAD_DELAY_MS); + + return lt8712sx_write(lt8712sx, 0x60, 0x00); +} + +static int lt8712sx_load_bank_fw_to_sram(struct lt8712sx *lt8712sx, + u32 address) +{ + int ret; + + ret = lt8712sx_write(lt8712sx, 0xff, 0xe0); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0xee, 0x01); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x68, FIELD_GET(0xff0000, address)); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x69, FIELD_GET(0x00ff00, address)); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x6a, address & 0xff); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x65, 0x00); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x66, 0x30); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x67, 0x00); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x6b, 0x00); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x6c, 0x00); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x60, 0x01); + if (ret) + return ret; + + msleep(LT8712SX_BANK_LOAD_DELAY_MS); + + return lt8712sx_write(lt8712sx, 0x60, 0x00); +} + +static int lt8712sx_write_firmware_pages(struct lt8712sx *lt8712sx, + const struct lt8712sx_firmware *fw) +{ + u8 page_buf[LT8712SX_PAGE_SIZE]; + unsigned int page_count; + unsigned int page; + int ret; + + page_count = DIV_ROUND_UP(fw->size, LT8712SX_PAGE_SIZE); + + for (page = 0; page < page_count; page++) { + size_t offset = page * LT8712SX_PAGE_SIZE; + size_t copy_len = min_t(size_t, fw->size - offset, + LT8712SX_PAGE_SIZE); + + memset(page_buf, 0xff, sizeof(page_buf)); + memcpy(page_buf, fw->data + offset, copy_len); + + ret = lt8712sx_i2c_to_sram(lt8712sx); + if (ret) + return ret; + + ret = regmap_noinc_write(lt8712sx->regmap, 0x59, page_buf, + sizeof(page_buf)); + if (ret) + return ret; + + ret = lt8712sx_wren(lt8712sx); + if (ret) + return ret; + + ret = lt8712sx_sram_to_flash(lt8712sx); + if (ret) + return ret; + } + + ret = lt8712sx_wrdi(lt8712sx); + if (ret) + return ret; + + return lt8712sx_disable_sram_write(lt8712sx); +} + +static int lt8712sx_needs_firmware_update(struct lt8712sx *lt8712sx, + u8 expected_crc, + bool *needs_update) +{ + u8 flash_crc; + int ret; + + ret = lt8712sx_configure(lt8712sx); + if (ret) + return ret; + + ret = lt8712sx_flash_to_fifo(lt8712sx, LT8712SX_MAIN_FW_PAYLOAD_SIZE); + if (ret) + return ret; + + ret = lt8712sx_write(lt8712sx, 0x58, 0x21); + if (ret) + return ret; + + ret = lt8712sx_read(lt8712sx, 0x5f, &flash_crc); + if (ret) + return ret; + + ret = lt8712sx_wrdi(lt8712sx); + if (ret) + return ret; + + ret = lt8712sx_fifo_reset(lt8712sx); + if (ret) + return ret; + + *needs_update = flash_crc != expected_crc; + + return 0; +} + +static int lt8712sx_verify_main_firmware(struct lt8712sx *lt8712sx, + u8 expected_crc) +{ + u8 actual_crc; + int ret; + + ret = lt8712sx_read(lt8712sx, 0x23, &actual_crc); + if (ret) + return ret; + + if (actual_crc != expected_crc) + return -EIO; + + return 0; +} + +static int lt8712sx_verify_bank_firmware(struct lt8712sx *lt8712sx, + const struct lt8712sx_firmware *fw) +{ + unsigned int bank; + int ret; + + for (bank = 0; bank < fw->bank_count; bank++) { + u8 actual_crc; + u32 address = 0x010000 + bank * LT8712SX_BANK_FW_SIZE; + + ret = lt8712sx_load_bank_fw_to_sram(lt8712sx, address); + if (ret) + return ret; + + ret = lt8712sx_read(lt8712sx, 0x23, &actual_crc); + if (ret) + return ret; + + if (actual_crc != fw->bank_crc[bank]) + return -EIO; + } + + return 0; +} + +static int lt8712sx_prepare_firmware(const struct firmware *firmware, + struct lt8712sx_firmware *prepared) +{ + size_t bank_payload_size; + unsigned int bank; + + if (firmware->size > LT8712SX_MAX_FW_SIZE - 1) + return -EFBIG; + + prepared->data = kzalloc(LT8712SX_MAX_FW_SIZE, GFP_KERNEL); + if (!prepared->data) + return -ENOMEM; + + memset(prepared->data, 0xff, LT8712SX_MAX_FW_SIZE); + + if (firmware->size < LT8712SX_MAIN_FW_SIZE) { + memcpy(prepared->data, firmware->data, firmware->size); + prepared->main_crc = lt8712sx_crc8_padded(firmware->data, + firmware->size, + LT8712SX_MAIN_FW_PAYLOAD_SIZE); + prepared->data[LT8712SX_MAIN_FW_PAYLOAD_SIZE] = prepared->main_crc; + prepared->size = LT8712SX_MAIN_FW_SIZE; + + return 0; + } + + memcpy(prepared->data, firmware->data, LT8712SX_MAIN_FW_PAYLOAD_SIZE); + prepared->main_crc = lt8712sx_crc8_padded(prepared->data, + LT8712SX_MAIN_FW_PAYLOAD_SIZE, + LT8712SX_MAIN_FW_PAYLOAD_SIZE); + prepared->data[LT8712SX_MAIN_FW_PAYLOAD_SIZE] = prepared->main_crc; + + if (firmware->size > LT8712SX_MAIN_FW_SIZE) + memcpy(prepared->data + LT8712SX_MAIN_FW_SIZE, + firmware->data + LT8712SX_MAIN_FW_SIZE, + firmware->size - LT8712SX_MAIN_FW_SIZE); + + prepared->size = firmware->size; + prepared->bank_count = DIV_ROUND_UP(firmware->size - LT8712SX_MAIN_FW_SIZE, + LT8712SX_BANK_FW_SIZE); + if (prepared->bank_count > LT8712SX_MAX_BANKS) + return -EINVAL; + + bank_payload_size = firmware->size - LT8712SX_MAIN_FW_SIZE; + + for (bank = 0; bank < prepared->bank_count; bank++) { + size_t offset = LT8712SX_MAIN_FW_SIZE + bank * LT8712SX_BANK_FW_SIZE; + size_t valid = 0; + + if (bank * LT8712SX_BANK_FW_SIZE < bank_payload_size) + valid = min_t(size_t, + bank_payload_size - bank * LT8712SX_BANK_FW_SIZE, + LT8712SX_BANK_FW_SIZE); + + prepared->bank_crc[bank] = + lt8712sx_crc8_padded(prepared->data + offset, valid, + LT8712SX_BANK_FW_SIZE); + } + + return 0; +} + +static int lt8712sx_program_firmware(struct lt8712sx *lt8712sx, + const struct lt8712sx_firmware *fw) +{ + bool needs_update; + bool updated = false; + int ret; + + mutex_lock(<8712sx->lock); + + msleep(LT8712SX_POWER_ON_DELAY_MS); + + ret = lt8712sx_i2c_enable(lt8712sx); + if (ret) + goto unlock; + + ret = lt8712sx_needs_firmware_update(lt8712sx, fw->main_crc, + &needs_update); + if (ret) + goto disable_i2c; + + if (!needs_update) { + ret = 0; + goto disable_i2c; + } + + ret = lt8712sx_configure(lt8712sx); + if (ret) + goto disable_i2c; + + ret = lt8712sx_block_erase(lt8712sx); + if (ret) + goto disable_i2c; + + ret = lt8712sx_write_firmware_pages(lt8712sx, fw); + if (ret) + goto disable_i2c; + + ret = lt8712sx_load_main_fw_to_sram(lt8712sx); + if (ret) + goto disable_i2c; + + ret = lt8712sx_verify_main_firmware(lt8712sx, fw->main_crc); + if (ret) + goto disable_i2c; + + ret = lt8712sx_verify_bank_firmware(lt8712sx, fw); + if (ret) + goto disable_i2c; + + ret = lt8712sx_wrdi(lt8712sx); + if (ret) + goto disable_i2c; + + updated = true; + +disable_i2c: + if (lt8712sx_i2c_disable(lt8712sx) && !ret) + ret = -EIO; + + if (lt8712sx_hw_reset(lt8712sx) && !ret) + ret = -EIO; + +unlock: + mutex_unlock(<8712sx->lock); + + if (ret) + return ret; + + return updated; +} + +static void lt8712sx_try_optional_firmware(struct lt8712sx *lt8712sx) +{ + struct lt8712sx_firmware prepared = {}; + const struct firmware *firmware; + const char *firmware_name = lt8712sx->info->firmware_name; + int ret; + + ret = device_property_read_string(lt8712sx->dev, "firmware-name", + &firmware_name); + if (ret) + firmware_name = lt8712sx->info->firmware_name; + + if (!firmware_name) + return; + + ret = firmware_request_nowarn(&firmware, firmware_name, lt8712sx->dev); + if (ret) { + dev_info(lt8712sx->dev, + "optional firmware %s unavailable (%d), continuing\n", + firmware_name, ret); + return; + } + + ret = lt8712sx_prepare_firmware(firmware, &prepared); + if (ret) { + dev_warn(lt8712sx->dev, + "failed to prepare firmware %s (%d), continuing\n", + firmware_name, ret); + goto free_prepared; + } + + ret = lt8712sx_program_firmware(lt8712sx, &prepared); + if (ret < 0) { + dev_warn(lt8712sx->dev, + "failed to update firmware %s (%d), continuing\n", + firmware_name, ret); + } else if (ret > 0) { + dev_info(lt8712sx->dev, "updated firmware from %s\n", + firmware_name); + } else { + dev_dbg(lt8712sx->dev, "firmware %s already matches flash\n", + firmware_name); + } + + /* Fall through so partially prepared buffers also get cleaned up. */ + + free_prepared: + kfree(prepared.data); + release_firmware(firmware); +} + +static int lt8712sx_connector_get_modes(struct drm_connector *connector) +{ + struct lt8712sx *lt8712sx = connector_to_lt8712sx(connector); + const struct drm_edid *drm_edid; + int ret; + + if (lt8712sx->next_bridge->ops & DRM_BRIDGE_OP_EDID) { + drm_edid = drm_bridge_edid_read(lt8712sx->next_bridge, connector); + if (!drm_edid) + DRM_INFO("EDID read failed. Fallback to standard modes\n"); + } else { + drm_edid = NULL; + } + + drm_edid_connector_update(connector, drm_edid); + + if (!drm_edid) { + /* + * If the downstream HDMI connector does not expose DDC to the SoC, + * keep the same no-EDID fallback used by simple-bridge. + */ + ret = drm_add_modes_noedid(connector, 1920, 1200); + drm_set_preferred_mode(connector, 1024, 768); + return ret; + } + + ret = drm_edid_connector_add_modes(connector); + drm_edid_free(drm_edid); + + return ret; +} + +static const struct drm_connector_helper_funcs lt8712sx_con_helper_funcs = { + .get_modes = lt8712sx_connector_get_modes, +}; + +static enum drm_connector_status +lt8712sx_connector_detect(struct drm_connector *connector, bool force) +{ + struct lt8712sx *lt8712sx = connector_to_lt8712sx(connector); + + return drm_bridge_detect(lt8712sx->next_bridge, connector); +} + +static const struct drm_connector_funcs lt8712sx_con_funcs = { + .detect = lt8712sx_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int lt8712sx_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct lt8712sx *lt8712sx = bridge_to_lt8712sx(bridge); + int ret; + + ret = drm_bridge_attach(encoder, lt8712sx->next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return ret; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return 0; + + drm_connector_helper_add(<8712sx->connector, + <8712sx_con_helper_funcs); + + ret = drm_connector_init_with_ddc(bridge->dev, <8712sx->connector, + <8712sx_con_funcs, + lt8712sx->info->connector_type, + lt8712sx->next_bridge->ddc); + if (ret) { + DRM_ERROR("Failed to initialize connector\n"); + return ret; + } + + drm_connector_attach_encoder(<8712sx->connector, encoder); + + return 0; +} + +static void lt8712sx_enable(struct drm_bridge *bridge) +{ + struct lt8712sx *lt8712sx = bridge_to_lt8712sx(bridge); + + if (lt8712sx->enable_gpio) + gpiod_set_value_cansleep(lt8712sx->enable_gpio, 1); +} + +static void lt8712sx_disable(struct drm_bridge *bridge) +{ + struct lt8712sx *lt8712sx = bridge_to_lt8712sx(bridge); + + if (lt8712sx->enable_gpio) + gpiod_set_value_cansleep(lt8712sx->enable_gpio, 0); +} + +static const struct drm_bridge_funcs lt8712sx_bridge_funcs = { + .attach = lt8712sx_attach, + .enable = lt8712sx_enable, + .disable = lt8712sx_disable, +}; + +static int lt8712sx_probe(struct i2c_client *client) +{ + struct lt8712sx *lt8712sx; + struct device *dev = &client->dev; + struct device_node *remote; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENXIO; + + lt8712sx = devm_drm_bridge_alloc(dev, struct lt8712sx, bridge, + <8712sx_bridge_funcs); + if (IS_ERR(lt8712sx)) + return PTR_ERR(lt8712sx); + + lt8712sx->dev = dev; + lt8712sx->info = i2c_get_match_data(client); + if (!lt8712sx->info) + lt8712sx->info = <8712sx_hdmi_info; + + mutex_init(<8712sx->lock); + i2c_set_clientdata(client, lt8712sx); + + lt8712sx->regmap = devm_regmap_init_i2c(client, <8712sx_regmap_config); + if (IS_ERR(lt8712sx->regmap)) + return dev_err_probe(dev, PTR_ERR(lt8712sx->regmap), + "failed to initialize regmap\n"); + + remote = of_graph_get_remote_node(dev->of_node, 1, -1); + if (!remote) + return dev_err_probe(dev, -ENODEV, "port@1 is unconnected\n"); + + lt8712sx->next_bridge = of_drm_find_bridge(remote); + of_node_put(remote); + if (!lt8712sx->next_bridge) { + dev_dbg(dev, "next bridge not found, deferring probe\n"); + return -EPROBE_DEFER; + } + + lt8712sx->vdd = devm_regulator_get_optional(dev, "vdd"); + if (IS_ERR(lt8712sx->vdd)) { + ret = PTR_ERR(lt8712sx->vdd); + if (ret == -EPROBE_DEFER) + return -EPROBE_DEFER; + + lt8712sx->vdd = NULL; + dev_dbg(dev, "no vdd regulator found: %d\n", ret); + } + + lt8712sx->power_gpio = devm_gpiod_get_optional(dev, "power", + GPIOD_OUT_LOW); + if (IS_ERR(lt8712sx->power_gpio)) + return dev_err_probe(dev, PTR_ERR(lt8712sx->power_gpio), + "failed to get power GPIO\n"); + + lt8712sx->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(lt8712sx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(lt8712sx->reset_gpio), + "failed to get reset GPIO\n"); + + lt8712sx->enable_gpio = devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(lt8712sx->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(lt8712sx->enable_gpio), + "failed to get enable GPIO\n"); + + ret = lt8712sx_power_on(lt8712sx); + if (ret) + return dev_err_probe(dev, ret, "failed to power on bridge\n"); + + ret = devm_add_action_or_reset(dev, lt8712sx_power_off_action, lt8712sx); + if (ret) + return ret; + + lt8712sx_try_optional_firmware(lt8712sx); + + lt8712sx->bridge.of_node = dev->of_node; + lt8712sx->bridge.type = lt8712sx->info->connector_type; + + return devm_drm_bridge_add(dev, <8712sx->bridge); +} + +static const struct of_device_id lt8712sx_of_match[] = { + { + .compatible = "lontium,lt8712sx", + .data = <8712sx_info, + }, { + .compatible = "lontium,lt8713sx", + .data = <8713sx_info, + }, {} +}; +MODULE_DEVICE_TABLE(of, lt8712sx_of_match); + +static const struct i2c_device_id lt8712sx_i2c_ids[] = { + { "lt8712sx", (kernel_ulong_t)<8712sx_info }, + { "lt8713sx", (kernel_ulong_t)<8713sx_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lt8712sx_i2c_ids); + +static struct i2c_driver lt8712sx_driver = { + .probe = lt8712sx_probe, + .id_table = lt8712sx_i2c_ids, + .driver = { + .name = "lontium-lt8712sx", + .of_match_table = lt8712sx_of_match, + }, +}; +module_i2c_driver(lt8712sx_driver); + +MODULE_AUTHOR("Chen Jiali"); +MODULE_DESCRIPTION("Lontium LT8712SX DP/eDP to HDMI bridge driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(LT8712SX_DEFAULT_FIRMWARE); +MODULE_FIRMWARE(LT8713SX_DEFAULT_FIRMWARE); diff --git a/drivers/gpu/drm/bridge/tc358762.c b/drivers/gpu/drm/bridge/tc358762.c index 98df3e667d4aa..02f94ce98ca03 100644 --- a/drivers/gpu/drm/bridge/tc358762.c +++ b/drivers/gpu/drm/bridge/tc358762.c @@ -278,7 +278,7 @@ static int tc358762_probe(struct mipi_dsi_device *dsi) /* TODO: Find out how to get dual-lane mode working */ dsi->lanes = 1; dsi->format = MIPI_DSI_FMT_RGB888; - dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_VIDEO_HSE; ret = tc358762_parse_dt(ctx); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index a5fcb4ba527ad..8c7e950addd6c 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -912,6 +912,48 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) return 0; } +static const struct msm_dp_bridge_mode_filter { + const char *compatible; + u32 max_pixels; + u32 max_vrefresh; +} msm_dp_bridge_mode_filters[] = { + { "radxa,dragon-q6a", 3440 * 1440, 30 }, + {} +}; + +static bool msm_dp_bridge_mode_filter_valid(const struct drm_display_mode *mode) +{ + const struct msm_dp_bridge_mode_filter *filter; + struct device_node *np; + int vrefresh; + u32 pixels; + + np = of_find_node_by_path("/"); + if (!np) + return true; + + pixels = mode->hdisplay * mode->vdisplay; + vrefresh = drm_mode_vrefresh(mode); + + for (filter = msm_dp_bridge_mode_filters; filter->compatible; filter++) { + if (!of_device_is_compatible(np, filter->compatible)) + continue; + + if (pixels <= filter->max_pixels || + vrefresh <= filter->max_vrefresh) + continue; + + DRM_INFO("mode filtered by %s: %ux%u@%u\n", + filter->compatible, mode->hdisplay, + mode->vdisplay, vrefresh); + of_node_put(np); + return false; + } + + of_node_put(np); + return true; +} + /** * msm_dp_bridge_mode_valid - callback to determine if specified mode is valid * @bridge: Pointer to drm bridge structure @@ -929,7 +971,6 @@ enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; struct msm_dp *dp; int mode_pclk_khz = mode->clock; - int vrefresh = drm_mode_vrefresh(mode); dp = to_dp_bridge(bridge)->msm_dp_display; @@ -962,7 +1003,7 @@ enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, if (mode_rate_khz > supported_rate_khz) return MODE_BAD; - if ((mode->hdisplay * mode->vdisplay > 3440 * 1440) && vrefresh > 30) + if (!msm_dp_bridge_mode_filter_valid(mode)) return MODE_BAD; return MODE_OK; diff --git a/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c b/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c index c5e1d2016bcca..00ae46848de15 100644 --- a/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c +++ b/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c @@ -1332,6 +1332,7 @@ const struct msm_dsi_phy_cfg dsi_phy_7nm_7280_cfgs = { .pll_init = dsi_pll_7nm_init, .save_pll_state = dsi_7nm_pll_save_state, .restore_pll_state = dsi_7nm_pll_restore_state, + .set_continuous_clock = dsi_7nm_set_continuous_clock, }, .min_pll_rate = 600000000UL, #ifdef CONFIG_64BIT diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 0019de93be1b6..48502634b1e42 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -160,8 +160,6 @@ struct panel_simple { const struct drm_edid *drm_edid; struct drm_display_mode override_mode; - - enum drm_panel_orientation orientation; }; static inline struct panel_simple *to_panel_simple(struct drm_panel *panel) @@ -396,12 +394,6 @@ static int panel_simple_get_modes(struct drm_panel *panel, /* add hard-coded panel modes */ num += panel_simple_get_non_edid_modes(p, connector); - /* - * TODO: Remove once all drm drivers call - * drm_connector_set_orientation_from_panel() - */ - drm_connector_set_panel_orientation(connector, p->orientation); - return num; } @@ -422,20 +414,12 @@ static int panel_simple_get_timings(struct drm_panel *panel, return p->desc->num_timings; } -static enum drm_panel_orientation panel_simple_get_orientation(struct drm_panel *panel) -{ - struct panel_simple *p = to_panel_simple(panel); - - return p->orientation; -} - static const struct drm_panel_funcs panel_simple_funcs = { .disable = panel_simple_disable, .unprepare = panel_simple_unprepare, .prepare = panel_simple_prepare, .enable = panel_simple_enable, .get_modes = panel_simple_get_modes, - .get_orientation = panel_simple_get_orientation, .get_timings = panel_simple_get_timings, }; @@ -469,6 +453,7 @@ static struct panel_desc *panel_dpi_probe(struct device *dev) of_property_read_u32(np, "width-mm", &desc->size.width); of_property_read_u32(np, "height-mm", &desc->size.height); + of_property_read_u32(np, "bus-format", &desc->bus_format); /* Extract bus_flags from display_timing */ bus_flags = 0; @@ -478,6 +463,8 @@ static struct panel_desc *panel_dpi_probe(struct device *dev) /* We do not know the connector for the DT node, so guess it */ desc->connector_type = DRM_MODE_CONNECTOR_DPI; + /* Likewise for the bit depth. */ + desc->bpc = 8; return desc; } @@ -640,12 +627,6 @@ static struct panel_simple *panel_simple_probe(struct device *dev) return dev_err_cast_probe(dev, panel->enable_gpio, "failed to request GPIO\n"); - err = of_drm_get_panel_orientation(dev->of_node, &panel->orientation); - if (err) { - dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, err); - return ERR_PTR(err); - } - ddc = of_parse_phandle(dev->of_node, "ddc-i2c-bus", 0); if (ddc) { panel->ddc = of_find_i2c_adapter_by_node(ddc); @@ -2381,6 +2362,32 @@ static const struct panel_desc friendlyarm_hd702e = { }, }; +static const struct drm_display_mode geekworm_mzp280_mode = { + .clock = 32000, + .hdisplay = 480, + .hsync_start = 480 + 41, + .hsync_end = 480 + 41 + 20, + .htotal = 480 + 41 + 20 + 60, + .vdisplay = 640, + .vsync_start = 640 + 5, + .vsync_end = 640 + 5 + 10, + .vtotal = 640 + 5 + 10 + 10, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc geekworm_mzp280 = { + .modes = &geekworm_mzp280_mode, + .num_modes = 1, + .bpc = 6, + .size = { + .width = 47, + .height = 61, + }, + .bus_format = MEDIA_BUS_FMT_RGB565_1X24_CPADHI, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + static const struct drm_display_mode giantplus_gpg482739qs5_mode = { .clock = 9000, .hdisplay = 480, @@ -2561,6 +2568,38 @@ static const struct panel_desc innolux_at043tn24 = { .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, }; +static const struct display_timing innolux_at056tn53v1_timing = { + .pixelclock = { 39700000, 39700000, 39700000}, + .hactive = { 640, 640, 640 }, + .hfront_porch = { 16, 16, 16 }, + .hback_porch = { 134, 134, 134 }, + .hsync_len = { 10, 10, 10}, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 32, 32, 32}, + .vback_porch = { 11, 11, 11 }, + .vsync_len = { 2, 2, 2 }, + .flags = DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_PHSYNC, +}; + +static const struct panel_desc innolux_at056tn53v1 = { + .timings = &innolux_at056tn53v1_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 112, + .height = 84, + }, + .delay = { + .prepare = 50, + .enable = 200, + .disable = 110, + .unprepare = 200, + }, + .bus_format = MEDIA_BUS_FMT_BGR666_1X24_CPADHI, + .bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + static const struct drm_display_mode innolux_at070tn92_mode = { .clock = 33333, .hdisplay = 800, @@ -4101,6 +4140,31 @@ static const struct panel_desc rocktech_rk043fn48h = { .connector_type = DRM_MODE_CONNECTOR_DPI, }; +static const struct drm_display_mode raspberrypi_7inch_mode = { + .clock = 30000, + .hdisplay = 800, + .hsync_start = 800 + 131, + .hsync_end = 800 + 131 + 2, + .htotal = 800 + 131 + 2 + 45, + .vdisplay = 480, + .vsync_start = 480 + 7, + .vsync_end = 480 + 7 + 2, + .vtotal = 480 + 7 + 2 + 22, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static const struct panel_desc raspberrypi_7inch = { + .modes = &raspberrypi_7inch_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 154, + .height = 86, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .connector_type = DRM_MODE_CONNECTOR_DSI, +}; + static const struct display_timing rocktech_rk070er9427_timing = { .pixelclock = { 26400000, 33300000, 46800000 }, .hactive = { 800, 800, 800 }, @@ -5151,6 +5215,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "friendlyarm,hd702e", .data = &friendlyarm_hd702e, + }, { + .compatible = "geekworm,mzp280", + .data = &geekworm_mzp280, }, { .compatible = "giantplus,gpg482739qs5", .data = &giantplus_gpg482739qs5 @@ -5172,6 +5239,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "innolux,at043tn24", .data = &innolux_at043tn24, + }, { + .compatible = "innolux,at056tn53v1", + .data = &innolux_at056tn53v1, }, { .compatible = "innolux,at070tn92", .data = &innolux_at070tn92, @@ -5346,6 +5416,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "rocktech,rk043fn48h", .data = &rocktech_rk043fn48h, + }, { + .compatible = "raspberrypi,7inch-dsi", + .data = &raspberrypi_7inch, }, { .compatible = "rocktech,rk070er9427", .data = &rocktech_rk070er9427, @@ -5709,6 +5782,9 @@ static const struct panel_desc_dsi osd101t2045_53ts = { .lanes = 4, }; +// for panels using generic panel-dsi binding +static struct panel_desc_dsi panel_dsi; + static const struct of_device_id dsi_of_match[] = { { .compatible = "auo,b080uan01", @@ -5731,12 +5807,113 @@ static const struct of_device_id dsi_of_match[] = { }, { .compatible = "osddisplays,osd101t2045-53ts", .data = &osd101t2045_53ts + }, { + /* Must be the last entry */ + .compatible = "panel-dsi", + .data = &panel_dsi, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, dsi_of_match); + +/* Checks for DSI panel definition in device-tree, analog to panel_dpi */ +static int panel_dsi_dt_probe(struct device *dev, + struct panel_desc_dsi *desc_dsi) +{ + struct panel_desc *desc; + struct display_timing *timing; + const struct device_node *np; + const char *dsi_color_format; + const char *dsi_mode_flags; + struct property *prop; + int dsi_lanes, ret; + + np = dev->of_node; + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + timing = devm_kzalloc(dev, sizeof(*timing), GFP_KERNEL); + if (!timing) + return -ENOMEM; + + ret = of_get_display_timing(np, "panel-timing", timing); + if (ret < 0) { + dev_err(dev, "%pOF: no panel-timing node found for \"panel-dsi\" binding\n", + np); + return ret; + } + + desc->timings = timing; + desc->num_timings = 1; + + of_property_read_u32(np, "width-mm", &desc->size.width); + of_property_read_u32(np, "height-mm", &desc->size.height); + + dsi_lanes = drm_of_get_data_lanes_count_ep(np, 0, 0, 1, 4); + + if (dsi_lanes < 0) { + dev_err(dev, "%pOF: no or too many data-lanes defined", np); + return dsi_lanes; + } + + desc_dsi->lanes = dsi_lanes; + + of_property_read_string(np, "dsi-color-format", &dsi_color_format); + if (!strcmp(dsi_color_format, "RGB888")) { + desc_dsi->format = MIPI_DSI_FMT_RGB888; + desc->bpc = 8; + } else if (!strcmp(dsi_color_format, "RGB565")) { + desc_dsi->format = MIPI_DSI_FMT_RGB565; + desc->bpc = 6; + } else if (!strcmp(dsi_color_format, "RGB666")) { + desc_dsi->format = MIPI_DSI_FMT_RGB666; + desc->bpc = 6; + } else if (!strcmp(dsi_color_format, "RGB666_PACKED")) { + desc_dsi->format = MIPI_DSI_FMT_RGB666_PACKED; + desc->bpc = 6; + } else { + dev_err(dev, "%pOF: no valid dsi-color-format defined", np); + return -EINVAL; + } + + + of_property_for_each_string(np, "mode", prop, dsi_mode_flags) { + if (!strcmp(dsi_mode_flags, "MODE_VIDEO")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_BURST")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_BURST; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_SYNC_PULSE")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_AUTO_VERT")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_AUTO_VERT; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_HSE")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_HSE; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_NO_HFP")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_NO_HFP; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_NO_HBP")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_NO_HBP; + else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_NO_HSA")) + desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_NO_HSA; + else if (!strcmp(dsi_mode_flags, "MODE_NO_EOT_PACKET")) + desc_dsi->flags |= MIPI_DSI_MODE_NO_EOT_PACKET; + else if (!strcmp(dsi_mode_flags, "CLOCK_NON_CONTINUOUS")) + desc_dsi->flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS; + else if (!strcmp(dsi_mode_flags, "MODE_LPM")) + desc_dsi->flags |= MIPI_DSI_MODE_LPM; + else if (!strcmp(dsi_mode_flags, "HS_PKT_END_ALIGNED")) + desc_dsi->flags |= MIPI_DSI_HS_PKT_END_ALIGNED; + } + + desc->connector_type = DRM_MODE_CONNECTOR_DSI; + desc_dsi->desc = *desc; + + return 0; +} + static int panel_simple_dsi_probe(struct mipi_dsi_device *dsi) { const struct panel_desc_dsi *desc; @@ -5748,6 +5925,21 @@ static int panel_simple_dsi_probe(struct mipi_dsi_device *dsi) return PTR_ERR(panel); desc = container_of(panel->desc, struct panel_desc_dsi, desc); + + if (desc == &panel_dsi) { + /* Handle the generic panel-dsi binding */ + struct panel_desc_dsi *dt_desc; + dt_desc = devm_kzalloc(&dsi->dev, sizeof(*dt_desc), GFP_KERNEL); + if (!dt_desc) + return -ENOMEM; + + err = panel_dsi_dt_probe(&dsi->dev, dt_desc); + if (err < 0) + return err; + + desc = dt_desc; + } + dsi->mode_flags = desc->flags; dsi->format = desc->format; dsi->lanes = desc->lanes; diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index af3c2f659f5e9..ed5e419995415 100644 --- a/drivers/iio/adc/qcom-spmi-adc5.c +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -574,6 +574,14 @@ static const struct adc5_channels adc7_chans_pmic[ADC5_MAX_CHANNEL] = { SCALE_HW_CALIB_DEFAULT) [ADC7_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 0, SCALE_HW_CALIB_PMIC_THERM_PM7) + [ADC7_GPIO1] = ADC5_CHAN_VOLT("gpio1", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC7_GPIO2] = ADC5_CHAN_VOLT("gpio2", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC7_GPIO3] = ADC5_CHAN_VOLT("gpio3", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC7_GPIO4] = ADC5_CHAN_VOLT("gpio4", 0, + SCALE_HW_CALIB_DEFAULT) [ADC7_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_pu2", 0, SCALE_HW_CALIB_THERM_100K_PU_PM7) [ADC7_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_pu2", 0, diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index cdd7ba5da0d50..132ccdfc265f4 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -277,6 +277,17 @@ config VIDEO_IMX415 To compile this driver as a module, choose M here: the module will be called imx415. +config VIDEO_IMX708 + tristate "Sony IMX708 sensor support" + depends on OF_GPIO + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX708 camera. + + To compile this driver as a module, choose M here: the + module will be called imx708. + config VIDEO_MAX9271_LIB tristate diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 57cdd8dc96f63..f7ae97375c1dc 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -60,6 +60,7 @@ 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_IMX708) += imx708.o obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o obj-$(CONFIG_VIDEO_KS0127) += ks0127.o diff --git a/drivers/media/i2c/imx708.c b/drivers/media/i2c/imx708.c new file mode 100644 index 0000000000000..855996ce39a4f --- /dev/null +++ b/drivers/media/i2c/imx708.c @@ -0,0 +1,2195 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX708 cameras. + * Copyright (C) 2022-2023, Raspberry Pi Ltd + * + * Based on Sony imx477 camera driver + * Copyright (C) 2020 Raspberry Pi Ltd + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Chip ID */ +#define IMX708_REG_CHIP_ID CCI_REG16(0x0016) +#define IMX708_CHIP_ID 0x0708 + +#define IMX708_REG_MODE_SELECT CCI_REG8(0x0100) +#define IMX708_MODE_STANDBY 0x00 +#define IMX708_MODE_STREAMING 0x01 + +#define IMX708_REG_ORIENTATION CCI_REG8(0x0101) + +#define IMX708_XCLK_FREQ 24000000 + +/* + * Parameter to adjust Quad Bayer re-mosaic broken line correction strength, + * used in full-resolution mode only. Set zero to disable. + */ +static int qbc_adjust = 2; +module_param(qbc_adjust, int, 0644); +MODULE_PARM_DESC(qbc_adjust, + "Quad Bayer broken line correction strength [0,2-5]"); + +/* QBC Re-mosaic broken line correction registers */ +#define IMX708_LPF_INTENSITY_EN CCI_REG8(0xc428) +#define IMX708_LPF_INTENSITY_ENABLED 0x00 +#define IMX708_LPF_INTENSITY_DISABLED 0x01 +#define IMX708_LPF_INTENSITY CCI_REG8(0xc429) + +/* V_TIMING internal */ +#define IMX708_REG_FRAME_LENGTH CCI_REG16(0x0340) +#define IMX708_FRAME_LENGTH_MAX 0xffff + +/* Long exposure multiplier */ +#define IMX708_LONG_EXP_SHIFT_MAX 7 +#define IMX708_LONG_EXP_SHIFT_REG CCI_REG8(0x3100) + +/* Exposure control */ +#define IMX708_REG_EXPOSURE CCI_REG16(0x0202) +#define IMX708_EXPOSURE_OFFSET 48 +#define IMX708_EXPOSURE_DEFAULT 0x640 +#define IMX708_EXPOSURE_STEP 1 +#define IMX708_EXPOSURE_MIN 1 +#define IMX708_EXPOSURE_MAX (IMX708_FRAME_LENGTH_MAX - \ + IMX708_EXPOSURE_OFFSET) + +/* Analog gain control */ +#define IMX708_REG_ANALOG_GAIN CCI_REG16(0x0204) +#define IMX708_ANA_GAIN_MIN 112 +#define IMX708_ANA_GAIN_MAX 960 +#define IMX708_ANA_GAIN_STEP 1 +#define IMX708_ANA_GAIN_DEFAULT IMX708_ANA_GAIN_MIN + +/* Digital gain control */ +#define IMX708_REG_DIGITAL_GAIN CCI_REG16(0x020e) +#define IMX708_DGTL_GAIN_MIN 0x0100 +#define IMX708_DGTL_GAIN_MAX 0xffff +#define IMX708_DGTL_GAIN_DEFAULT 0x0100 +#define IMX708_DGTL_GAIN_STEP 1 + +/* Colour balance controls */ +#define IMX708_REG_COLOUR_BALANCE_RED CCI_REG16(0x0b90) +#define IMX708_REG_COLOUR_BALANCE_BLUE CCI_REG16(0x0b92) +#define IMX708_COLOUR_BALANCE_MIN 0x01 +#define IMX708_COLOUR_BALANCE_MAX 0xffff +#define IMX708_COLOUR_BALANCE_STEP 0x01 +#define IMX708_COLOUR_BALANCE_DEFAULT 0x100 + +/* Test Pattern Control */ +#define IMX708_REG_TEST_PATTERN CCI_REG16(0x0600) +#define IMX708_TEST_PATTERN_DISABLE 0 +#define IMX708_TEST_PATTERN_SOLID_COLOR 1 +#define IMX708_TEST_PATTERN_COLOR_BARS 2 +#define IMX708_TEST_PATTERN_GREY_COLOR 3 +#define IMX708_TEST_PATTERN_PN9 4 + +/* Test pattern colour components */ +#define IMX708_REG_TEST_PATTERN_R CCI_REG16(0x0602) +#define IMX708_REG_TEST_PATTERN_GR CCI_REG16(0x0604) +#define IMX708_REG_TEST_PATTERN_B CCI_REG16(0x0606) +#define IMX708_REG_TEST_PATTERN_GB CCI_REG16(0x0608) +#define IMX708_TEST_PATTERN_COLOUR_MIN 0 +#define IMX708_TEST_PATTERN_COLOUR_MAX 0x0fff +#define IMX708_TEST_PATTERN_COLOUR_STEP 1 + +#define IMX708_REG_BASE_SPC_GAINS_L CCI_REG8(0x7b10) +#define IMX708_REG_BASE_SPC_GAINS_R CCI_REG8(0x7c00) + +/* HDR exposure ratio (long:med == med:short) */ +#define IMX708_HDR_EXPOSURE_RATIO 4 +#define IMX708_REG_MID_EXPOSURE CCI_REG16(0x3116) +#define IMX708_REG_SHT_EXPOSURE CCI_REG16(0x0224) +#define IMX708_REG_MID_ANALOG_GAIN CCI_REG16(0x3118) +#define IMX708_REG_SHT_ANALOG_GAIN CCI_REG16(0x0216) + +/* IMX708 native and active pixel array size. */ +#define IMX708_NATIVE_WIDTH 4640U +#define IMX708_NATIVE_HEIGHT 2658U +#define IMX708_PIXEL_ARRAY_LEFT 16U +#define IMX708_PIXEL_ARRAY_TOP 24U +#define IMX708_PIXEL_ARRAY_WIDTH 4608U +#define IMX708_PIXEL_ARRAY_HEIGHT 2592U + +struct imx708_reg_list { + unsigned int num_of_regs; + const struct cci_reg_sequence *regs; +}; + +#define IMX708_REG_8BIT(addr, value) \ + { .reg = CCI_REG8(addr), .val = (value) } + +/* Link frequency setup */ +enum { + IMX708_LINK_FREQ_450MHZ, + IMX708_LINK_FREQ_447MHZ, + IMX708_LINK_FREQ_453MHZ, +}; + +static const s64 imx708_link_freq_menu[] = { + [IMX708_LINK_FREQ_450MHZ] = 450000000, + [IMX708_LINK_FREQ_447MHZ] = 447000000, + [IMX708_LINK_FREQ_453MHZ] = 453000000, +}; + +static const struct cci_reg_sequence link_450mhz_regs[] = { + IMX708_REG_8BIT(0x030e, 0x01), + IMX708_REG_8BIT(0x030f, 0x2c), +}; + +static const struct cci_reg_sequence link_447mhz_regs[] = { + IMX708_REG_8BIT(0x030e, 0x01), + IMX708_REG_8BIT(0x030f, 0x2a), +}; + +static const struct cci_reg_sequence link_453mhz_regs[] = { + IMX708_REG_8BIT(0x030e, 0x01), + IMX708_REG_8BIT(0x030f, 0x2e), +}; + +#define IMX708_REG_PLL_MPY_MSB CCI_REG8(0x030e) +#define IMX708_REG_PLL_MPY_LSB CCI_REG8(0x030f) + +static const struct imx708_reg_list link_freq_regs[] = { + [IMX708_LINK_FREQ_450MHZ] = { + .regs = link_450mhz_regs, + .num_of_regs = ARRAY_SIZE(link_450mhz_regs), + }, + [IMX708_LINK_FREQ_447MHZ] = { + .regs = link_447mhz_regs, + .num_of_regs = ARRAY_SIZE(link_447mhz_regs), + }, + [IMX708_LINK_FREQ_453MHZ] = { + .regs = link_453mhz_regs, + .num_of_regs = ARRAY_SIZE(link_453mhz_regs), + }, +}; + +/* Mode : resolution and related config&values */ +struct imx708_mode { + /* Frame width */ + unsigned int width; + + /* Frame height */ + unsigned int height; + + /* H-timing in pixels */ + unsigned int line_length_pix; + + /* Analog crop rectangle. */ + struct v4l2_rect crop; + + /* Highest possible framerate. */ + unsigned int vblank_min; + + /* Default framerate. */ + unsigned int vblank_default; + + /* Default register values */ + struct imx708_reg_list reg_list; + + /* Not all modes have the same pixel rate. */ + u64 pixel_rate; + + /* Not all modes have the same minimum exposure. */ + u32 exposure_lines_min; + + /* Not all modes have the same exposure lines step. */ + u32 exposure_lines_step; + + /* HDR flag, currently not used at runtime */ + bool hdr; + + /* Quad Bayer re-mosaic flag */ + bool remosaic; +}; + +/* Default PDAF pixel correction gains */ +static const u8 pdaf_gains[2][9] = { + { 0x4c, 0x4c, 0x4c, 0x46, 0x3e, 0x38, 0x35, 0x35, 0x35 }, + { 0x35, 0x35, 0x35, 0x38, 0x3e, 0x46, 0x4c, 0x4c, 0x4c } +}; + +static const struct cci_reg_sequence mode_common_regs[] = { + IMX708_REG_8BIT(0x0100, 0x00), + IMX708_REG_8BIT(0x0136, 0x18), + IMX708_REG_8BIT(0x0137, 0x00), + IMX708_REG_8BIT(0x33f0, 0x02), + IMX708_REG_8BIT(0x33f1, 0x05), + IMX708_REG_8BIT(0x3062, 0x00), + IMX708_REG_8BIT(0x3063, 0x12), + IMX708_REG_8BIT(0x3068, 0x00), + IMX708_REG_8BIT(0x3069, 0x12), + IMX708_REG_8BIT(0x306a, 0x00), + IMX708_REG_8BIT(0x306b, 0x30), + IMX708_REG_8BIT(0x3076, 0x00), + IMX708_REG_8BIT(0x3077, 0x30), + IMX708_REG_8BIT(0x3078, 0x00), + IMX708_REG_8BIT(0x3079, 0x30), + IMX708_REG_8BIT(0x5e54, 0x0c), + IMX708_REG_8BIT(0x6e44, 0x00), + IMX708_REG_8BIT(0xb0b6, 0x01), + IMX708_REG_8BIT(0xe829, 0x00), + IMX708_REG_8BIT(0xf001, 0x08), + IMX708_REG_8BIT(0xf003, 0x08), + IMX708_REG_8BIT(0xf00d, 0x10), + IMX708_REG_8BIT(0xf00f, 0x10), + IMX708_REG_8BIT(0xf031, 0x08), + IMX708_REG_8BIT(0xf033, 0x08), + IMX708_REG_8BIT(0xf03d, 0x10), + IMX708_REG_8BIT(0xf03f, 0x10), + IMX708_REG_8BIT(0x0112, 0x0a), + IMX708_REG_8BIT(0x0113, 0x0a), + IMX708_REG_8BIT(0x0114, 0x01), + IMX708_REG_8BIT(0x0b8e, 0x01), + IMX708_REG_8BIT(0x0b8f, 0x00), + IMX708_REG_8BIT(0x0b94, 0x01), + IMX708_REG_8BIT(0x0b95, 0x00), + IMX708_REG_8BIT(0x3400, 0x01), + IMX708_REG_8BIT(0x3478, 0x01), + IMX708_REG_8BIT(0x3479, 0x1c), + IMX708_REG_8BIT(0x3091, 0x01), + IMX708_REG_8BIT(0x3092, 0x00), + IMX708_REG_8BIT(0x3419, 0x00), + IMX708_REG_8BIT(0xbcf1, 0x02), + IMX708_REG_8BIT(0x3094, 0x01), + IMX708_REG_8BIT(0x3095, 0x01), + IMX708_REG_8BIT(0x3362, 0x00), + IMX708_REG_8BIT(0x3363, 0x00), + IMX708_REG_8BIT(0x3364, 0x00), + IMX708_REG_8BIT(0x3365, 0x00), + IMX708_REG_8BIT(0x0138, 0x01), +}; + +/* 10-bit. */ +static const struct cci_reg_sequence mode_4608x2592_regs[] = { + IMX708_REG_8BIT(0x0342, 0x3d), + IMX708_REG_8BIT(0x0343, 0x20), + IMX708_REG_8BIT(0x0340, 0x0a), + IMX708_REG_8BIT(0x0341, 0x59), + IMX708_REG_8BIT(0x0344, 0x00), + IMX708_REG_8BIT(0x0345, 0x00), + IMX708_REG_8BIT(0x0346, 0x00), + IMX708_REG_8BIT(0x0347, 0x00), + IMX708_REG_8BIT(0x0348, 0x11), + IMX708_REG_8BIT(0x0349, 0xff), + IMX708_REG_8BIT(0x034a, 0x0a), + IMX708_REG_8BIT(0x034b, 0x1f), + IMX708_REG_8BIT(0x0220, 0x62), + IMX708_REG_8BIT(0x0222, 0x01), + IMX708_REG_8BIT(0x0900, 0x00), + IMX708_REG_8BIT(0x0901, 0x11), + IMX708_REG_8BIT(0x0902, 0x0a), + IMX708_REG_8BIT(0x3200, 0x01), + IMX708_REG_8BIT(0x3201, 0x01), + IMX708_REG_8BIT(0x32d5, 0x01), + IMX708_REG_8BIT(0x32d6, 0x00), + IMX708_REG_8BIT(0x32db, 0x01), + IMX708_REG_8BIT(0x32df, 0x00), + IMX708_REG_8BIT(0x350c, 0x00), + IMX708_REG_8BIT(0x350d, 0x00), + IMX708_REG_8BIT(0x0408, 0x00), + IMX708_REG_8BIT(0x0409, 0x00), + IMX708_REG_8BIT(0x040a, 0x00), + IMX708_REG_8BIT(0x040b, 0x00), + IMX708_REG_8BIT(0x040c, 0x12), + IMX708_REG_8BIT(0x040d, 0x00), + IMX708_REG_8BIT(0x040e, 0x0a), + IMX708_REG_8BIT(0x040f, 0x20), + IMX708_REG_8BIT(0x034c, 0x12), + IMX708_REG_8BIT(0x034d, 0x00), + IMX708_REG_8BIT(0x034e, 0x0a), + IMX708_REG_8BIT(0x034f, 0x20), + IMX708_REG_8BIT(0x0301, 0x05), + IMX708_REG_8BIT(0x0303, 0x02), + IMX708_REG_8BIT(0x0305, 0x02), + IMX708_REG_8BIT(0x0306, 0x00), + IMX708_REG_8BIT(0x0307, 0x7c), + IMX708_REG_8BIT(0x030b, 0x02), + IMX708_REG_8BIT(0x030d, 0x04), + IMX708_REG_8BIT(0x0310, 0x01), + IMX708_REG_8BIT(0x3ca0, 0x00), + IMX708_REG_8BIT(0x3ca1, 0x64), + IMX708_REG_8BIT(0x3ca4, 0x00), + IMX708_REG_8BIT(0x3ca5, 0x00), + IMX708_REG_8BIT(0x3ca6, 0x00), + IMX708_REG_8BIT(0x3ca7, 0x00), + IMX708_REG_8BIT(0x3caa, 0x00), + IMX708_REG_8BIT(0x3cab, 0x00), + IMX708_REG_8BIT(0x3cb8, 0x00), + IMX708_REG_8BIT(0x3cb9, 0x08), + IMX708_REG_8BIT(0x3cba, 0x00), + IMX708_REG_8BIT(0x3cbb, 0x00), + IMX708_REG_8BIT(0x3cbc, 0x00), + IMX708_REG_8BIT(0x3cbd, 0x3c), + IMX708_REG_8BIT(0x3cbe, 0x00), + IMX708_REG_8BIT(0x3cbf, 0x00), + IMX708_REG_8BIT(0x0202, 0x0a), + IMX708_REG_8BIT(0x0203, 0x29), + IMX708_REG_8BIT(0x0224, 0x01), + IMX708_REG_8BIT(0x0225, 0xf4), + IMX708_REG_8BIT(0x3116, 0x01), + IMX708_REG_8BIT(0x3117, 0xf4), + IMX708_REG_8BIT(0x0204, 0x00), + IMX708_REG_8BIT(0x0205, 0x00), + IMX708_REG_8BIT(0x0216, 0x00), + IMX708_REG_8BIT(0x0217, 0x00), + IMX708_REG_8BIT(0x0218, 0x01), + IMX708_REG_8BIT(0x0219, 0x00), + IMX708_REG_8BIT(0x020e, 0x01), + IMX708_REG_8BIT(0x020f, 0x00), + IMX708_REG_8BIT(0x3118, 0x00), + IMX708_REG_8BIT(0x3119, 0x00), + IMX708_REG_8BIT(0x311a, 0x01), + IMX708_REG_8BIT(0x311b, 0x00), + IMX708_REG_8BIT(0x341a, 0x00), + IMX708_REG_8BIT(0x341b, 0x00), + IMX708_REG_8BIT(0x341c, 0x00), + IMX708_REG_8BIT(0x341d, 0x00), + IMX708_REG_8BIT(0x341e, 0x01), + IMX708_REG_8BIT(0x341f, 0x20), + IMX708_REG_8BIT(0x3420, 0x00), + IMX708_REG_8BIT(0x3421, 0xd8), + IMX708_REG_8BIT(0x3366, 0x00), + IMX708_REG_8BIT(0x3367, 0x00), + IMX708_REG_8BIT(0x3368, 0x00), + IMX708_REG_8BIT(0x3369, 0x00), +}; + +static const struct cci_reg_sequence mode_2x2binned_regs[] = { + IMX708_REG_8BIT(0x0342, 0x1e), + IMX708_REG_8BIT(0x0343, 0x90), + IMX708_REG_8BIT(0x0340, 0x05), + IMX708_REG_8BIT(0x0341, 0x38), + IMX708_REG_8BIT(0x0344, 0x00), + IMX708_REG_8BIT(0x0345, 0x00), + IMX708_REG_8BIT(0x0346, 0x00), + IMX708_REG_8BIT(0x0347, 0x00), + IMX708_REG_8BIT(0x0348, 0x11), + IMX708_REG_8BIT(0x0349, 0xff), + IMX708_REG_8BIT(0x034a, 0x0a), + IMX708_REG_8BIT(0x034b, 0x1f), + IMX708_REG_8BIT(0x0220, 0x62), + IMX708_REG_8BIT(0x0222, 0x01), + IMX708_REG_8BIT(0x0900, 0x01), + IMX708_REG_8BIT(0x0901, 0x22), + IMX708_REG_8BIT(0x0902, 0x08), + IMX708_REG_8BIT(0x3200, 0x41), + IMX708_REG_8BIT(0x3201, 0x41), + IMX708_REG_8BIT(0x32d5, 0x00), + IMX708_REG_8BIT(0x32d6, 0x00), + IMX708_REG_8BIT(0x32db, 0x01), + IMX708_REG_8BIT(0x32df, 0x00), + IMX708_REG_8BIT(0x350c, 0x00), + IMX708_REG_8BIT(0x350d, 0x00), + IMX708_REG_8BIT(0x0408, 0x00), + IMX708_REG_8BIT(0x0409, 0x00), + IMX708_REG_8BIT(0x040a, 0x00), + IMX708_REG_8BIT(0x040b, 0x00), + IMX708_REG_8BIT(0x040c, 0x09), + IMX708_REG_8BIT(0x040d, 0x00), + IMX708_REG_8BIT(0x040e, 0x05), + IMX708_REG_8BIT(0x040f, 0x10), + IMX708_REG_8BIT(0x034c, 0x09), + IMX708_REG_8BIT(0x034d, 0x00), + IMX708_REG_8BIT(0x034e, 0x05), + IMX708_REG_8BIT(0x034f, 0x10), + IMX708_REG_8BIT(0x0301, 0x05), + IMX708_REG_8BIT(0x0303, 0x02), + IMX708_REG_8BIT(0x0305, 0x02), + IMX708_REG_8BIT(0x0306, 0x00), + IMX708_REG_8BIT(0x0307, 0x7a), + IMX708_REG_8BIT(0x030b, 0x02), + IMX708_REG_8BIT(0x030d, 0x04), + IMX708_REG_8BIT(0x0310, 0x01), + IMX708_REG_8BIT(0x3ca0, 0x00), + IMX708_REG_8BIT(0x3ca1, 0x3c), + IMX708_REG_8BIT(0x3ca4, 0x00), + IMX708_REG_8BIT(0x3ca5, 0x3c), + IMX708_REG_8BIT(0x3ca6, 0x00), + IMX708_REG_8BIT(0x3ca7, 0x00), + IMX708_REG_8BIT(0x3caa, 0x00), + IMX708_REG_8BIT(0x3cab, 0x00), + IMX708_REG_8BIT(0x3cb8, 0x00), + IMX708_REG_8BIT(0x3cb9, 0x1c), + IMX708_REG_8BIT(0x3cba, 0x00), + IMX708_REG_8BIT(0x3cbb, 0x08), + IMX708_REG_8BIT(0x3cbc, 0x00), + IMX708_REG_8BIT(0x3cbd, 0x1e), + IMX708_REG_8BIT(0x3cbe, 0x00), + IMX708_REG_8BIT(0x3cbf, 0x0a), + IMX708_REG_8BIT(0x0202, 0x05), + IMX708_REG_8BIT(0x0203, 0x08), + IMX708_REG_8BIT(0x0224, 0x01), + IMX708_REG_8BIT(0x0225, 0xf4), + IMX708_REG_8BIT(0x3116, 0x01), + IMX708_REG_8BIT(0x3117, 0xf4), + IMX708_REG_8BIT(0x0204, 0x00), + IMX708_REG_8BIT(0x0205, 0x70), + IMX708_REG_8BIT(0x0216, 0x00), + IMX708_REG_8BIT(0x0217, 0x70), + IMX708_REG_8BIT(0x0218, 0x01), + IMX708_REG_8BIT(0x0219, 0x00), + IMX708_REG_8BIT(0x020e, 0x01), + IMX708_REG_8BIT(0x020f, 0x00), + IMX708_REG_8BIT(0x3118, 0x00), + IMX708_REG_8BIT(0x3119, 0x70), + IMX708_REG_8BIT(0x311a, 0x01), + IMX708_REG_8BIT(0x311b, 0x00), + IMX708_REG_8BIT(0x341a, 0x00), + IMX708_REG_8BIT(0x341b, 0x00), + IMX708_REG_8BIT(0x341c, 0x00), + IMX708_REG_8BIT(0x341d, 0x00), + IMX708_REG_8BIT(0x341e, 0x00), + IMX708_REG_8BIT(0x341f, 0x90), + IMX708_REG_8BIT(0x3420, 0x00), + IMX708_REG_8BIT(0x3421, 0x6c), + IMX708_REG_8BIT(0x3366, 0x00), + IMX708_REG_8BIT(0x3367, 0x00), + IMX708_REG_8BIT(0x3368, 0x00), + IMX708_REG_8BIT(0x3369, 0x00), +}; + +static const struct cci_reg_sequence mode_2x2binned_720p_regs[] = { + IMX708_REG_8BIT(0x0342, 0x14), + IMX708_REG_8BIT(0x0343, 0x60), + IMX708_REG_8BIT(0x0340, 0x04), + IMX708_REG_8BIT(0x0341, 0xb6), + IMX708_REG_8BIT(0x0344, 0x03), + IMX708_REG_8BIT(0x0345, 0x00), + IMX708_REG_8BIT(0x0346, 0x01), + IMX708_REG_8BIT(0x0347, 0xb0), + IMX708_REG_8BIT(0x0348, 0x0e), + IMX708_REG_8BIT(0x0349, 0xff), + IMX708_REG_8BIT(0x034a, 0x08), + IMX708_REG_8BIT(0x034b, 0x6f), + IMX708_REG_8BIT(0x0220, 0x62), + IMX708_REG_8BIT(0x0222, 0x01), + IMX708_REG_8BIT(0x0900, 0x01), + IMX708_REG_8BIT(0x0901, 0x22), + IMX708_REG_8BIT(0x0902, 0x08), + IMX708_REG_8BIT(0x3200, 0x41), + IMX708_REG_8BIT(0x3201, 0x41), + IMX708_REG_8BIT(0x32d5, 0x00), + IMX708_REG_8BIT(0x32d6, 0x00), + IMX708_REG_8BIT(0x32db, 0x01), + IMX708_REG_8BIT(0x32df, 0x01), + IMX708_REG_8BIT(0x350c, 0x00), + IMX708_REG_8BIT(0x350d, 0x00), + IMX708_REG_8BIT(0x0408, 0x00), + IMX708_REG_8BIT(0x0409, 0x00), + IMX708_REG_8BIT(0x040a, 0x00), + IMX708_REG_8BIT(0x040b, 0x00), + IMX708_REG_8BIT(0x040c, 0x06), + IMX708_REG_8BIT(0x040d, 0x00), + IMX708_REG_8BIT(0x040e, 0x03), + IMX708_REG_8BIT(0x040f, 0x60), + IMX708_REG_8BIT(0x034c, 0x06), + IMX708_REG_8BIT(0x034d, 0x00), + IMX708_REG_8BIT(0x034e, 0x03), + IMX708_REG_8BIT(0x034f, 0x60), + IMX708_REG_8BIT(0x0301, 0x05), + IMX708_REG_8BIT(0x0303, 0x02), + IMX708_REG_8BIT(0x0305, 0x02), + IMX708_REG_8BIT(0x0306, 0x00), + IMX708_REG_8BIT(0x0307, 0x76), + IMX708_REG_8BIT(0x030b, 0x02), + IMX708_REG_8BIT(0x030d, 0x04), + IMX708_REG_8BIT(0x0310, 0x01), + IMX708_REG_8BIT(0x3ca0, 0x00), + IMX708_REG_8BIT(0x3ca1, 0x3c), + IMX708_REG_8BIT(0x3ca4, 0x01), + IMX708_REG_8BIT(0x3ca5, 0x5e), + IMX708_REG_8BIT(0x3ca6, 0x00), + IMX708_REG_8BIT(0x3ca7, 0x00), + IMX708_REG_8BIT(0x3caa, 0x00), + IMX708_REG_8BIT(0x3cab, 0x00), + IMX708_REG_8BIT(0x3cb8, 0x00), + IMX708_REG_8BIT(0x3cb9, 0x0c), + IMX708_REG_8BIT(0x3cba, 0x00), + IMX708_REG_8BIT(0x3cbb, 0x04), + IMX708_REG_8BIT(0x3cbc, 0x00), + IMX708_REG_8BIT(0x3cbd, 0x1e), + IMX708_REG_8BIT(0x3cbe, 0x00), + IMX708_REG_8BIT(0x3cbf, 0x05), + IMX708_REG_8BIT(0x0202, 0x04), + IMX708_REG_8BIT(0x0203, 0x86), + IMX708_REG_8BIT(0x0224, 0x01), + IMX708_REG_8BIT(0x0225, 0xf4), + IMX708_REG_8BIT(0x3116, 0x01), + IMX708_REG_8BIT(0x3117, 0xf4), + IMX708_REG_8BIT(0x0204, 0x00), + IMX708_REG_8BIT(0x0205, 0x70), + IMX708_REG_8BIT(0x0216, 0x00), + IMX708_REG_8BIT(0x0217, 0x70), + IMX708_REG_8BIT(0x0218, 0x01), + IMX708_REG_8BIT(0x0219, 0x00), + IMX708_REG_8BIT(0x020e, 0x01), + IMX708_REG_8BIT(0x020f, 0x00), + IMX708_REG_8BIT(0x3118, 0x00), + IMX708_REG_8BIT(0x3119, 0x70), + IMX708_REG_8BIT(0x311a, 0x01), + IMX708_REG_8BIT(0x311b, 0x00), + IMX708_REG_8BIT(0x341a, 0x00), + IMX708_REG_8BIT(0x341b, 0x00), + IMX708_REG_8BIT(0x341c, 0x00), + IMX708_REG_8BIT(0x341d, 0x00), + IMX708_REG_8BIT(0x341e, 0x00), + IMX708_REG_8BIT(0x341f, 0x60), + IMX708_REG_8BIT(0x3420, 0x00), + IMX708_REG_8BIT(0x3421, 0x48), + IMX708_REG_8BIT(0x3366, 0x00), + IMX708_REG_8BIT(0x3367, 0x00), + IMX708_REG_8BIT(0x3368, 0x00), + IMX708_REG_8BIT(0x3369, 0x00), +}; + +static const struct cci_reg_sequence mode_hdr_regs[] = { + IMX708_REG_8BIT(0x0342, 0x14), + IMX708_REG_8BIT(0x0343, 0x60), + IMX708_REG_8BIT(0x0340, 0x0a), + IMX708_REG_8BIT(0x0341, 0x5b), + IMX708_REG_8BIT(0x0344, 0x00), + IMX708_REG_8BIT(0x0345, 0x00), + IMX708_REG_8BIT(0x0346, 0x00), + IMX708_REG_8BIT(0x0347, 0x00), + IMX708_REG_8BIT(0x0348, 0x11), + IMX708_REG_8BIT(0x0349, 0xff), + IMX708_REG_8BIT(0x034a, 0x0a), + IMX708_REG_8BIT(0x034b, 0x1f), + IMX708_REG_8BIT(0x0220, 0x01), + IMX708_REG_8BIT(0x0222, IMX708_HDR_EXPOSURE_RATIO), + IMX708_REG_8BIT(0x0900, 0x00), + IMX708_REG_8BIT(0x0901, 0x11), + IMX708_REG_8BIT(0x0902, 0x0a), + IMX708_REG_8BIT(0x3200, 0x01), + IMX708_REG_8BIT(0x3201, 0x01), + IMX708_REG_8BIT(0x32d5, 0x00), + IMX708_REG_8BIT(0x32d6, 0x00), + IMX708_REG_8BIT(0x32db, 0x01), + IMX708_REG_8BIT(0x32df, 0x00), + IMX708_REG_8BIT(0x350c, 0x00), + IMX708_REG_8BIT(0x350d, 0x00), + IMX708_REG_8BIT(0x0408, 0x00), + IMX708_REG_8BIT(0x0409, 0x00), + IMX708_REG_8BIT(0x040a, 0x00), + IMX708_REG_8BIT(0x040b, 0x00), + IMX708_REG_8BIT(0x040c, 0x09), + IMX708_REG_8BIT(0x040d, 0x00), + IMX708_REG_8BIT(0x040e, 0x05), + IMX708_REG_8BIT(0x040f, 0x10), + IMX708_REG_8BIT(0x034c, 0x09), + IMX708_REG_8BIT(0x034d, 0x00), + IMX708_REG_8BIT(0x034e, 0x05), + IMX708_REG_8BIT(0x034f, 0x10), + IMX708_REG_8BIT(0x0301, 0x05), + IMX708_REG_8BIT(0x0303, 0x02), + IMX708_REG_8BIT(0x0305, 0x02), + IMX708_REG_8BIT(0x0306, 0x00), + IMX708_REG_8BIT(0x0307, 0xa2), + IMX708_REG_8BIT(0x030b, 0x02), + IMX708_REG_8BIT(0x030d, 0x04), + IMX708_REG_8BIT(0x0310, 0x01), + IMX708_REG_8BIT(0x3ca0, 0x00), + IMX708_REG_8BIT(0x3ca1, 0x00), + IMX708_REG_8BIT(0x3ca4, 0x00), + IMX708_REG_8BIT(0x3ca5, 0x00), + IMX708_REG_8BIT(0x3ca6, 0x00), + IMX708_REG_8BIT(0x3ca7, 0x28), + IMX708_REG_8BIT(0x3caa, 0x00), + IMX708_REG_8BIT(0x3cab, 0x00), + IMX708_REG_8BIT(0x3cb8, 0x00), + IMX708_REG_8BIT(0x3cb9, 0x30), + IMX708_REG_8BIT(0x3cba, 0x00), + IMX708_REG_8BIT(0x3cbb, 0x00), + IMX708_REG_8BIT(0x3cbc, 0x00), + IMX708_REG_8BIT(0x3cbd, 0x32), + IMX708_REG_8BIT(0x3cbe, 0x00), + IMX708_REG_8BIT(0x3cbf, 0x00), + IMX708_REG_8BIT(0x0202, 0x0a), + IMX708_REG_8BIT(0x0203, 0x2b), + IMX708_REG_8BIT(0x0224, 0x0a), + IMX708_REG_8BIT(0x0225, 0x2b), + IMX708_REG_8BIT(0x3116, 0x0a), + IMX708_REG_8BIT(0x3117, 0x2b), + IMX708_REG_8BIT(0x0204, 0x00), + IMX708_REG_8BIT(0x0205, 0x00), + IMX708_REG_8BIT(0x0216, 0x00), + IMX708_REG_8BIT(0x0217, 0x00), + IMX708_REG_8BIT(0x0218, 0x01), + IMX708_REG_8BIT(0x0219, 0x00), + IMX708_REG_8BIT(0x020e, 0x01), + IMX708_REG_8BIT(0x020f, 0x00), + IMX708_REG_8BIT(0x3118, 0x00), + IMX708_REG_8BIT(0x3119, 0x00), + IMX708_REG_8BIT(0x311a, 0x01), + IMX708_REG_8BIT(0x311b, 0x00), + IMX708_REG_8BIT(0x341a, 0x00), + IMX708_REG_8BIT(0x341b, 0x00), + IMX708_REG_8BIT(0x341c, 0x00), + IMX708_REG_8BIT(0x341d, 0x00), + IMX708_REG_8BIT(0x341e, 0x00), + IMX708_REG_8BIT(0x341f, 0x90), + IMX708_REG_8BIT(0x3420, 0x00), + IMX708_REG_8BIT(0x3421, 0x6c), + IMX708_REG_8BIT(0x3360, 0x01), + IMX708_REG_8BIT(0x3361, 0x01), + IMX708_REG_8BIT(0x3366, 0x09), + IMX708_REG_8BIT(0x3367, 0x00), + IMX708_REG_8BIT(0x3368, 0x05), + IMX708_REG_8BIT(0x3369, 0x10), +}; + +/* Mode configs. Keep separate lists for when HDR is enabled or not. */ +static const struct imx708_mode supported_modes_10bit_no_hdr[] = { + { + /* Full resolution. */ + .width = 4608, + .height = 2592, + .line_length_pix = 0x3d20, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT, + .top = IMX708_PIXEL_ARRAY_TOP, + .width = 4608, + .height = 2592, + }, + .vblank_min = 58, + .vblank_default = 58, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4608x2592_regs), + .regs = mode_4608x2592_regs, + }, + .pixel_rate = 595200000, + .exposure_lines_min = 8, + .exposure_lines_step = 1, + .hdr = false, + .remosaic = true + }, + { + /* regular 2x2 binned. */ + .width = 2304, + .height = 1296, + .line_length_pix = 0x1e90, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT, + .top = IMX708_PIXEL_ARRAY_TOP, + .width = 4608, + .height = 2592, + }, + .vblank_min = 40, + .vblank_default = 1198, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2x2binned_regs), + .regs = mode_2x2binned_regs, + }, + .pixel_rate = 585600000, + .exposure_lines_min = 4, + .exposure_lines_step = 2, + .hdr = false, + .remosaic = false + }, + { + /* 2x2 binned and cropped for 720p. */ + .width = 1536, + .height = 864, + .line_length_pix = 0x1460, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT + 768, + .top = IMX708_PIXEL_ARRAY_TOP + 432, + .width = 3072, + .height = 1728, + }, + .vblank_min = 40, + .vblank_default = 2755, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2x2binned_720p_regs), + .regs = mode_2x2binned_720p_regs, + }, + .pixel_rate = 566400000, + .exposure_lines_min = 4, + .exposure_lines_step = 2, + .hdr = false, + .remosaic = false + }, +}; + +static const struct imx708_mode supported_modes_10bit_hdr[] = { + { + /* There's only one HDR mode, which is 2x2 downscaled */ + .width = 2304, + .height = 1296, + .line_length_pix = 0x1460, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT, + .top = IMX708_PIXEL_ARRAY_TOP, + .width = 4608, + .height = 2592, + }, + .vblank_min = 3673, + .vblank_default = 3673, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_hdr_regs), + .regs = mode_hdr_regs, + }, + .pixel_rate = 777600000, + .exposure_lines_min = 8 * IMX708_HDR_EXPOSURE_RATIO * + IMX708_HDR_EXPOSURE_RATIO, + .exposure_lines_step = 2 * IMX708_HDR_EXPOSURE_RATIO * + IMX708_HDR_EXPOSURE_RATIO, + .hdr = true, + .remosaic = false + } +}; + +/* + * 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 codes[] = { + /* 10-bit modes. */ + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, +}; + +static const char * const imx708_test_pattern_menu[] = { + "Disabled", + "Color Bars", + "Solid Color", + "Grey Color Bars", + "PN9" +}; + +static const int imx708_test_pattern_val[] = { + IMX708_TEST_PATTERN_DISABLE, + IMX708_TEST_PATTERN_COLOR_BARS, + IMX708_TEST_PATTERN_SOLID_COLOR, + IMX708_TEST_PATTERN_GREY_COLOR, + IMX708_TEST_PATTERN_PN9, +}; + +/* regulator supplies */ +static const char * const imx708_supply_name[] = { + /* Supplies can be enabled in any order */ + "VANA1", /* Analog1 (2.8V) supply */ + "VANA2", /* Analog2 (1.8V) supply */ + "VDIG", /* Digital Core (1.1V) supply */ + "VDDL", /* IF (1.8V) supply */ +}; + +#define IMX708_NUM_SUPPLIES ARRAY_SIZE(imx708_supply_name) + +/* + * Initialisation delay between XCLR low->high and the moment when the sensor + * can start capture (i.e. can leave software standby), given by T7 in the + * datasheet is 8ms. This does include I2C setup time as well. + * + * Note, that delay between XCLR low->high and reading the CCI ID register (T6 + * in the datasheet) is much smaller - 600us. + */ +#define IMX708_XCLR_MIN_DELAY_US 8000 +#define IMX708_XCLR_DELAY_RANGE_US 1000 + +struct imx708 { + struct v4l2_subdev sd; + struct media_pad pad; + struct regmap *regmap; + + struct clk *xclk; + u32 xclk_freq; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[IMX708_NUM_SUPPLIES]; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *red_balance; + struct v4l2_ctrl *blue_balance; + struct v4l2_ctrl *notify_gains; + struct v4l2_ctrl *hdr_mode; + + /* Current mode */ + const struct imx708_mode *mode; + + /* Mutex for serialized access */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; + + /* Rewrite common registers on stream on? */ + bool common_regs_written; + + /* Current long exposure factor in use. Set through V4L2_CID_VBLANK */ + unsigned int long_exp_shift; + unsigned int link_freq_idx; + u32 csi2_flags; +}; + +static inline struct imx708 *to_imx708(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx708, sd); +} + +static inline void get_mode_table(const struct imx708_mode **mode_list, + unsigned int *num_modes, + bool hdr_enable) +{ + if (hdr_enable) { + *mode_list = supported_modes_10bit_hdr; + *num_modes = ARRAY_SIZE(supported_modes_10bit_hdr); + } else { + *mode_list = supported_modes_10bit_no_hdr; + *num_modes = ARRAY_SIZE(supported_modes_10bit_no_hdr); + } +} + +static const struct imx708_mode * +imx708_find_mode(bool hdr_enable, u32 width, u32 height) +{ + const struct imx708_mode *mode_list; + unsigned int num_modes; + + get_mode_table(&mode_list, &num_modes, hdr_enable); + + return v4l2_find_nearest_size(mode_list, num_modes, width, height, + width, height); +} + +static void imx708_calc_frame_interval(const struct imx708_mode *mode, + u32 vblank, struct v4l2_fract *interval) +{ + u32 frame_length = mode->height + vblank; + u64 numerator = (u64)mode->line_length_pix * frame_length; + u64 denominator = mode->pixel_rate; + u64 scale; + + /* + * struct v4l2_fract stores 32-bit numerators/denominators. Long + * exposure support allows frame lengths that overflow 32-bit if we + * keep the fraction unscaled, so shrink both terms proportionally. + */ + if (numerator > U32_MAX) { + scale = DIV64_U64_ROUND_UP(numerator, U32_MAX); + numerator = DIV64_U64_ROUND_CLOSEST(numerator, scale); + denominator = DIV64_U64_ROUND_CLOSEST(denominator, scale); + } + + if (!denominator) + denominator = 1; + + interval->numerator = numerator; + interval->denominator = denominator; +} + +static int imx708_read_reg(struct imx708 *imx708, u32 reg, u64 *val); + +static void imx708_log_mode_timing(struct imx708 *imx708, const char *tag) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + struct v4l2_fract interval; + const struct imx708_mode *mode = imx708->mode; + u32 vblank, frame_length; + u64 fps_milli; + + if (!mode) { + dev_dbg(&client->dev, + "%s: mode not initialized yet, link=%lld xclk=%u\n", + tag, + (long long)imx708_link_freq_menu[imx708->link_freq_idx], + imx708->xclk_freq); + return; + } + + vblank = imx708->vblank ? imx708->vblank->val : mode->vblank_default; + frame_length = mode->height + vblank; + + imx708_calc_frame_interval(mode, vblank, &interval); + fps_milli = DIV_ROUND_CLOSEST_ULL((u64)interval.denominator * 1000, + interval.numerator); + + dev_dbg(&client->dev, + "%s: mode=%ux%u hdr=%u remosaic=%u link=%lld pixel_rate=%llu line=%u vblank=%u frame_len=%u fps=%llu.%03llu xclk=%u\n", + tag, mode->width, mode->height, + mode->hdr, mode->remosaic, + (long long)imx708_link_freq_menu[imx708->link_freq_idx], + mode->pixel_rate, mode->line_length_pix, + vblank, frame_length, fps_milli / 1000, fps_milli % 1000, + imx708->xclk_freq); +} + +static int imx708_read_reg(struct imx708 *imx708, u32 reg, u64 *val) +{ + return cci_read(imx708->regmap, reg, val, NULL); +} + +static int imx708_write_reg(struct imx708 *imx708, u32 reg, u64 val) +{ + return cci_write(imx708->regmap, reg, val, NULL); +} + +/* Write a list of registers */ +static int imx708_write_regs(struct imx708 *imx708, + const struct cci_reg_sequence *regs, u32 len) +{ + return cci_multi_reg_write(imx708->regmap, regs, len, NULL); +} + +/* Get bayer order based on flip setting. */ +static u32 imx708_get_format_code(struct imx708 *imx708) +{ + unsigned int i; + + i = (imx708->vflip->val ? 2 : 0) | + (imx708->hflip->val ? 1 : 0); + + return codes[i]; +} + +static void imx708_set_default_mode(struct imx708 *imx708) +{ + /* Set default mode to max resolution */ + imx708->mode = &supported_modes_10bit_no_hdr[0]; +} + +static void imx708_update_image_pad_format(struct v4l2_mbus_framefmt *fmt, + const struct imx708_mode *mode) +{ + fmt->width = mode->width; + fmt->height = mode->height; + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_RAW; + 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 imx708_set_exposure(struct imx708 *imx708, unsigned int val) +{ + int ret; + + val = max(val, imx708->mode->exposure_lines_min); + val -= val % imx708->mode->exposure_lines_step; + + /* + * In HDR mode this will set the longest exposure. The sensor + * will automatically divide the medium and short ones by 4,16. + */ + ret = imx708_write_reg(imx708, IMX708_REG_EXPOSURE, + val >> imx708->long_exp_shift); + + return ret; +} + +static void imx708_adjust_exposure_range(struct imx708 *imx708, + struct v4l2_ctrl *ctrl) +{ + int exposure_max, exposure_def; + + /* Honour the VBLANK limits when setting exposure. */ + exposure_max = imx708->mode->height + imx708->vblank->val - + IMX708_EXPOSURE_OFFSET; + exposure_def = min(exposure_max, imx708->exposure->val); + __v4l2_ctrl_modify_range(imx708->exposure, imx708->exposure->minimum, + exposure_max, imx708->exposure->step, + exposure_def); +} + +static int imx708_set_analogue_gain(struct imx708 *imx708, unsigned int val) +{ + int ret; + + /* + * In HDR mode this will set the gain for the longest exposure, + * and by default the sensor uses the same gain for all of them. + */ + ret = imx708_write_reg(imx708, IMX708_REG_ANALOG_GAIN, + val); + + return ret; +} + +static int imx708_set_frame_length(struct imx708 *imx708, unsigned int val) +{ + int ret = 0; + + imx708->long_exp_shift = 0; + + while (val > IMX708_FRAME_LENGTH_MAX) { + imx708->long_exp_shift++; + val >>= 1; + if (imx708->long_exp_shift > IMX708_LONG_EXP_SHIFT_MAX) + return -EINVAL; + } + + ret = imx708_write_reg(imx708, IMX708_REG_FRAME_LENGTH, + val); + if (ret) + return ret; + + return imx708_write_reg(imx708, IMX708_LONG_EXP_SHIFT_REG, + imx708->long_exp_shift); +} + +static int imx708_set_framing_limits(struct imx708 *imx708) +{ + unsigned int hblank; + const struct imx708_mode *mode = imx708->mode; + int ret; + + /* Default to no long exposure multiplier */ + imx708->long_exp_shift = 0; + + __v4l2_ctrl_modify_range(imx708->pixel_rate, + mode->pixel_rate, mode->pixel_rate, + 1, mode->pixel_rate); + + /* Update limits and set FPS to default */ + __v4l2_ctrl_modify_range(imx708->vblank, mode->vblank_min, + ((1 << IMX708_LONG_EXP_SHIFT_MAX) * + IMX708_FRAME_LENGTH_MAX) - mode->height, + 1, mode->vblank_default); + + /* + * Currently PPL is fixed to the mode specified value, so hblank + * depends on mode->width only, and is not changeable in any + * way other than changing the mode. + */ + hblank = mode->line_length_pix - mode->width; + __v4l2_ctrl_modify_range(imx708->hblank, hblank, hblank, 1, hblank); + + ret = __v4l2_ctrl_s_ctrl_int64(imx708->pixel_rate, mode->pixel_rate); + if (ret) + return ret; + + ret = __v4l2_ctrl_s_ctrl(imx708->vblank, mode->vblank_default); + if (ret) + return ret; + + return __v4l2_ctrl_s_ctrl(imx708->hblank, hblank); +} + +static void imx708_update_pad_state(struct imx708 *imx708, + struct v4l2_subdev_state *state, + const struct imx708_mode *mode, + unsigned int pad) +{ + struct v4l2_mbus_framefmt *fmt = v4l2_subdev_state_get_format(state, pad); + struct v4l2_rect *crop = v4l2_subdev_state_get_crop(state, pad); + + imx708_update_image_pad_format(fmt, mode); + fmt->code = imx708_get_format_code(imx708); + *crop = mode->crop; +} + +static int imx708_update_hdr_mode(struct imx708 *imx708, bool hdr_enable) +{ + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + const struct imx708_mode *mode; + u32 width = imx708->mode->width; + u32 height = imx708->mode->height; + int ret; + + state = v4l2_subdev_get_locked_active_state(&imx708->sd); + if (state) { + fmt = v4l2_subdev_state_get_format(state, 0); + width = fmt->width; + height = fmt->height; + } + + mode = imx708_find_mode(hdr_enable, width, height); + imx708->mode = mode; + + ret = imx708_set_framing_limits(imx708); + if (ret) + return ret; + + if (state) + imx708_update_pad_state(imx708, state, mode, 0); + + return 0; +} + +static int imx708_get_qbc_adjust(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int val = qbc_adjust; + + if (val <= 0) + return 0; + + if (val == 1) { + dev_warn_once(&client->dev, + "qbc_adjust=%d is unsupported, clamping to 2\n", + val); + return 2; + } + + if (val > 5) { + dev_warn_once(&client->dev, + "qbc_adjust=%d is unsupported, clamping to 5\n", + val); + return 5; + } + + return val; +} + +static int imx708_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx708 *imx708 = + container_of(ctrl->handler, struct imx708, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int ret; + + /* + * The VBLANK control may change the limits of usable exposure, so check + * and adjust if necessary. + */ + if (ctrl->id == V4L2_CID_VBLANK) + imx708_adjust_exposure_range(imx708, ctrl); + + if (ctrl->id == V4L2_CID_WIDE_DYNAMIC_RANGE) + return imx708_update_hdr_mode(imx708, ctrl->val); + + /* + * 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 = imx708_set_analogue_gain(imx708, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = imx708_set_exposure(imx708, ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = imx708_write_reg(imx708, IMX708_REG_DIGITAL_GAIN, + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN, + imx708_test_pattern_val[ctrl->val]); + break; + case V4L2_CID_TEST_PATTERN_RED: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_R, + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENR: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GR, + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_BLUE: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_B, + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENB: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GB, + ctrl->val); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = imx708_write_reg(imx708, IMX708_REG_ORIENTATION, + imx708->hflip->val | + imx708->vflip->val << 1); + break; + case V4L2_CID_VBLANK: + ret = imx708_set_frame_length(imx708, + imx708->mode->height + ctrl->val); + break; + case V4L2_CID_HBLANK: + case V4L2_CID_PIXEL_RATE: + /* + * Both controls are fixed by the selected mode. The mode tables + * program the timing registers directly, so replaying these + * read-only controls during handler setup is a no-op. + */ + ret = 0; + break; + case V4L2_CID_NOTIFY_GAINS: + ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_BLUE, + imx708->notify_gains->p_new.p_u32[0]); + if (ret) + break; + ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_RED, + imx708->notify_gains->p_new.p_u32[3]); + break; + default: + WARN_ONCE(1, "imx708: unhandled ctrl id 0x%x\n", ctrl->id); + ret = -EINVAL; + break; + } + + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx708_ctrl_ops = { + .s_ctrl = imx708_set_ctrl, +}; + +static int imx708_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx708 *imx708 = to_imx708(sd); + + if (code->index >= 1) + return -EINVAL; + + code->code = imx708_get_format_code(imx708); + + return 0; +} + +static int imx708_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx708 *imx708 = to_imx708(sd); + const struct imx708_mode *mode_list; + unsigned int num_modes; + + get_mode_table(&mode_list, &num_modes, imx708->hdr_mode->val); + + if (fse->index >= num_modes) + return -EINVAL; + + if (fse->code != imx708_get_format_code(imx708)) + return -EINVAL; + + fse->min_width = mode_list[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = mode_list[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int imx708_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct imx708 *imx708 = to_imx708(sd); + const struct imx708_mode *mode_list; + unsigned int num_modes; + + if (fie->pad != 0) + return -EINVAL; + + get_mode_table(&mode_list, &num_modes, imx708->hdr_mode->val); + + if (fie->index >= num_modes) + return -EINVAL; + + if (fie->code != imx708_get_format_code(imx708)) + return -EINVAL; + + fie->width = mode_list[fie->index].width; + fie->height = mode_list[fie->index].height; + imx708_calc_frame_interval(&mode_list[fie->index], + mode_list[fie->index].vblank_default, + &fie->interval); + + return 0; +} + +static int imx708_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx708 *imx708 = to_imx708(sd); + struct v4l2_mbus_framefmt *framefmt; + + framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); + fmt->format = *framefmt; + fmt->format.code = imx708_get_format_code(imx708); + + return 0; +} + +static int imx708_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx708 *imx708 = to_imx708(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *framefmt; + struct v4l2_rect *crop; + const struct imx708_mode *mode; + u32 req_width = fmt->format.width; + u32 req_height = fmt->format.height; + + /* Bayer order varies with flips */ + fmt->format.code = imx708_get_format_code(imx708); + + mode = imx708_find_mode(imx708->hdr_mode->val, + fmt->format.width, fmt->format.height); + imx708_update_image_pad_format(&fmt->format, mode); + + framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); + crop = v4l2_subdev_state_get_crop(sd_state, fmt->pad); + *framefmt = fmt->format; + *crop = mode->crop; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + int ret; + + imx708->mode = mode; + ret = imx708_set_framing_limits(imx708); + if (ret) + return ret; + + dev_dbg(&client->dev, + "set_fmt active: req=%ux%u selected=%ux%u hdr=%u code=0x%x\n", + req_width, req_height, mode->width, mode->height, + imx708->hdr_mode ? imx708->hdr_mode->val : 0, + fmt->format.code); + imx708_log_mode_timing(imx708, "set_fmt"); + } + + return 0; +} + +static int imx708_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, sel->pad); + return 0; + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = IMX708_NATIVE_WIDTH; + sel->r.height = IMX708_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = IMX708_PIXEL_ARRAY_LEFT; + sel->r.top = IMX708_PIXEL_ARRAY_TOP; + sel->r.width = IMX708_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX708_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + +static int imx708_get_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval *fi) +{ + struct imx708 *imx708 = to_imx708(sd); + + if (fi->pad != 0) + return -EINVAL; + + imx708_calc_frame_interval(imx708->mode, imx708->vblank->val, + &fi->interval); + + return 0; +} + +/* Start streaming */ +static int imx708_start_streaming(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + const struct imx708_reg_list *reg_list; + const struct imx708_reg_list *freq_regs; + int qbc_strength; + int i, ret; + u64 val; + + imx708_log_mode_timing(imx708, "start_streaming"); + + if (!imx708->common_regs_written) { + ret = imx708_write_regs(imx708, mode_common_regs, + ARRAY_SIZE(mode_common_regs)); + if (ret) { + dev_err(&client->dev, "%s failed to set common settings\n", + __func__); + return ret; + } + + ret = imx708_read_reg(imx708, IMX708_REG_BASE_SPC_GAINS_L, + &val); + if (ret == 0 && val == 0x40) { + for (i = 0; i < 54 && ret == 0; i++) { + u32 reg = CCI_REG8(CCI_REG_ADDR(IMX708_REG_BASE_SPC_GAINS_L) + + i); + + ret = imx708_write_reg(imx708, reg, + pdaf_gains[0][i % 9]); + } + for (i = 0; i < 54 && ret == 0; i++) { + u32 reg = CCI_REG8(CCI_REG_ADDR(IMX708_REG_BASE_SPC_GAINS_R) + + i); + + ret = imx708_write_reg(imx708, reg, + pdaf_gains[1][i % 9]); + } + } + if (ret) { + dev_err(&client->dev, "%s failed to set PDAF gains\n", + __func__); + return ret; + } + + imx708->common_regs_written = true; + } + + /* Apply default values of current mode */ + reg_list = &imx708->mode->reg_list; + ret = imx708_write_regs(imx708, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Update the link frequency registers after the mode table programs PLLs. */ + freq_regs = &link_freq_regs[imx708->link_freq_idx]; + ret = imx708_write_regs(imx708, freq_regs->regs, freq_regs->num_of_regs); + if (ret) { + dev_err(&client->dev, + "%s failed to set link frequency registers\n", __func__); + return ret; + } + + /* Quad Bayer re-mosaic adjustments (for full-resolution mode only). */ + qbc_strength = imx708_get_qbc_adjust(imx708); + if (imx708->mode->remosaic && qbc_strength > 0) { + ret = imx708_write_reg(imx708, IMX708_LPF_INTENSITY, + qbc_strength); + if (ret) + return ret; + ret = imx708_write_reg(imx708, IMX708_LPF_INTENSITY_EN, + IMX708_LPF_INTENSITY_ENABLED); + } else { + ret = imx708_write_reg(imx708, IMX708_LPF_INTENSITY_EN, + IMX708_LPF_INTENSITY_DISABLED); + } + if (ret) + return ret; + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx708->sd.ctrl_handler); + if (ret) + return ret; + + /* set stream on register */ + ret = imx708_write_reg(imx708, IMX708_REG_MODE_SELECT, + IMX708_MODE_STREAMING); + if (ret) + return ret; + + return 0; +} + +/* Stop streaming */ +static void imx708_stop_streaming(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int ret; + + /* set stream off register */ + ret = imx708_write_reg(imx708, IMX708_REG_MODE_SELECT, + IMX708_MODE_STANDBY); + if (ret) + dev_err(&client->dev, "%s failed to set stream\n", __func__); +} + +static int imx708_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx708 *imx708 = to_imx708(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&imx708->mutex); + if (imx708->streaming == enable) { + dev_dbg(&client->dev, "set_stream: enable=%d already in requested state\n", + enable); + mutex_unlock(&imx708->mutex); + return 0; + } + + dev_dbg(&client->dev, "set_stream: enable=%d begin\n", enable); + + if (enable) { + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) { + dev_err(&client->dev, + "set_stream: runtime resume failed: %d\n", ret); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx708_start_streaming(imx708); + if (ret) { + dev_err(&client->dev, "set_stream: start_streaming failed: %d\n", + ret); + goto err_rpm_put; + } + } else { + imx708_stop_streaming(imx708); + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + } + + imx708->streaming = enable; + + /* vflip/hflip and hdr mode cannot change during streaming */ + __v4l2_ctrl_grab(imx708->vflip, enable); + __v4l2_ctrl_grab(imx708->hflip, enable); + __v4l2_ctrl_grab(imx708->hdr_mode, enable); + dev_dbg(&client->dev, "set_stream: enable=%d done\n", enable); + + mutex_unlock(&imx708->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put_sync(&client->dev); +err_unlock: + mutex_unlock(&imx708->mutex); + + return ret; +} + +/* Power/clock management functions */ +static int imx708_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + int ret; + + ret = regulator_bulk_enable(IMX708_NUM_SUPPLIES, + imx708->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + ret = clk_prepare_enable(imx708->xclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", + __func__); + goto reg_off; + } + + dev_dbg(&client->dev, + "power_on: xclk=%u reset_gpio=%s link=%lld\n", + imx708->xclk_freq, + imx708->reset_gpio ? "present" : "absent", + (long long)imx708_link_freq_menu[imx708->link_freq_idx]); + + /* + * XCLR is active-low: keep it asserted until supplies and clock are + * stable, then release the sensor by driving the line high. + */ + if (imx708->reset_gpio) + gpiod_set_value_cansleep(imx708->reset_gpio, 0); + usleep_range(IMX708_XCLR_MIN_DELAY_US, + IMX708_XCLR_MIN_DELAY_US + IMX708_XCLR_DELAY_RANGE_US); + + imx708_log_mode_timing(imx708, "power_on"); + + return 0; + +reg_off: + regulator_bulk_disable(IMX708_NUM_SUPPLIES, imx708->supplies); + return ret; +} + +static int imx708_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + + dev_dbg(&client->dev, "power_off\n"); + + if (imx708->reset_gpio) + gpiod_set_value_cansleep(imx708->reset_gpio, 1); + clk_disable_unprepare(imx708->xclk); + regulator_bulk_disable(IMX708_NUM_SUPPLIES, imx708->supplies); + + /* Force reprogramming of the common registers when powered up again. */ + imx708->common_regs_written = false; + + return 0; +} + +static int imx708_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + + if (imx708->streaming) + imx708_stop_streaming(imx708); + + return 0; +} + +static int imx708_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + int ret; + + if (imx708->streaming) { + ret = imx708_start_streaming(imx708); + if (ret) + goto error; + } + + return 0; + +error: + imx708_stop_streaming(imx708); + imx708->streaming = 0; + return ret; +} + +static int imx708_get_regulators(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + unsigned int i; + + for (i = 0; i < IMX708_NUM_SUPPLIES; i++) + imx708->supplies[i].supply = imx708_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, + IMX708_NUM_SUPPLIES, + imx708->supplies); +} + +/* Verify chip ID */ +static int imx708_identify_module(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int ret, attempt; + u64 val; + + /* + * Some IMX708 modules take several milliseconds after power-on + * before the I2C interface becomes responsive. Retry chip ID + * reads to accommodate this hardware ramp-up latency. + */ + for (attempt = 0; attempt < 10; attempt++) { + ret = imx708_read_reg(imx708, IMX708_REG_CHIP_ID, &val); + if (!ret) + break; + usleep_range(5000, 6000); + } + if (ret) { + dev_err(&client->dev, + "failed to read chip id %x after %d attempts, error %d\n", + IMX708_CHIP_ID, attempt, ret); + return ret; + } + + if (val != IMX708_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + IMX708_CHIP_ID, (unsigned int)val); + return -EIO; + } + + return 0; +} + +static int imx708_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_config *config) +{ + struct imx708 *imx708 = to_imx708(sd); + + if (pad != 0) + return -EINVAL; + + config->type = V4L2_MBUS_CSI2_DPHY; + config->link_freq = imx708_link_freq_menu[imx708->link_freq_idx]; + config->bus.mipi_csi2.num_data_lanes = 2; + config->bus.mipi_csi2.flags = imx708->csi2_flags; + + return 0; +} + +static int imx708_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct imx708 *imx708 = to_imx708(sd); + const struct imx708_mode *mode_list; + unsigned int num_modes; + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .pad = 0, + }; + + get_mode_table(&mode_list, &num_modes, + imx708->hdr_mode && imx708->hdr_mode->val); + fmt.format.width = mode_list[0].width; + fmt.format.height = mode_list[0].height; + + return imx708_set_pad_format(sd, sd_state, &fmt); +} + +static const struct v4l2_subdev_core_ops imx708_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops imx708_video_ops = { + .s_stream = imx708_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx708_pad_ops = { + .enum_mbus_code = imx708_enum_mbus_code, + .enum_frame_interval = imx708_enum_frame_interval, + .get_fmt = imx708_get_pad_format, + .set_fmt = imx708_set_pad_format, + .get_selection = imx708_get_selection, + .get_frame_interval = imx708_get_frame_interval, + .enum_frame_size = imx708_enum_frame_size, + .get_mbus_config = imx708_get_mbus_config, +}; + +static const struct v4l2_subdev_ops imx708_subdev_ops = { + .core = &imx708_core_ops, + .video = &imx708_video_ops, + .pad = &imx708_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx708_internal_ops = { + .init_state = imx708_init_state, +}; + +static const struct v4l2_ctrl_config imx708_notify_gains_ctrl = { + .ops = &imx708_ctrl_ops, + .id = V4L2_CID_NOTIFY_GAINS, + .type = V4L2_CTRL_TYPE_U32, + .min = IMX708_COLOUR_BALANCE_MIN, + .max = IMX708_COLOUR_BALANCE_MAX, + .step = IMX708_COLOUR_BALANCE_STEP, + .def = IMX708_COLOUR_BALANCE_DEFAULT, + .dims = { 4 }, + .elem_size = sizeof(u32), +}; + +/* Initialize control handlers */ +static int imx708_init_controls(struct imx708 *imx708) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + struct v4l2_fwnode_device_properties props; + unsigned int i; + int ret; + + ctrl_hdlr = &imx708->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 17); + if (ret) + return ret; + + mutex_init(&imx708->mutex); + ctrl_hdlr->lock = &imx708->mutex; + + /* By default, PIXEL_RATE is read only */ + imx708->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_PIXEL_RATE, + imx708->mode->pixel_rate, + imx708->mode->pixel_rate, 1, + imx708->mode->pixel_rate); + + imx708->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, NULL, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(imx708_link_freq_menu) - 1, + imx708->link_freq_idx, + imx708_link_freq_menu); + if (imx708->link_freq) + imx708->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* + * Create the controls here, but mode specific limits are setup + * in the imx708_set_framing_limits() call below. + */ + imx708->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_VBLANK, 0, 0xffff, 1, 0); + imx708->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_HBLANK, 0, 0xffff, 1, 0); + + imx708->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX708_EXPOSURE_MIN, + IMX708_EXPOSURE_MAX, + IMX708_EXPOSURE_STEP, + IMX708_EXPOSURE_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX708_ANA_GAIN_MIN, IMX708_ANA_GAIN_MAX, + IMX708_ANA_GAIN_STEP, IMX708_ANA_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX708_DGTL_GAIN_MIN, IMX708_DGTL_GAIN_MAX, + IMX708_DGTL_GAIN_STEP, IMX708_DGTL_GAIN_DEFAULT); + + imx708->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + imx708->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx708_test_pattern_menu) - 1, + 0, 0, imx708_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, &imx708_ctrl_ops, + V4L2_CID_TEST_PATTERN_RED + i, + IMX708_TEST_PATTERN_COLOUR_MIN, + IMX708_TEST_PATTERN_COLOUR_MAX, + IMX708_TEST_PATTERN_COLOUR_STEP, + IMX708_TEST_PATTERN_COLOUR_MAX); + /* The "Solid color" pattern is white by default */ + } + + imx708->notify_gains = v4l2_ctrl_new_custom(ctrl_hdlr, + &imx708_notify_gains_ctrl, + NULL); + + imx708->hdr_mode = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_WIDE_DYNAMIC_RANGE, + 0, 1, 1, 0); + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + /* + * IMX708 is typically used as a pluggable camera module. If the board + * description does not provide explicit metadata, fall back to a sane + * default so userspace still gets the recommended read-only controls. + */ + if (props.orientation == V4L2_FWNODE_PROPERTY_UNSET) + props.orientation = V4L2_FWNODE_ORIENTATION_EXTERNAL; + + if (props.rotation == V4L2_FWNODE_PROPERTY_UNSET) + props.rotation = 0; + + v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx708_ctrl_ops, &props); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + imx708->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + imx708->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + imx708->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + imx708->hdr_mode->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + imx708->sd.ctrl_handler = ctrl_hdlr; + + /* Setup exposure and frame/line length limits. */ + ret = imx708_set_framing_limits(imx708); + if (ret) + goto error; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&imx708->mutex); + + return ret; +} + +static void imx708_free_controls(struct imx708 *imx708) +{ + v4l2_ctrl_handler_free(imx708->sd.ctrl_handler); + mutex_destroy(&imx708->mutex); +} + +static int imx708_check_hwcfg(struct device *dev, struct imx708 *imx708) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret = -EINVAL; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { + dev_err(dev, "could not parse endpoint\n"); + goto error_out; + } + + /* 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"); + goto error_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"); + goto error_out; + } + + if (ep_cfg.nr_of_link_frequencies != 1) { + dev_err(dev, "exactly one link frequency must be provided\n"); + goto error_out; + } + + for (imx708->link_freq_idx = 0; + imx708->link_freq_idx < ARRAY_SIZE(imx708_link_freq_menu); + imx708->link_freq_idx++) { + if (ep_cfg.link_frequencies[0] == + imx708_link_freq_menu[imx708->link_freq_idx]) + break; + } + + if (imx708->link_freq_idx == ARRAY_SIZE(imx708_link_freq_menu)) { + dev_err(dev, "Link frequency not supported: %llu\n", + ep_cfg.link_frequencies[0]); + goto error_out; + } + + imx708->csi2_flags = ep_cfg.bus.mipi_csi2.flags; + + ret = 0; + +error_out: + v4l2_fwnode_endpoint_free(&ep_cfg); + fwnode_handle_put(endpoint); + + return ret; +} + +static int imx708_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct imx708 *imx708; + int ret; + + imx708 = devm_kzalloc(&client->dev, sizeof(*imx708), GFP_KERNEL); + if (!imx708) + return -ENOMEM; + + imx708_set_default_mode(imx708); + + v4l2_i2c_subdev_init(&imx708->sd, client, &imx708_subdev_ops); + + /* Check the hardware configuration in device tree */ + if (imx708_check_hwcfg(dev, imx708)) + return -EINVAL; + + imx708->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(imx708->regmap)) + return dev_err_probe(dev, PTR_ERR(imx708->regmap), + "failed to initialize CCI regmap\n"); + + /* Get system clock (xclk) */ + imx708->xclk = devm_v4l2_sensor_clk_get(dev, NULL); + if (IS_ERR(imx708->xclk)) + return dev_err_probe(dev, PTR_ERR(imx708->xclk), + "failed to get xclk\n"); + + imx708->xclk_freq = clk_get_rate(imx708->xclk); + if (imx708->xclk_freq != IMX708_XCLK_FREQ) + return dev_err_probe(dev, -EINVAL, + "xclk frequency not supported: %d Hz\n", + imx708->xclk_freq); + + ret = imx708_get_regulators(imx708); + if (ret) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + /* Request optional enable pin */ + imx708->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(imx708->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(imx708->reset_gpio), + "failed to get reset GPIO\n"); + + /* + * The sensor must be powered for imx708_identify_module() + * to be able to read the CHIP_ID register + */ + ret = imx708_power_on(dev); + if (ret) + return ret; + + ret = imx708_identify_module(imx708); + if (ret) + goto err_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); + + /* This needs the pm runtime to be registered. */ + ret = imx708_init_controls(imx708); + if (ret) + goto err_pm; + + /* Initialize subdev */ + imx708->sd.internal_ops = &imx708_internal_ops; + imx708->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + imx708->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + imx708->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&imx708->sd.entity, 1, &imx708->pad); + if (ret) { + dev_err(dev, "failed to init entity pads: %d\n", ret); + goto err_ctrls; + } + + imx708->sd.state_lock = &imx708->mutex; + ret = v4l2_subdev_init_finalize(&imx708->sd); + if (ret < 0) { + dev_err(dev, "subdev init error: %d\n", ret); + goto err_media_entity; + } + + ret = v4l2_async_register_subdev_sensor(&imx708->sd); + if (ret < 0) { + dev_err(dev, "failed to register sensor sub-device: %d\n", ret); + goto err_subdev_cleanup; + } + + /* + * Decrease the PM usage count. The device will get suspended after the + * autosuspend delay, turning the power off. + */ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; + +err_subdev_cleanup: + v4l2_subdev_cleanup(&imx708->sd); + +err_media_entity: + media_entity_cleanup(&imx708->sd.entity); + +err_ctrls: + imx708_free_controls(imx708); + +err_pm: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + +err_power_off: + imx708_power_off(dev); + + return ret; +} + +static void imx708_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + imx708_free_controls(imx708); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + imx708_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id imx708_dt_ids[] = { + { .compatible = "sony,imx708" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx708_dt_ids); + +static const struct dev_pm_ops imx708_pm_ops = { + SYSTEM_SLEEP_PM_OPS(imx708_suspend, imx708_resume) + RUNTIME_PM_OPS(imx708_power_off, imx708_power_on, NULL) +}; + +static struct i2c_driver imx708_i2c_driver = { + .probe = imx708_probe, + .remove = imx708_remove, + .driver = { + .name = "imx708", + .of_match_table = imx708_dt_ids, + .pm = pm_ptr(&imx708_pm_ops), + }, +}; + +module_i2c_driver(imx708_i2c_driver); + +MODULE_AUTHOR("David Plowman "); +MODULE_DESCRIPTION("Sony IMX708 sensor driver"); +MODULE_LICENSE("GPL");