From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: SENEMOS patch gate Date: Mon, 31 Aug 2026 13:12:35 +0300 Subject: [PATCH 18/23] senemos: complete Linux 6.18 Nabu hardware baseline --- .../devicetree/bindings/arm/qcom.yaml | 1 + .../touchscreen/novatek,nt36523-spi.yaml | 81 ++ .../bindings/input/xiaomi,nabu-keyboard.yaml | 57 ++ .../bindings/mfd/qcom,spmi-pmic.yaml | 4 + .../bindings/power/supply/idt,p9418.yaml | 67 ++ .../power/supply/qcom,pm8150b-fg.yaml | 63 ++ .../bindings/sound/qcom,sm8250.yaml | 1 + arch/arm64/boot/dts/qcom/pm8150.dtsi | 2 + arch/arm64/boot/dts/qcom/pm8150b.dtsi | 2 + arch/arm64/boot/dts/qcom/sm8150-wcd9340.dtsi | 3 +- .../sm8150-xiaomi-nabu-power-scaffold.dtsi | 6 +- .../boot/dts/qcom/sm8150-xiaomi-nabu.dts | 215 ++--- arch/arm64/boot/dts/qcom/sm8150.dtsi | 10 +- drivers/gpu/drm/msm/dsi/dsi_manager.c | 17 +- drivers/hid/Kconfig | 3 +- drivers/hid/hid-ids.h | 2 + drivers/hid/hid-xiaomi.c | 14 + drivers/input/misc/Kconfig | 16 +- drivers/input/misc/Makefile | 1 + drivers/input/misc/xiaomi-nabu-keyboard.c | 218 +++++ drivers/input/misc/xiaomi-nabu-tablet-mode.c | 44 +- drivers/input/touchscreen/nt36523/nt36xxx.c | 1 + drivers/net/wireless/ath/ath10k/core.c | 11 + drivers/net/wireless/ath/ath10k/snoc.c | 10 + drivers/power/supply/Kconfig | 8 + drivers/power/supply/Makefile | 1 + drivers/power/supply/idtp9418.c | 883 ++++++++++++++++++ drivers/power/supply/idtp9418.h | 224 +++++ include/linux/input/xiaomi-nabu-keyboard.h | 13 + senemos/README.md | 58 +- senemos/configs/nabu-minimal.config | 3 + 31 files changed, 1880 insertions(+), 159 deletions(-) create mode 100644 Documentation/devicetree/bindings/input/touchscreen/novatek,nt36523-spi.yaml create mode 100644 Documentation/devicetree/bindings/input/xiaomi,nabu-keyboard.yaml create mode 100644 Documentation/devicetree/bindings/power/supply/idt,p9418.yaml create mode 100644 Documentation/devicetree/bindings/power/supply/qcom,pm8150b-fg.yaml create mode 100644 drivers/input/misc/xiaomi-nabu-keyboard.c create mode 100644 drivers/power/supply/idtp9418.c create mode 100644 drivers/power/supply/idtp9418.h create mode 100644 include/linux/input/xiaomi-nabu-keyboard.h diff --git a/Documentation/devicetree/bindings/arm/qcom.yaml b/Documentation/devicetree/bindings/arm/qcom.yaml index 18b5ed044..69a5916fd 100644 --- a/Documentation/devicetree/bindings/arm/qcom.yaml +++ b/Documentation/devicetree/bindings/arm/qcom.yaml @@ -983,6 +983,7 @@ properties: - qcom,sm8150-mtp - sony,bahamut-generic - sony,griffin-generic + - xiaomi,nabu - const: qcom,sm8150 - items: diff --git a/Documentation/devicetree/bindings/input/touchscreen/novatek,nt36523-spi.yaml b/Documentation/devicetree/bindings/input/touchscreen/novatek,nt36523-spi.yaml new file mode 100644 index 000000000..31f9f0307 --- /dev/null +++ b/Documentation/devicetree/bindings/input/touchscreen/novatek,nt36523-spi.yaml @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/touchscreen/novatek,nt36523-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Novatek NT36523 SPI touchscreen controller + +maintainers: + - Senemos Nabu Kernel Team + +allOf: + - $ref: touchscreen.yaml# + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + const: novatek,nt36523-spi + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 9600000 + + irq-gpios: + maxItems: 1 + + firmware-name: + maxItems: 1 + + novatek,pen-support: + type: boolean + description: Enable the controller's active-pen input path. + + novatek,wgp-stylus: + type: boolean + description: Enable Novatek WGP stylus protocol support. + + novatek,double-tap-to-wake: + type: boolean + description: Keep gesture detection armed for double-tap wake. + + novatek,swrst-n8-addr: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Controller software-reset register address. + + novatek,spi-rd-fast-addr: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Optional fast SPI-read command register address. + + wakeup-source: true + +required: + - compatible + - reg + - spi-max-frequency + - irq-gpios + - firmware-name + - novatek,swrst-n8-addr + +unevaluatedProperties: false + +examples: + - | + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + touchscreen@0 { + compatible = "novatek,nt36523-spi"; + reg = <0>; + spi-max-frequency = <9600000>; + irq-gpios = <&tlmm 39 GPIO_ACTIVE_LOW>; + firmware-name = "novatek/novatek_nt36523_fw.bin"; + novatek,swrst-n8-addr = <0x03f0fe>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/input/xiaomi,nabu-keyboard.yaml b/Documentation/devicetree/bindings/input/xiaomi,nabu-keyboard.yaml new file mode 100644 index 000000000..62de45a9f --- /dev/null +++ b/Documentation/devicetree/bindings/input/xiaomi,nabu-keyboard.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/xiaomi,nabu-keyboard.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Xiaomi Pad 5 pogo keyboard-cover controller + +maintainers: + - MCC45TR + +description: | + The Xiaomi Pad 5 contains a USB keyboard controller whose power, reset and + physical cover-detect signals are wired to TLMM GPIOs. The controller + powers the keyboard cover through its pogo connector. + +properties: + compatible: + const: xiaomi,nabu-keyboard + + vdd-gpios: + maxItems: 1 + + reset-gpios: + maxItems: 1 + + detect-gpios: + maxItems: 1 + + pinctrl-names: + items: + - const: default + - const: sleep + + pinctrl-0: true + pinctrl-1: true + wakeup-source: true + +required: + - compatible + - vdd-gpios + - reset-gpios + - detect-gpios + +additionalProperties: false + +examples: + - | + #include + + keyboard-cover { + compatible = "xiaomi,nabu-keyboard"; + vdd-gpios = <&tlmm 82 GPIO_ACTIVE_HIGH>; + reset-gpios = <&tlmm 49 GPIO_ACTIVE_LOW>; + detect-gpios = <&tlmm 41 GPIO_ACTIVE_LOW>; + wakeup-source; + }; diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml index 078a6886f..a62e02079 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml @@ -180,6 +180,10 @@ patternProperties: type: object $ref: /schemas/rtc/qcom-pm8xxx-rtc.yaml# + "^fuel-gauge@[0-9a-f]+$": + type: object + $ref: /schemas/power/supply/qcom,pm8150b-fg.yaml# + "^temp-alarm@[0-9a-f]+$": type: object $ref: /schemas/thermal/qcom,spmi-temp-alarm.yaml# diff --git a/Documentation/devicetree/bindings/power/supply/idt,p9418.yaml b/Documentation/devicetree/bindings/power/supply/idt,p9418.yaml new file mode 100644 index 000000000..a2a5c22bd --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/idt,p9418.yaml @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/supply/idt,p9418.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: IDT P9418 wireless power transmitter + +maintainers: + - MCC45TR + +description: | + IDT P9418 transmitter used by Xiaomi Pad 5 for stylus detection, reverse + wireless charging, charge-state monitoring and stylus Bluetooth MAC + reporting. + +properties: + compatible: + const: idt,p9418 + + reg: + maxItems: 1 + + idt,irq-gpios: + maxItems: 1 + + idt,hall3-gpios: + maxItems: 1 + + idt,hall4-gpios: + maxItems: 1 + + idt,reverse-enable-gpios: + maxItems: 1 + + idt,reverse-boost-enable-gpios: + maxItems: 1 + +required: + - compatible + - reg + - idt,irq-gpios + - idt,hall3-gpios + - idt,hall4-gpios + - idt,reverse-enable-gpios + - idt,reverse-boost-enable-gpios + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + wireless-power@3b { + compatible = "idt,p9418"; + reg = <0x3b>; + idt,irq-gpios = <&tlmm 95 GPIO_ACTIVE_HIGH>; + idt,hall3-gpios = <&tlmm 101 GPIO_ACTIVE_HIGH>; + idt,hall4-gpios = <&tlmm 134 GPIO_ACTIVE_HIGH>; + idt,reverse-enable-gpios = <&tlmm 3 GPIO_ACTIVE_HIGH>; + idt,reverse-boost-enable-gpios = <&tlmm 47 GPIO_ACTIVE_HIGH>; + }; + }; diff --git a/Documentation/devicetree/bindings/power/supply/qcom,pm8150b-fg.yaml b/Documentation/devicetree/bindings/power/supply/qcom,pm8150b-fg.yaml new file mode 100644 index 000000000..a601d1c84 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/qcom,pm8150b-fg.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/supply/qcom,pm8150b-fg.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm PM8150B fuel gauge + +maintainers: + - Senemos Nabu Kernel Team + +allOf: + - $ref: power-supply.yaml# + +properties: + compatible: + const: qcom,pm8150b-fg + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + interrupt-names: + const: soc-delta + + monitored-battery: true + + power-supplies: true + +required: + - compatible + - reg + - interrupts + - interrupt-names + +additionalProperties: false + +examples: + - | + #include + #include + + spmi { + #address-cells = <2>; + #size-cells = <0>; + + pmic@2 { + compatible = "qcom,pm8150b", "qcom,spmi-pmic"; + reg = <0x2 SPMI_USID>; + #address-cells = <1>; + #size-cells = <0>; + + fuel-gauge@4000 { + compatible = "qcom,pm8150b-fg"; + reg = <0x4000>; + interrupts = <0x2 0x40 0x3 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "soc-delta"; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml index b49a920af..34adefc5d 100644 --- a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml @@ -43,6 +43,7 @@ properties: - qcom,sc7180-qdsp6-sndcard - qcom,sc8280xp-sndcard - qcom,sdm845-sndcard + - qcom,sm8150-sndcard - qcom,sm8250-sndcard - qcom,sm8450-sndcard - qcom,x1e80100-sndcard diff --git a/arch/arm64/boot/dts/qcom/pm8150.dtsi b/arch/arm64/boot/dts/qcom/pm8150.dtsi index d2568686a..a74a7ff66 100644 --- a/arch/arm64/boot/dts/qcom/pm8150.dtsi +++ b/arch/arm64/boot/dts/qcom/pm8150.dtsi @@ -49,6 +49,8 @@ pm8150_0: pmic@0 { pon: pon@800 { compatible = "qcom,pm8998-pon"; reg = <0x0800>; + mode-bootloader = <0x2>; + mode-recovery = <0x1>; pon_pwrkey: pwrkey { compatible = "qcom,pm8941-pwrkey"; diff --git a/arch/arm64/boot/dts/qcom/pm8150b.dtsi b/arch/arm64/boot/dts/qcom/pm8150b.dtsi index d1fc0289a..45c62414a 100644 --- a/arch/arm64/boot/dts/qcom/pm8150b.dtsi +++ b/arch/arm64/boot/dts/qcom/pm8150b.dtsi @@ -56,6 +56,8 @@ pm8150b_vbus: usb-vbus-regulator@1100 { compatible = "qcom,pm8150b-vbus-reg"; status = "disabled"; reg = <0x1100>; + regulator-min-microamp = <500000>; + regulator-max-microamp = <3000000>; }; pm8150b_typec: typec@1500 { diff --git a/arch/arm64/boot/dts/qcom/sm8150-wcd9340.dtsi b/arch/arm64/boot/dts/qcom/sm8150-wcd9340.dtsi index 2b7db96dd..283bc8bcd 100644 --- a/arch/arm64/boot/dts/qcom/sm8150-wcd9340.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8150-wcd9340.dtsi @@ -54,7 +54,7 @@ wcdgpio: gpio-controller@42 { reg = <0x42 0x2>; }; - swm: swm@c85 { + swm: soundwire@c85 { compatible = "qcom,soundwire-v1.3.0"; reg = <0xc85 0x40>; interrupts-extended = <&wcd9340 20>; @@ -81,6 +81,5 @@ wcd_intr_default: wcd-intr-default-state { function = "gpio"; drive-strength = <2>; bias-pull-down; - input-enable; }; }; diff --git a/arch/arm64/boot/dts/qcom/sm8150-xiaomi-nabu-power-scaffold.dtsi b/arch/arm64/boot/dts/qcom/sm8150-xiaomi-nabu-power-scaffold.dtsi index 861062547..37503bb1c 100644 --- a/arch/arm64/boot/dts/qcom/sm8150-xiaomi-nabu-power-scaffold.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8150-xiaomi-nabu-power-scaffold.dtsi @@ -152,7 +152,7 @@ backlight-therm@0 { }; &{/thermal-zones} { - nabu-charger-thermal { + charger-thermal { polling-delay-passive = <0>; polling-delay = <0>; thermal-sensors = <&pm8150_adc_tm 2>; @@ -167,7 +167,7 @@ nabu_charger_alert: charger-alert { }; }; - nabu-connector-thermal { + connector-thermal { polling-delay-passive = <0>; polling-delay = <0>; thermal-sensors = <&pm8150b_adc_tm 1>; @@ -182,7 +182,7 @@ nabu_connector_alert: connector-alert { }; }; - nabu-backlight-thermal { + backlight-thermal { polling-delay-passive = <0>; polling-delay = <0>; thermal-sensors = <&pm8150l_adc_tm 0>; diff --git a/arch/arm64/boot/dts/qcom/sm8150-xiaomi-nabu.dts b/arch/arm64/boot/dts/qcom/sm8150-xiaomi-nabu.dts index 08c007aff..a496d9f71 100644 --- a/arch/arm64/boot/dts/qcom/sm8150-xiaomi-nabu.dts +++ b/arch/arm64/boot/dts/qcom/sm8150-xiaomi-nabu.dts @@ -140,6 +140,18 @@ battery: battery { voltage-max-design-microvolt = <4480000>; }; + xiaomi_keyboard: keyboard-cover { + compatible = "xiaomi,nabu-keyboard"; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&keyboard_active>; + pinctrl-1 = <&keyboard_suspend>; + vdd-gpios = <&tlmm 82 GPIO_ACTIVE_HIGH>; + reset-gpios = <&tlmm 49 GPIO_ACTIVE_LOW>; + detect-gpios = <&tlmm 41 GPIO_ACTIVE_LOW>; + wakeup-source; + status = "okay"; + }; + bl_vddpos_5p5: bl-vddpos-regulator { compatible = "regulator-fixed"; regulator-name = "bl_vddpos_5p5"; @@ -214,7 +226,7 @@ event-hall-sensor { }; &apps_rsc { - pm8150-rpmh-regulators { + regulators-0 { compatible = "qcom,pm8150-rpmh-regulators"; qcom,pmic-id = "a"; @@ -337,7 +349,7 @@ vreg_l17a_3p0: ldo17 { }; }; - pm8150l-rpmh-regulators { + regulators-1 { compatible = "qcom,pm8150l-rpmh-regulators"; qcom,pmic-id = "c"; @@ -723,6 +735,8 @@ pm8150b_sbu_mux_in: endpoint { }; &pm8150b_vbus { + regulator-min-microamp = <500000>; + regulator-max-microamp = <3000000>; status = "okay"; }; @@ -857,9 +871,11 @@ codec { &spi18 { status = "okay"; + /delete-property/ reg-names; + /delete-property/ spi-max-frequency; touchscreen@0 { - compatible = "novatek,NVT-ts-spi"; + compatible = "novatek,nt36523-spi"; reg = <0>; spi-max-frequency = <9600000>; @@ -896,7 +912,37 @@ quat_tdm_sd1_active: quat-tdm-sd1-active-state { bias-disable; }; - qup_uart13_sleep: qup-uart13-sleep { + qup_uart13_default: qup-uart13-default-state { + cts-pins { + pins = "gpio43"; + function = "qup13"; + drive-strength = <2>; + bias-bus-hold; + }; + + rts-pins { + pins = "gpio44"; + function = "qup13"; + drive-strength = <2>; + bias-disable; + }; + + tx-pins { + pins = "gpio45"; + function = "qup13"; + drive-strength = <2>; + bias-disable; + }; + + rx-pins { + pins = "gpio46"; + function = "qup13"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + qup_uart13_sleep: qup-uart13-sleep-state { pinmux { pins = "gpio43", "gpio44", "gpio45", "gpio46"; @@ -944,124 +990,47 @@ pinconf-rx { }; bl_en_state: bl-default-state { - bl-en { - pins = "gpio27"; - function = "gpio"; - - drive-strength = <16>; - output-high; - bias-disable; - }; + pins = "gpio27"; + function = "gpio"; + drive-strength = <16>; + output-high; + bias-disable; }; keyboard_active: keyboard-active-state { - sleep-pins { + data-pins { pins = "gpio4"; function = "gpio"; drive-strength = <8>; bias-pull-up; }; - reset-pins { - pins = "gpio49"; - function = "gpio"; - drive-strength = <4>; - bias-pull-up; - output-high; - }; - - vdd-pins { - pins = "gpio82"; + detect-pins { + pins = "gpio41"; function = "gpio"; - drive-strength = <4>; + drive-strength = <2>; bias-pull-up; - output-high; + input-enable; }; }; keyboard_suspend: keyboard-suspend-state { - sleep-pins { + data-pins { pins = "gpio4"; function = "gpio"; drive-strength = <8>; bias-pull-down; }; - reset-pins { - pins = "gpio49"; - function = "gpio"; - drive-strength = <4>; - bias-pull-down; - }; - - vdd-pins { - pins = "gpio82"; + detect-pins { + pins = "gpio41"; function = "gpio"; - drive-strength = <4>; - bias-pull-down; + drive-strength = <2>; + bias-pull-up; + input-enable; }; }; - hall { - hall_intr3: hall_intr3 { - mux { - pins = "gpio101"; - function = "gpio"; - }; - - config { - pins = "gpio101"; - drive-strength = <2>; - bias-pull-up; - input-enable; - }; - }; - - hall_intr4: hall_intr4 { - mux { - pins = "gpio134"; - function = "gpio"; - }; - - config { - pins = "gpio134"; - drive-strength = <2>; - bias-pull-up; - input-enable; - }; - }; - }; - - idt { - idt_int: idt_int { - mux { - pins = "gpio95"; - function = "gpio"; - }; - - config { - pins = "gpio95"; - drive-strength = <2>; - bias-pull-up; - input-enable; - }; - }; - - txon_enable: txon_enable { - mux { - pins = "gpio3"; - function = "gpio"; - }; - - config { - pins = "gpio3"; - drive-strength = <16>; - bias-disable; - bias-pull-down; - output-low; - }; - }; - }; }; &uart2 { @@ -1076,6 +1045,7 @@ &uart13 { <&tlmm 46 IRQ_TYPE_EDGE_FALLING>; pinctrl-names = "default", "sleep"; + pinctrl-0 = <&qup_uart13_default>; pinctrl-1 = <&qup_uart13_sleep>; bluetooth: bluetooth { @@ -1106,9 +1076,26 @@ &ufs_mem_phy { status = "okay"; vdda-phy-supply = <&vreg_l5a_0p8>; - vdda-phy-max-microamp = <90200>; vdda-pll-supply = <&vreg_l3c_1p2>; - vdda-pll-max-microamp = <19000>; +}; + +&wcd9340 { + reset-gpios = <&tlmm 143 GPIO_ACTIVE_HIGH>; + vdd-buck-sido-supply = <&vreg_s4a_1p8>; + vdd-buck-supply = <&vreg_s4a_1p8>; + vdd-tx-supply = <&vreg_s4a_1p8>; + vdd-rx-supply = <&vreg_s4a_1p8>; + vdd-io-supply = <&vreg_s4a_1p8>; + + qcom,micbias1-microvolt = <1800000>; + qcom,micbias2-microvolt = <2750000>; + 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 = <2600000>; + qcom,mbhc-headphone-vthreshold-microvolt = <50000>; }; &usb_1 { @@ -1148,9 +1135,6 @@ &usb_2_hsphy { vdda18-supply = <&vreg_l12a_1p8>; vdda33-supply = <&vreg_l2a_3p1>; status = "okay"; - pinctrl-0 = <&keyboard_active>; - pinctrl-1 = <&keyboard_suspend>; - pinctrl-names = "default", "sleep"; }; &usb_2_dwc3 { @@ -1173,16 +1157,21 @@ &wifi { &i2c17 { status = "okay"; - idtp9418: idtp9418@3b { - compatible = "idt,p9418"; - reg = <0x3b>; - status = "okay"; - irq-gpios = <&tlmm 95 GPIO_ACTIVE_HIGH>; - pen-det-active-high-gpios = <&tlmm 101 GPIO_ACTIVE_HIGH>; - pen-det-active-low-gpios =<&tlmm 134 GPIO_ACTIVE_HIGH>; - enable-gpios = <&tlmm 3 GPIO_ACTIVE_HIGH>; - boost-enable-gpios = <&tlmm 47 GPIO_ACTIVE_HIGH>; - }; + idtp9418: wireless-power@3b { + compatible = "idt,p9418"; + reg = <0x3b>; + status = "okay"; + idt,irq-gpios = <&tlmm 95 GPIO_ACTIVE_HIGH>; + idt,hall3-gpios = <&tlmm 101 GPIO_ACTIVE_HIGH>; + idt,hall4-gpios = <&tlmm 134 GPIO_ACTIVE_HIGH>; + idt,reverse-enable-gpios = <&tlmm 3 GPIO_ACTIVE_HIGH>; + idt,reverse-boost-enable-gpios = <&tlmm 47 GPIO_ACTIVE_HIGH>; + }; }; -#include "sm8150-xiaomi-nabu-power-scaffold.dtsi" +/* + * The high-power charge-pump and board-thermistor scaffold remains in the + * source tree for future hardware qualification. It is deliberately not + * included in the production DTB until matching drivers and physical safety + * limits have been validated on Nabu. + */ diff --git a/arch/arm64/boot/dts/qcom/sm8150.dtsi b/arch/arm64/boot/dts/qcom/sm8150.dtsi index 9ab2660cd..e047aae66 100644 --- a/arch/arm64/boot/dts/qcom/sm8150.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8150.dtsi @@ -4398,12 +4398,12 @@ apr { #address-cells = <1>; #size-cells = <0>; - apr-service@3 { + service@3 { reg = ; compatible = "qcom,q6core"; }; - q6afe: apr-service@4 { + q6afe: service@4 { compatible = "qcom,q6afe"; reg = ; q6afedai: dais { @@ -4413,13 +4413,13 @@ q6afedai: dais { #sound-dai-cells = <1>; }; - q6afecc: cc { + q6afecc: clock-controller { compatible = "qcom,q6afe-clocks"; #clock-cells = <2>; }; }; - q6asm: apr-service@7 { + q6asm: service@7 { compatible = "qcom,q6asm"; reg = ; q6asmdai: dais { @@ -4431,7 +4431,7 @@ q6asmdai: dais { }; }; - q6adm: apr-service@8 { + q6adm: service@8 { compatible = "qcom,q6adm"; reg = ; q6routing: routing { diff --git a/drivers/gpu/drm/msm/dsi/dsi_manager.c b/drivers/gpu/drm/msm/dsi/dsi_manager.c index 57b500e22..00e65a862 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_manager.c +++ b/drivers/gpu/drm/msm/dsi/dsi_manager.c @@ -276,6 +276,18 @@ static void dsi_mgr_bridge_power_off(struct drm_bridge *bridge) dsi_mgr_phy_disable(id); } +static void dsi_mgr_bridge_enable(struct drm_bridge *bridge) +{ + enum drm_notifier_data notifier_data = MI_DRM_BLANK_UNBLANK; + + /* + * The panel bridge is enabled before this callback. Notify the touch + * controller only after the complete display pipeline is ready; resuming + * touch from pre_enable() races DSI host/panel bring-up on Nabu. + */ + mi_drm_notifier_call_chain(MI_DRM_EVENT_BLANK, ¬ifier_data); +} + static void dsi_mgr_bridge_pre_enable(struct drm_bridge *bridge) { int id = dsi_mgr_bridge_get_id(bridge); @@ -283,7 +295,6 @@ static void dsi_mgr_bridge_pre_enable(struct drm_bridge *bridge) struct msm_dsi *msm_dsi1 = dsi_mgr_get_dsi(DSI_1); struct mipi_dsi_host *host = msm_dsi->host; bool is_bonded_dsi = IS_BONDED_DSI(); - enum drm_notifier_data notifier_data; int ret; DBG("id=%d", id); @@ -298,9 +309,6 @@ static void dsi_mgr_bridge_pre_enable(struct drm_bridge *bridge) return; } - notifier_data = MI_DRM_BLANK_UNBLANK; - mi_drm_notifier_call_chain(MI_DRM_EVENT_BLANK, ¬ifier_data); - ret = msm_dsi_host_enable(host); if (ret) { pr_err("%s: enable host %d failed, %d\n", __func__, id, ret); @@ -521,6 +529,7 @@ static int dsi_mgr_bridge_attach(struct drm_bridge *bridge, static const struct drm_bridge_funcs dsi_mgr_bridge_funcs = { .attach = dsi_mgr_bridge_attach, .pre_enable = dsi_mgr_bridge_pre_enable, + .enable = dsi_mgr_bridge_enable, .post_disable = dsi_mgr_bridge_post_disable, .mode_set = dsi_mgr_bridge_mode_set, .mode_valid = dsi_mgr_bridge_mode_valid, diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 03ad8b5c2..52e4cab10 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -546,7 +546,8 @@ config HID_XIAOMI tristate "Xiaomi" help Adds support for side buttons of Xiaomi Mi Dual Mode Wireless - Mouse Silent Edition. + Mouse Silent Edition and filters the phantom pointer interface of + the Xiaomi Pad 5 keyboard-cover controller. config HID_GYRATION tristate "Gyration remote control" diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 96a9fa53e..aeb04e303 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1501,6 +1501,8 @@ #define USB_VENDOR_ID_XIAOMI 0x2717 #define USB_DEVICE_ID_MI_SILENT_MOUSE 0x5014 +#define USB_VENDOR_ID_NANO_IC 0x3206 +#define USB_DEVICE_ID_XIAOMI_NABU_KEYBOARD 0x3ffc #define USB_VENDOR_ID_XIN_MO 0x16c0 #define USB_DEVICE_ID_XIN_MO_DUAL_ARCADE 0x05e1 diff --git a/drivers/hid/hid-xiaomi.c b/drivers/hid/hid-xiaomi.c index ef6598550..615163721 100644 --- a/drivers/hid/hid-xiaomi.c +++ b/drivers/hid/hid-xiaomi.c @@ -76,8 +76,21 @@ static const __u8 *xiaomi_report_fixup(struct hid_device *hdev, __u8 *rdesc, return rdesc; } +static int xiaomi_input_configured(struct hid_device *hdev, + struct hid_input *hidinput) +{ + /* The Nabu keyboard controller exposes a non-functional mouse. */ + if (hdev->vendor == USB_VENDOR_ID_NANO_IC && + hdev->product == USB_DEVICE_ID_XIAOMI_NABU_KEYBOARD) + __clear_bit(EV_REL, hidinput->input->evbit); + + return 0; +} + static const struct hid_device_id xiaomi_devices[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_XIAOMI, USB_DEVICE_ID_MI_SILENT_MOUSE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NANO_IC, + USB_DEVICE_ID_XIAOMI_NABU_KEYBOARD) }, { } }; MODULE_DEVICE_TABLE(hid, xiaomi_devices); @@ -86,6 +99,7 @@ static struct hid_driver xiaomi_driver = { .name = "xiaomi", .id_table = xiaomi_devices, .report_fixup = xiaomi_report_fixup, + .input_configured = xiaomi_input_configured, }; module_hid_driver(xiaomi_driver); diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 40fddb64f..d652a726e 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -993,12 +993,20 @@ config INPUT_STPMIC1_ONKEY module will be called stpmic1_onkey. config INPUT_XIAOMI_NABU_TABLET_MODE - bool "Xiaomi Pad 5 permanent tablet-mode switch" + bool "Xiaomi Pad 5 keyboard-aware tablet-mode switch" depends on ARCH_QCOM help - Report the Xiaomi Pad 5 as permanently operating in tablet mode. - Desktop environments otherwise disable automatic rotation whenever a - USB pointer is present, including the SENEMOS recovery bridge mouse. + Report standard SW_TABLET_MODE state and allow the Xiaomi Pad 5 + keyboard-cover driver to switch it off while the cover is attached. + +config INPUT_XIAOMI_NABU_KEYBOARD + tristate "Xiaomi Pad 5 keyboard-cover controller" + depends on ARCH_QCOM && GPIOLIB + select INPUT_XIAOMI_NABU_TABLET_MODE + help + Control power/reset for the internal pogo-keyboard USB controller, + debounce its physical detect line, publish connection uevents, and + update the standard tablet-mode input switch. Say Y when building a kernel for Xiaomi Pad 5 (nabu). diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 97528a30b..7587f5f18 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -96,3 +96,4 @@ obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o obj-$(CONFIG_INPUT_YEALINK) += yealink.o obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o obj-$(CONFIG_INPUT_XIAOMI_NABU_TABLET_MODE) += xiaomi-nabu-tablet-mode.o +obj-$(CONFIG_INPUT_XIAOMI_NABU_KEYBOARD) += xiaomi-nabu-keyboard.o diff --git a/drivers/input/misc/xiaomi-nabu-keyboard.c b/drivers/input/misc/xiaomi-nabu-keyboard.c new file mode 100644 index 000000000..c10ec69d5 --- /dev/null +++ b/drivers/input/misc/xiaomi-nabu-keyboard.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Xiaomi Pad 5 pogo keyboard-cover power and presence controller. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NABU_KEYBOARD_DEBOUNCE_MS 35 + +struct nabu_keyboard { + struct device *dev; + struct gpio_desc *vdd; + struct gpio_desc *reset; + struct gpio_desc *detect; + struct delayed_work detect_work; + int irq; + bool connected; + bool wake_enabled; +}; + +static void nabu_keyboard_publish_state(struct nabu_keyboard *keyboard, + bool connected, bool force) +{ + char state[40]; + char *envp[2]; + + if (!force && keyboard->connected == connected) + return; + + keyboard->connected = connected; + xiaomi_nabu_keyboard_set_attached(connected); + snprintf(state, sizeof(state), "XIAOMI_KEYBOARD_CONNECTED=%u", + connected); + envp[0] = state; + envp[1] = NULL; + kobject_uevent_env(&keyboard->dev->kobj, KOBJ_CHANGE, envp); + dev_info(keyboard->dev, "keyboard cover %s\n", + connected ? "attached" : "detached"); +} + +static void nabu_keyboard_detect_work(struct work_struct *work) +{ + struct nabu_keyboard *keyboard; + int value; + + keyboard = container_of(to_delayed_work(work), struct nabu_keyboard, + detect_work); + value = gpiod_get_value_cansleep(keyboard->detect); + if (value < 0) { + dev_err_ratelimited(keyboard->dev, + "failed to read keyboard detect GPIO: %d\n", + value); + return; + } + + nabu_keyboard_publish_state(keyboard, value, false); +} + +static irqreturn_t nabu_keyboard_detect_irq(int irq, void *data) +{ + struct nabu_keyboard *keyboard = data; + + pm_wakeup_event(keyboard->dev, 500); + mod_delayed_work(system_wq, &keyboard->detect_work, + msecs_to_jiffies(NABU_KEYBOARD_DEBOUNCE_MS)); + return IRQ_HANDLED; +} + +static ssize_t connected_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nabu_keyboard *keyboard = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%u\n", keyboard->connected); +} +static DEVICE_ATTR_RO(connected); + +static int nabu_keyboard_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nabu_keyboard *keyboard; + int connected; + int ret; + + keyboard = devm_kzalloc(dev, sizeof(*keyboard), GFP_KERNEL); + if (!keyboard) + return -ENOMEM; + + keyboard->dev = dev; + keyboard->vdd = devm_gpiod_get(dev, "vdd", GPIOD_OUT_LOW); + if (IS_ERR(keyboard->vdd)) + return dev_err_probe(dev, PTR_ERR(keyboard->vdd), + "failed to acquire VDD GPIO\n"); + + keyboard->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(keyboard->reset)) + return dev_err_probe(dev, PTR_ERR(keyboard->reset), + "failed to acquire reset GPIO\n"); + + keyboard->detect = devm_gpiod_get(dev, "detect", GPIOD_IN); + if (IS_ERR(keyboard->detect)) + return dev_err_probe(dev, PTR_ERR(keyboard->detect), + "failed to acquire detect GPIO\n"); + + platform_set_drvdata(pdev, keyboard); + INIT_DELAYED_WORK(&keyboard->detect_work, nabu_keyboard_detect_work); + + /* Power the internal USB controller and release its active-low reset. */ + gpiod_set_value_cansleep(keyboard->vdd, 1); + usleep_range(1000, 1500); + gpiod_set_value_cansleep(keyboard->reset, 0); + usleep_range(2000, 3000); + + keyboard->irq = gpiod_to_irq(keyboard->detect); + if (keyboard->irq < 0) { + ret = keyboard->irq; + goto err_power_off; + } + + ret = devm_request_threaded_irq(dev, keyboard->irq, NULL, + nabu_keyboard_detect_irq, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(dev), keyboard); + if (ret) + goto err_power_off; + + ret = device_create_file(dev, &dev_attr_connected); + if (ret) + goto err_power_off; + + device_init_wakeup(dev, + of_property_read_bool(dev->of_node, "wakeup-source")); + connected = gpiod_get_value_cansleep(keyboard->detect); + if (connected < 0) { + ret = connected; + goto err_remove_file; + } + nabu_keyboard_publish_state(keyboard, connected, true); + + return 0; + +err_remove_file: + device_remove_file(dev, &dev_attr_connected); +err_power_off: + gpiod_set_value_cansleep(keyboard->reset, 1); + gpiod_set_value_cansleep(keyboard->vdd, 0); + return ret; +} + +static void nabu_keyboard_remove(struct platform_device *pdev) +{ + struct nabu_keyboard *keyboard = platform_get_drvdata(pdev); + + device_remove_file(keyboard->dev, &dev_attr_connected); + cancel_delayed_work_sync(&keyboard->detect_work); + nabu_keyboard_publish_state(keyboard, false, true); + gpiod_set_value_cansleep(keyboard->reset, 1); + gpiod_set_value_cansleep(keyboard->vdd, 0); +} + +static int nabu_keyboard_suspend(struct device *dev) +{ + struct nabu_keyboard *keyboard = dev_get_drvdata(dev); + int ret; + + cancel_delayed_work_sync(&keyboard->detect_work); + if (!device_may_wakeup(dev)) + return 0; + + ret = enable_irq_wake(keyboard->irq); + if (!ret) + keyboard->wake_enabled = true; + return ret; +} + +static int nabu_keyboard_resume(struct device *dev) +{ + struct nabu_keyboard *keyboard = dev_get_drvdata(dev); + + if (keyboard->wake_enabled) { + disable_irq_wake(keyboard->irq); + keyboard->wake_enabled = false; + } + mod_delayed_work(system_wq, &keyboard->detect_work, + msecs_to_jiffies(NABU_KEYBOARD_DEBOUNCE_MS)); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(nabu_keyboard_pm_ops, + nabu_keyboard_suspend, nabu_keyboard_resume); + +static const struct of_device_id nabu_keyboard_of_match[] = { + { .compatible = "xiaomi,nabu-keyboard" }, + { } +}; +MODULE_DEVICE_TABLE(of, nabu_keyboard_of_match); + +static struct platform_driver nabu_keyboard_driver = { + .probe = nabu_keyboard_probe, + .remove = nabu_keyboard_remove, + .driver = { + .name = "xiaomi-nabu-keyboard", + .of_match_table = nabu_keyboard_of_match, + .pm = pm_sleep_ptr(&nabu_keyboard_pm_ops), + }, +}; +module_platform_driver(nabu_keyboard_driver); + +MODULE_DESCRIPTION("Xiaomi Pad 5 pogo keyboard-cover controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/xiaomi-nabu-tablet-mode.c b/drivers/input/misc/xiaomi-nabu-tablet-mode.c index 9306f5723..8d24156b5 100644 --- a/drivers/input/misc/xiaomi-nabu-tablet-mode.c +++ b/drivers/input/misc/xiaomi-nabu-tablet-mode.c @@ -1,17 +1,36 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Xiaomi Pad 5 permanent tablet-mode input switch. + * Xiaomi Pad 5 dynamic tablet-mode input switch. * - * Nabu has no convertible hinge: it is always a tablet. Exposing that fact - * through the standard SW_TABLET_MODE interface keeps desktop touch mode and - * automatic rotation active when a USB mouse or recovery bridge is attached. + * Nabu is a tablet unless its keyboard cover is physically attached. Exposing + * that state through SW_TABLET_MODE lets desktop environments switch between + * touch-first tablet mode and keyboard-first computer mode without mistaking + * an unrelated USB pointer for a convertible keyboard. */ #include #include +#include #include +#include + static struct input_dev *nabu_tablet_mode_input; +static DEFINE_MUTEX(nabu_tablet_mode_lock); +static bool nabu_keyboard_attached; + +void xiaomi_nabu_keyboard_set_attached(bool attached) +{ + mutex_lock(&nabu_tablet_mode_lock); + nabu_keyboard_attached = attached; + if (nabu_tablet_mode_input) { + input_report_switch(nabu_tablet_mode_input, SW_TABLET_MODE, + !attached); + input_sync(nabu_tablet_mode_input); + } + mutex_unlock(&nabu_tablet_mode_lock); +} +EXPORT_SYMBOL_GPL(xiaomi_nabu_keyboard_set_attached); static int __init nabu_tablet_mode_init(void) { @@ -36,9 +55,11 @@ static int __init nabu_tablet_mode_init(void) return ret; } + mutex_lock(&nabu_tablet_mode_lock); nabu_tablet_mode_input = input; - input_report_switch(input, SW_TABLET_MODE, 1); + input_report_switch(input, SW_TABLET_MODE, !nabu_keyboard_attached); input_sync(input); + mutex_unlock(&nabu_tablet_mode_lock); return 0; } @@ -46,10 +67,17 @@ module_init(nabu_tablet_mode_init); static void __exit nabu_tablet_mode_exit(void) { - if (nabu_tablet_mode_input) - input_unregister_device(nabu_tablet_mode_input); + struct input_dev *input; + + mutex_lock(&nabu_tablet_mode_lock); + input = nabu_tablet_mode_input; + nabu_tablet_mode_input = NULL; + mutex_unlock(&nabu_tablet_mode_lock); + + if (input) + input_unregister_device(input); } module_exit(nabu_tablet_mode_exit); -MODULE_DESCRIPTION("Xiaomi Pad 5 permanent tablet-mode switch"); +MODULE_DESCRIPTION("Xiaomi Pad 5 keyboard-aware tablet-mode switch"); MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/nt36523/nt36xxx.c b/drivers/input/touchscreen/nt36523/nt36xxx.c index 48864b7d3..f35acdae9 100644 --- a/drivers/input/touchscreen/nt36523/nt36xxx.c +++ b/drivers/input/touchscreen/nt36523/nt36xxx.c @@ -1953,6 +1953,7 @@ MODULE_DEVICE_TABLE(spi, nvt_ts_id); #ifdef CONFIG_OF static struct of_device_id nvt_match_table[] = { + { .compatible = "novatek,nt36523-spi",}, { .compatible = "novatek,NVT-ts-spi",}, { }, }; diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c index 9ae3595fb..03c7b5de0 100644 --- a/drivers/net/wireless/ath/ath10k/core.c +++ b/drivers/net/wireless/ath/ath10k/core.c @@ -2987,6 +2987,17 @@ int ath10k_core_start(struct ath10k *ar, enum ath10k_firmware_mode mode, ar->running_fw = fw; + /* + * WCN3990 HL firmware reports one channel-info event per scanned + * channel and finishes with a frequency-zero marker, while some shipped + * firmware-5.bin metadata omits the matching feature bit. Select the + * unpaired handler explicitly to avoid treating that marker as a channel + * array index ("frequency 0 / idx out of bounds"). + */ + if (QCA_REV_WCN3990(ar)) + set_bit(ATH10K_FW_FEATURE_SINGLE_CHAN_INFO_PER_CHANNEL, + ar->normal_mode_fw.fw_file.fw_features); + if (!test_bit(ATH10K_FW_FEATURE_NON_BMI, ar->running_fw->fw_file.fw_features)) { ath10k_bmi_start(ar); diff --git a/drivers/net/wireless/ath/ath10k/snoc.c b/drivers/net/wireless/ath/ath10k/snoc.c index b3f6424c1..ba210db04 100644 --- a/drivers/net/wireless/ath/ath10k/snoc.c +++ b/drivers/net/wireless/ath/ath10k/snoc.c @@ -1815,10 +1815,19 @@ static int ath10k_snoc_probe(struct platform_device *pdev) if (ret) goto err_qmi_deinit; + ret = device_init_wakeup(dev, true); + if (ret) { + ath10k_warn(ar, "failed to enable wakeup support: %d\n", ret); + goto err_modem_deinit; + } + ath10k_dbg(ar, ATH10K_DBG_SNOC, "snoc probe\n"); return 0; +err_modem_deinit: + ath10k_modem_deinit(ar); + err_qmi_deinit: ath10k_qmi_deinit(ar); @@ -1844,6 +1853,7 @@ static int ath10k_snoc_free_resources(struct ath10k *ar) ath10k_dbg(ar, ATH10K_DBG_SNOC, "snoc free resources\n"); set_bit(ATH10K_SNOC_FLAG_UNREGISTERING, &ar_snoc->flags); + device_init_wakeup(ar->dev, false); ath10k_core_unregister(ar); ath10k_fw_deinit(ar); diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 67164e96d..ab25e0390 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -1105,4 +1105,12 @@ config FUEL_GAUGE_MM8013 the state of charge, temperature, cycle count, actual and design capacity, etc. +config CHARGER_IDTP9418 + tristate "IDT P9418 wireless power transmitter driver" + depends on I2C + select REGMAP_I2C + help + Say Y or M here to enable the IDT P9418 wireless power transmitter + used by Xiaomi Nabu to charge its active stylus. + endif # POWER_SUPPLY diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 7924d2f5b..c74e6fab6 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -125,4 +125,5 @@ obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o obj-$(CONFIG_BATTERY_QCOM_FG) += qcom_fg.o obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_smbx.o +obj-$(CONFIG_CHARGER_IDTP9418) += idtp9418.o obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o diff --git a/drivers/power/supply/idtp9418.c b/drivers/power/supply/idtp9418.c new file mode 100644 index 000000000..5c45e106c --- /dev/null +++ b/drivers/power/supply/idtp9418.c @@ -0,0 +1,883 @@ +// SPDX-License-Identifier: GPL-2.0 +// Simplified IDTP9418 driver for reverse charging and I2C communication +// Supports touch pen charging + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "idtp9418.h" + +struct idtp9418_event { + __u8 soc; + __u8 is_charging; + __u8 is_attached; + __u8 charge_limit; + __u8 pen_mac[MAC_LEN]; + __u8 state; +}; + +struct idtp9418_device_info { + struct device *dev; + struct regmap *regmap; + struct power_supply *psy; + + struct alarm reverse_dping_alarm; + struct alarm reverse_chg_alarm; + + struct kfifo kfifo; + wait_queue_head_t wait_queue; + spinlock_t fifo_lock; /* protects the byte FIFO as whole event records */ + atomic_t users; + + struct delayed_work irq_work; + struct delayed_work hall_irq_work; + struct delayed_work charge_monitor_work; + struct delayed_work reverse_ept_type_work; + + struct gpio_desc *irq_gpiod; + struct gpio_desc *hall3_gpiod; + struct gpio_desc *hall4_gpiod; + struct gpio_desc *reverse_gpiod; + struct gpio_desc *reverse_boost_en_gpiod; + + bool is_attached; + bool is_charging; + bool is_reverse_mode; + bool charge_monitor_first; + bool pen_mac_valid; + bool removing; + u8 pen_mac[MAC_LEN]; + int reverse_pen_soc; + int charge_limit; +}; + +static enum power_supply_property idtp9418_props[] = { + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, + POWER_SUPPLY_PROP_ONLINE, +}; + +static const struct regmap_config i2c_idtp9418_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xFFFF, +}; + +static struct idtp9418_device_info *idtp9418_global_di; +static DEFINE_MUTEX(idtp9418_global_lock); + +static void idtp9418_push_event(struct idtp9418_device_info *di, u8 state) +{ + struct idtp9418_event evt; + struct idtp9418_event dropped; + unsigned long flags; + + evt.soc = (u8)di->reverse_pen_soc; + evt.is_charging = (u8)di->is_charging; + evt.is_attached = (u8)di->is_attached; + evt.charge_limit = (u8)di->charge_limit; + memcpy(evt.pen_mac, di->pen_mac, MAC_LEN); + evt.state = state; + + spin_lock_irqsave(&di->fifo_lock, flags); + if (kfifo_avail(&di->kfifo) < sizeof(evt) && + kfifo_out(&di->kfifo, &dropped, sizeof(dropped)) != + sizeof(dropped)) + kfifo_reset(&di->kfifo); + kfifo_in(&di->kfifo, &evt, sizeof(evt)); + spin_unlock_irqrestore(&di->fifo_lock, flags); + + wake_up(&di->wait_queue); +} + +static int idtp9418_kfifo_open(struct inode *inode, struct file *filp) +{ + struct idtp9418_device_info *di; + + mutex_lock(&idtp9418_global_lock); + di = idtp9418_global_di; + if (!di || di->removing) { + mutex_unlock(&idtp9418_global_lock); + return -ENODEV; + } + atomic_inc(&di->users); + filp->private_data = di; + mutex_unlock(&idtp9418_global_lock); + return 0; +} + +static int idtp9418_kfifo_release(struct inode *inode, struct file *filp) +{ + struct idtp9418_device_info *di = filp->private_data; + + if (atomic_dec_and_test(&di->users)) + wake_up_all(&di->wait_queue); + return 0; +} + +static ssize_t idtp9418_kfifo_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct idtp9418_device_info *di = filp->private_data; + struct idtp9418_event evt; + unsigned long flags; + + if (count < sizeof(evt)) + return -EINVAL; + + if (READ_ONCE(di->removing)) + return -ENODEV; + + if (kfifo_is_empty(&di->kfifo)) { + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + if (wait_event_interruptible(di->wait_queue, + READ_ONCE(di->removing) || + !kfifo_is_empty(&di->kfifo))) + return -ERESTARTSYS; + } + + if (READ_ONCE(di->removing)) + return -ENODEV; + + spin_lock_irqsave(&di->fifo_lock, flags); + count = kfifo_out(&di->kfifo, &evt, sizeof(evt)); + spin_unlock_irqrestore(&di->fifo_lock, flags); + if (count != sizeof(evt)) + return -EIO; + + if (copy_to_user(buf, &evt, sizeof(evt))) + return -EFAULT; + + return sizeof(evt); +} + +static __poll_t idtp9418_kfifo_poll(struct file *filp, poll_table *wait) +{ + struct idtp9418_device_info *di = filp->private_data; + __poll_t mask = 0; + + poll_wait(filp, &di->wait_queue, wait); + if (READ_ONCE(di->removing)) + return EPOLLERR | EPOLLHUP; + if (!kfifo_is_empty(&di->kfifo)) + mask |= EPOLLIN | EPOLLRDNORM; + return mask; +} + +static const struct file_operations idtp9418_fops = { + .owner = THIS_MODULE, + .open = idtp9418_kfifo_open, + .read = idtp9418_kfifo_read, + .poll = idtp9418_kfifo_poll, + .release = idtp9418_kfifo_release, +}; + +static struct miscdevice idtp9418_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "idtp9418", + .fops = &idtp9418_fops, + .mode = 0600, +}; + +static int idtp9418_read(struct idtp9418_device_info *di, u16 reg, u8 *val) +{ + unsigned int temp; + int rc; + + usleep_range(1000, 2000); + rc = regmap_read(di->regmap, reg, &temp); + if (rc >= 0) + *val = (u8)temp; + return rc; +} + +static int idtp9418_write(struct idtp9418_device_info *di, u16 reg, u8 val) +{ + int rc; + + usleep_range(1000, 2000); + rc = regmap_write(di->regmap, reg, val); + if (rc < 0) + dev_err(di->dev, "idtp9418 write error: %d\n", rc); + return rc; +} + +static int idtp9418_read_buffer(struct idtp9418_device_info *di, u16 reg, + u8 *buf, u32 size) +{ + int rc; + + while (size--) { + rc = idtp9418_read(di, reg++, buf++); + if (rc < 0) { + dev_err(di->dev, "read buf error: %d\n", rc); + return rc; + } + } + return 0; +} + +static int idtp9418_write_buffer(struct idtp9418_device_info *di, u16 reg, + u8 *buf, u32 size) +{ + int rc; + + while (size--) { + rc = idtp9418_write(di, reg++, *buf++); + if (rc < 0) { + dev_err(di->dev, "write buf error: %d\n", rc); + return rc; + } + } + return 0; +} + +static void reverse_clear_interrupt(struct idtp9418_device_info *di, u8 *buf, + u32 size) +{ + idtp9418_write_buffer(di, REG_SYS_INT_CLR, buf, size); + idtp9418_write(di, REG_TX_CMD, TX_FOD_EN | TX_CLRINT); +} + +static void idtp9418_set_reverse_gpio(struct idtp9418_device_info *di, + int enable) +{ + if (enable) { + gpiod_set_value(di->reverse_gpiod, true); + gpiod_set_value(di->reverse_boost_en_gpiod, true); + di->is_reverse_mode = true; + } else { + di->is_reverse_mode = false; + gpiod_set_value(di->reverse_boost_en_gpiod, false); + gpiod_set_value(di->reverse_gpiod, false); + } +} + +static void idtp9418_reverse_stop(struct idtp9418_device_info *di) +{ + idtp9418_set_reverse_gpio(di, false); + di->is_charging = false; + power_supply_changed(di->psy); +} + +static void idt_set_reverse_fod(struct idtp9418_device_info *di, int mw) +{ + u8 mw_l = mw & 0xff; + u8 mw_h = mw >> 8; + + idtp9418_write(di, REG_FOD_LOW, mw_l); + idtp9418_write(di, REG_FOD_HIGH, mw_h); +} + +static void idt_get_reverse_soc(struct idtp9418_device_info *di) +{ + u8 soc; + + if (idtp9418_read(di, REG_CHG_STATUS, &soc) < 0) + return; + if (soc > 0x64 && soc != 0xFF) { + dev_err(di->dev, "soc illegal: %d\n", soc); + return; + } + di->reverse_pen_soc = soc; +} + +static void idtp9418_reverse_charge_enable(struct idtp9418_device_info *di) +{ + u8 mode; + + usleep_range(1000, 2000); + idt_set_reverse_fod(di, REVERSE_FOD); + for (int i = 0; i < 3; i++) { + idtp9418_write(di, REG_TX_CMD, TX_EN | TX_FOD_EN); + idtp9418_read(di, REG_TX_DATA, &mode); + if (mode & BIT(0)) { + di->is_charging = true; + power_supply_changed(di->psy); + return; + } + } + dev_err(di->dev, "reverse charging failed start\n"); + idtp9418_reverse_stop(di); +} + +static void idtp9418_get_pen_mac(struct idtp9418_device_info *di) +{ + char mac_env[32]; + char *envp[2]; + int rc; + + rc = idtp9418_read_buffer(di, REG_MAC_ADDR, di->pen_mac, MAC_LEN); + if (rc < 0) { + dev_err(di->dev, "read pen mac error: %d\n", rc); + } else { + di->pen_mac_valid = true; + snprintf(mac_env, sizeof(mac_env), "IDTP9418_PEN_MAC=%pM", + di->pen_mac); + envp[0] = mac_env; + envp[1] = NULL; + sysfs_notify(&di->dev->kobj, NULL, "pen_mac"); + kobject_uevent_env(&di->dev->kobj, KOBJ_CHANGE, envp); + } +} + +static ssize_t pen_mac_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct idtp9418_device_info *di = dev_get_drvdata(dev); + + if (!di->pen_mac_valid) + return sysfs_emit(buf, "unknown\n"); + return sysfs_emit(buf, "%pM\n", di->pen_mac); +} +static DEVICE_ATTR_RO(pen_mac); + +static void idtp9418_charge_monitor_work(struct work_struct *work) +{ + struct idtp9418_device_info *di; + + di = container_of(work, struct idtp9418_device_info, + charge_monitor_work.work); + + idt_get_reverse_soc(di); + + if (di->is_attached && di->reverse_pen_soc >= 0 && + di->reverse_pen_soc < di->charge_limit) + goto queue_work; + + idtp9418_reverse_stop(di); + di->charge_monitor_first = false; + goto push; + +queue_work: + schedule_delayed_work(&di->charge_monitor_work, + msecs_to_jiffies(CHARGE_MONITOR_INTERVAL)); + if (!di->charge_monitor_first) + return; +push: + idtp9418_push_event(di, IDTP9418_STATE_COMPLETE); + di->charge_monitor_first = !di->charge_monitor_first; +} + +static void reverse_chg_alarm_cb(struct alarm *alarm, ktime_t now) +{ + struct idtp9418_device_info *di; + + di = container_of(alarm, struct idtp9418_device_info, + reverse_chg_alarm); + + dev_info(di->dev, "Reverse Chg Alarm Triggered %lld\n", + ktime_to_ms(now)); + idtp9418_reverse_stop(di); +} + +static void reverse_dping_alarm_cb(struct alarm *alarm, ktime_t now) +{ + struct idtp9418_device_info *di; + + di = container_of(alarm, struct idtp9418_device_info, + reverse_dping_alarm); + + dev_info(di->dev, + "Reverse Dping Alarm Triggered %lld, is_charging=%d\n", + ktime_to_ms(now), di->is_charging); + idtp9418_reverse_stop(di); +} + +#define EPT_FATAL_MASK \ + (EPT_FOD | EPT_CMD | EPT_OCP | EPT_OVP | EPT_LVP | EPT_OTP | EPT_POCP) + +static void reverse_ept_type_get_work(struct work_struct *work) +{ + struct idtp9418_device_info *di; + int rc; + u8 buf[2] = { 0 }; + u16 ept_val; + + di = container_of(work, struct idtp9418_device_info, + reverse_ept_type_work.work); + + rc = idtp9418_read_buffer(di, REG_EPT_TYPE, buf, 2); + if (rc < 0) { + dev_err(di->dev, "read tx ept type error: %d\n", rc); + return; + } + + ept_val = buf[0] | (buf[1] << 8); + dev_info(di->dev, "tx ept type: 0x%04x\n", ept_val); + + if (!ept_val) + return; + + if (ept_val & EPT_FATAL_MASK) { + dev_info(di->dev, "TX mode in ept, disable reverse charging\n"); + idtp9418_reverse_stop(di); + } else if (ept_val & EPT_CEP_TIMEOUT) { + dev_info(di->dev, "recheck ping state\n"); + } +} + +static void idtp9418_hall_irq_work(struct work_struct *work) +{ + struct idtp9418_device_info *di; + bool attached; + + di = container_of(work, struct idtp9418_device_info, + hall_irq_work.work); + + attached = gpiod_get_value(di->hall3_gpiod) && + !gpiod_get_value(di->hall4_gpiod); + di->is_attached = attached; + + if (!attached) { + alarm_cancel(&di->reverse_dping_alarm); + alarm_cancel(&di->reverse_chg_alarm); + cancel_delayed_work(&di->charge_monitor_work); + idtp9418_reverse_stop(di); + idtp9418_push_event(di, IDTP9418_STATE_COMPLETE); + goto out; + } + + idtp9418_set_reverse_gpio(di, true); + idtp9418_reverse_charge_enable(di); + idtp9418_push_event(di, IDTP9418_STATE_ATTACHING); + alarm_start_timer(&di->reverse_dping_alarm, + ms_to_ktime(REVERSE_DPING_CHECK_DELAY_MS), true); +out: + pm_relax(di->dev); +} + +static irqreturn_t idtp9418_irq_handler(int irq, void *dev_id) +{ + struct idtp9418_device_info *di = dev_id; + + if (READ_ONCE(di->removing)) + return IRQ_HANDLED; + + pm_stay_awake(di->dev); + schedule_delayed_work(&di->irq_work, msecs_to_jiffies(10)); + return IRQ_HANDLED; +} + +static irqreturn_t idtp9418_hall_irq_handler(int irq, void *dev_id) +{ + struct idtp9418_device_info *di = dev_id; + + if (READ_ONCE(di->removing)) + return IRQ_HANDLED; + + pm_stay_awake(di->dev); + schedule_delayed_work(&di->hall_irq_work, msecs_to_jiffies(10)); + return IRQ_HANDLED; +} + +static bool reverse_need_irq_cleared(struct idtp9418_device_info *di, u32 val) +{ + u8 int_buf[4]; + u32 int_val; + + if (idtp9418_read_buffer(di, REG_SYS_INT, int_buf, 4) < 0) + return false; + int_val = int_buf[0] | (int_buf[1] << 8) | (int_buf[2] << 16) | + (int_buf[3] << 24); + if (int_val && int_val == val) + return true; + return false; +} + +static void idtp9418_irq_work(struct work_struct *work) +{ + struct idtp9418_device_info *di = + container_of(work, struct idtp9418_device_info, irq_work.work); + u8 int_buf[4] = { 0 }; + u8 clr_buf[4] = { 0xFF, 0xFF, 0xFF, 0xFF }; + u32 int_val; + + if (gpiod_get_value(di->irq_gpiod)) + goto out; + + if (idtp9418_read_buffer(di, REG_SYS_INT, int_buf, 4) < 0) + goto out; + + if (!di->is_reverse_mode) + goto out; + + reverse_clear_interrupt(di, int_buf, 4); + int_val = int_buf[0] | (int_buf[1] << 8) | (int_buf[2] << 16) | + (int_buf[3] << 24); + + usleep_range(5000, 6000); + if (reverse_need_irq_cleared(di, int_val)) { + reverse_clear_interrupt(di, clr_buf, 4); + usleep_range(5000, 6000); + } + + if (int_val & INT_EPT_TYPE) + schedule_delayed_work(&di->reverse_ept_type_work, 0); + + if (int_val & INT_GET_DPING) { + dev_info(di->dev, "TRX get dping, disable reverse charging\n"); + idtp9418_reverse_stop(di); + } + + if (int_val & INT_START_DPING) { + alarm_start_timer(&di->reverse_chg_alarm, + ms_to_ktime(REVERSE_CHG_CHECK_DELAY_MS), true); + alarm_cancel(&di->reverse_dping_alarm); + } + + if (int_val & INT_GET_CFG) + alarm_cancel(&di->reverse_chg_alarm); + + if (int_val & INT_GET_BLE_ADDR) { + idtp9418_get_pen_mac(di); + schedule_delayed_work(&di->charge_monitor_work, + msecs_to_jiffies(1500)); + } + +out: + pm_relax(di->dev); +} + +static int idtp9418_parse_dt(struct idtp9418_device_info *di) +{ + di->irq_gpiod = devm_gpiod_get(di->dev, "idt,irq", GPIOD_IN); + if (IS_ERR(di->irq_gpiod)) { + dev_err(di->dev, "get irq gpio failed: %ld\n", + PTR_ERR(di->irq_gpiod)); + return PTR_ERR(di->irq_gpiod); + } + + di->hall3_gpiod = devm_gpiod_get(di->dev, "idt,hall3", GPIOD_IN); + if (IS_ERR(di->hall3_gpiod)) { + dev_err(di->dev, "get hall3 gpio failed: %ld\n", + PTR_ERR(di->hall3_gpiod)); + return PTR_ERR(di->hall3_gpiod); + } + + di->hall4_gpiod = devm_gpiod_get(di->dev, "idt,hall4", GPIOD_IN); + if (IS_ERR(di->hall4_gpiod)) { + dev_err(di->dev, "get hall4 gpio failed: %ld\n", + PTR_ERR(di->hall4_gpiod)); + return PTR_ERR(di->hall4_gpiod); + } + + di->reverse_gpiod = + devm_gpiod_get(di->dev, "idt,reverse-enable", GPIOD_OUT_LOW); + if (IS_ERR(di->reverse_gpiod)) { + dev_err(di->dev, "get reverse gpio failed: %ld\n", + PTR_ERR(di->reverse_gpiod)); + return PTR_ERR(di->reverse_gpiod); + } + + di->reverse_boost_en_gpiod = + devm_gpiod_get(di->dev, "idt,reverse-boost-enable", GPIOD_OUT_LOW); + if (IS_ERR(di->reverse_boost_en_gpiod)) { + dev_err(di->dev, "get reverse_boost_enable gpio failed: %ld\n", + PTR_ERR(di->reverse_boost_en_gpiod)); + return PTR_ERR(di->reverse_boost_en_gpiod); + } + + return 0; +} + +static int idtp9418_irq_request(struct idtp9418_device_info *di) +{ + int irq = gpiod_to_irq(di->irq_gpiod); + int ret; + + if (irq < 0) { + dev_err(di->dev, "gpiod_to_irq failed: %d\n", irq); + return irq; + } + ret = devm_request_irq(di->dev, irq, idtp9418_irq_handler, + IRQF_TRIGGER_FALLING, IDT_DRIVER_NAME, di); + if (ret < 0) { + dev_err(di->dev, "request irq failed: %d\n", ret); + return ret; + } + return enable_irq_wake(irq); +} + +static int idtp9418_hall_gpio_request(struct idtp9418_device_info *di, + struct gpio_desc *gpiod, const char *name) +{ + int irq = gpiod_to_irq(gpiod); + int ret; + + if (irq < 0) + return irq; + ret = devm_request_irq(di->dev, irq, idtp9418_hall_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, name, + di); + if (ret < 0) + return ret; + enable_irq_wake(irq); + return 0; +} + +static int idtp9418_hall_irq_request(struct idtp9418_device_info *di) +{ + int ret; + + ret = idtp9418_hall_gpio_request(di, di->hall3_gpiod, "idtp_hall3"); + if (ret < 0) + return ret; + return idtp9418_hall_gpio_request(di, di->hall4_gpiod, "idtp_hall4"); +} + +static int idtp9418_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct idtp9418_device_info *di = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CAPACITY: + if (!di->is_attached) { + val->intval = 0; + return 0; + } + val->intval = di->reverse_pen_soc; + if (val->intval < 0) + val->intval = 0; + return 0; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + val->intval = di->charge_limit; + return 0; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->is_charging; + return 0; + default: + return -EINVAL; + } +} + +static int idtp9418_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD; +} + +static int idtp9418_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct idtp9418_device_info *di = power_supply_get_drvdata(psy); + + if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) { + if (val->intval < 0 || val->intval > 95) + return -EINVAL; + di->charge_limit = val->intval; + dev_info(di->dev, "Charge limit set to %d%%\n", + di->charge_limit); + return 0; + } + return -EINVAL; +} + +static void idtp9418_cancel_works(struct idtp9418_device_info *di) +{ + alarm_cancel(&di->reverse_dping_alarm); + alarm_cancel(&di->reverse_chg_alarm); + cancel_delayed_work_sync(&di->irq_work); + cancel_delayed_work_sync(&di->hall_irq_work); + cancel_delayed_work_sync(&di->reverse_ept_type_work); + cancel_delayed_work_sync(&di->charge_monitor_work); +} + +static int idtp9418_probe(struct i2c_client *client) +{ + struct idtp9418_device_info *di; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct power_supply_config idtp_cfg = { 0 }; + struct power_supply_desc *psy_desc; + int ret = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + di = devm_kzalloc(&client->dev, sizeof(*di), GFP_KERNEL); + if (!di) + return -ENOMEM; + + di->dev = &client->dev; + di->reverse_pen_soc = 255; + di->charge_limit = LIMIT_SOC; + di->is_charging = false; + di->is_attached = false; + di->is_reverse_mode = false; + di->charge_monitor_first = true; + spin_lock_init(&di->fifo_lock); + atomic_set(&di->users, 0); + + di->regmap = devm_regmap_init_i2c(client, &i2c_idtp9418_regmap_config); + if (IS_ERR(di->regmap)) { + dev_err(&client->dev, "Failed to init regmap: %ld\n", + PTR_ERR(di->regmap)); + return PTR_ERR(di->regmap); + } + + INIT_DELAYED_WORK(&di->irq_work, idtp9418_irq_work); + INIT_DELAYED_WORK(&di->hall_irq_work, idtp9418_hall_irq_work); + INIT_DELAYED_WORK(&di->reverse_ept_type_work, + reverse_ept_type_get_work); + INIT_DELAYED_WORK(&di->charge_monitor_work, + idtp9418_charge_monitor_work); + alarm_init(&di->reverse_dping_alarm, ALARM_BOOTTIME, + reverse_dping_alarm_cb); + alarm_init(&di->reverse_chg_alarm, ALARM_BOOTTIME, + reverse_chg_alarm_cb); + init_waitqueue_head(&di->wait_queue); + + ret = kfifo_alloc(&di->kfifo, IDTP_KFIFO_SIZE, GFP_KERNEL); + if (ret) { + dev_err(di->dev, "Failed to allocate kfifo\n"); + return ret; + } + + device_init_wakeup(&client->dev, true); + i2c_set_clientdata(client, di); + + ret = idtp9418_parse_dt(di); + if (ret < 0) + goto cleanup; + + psy_desc = devm_kzalloc(&client->dev, sizeof(*psy_desc), GFP_KERNEL); + if (!psy_desc) { + ret = -ENOMEM; + goto cleanup; + } + psy_desc->name = IDT_DRIVER_NAME; + psy_desc->type = POWER_SUPPLY_TYPE_WIRELESS; + psy_desc->properties = idtp9418_props; + psy_desc->num_properties = ARRAY_SIZE(idtp9418_props); + psy_desc->get_property = idtp9418_get_property; + psy_desc->set_property = idtp9418_set_property; + psy_desc->property_is_writeable = idtp9418_property_is_writeable; + + idtp_cfg.drv_data = di; + di->psy = devm_power_supply_register(&client->dev, psy_desc, &idtp_cfg); + if (IS_ERR(di->psy)) { + dev_err(&client->dev, "Failed to register power supply\n"); + ret = PTR_ERR(di->psy); + goto cleanup; + } + + mutex_lock(&idtp9418_global_lock); + idtp9418_global_di = di; + mutex_unlock(&idtp9418_global_lock); + + ret = misc_register(&idtp9418_miscdev); + if (ret < 0) { + dev_err(&client->dev, "Failed to register misc device\n"); + mutex_lock(&idtp9418_global_lock); + idtp9418_global_di = NULL; + mutex_unlock(&idtp9418_global_lock); + goto cleanup; + } + + ret = device_create_file(di->dev, &dev_attr_pen_mac); + if (ret) { + misc_deregister(&idtp9418_miscdev); + mutex_lock(&idtp9418_global_lock); + idtp9418_global_di = NULL; + mutex_unlock(&idtp9418_global_lock); + goto cleanup; + } + + /* Do not expose IRQ work until its power-supply and userspace ABI exist. */ + ret = idtp9418_irq_request(di); + if (ret < 0) + goto cleanup_registered; + + ret = idtp9418_hall_irq_request(di); + if (ret < 0) + goto cleanup_registered; + + schedule_delayed_work(&di->hall_irq_work, 0); + dev_info(di->dev, "idtp9418 probe success\n"); + return 0; + +cleanup_registered: + WRITE_ONCE(di->removing, true); + device_remove_file(di->dev, &dev_attr_pen_mac); + mutex_lock(&idtp9418_global_lock); + idtp9418_global_di = NULL; + mutex_unlock(&idtp9418_global_lock); + wake_up_all(&di->wait_queue); + misc_deregister(&idtp9418_miscdev); + wait_event(di->wait_queue, !atomic_read(&di->users)); +cleanup: + WRITE_ONCE(di->removing, true); + device_init_wakeup(&client->dev, false); + idtp9418_cancel_works(di); + kfifo_free(&di->kfifo); + return ret; +} + +static void idtp9418_remove(struct i2c_client *client) +{ + struct idtp9418_device_info *di = i2c_get_clientdata(client); + + device_remove_file(di->dev, &dev_attr_pen_mac); + mutex_lock(&idtp9418_global_lock); + di->removing = true; + idtp9418_global_di = NULL; + mutex_unlock(&idtp9418_global_lock); + wake_up_all(&di->wait_queue); + misc_deregister(&idtp9418_miscdev); + wait_event(di->wait_queue, !atomic_read(&di->users)); + idtp9418_cancel_works(di); + idtp9418_set_reverse_gpio(di, false); + device_init_wakeup(&client->dev, false); + kfifo_free(&di->kfifo); +} + +static void idtp9418_shutdown(struct i2c_client *client) +{ + struct idtp9418_device_info *di = i2c_get_clientdata(client); + + WRITE_ONCE(di->removing, true); + idtp9418_cancel_works(di); + idtp9418_set_reverse_gpio(di, false); +} + +static const struct i2c_device_id idtp9418_id[] = { + { IDT_DRIVER_NAME, 0 }, + {} +}; + +static const struct of_device_id idt_match_table[] = { + { .compatible = "idt,p9418" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, idtp9418_id); +MODULE_DEVICE_TABLE(of, idt_match_table); + +static struct i2c_driver idtp9418_driver = { + .driver = { + .name = IDT_DRIVER_NAME, + .of_match_table = idt_match_table, + }, + .probe = idtp9418_probe, + .remove = idtp9418_remove, + .shutdown = idtp9418_shutdown, + .id_table = idtp9418_id, +}; + +module_i2c_driver(idtp9418_driver); +MODULE_DESCRIPTION("IDTP9418 Reverse Charging Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/idtp9418.h b/drivers/power/supply/idtp9418.h new file mode 100644 index 000000000..4551ed6d3 --- /dev/null +++ b/drivers/power/supply/idtp9418.h @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __IDTP9418_H__ +#define __IDTP9418_H__ + +#include + +#define IDT_DRIVER_NAME "idtp9418" +#define IDT_I2C_ADDR 0x3b + +#define HSCLK 60000 + +#define ADJUST_METE_MV 35 +#define IDTP9220_DELAY 2000 +#define CHARGING_FULL 100 +#define CHARGING_NEED 95 + +/* status low regiter bits define */ +#define STATUS_VOUT_ON BIT(7) +#define STATUS_VOUT_OFF BIT(6) +#define STATUS_TX_DATA_RECV BIT(4) +#define STATUS_OV_TEMP BIT(2) +#define STATUS_OV_VOL BIT(1) +#define STATUS_OV_CURR BIT(0) + +#define OVER_EVENT_OCCUR (STATUS_OV_TEMP | STATUS_OV_VOL | STATUS_OV_CURR) +#define TOGGLE_LDO_ON_OFF BIT(1) + +/* interrupt register bits define */ +#define INT_RXREADY BIT(22) +#define INT_POWER_LOSS BIT(21) +#define INT_OVERCURRENT_HIGH BIT(20) +#define RENEG_FAIL BIT(19) +#define RENEG_SUCCESS BIT(18) +#define INT_RPP_READ BIT(17) +#define INT_EXT5V_FAIL BIT(16) +#define INT_VSWITCH_FAIL BIT(15) +#define INT_SLEEPMODE BIT(14) +#define INT_IDAUTH_SUCESS BIT(13) +#define INT_IDAUTH_FAIL BIT(12) +#define INT_SEND_SUCESS BIT(11) +#define INT_SEND_TIMEOUT BIT(10) +#define INT_AUTH_SUCESS BIT(9) +#define INT_AUTH_FAIL BIT(8) +#define INT_VOUT_OFF BIT(7) +#define INT_VOUT_ON BIT(6) +#define INT_MODE_CHANGE BIT(5) +#define INT_TX_DATA_RECV BIT(4) +#define INT_VSWITCH_SUCESS BIT(3) +#define INT_OV_TEMP BIT(2) +#define INT_OV_VOL BIT(1) +#define INT_OV_CURR BIT(0) + +/*TRX int bits define*/ +#define INT_GET_BLE_ADDR BIT(8) +#define INT_INIT_TX BIT(7) +#define INT_GET_DPING BIT(6) +#define INT_GET_PPP BIT(5) +#define INT_GET_CFG BIT(4) +#define INT_GET_ID BIT(3) +#define INT_GET_SS BIT(2) +#define INT_START_DPING BIT(1) +#define INT_EPT_TYPE BIT(0) + +#define TX_TOGGLE BIT(6) // toggle work mode(148K or 190K) +#define TX_FOD_EN BIT(5) // enable FOD +#define TX_WD BIT(4) // enable WD +#define TX_SEND_FSK BIT(3) // SEND_FSK +#define TX_DIS BIT(2) // disable tx +#define TX_CLRINT BIT(1) // clr int +#define TX_EN BIT(0) // enable tx mode + +/* used registers define */ +#define REG_CHIP_ID_L 0x0000 +#define REG_CHIP_ID_H 0x0001 +#define REG_CHIP_REV 0x0002 +#define REG_CTM_ID 0x0003 +#define REG_OTPFWVER_ADDR 0x0004 // OTP firmware version +#define REG_EPRFWVER_ADDR 0x001c // EEPROM firmware version +#define REG_SYS_INT_CLR 0x0028 // int clear +#define REG_SYS_INT 0x0030 // interrupt +#define REG_STATUS_L 0x0034 +#define REG_STATUS_H 0x0035 +#define REG_INTR_L 0x0036 +#define REG_INTR_H 0x0037 +#define REG_INTR_EN_L 0x0038 +#define REG_INTR_EN_H 0x0039 +#define REG_CHG_STATUS 0x003A +#define REG_ADC_VOUT_L 0x003C +#define REG_ADC_VOUT_H 0x003D +#define REG_VOUT_SET 0x003E +#define REG_VRECT_ADJ 0x003F +#define REG_ADC_VRECT 0x0040 +#define REG_RX_LOUT_L 0x0044 +#define REG_RX_LOUT_H 0x0045 +#define REG_RX_DIE_TEMP 0x0046 +#define REG_FREQ_ADDR \ + 0x0048 // Operating Frequency, Fop(KHz) = 64 * 6000 /value * 256) +#define REG_ILIM_SET 0x004A +#define REG_SIGNAL_STRENGTH 0x004B +#define REG_WPC_MODE 0x004D +#define REG_SSCMND 0x004e // Command Register, COM (0x4E) +#define REG_RX_RESET 0x004F +#define REG_PROPPKT \ + 0x0050 // Proprietary Packet Header Register, PPP_Header (0x50) +#define REG_PPPDATA \ + 0x0051 // PPP Data Value Register(0X51, 0x52, 0x53, 0x54, 0x55) +#define REG_SSINTCLR 0x0056 // Interrupt Clear Registers, INT_Clear_L (0x56) +#define REG_BCHEADER 0x0058 // Back Channel Packet Register (0x58) +#define REG_BCDATA \ + 0x0059 // Back Channel Packet Register (0x59, 0x5A, 0x5B, 0x5C) +#define REG_FC_VOLTAGE_L 0x0078 // Fast Charging Voltage Register +#define REG_FC_VOLTAGE_H 0x0079 +#define REG_REGULATOR_L 0x000C +#define REG_REGULATOR_H 0x000d +#define REG_POWER_MAX 0x0084 // Get the TX power on EPP mode. +#define REG_TX_TYPE 0x00A2 // Get the TX type. +#define REG_BLE_FLAG 0x00A4 // Get the rx ble flag. +#define REG_CEP 0x00A5 // Get the CEP. +#define REG_RPP 0x00A6 // Get the RPP. +#define REG_MAX_POWER 0x008E +#define REG_OCP_CONFIG 0x00F1 +#define REG_TEMPTER 0x00F2 + +/*add for reverse charge*/ +#define REG_EPT_TYPE 0x0074 +#define REG_TX_CMD 0x0076 +#define REG_TX_DATA 0x0078 +#define REG_WROK_MODE 0x007B +#define REG_VRECT_TAEGET 0x0090 +#define REG_FOD_LOW 0x0092 +#define REG_FOD_HIGH 0x0093 +#define REG_MAC_ADDR 0x00be + +#define REG_REVERSE_IIN_LOW 0x006e +#define REG_REVERSE_IIN_HIGH 0x006f +#define REG_REVERSE_VIN_LOW 0x0070 +#define REG_REVERSE_VIN_HIGH 0x0071 +#define REG_REVERSE_TEMP 0x007a + +#define EPT_POCP BIT(15) +#define EPT_OTP BIT(14) +#define EPT_FOD BIT(13) +#define EPT_LVP BIT(12) +#define EPT_OVP BIT(11) +#define EPT_OCP BIT(10) +#define EPT_RPP_TIMEOUT BIT(9) +#define EPT_CEP_TIMEOUT BIT(8) +#define EPT_CMD BIT(0) + +#define REG_EXTERNAL_5V 0x00dc // external 5v enable + +// RX -> TX +#define PROPRIETARY18 0x18 +#define PROPRIETARY28 0x28 +#define PROPRIETARY38 0x38 +#define PROPRIETARY48 0x48 +#define PROPRIETARY58 0x58 + +// bitmap for customer command +#define BC_NONE 0x00 +#define BC_SET_FREQ 0x03 +#define BC_GET_FREQ 0x04 +#define BC_READ_FW_VER 0x05 +#define BC_READ_Iin 0x06 +#define BC_READ_Vin 0x07 +#define BC_SET_Vin 0x0a + +#define BC_ADAPTER_TYPE 0x0b +#define BC_RESET 0x0c +#define BC_READ_I2C 0x0d +#define BC_WRITE_I2C 0x0e +#define BC_VI2C_INIT 0x10 +#define BC_RX_ID_AUTH 0x3b +#define BC_TX_COMPATIBLE_HWID 0x3f +#define BC_TX_HWID 0x4c +#define BC_TX_TOGGLE 0xc4 +#define CMD_GET_BLEMAC_2_0 0xb6 +#define CMD_GET_BLEMAC_5_3 0xb7 +#define CMD_FACOTRY_REVERSE 0x30 + +#define NORMAL_MODE 0x1 +#define TAPER_MODE 0x2 +#define FULL_MODE 0x3 +#define RECHG_MODE 0x4 + +// fw version to update +#define FW_VERSION 0x1 + +// add for crc verify +#define CRC_VERIFY_LOW 0xB5 +#define CRC_VERIFY_HIGH 0xA9 + +// add for reverse fod +#define REVERSE_FOD 500 + +/*CLRPOWERGOOD BIT(10), upper byte is 0x8*/ +#define CLRPOWERGOOD 0x04 +// bitmap for status flags +// 1: indicates a pending interrupt for LDO Vout state change – from OFF to ON +#define VOUTCHANGED BIT(7) // Stat_Vout_ON +/* Indicates pending TX Data Received transition from no data to data. */ +#define TXDATARCVD BIT(4) // TX Data Received + +// bitmap for SSCmnd register 0x4e +#define VSWITCH BIT(7) +/* Clear the interrupt bits selected by the AP. */ +#define CLRINT BIT(5) // Clear Interrupt +/* Toggle the LDO output once and let firmware clear the command bit. */ +#define LDOTGL BIT(1) // Toggle LDO On/OFF +// If AP sets this bit to “1” then IDTP9220 M0 sends the Proprietary Packet +#define SENDPROPP BIT(0) // SEND RX Data + +#define SEND_DEVICE_AUTH BIT(2) + +#define REVERSE_DPING_CHECK_DELAY_MS 1000 +#define REVERSE_CHG_CHECK_DELAY_MS 1000 +#define CHARGE_MONITOR_INTERVAL (2 * HZ) +#define IDTP9418_STATE_ATTACHING 1 +#define IDTP9418_STATE_COMPLETE 2 +#define IDTP_KFIFO_SIZE 128 +#define LIMIT_SOC 85 +#define MAC_LEN 6 + +#endif diff --git a/include/linux/input/xiaomi-nabu-keyboard.h b/include/linux/input/xiaomi-nabu-keyboard.h new file mode 100644 index 000000000..6632e16f8 --- /dev/null +++ b/include/linux/input/xiaomi-nabu-keyboard.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _LINUX_INPUT_XIAOMI_NABU_KEYBOARD_H +#define _LINUX_INPUT_XIAOMI_NABU_KEYBOARD_H + +#include + +#if IS_ENABLED(CONFIG_INPUT_XIAOMI_NABU_TABLET_MODE) +void xiaomi_nabu_keyboard_set_attached(bool attached); +#else +static inline void xiaomi_nabu_keyboard_set_attached(bool attached) { } +#endif + +#endif diff --git a/senemos/README.md b/senemos/README.md index 3fc93b856..3094da0e3 100644 --- a/senemos/README.md +++ b/senemos/README.md @@ -1,33 +1,53 @@ # Senemos Nabu Linux 7.2 port -This branch rebases the Xiaomi Pad 5 (`nabu`) support on the verified Linux -7.2 release tarball. The imported tarball SHA-256 is: +The `7.2.0` branch rebases Xiaomi Pad 5 (`nabu`) support onto the verified +Linux 7.2 release tarball. The upstream archive SHA-256 is: `f9fef3d14c0df53819026f4be74459835c2a0b0dcbf5b5bbd9ea19f0829402b3` -The initial port is intentionally boot-minimal. It contains the board DT and -the fixed-refresh Nabu CSOT panel support from the traceable 6.17 publication -baseline. It does not contain the later seamless DFPS, custom SM8150 audio, -FastRPC mount-matrix, SMP2P sleep-state, or experimental power scaffold code. +The comparison baseline is the consolidated `6.17.0` source branch at commit +`d5731dcf7d5e418483d62f65b03266206b133f88`, whose Linux 6.17 archive +SHA-256 is: -The Fedora base configuration was extracted from the Rawhide aarch64 -`kernel-core-7.3.0-0.rc0.260819gbd5f485f3f02.5.fc46` RPM and is normalized for -Linux 7.2 with `nabu-minimal.config`. +`9b607166a1c999d8326098121222feb080a20a3253975fcdfa2de96ba7f757a7` -Expected first-stage limitations: +## Port scope -- audio is deliberately not enabled in the board DT; -- the downstream Novatek SPI touchscreen driver still needs a separate port; -- SLPI uses the upstream generic compatible, but Nabu-specific register and - FastRPC/IOMMU behavior still requires device validation; -- battery, charger, RTC offset, seamless DFPS and suspend policy are deferred; -- a successful build is not physical Nabu HIL evidence. +The branch carries the reviewed Nabu display/touch synchronization, stable +60/120 Hz modes, opt-in 90 Hz experiment, Night Light-safe color updates, +double-tap wake, PM8150B charger and fuel gauge, legacy ADSP/APR audio, +FastRPC mount matrices, tablet-mode reporting, late optional XHCI policy and +IDT P9418 stylus charging support. -Build from the Rawhide toolchain container with: +The final 6.17 delta is also carried: pogo-keyboard power/detect and dynamic +tablet-mode reporting, Xiaomi HID pointer filtering, WCN3990 single-channel +scan handling, post-enable DSI/touch wake ordering, and the P9418 event and +stylus-MAC userspace ABI used by automatic BlueZ pairing. + +The optional high-power charge-pump and board-thermistor scaffold stays in the +source tree but is excluded from the production DTB. It must not be enabled +until its Linux drivers, limits and thermal behavior have been validated on +physical Nabu hardware. + +## Build contract + +Run: ```sh ./senemos/build.sh ``` -The default release is `7.2.0-senemos.nabu.v2.0`. Set -`SENEMOS_EXTRA_LOCALVERSION` only when an additional build suffix is required. +The script recreates the output configuration from the Fedora Rawhide arm64 +base plus `nabu-minimal.config`, and builds `Image`, the Nabu DTB and all +modules. The resulting ABI is: + +`7.2.0-nabu-senemos-v7.2.0` + +The default output directory is: + +`/workspace/kernel-builds/linux-nabu-senemos-v7.2.0-linux-7.2` + +Source review, successful compilation, DT schema validation and RPM/COPR +publication are separate gates. None of them proves boot, display, touch, +charging, audio, suspend or Wi-Fi behavior on a physical tablet; those remain +part of the later BOOT/HIL phase. diff --git a/senemos/configs/nabu-minimal.config b/senemos/configs/nabu-minimal.config index 25fe81657..3f184d3de 100644 --- a/senemos/configs/nabu-minimal.config +++ b/senemos/configs/nabu-minimal.config @@ -34,8 +34,11 @@ CONFIG_BACKLIGHT_KTZ8866=m # Nabu SPI touch, wake gestures and PM8150 fuel gauge. CONFIG_TOUCHSCREEN_NT36523_SPI=m CONFIG_BATTERY_QCOM_FG=y +CONFIG_CHARGER_IDTP9418=m CONFIG_CHARGER_QCOM_SMB2=m CONFIG_INPUT_XIAOMI_NABU_TABLET_MODE=y +CONFIG_INPUT_XIAOMI_NABU_KEYBOARD=y +CONFIG_HID_XIAOMI=y # The optional pogo-keyboard XHCI controller must not block built-in init. CONFIG_USB_XHCI_HCD=m