diff -rupN linux.orig/arch/arm64/boot/dts/amlogic/Makefile linux/arch/arm64/boot/dts/amlogic/Makefile --- linux.orig/arch/arm64/boot/dts/amlogic/Makefile 2023-09-12 12:02:56.937601871 +0000 +++ linux/arch/arm64/boot/dts/amlogic/Makefile 2023-09-12 12:03:27.490291628 +0000 @@ -12,8 +12,10 @@ dtb-$(CONFIG_ARCH_MESON) += meson-g12b-a dtb-$(CONFIG_ARCH_MESON) += meson-g12b-gsking-x.dtb dtb-$(CONFIG_ARCH_MESON) += meson-g12b-gtking-pro.dtb dtb-$(CONFIG_ARCH_MESON) += meson-g12b-gtking.dtb +dtb-$(CONFIG_ARCH_MESON) += meson-g12b-odroid-go-ultra.dtb dtb-$(CONFIG_ARCH_MESON) += meson-g12b-odroid-n2-plus.dtb dtb-$(CONFIG_ARCH_MESON) += meson-g12b-odroid-n2.dtb +dtb-$(CONFIG_ARCH_MESON) += meson-g12b-powkiddy-rgb10-max-3.dtb dtb-$(CONFIG_ARCH_MESON) += meson-g12b-s922x-khadas-vim3.dtb dtb-$(CONFIG_ARCH_MESON) += meson-g12b-ugoos-am6.dtb dtb-$(CONFIG_ARCH_MESON) += meson-gxbb-kii-pro.dtb diff -rupN linux.orig/arch/arm64/boot/dts/amlogic/meson-g12-common.dtsi linux/arch/arm64/boot/dts/amlogic/meson-g12-common.dtsi --- linux.orig/arch/arm64/boot/dts/amlogic/meson-g12-common.dtsi 2023-09-12 12:02:56.937601871 +0000 +++ linux/arch/arm64/boot/dts/amlogic/meson-g12-common.dtsi 2023-09-12 12:03:27.490291628 +0000 @@ -61,18 +61,6 @@ gpu_opp_table: opp-table-gpu { compatible = "operating-points-v2"; - opp-124999998 { - opp-hz = /bits/ 64 <124999998>; - opp-microvolt = <800000>; - }; - opp-249999996 { - opp-hz = /bits/ 64 <249999996>; - opp-microvolt = <800000>; - }; - opp-285714281 { - opp-hz = /bits/ 64 <285714281>; - opp-microvolt = <800000>; - }; opp-399999994 { opp-hz = /bits/ 64 <399999994>; opp-microvolt = <800000>; @@ -1884,6 +1872,15 @@ }; }; + uart_ao_b_pins: uart-b-ao { + mux { + groups = "uart_ao_b_tx_8", + "uart_ao_b_rx_9"; + function = "uart_ao_b"; + bias-disable; + }; + }; + uart_ao_a_pins: uart-a-ao { mux { groups = "uart_ao_a_tx", @@ -2394,14 +2391,20 @@ }; mali: gpu@ffe40000 { - compatible = "amlogic,meson-g12a-mali", "arm,mali-bifrost"; - reg = <0x0 0xffe40000 0x0 0x40000>; + compatible = "arm,mali-midgard"; + reg = <0x0 0xffe40000 0x0 0x40000>, + <0 0xFFD01000 0 0x01000>, + <0 0xFF800000 0 0x01000>, + <0 0xFF63c000 0 0x01000>, + <0 0xFFD01000 0 0x01000>; + interrupt-parent = <&gic>; - interrupts = , + interrupts = , , - ; - interrupt-names = "job", "mmu", "gpu"; + ; + interrupt-names = "GPU", "MMU", "JOB"; clocks = <&clkc CLKID_MALI>; + clock-names = "clk_mali"; resets = <&reset RESET_DVALIN_CAPB3>, <&reset RESET_DVALIN>; operating-points-v2 = <&gpu_opp_table>; #cooling-cells = <2>; diff -rupN linux.orig/arch/arm64/boot/dts/amlogic/meson-g12b-a311d.dtsi linux/arch/arm64/boot/dts/amlogic/meson-g12b-a311d.dtsi --- linux.orig/arch/arm64/boot/dts/amlogic/meson-g12b-a311d.dtsi 2023-09-12 12:02:56.937601871 +0000 +++ linux/arch/arm64/boot/dts/amlogic/meson-g12b-a311d.dtsi 2023-09-12 12:03:27.490291628 +0000 @@ -11,6 +11,11 @@ compatible = "operating-points-v2"; opp-shared; + opp-667000000 { + opp-hz = /bits/ 64 <667000000>; + opp-microvolt = <731000>; + }; + opp-1000000000 { opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <761000>; @@ -51,6 +56,11 @@ compatible = "operating-points-v2"; opp-shared; + opp-667000000 { + opp-hz = /bits/ 64 <667000000>; + opp-microvolt = <731000>; + }; + opp-1000000000 { opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <731000>; diff -rupN linux.orig/arch/arm64/boot/dts/amlogic/meson-g12b-odroid-go-ultra.dts linux/arch/arm64/boot/dts/amlogic/meson-g12b-odroid-go-ultra.dts --- linux.orig/arch/arm64/boot/dts/amlogic/meson-g12b-odroid-go-ultra.dts 1970-01-01 00:00:00.000000000 +0000 +++ linux/arch/arm64/boot/dts/amlogic/meson-g12b-odroid-go-ultra.dts 2023-09-12 13:20:28.956694441 +0000 @@ -0,0 +1,1021 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2022 Neil Armstrong + */ + +/dts-v1/; + +#include "meson-g12b-s922x.dtsi" +#include +#include +#include +#include +#include + +/ { + compatible = "hardkernel,odroid-go-ultra", "amlogic,s922x", "amlogic,g12b"; + model = "Hardkernel ODROID-GO-Ultra"; + + aliases { + serial0 = &uart_AO; + rtc0 = &vrtc; + mmc0 = &sd_emmc_c; + mmc1 = &sd_emmc_b; + }; + + panel_backlight: backlight { + compatible = "pwm-backlight"; + pwms = <&pwm_ef 1 40000 0>; + brightness-levels = <0 255>; + num-interpolated-steps = <255>; + default-brightness-level = <255>; + }; + + bat: battery { + compatible = "simple-battery"; + voltage-max-design-microvolt = <4200000>; + voltage-min-design-microvolt = <3500000>; + charge-full-design-microamp-hours = <4000000>; + charge-term-current-microamp = <200000>; + constant-charge-current-max-microamp = <1500000>; + constant-charge-voltage-max-microvolt = <4200000>; + factory-internal-resistance-micro-ohms = <180000>; + + + ocv-capacity-celsius = <20>; + ocv-capacity-table-0 = <4146950 100>, <4001920 95>, <3967900 90>, <3919950 85>, + <3888450 80>, <3861850 75>, <3831540 70>, <3799130 65>, + <3768190 60>, <3745650 55>, <3726610 50>, <3711630 45>, + <3696720 40>, <3685660 35>, <3674950 30>, <3663050 25>, + <3649470 20>, <3635260 15>, <3616920 10>, <3592440 5>, + <3574170 0>; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + codec_clk: codec-clk { + compatible = "fixed-clock"; + clock-frequency = <12288000>; + clock-output-names = "codec_clk"; + #clock-cells = <0>; + }; + + gpio_keys: volume-keys { + compatible = "gpio-keys-polled"; + poll-interval = <5>; + autorepeat; + + volume-up-button { + label = "VOLUME-UP"; + linux,code = ; + gpios = <&gpio GPIOX_8 GPIO_ACTIVE_LOW>; + }; + volume-down-button { + label = "VOLUME-DOWN"; + linux,code = ; + gpios = <&gpio GPIOX_9 GPIO_ACTIVE_LOW>; + }; + }; + + hp_detect_switch: hp-detect-switch { + compatible = "gpio-keys-polled"; + poll-interval = <5>; + autorepeat; + + hp-detect-pin { + label = "HEADPHONE"; + linux,input-type = ; + linux,code = ; + gpios = <&gpio_ao GPIOAO_9 GPIO_ACTIVE_HIGH>; + }; + }; + + joypad: gou_joypad { + compatible = "odroidgou-joypad"; + poll-interval = <10>; + pinctrl-0 = <&keypad_gpio_pins>; + pinctrl-names = "default"; + status = "okay"; + + joypad-name = "GO-Ultra Gamepad"; + //joypad-vendor = <0x045e>; + joypad-product = <0x1000>; + joypad-revision = <0x0100>; + + /* Analog sticks */ + io-channels = <&saradc 0>, <&saradc 1>, <&saradc 2>, <&saradc 3>; + io-channel-names = "key-RY", "key-RX", "key-LY", "key-LX"; + button-adc-scale = <4>; + button-adc-deadzone = <64>; + button-adc-fuzz = <32>; + button-adc-flat = <32>; + abs_x-p-tuning = <350>; + abs_x-n-tuning = <350>; + abs_y-p-tuning = <350>; + abs_y-n-tuning = <350>; + abs_rx-p-tuning = <350>; + abs_rx-n-tuning = <350>; + abs_ry-p-tuning = <350>; + abs_ry-n-tuning = <350>; + + /* Buttons */ + sw1 { + gpios = <&gpio GPIOX_0 GPIO_ACTIVE_LOW>; + label = "GPIO DPAD-UP"; + linux,code = ; // 0x220 + }; + sw2 { + gpios = <&gpio GPIOX_1 GPIO_ACTIVE_LOW>; + label = "GPIO DPAD-DOWN"; + linux,code = ; // 0x221 + }; + sw3 { + gpios = <&gpio GPIOX_2 GPIO_ACTIVE_LOW>; + label = "GPIO DPAD-LEFT"; + linux,code = ; // 0x222 + }; + sw4 { + gpios = <&gpio GPIOX_3 GPIO_ACTIVE_LOW>; + label = "GPIO DPAD-RIGHT"; + linux,code = ; // 0x223 + }; + sw5 { + gpios = <&gpio GPIOX_4 GPIO_ACTIVE_LOW>; + label = "GPIO BTN-A"; + linux,code = ; // 0x131 + }; + sw6 { + gpios = <&gpio GPIOX_5 GPIO_ACTIVE_LOW>; + label = "GPIO BTN-B"; + linux,code = ; // 0x130 + }; + sw7 { + gpios = <&gpio GPIOX_6 GPIO_ACTIVE_LOW>; + label = "GPIO BTN-Y"; + linux,code = ; // 0x134 + }; + sw8 { + gpios = <&gpio GPIOX_7 GPIO_ACTIVE_LOW>; + label = "GPIO BTN-X"; + linux,code = ; // 0x133 + }; + sw11 { + gpios = <&gpio GPIOX_10 GPIO_ACTIVE_LOW>; + label = "GPIO F2"; + linux,code = ; // 0x2c2 + }; + sw12 { + gpios = <&gpio GPIOX_11 GPIO_ACTIVE_LOW>; + label = "GPIO F3"; + linux,code = ; // 0x2c3 + }; + sw13 { + gpios = <&gpio GPIOX_12 GPIO_ACTIVE_LOW>; + label = "GPIO F4"; + linux,code = ; // 0x2c4 + }; + sw14 { + gpios = <&gpio GPIOX_13 GPIO_ACTIVE_LOW>; + label = "GPIO F5"; + linux,code = ; // 0x13c + }; + sw15 { + gpios = <&gpio GPIOX_14 GPIO_ACTIVE_LOW>; + label = "GPIO TOP-LEFT"; + linux,code = ; // 0x02 + }; + sw16 { + gpios = <&gpio GPIOX_15 GPIO_ACTIVE_LOW>; + label = "GPIO TOP-RIGHT"; + linux,code = ; // 0x05 + }; + sw17 { + gpios = <&gpio GPIOX_16 GPIO_ACTIVE_LOW>; + label = "GPIO F6"; + linux,code = ; + }; + sw18 { + gpios = <&gpio GPIOX_17 GPIO_ACTIVE_LOW>; + label = "GPIO F1"; + linux,code = ; + }; + sw19 { + gpios = <&gpio GPIOX_18 GPIO_ACTIVE_LOW>; + label = "GPIO TOP-RIGHT2"; + linux,code = ; + }; + sw20 { + gpios = <&gpio GPIOX_19 GPIO_ACTIVE_LOW>; + label = "GPIO TOP-LEFT2"; + linux,code = ; + }; + }; + + memory@0 { + device_type = "memory"; + reg = <0x0 0x0 0x0 0x40000000>; + }; + + emmc_pwrseq: emmc-pwrseq { + compatible = "mmc-pwrseq-emmc"; + reset-gpios = <&gpio BOOT_12 GPIO_ACTIVE_LOW>; + }; + + leds { + compatible = "gpio-leds"; + + led-blue { + color = ; + function = LED_FUNCTION_STATUS; + gpios = <&gpio_ao GPIOAO_11 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + }; + + led-red { + color = ; + function = LED_FUNCTION_STATUS; + gpios = <&gpio_ao GPIOAO_6 GPIO_ACTIVE_HIGH>; + }; + }; + + poweroff { + compatible = "hardkernel,odroid-go-ultra-poweroff"; + hardkernel,rk817-pmic = <&rk817>; + hardkernel,rk818-pmic = <&rk818>; + }; + + vdd_sys: regulator-vdd_sys { + compatible = "regulator-fixed"; + regulator-name = "VDD_SYS"; + regulator-min-microvolt = <3800000>; + regulator-max-microvolt = <3800000>; + regulator-always-on; + }; + + sound { + compatible = "amlogic,axg-sound-card"; + model = "ODROID-GO-ULTRA"; + audio-aux-devs = <&tdmout_b>; + audio-routing = "TDMOUT_B IN 0", "FRDDR_A OUT 1", + "TDM_B Playback", "TDMOUT_B OUT"; + + assigned-clocks = <&clkc CLKID_MPLL2>, + <&clkc CLKID_MPLL0>, + <&clkc CLKID_MPLL1>; + assigned-clock-parents = <0>, <0>, <0>; + assigned-clock-rates = <294912000>, + <270950400>, + <393216000>; + status = "okay"; + + dai-link-0 { + sound-dai = <&frddr_a>; + }; + + dai-link-1 { + sound-dai = <&frddr_b>; + }; + + dai-link-2 { + sound-dai = <&frddr_c>; + }; + + dai-link-3 { + sound-dai = <&toddr_a>; + }; + + dai-link-4 { + sound-dai = <&toddr_b>; + }; + + dai-link-5 { + sound-dai = <&toddr_c>; + }; + + /* 8ch hdmi interface */ + dai-link-6 { + sound-dai = <&tdmif_b>; + dai-format = "i2s"; + dai-tdm-slot-tx-mask-0 = <1 1>; + dai-tdm-slot-tx-mask-1 = <1 1>; + mclk-fs = <256>; + + codec-0 { + sound-dai = <&rk817>; + }; + }; + }; +}; + +&arb { + status = "okay"; +}; + +&cpu0 { + cpu-supply = <&vddcpu_b>; + operating-points-v2 = <&cpu_opp_table_0>; + clocks = <&clkc CLKID_CPU_CLK>; + clock-latency = <50000>; +}; + +&cpu1 { + cpu-supply = <&vddcpu_b>; + operating-points-v2 = <&cpu_opp_table_0>; + clocks = <&clkc CLKID_CPU_CLK>; + clock-latency = <50000>; +}; + +&cpu100 { + cpu-supply = <&vddcpu_a>; + operating-points-v2 = <&cpub_opp_table_1>; + clocks = <&clkc CLKID_CPUB_CLK>; + clock-latency = <50000>; +}; + +&cpu101 { + cpu-supply = <&vddcpu_a>; + operating-points-v2 = <&cpub_opp_table_1>; + clocks = <&clkc CLKID_CPUB_CLK>; + clock-latency = <50000>; +}; + +&cpu102 { + cpu-supply = <&vddcpu_a>; + operating-points-v2 = <&cpub_opp_table_1>; + clocks = <&clkc CLKID_CPUB_CLK>; + clock-latency = <50000>; +}; + +&cpu103 { + cpu-supply = <&vddcpu_a>; + operating-points-v2 = <&cpub_opp_table_1>; + clocks = <&clkc CLKID_CPUB_CLK>; + clock-latency = <50000>; +}; + +/* RK817 only supports 12.5mV steps, round up the values */ +&cpu_opp_table_0 { + opp-667000000 { + opp-microvolt = <725000>; + }; + opp-1000000000 { + opp-microvolt = <737500>; + }; + opp-1200000000 { + opp-microvolt = <737500>; + }; + opp-1398000000 { + opp-microvolt = <762500>; + }; + opp-1512000000 { + opp-microvolt = <800000>; + }; + opp-1608000000 { + opp-microvolt = <837500>; + }; + opp-1704000000 { + opp-microvolt = <862500>; + }; + opp-1896000000 { + opp-microvolt = <987500>; + }; + opp-1992000000 { + opp-microvolt = <1050000>; + }; + opp-2016000000 { + opp-hz = /bits/ 64 <2016000000>; + opp-microvolt = <1050000>; + }; +}; + +/* RK818 only supports 12.5mV steps, round up the values */ +&cpub_opp_table_1 { + opp-667000000 { + opp-microvolt = <750000>; + }; + opp-1000000000 { + opp-microvolt = <775000>; + }; + opp-1200000000 { + opp-microvolt = <775000>; + }; + opp-1398000000 { + opp-microvolt = <800000>; + }; + opp-1512000000 { + opp-microvolt = <825000>; + }; + opp-1608000000 { + opp-microvolt = <862500>; + }; + opp-1704000000 { + opp-microvolt = <900000>; + }; + opp-1800000000 { + opp-microvolt = <987500>; + }; + opp-1908000000 { + opp-microvolt = <1025000>; + }; + opp-2016000000 { + opp-hz = /bits/ 64 <2016000000>; + opp-microvolt = <1025000>; + }; + opp-2100000000 { + opp-hz = /bits/ 64 <2100000000>; + opp-microvolt = <1025000>; + }; + opp-2208000000 { + opp-hz = /bits/ 64 <2208000000>; + opp-microvolt = <1050000>; + }; + opp-2304000000 { + opp-hz = /bits/ 64 <2304000000>; + opp-microvolt = <1050000>; + }; + opp-2400000000 { + opp-hz = /bits/ 64 <2400000000>; + opp-microvolt = <1050000>; + }; +}; + +&i2c_AO { + status = "okay"; + pinctrl-0 = <&i2c_ao_sck_pins>, <&i2c_ao_sda_pins>; + pinctrl-names = "default"; + + rk818: pmic@1c { + compatible = "rockchip,rk818"; + reg = <0x1c>; + interrupt-parent = <&gpio_intc>; + interrupts = <7 IRQ_TYPE_LEVEL_LOW>; /* GPIOAO_7 */ + rockchip,system-power-controller; + clock-output-names = "rk808-clkout1", "rk808-clkout2"; + + vcc1-supply = <&vdd_sys>; + vcc2-supply = <&vdd_sys>; + vcc3-supply = <&vdd_sys>; + vcc4-supply = <&vdd_sys>; + vcc6-supply = <&vdd_sys>; + vcc7-supply = <&vcc_2v3>; + vcc8-supply = <&vcc_2v3>; + vcc9-supply = <&vddao_3v3>; + boost-supply = <&vdd_sys>; + switch-supply = <&vdd_sys>; + + regulators { + vddcpu_a: DCDC_REG1 { + regulator-name = "vddcpu_a"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <750000>; + regulator-max-microvolt = <1050000>; + regulator-ramp-delay = <6001>; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <750000>; + }; + }; + + vdd_ee: DCDC_REG2 { + regulator-name = "vdd_ee"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <875000>; + regulator-max-microvolt = <900000>; + regulator-ramp-delay = <6001>; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <875000>; + }; + }; + + vddq_1v1: DCDC_REG3 { + regulator-name = "vddq_1v1"; + regulator-always-on; + regulator-boot-on; + regulator-state-mem { + regulator-on-in-suspend; + }; + }; + + vddao_3v3: DCDC_REG4 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + regulator-name = "vddao_3v3"; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <3000000>; + }; + }; + + hp_5v: DCDC_BOOST { + regulator-always-on; + regulator-boot-on; + regulator-name = "hp_5v"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG1 { + regulator-boot-off; + regulator-name = "rk818_LDO1"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG2 { + regulator-boot-off; + regulator-name = "rk818_LDO2"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG3 { + regulator-boot-off; + regulator-name = "rk818_LDO3"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG4 { + regulator-boot-off; + regulator-name = "rk818_LDO4"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vddio_ao1v8: LDO_REG5 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vddio_ao1v8"; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + LDO_REG6 { + regulator-boot-off; + regulator-name = "rk818_LDO6"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vddq_1v8: LDO_REG7 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vddq_1v8"; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + LDO_REG8 { + regulator-boot-off; + regulator-name = "rk818_LDO8"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vddio_c: LDO_REG9 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vddio_c"; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <3300000>; + }; + }; + + vcc_sd: SWITCH_REG { + regulator-name = "vcc_sd"; + regulator-always-on; + regulator-boot-on; + regulator-state-mem { + regulator-on-in-suspend; + }; + }; + + rk818_otg_switch: OTG_SWITCH { + regulator-name = "otg_switch"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + }; + + battery { + compatible = "rockchip,rk818-battery"; + + ocv_table = < + 3470 3599 3671 3701 3728 3746 3762 + 3772 3781 3792 3816 3836 3866 3910 + 3942 3971 4002 4050 4088 4132 4200>; + design_capacity = <4000>; + design_qmax = <4100>; + bat_res = <180>; + max_input_current = <2000>; + max_chrg_current = <1500>; + max_chrg_voltage = <4250>; + sleep_enter_current = <300>; + sleep_exit_current = <300>; + power_off_thresd = <3450>; + zero_algorithm_vol = <3700>; + fb_temperature = <105>; + sample_res = <10>; + max_soc_offset = <60>; + energy_mode = <0>; + monitor_sec = <5>; + virtual_power = <0>; + power_dc2otg = <0>; + otg5v_suspend_enable = <0>; + }; + + charger { + compatible = "rockchip,rk818-charger"; + monitored-battery = <&bat>; + }; + + }; +}; + +&i2c3 { + status = "okay"; + pinctrl-0 = <&i2c3_sda_a_pins>, <&i2c3_sck_a_pins>; + pinctrl-names = "default"; + + rk817: pmic@20 { + compatible = "rockchip,rk817"; + reg = <0x20>; + status = "okay"; + interrupt-parent = <&gpio_intc>; + interrupts = <5 IRQ_TYPE_LEVEL_LOW>; /* GPIOAO_5 */ + wakeup-source; + + vcc1-supply = <&vdd_sys>; + vcc2-supply = <&vdd_sys>; + vcc3-supply = <&vdd_sys>; + vcc4-supply = <&vdd_sys>; + vcc5-supply = <&vdd_sys>; + vcc6-supply = <&vdd_sys>; + vcc7-supply = <&vdd_sys>; + vcc8-supply = <&vdd_sys>; + vcc9-supply = <&rk817_boost>; + + #sound-dai-cells = <0>; + clocks = <&codec_clk>; + clock-names = "mclk"; + + regulators { + DCDC_REG1 { + regulator-boot-off; + regulator-name = "rk817_BUCK1"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vddcpu_b: DCDC_REG2 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <725000>; + regulator-max-microvolt = <1050000>; + regulator-ramp-delay = <6001>; + regulator-initial-mode = <0x2>; + regulator-name = "vddcpu_b"; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1000000>; + }; + }; + + vcc_2v3: DCDC_REG3 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <2300000>; + regulator-max-microvolt = <2400000>; + regulator-initial-mode = <0x2>; + regulator-name = "vcc_2v3"; + regulator-state-mem { + regulator-on-in-suspend; + }; + }; + + DCDC_REG4 { + regulator-boot-off; + regulator-name = "rk817_BUCK4"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG1 { + regulator-boot-off; + regulator-name = "rk817_LDO1"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG2 { + regulator-boot-off; + regulator-name = "rk817_LDO2"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG3 { + regulator-boot-off; + regulator-name = "rk817_LDO3"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG4 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vdd_codec"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG5 { + regulator-boot-off; + regulator-name = "rk817_LDO5"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG6 { + regulator-boot-off; + regulator-name = "rk817_LDO6"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG7 { + regulator-boot-off; + regulator-name = "rk817_LDO7"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcc_lcd: LDO_REG8 { + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + regulator-name = "vcc_lcd"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG9 { + regulator-boot-off; + regulator-name = "rk817_LDO9"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + rk817_boost: BOOST { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5400000>; + regulator-name = "rk817_boost"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + usb_host: OTG_SWITCH { + regulator-name = "usb_host"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + }; + + rk817_codec: codec { + rockchip,mic-in-differential; + }; + }; +}; + +&clkc_audio { + status = "okay"; +}; + +ð_phy { + status = "disabled"; +}; + +&frddr_a { + status = "okay"; +}; + +&frddr_b { + status = "okay"; +}; + +&frddr_c { + status = "okay"; +}; + +&toddr_a { + status = "okay"; +}; + +&toddr_b { + status = "okay"; +}; + +&toddr_c { + status = "okay"; +}; + +&mipi_dsi { + status = "okay"; + + assigned-clocks = <&clkc CLKID_GP0_PLL>, + <&clkc CLKID_MIPI_DSI_PXCLK_SEL>, + <&clkc CLKID_MIPI_DSI_PXCLK>, + <&clkc CLKID_CTS_ENCL_SEL>, + <&clkc CLKID_VCLK2_SEL>; + assigned-clock-parents = <0>, + <&clkc CLKID_GP0_PLL>, + <0>, + <&clkc CLKID_VCLK2_DIV1>, + <&clkc CLKID_GP0_PLL>; + assigned-clock-rates = <344976000>, + <0>, + <344976000>, + <0>, + <0>; + + panel@0 { + compatible = "elida,kd50t048a", "sitronix,st7701"; + reset-gpios = <&gpio GPIOH_4 GPIO_ACTIVE_HIGH>; + IOVCC-supply = <&vcc_lcd>; + VCC-supply = <&vcc_lcd>; + backlight = <&panel_backlight>; + rotation = <270>; + reg = <0>; + + port { + mipi_in_panel: endpoint { + remote-endpoint = <&mipi_out_panel>; + }; + }; + }; +}; + +&mipi_analog_dphy { + status = "okay"; +}; + +&mipi_dphy { + status = "okay"; +}; + +&mipi_dsi_panel_port { + mipi_out_panel: endpoint { + remote-endpoint = <&mipi_in_panel>; + }; +}; + +&periphs_pinctrl { + keypad_gpio_pins: keypad-gpio { + mux { + groups = "GPIOX_0", "GPIOX_1", "GPIOX_2", "GPIOX_3", + "GPIOX_4", "GPIOX_5", "GPIOX_6", "GPIOX_7", + "GPIOX_8", "GPIOX_9", "GPIOX_10", "GPIOX_11", + "GPIOX_12", "GPIOX_13", "GPIOX_14", "GPIOX_15", + "GPIOX_16", "GPIOX_17", "GPIOX_18", "GPIOX_19"; + function = "gpio_periphs"; + bias-pull-up; + output-disable; + }; + }; +}; + +&pwm_ef { + status = "okay"; + pinctrl-0 = <&pwm_f_h_pins>; + pinctrl-names = "default"; +}; + +&saradc { + status = "okay"; + vref-supply = <&vddio_ao1v8>; +}; + +/* SD card */ +&sd_emmc_b { + status = "okay"; + pinctrl-0 = <&sdcard_c_pins>; + pinctrl-1 = <&sdcard_clk_gate_c_pins>; + pinctrl-names = "default", "clk-gate"; + + bus-width = <4>; + cap-sd-highspeed; + max-frequency = <50000000>; + disable-wp; + + cd-gpios = <&gpio GPIOC_6 GPIO_ACTIVE_LOW>; + vmmc-supply = <&vcc_sd>; + vqmmc-supply = <&vddio_c>; + +}; + +/* eMMC */ +&sd_emmc_c { + status = "okay"; + pinctrl-0 = <&emmc_ctrl_pins>, <&emmc_data_8b_pins>, <&emmc_ds_pins>; + pinctrl-1 = <&emmc_clk_gate_pins>; + pinctrl-names = "default", "clk-gate"; + + bus-width = <8>; + cap-mmc-highspeed; + mmc-ddr-1_8v; + mmc-hs200-1_8v; + max-frequency = <200000000>; + disable-wp; + + mmc-pwrseq = <&emmc_pwrseq>; + vmmc-supply = <&vcc_sd>; + vqmmc-supply = <&vddio_ao1v8>; +}; + + +&tdmif_b { + pinctrl-0 = <&mclk0_a_pins>, <&tdm_b_fs_pins>, <&tdm_b_sclk_pins>, + <&tdm_b_dout0_pins>; + pinctrl-names = "default"; + status = "okay"; + + assigned-clocks = <&clkc_audio AUD_CLKID_TDM_MCLK_PAD0>, + <&clkc_audio AUD_CLKID_TDM_SCLK_PAD1>, + <&clkc_audio AUD_CLKID_TDM_LRCLK_PAD1>; + assigned-clock-parents = <&clkc_audio AUD_CLKID_MST_B_MCLK>, + <&clkc_audio AUD_CLKID_MST_B_SCLK>, + <&clkc_audio AUD_CLKID_MST_B_LRCLK>; + assigned-clock-rates = <0>, <0>, <0>; +}; + +&tdmout_b { + status = "okay"; +}; + +&uart_AO { + status = "okay"; + pinctrl-0 = <&uart_ao_a_pins>; + pinctrl-names = "default"; +}; + +&usb { + status = "okay"; + dr_mode = "peripheral"; +}; + +&usb2_phy0 { + status = "okay"; +}; + +&usb2_phy1 { + status = "okay"; + phy-supply = <&usb_host>; +}; diff -rupN linux.orig/arch/arm64/boot/dts/amlogic/meson-g12b-powkiddy-rgb10-max-3.dts linux/arch/arm64/boot/dts/amlogic/meson-g12b-powkiddy-rgb10-max-3.dts --- linux.orig/arch/arm64/boot/dts/amlogic/meson-g12b-powkiddy-rgb10-max-3.dts 1970-01-01 00:00:00.000000000 +0000 +++ linux/arch/arm64/boot/dts/amlogic/meson-g12b-powkiddy-rgb10-max-3.dts 2023-09-12 12:04:13.567330573 +0000 @@ -0,0 +1,995 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2022 Neil Armstrong + * Copyright (C) 2023 BrooksyTech (https://github.com/brooksytech) + */ + +/dts-v1/; + +#include "meson-g12b-a311d.dtsi" +#include +#include +#include +#include +#include + +/ { + compatible = "powkiddy,rgb10-max-3", "amlogic,s922x", "amlogic,g12b"; + model = "Powkiddy RGB10 MAX 3"; + + aliases { + serial0 = &uart_AO; + rtc0 = &vrtc; + mmc0 = &sd_emmc_c; + mmc1 = &sd_emmc_b; + }; + + panel_backlight: backlight { + compatible = "pwm-backlight"; + pwms = <&pwm_ef 1 40000 0>; + brightness-levels = <0 255>; + num-interpolated-steps = <255>; + default-brightness-level = <255>; + }; + + bat: battery { + compatible = "simple-battery"; + voltage-max-design-microvolt = <4200000>; + voltage-min-design-microvolt = <3500000>; + charge-full-design-microamp-hours = <4000000>; + charge-term-current-microamp = <200000>; + constant-charge-current-max-microamp = <1500000>; + constant-charge-voltage-max-microvolt = <4200000>; + factory-internal-resistance-micro-ohms = <180000>; + + + ocv-capacity-celsius = <20>; + ocv-capacity-table-0 = <4146950 100>, <4001920 95>, <3967900 90>, <3919950 85>, + <3888450 80>, <3861850 75>, <3831540 70>, <3799130 65>, + <3768190 60>, <3745650 55>, <3726610 50>, <3711630 45>, + <3696720 40>, <3685660 35>, <3674950 30>, <3663050 25>, + <3649470 20>, <3635260 15>, <3616920 10>, <3592440 5>, + <3574170 0>; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + codec_clk: codec-clk { + compatible = "fixed-clock"; + clock-frequency = <12288000>; + clock-output-names = "codec_clk"; + #clock-cells = <0>; + }; + + gpio_keys: volume-keys { + compatible = "gpio-keys-polled"; + poll-interval = <5>; + autorepeat; + + volume-up-button { + label = "VOLUME-UP"; + linux,code = ; + gpios = <&gpio GPIOX_8 GPIO_ACTIVE_LOW>; + }; + volume-down-button { + label = "VOLUME-DOWN"; + linux,code = ; + gpios = <&gpio GPIOX_9 GPIO_ACTIVE_LOW>; + }; + }; + + joypad: gou_joypad { + compatible = "odroidgou-joypad"; + poll-interval = <10>; + pinctrl-0 = <&keypad_gpio_pins>; + pinctrl-names = "default"; + status = "okay"; + + joypad-name = "GO-Ultra Gamepad"; + //joypad-vendor = <0x045e>; + joypad-product = <0x1000>; + joypad-revision = <0x0100>; + + /* Analog sticks */ + + io-channels = <&saradc 0>, <&saradc 1>, <&saradc 2>, <&saradc 3>; + io-channel-names = "key-RY", "key-RX", "key-LY", "key-LX"; + button-adc-scale = <4>; + button-adc-deadzone = <400>; + button-adc-fuzz = <64>; + button-adc-flat = <32>; + abs_x-p-tuning = <350>; + abs_x-n-tuning = <350>; + abs_y-p-tuning = <350>; + abs_y-n-tuning = <350>; + abs_rx-p-tuning = <350>; + abs_rx-n-tuning = <350>; + abs_ry-p-tuning = <350>; + abs_ry-n-tuning = <350>; + + /* Buttons */ + sw1 { + gpios = <&gpio GPIOX_0 GPIO_ACTIVE_LOW>; + label = "GPIO DPAD-UP"; + linux,code = ; // 0x220 + }; + sw2 { + gpios = <&gpio GPIOX_1 GPIO_ACTIVE_LOW>; + label = "GPIO DPAD-DOWN"; + linux,code = ; // 0x221 + }; + sw3 { + gpios = <&gpio GPIOX_2 GPIO_ACTIVE_LOW>; + label = "GPIO DPAD-LEFT"; + linux,code = ; // 0x222 + }; + sw4 { + gpios = <&gpio GPIOX_3 GPIO_ACTIVE_LOW>; + label = "GPIO DPAD-RIGHT"; + linux,code = ; // 0x223 + }; + sw5 { + gpios = <&gpio GPIOX_4 GPIO_ACTIVE_LOW>; + label = "GPIO BTN-A"; + linux,code = ; // 0x131 + }; + sw6 { + gpios = <&gpio GPIOX_5 GPIO_ACTIVE_LOW>; + label = "GPIO BTN-B"; + linux,code = ; // 0x130 + }; + sw7 { + gpios = <&gpio GPIOX_6 GPIO_ACTIVE_LOW>; + label = "GPIO BTN-Y"; + linux,code = ; // 0x134 + }; + sw8 { + gpios = <&gpio GPIOX_7 GPIO_ACTIVE_LOW>; + label = "GPIO BTN-X"; + linux,code = ; // 0x133 + }; + sw11 { + gpios = <&gpio GPIOX_10 GPIO_ACTIVE_LOW>; + label = "GPIO F2"; + linux,code = ; // 0x2c2 + }; + sw12 { + gpios = <&gpio GPIOX_17 GPIO_ACTIVE_LOW>; + label = "GPIO F3"; + linux,code = ; // 0x2c3 + }; + sw13 { + gpios = <&gpio GPIOX_16 GPIO_ACTIVE_LOW>; + label = "GPIO F4"; + linux,code = ; // 0x2c4 + }; + sw14 { + gpios = <&gpio GPIOX_11 GPIO_ACTIVE_LOW>; + label = "GPIO F5"; + linux,code = ; // 0x13c + }; + sw15 { + gpios = <&gpio GPIOX_14 GPIO_ACTIVE_LOW>; + label = "GPIO TOP-LEFT"; + linux,code = ; // 0x02 + }; + sw16 { + gpios = <&gpio GPIOX_15 GPIO_ACTIVE_LOW>; + label = "GPIO TOP-RIGHT"; + linux,code = ; // 0x05 + }; + sw17 { + gpios = <&gpio GPIOX_13 GPIO_ACTIVE_LOW>; + label = "GPIO F6"; + linux,code = ; + }; + sw18 { + gpios = <&gpio GPIOX_12 GPIO_ACTIVE_LOW>; + label = "GPIO F1"; + linux,code = ; + }; + sw19 { + gpios = <&gpio GPIOX_18 GPIO_ACTIVE_LOW>; + label = "GPIO TOP-RIGHT2"; + linux,code = ; + }; + sw20 { + gpios = <&gpio GPIOX_19 GPIO_ACTIVE_LOW>; + label = "GPIO TOP-LEFT2"; + linux,code = ; + }; + }; + + memory@0 { + device_type = "memory"; + reg = <0x0 0x0 0x0 0x40000000>; + }; + + emmc_pwrseq: emmc-pwrseq { + compatible = "mmc-pwrseq-emmc"; + reset-gpios = <&gpio BOOT_12 GPIO_ACTIVE_LOW>; + }; + + leds { + compatible = "gpio-leds"; + + led-blue { + color = ; + function = LED_FUNCTION_STATUS; + gpios = <&gpio_ao GPIOAO_11 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + }; + + led-red { + color = ; + function = LED_FUNCTION_STATUS; + gpios = <&gpio_ao GPIOAO_6 GPIO_ACTIVE_HIGH>; + }; + }; + + poweroff { + compatible = "hardkernel,odroid-go-ultra-poweroff"; + hardkernel,rk817-pmic = <&rk817>; + hardkernel,rk818-pmic = <&rk818>; + }; + + vdd_sys: regulator-vdd_sys { + compatible = "regulator-fixed"; + regulator-name = "VDD_SYS"; + regulator-min-microvolt = <3800000>; + regulator-max-microvolt = <3800000>; + regulator-always-on; + }; + + sound { + compatible = "amlogic,axg-sound-card"; + model = "RGB10-MAX3"; + audio-aux-devs = <&tdmout_b>; + audio-routing = "TDMOUT_B IN 0", "FRDDR_A OUT 1", + "TDM_B Playback", "TDMOUT_B OUT"; + + assigned-clocks = <&clkc CLKID_MPLL2>, + <&clkc CLKID_MPLL0>, + <&clkc CLKID_MPLL1>; + assigned-clock-parents = <0>, <0>, <0>; + assigned-clock-rates = <294912000>, + <270950400>, + <393216000>; + status = "okay"; + + dai-link-0 { + sound-dai = <&frddr_a>; + }; + + dai-link-1 { + sound-dai = <&frddr_b>; + }; + + dai-link-2 { + sound-dai = <&frddr_c>; + }; + + dai-link-3 { + sound-dai = <&toddr_a>; + }; + + dai-link-4 { + sound-dai = <&toddr_b>; + }; + + dai-link-5 { + sound-dai = <&toddr_c>; + }; + + /* 8ch hdmi interface */ + dai-link-6 { + sound-dai = <&tdmif_b>; + dai-format = "i2s"; + dai-tdm-slot-tx-mask-0 = <1 1>; + dai-tdm-slot-tx-mask-1 = <1 1>; + mclk-fs = <256>; + + codec-0 { + sound-dai = <&rk817>; + }; + }; + }; +}; + +&arb { + status = "okay"; +}; + +&cpu0 { + cpu-supply = <&vddcpu_b>; + operating-points-v2 = <&cpu_opp_table_0>; + clocks = <&clkc CLKID_CPU_CLK>; + clock-latency = <50000>; +}; + +&cpu1 { + cpu-supply = <&vddcpu_b>; + operating-points-v2 = <&cpu_opp_table_0>; + clocks = <&clkc CLKID_CPU_CLK>; + clock-latency = <50000>; +}; + +&cpu100 { + cpu-supply = <&vddcpu_a>; + operating-points-v2 = <&cpub_opp_table_1>; + clocks = <&clkc CLKID_CPUB_CLK>; + clock-latency = <50000>; +}; + +&cpu101 { + cpu-supply = <&vddcpu_a>; + operating-points-v2 = <&cpub_opp_table_1>; + clocks = <&clkc CLKID_CPUB_CLK>; + clock-latency = <50000>; +}; + +&cpu102 { + cpu-supply = <&vddcpu_a>; + operating-points-v2 = <&cpub_opp_table_1>; + clocks = <&clkc CLKID_CPUB_CLK>; + clock-latency = <50000>; +}; + +&cpu103 { + cpu-supply = <&vddcpu_a>; + operating-points-v2 = <&cpub_opp_table_1>; + clocks = <&clkc CLKID_CPUB_CLK>; + clock-latency = <50000>; +}; + +/* RK817 only supports 12.5mV steps, round up the values */ +&cpu_opp_table_0 { + opp-667000000 { + opp-microvolt = <731250>; + }; + opp-1000000000 { + opp-microvolt = <760000>; + }; + opp-1200000000 { + opp-microvolt = <780000>; + }; + opp-1398000000 { + opp-microvolt = <800000>; + }; + opp-1512000000 { + opp-microvolt = <860000>; + }; + opp-1608000000 { + opp-microvolt = <900000>; + }; + opp-1704000000 { + opp-microvolt = <950000>; + }; + opp-1800000000 { + opp-microvolt = <1000000>; + }; +}; + +/* RK818 only supports 12.5mV steps, round up the values */ +&cpub_opp_table_1 { + opp-667000000 { + opp-microvolt = <750000>; + }; + opp-1000000000 { + opp-microvolt = <775000>; + }; + opp-1200000000 { + opp-microvolt = <775000>; + }; + opp-1398000000 { + opp-microvolt = <800000>; + }; + opp-1512000000 { + opp-microvolt = <825000>; + }; + opp-1608000000 { + opp-microvolt = <862500>; + }; + opp-1704000000 { + opp-microvolt = <900000>; + }; + opp-1800000000 { + opp-microvolt = <987500>; + }; + opp-1908000000 { + opp-microvolt = <1025000>; + }; + opp-2016000000 { + opp-hz = /bits/ 64 <2016000000>; + opp-microvolt = <1025000>; + }; + opp-2100000000 { + opp-hz = /bits/ 64 <2100000000>; + opp-microvolt = <1025000>; + }; + opp-2208000000 { + opp-hz = /bits/ 64 <2208000000>; + opp-microvolt = <1050000>; + }; +}; + +&i2c_AO { + status = "okay"; + pinctrl-0 = <&i2c_ao_sck_pins>, <&i2c_ao_sda_pins>; + pinctrl-names = "default"; + + rk818: pmic@1c { + compatible = "rockchip,rk818"; + reg = <0x1c>; + interrupt-parent = <&gpio_intc>; + interrupts = <7 IRQ_TYPE_LEVEL_LOW>; /* GPIOAO_7 */ + rockchip,system-power-controller; + clock-output-names = "rk808-clkout1", "rk808-clkout2"; + + vcc1-supply = <&vdd_sys>; + vcc2-supply = <&vdd_sys>; + vcc3-supply = <&vdd_sys>; + vcc4-supply = <&vdd_sys>; + vcc6-supply = <&vdd_sys>; + vcc7-supply = <&vcc_2v3>; + vcc8-supply = <&vcc_2v3>; + vcc9-supply = <&vddao_3v3>; + boost-supply = <&vdd_sys>; + switch-supply = <&vdd_sys>; + + regulators { + vddcpu_a: DCDC_REG1 { + regulator-name = "vddcpu_a"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <750000>; + regulator-max-microvolt = <1050000>; + regulator-ramp-delay = <6001>; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <750000>; + }; + }; + + vdd_ee: DCDC_REG2 { + regulator-name = "vdd_ee"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <875000>; + regulator-max-microvolt = <900000>; + regulator-ramp-delay = <6001>; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <875000>; + }; + }; + + vddq_1v1: DCDC_REG3 { + regulator-name = "vddq_1v1"; + regulator-always-on; + regulator-boot-on; + regulator-state-mem { + regulator-on-in-suspend; + }; + }; + + vddao_3v3: DCDC_REG4 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + regulator-name = "vddao_3v3"; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <3000000>; + }; + }; + + hp_5v: DCDC_BOOST { + regulator-always-on; + regulator-boot-on; + regulator-name = "hp_5v"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG1 { + regulator-boot-off; + regulator-name = "rk818_LDO1"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG2 { + regulator-boot-off; + regulator-name = "rk818_LDO2"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG3 { + regulator-boot-off; + regulator-name = "rk818_LDO3"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG4 { + regulator-boot-off; + regulator-name = "rk818_LDO4"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vddio_ao1v8: LDO_REG5 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vddio_ao1v8"; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + LDO_REG6 { + regulator-boot-off; + regulator-name = "rk818_LDO6"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vddq_1v8: LDO_REG7 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vddq_1v8"; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + LDO_REG8 { + regulator-boot-off; + regulator-name = "rk818_LDO8"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vddio_c: LDO_REG9 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vddio_c"; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <3300000>; + }; + }; + + vcc_sd: SWITCH_REG { + regulator-name = "vcc_sd"; + regulator-always-on; + regulator-boot-on; + regulator-state-mem { + regulator-on-in-suspend; + }; + }; + + rk818_otg_switch: OTG_SWITCH { + regulator-name = "otg_switch"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + }; + + battery { + compatible = "rockchip,rk818-battery"; + + ocv_table = < + 3470 3599 3671 3701 3728 3746 3762 + 3772 3781 3792 3816 3836 3866 3910 + 3942 3971 4002 4050 4088 4132 4200>; + design_capacity = <4000>; + design_qmax = <4100>; + bat_res = <180>; + max_input_current = <2000>; + max_chrg_current = <1500>; + max_chrg_voltage = <4250>; + sleep_enter_current = <300>; + sleep_exit_current = <300>; + power_off_thresd = <3450>; + zero_algorithm_vol = <3700>; + fb_temperature = <105>; + sample_res = <10>; + max_soc_offset = <60>; + energy_mode = <0>; + monitor_sec = <5>; + virtual_power = <0>; + power_dc2otg = <0>; + otg5v_suspend_enable = <0>; + }; + + charger { + compatible = "rockchip,rk818-charger"; + monitored-battery = <&bat>; + }; + + }; +}; + +&i2c3 { + status = "okay"; + pinctrl-0 = <&i2c3_sda_a_pins>, <&i2c3_sck_a_pins>; + pinctrl-names = "default"; + + rk817: pmic@20 { + compatible = "rockchip,rk817"; + reg = <0x20>; + status = "okay"; + interrupt-parent = <&gpio_intc>; + interrupts = <5 IRQ_TYPE_LEVEL_LOW>; /* GPIOAO_5 */ + wakeup-source; + + vcc1-supply = <&vdd_sys>; + vcc2-supply = <&vdd_sys>; + vcc3-supply = <&vdd_sys>; + vcc4-supply = <&vdd_sys>; + vcc5-supply = <&vdd_sys>; + vcc6-supply = <&vdd_sys>; + vcc7-supply = <&vdd_sys>; + vcc8-supply = <&vdd_sys>; + vcc9-supply = <&rk817_boost>; + + #sound-dai-cells = <0>; + clocks = <&codec_clk>; + clock-names = "mclk"; + + regulators { + DCDC_REG1 { + regulator-boot-off; + regulator-name = "rk817_BUCK1"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vddcpu_b: DCDC_REG2 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <725000>; + regulator-max-microvolt = <1050000>; + regulator-ramp-delay = <6001>; + regulator-initial-mode = <0x2>; + regulator-name = "vddcpu_b"; + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1000000>; + }; + }; + + vcc_2v3: DCDC_REG3 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <2300000>; + regulator-max-microvolt = <2400000>; + regulator-initial-mode = <0x2>; + regulator-name = "vcc_2v3"; + regulator-state-mem { + regulator-on-in-suspend; + }; + }; + + DCDC_REG4 { + regulator-boot-off; + regulator-name = "rk817_BUCK4"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG1 { + regulator-boot-off; + regulator-name = "rk817_LDO1"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG2 { + regulator-boot-off; + regulator-name = "rk817_LDO2"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG3 { + regulator-boot-off; + regulator-name = "rk817_LDO3"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG4 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vdd_codec"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG5 { + regulator-boot-off; + regulator-name = "rk817_LDO5"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG6 { + regulator-boot-off; + regulator-name = "rk817_LDO6"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG7 { + regulator-boot-off; + regulator-name = "rk817_LDO7"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcc_lcd: LDO_REG8 { + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + regulator-name = "vcc_lcd"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + LDO_REG9 { + regulator-boot-off; + regulator-name = "rk817_LDO9"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + rk817_boost: BOOST { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5400000>; + regulator-name = "rk817_boost"; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + usb_host: OTG_SWITCH { + regulator-name = "usb_host"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + }; + + rk817_codec: codec { + rockchip,mic-in-differential; + }; + }; +}; + +&clkc_audio { + status = "okay"; +}; + +ð_phy { + status = "disabled"; +}; + +&frddr_a { + status = "okay"; +}; + +&frddr_b { + status = "okay"; +}; + +&frddr_c { + status = "okay"; +}; + +&toddr_a { + status = "okay"; +}; + +&toddr_b { + status = "okay"; +}; + +&toddr_c { + status = "okay"; +}; + +&mipi_dsi { + status = "okay"; + + assigned-clocks = <&clkc CLKID_GP0_PLL>, + <&clkc CLKID_MIPI_DSI_PXCLK_SEL>, + <&clkc CLKID_MIPI_DSI_PXCLK>, + <&clkc CLKID_CTS_ENCL_SEL>, + <&clkc CLKID_VCLK2_SEL>; + assigned-clock-parents = <0>, + <&clkc CLKID_GP0_PLL>, + <0>, + <&clkc CLKID_VCLK2_DIV1>, + <&clkc CLKID_GP0_PLL>; + assigned-clock-rates = <344976000>, + <0>, + <344976000>, + <0>, + <0>; + + panel@0 { + compatible = "elida,kd50t048a", "sitronix,st7701"; + reset-gpios = <&gpio GPIOH_4 GPIO_ACTIVE_HIGH>; + IOVCC-supply = <&vcc_lcd>; + VCC-supply = <&vcc_lcd>; + backlight = <&panel_backlight>; + rotation = <270>; + reg = <0>; + + port { + mipi_in_panel: endpoint { + remote-endpoint = <&mipi_out_panel>; + }; + }; + }; +}; + +&mipi_analog_dphy { + status = "okay"; +}; + +&mipi_dphy { + status = "okay"; +}; + +&mipi_dsi_panel_port { + mipi_out_panel: endpoint { + remote-endpoint = <&mipi_in_panel>; + }; +}; + +&periphs_pinctrl { + keypad_gpio_pins: keypad-gpio { + mux { + groups = "GPIOX_0", "GPIOX_1", "GPIOX_2", "GPIOX_3", + "GPIOX_4", "GPIOX_5", "GPIOX_6", "GPIOX_7", + "GPIOX_8", "GPIOX_9", "GPIOX_10", "GPIOX_11", + "GPIOX_12", "GPIOX_13", "GPIOX_14", "GPIOX_15", + "GPIOX_16", "GPIOX_17", "GPIOX_18", "GPIOX_19"; + function = "gpio_periphs"; + bias-pull-up; + output-disable; + }; + }; +}; + +&pwm_ef { + status = "okay"; + pinctrl-0 = <&pwm_f_h_pins>; + pinctrl-names = "default"; +}; + +&saradc { + status = "okay"; + vref-supply = <&vddio_ao1v8>; +}; + +/* SD card */ +&sd_emmc_b { + status = "okay"; + pinctrl-0 = <&sdcard_c_pins>; + pinctrl-1 = <&sdcard_clk_gate_c_pins>; + pinctrl-names = "default", "clk-gate"; + + bus-width = <4>; + cap-sd-highspeed; + max-frequency = <50000000>; + disable-wp; + + cd-gpios = <&gpio GPIOC_6 GPIO_ACTIVE_LOW>; + vmmc-supply = <&vcc_sd>; + vqmmc-supply = <&vddio_c>; + +}; + +/* eMMC */ +&sd_emmc_c { + status = "okay"; + pinctrl-0 = <&emmc_ctrl_pins>, <&emmc_data_8b_pins>, <&emmc_ds_pins>; + pinctrl-1 = <&emmc_clk_gate_pins>; + pinctrl-names = "default", "clk-gate"; + + bus-width = <8>; + cap-mmc-highspeed; + mmc-ddr-1_8v; + mmc-hs200-1_8v; + max-frequency = <200000000>; + disable-wp; + + mmc-pwrseq = <&emmc_pwrseq>; + vmmc-supply = <&vcc_sd>; + vqmmc-supply = <&vddio_ao1v8>; +}; + + +&tdmif_b { + pinctrl-0 = <&mclk0_a_pins>, <&tdm_b_fs_pins>, <&tdm_b_sclk_pins>, + <&tdm_b_dout0_pins>; + pinctrl-names = "default"; + status = "okay"; + + assigned-clocks = <&clkc_audio AUD_CLKID_TDM_MCLK_PAD0>, + <&clkc_audio AUD_CLKID_TDM_SCLK_PAD1>, + <&clkc_audio AUD_CLKID_TDM_LRCLK_PAD1>; + assigned-clock-parents = <&clkc_audio AUD_CLKID_MST_B_MCLK>, + <&clkc_audio AUD_CLKID_MST_B_SCLK>, + <&clkc_audio AUD_CLKID_MST_B_LRCLK>; + assigned-clock-rates = <0>, <0>, <0>; +}; + +&tdmout_b { + status = "okay"; +}; + +&uart_AO { + status = "okay"; + pinctrl-0 = <&uart_ao_a_pins>; + pinctrl-names = "default"; +}; + +&usb { + status = "okay"; + dr_mode = "peripheral"; +}; + +&usb2_phy0 { + status = "okay"; +}; + +&usb2_phy1 { + status = "okay"; + phy-supply = <&usb_host>; +}; diff -rupN linux.orig/arch/arm64/boot/dts/amlogic/meson-g12b-s922x.dtsi linux/arch/arm64/boot/dts/amlogic/meson-g12b-s922x.dtsi --- linux.orig/arch/arm64/boot/dts/amlogic/meson-g12b-s922x.dtsi 2023-09-12 12:02:56.937601871 +0000 +++ linux/arch/arm64/boot/dts/amlogic/meson-g12b-s922x.dtsi 2023-09-12 12:03:27.490291628 +0000 @@ -11,6 +11,11 @@ compatible = "operating-points-v2"; opp-shared; + opp-667000000 { + opp-hz = /bits/ 64 <667000000>; + opp-microvolt = <731000>; + }; + opp-1000000000 { opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <731000>; @@ -56,6 +61,11 @@ compatible = "operating-points-v2"; opp-shared; + opp-667000000 { + opp-hz = /bits/ 64 <667000000>; + opp-microvolt = <751000>; + }; + opp-1000000000 { opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <771000>; diff -rupN linux.orig/arch/arm64/boot/dts/amlogic/meson-g12b.dtsi linux/arch/arm64/boot/dts/amlogic/meson-g12b.dtsi --- linux.orig/arch/arm64/boot/dts/amlogic/meson-g12b.dtsi 2023-09-12 12:02:56.937601871 +0000 +++ linux/arch/arm64/boot/dts/amlogic/meson-g12b.dtsi 2023-09-12 12:03:27.490291628 +0000 @@ -105,6 +105,8 @@ l2: l2-cache0 { compatible = "cache"; + cache-level = <2>; + cache-unified; }; }; }; @@ -137,5 +139,6 @@ }; &mali { - dma-coherent; + system-coherency = <0>; + power_policy = "always_on"; }; diff -rupN linux.orig/drivers/gpu/drm/drm_panel_orientation_quirks.c linux/drivers/gpu/drm/drm_panel_orientation_quirks.c --- linux.orig/drivers/gpu/drm/drm_panel_orientation_quirks.c 2023-09-12 12:02:57.953624819 +0000 +++ linux/drivers/gpu/drm/drm_panel_orientation_quirks.c 2023-09-12 12:03:27.490291628 +0000 @@ -461,7 +461,7 @@ EXPORT_SYMBOL(drm_get_panel_orientation_ /* There are no quirks for non x86 devices yet */ int drm_get_panel_orientation_quirk(int width, int height) { - return DRM_MODE_PANEL_ORIENTATION_UNKNOWN; + return DRM_MODE_PANEL_ORIENTATION_LEFT_UP; } EXPORT_SYMBOL(drm_get_panel_orientation_quirk); diff -rupN linux.orig/drivers/input/Kconfig linux/drivers/input/Kconfig --- linux.orig/drivers/input/Kconfig 2023-09-12 12:02:58.173629790 +0000 +++ linux/drivers/input/Kconfig 2023-09-12 12:03:27.490291628 +0000 @@ -51,6 +51,19 @@ config INPUT_FF_MEMLESS To compile this driver as a module, choose M here: the module will be called ff-memless. +config INPUT_POLLDEV + tristate "Polled input device skeleton" + help + Say Y here if you are using a driver for an input + device that periodically polls hardware state. This + option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called input-polldev. + config INPUT_SPARSEKMAP tristate "Sparse keymap support library" help diff -rupN linux.orig/drivers/input/Makefile linux/drivers/input/Makefile --- linux.orig/drivers/input/Makefile 2023-09-12 12:02:58.173629790 +0000 +++ linux/drivers/input/Makefile 2023-09-12 12:03:27.490291628 +0000 @@ -10,6 +10,7 @@ input-core-y := input.o input-compat.o i input-core-y += touchscreen.o obj-$(CONFIG_INPUT_FF_MEMLESS) += ff-memless.o +obj-$(CONFIG_INPUT_POLLDEV) += input-polldev.o obj-$(CONFIG_INPUT_SPARSEKMAP) += sparse-keymap.o obj-$(CONFIG_INPUT_MATRIXKMAP) += matrix-keymap.o obj-$(CONFIG_INPUT_VIVALDIFMAP) += vivaldi-fmap.o diff -rupN linux.orig/drivers/input/input-polldev.c linux/drivers/input/input-polldev.c --- linux.orig/drivers/input/input-polldev.c 1970-01-01 00:00:00.000000000 +0000 +++ linux/drivers/input/input-polldev.c 2023-09-12 12:03:27.490291628 +0000 @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generic implementation of a polled input device + + * Copyright (c) 2007 Dmitry Torokhov + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Dmitry Torokhov "); +MODULE_DESCRIPTION("Generic implementation of a polled input device"); +MODULE_LICENSE("GPL v2"); + +static void input_polldev_queue_work(struct input_polled_dev *dev) +{ + unsigned long delay; + + delay = msecs_to_jiffies(dev->poll_interval); + if (delay >= HZ) + delay = round_jiffies_relative(delay); + + queue_delayed_work(system_freezable_wq, &dev->work, delay); +} + +static void input_polled_device_work(struct work_struct *work) +{ + struct input_polled_dev *dev = + container_of(work, struct input_polled_dev, work.work); + + dev->poll(dev); + input_polldev_queue_work(dev); +} + +static int input_open_polled_device(struct input_dev *input) +{ + struct input_polled_dev *dev = input_get_drvdata(input); + + if (dev->open) + dev->open(dev); + + /* Only start polling if polling is enabled */ + if (dev->poll_interval > 0) { + dev->poll(dev); + input_polldev_queue_work(dev); + } + + return 0; +} + +static void input_close_polled_device(struct input_dev *input) +{ + struct input_polled_dev *dev = input_get_drvdata(input); + + cancel_delayed_work_sync(&dev->work); + + if (dev->close) + dev->close(dev); +} + +/* SYSFS interface */ + +static ssize_t input_polldev_get_poll(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct input_polled_dev *polldev = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", polldev->poll_interval); +} + +static ssize_t input_polldev_set_poll(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct input_polled_dev *polldev = dev_get_drvdata(dev); + struct input_dev *input = polldev->input; + unsigned int interval; + int err; + + err = kstrtouint(buf, 0, &interval); + if (err) + return err; + + if (interval < polldev->poll_interval_min) + return -EINVAL; + + if (interval > polldev->poll_interval_max) + return -EINVAL; + + mutex_lock(&input->mutex); + + polldev->poll_interval = interval; + + if (input->users) { + cancel_delayed_work_sync(&polldev->work); + if (polldev->poll_interval > 0) + input_polldev_queue_work(polldev); + } + + mutex_unlock(&input->mutex); + + return count; +} + +static DEVICE_ATTR(poll, S_IRUGO | S_IWUSR, input_polldev_get_poll, + input_polldev_set_poll); + + +static ssize_t input_polldev_get_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct input_polled_dev *polldev = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", polldev->poll_interval_max); +} + +static DEVICE_ATTR(max, S_IRUGO, input_polldev_get_max, NULL); + +static ssize_t input_polldev_get_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct input_polled_dev *polldev = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", polldev->poll_interval_min); +} + +static DEVICE_ATTR(min, S_IRUGO, input_polldev_get_min, NULL); + +static struct attribute *sysfs_attrs[] = { + &dev_attr_poll.attr, + &dev_attr_max.attr, + &dev_attr_min.attr, + NULL +}; + +static struct attribute_group input_polldev_attribute_group = { + .attrs = sysfs_attrs +}; + +static const struct attribute_group *input_polldev_attribute_groups[] = { + &input_polldev_attribute_group, + NULL +}; + +/** + * input_allocate_polled_device - allocate memory for polled device + * + * The function allocates memory for a polled device and also + * for an input device associated with this polled device. + */ +struct input_polled_dev *input_allocate_polled_device(void) +{ + struct input_polled_dev *dev; + + dev = kzalloc(sizeof(struct input_polled_dev), GFP_KERNEL); + if (!dev) + return NULL; + + dev->input = input_allocate_device(); + if (!dev->input) { + kfree(dev); + return NULL; + } + + return dev; +} +EXPORT_SYMBOL(input_allocate_polled_device); + +struct input_polled_devres { + struct input_polled_dev *polldev; +}; + +static int devm_input_polldev_match(struct device *dev, void *res, void *data) +{ + struct input_polled_devres *devres = res; + + return devres->polldev == data; +} + +static void devm_input_polldev_release(struct device *dev, void *res) +{ + struct input_polled_devres *devres = res; + struct input_polled_dev *polldev = devres->polldev; + + dev_dbg(dev, "%s: dropping reference/freeing %s\n", + __func__, dev_name(&polldev->input->dev)); + + input_put_device(polldev->input); + kfree(polldev); +} + +static void devm_input_polldev_unregister(struct device *dev, void *res) +{ + struct input_polled_devres *devres = res; + struct input_polled_dev *polldev = devres->polldev; + + dev_dbg(dev, "%s: unregistering device %s\n", + __func__, dev_name(&polldev->input->dev)); + input_unregister_device(polldev->input); + + /* + * Note that we are still holding extra reference to the input + * device so it will stick around until devm_input_polldev_release() + * is called. + */ +} + +/** + * devm_input_allocate_polled_device - allocate managed polled device + * @dev: device owning the polled device being created + * + * Returns prepared &struct input_polled_dev or %NULL. + * + * Managed polled input devices do not need to be explicitly unregistered + * or freed as it will be done automatically when owner device unbinds + * from * its driver (or binding fails). Once such managed polled device + * is allocated, it is ready to be set up and registered in the same + * fashion as regular polled input devices (using + * input_register_polled_device() function). + * + * If you want to manually unregister and free such managed polled devices, + * it can be still done by calling input_unregister_polled_device() and + * input_free_polled_device(), although it is rarely needed. + * + * NOTE: the owner device is set up as parent of input device and users + * should not override it. + */ +struct input_polled_dev *devm_input_allocate_polled_device(struct device *dev) +{ + struct input_polled_dev *polldev; + struct input_polled_devres *devres; + + devres = devres_alloc(devm_input_polldev_release, sizeof(*devres), + GFP_KERNEL); + if (!devres) + return NULL; + + polldev = input_allocate_polled_device(); + if (!polldev) { + devres_free(devres); + return NULL; + } + + polldev->input->dev.parent = dev; + polldev->devres_managed = true; + + devres->polldev = polldev; + devres_add(dev, devres); + + return polldev; +} +EXPORT_SYMBOL(devm_input_allocate_polled_device); + +/** + * input_free_polled_device - free memory allocated for polled device + * @dev: device to free + * + * The function frees memory allocated for polling device and drops + * reference to the associated input device. + */ +void input_free_polled_device(struct input_polled_dev *dev) +{ + if (dev) { + if (dev->devres_managed) + WARN_ON(devres_destroy(dev->input->dev.parent, + devm_input_polldev_release, + devm_input_polldev_match, + dev)); + input_put_device(dev->input); + kfree(dev); + } +} +EXPORT_SYMBOL(input_free_polled_device); + +/** + * input_register_polled_device - register polled device + * @dev: device to register + * + * The function registers previously initialized polled input device + * with input layer. The device should be allocated with call to + * input_allocate_polled_device(). Callers should also set up poll() + * method and set up capabilities (id, name, phys, bits) of the + * corresponding input_dev structure. + */ +int input_register_polled_device(struct input_polled_dev *dev) +{ + struct input_polled_devres *devres = NULL; + struct input_dev *input = dev->input; + int error; + + if (dev->devres_managed) { + devres = devres_alloc(devm_input_polldev_unregister, + sizeof(*devres), GFP_KERNEL); + if (!devres) + return -ENOMEM; + + devres->polldev = dev; + } + + input_set_drvdata(input, dev); + INIT_DELAYED_WORK(&dev->work, input_polled_device_work); + + if (!dev->poll_interval) + dev->poll_interval = 500; + if (!dev->poll_interval_max) + dev->poll_interval_max = dev->poll_interval; + + input->open = input_open_polled_device; + input->close = input_close_polled_device; + + input->dev.groups = input_polldev_attribute_groups; + + error = input_register_device(input); + if (error) { + devres_free(devres); + return error; + } + + /* + * Take extra reference to the underlying input device so + * that it survives call to input_unregister_polled_device() + * and is deleted only after input_free_polled_device() + * has been invoked. This is needed to ease task of freeing + * sparse keymaps. + */ + input_get_device(input); + + if (dev->devres_managed) { + dev_dbg(input->dev.parent, "%s: registering %s with devres.\n", + __func__, dev_name(&input->dev)); + devres_add(input->dev.parent, devres); + } + + return 0; +} +EXPORT_SYMBOL(input_register_polled_device); + +/** + * input_unregister_polled_device - unregister polled device + * @dev: device to unregister + * + * The function unregisters previously registered polled input + * device from input layer. Polling is stopped and device is + * ready to be freed with call to input_free_polled_device(). + */ +void input_unregister_polled_device(struct input_polled_dev *dev) +{ + if (dev->devres_managed) + WARN_ON(devres_destroy(dev->input->dev.parent, + devm_input_polldev_unregister, + devm_input_polldev_match, + dev)); + + input_unregister_device(dev->input); +} +EXPORT_SYMBOL(input_unregister_polled_device); diff -rupN linux.orig/drivers/input/joystick/Kconfig linux/drivers/input/joystick/Kconfig --- linux.orig/drivers/input/joystick/Kconfig 2023-09-12 12:02:58.173629790 +0000 +++ linux/drivers/input/joystick/Kconfig 2023-09-12 12:03:27.490291628 +0000 @@ -344,6 +344,12 @@ config JOYSTICK_MAPLE To compile this as a module choose M here: the module will be called maplecontrol. +config JOYSTICK_ODROID_GOU + tristate "ODROID-Go-Ultra joypad driver" + depends on INPUT_POLLDEV + help + Made for ODROID-GO-Ultra. + config JOYSTICK_PSXPAD_SPI tristate "PlayStation 1/2 joypads via SPI interface" depends on SPI diff -rupN linux.orig/drivers/input/joystick/Makefile linux/drivers/input/joystick/Makefile --- linux.orig/drivers/input/joystick/Makefile 2023-09-12 12:02:58.173629790 +0000 +++ linux/drivers/input/joystick/Makefile 2023-09-12 12:03:27.490291628 +0000 @@ -25,6 +25,7 @@ obj-$(CONFIG_JOYSTICK_JOYDUMP) += joydu obj-$(CONFIG_JOYSTICK_MAGELLAN) += magellan.o obj-$(CONFIG_JOYSTICK_MAPLE) += maplecontrol.o obj-$(CONFIG_JOYSTICK_N64) += n64joy.o +obj-$(CONFIG_JOYSTICK_ODROID_GOU) += odroid-gou-joypad.o obj-$(CONFIG_JOYSTICK_PSXPAD_SPI) += psxpad-spi.o obj-$(CONFIG_JOYSTICK_PXRC) += pxrc.o obj-$(CONFIG_JOYSTICK_QWIIC) += qwiic-joystick.o diff -rupN linux.orig/drivers/input/joystick/amlogic-saradc.h linux/drivers/input/joystick/amlogic-saradc.h --- linux.orig/drivers/input/joystick/amlogic-saradc.h 1970-01-01 00:00:00.000000000 +0000 +++ linux/drivers/input/joystick/amlogic-saradc.h 2023-09-12 12:03:27.490291628 +0000 @@ -0,0 +1,32 @@ +/* + * include/dt-bindings/iio/adc/amlogic-saradc.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef _DT_BINDINGS_IIO_ADC_AMLOGIC_H +#define _DT_BINDINGS_IIO_ADC_AMLOGIC_H + +#define SARADC_CH0 0 +#define SARADC_CH1 1 +#define SARADC_CH2 2 +#define SARADC_CH3 3 +#define SARADC_CH4 4 +#define SARADC_CH5 5 +#define SARADC_CH6 6 +#define SARADC_CH7 7 + +#define SARADC_CH_NUM 8 + +#endif diff -rupN linux.orig/drivers/input/joystick/odroid-gou-joypad.c linux/drivers/input/joystick/odroid-gou-joypad.c --- linux.orig/drivers/input/joystick/odroid-gou-joypad.c 1970-01-01 00:00:00.000000000 +0000 +++ linux/drivers/input/joystick/odroid-gou-joypad.c 2023-09-12 12:03:27.490291628 +0000 @@ -0,0 +1,926 @@ +/*----------------------------------------------------------------------------*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "amlogic-saradc.h" +#include + +/*----------------------------------------------------------------------------*/ +#define DRV_NAME "odroidgo_joypad" + +/*----------------------------------------------------------------------------*/ + + +/*----------------------------------------------------------------------------*/ +#define ADC_MAX_VOLTAGE 1800 +#define ADC_DATA_TUNING(x, p) ((x * p) / 100) +#define ADC_TUNING_DEFAULT 180 + +struct bt_adc { + /* report value (mV) */ + int value; + /* report type */ + int report_type; + /* input device init value (mV) */ + int max, min; + /* calibrated adc value */ + int cal; + /* adc scale value */ + int scale; + /* invert report */ + bool invert; + /* adc channel */ + int channel; + /* adc data tuning value([percent), p = positive, n = negative */ + int tuning_p, tuning_n; +}; + +struct bt_gpio { + /* GPIO Request label */ + const char *label; + /* GPIO Number */ + int num; + /* report type */ + int report_type; + /* report linux code */ + int linux_code; + /* prev button value */ + bool old_value; + /* button press level */ + bool active_level; +}; + +struct joypad { + struct device *dev; + struct input_polled_dev *poll_dev; + int poll_interval; + + /* report enable/disable */ + bool enable; + + /* analog mux & joystick control */ + struct iio_channel *adc_ch[SARADC_CH_NUM]; + + /* adc input channel count */ + int chan_count; + /* analog button */ + struct bt_adc *adcs; + + /* report interval (ms) */ + int bt_gpio_count; + struct bt_gpio *gpios; + + /* button auto repeat */ + int auto_repeat; + + /* report threshold (mV) */ + int bt_adc_fuzz, bt_adc_flat; + /* adc read value scale */ + int bt_adc_scale; + /* joystick deadzone control */ + int bt_adc_deadzone; + + struct mutex lock; + + /* adc debug channel */ + int debug_ch; +}; + +/*----------------------------------------------------------------------------*/ +// +// set to the value in the boot.ini file. (if exist) +// +/*----------------------------------------------------------------------------*/ +static unsigned int g_button_adc_fuzz = 0; +static unsigned int g_button_adc_flat = 0; +static unsigned int g_button_adc_scale = 0; +static unsigned int g_button_adc_deadzone = 0; + +static int button_adc_fuzz(char *str) +{ + if (!str) + return -EINVAL; + g_button_adc_fuzz = simple_strtoul(str, NULL, 10); + return 0; +} +__setup("button-adc-fuzz=", button_adc_fuzz); + +static int button_adc_flat(char *str) +{ + if (!str) + return -EINVAL; + g_button_adc_flat = simple_strtoul(str, NULL, 10); + return 0; +} +__setup("button-adc-flat=", button_adc_flat); + +static int button_adc_scale(char *str) +{ + if (!str) + return -EINVAL; + g_button_adc_scale = simple_strtoul(str, NULL, 10); + return 0; +} +__setup("button-adc-scale=", button_adc_scale); + +static int button_adc_deadzone(char *str) +{ + if (!str) + return -EINVAL; + g_button_adc_deadzone = simple_strtoul(str, NULL, 10); + return 0; +} +__setup("button-adc-deadzone=", button_adc_deadzone); + +/*----------------------------------------------------------------------------*/ +static int joypad_adc_read(struct joypad *joypad, struct bt_adc *adc) +{ + int value; + + if (iio_read_channel_processed(joypad->adc_ch[adc->channel], &value) < 0) + return 0; + + value *= adc->scale; + + return (adc->invert ? (adc->max - value) : value); +} + +/*----------------------------------------------------------------------------*/ +/*----------------------------------------------------------------------------*/ +/* + * ATTRIBUTES: + * + * /sys/devices/platform/odroidgo_joypad/poll_interval [rw] + */ +/*----------------------------------------------------------------------------*/ +static ssize_t joypad_store_poll_interval(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct joypad *joypad = platform_get_drvdata(pdev); + + mutex_lock(&joypad->lock); + joypad->poll_interval = simple_strtoul(buf, NULL, 10); + mutex_unlock(&joypad->lock); + + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t joypad_show_poll_interval(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct joypad *joypad = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", joypad->poll_interval); +} + +/*----------------------------------------------------------------------------*/ +static DEVICE_ATTR(poll_interval, S_IWUSR | S_IRUGO, + joypad_show_poll_interval, + joypad_store_poll_interval); + +/*----------------------------------------------------------------------------*/ +/* + * ATTRIBUTES: + * + * /sys/devices/platform/odroidgo_joypad/adc_fuzz [r] + */ +/*----------------------------------------------------------------------------*/ +static ssize_t joypad_show_adc_fuzz(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct joypad *joypad = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", joypad->bt_adc_fuzz); +} + +/*----------------------------------------------------------------------------*/ +static DEVICE_ATTR(adc_fuzz, S_IWUSR | S_IRUGO, + joypad_show_adc_fuzz, + NULL); + +/*----------------------------------------------------------------------------*/ +/* + * ATTRIBUTES: + * + * /sys/devices/platform/odroidgo_joypad/adc_flat [r] + */ +/*----------------------------------------------------------------------------*/ +static ssize_t joypad_show_adc_flat(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct joypad *joypad = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", joypad->bt_adc_flat); +} + +/*----------------------------------------------------------------------------*/ +static DEVICE_ATTR(adc_flat, S_IWUSR | S_IRUGO, + joypad_show_adc_flat, + NULL); + +/*----------------------------------------------------------------------------*/ +/* + * ATTRIBUTES: + * + * /sys/devices/platform/oodroidgo_joypad/enable [rw] + */ +/*----------------------------------------------------------------------------*/ +static ssize_t joypad_store_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct joypad *joypad = platform_get_drvdata(pdev); + + mutex_lock(&joypad->lock); + joypad->enable = simple_strtoul(buf, NULL, 10); + mutex_unlock(&joypad->lock); + + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t joypad_show_enable(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct joypad *joypad = platform_get_drvdata(pdev); + + return sprintf(buf, "%d\n", joypad->enable); +} + +/*----------------------------------------------------------------------------*/ +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + joypad_show_enable, + joypad_store_enable); + +/*----------------------------------------------------------------------------*/ +/* + * ATTRIBUTES: + * + * /sys/devices/platform/odroidgo_joypad/adc_cal [rw] + */ +/*----------------------------------------------------------------------------*/ +static ssize_t joypad_store_adc_cal(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct joypad *joypad = platform_get_drvdata(pdev); + bool calibration; + + calibration = simple_strtoul(buf, NULL, 10); + + if (calibration) { + int nbtn; + + mutex_lock(&joypad->lock); + for (nbtn = 0; nbtn < joypad->chan_count; nbtn++) { + struct bt_adc *adc = &joypad->adcs[nbtn]; + + adc->value = joypad_adc_read(joypad, adc); + if (!adc->value) { + dev_err(joypad->dev, "%s : saradc channels[%d]!\n", + __func__, nbtn); + continue; + } + adc->cal = adc->value; + } + mutex_unlock(&joypad->lock); + } + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t joypad_show_adc_cal(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct joypad *joypad = platform_get_drvdata(pdev); + int nbtn; + ssize_t pos; + + for (nbtn = 0, pos = 0; nbtn < joypad->chan_count; nbtn++) { + struct bt_adc *adc = &joypad->adcs[nbtn]; + pos += sprintf(&buf[pos], "adc[%d]->cal = %d\n", + nbtn, adc->cal); + } + pos += sprintf(&buf[pos], "adc scale = %d\n", joypad->bt_adc_scale); + return pos; +} + +/*----------------------------------------------------------------------------*/ +static DEVICE_ATTR(adc_cal, S_IWUSR | S_IRUGO, + joypad_show_adc_cal, + joypad_store_adc_cal); + +/*----------------------------------------------------------------------------*/ +/*----------------------------------------------------------------------------*/ +static struct attribute *joypad_attrs[] = { + &dev_attr_poll_interval.attr, + &dev_attr_adc_fuzz.attr, + &dev_attr_adc_flat.attr, + &dev_attr_enable.attr, + &dev_attr_adc_cal.attr, + NULL, +}; + +static struct attribute_group joypad_attr_group = { + .attrs = joypad_attrs, +}; + +/*----------------------------------------------------------------------------*/ +/*----------------------------------------------------------------------------*/ +static void joypad_gpio_check(struct input_polled_dev *poll_dev) +{ + struct joypad *joypad = poll_dev->private; + int nbtn, value; + + for (nbtn = 0; nbtn < joypad->bt_gpio_count; nbtn++) { + struct bt_gpio *gpio = &joypad->gpios[nbtn]; + + if (gpio_get_value_cansleep(gpio->num) < 0) { + dev_err(joypad->dev, "failed to get gpio state\n"); + continue; + } + value = gpio_get_value(gpio->num); + if (value != gpio->old_value) { + input_event(poll_dev->input, + gpio->report_type, + gpio->linux_code, + (value == gpio->active_level) ? 1 : 0); + gpio->old_value = value; + } + } + input_sync(poll_dev->input); + +} + +/*----------------------------------------------------------------------------*/ +static void joypad_adc_check(struct input_polled_dev *poll_dev) +{ + struct joypad *joypad = poll_dev->private; + int nbtn; + + for (nbtn = 0; nbtn < joypad->chan_count; nbtn++) { + struct bt_adc *adc = &joypad->adcs[nbtn]; + + adc->value = joypad_adc_read(joypad, adc); + if (!adc->value) { + dev_err(joypad->dev, "%s : saradc channels[%d]!\n", + __func__, nbtn); + continue; + } + adc->value = adc->value - adc->cal; + + /* Joystick Deadzone check */ + if (joypad->bt_adc_deadzone) { + if (abs(adc->value) < joypad->bt_adc_deadzone) + adc->value = 0; + } + + /* adc data tuning */ + if (adc->tuning_n && adc->value < 0) + adc->value = ADC_DATA_TUNING(adc->value, adc->tuning_n); + if (adc->tuning_p && adc->value > 0) + adc->value = ADC_DATA_TUNING(adc->value, adc->tuning_p); + + adc->value = adc->value > adc->max ? adc->max : adc->value; + adc->value = adc->value < adc->min ? adc->min : adc->value; + + input_report_abs(poll_dev->input, + adc->report_type, + adc->invert ? adc->value * (-1) : adc->value); + } + input_sync(poll_dev->input); +} + +/*----------------------------------------------------------------------------*/ +static void joypad_poll(struct input_polled_dev *poll_dev) +{ + struct joypad *joypad = poll_dev->private; + + if (joypad->enable) { + joypad_adc_check(poll_dev); + joypad_gpio_check(poll_dev); + } + if (poll_dev->poll_interval != joypad->poll_interval) { + mutex_lock(&joypad->lock); + poll_dev->poll_interval = joypad->poll_interval; + mutex_unlock(&joypad->lock); + } +} + +/*----------------------------------------------------------------------------*/ +static void joypad_open(struct input_polled_dev *poll_dev) +{ + struct joypad *joypad = poll_dev->private; + int nbtn; + + for (nbtn = 0; nbtn < joypad->bt_gpio_count; nbtn++) { + struct bt_gpio *gpio = &joypad->gpios[nbtn]; + gpio->old_value = gpio->active_level ? 0 : 1; + } + for (nbtn = 0; nbtn < joypad->chan_count; nbtn++) { + struct bt_adc *adc = &joypad->adcs[nbtn]; + + adc->value = joypad_adc_read(joypad, adc); + if (!adc->value) { + dev_err(joypad->dev, "%s : saradc channels[%d]!\n", + __func__, nbtn); + continue; + } + adc->cal = adc->value; + dev_info(joypad->dev, "%s : adc[%d] adc->cal = %d\n", + __func__, nbtn, adc->cal); + } + /* buttons status sync */ + joypad_adc_check(poll_dev); + joypad_gpio_check(poll_dev); + + /* button report enable */ + mutex_lock(&joypad->lock); + joypad->enable = true; + mutex_unlock(&joypad->lock); + + dev_info(joypad->dev, "%s : opened\n", __func__); +} + +/*----------------------------------------------------------------------------*/ +static void joypad_close(struct input_polled_dev *poll_dev) +{ + struct joypad *joypad = poll_dev->private; + + /* button report disable */ + mutex_lock(&joypad->lock); + joypad->enable = false; + mutex_unlock(&joypad->lock); + + dev_info(joypad->dev, "%s : closed\n", __func__); +} + +/*----------------------------------------------------------------------------*/ +static int joypad_iochannel_setup(struct device *dev, struct joypad *joypad) +{ + enum iio_chan_type type; + unsigned char cnt; + const char *uname; + int ret; + + for (cnt = 0; cnt < joypad->chan_count; cnt++) { + + ret = of_property_read_string_index(dev->of_node, + "io-channel-names", cnt, &uname); + if (ret < 0) { + dev_err(dev, "invalid channel name index[%d]\n", cnt); + return -EINVAL; + } + + joypad->adc_ch[cnt] = devm_iio_channel_get(dev, + uname); + if (IS_ERR(joypad->adc_ch[cnt])) { + dev_err(dev, "iio channel get error\n"); + return -EINVAL; + } + if (!joypad->adc_ch[cnt]->indio_dev) + return -ENXIO; + + if (iio_get_channel_type(joypad->adc_ch[cnt], &type)) + return -EINVAL; + + if (type != IIO_VOLTAGE) { + dev_err(dev, "Incompatible channel type %d\n", type); + return -EINVAL; + } + } + return ret; +} + +/*----------------------------------------------------------------------------*/ +static int joypad_adc_setup(struct device *dev, struct joypad *joypad) +{ + int nbtn; + + /* adc button struct init */ + joypad->adcs = devm_kzalloc(dev, joypad->chan_count * + sizeof(struct bt_adc), GFP_KERNEL); + if (!joypad->adcs) { + dev_err(dev, "%s devm_kzmalloc error!", __func__); + return -ENOMEM; + } + + for (nbtn = 0; nbtn < joypad->chan_count; nbtn++) { + struct bt_adc *adc = &joypad->adcs[nbtn]; + + adc->scale = joypad->bt_adc_scale; + + adc->max = (ADC_MAX_VOLTAGE / 2); + adc->min = (ADC_MAX_VOLTAGE / 2) * (-1); + if (adc->scale) { + adc->max *= adc->scale; + adc->min *= adc->scale; + } + adc->channel = nbtn; + adc->invert = false; + + switch (nbtn) { + case 0: + adc->report_type = ABS_RY; + if (device_property_read_u32(dev, + "abs_ry-p-tuning", + &adc->tuning_p)) + adc->tuning_p = ADC_TUNING_DEFAULT; + if (device_property_read_u32(dev, + "abs_ry-n-tuning", + &adc->tuning_n)) + adc->tuning_n = ADC_TUNING_DEFAULT; + break; + case 1: + adc->report_type = ABS_RX; + if (device_property_read_u32(dev, + "abs_rx-p-tuning", + &adc->tuning_p)) + adc->tuning_p = ADC_TUNING_DEFAULT; + if (device_property_read_u32(dev, + "abs_rx-n-tuning", + &adc->tuning_n)) + adc->tuning_n = ADC_TUNING_DEFAULT; + break; + case 2: + adc->report_type = ABS_Y; + if (device_property_read_u32(dev, + "abs_y-p-tuning", + &adc->tuning_p)) + adc->tuning_p = ADC_TUNING_DEFAULT; + if (device_property_read_u32(dev, + "abs_y-n-tuning", + &adc->tuning_n)) + adc->tuning_n = ADC_TUNING_DEFAULT; + break; + case 3: + adc->report_type = ABS_X; + if (device_property_read_u32(dev, + "abs_x-p-tuning", + &adc->tuning_p)) + adc->tuning_p = ADC_TUNING_DEFAULT; + if (device_property_read_u32(dev, + "abs_x-n-tuning", + &adc->tuning_n)) + adc->tuning_n = ADC_TUNING_DEFAULT; + break; + default : + dev_err(dev, "%s io channel count(%d) error!", + __func__, nbtn); + return -EINVAL; + } + } + return 0; +} + +/*----------------------------------------------------------------------------*/ +static int joypad_gpio_setup(struct device *dev, struct joypad *joypad) +{ + struct device_node *node, *pp; + int nbtn; + + node = dev->of_node; + if (!node) + return -ENODEV; + + joypad->gpios = devm_kzalloc(dev, joypad->bt_gpio_count * + sizeof(struct bt_gpio), GFP_KERNEL); + + if (!joypad->gpios) { + dev_err(dev, "%s devm_kzmalloc error!", __func__); + return -ENOMEM; + } + + nbtn = 0; + for_each_child_of_node(node, pp) { + enum of_gpio_flags flags; + struct bt_gpio *gpio = &joypad->gpios[nbtn++]; + int error; + + gpio->num = of_get_gpio_flags(pp, 0, &flags); + if (gpio->num < 0) { + error = gpio->num; + dev_err(dev, "Failed to get gpio flags, error: %d\n", + error); + return error; + } + + /* gpio active level(key press level) */ + gpio->active_level = (flags & OF_GPIO_ACTIVE_LOW) ? 0 : 1; + + gpio->label = of_get_property(pp, "label", NULL); + + if (gpio_is_valid(gpio->num)) { + error = devm_gpio_request_one(dev, gpio->num, + GPIOF_IN, gpio->label); + if (error < 0) { + dev_err(dev, + "Failed to request GPIO %d, error %d\n", + gpio->num, error); + return error; + } + #if 0 + error = gpiod_set_pull(gpio_to_desc(gpio->num), GPIOD_PULL_UP); + if (error < 0) { + dev_err(dev, + "Failed to set pull-up GPIO %d, error %d\n", + gpio->num, error); + return error; + } + #endif + } + if (of_property_read_u32(pp, "linux,code", &gpio->linux_code)) { + dev_err(dev, "Button without keycode: 0x%x\n", + gpio->num); + return -EINVAL; + } + if (of_property_read_u32(pp, "linux,input-type", + &gpio->report_type)) + gpio->report_type = EV_KEY; + } + if (nbtn == 0) + return -EINVAL; + + return 0; +} + +/*----------------------------------------------------------------------------*/ +static int joypad_input_setup(struct device *dev, struct joypad *joypad) +{ + struct input_polled_dev *poll_dev; + struct input_dev *input; + int nbtn, error; + u32 joypad_vendor = 0; + u32 joypad_revision = 0; + u32 joypad_product = 0; + + poll_dev = devm_input_allocate_polled_device(dev); + if (!poll_dev) { + dev_err(dev, "no memory for polled device\n"); + return -ENOMEM; + } + + poll_dev->private = joypad; + poll_dev->poll = joypad_poll; + poll_dev->poll_interval = joypad->poll_interval; + poll_dev->open = joypad_open; + poll_dev->close = joypad_close; + + input = poll_dev->input; + + input->name = DRV_NAME; + + device_property_read_string(dev, "joypad-name", &input->name); + input->phys = DRV_NAME"/input0"; + + device_property_read_u32(dev, "joypad-vendor", &joypad_vendor); + device_property_read_u32(dev, "joypad-revision", &joypad_revision); + device_property_read_u32(dev, "joypad-product", &joypad_product); + //input->id.bustype = BUS_HOST; + input->id.bustype = BUS_USB; + input->id.vendor = (u16)joypad_vendor; + input->id.product = (u16)joypad_product; + input->id.version = (u16)joypad_revision; + + /* IIO ADC key setup (0 mv ~ 1800 mv) * adc->scale */ + __set_bit(EV_ABS, input->evbit); + + // Set mapped ones on dt + for(nbtn = 0; nbtn < joypad->chan_count; nbtn++) { + struct bt_adc *adc = &joypad->adcs[nbtn]; + input_set_abs_params(input, adc->report_type, + adc->min, adc->max, + joypad->bt_adc_fuzz, + joypad->bt_adc_flat); + dev_info(dev, + "%s : SCALE = %d, ABS min = %d, max = %d," + " fuzz = %d, flat = %d, deadzone = %d\n", + __func__, adc->scale, adc->min, adc->max, + joypad->bt_adc_fuzz, joypad->bt_adc_flat, + joypad->bt_adc_deadzone); + dev_info(dev, + "%s : adc tuning_p = %d, adc_tuning_n = %d\n\n", + __func__, adc->tuning_p, adc->tuning_n); + } + + /* GPIO key setup */ + __set_bit(EV_KEY, input->evbit); + for(nbtn = 0; nbtn < joypad->bt_gpio_count; nbtn++) { + struct bt_gpio *gpio = &joypad->gpios[nbtn]; + input_set_capability(input, gpio->report_type, + gpio->linux_code); + } + + if (joypad->auto_repeat) + __set_bit(EV_REP, input->evbit); + + joypad->dev = dev; + + error = input_register_polled_device(poll_dev); + if (error) { + dev_err(dev, "unable to register polled device, err=%d\n", + error); + return error; + } + joypad->dev = dev; + joypad->poll_dev = poll_dev; + + return 0; +} + +/*----------------------------------------------------------------------------*/ +static void joypad_setup_value_check(struct device *dev, struct joypad *joypad) +{ + /* + fuzz: specifies fuzz value that is used to filter noise from + the event stream. + */ + if (g_button_adc_fuzz) + joypad->bt_adc_fuzz = g_button_adc_fuzz; + else + device_property_read_u32(dev, "button-adc-fuzz", + &joypad->bt_adc_fuzz); + /* + flat: values that are within this value will be discarded by + joydev interface and reported as 0 instead. + */ + if (g_button_adc_flat) + joypad->bt_adc_flat = g_button_adc_flat; + else + device_property_read_u32(dev, "button-adc-flat", + &joypad->bt_adc_flat); + + /* Joystick report value control */ + if (g_button_adc_scale) + joypad->bt_adc_scale = g_button_adc_scale; + else + device_property_read_u32(dev, "button-adc-scale", + &joypad->bt_adc_scale); + + /* Joystick deadzone value control */ + if (g_button_adc_deadzone) + joypad->bt_adc_deadzone = g_button_adc_deadzone; + else + device_property_read_u32(dev, "button-adc-deadzone", + &joypad->bt_adc_deadzone); + +} + +/*----------------------------------------------------------------------------*/ +static int joypad_dt_parse(struct device *dev, struct joypad *joypad) +{ + int error = 0; + + /* initialize value check from boot.ini */ + joypad_setup_value_check(dev, joypad); + + joypad->chan_count = of_property_count_strings(dev->of_node, + "io-channel-names"); + + device_property_read_u32(dev, "poll-interval", + &joypad->poll_interval); + + joypad->auto_repeat = device_property_present(dev, "autorepeat"); + + joypad->bt_gpio_count = device_get_child_node_count(dev); + + if ((joypad->chan_count == 0) || (joypad->bt_gpio_count == 0)) { + dev_err(dev, "adc key = %d, gpio key = %d error!", + joypad->chan_count, joypad->bt_gpio_count); + return -EINVAL; + } + + error = joypad_adc_setup(dev, joypad); + if (error) + return error; + + error = joypad_iochannel_setup(dev, joypad); + if (error) + return error; + + error = joypad_gpio_setup(dev, joypad); + if (error) + return error; + + dev_info(dev, "%s : adc key cnt = %d, gpio key cnt = %d\n", + __func__, joypad->chan_count, joypad->bt_gpio_count); + + return error; +} + +/*----------------------------------------------------------------------------*/ +static int joypad_probe(struct platform_device *pdev) +{ + struct joypad *joypad; + struct device *dev = &pdev->dev; + int error; + + joypad = devm_kzalloc(dev, sizeof(struct joypad), GFP_KERNEL); + if (!joypad) { + dev_err(dev, "joypad devm_kzmalloc error!"); + return -ENOMEM; + } + + /* device tree data parse */ + error = joypad_dt_parse(dev, joypad); + if (error) { + dev_err(dev, "dt parse error!(err = %d)\n", error); + return error; + } + + mutex_init(&joypad->lock); + platform_set_drvdata(pdev, joypad); + + error = sysfs_create_group(&pdev->dev.kobj, &joypad_attr_group); + if (error) { + dev_err(dev, "create sysfs group fail, error: %d\n", + error); + return error; + } + + /* poll input device setup */ + error = joypad_input_setup(dev, joypad); + if (error) { + dev_err(dev, "input setup failed!(err = %d)\n", error); + return error; + } + dev_info(dev, "%s : probe success\n", __func__); + return 0; +} + +static void joypad_shutdown(struct platform_device *pdev) +{ + struct joypad *joypad = platform_get_drvdata(pdev); + input_unregister_polled_device(joypad->poll_dev); +} +/*----------------------------------------------------------------------------*/ +static const struct of_device_id joypad_of_match[] = { + { .compatible = "odroidgou-joypad", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, joypad_of_match); + +/*----------------------------------------------------------------------------*/ +static struct platform_driver joypad_driver = { + .probe = joypad_probe, + .shutdown = joypad_shutdown, + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(joypad_of_match), + }, +}; + +/*----------------------------------------------------------------------------*/ +static int __init joypad_init(void) +{ + return platform_driver_register(&joypad_driver); +} + +/*----------------------------------------------------------------------------*/ +static void __exit joypad_exit(void) +{ + platform_driver_unregister(&joypad_driver); +} + +/*----------------------------------------------------------------------------*/ +late_initcall(joypad_init); +module_exit(joypad_exit); + +/*----------------------------------------------------------------------------*/ +MODULE_AUTHOR("Hardkernel Co.,LTD"); +MODULE_DESCRIPTION("Keypad driver(ADC&GPIO) for ODROIDGO-Advance"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); + +/*----------------------------------------------------------------------------*/ diff -rupN linux.orig/drivers/mfd/rk808.c linux/drivers/mfd/rk808.c --- linux.orig/drivers/mfd/rk808.c 2023-09-12 12:02:58.341633584 +0000 +++ linux/drivers/mfd/rk808.c 2023-09-12 12:03:27.490291628 +0000 @@ -81,12 +81,47 @@ static bool rk817_is_volatile_reg(struct return false; } +static bool rk818_is_volatile_reg(struct device *dev, unsigned int reg) +{ + /* + * Notes: + * - Technically the ROUND_30s bit makes RTC_CTRL_REG volatile, but + * we don't use that feature. It's better to cache. + * - It's unlikely we care that RK808_DEVCTRL_REG is volatile since + * bits are cleared in case when we shutoff anyway, but better safe. + */ + + switch (reg) { + case RK808_SECONDS_REG ... RK808_WEEKS_REG: + case RK808_RTC_STATUS_REG: + case RK808_VB_MON_REG: + case RK808_THERMAL_REG: + case RK808_DCDC_EN_REG: + case RK808_LDO_EN_REG: + case RK808_DCDC_UV_STS_REG: + case RK808_LDO_UV_STS_REG: + case RK808_DCDC_PG_REG: + case RK808_LDO_PG_REG: + case RK808_DEVCTRL_REG: + case RK808_INT_STS_REG1: + case RK808_INT_STS_REG2: + case RK808_INT_STS_MSK_REG1: + case RK808_INT_STS_MSK_REG2: + case RK818_LDO8_ON_VSEL_REG: // TODO(ayufan):?? + case RK818_LDO8_SLP_VSEL_REG: // TODO(ayufan):?? + case RK818_SUP_STS_REG ... RK818_SAVE_DATA19: + return true; + } + + return false; +} + static const struct regmap_config rk818_regmap_config = { .reg_bits = 8, .val_bits = 8, - .max_register = RK818_USB_CTRL_REG, + .max_register = RK818_SAVE_DATA19, .cache_type = REGCACHE_RBTREE, - .volatile_reg = rk808_is_volatile_reg, + .volatile_reg = rk818_is_volatile_reg, }; static const struct regmap_config rk805_regmap_config = { @@ -137,58 +172,67 @@ static const struct resource rk817_charg }; static const struct mfd_cell rk805s[] = { - { .name = "rk808-clkout", }, - { .name = "rk808-regulator", }, - { .name = "rk805-pinctrl", }, + { .name = "rk808-clkout", .id = -1, }, + { .name = "rk808-regulator", .id = -1, }, + { .name = "rk805-pinctrl", .id = -1, }, { .name = "rk808-rtc", .num_resources = ARRAY_SIZE(rtc_resources), .resources = &rtc_resources[0], + .id = -1, }, { .name = "rk805-pwrkey", .num_resources = ARRAY_SIZE(rk805_key_resources), .resources = &rk805_key_resources[0], + .id = -1, }, }; static const struct mfd_cell rk808s[] = { - { .name = "rk808-clkout", }, - { .name = "rk808-regulator", }, + { .name = "rk808-clkout", .id = -1, }, + { .name = "rk808-regulator", .id = -1, }, { .name = "rk808-rtc", .num_resources = ARRAY_SIZE(rtc_resources), .resources = rtc_resources, + .id = -1, }, }; static const struct mfd_cell rk817s[] = { - { .name = "rk808-clkout",}, - { .name = "rk808-regulator",}, + { .name = "rk808-clkout", .id = -1, }, + { .name = "rk808-regulator", .id = -1, }, { .name = "rk805-pwrkey", .num_resources = ARRAY_SIZE(rk817_pwrkey_resources), .resources = &rk817_pwrkey_resources[0], + .id = -1, }, { .name = "rk808-rtc", .num_resources = ARRAY_SIZE(rk817_rtc_resources), .resources = &rk817_rtc_resources[0], + .id = -1, }, - { .name = "rk817-codec",}, + { .name = "rk817-codec", .id = -1, }, { .name = "rk817-charger", .num_resources = ARRAY_SIZE(rk817_charger_resources), .resources = &rk817_charger_resources[0], + .id = -1, }, }; static const struct mfd_cell rk818s[] = { - { .name = "rk808-clkout", }, - { .name = "rk808-regulator", }, + { .name = "rk808-clkout", .id = -1, }, + { .name = "rk808-regulator", .id = -1, }, + { .name = "rk818-battery", .of_compatible = "rockchip,rk818-battery", }, + { .name = "rk818-charger", .of_compatible = "rockchip,rk818-charger", }, { .name = "rk808-rtc", .num_resources = ARRAY_SIZE(rtc_resources), .resources = rtc_resources, + .id = -1, }, }; @@ -318,6 +362,7 @@ static const struct rk808_reg_data rk818 { RK818_H5V_EN_REG, BIT(0), RK818_H5V_EN }, { RK808_VB_MON_REG, MASK_ALL, VB_LO_ACT | VB_LO_SEL_3500MV }, + { RK808_CLK32OUT_REG, CLK32KOUT2_FUNC_MASK, CLK32KOUT2_FUNC }, }; static const struct regmap_irq rk805_irqs[] = { diff -rupN linux.orig/drivers/power/reset/Kconfig linux/drivers/power/reset/Kconfig --- linux.orig/drivers/power/reset/Kconfig 2023-09-12 12:02:58.741642619 +0000 +++ linux/drivers/power/reset/Kconfig 2023-09-12 12:03:27.490291628 +0000 @@ -141,6 +141,13 @@ config POWER_RESET_OCELOT_RESET help This driver supports restart for Microsemi Ocelot SoC and similar. +config POWER_RESET_ODROID_GO_ULTRA_POWEROFF + bool "Odroid Go Ultra power-off driver" + depends on ARCH_MESON || COMPILE_TEST + depends on I2C=y && OF + help + This driver supports Power off for Odroid Go Ultra device. + config POWER_RESET_OXNAS bool "OXNAS SoC restart driver" depends on ARCH_OXNAS diff -rupN linux.orig/drivers/power/reset/Makefile linux/drivers/power/reset/Makefile --- linux.orig/drivers/power/reset/Makefile 2023-09-12 12:02:58.741642619 +0000 +++ linux/drivers/power/reset/Makefile 2023-09-12 12:03:27.490291628 +0000 @@ -17,6 +17,7 @@ obj-$(CONFIG_POWER_RESET_MT6323) += mt63 obj-$(CONFIG_POWER_RESET_OXNAS) += oxnas-restart.o obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o obj-$(CONFIG_POWER_RESET_OCELOT_RESET) += ocelot-reset.o +obj-$(CONFIG_POWER_RESET_ODROID_GO_ULTRA_POWEROFF) += odroid-go-ultra-poweroff.o obj-$(CONFIG_POWER_RESET_PIIX4_POWEROFF) += piix4-poweroff.o obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o diff -rupN linux.orig/drivers/power/reset/odroid-go-ultra-poweroff.c linux/drivers/power/reset/odroid-go-ultra-poweroff.c --- linux.orig/drivers/power/reset/odroid-go-ultra-poweroff.c 1970-01-01 00:00:00.000000000 +0000 +++ linux/drivers/power/reset/odroid-go-ultra-poweroff.c 2023-09-12 12:03:27.490291628 +0000 @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2023 Neil Armstrong + */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The Odroid Go Ultra has 2 PMICs: + * - RK818 (manages the battery and USB-C power supply) + * - RK817 + * Both PMICs feeds power to the S922X SoC, so they must be powered-off in sequence. + * Vendor does power-off the RK817 first, then the RK818 so here we follow this sequence. + */ + +struct odroid_go_ultra_poweroff_data { + struct device *dev; + struct device *rk817; + struct device *rk818; +}; + +static int odroid_go_ultra_poweroff_prepare(struct sys_off_data *data) +{ + struct odroid_go_ultra_poweroff_data *poweroff_data = data->cb_data; + struct regmap *rk817, *rk818; + int ret; + + /* RK817 Regmap */ + rk817 = dev_get_regmap(poweroff_data->rk817, NULL); + if (!rk817) { + dev_err(poweroff_data->dev, "failed to get rk817 regmap\n"); + return notifier_from_errno(-EINVAL); + } + + /* RK818 Regmap */ + rk818 = dev_get_regmap(poweroff_data->rk818, NULL); + if (!rk818) { + dev_err(poweroff_data->dev, "failed to get rk818 regmap\n"); + return notifier_from_errno(-EINVAL); + } + + dev_info(poweroff_data->dev, "Setting PMICs for power off"); + + /* RK817 */ + ret = regmap_update_bits(rk817, RK817_SYS_CFG(3), DEV_OFF, DEV_OFF); + if (ret) { + dev_err(poweroff_data->dev, "failed to poweroff rk817\n"); + return notifier_from_errno(ret); + } + + /* RK818 */ + ret = regmap_update_bits(rk818, RK818_DEVCTRL_REG, DEV_OFF, DEV_OFF); + if (ret) { + dev_err(poweroff_data->dev, "failed to poweroff rk818\n"); + return notifier_from_errno(ret); + } + + return NOTIFY_OK; +} + +static void odroid_go_ultra_poweroff_put_pmic_device(void *data) +{ + struct device *dev = data; + + put_device(dev); +} + +static int odroid_go_ultra_poweroff_get_pmic_device(struct device *dev, const char *compatible, + struct device **pmic) +{ + struct device_node *pmic_node; + struct i2c_client *pmic_client; + + pmic_node = of_find_compatible_node(NULL, NULL, compatible); + if (!pmic_node) + return -ENODEV; + + pmic_client = of_find_i2c_device_by_node(pmic_node); + of_node_put(pmic_node); + if (!pmic_client) + return -EPROBE_DEFER; + + *pmic = &pmic_client->dev; + + return devm_add_action_or_reset(dev, odroid_go_ultra_poweroff_put_pmic_device, *pmic); +} + +static int odroid_go_ultra_poweroff_probe(struct platform_device *pdev) +{ + struct odroid_go_ultra_poweroff_data *poweroff_data; + int ret; + + poweroff_data = devm_kzalloc(&pdev->dev, sizeof(*poweroff_data), GFP_KERNEL); + if (!poweroff_data) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, poweroff_data); + + /* RK818 PMIC Device */ + ret = odroid_go_ultra_poweroff_get_pmic_device(&pdev->dev, "rockchip,rk818", + &poweroff_data->rk818); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to get rk818 mfd data\n"); + + /* RK817 PMIC Device */ + ret = odroid_go_ultra_poweroff_get_pmic_device(&pdev->dev, "rockchip,rk817", + &poweroff_data->rk817); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to get rk817 mfd data\n"); + + /* Register as SYS_OFF_MODE_POWER_OFF_PREPARE because regmap_update_bits may sleep */ + ret = devm_register_sys_off_handler(&pdev->dev, + SYS_OFF_MODE_POWER_OFF_PREPARE, + SYS_OFF_PRIO_DEFAULT, + odroid_go_ultra_poweroff_prepare, + poweroff_data); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to register sys-off handler\n"); + + dev_info(&pdev->dev, "Registered Power-Off handler\n"); + + return 0; +} +static struct platform_device *pdev; + +static struct platform_driver odroid_go_ultra_poweroff_driver = { + .driver = { + .name = "odroid-go-ultra-poweroff", + }, + .probe = odroid_go_ultra_poweroff_probe, +}; + +static int __init odroid_go_ultra_poweroff_init(void) +{ + int ret; + + /* Only create when running on the Odroid Go Ultra device */ + if (!of_device_is_compatible(of_root, "hardkernel,odroid-go-ultra")) + return -ENODEV; + + ret = platform_driver_register(&odroid_go_ultra_poweroff_driver); + if (ret) + return ret; + + pdev = platform_device_register_resndata(NULL, "odroid-go-ultra-poweroff", -1, + NULL, 0, NULL, 0); + + if (IS_ERR(pdev)) { + platform_driver_unregister(&odroid_go_ultra_poweroff_driver); + return PTR_ERR(pdev); + } + + return 0; +} + +static void __exit odroid_go_ultra_poweroff_exit(void) +{ + /* Only delete when running on the Odroid Go Ultra device */ + if (!of_device_is_compatible(of_root, "hardkernel,odroid-go-ultra")) + return; + + platform_device_unregister(pdev); + platform_driver_unregister(&odroid_go_ultra_poweroff_driver); +} + +module_init(odroid_go_ultra_poweroff_init); +module_exit(odroid_go_ultra_poweroff_exit); + +MODULE_AUTHOR("Neil Armstrong "); +MODULE_DESCRIPTION("Odroid Go Ultra poweroff driver"); +MODULE_LICENSE("GPL"); diff -rupN linux.orig/drivers/power/supply/Kconfig linux/drivers/power/supply/Kconfig --- linux.orig/drivers/power/supply/Kconfig 2023-09-12 12:02:58.745642709 +0000 +++ linux/drivers/power/supply/Kconfig 2023-09-12 12:03:27.490291628 +0000 @@ -918,4 +918,12 @@ config BATTERY_UG3105 device is off or suspended, the functionality of this driver is limited to reporting capacity only. +config CHARGER_RK818 + bool "RK818 Charger driver" + depends on MFD_RK808 + default n + help + If you say yes here you will get support for the charger of RK818 PMIC. + This driver can give support for Rk818 Charger Interface. + endif # POWER_SUPPLY diff -rupN linux.orig/drivers/power/supply/Makefile linux/drivers/power/supply/Makefile --- linux.orig/drivers/power/supply/Makefile 2023-09-12 12:02:58.745642709 +0000 +++ linux/drivers/power/supply/Makefile 2023-09-12 12:03:27.490291628 +0000 @@ -110,3 +110,4 @@ obj-$(CONFIG_BATTERY_ACER_A500) += acer_ obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o +obj-$(CONFIG_CHARGER_RK818) += rk818_charger.o rk818_battery.o diff -rupN linux.orig/drivers/power/supply/rk818_battery.c linux/drivers/power/supply/rk818_battery.c --- linux.orig/drivers/power/supply/rk818_battery.c 1970-01-01 00:00:00.000000000 +0000 +++ linux/drivers/power/supply/rk818_battery.c 2023-09-12 12:03:27.490291628 +0000 @@ -0,0 +1,3498 @@ +/* + * rk818 battery driver + * + * Copyright (C) 2016 Rockchip Electronics Co., Ltd + * chenjh + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include +//#include +#include +#include +#include +//#include +#include +#include "rk818_battery.h" + +static int dbg_enable = 0; +module_param_named(dbg_level, dbg_enable, int, 0644); + +#define DBG(args...) \ + do { \ + if (dbg_enable) { \ + pr_info(args); \ + } \ + } while (0) + +#define BAT_INFO(fmt, args...) pr_info("rk818-bat: "fmt, ##args) + +/* default param */ +#define DEFAULT_BAT_RES 135 +#define DEFAULT_SLP_ENTER_CUR 300 +#define DEFAULT_SLP_EXIT_CUR 300 +#define DEFAULT_SLP_FILTER_CUR 100 +#define DEFAULT_PWROFF_VOL_THRESD 3400 +#define DEFAULT_MONITOR_SEC 5 +#define DEFAULT_ALGR_VOL_THRESD1 3850 +#define DEFAULT_ALGR_VOL_THRESD2 3950 +#define DEFAULT_MAX_SOC_OFFSET 60 +#define DEFAULT_FB_TEMP TEMP_105C +#define DEFAULT_ZERO_RESERVE_DSOC 10 +#define DEFAULT_POFFSET 42 +#define DEFAULT_COFFSET 0x832 +#define DEFAULT_SAMPLE_RES 20 +#define DEFAULT_ENERGY_MODE 0 +#define INVALID_COFFSET_MIN 0x780 +#define INVALID_COFFSET_MAX 0x980 +#define INVALID_VOL_THRESD 2500 + +/* sample resistor and division */ +#define SAMPLE_RES_10MR 10 +#define SAMPLE_RES_20MR 20 +#define SAMPLE_RES_DIV1 1 +#define SAMPLE_RES_DIV2 2 + +/* virtual params */ +#define VIRTUAL_CURRENT 1000 +#define VIRTUAL_VOLTAGE 3888 +#define VIRTUAL_SOC 66 +#define VIRTUAL_PRESET 1 +#define VIRTUAL_TEMPERATURE 188 +#define VIRTUAL_STATUS POWER_SUPPLY_STATUS_CHARGING + +/* charge */ +#define FINISH_CHRG_CUR1 1000 +#define FINISH_CHRG_CUR2 1500 +#define FINISH_MAX_SOC_DELAY 20 +#define TERM_CHRG_DSOC 88 +#define TERM_CHRG_CURR 600 +#define TERM_CHRG_K 650 +#define SIMULATE_CHRG_INTV 8 +#define SIMULATE_CHRG_CURR 400 +#define SIMULATE_CHRG_K 1500 +#define FULL_CHRG_K 400 + +/* zero algorithm */ +#define PWROFF_THRESD 3400 +#define MIN_ZERO_DSOC_ACCURACY 10 /*0.01%*/ +#define MIN_ZERO_OVERCNT 100 +#define MIN_ACCURACY 1 +#define DEF_PWRPATH_RES 50 +#define WAIT_DSOC_DROP_SEC 15 +#define WAIT_SHTD_DROP_SEC 30 +#define ZERO_GAP_XSOC1 10 +#define ZERO_GAP_XSOC2 5 +#define ZERO_GAP_XSOC3 3 +#define ZERO_LOAD_LVL1 1400 +#define ZERO_LOAD_LVL2 600 +#define ZERO_GAP_CALIB 5 + +#define ADC_CALIB_THRESHOLD 4 +#define ADC_CALIB_LMT_MIN 3 +#define ADC_CALIB_CNT 5 +#define NTC_CALC_FACTOR 7 + +/* time */ +#define POWER_ON_SEC_BASE 1 +#define MINUTE(x) ((x) * 60) + +/* sleep */ +#define SLP_CURR_MAX 40 +#define SLP_CURR_MIN 6 +#define DISCHRG_TIME_STEP1 MINUTE(10) +#define DISCHRG_TIME_STEP2 MINUTE(60) +#define SLP_DSOC_VOL_THRESD 3600 +#define REBOOT_PERIOD_SEC 180 +#define REBOOT_MAX_CNT 80 + +/* fcc */ +#define MIN_FCC 500 + +/* TS detect battery temperature */ +#define ADC_CUR_MSK 0x03 +#define ADC_CUR_20UA 0x00 +#define ADC_CUR_40UA 0x01 +#define ADC_CUR_60UA 0x02 +#define ADC_CUR_80UA 0x03 + +#define NTC_CALC_FACTOR_80UA 7 +#define NTC_CALC_FACTOR_60UA 9 +#define NTC_CALC_FACTOR_40UA 13 +#define NTC_CALC_FACTOR_20UA 27 +#define NTC_80UA_MAX_MEASURE 27500 +#define NTC_60UA_MAX_MEASURE 36666 +#define NTC_40UA_MAX_MEASURE 55000 +#define NTC_20UA_MAX_MEASURE 110000 + +static const char *bat_status[] = { + "charge off", "dead charge", "trickle charge", "cc cv", + "finish", "usb over vol", "bat temp error", "timer error", +}; + +struct rk818_battery { + struct platform_device *pdev; + struct rk808 *rk818; + struct regmap *regmap; + struct device *dev; + struct power_supply *bat; + struct power_supply *usb_psy; + struct power_supply *ac_psy; + struct battery_platform_data *pdata; + struct workqueue_struct *bat_monitor_wq; + struct delayed_work bat_delay_work; + struct delayed_work calib_delay_work; + // struct wake_lock wake_lock; + struct notifier_block fb_nb; + struct timer_list caltimer; + time64_t rtc_base; + int bat_res; + int chrg_status; + bool is_initialized; + bool is_first_power_on; + u8 res_div; + int current_max; + int voltage_max; + int current_avg; + int voltage_avg; + int voltage_ocv; + int voltage_relax; + int voltage_k; + int voltage_b; + int remain_cap; + int design_cap; + int nac; + int fcc; + int qmax; + int dsoc; + int rsoc; + int poffset; + int age_ocv_soc; + bool age_allow_update; + int age_level; + int age_ocv_cap; + int age_voltage; + int age_adjust_cap; + unsigned long age_keep_sec; + int zero_timeout_cnt; + int zero_remain_cap; + int zero_dsoc; + int zero_linek; + u64 zero_drop_sec; + u64 shtd_drop_sec; + int sm_remain_cap; + int sm_linek; + int sm_chrg_dsoc; + int sm_dischrg_dsoc; + int algo_rest_val; + int algo_rest_mode; + int sleep_sum_cap; + int sleep_remain_cap; + unsigned long sleep_dischrg_sec; + unsigned long sleep_sum_sec; + bool sleep_chrg_online; + u8 sleep_chrg_status; + bool adc_allow_update; + bool s2r; /*suspend to resume*/ + u32 work_mode; + int temperature; + u32 monitor_ms; + u32 pwroff_min; + u32 adc_calib_cnt; + unsigned long finish_base; + unsigned long boot_base; + unsigned long flat_match_sec; + unsigned long plug_in_base; + unsigned long plug_out_base; + u8 halt_cnt; + bool is_halt; + bool is_max_soc_offset; + bool is_sw_reset; + bool is_ocv_calib; + bool is_first_on; + bool is_force_calib; + int last_dsoc; + int ocv_pre_dsoc; + int ocv_new_dsoc; + int max_pre_dsoc; + int max_new_dsoc; + int force_pre_dsoc; + int force_new_dsoc; + int dbg_cap_low0; + int dbg_pwr_dsoc; + int dbg_pwr_rsoc; + int dbg_pwr_vol; + int dbg_chrg_min[10]; + int dbg_meet_soc; + int dbg_calc_dsoc; + int dbg_calc_rsoc; + u8 ac_in; + u8 usb_in; + int is_charging; + unsigned long charge_count; +}; + +#define DIV(x) ((x) ? (x) : 1) + +static void rk_send_wakeup_key(void) +{ + // TODO: WHAT TO DO HERE? +} + +static u64 get_boot_sec(void) +{ + struct timespec64 ts; + + ktime_get_boottime_ts64(&ts); + + return ts.tv_sec; +} + +static unsigned long base2sec(unsigned long x) +{ + if (x) + return (get_boot_sec() > x) ? (get_boot_sec() - x) : 0; + else + return 0; +} + +static unsigned long base2min(unsigned long x) +{ + return base2sec(x) / 60; +} + +static u32 interpolate(int value, u32 *table, int size) +{ + u8 i; + u16 d; + + for (i = 0; i < size; i++) { + if (value < table[i]) + break; + } + + if ((i > 0) && (i < size)) { + d = (value - table[i - 1]) * (MAX_INTERPOLATE / (size - 1)); + d /= table[i] - table[i - 1]; + d = d + (i - 1) * (MAX_INTERPOLATE / (size - 1)); + } else { + d = i * ((MAX_INTERPOLATE + size / 2) / size); + } + + if (d > 1000) + d = 1000; + + return d; +} + +/* (a*b)/c */ +static int32_t ab_div_c(u32 a, u32 b, u32 c) +{ + bool sign; + u32 ans = MAX_INT; + int tmp; + + sign = ((((a ^ b) ^ c) & 0x80000000) != 0); + if (c != 0) { + if (sign) + c = -c; + tmp = (a * b + (c >> 1)) / c; + if (tmp < MAX_INT) + ans = tmp; + } + + if (sign) + ans = -ans; + + return ans; +} + +static int rk818_bat_read(struct rk818_battery *di, u8 reg) +{ + int ret, val; + + ret = regmap_read(di->regmap, reg, &val); + if (ret) + dev_err(di->dev, "read reg:0x%x failed\n", reg); + + return val; +} + +static int rk818_bat_write(struct rk818_battery *di, u8 reg, u8 buf) +{ + int ret; + + ret = regmap_write(di->regmap, reg, buf); + if (ret) + dev_err(di->dev, "i2c write reg: 0x%2x error\n", reg); + + return ret; +} + +static int rk818_bat_set_bits(struct rk818_battery *di, u8 reg, u8 mask, u8 buf) +{ + int ret; + + ret = regmap_update_bits(di->regmap, reg, mask, buf); + if (ret) + dev_err(di->dev, "write reg:0x%x failed\n", reg); + + return ret; +} + +static int rk818_bat_clear_bits(struct rk818_battery *di, u8 reg, u8 mask) +{ + int ret; + + ret = regmap_update_bits(di->regmap, reg, mask, 0); + if (ret) + dev_err(di->dev, "clr reg:0x%02x failed\n", reg); + + return ret; +} + +static void rk818_bat_dump_regs(struct rk818_battery *di, u8 start, u8 end) +{ + int i; + + if (!dbg_enable) + return; + + DBG("dump regs from: 0x%x-->0x%x\n", start, end); + for (i = start; i < end; i++) + DBG("0x%x: 0x%0x\n", i, rk818_bat_read(di, i)); +} + +static bool rk818_bat_chrg_online(struct rk818_battery *di) +{ + u8 buf; + + buf = rk818_bat_read(di, RK818_VB_MON_REG); + + return (buf & PLUG_IN_STS) ? true : false; +} + +static int rk818_bat_get_coulomb_cap(struct rk818_battery *di) +{ + int val = 0; + + val |= rk818_bat_read(di, RK818_GASCNT3_REG) << 24; + val |= rk818_bat_read(di, RK818_GASCNT2_REG) << 16; + val |= rk818_bat_read(di, RK818_GASCNT1_REG) << 8; + val |= rk818_bat_read(di, RK818_GASCNT0_REG) << 0; + + return (val / 2390) * di->res_div; +} + +static int rk818_bat_get_rsoc(struct rk818_battery *di) +{ + int remain_cap; + + remain_cap = rk818_bat_get_coulomb_cap(di); + return (remain_cap + di->fcc / 200) * 100 / DIV(di->fcc); +} + +static ssize_t bat_info_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + char cmd; + struct rk818_battery *di = dev_get_drvdata(dev); + + sscanf(buf, "%c", &cmd); + + if (cmd == 'n') + rk818_bat_set_bits(di, RK818_MISC_MARK_REG, + FG_RESET_NOW, FG_RESET_NOW); + else if (cmd == 'm') + rk818_bat_set_bits(di, RK818_MISC_MARK_REG, + FG_RESET_LATE, FG_RESET_LATE); + else if (cmd == 'c') + rk818_bat_clear_bits(di, RK818_MISC_MARK_REG, + FG_RESET_LATE | FG_RESET_NOW); + else if (cmd == 'r') + BAT_INFO("0x%2x\n", rk818_bat_read(di, RK818_MISC_MARK_REG)); + else + BAT_INFO("command error\n"); + + return count; +} + +static struct device_attribute rk818_bat_attr[] = { + __ATTR(bat, 0664, NULL, bat_info_store), +}; + +static void rk818_bat_enable_gauge(struct rk818_battery *di) +{ + u8 buf; + + buf = rk818_bat_read(di, RK818_TS_CTRL_REG); + buf |= GG_EN; + rk818_bat_write(di, RK818_TS_CTRL_REG, buf); +} + +static void rk818_bat_save_age_level(struct rk818_battery *di, u8 level) +{ + rk818_bat_write(di, RK818_UPDAT_LEVE_REG, level); +} + +static u8 rk818_bat_get_age_level(struct rk818_battery *di) +{ + return rk818_bat_read(di, RK818_UPDAT_LEVE_REG); +} + +static int rk818_bat_get_vcalib0(struct rk818_battery *di) +{ + int val = 0; + + val |= rk818_bat_read(di, RK818_VCALIB0_REGL) << 0; + val |= rk818_bat_read(di, RK818_VCALIB0_REGH) << 8; + + DBG("<%s>. voffset0: 0x%x\n", __func__, val); + return val; +} + +static int rk818_bat_get_vcalib1(struct rk818_battery *di) +{ + int val = 0; + + val |= rk818_bat_read(di, RK818_VCALIB1_REGL) << 0; + val |= rk818_bat_read(di, RK818_VCALIB1_REGH) << 8; + + DBG("<%s>. voffset1: 0x%x\n", __func__, val); + return val; +} + +static int rk818_bat_get_ioffset(struct rk818_battery *di) +{ + int val = 0; + + val |= rk818_bat_read(di, RK818_IOFFSET_REGL) << 0; + val |= rk818_bat_read(di, RK818_IOFFSET_REGH) << 8; + + DBG("<%s>. ioffset: 0x%x\n", __func__, val); + return val; +} + +static int rk818_bat_get_coffset(struct rk818_battery *di) +{ + int val = 0; + + val |= rk818_bat_read(di, RK818_CAL_OFFSET_REGL) << 0; + val |= rk818_bat_read(di, RK818_CAL_OFFSET_REGH) << 8; + + DBG("<%s>. coffset: 0x%x\n", __func__, val); + return val; +} + +static void rk818_bat_set_coffset(struct rk818_battery *di, int val) +{ + u8 buf; + + if ((val < INVALID_COFFSET_MIN) || (val > INVALID_COFFSET_MAX)) { + BAT_INFO("set invalid coffset=0x%x\n", val); + return; + } + + buf = (val >> 8) & 0xff; + rk818_bat_write(di, RK818_CAL_OFFSET_REGH, buf); + buf = (val >> 0) & 0xff; + rk818_bat_write(di, RK818_CAL_OFFSET_REGL, buf); + DBG("<%s>. coffset: 0x%x\n", __func__, val); +} + +static void rk818_bat_init_voltage_kb(struct rk818_battery *di) +{ + int vcalib0, vcalib1; + + vcalib0 = rk818_bat_get_vcalib0(di); + vcalib1 = rk818_bat_get_vcalib1(di); + di->voltage_k = (4200 - 3000) * 1000 / DIV(vcalib1 - vcalib0); + di->voltage_b = 4200 - (di->voltage_k * vcalib1) / 1000; + + DBG("voltage_k=%d(*1000),voltage_b=%d\n", di->voltage_k, di->voltage_b); +} + +static int rk818_bat_get_ocv_voltage(struct rk818_battery *di) +{ + int vol, val = 0; + + val |= rk818_bat_read(di, RK818_BAT_OCV_REGL) << 0; + val |= rk818_bat_read(di, RK818_BAT_OCV_REGH) << 8; + + vol = di->voltage_k * val / 1000 + di->voltage_b; + + return vol; +} + +static int rk818_bat_get_avg_voltage(struct rk818_battery *di) +{ + int vol, val = 0; + + val |= rk818_bat_read(di, RK818_BAT_VOL_REGL) << 0; + val |= rk818_bat_read(di, RK818_BAT_VOL_REGH) << 8; + + vol = di->voltage_k * val / 1000 + di->voltage_b; + + return vol; +} + +static bool is_rk818_bat_relax_mode(struct rk818_battery *di) +{ + u8 status; + + status = rk818_bat_read(di, RK818_GGSTS_REG); + if (!(status & RELAX_VOL1_UPD) || !(status & RELAX_VOL2_UPD)) + return false; + else + return true; +} + +static u16 rk818_bat_get_relax_vol1(struct rk818_battery *di) +{ + u16 vol, val = 0; + + val |= rk818_bat_read(di, RK818_RELAX_VOL1_REGL) << 0; + val |= rk818_bat_read(di, RK818_RELAX_VOL1_REGH) << 8; + vol = di->voltage_k * val / 1000 + di->voltage_b; + + return vol; +} + +static u16 rk818_bat_get_relax_vol2(struct rk818_battery *di) +{ + u16 vol, val = 0; + + val |= rk818_bat_read(di, RK818_RELAX_VOL2_REGL) << 0; + val |= rk818_bat_read(di, RK818_RELAX_VOL2_REGH) << 8; + vol = di->voltage_k * val / 1000 + di->voltage_b; + + return vol; +} + +static u16 rk818_bat_get_relax_voltage(struct rk818_battery *di) +{ + u16 relax_vol1, relax_vol2; + + if (!is_rk818_bat_relax_mode(di)) + return 0; + + relax_vol1 = rk818_bat_get_relax_vol1(di); + relax_vol2 = rk818_bat_get_relax_vol2(di); + + return relax_vol1 > relax_vol2 ? relax_vol1 : relax_vol2; +} + +static int rk818_bat_get_avg_current(struct rk818_battery *di) +{ + int cur, val = 0; + + val |= rk818_bat_read(di, RK818_BAT_CUR_AVG_REGL) << 0; + val |= rk818_bat_read(di, RK818_BAT_CUR_AVG_REGH) << 8; + + if (val & 0x800) + val -= 4096; + cur = val * di->res_div * 1506 / 1000; + + return cur; +} + +static int rk818_bat_vol_to_ocvsoc(struct rk818_battery *di, int voltage) +{ + u32 *ocv_table, temp; + int ocv_size, ocv_soc; + + ocv_table = di->pdata->ocv_table; + ocv_size = di->pdata->ocv_size; + temp = interpolate(voltage, ocv_table, ocv_size); + ocv_soc = ab_div_c(temp, MAX_PERCENTAGE, MAX_INTERPOLATE); + + return ocv_soc; +} + +static int rk818_bat_vol_to_ocvcap(struct rk818_battery *di, int voltage) +{ + u32 *ocv_table, temp; + int ocv_size, cap; + + ocv_table = di->pdata->ocv_table; + ocv_size = di->pdata->ocv_size; + temp = interpolate(voltage, ocv_table, ocv_size); + cap = ab_div_c(temp, di->fcc, MAX_INTERPOLATE); + + return cap; +} + +static int rk818_bat_vol_to_zerosoc(struct rk818_battery *di, int voltage) +{ + u32 *ocv_table, temp; + int ocv_size, ocv_soc; + + ocv_table = di->pdata->zero_table; + ocv_size = di->pdata->ocv_size; + temp = interpolate(voltage, ocv_table, ocv_size); + ocv_soc = ab_div_c(temp, MAX_PERCENTAGE, MAX_INTERPOLATE); + + return ocv_soc; +} + +static int rk818_bat_vol_to_zerocap(struct rk818_battery *di, int voltage) +{ + u32 *ocv_table, temp; + int ocv_size, cap; + + ocv_table = di->pdata->zero_table; + ocv_size = di->pdata->ocv_size; + temp = interpolate(voltage, ocv_table, ocv_size); + cap = ab_div_c(temp, di->fcc, MAX_INTERPOLATE); + + return cap; +} + +static int rk818_bat_get_iadc(struct rk818_battery *di) +{ + int val = 0; + + val |= rk818_bat_read(di, RK818_BAT_CUR_AVG_REGL) << 0; + val |= rk818_bat_read(di, RK818_BAT_CUR_AVG_REGH) << 8; + if (val > 2047) + val -= 4096; + + return val; +} + +static bool rk818_bat_adc_calib(struct rk818_battery *di) +{ + int i, ioffset, coffset, adc, save_coffset; + + if ((di->chrg_status != CHARGE_FINISH) || + (di->adc_calib_cnt > ADC_CALIB_CNT) || + (base2min(di->boot_base) < ADC_CALIB_LMT_MIN) || + (abs(di->current_avg) < ADC_CALIB_THRESHOLD)) + return false; + + di->adc_calib_cnt++; + save_coffset = rk818_bat_get_coffset(di); + for (i = 0; i < 5; i++) { + adc = rk818_bat_get_iadc(di); + if (!rk818_bat_chrg_online(di)) { + rk818_bat_set_coffset(di, save_coffset); + BAT_INFO("quit, charger plugout when calib adc\n"); + return false; + } + coffset = rk818_bat_get_coffset(di); + rk818_bat_set_coffset(di, coffset + adc); + msleep(2000); + adc = rk818_bat_get_iadc(di); + if (abs(adc) < ADC_CALIB_THRESHOLD) { + coffset = rk818_bat_get_coffset(di); + ioffset = rk818_bat_get_ioffset(di); + di->poffset = coffset - ioffset; + rk818_bat_write(di, RK818_POFFSET_REG, di->poffset); + BAT_INFO("new offset:c=0x%x, i=0x%x, p=0x%x\n", + coffset, ioffset, di->poffset); + return true; + } else { + BAT_INFO("coffset calib again %d.., max_cnt=%d\n", + i, di->adc_calib_cnt); + rk818_bat_set_coffset(di, coffset); + msleep(2000); + } + } + + rk818_bat_set_coffset(di, save_coffset); + + return false; +} + +static void rk818_bat_set_ioffset_sample(struct rk818_battery *di) +{ + u8 ggcon; + + ggcon = rk818_bat_read(di, RK818_GGCON_REG); + ggcon &= ~ADC_CAL_MIN_MSK; + ggcon |= ADC_CAL_8MIN; + rk818_bat_write(di, RK818_GGCON_REG, ggcon); +} + +static void rk818_bat_set_ocv_sample(struct rk818_battery *di) +{ + u8 ggcon; + + ggcon = rk818_bat_read(di, RK818_GGCON_REG); + ggcon &= ~OCV_SAMP_MIN_MSK; + ggcon |= OCV_SAMP_8MIN; + rk818_bat_write(di, RK818_GGCON_REG, ggcon); +} + +static void rk818_bat_restart_relax(struct rk818_battery *di) +{ + u8 ggsts; + + ggsts = rk818_bat_read(di, RK818_GGSTS_REG); + ggsts &= ~RELAX_VOL12_UPD_MSK; + rk818_bat_write(di, RK818_GGSTS_REG, ggsts); +} + +static void rk818_bat_set_relax_sample(struct rk818_battery *di) +{ + u8 buf; + int enter_thres, exit_thres; + struct battery_platform_data *pdata = di->pdata; + + enter_thres = pdata->sleep_enter_current * 1000 / 1506 / DIV(di->res_div); + exit_thres = pdata->sleep_exit_current * 1000 / 1506 / DIV(di->res_div); + + /* set relax enter and exit threshold */ + buf = enter_thres & 0xff; + rk818_bat_write(di, RK818_RELAX_ENTRY_THRES_REGL, buf); + buf = (enter_thres >> 8) & 0xff; + rk818_bat_write(di, RK818_RELAX_ENTRY_THRES_REGH, buf); + + buf = exit_thres & 0xff; + rk818_bat_write(di, RK818_RELAX_EXIT_THRES_REGL, buf); + buf = (exit_thres >> 8) & 0xff; + rk818_bat_write(di, RK818_RELAX_EXIT_THRES_REGH, buf); + + /* reset relax update state */ + rk818_bat_restart_relax(di); + DBG("<%s>. sleep_enter_current = %d, sleep_exit_current = %d\n", + __func__, pdata->sleep_enter_current, pdata->sleep_exit_current); +} + +static bool is_rk818_bat_exist(struct rk818_battery *di) +{ + return (rk818_bat_read(di, RK818_SUP_STS_REG) & BAT_EXS) ? true : false; +} + +static bool is_rk818_bat_first_pwron(struct rk818_battery *di) +{ + u8 buf; + + buf = rk818_bat_read(di, RK818_GGSTS_REG); + if (buf & BAT_CON) { + buf &= ~BAT_CON; + rk818_bat_write(di, RK818_GGSTS_REG, buf); + return true; + } + + return false; +} + +static u8 rk818_bat_get_pwroff_min(struct rk818_battery *di) +{ + u8 cur, last; + + cur = rk818_bat_read(di, RK818_NON_ACT_TIMER_CNT_REG); + last = rk818_bat_read(di, RK818_NON_ACT_TIMER_CNT_SAVE_REG); + rk818_bat_write(di, RK818_NON_ACT_TIMER_CNT_SAVE_REG, cur); + + return (cur != last) ? cur : 0; +} + +static u8 is_rk818_bat_initialized(struct rk818_battery *di) +{ + u8 val = rk818_bat_read(di, RK818_MISC_MARK_REG); + + if (val & FG_INIT) { + val &= ~FG_INIT; + rk818_bat_write(di, RK818_MISC_MARK_REG, val); + return true; + } else { + return false; + } +} + +static bool is_rk818_bat_ocv_valid(struct rk818_battery *di) +{ + return (!di->is_initialized && di->pwroff_min >= 30) ? true : false; +} + +static void rk818_bat_init_age_algorithm(struct rk818_battery *di) +{ + int age_level, ocv_soc, ocv_cap, ocv_vol; + + if (di->is_first_power_on || is_rk818_bat_ocv_valid(di)) { + DBG("<%s> enter.\n", __func__); + ocv_vol = rk818_bat_get_ocv_voltage(di); + ocv_soc = rk818_bat_vol_to_ocvsoc(di, ocv_vol); + ocv_cap = rk818_bat_vol_to_ocvcap(di, ocv_vol); + if (ocv_soc < 20) { + di->age_voltage = ocv_vol; + di->age_ocv_cap = ocv_cap; + di->age_ocv_soc = ocv_soc; + di->age_adjust_cap = 0; + + if (ocv_soc <= 0) + di->age_level = 100; + else if (ocv_soc < 5) + di->age_level = 95; + else if (ocv_soc < 10) + di->age_level = 90; + else + di->age_level = 80; + + age_level = rk818_bat_get_age_level(di); + if (age_level > di->age_level) { + di->age_allow_update = false; + age_level -= 5; + if (age_level <= 80) + age_level = 80; + rk818_bat_save_age_level(di, age_level); + } else { + di->age_allow_update = true; + di->age_keep_sec = get_boot_sec(); + } + + BAT_INFO("init_age_algorithm: " + "age_vol:%d, age_ocv_cap:%d, " + "age_ocv_soc:%d, old_age_level:%d, " + "age_allow_update:%d, new_age_level:%d\n", + di->age_voltage, di->age_ocv_cap, + ocv_soc, age_level, di->age_allow_update, + di->age_level); + } + } +} + +static enum power_supply_property rk818_bat_props[] = { + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static int rk818_bat_get_charge_state(struct rk818_battery *di) +{ + return di->current_avg > 0; +} + +int rk818_battery_get_property(struct rk818_battery *di, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = rk818_bat_get_avg_current(di) * 1000;/*uA*/ + if (di->pdata->bat_mode == MODE_VIRTUAL) + val->intval = VIRTUAL_CURRENT * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = rk818_bat_get_avg_voltage(di) * 1000;/*uV*/ + if (di->pdata->bat_mode == MODE_VIRTUAL) + val->intval = VIRTUAL_VOLTAGE * 1000; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = is_rk818_bat_exist(di); + if (di->pdata->bat_mode == MODE_VIRTUAL) + val->intval = VIRTUAL_PRESET; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = di->dsoc; + if (di->pdata->bat_mode == MODE_VIRTUAL) + val->intval = VIRTUAL_SOC; + DBG("<%s>. report dsoc: %d\n", __func__, val->intval); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = di->temperature; + if (di->pdata->bat_mode == MODE_VIRTUAL) + val->intval = VIRTUAL_TEMPERATURE; + break; + case POWER_SUPPLY_PROP_STATUS: + if (di->pdata->bat_mode == MODE_VIRTUAL) + val->intval = VIRTUAL_STATUS; + else if (di->dsoc == 100) + val->intval = POWER_SUPPLY_STATUS_FULL; + else if (rk818_bat_get_charge_state(di)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + val->intval = di->charge_count; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = di->pdata->design_capacity * 1000;/* uAh */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = di->voltage_max * 1000; /* uV */ + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = di->current_max * 1000; /* uA */ + break; + default: + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rk818_battery_get_property); + +static int rk818_battery_get_property_psy(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rk818_battery *di = power_supply_get_drvdata(psy); + + return rk818_battery_get_property(di, psp, val); +} + +static const struct power_supply_desc rk818_bat_desc = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = rk818_bat_props, + .num_properties = ARRAY_SIZE(rk818_bat_props), + .get_property = rk818_battery_get_property_psy, +}; + +static __maybe_unused int rk818_bat_init_power_supply(struct rk818_battery *di) +{ + struct power_supply_config psy_cfg = { .drv_data = di, }; + + di->bat = devm_power_supply_register(di->dev, &rk818_bat_desc, &psy_cfg); + if (IS_ERR(di->bat)) { + dev_err(di->dev, "register bat power supply fail\n"); + return PTR_ERR(di->bat); + } + + return 0; +} + +static void rk818_bat_save_cap(struct rk818_battery *di, int cap) +{ + u8 buf; + static u32 old_cap; + + if (cap >= di->qmax) + cap = di->qmax; + if (cap <= 0) + cap = 0; + if (old_cap == cap) + return; + + old_cap = cap; + buf = (cap >> 24) & 0xff; + rk818_bat_write(di, RK818_REMAIN_CAP_REG3, buf); + buf = (cap >> 16) & 0xff; + rk818_bat_write(di, RK818_REMAIN_CAP_REG2, buf); + buf = (cap >> 8) & 0xff; + rk818_bat_write(di, RK818_REMAIN_CAP_REG1, buf); + buf = (cap >> 0) & 0xff; + rk818_bat_write(di, RK818_REMAIN_CAP_REG0, buf); +} + +static int rk818_bat_get_prev_cap(struct rk818_battery *di) +{ + int val = 0; + + val |= rk818_bat_read(di, RK818_REMAIN_CAP_REG3) << 24; + val |= rk818_bat_read(di, RK818_REMAIN_CAP_REG2) << 16; + val |= rk818_bat_read(di, RK818_REMAIN_CAP_REG1) << 8; + val |= rk818_bat_read(di, RK818_REMAIN_CAP_REG0) << 0; + + return val; +} + +static void rk818_bat_save_fcc(struct rk818_battery *di, u32 fcc) +{ + u8 buf; + + buf = (fcc >> 24) & 0xff; + rk818_bat_write(di, RK818_NEW_FCC_REG3, buf); + buf = (fcc >> 16) & 0xff; + rk818_bat_write(di, RK818_NEW_FCC_REG2, buf); + buf = (fcc >> 8) & 0xff; + rk818_bat_write(di, RK818_NEW_FCC_REG1, buf); + buf = (fcc >> 0) & 0xff; + rk818_bat_write(di, RK818_NEW_FCC_REG0, buf); + + BAT_INFO("save fcc: %d\n", fcc); +} + +static int rk818_bat_get_fcc(struct rk818_battery *di) +{ + u32 fcc = 0; + + fcc |= rk818_bat_read(di, RK818_NEW_FCC_REG3) << 24; + fcc |= rk818_bat_read(di, RK818_NEW_FCC_REG2) << 16; + fcc |= rk818_bat_read(di, RK818_NEW_FCC_REG1) << 8; + fcc |= rk818_bat_read(di, RK818_NEW_FCC_REG0) << 0; + + if (fcc < MIN_FCC) { + BAT_INFO("invalid fcc(%d), use design cap", fcc); + fcc = di->pdata->design_capacity; + rk818_bat_save_fcc(di, fcc); + } else if (fcc > di->pdata->design_qmax) { + BAT_INFO("invalid fcc(%d), use qmax", fcc); + fcc = di->pdata->design_qmax; + rk818_bat_save_fcc(di, fcc); + } + + return fcc; +} + +static void rk818_bat_init_coulomb_cap(struct rk818_battery *di, u32 capacity) +{ + u8 buf; + u32 cap; + + cap = capacity * 2390 / DIV(di->res_div); + buf = (cap >> 24) & 0xff; + rk818_bat_write(di, RK818_GASCNT_CAL_REG3, buf); + buf = (cap >> 16) & 0xff; + rk818_bat_write(di, RK818_GASCNT_CAL_REG2, buf); + buf = (cap >> 8) & 0xff; + rk818_bat_write(di, RK818_GASCNT_CAL_REG1, buf); + buf = ((cap >> 0) & 0xff); + rk818_bat_write(di, RK818_GASCNT_CAL_REG0, buf); + + DBG("<%s>. new coulomb cap = %d\n", __func__, capacity); + di->remain_cap = capacity; + di->rsoc = rk818_bat_get_rsoc(di); +} + +static void rk818_bat_save_dsoc(struct rk818_battery *di, u8 save_soc) +{ + static int last_soc = -1; + + if (last_soc != save_soc) { + rk818_bat_write(di, RK818_SOC_REG, save_soc); + last_soc = save_soc; + } +} + +static int rk818_bat_get_prev_dsoc(struct rk818_battery *di) +{ + return rk818_bat_read(di, RK818_SOC_REG); +} + +static void rk818_bat_save_reboot_cnt(struct rk818_battery *di, u8 save_cnt) +{ + rk818_bat_write(di, RK818_REBOOT_CNT_REG, save_cnt); +} + +static u8 rk818_bat_get_halt_cnt(struct rk818_battery *di) +{ + return rk818_bat_read(di, RK818_HALT_CNT_REG); +} + +static void rk818_bat_inc_halt_cnt(struct rk818_battery *di) +{ + u8 cnt; + + cnt = rk818_bat_read(di, RK818_HALT_CNT_REG); + rk818_bat_write(di, RK818_HALT_CNT_REG, ++cnt); +} + +static bool is_rk818_bat_last_halt(struct rk818_battery *di) +{ + int pre_cap = rk818_bat_get_prev_cap(di); + int now_cap = rk818_bat_get_coulomb_cap(di); + + /* over 10%: system halt last time */ + if (abs(now_cap - pre_cap) > (di->fcc / 10)) { + rk818_bat_inc_halt_cnt(di); + return true; + } else { + return false; + } +} + +static void rk818_bat_first_pwron(struct rk818_battery *di) +{ + int ocv_vol; + + rk818_bat_save_fcc(di, di->design_cap); + ocv_vol = rk818_bat_get_ocv_voltage(di); + di->fcc = rk818_bat_get_fcc(di); + di->nac = rk818_bat_vol_to_ocvcap(di, ocv_vol); + di->rsoc = rk818_bat_vol_to_ocvsoc(di, ocv_vol); + di->dsoc = di->rsoc; + di->is_first_on = true; + + BAT_INFO("first on: dsoc=%d, rsoc=%d cap=%d, fcc=%d, ov=%d\n", + di->dsoc, di->rsoc, di->nac, di->fcc, ocv_vol); +} + +static void rk818_bat_not_first_pwron(struct rk818_battery *di) +{ + int now_cap, pre_soc, pre_cap, ocv_cap, ocv_soc, ocv_vol; + + di->fcc = rk818_bat_get_fcc(di); + pre_soc = rk818_bat_get_prev_dsoc(di); + pre_cap = rk818_bat_get_prev_cap(di); + now_cap = rk818_bat_get_coulomb_cap(di); + di->is_halt = is_rk818_bat_last_halt(di); + di->halt_cnt = rk818_bat_get_halt_cnt(di); + di->is_initialized = is_rk818_bat_initialized(di); + di->is_ocv_calib = is_rk818_bat_ocv_valid(di); + + if (di->is_initialized) { + BAT_INFO("initialized yet..\n"); + goto finish; + } else if (di->is_halt) { + BAT_INFO("system halt last time... cap: pre=%d, now=%d\n", + pre_cap, now_cap); + if (now_cap < 0) + now_cap = 0; + rk818_bat_init_coulomb_cap(di, now_cap); + pre_cap = now_cap; + pre_soc = di->rsoc; + goto finish; + } else if (di->is_ocv_calib) { + ocv_vol = rk818_bat_get_ocv_voltage(di); + ocv_soc = rk818_bat_vol_to_ocvsoc(di, ocv_vol); + ocv_cap = rk818_bat_vol_to_ocvcap(di, ocv_vol); + pre_cap = ocv_cap; + di->ocv_pre_dsoc = pre_soc; + di->ocv_new_dsoc = ocv_soc; + if (abs(ocv_soc - pre_soc) >= di->pdata->max_soc_offset) { + di->ocv_pre_dsoc = pre_soc; + di->ocv_new_dsoc = ocv_soc; + di->is_max_soc_offset = true; + BAT_INFO("trigger max soc offset, dsoc: %d -> %d\n", + pre_soc, ocv_soc); + pre_soc = ocv_soc; + } + BAT_INFO("OCV calib: cap=%d, rsoc=%d\n", ocv_cap, ocv_soc); + } else if (di->pwroff_min > 0) { + ocv_vol = rk818_bat_get_ocv_voltage(di); + ocv_soc = rk818_bat_vol_to_ocvsoc(di, ocv_vol); + ocv_cap = rk818_bat_vol_to_ocvcap(di, ocv_vol); + di->force_pre_dsoc = pre_soc; + di->force_new_dsoc = ocv_soc; + if (abs(ocv_soc - pre_soc) >= 80) { + di->is_force_calib = true; + BAT_INFO("dsoc force calib: %d -> %d\n", + pre_soc, ocv_soc); + pre_soc = ocv_soc; + pre_cap = ocv_cap; + } + } + +finish: + di->dsoc = pre_soc; + di->nac = pre_cap; + if (di->nac < 0) + di->nac = 0; + + BAT_INFO("dsoc=%d cap=%d v=%d ov=%d rv=%d min=%d psoc=%d pcap=%d\n", + di->dsoc, di->nac, rk818_bat_get_avg_voltage(di), + rk818_bat_get_ocv_voltage(di), rk818_bat_get_relax_voltage(di), + di->pwroff_min, rk818_bat_get_prev_dsoc(di), + rk818_bat_get_prev_cap(di)); +} + +static bool rk818_bat_ocv_sw_reset(struct rk818_battery *di) +{ + u8 buf; + + buf = rk818_bat_read(di, RK818_MISC_MARK_REG); + if (((buf & FG_RESET_LATE) && di->pwroff_min >= 30) || + (buf & FG_RESET_NOW)) { + buf &= ~FG_RESET_LATE; + buf &= ~FG_RESET_NOW; + rk818_bat_write(di, RK818_MISC_MARK_REG, buf); + BAT_INFO("manual reset fuel gauge\n"); + return true; + } else { + return false; + } +} + +static void rk818_bat_init_rsoc(struct rk818_battery *di) +{ + di->is_first_power_on = is_rk818_bat_first_pwron(di); + di->is_sw_reset = rk818_bat_ocv_sw_reset(di); + di->pwroff_min = rk818_bat_get_pwroff_min(di); + + if (di->is_first_power_on || di->is_sw_reset) + rk818_bat_first_pwron(di); + else + rk818_bat_not_first_pwron(di); +} + +static u8 rk818_bat_get_chrg_status(struct rk818_battery *di) +{ + u8 status; + + status = rk818_bat_read(di, RK818_SUP_STS_REG) & CHRG_STATUS_MSK; + switch (status) { + case CHARGE_OFF: + DBG("CHARGE-OFF ...\n"); + break; + case DEAD_CHARGE: + BAT_INFO("DEAD CHARGE...\n"); + break; + case TRICKLE_CHARGE: + BAT_INFO("TRICKLE CHARGE...\n "); + break; + case CC_OR_CV: + DBG("CC or CV...\n"); + break; + case CHARGE_FINISH: + DBG("CHARGE FINISH...\n"); + break; + case USB_OVER_VOL: + BAT_INFO("USB OVER VOL...\n"); + break; + case BAT_TMP_ERR: + BAT_INFO("BAT TMP ERROR...\n"); + break; + case TIMER_ERR: + BAT_INFO("TIMER ERROR...\n"); + break; + case USB_EXIST: + BAT_INFO("USB EXIST...\n"); + break; + case USB_EFF: + BAT_INFO("USB EFF...\n"); + break; + default: + return -EINVAL; + } + + return status; +} + +static u8 rk818_bat_parse_fb_temperature(struct rk818_battery *di) +{ + u8 reg; + int index, fb_temp; + + reg = DEFAULT_FB_TEMP; + fb_temp = di->pdata->fb_temp; + for (index = 0; index < ARRAY_SIZE(feedback_temp_array); index++) { + if (fb_temp < feedback_temp_array[index]) + break; + reg = (index << FB_TEMP_SHIFT); + } + + return reg; +} + +static u8 rk818_bat_parse_finish_ma(struct rk818_battery *di, int fcc) +{ + u8 ma; + + if (di->pdata->sample_res == SAMPLE_RES_10MR) + ma = FINISH_100MA; + else if (fcc > 5000) + ma = FINISH_250MA; + else if (fcc >= 4000) + ma = FINISH_200MA; + else if (fcc >= 3000) + ma = FINISH_150MA; + else + ma = FINISH_100MA; + + return ma; +} + +static void rk818_bat_init_chrg_config(struct rk818_battery *di) +{ + u8 usb_ctrl, chrg_ctrl2, chrg_ctrl3; + u8 thermal, ggcon, finish_ma, fb_temp; + + finish_ma = rk818_bat_parse_finish_ma(di, di->fcc); + fb_temp = rk818_bat_parse_fb_temperature(di); + + ggcon = rk818_bat_read(di, RK818_GGCON_REG); + thermal = rk818_bat_read(di, RK818_THERMAL_REG); + usb_ctrl = rk818_bat_read(di, RK818_USB_CTRL_REG); + chrg_ctrl2 = rk818_bat_read(di, RK818_CHRG_CTRL_REG2); + chrg_ctrl3 = rk818_bat_read(di, RK818_CHRG_CTRL_REG3); + + /* set charge finish current */ + chrg_ctrl3 |= CHRG_TERM_DIG_SIGNAL; + chrg_ctrl2 &= ~FINISH_CUR_MSK; + chrg_ctrl2 |= finish_ma; + + /* disable cccv mode */ + chrg_ctrl3 &= ~CHRG_TIMER_CCCV_EN; + + /* set feed back temperature */ + if (di->pdata->fb_temp) + usb_ctrl |= CHRG_CT_EN; + else + usb_ctrl &= ~CHRG_CT_EN; + thermal &= ~FB_TEMP_MSK; + thermal |= fb_temp; + + /* adc current mode */ + ggcon |= ADC_CUR_MODE; + + rk818_bat_write(di, RK818_GGCON_REG, ggcon); + rk818_bat_write(di, RK818_THERMAL_REG, thermal); + rk818_bat_write(di, RK818_USB_CTRL_REG, usb_ctrl); + rk818_bat_write(di, RK818_CHRG_CTRL_REG2, chrg_ctrl2); + rk818_bat_write(di, RK818_CHRG_CTRL_REG3, chrg_ctrl3); +} + +static void rk818_bat_init_coffset(struct rk818_battery *di) +{ + int coffset, ioffset; + + ioffset = rk818_bat_get_ioffset(di); + di->poffset = rk818_bat_read(di, RK818_POFFSET_REG); + if (!di->poffset) + di->poffset = DEFAULT_POFFSET; + + coffset = di->poffset + ioffset; + if (coffset < INVALID_COFFSET_MIN || coffset > INVALID_COFFSET_MAX) + coffset = DEFAULT_COFFSET; + + rk818_bat_set_coffset(di, coffset); + + DBG("<%s>. offset: p=0x%x, i=0x%x, c=0x%x\n", + __func__, di->poffset, ioffset, rk818_bat_get_coffset(di)); +} + +static void rk818_bat_caltimer_isr(struct timer_list *t) +{ + struct rk818_battery *di = from_timer(di, t, caltimer); + + mod_timer(&di->caltimer, jiffies + MINUTE(8) * HZ); + queue_delayed_work(di->bat_monitor_wq, &di->calib_delay_work, + msecs_to_jiffies(10)); +} + +static void rk818_bat_internal_calib(struct work_struct *work) +{ + int ioffset, poffset; + struct rk818_battery *di = container_of(work, + struct rk818_battery, calib_delay_work.work); + + /* calib coffset */ + poffset = rk818_bat_read(di, RK818_POFFSET_REG); + if (poffset) + di->poffset = poffset; + else + di->poffset = DEFAULT_POFFSET; + + ioffset = rk818_bat_get_ioffset(di); + rk818_bat_set_coffset(di, ioffset + di->poffset); + + /* calib voltage kb */ + rk818_bat_init_voltage_kb(di); + BAT_INFO("caltimer: ioffset=0x%x, coffset=0x%x, poffset=%d\n", + ioffset, rk818_bat_get_coffset(di), di->poffset); +} + +static void rk818_bat_init_caltimer(struct rk818_battery *di) +{ + timer_setup(&di->caltimer, rk818_bat_caltimer_isr, 0); + di->caltimer.expires = jiffies + MINUTE(8) * HZ; + add_timer(&di->caltimer); + INIT_DELAYED_WORK(&di->calib_delay_work, rk818_bat_internal_calib); +} + +static void rk818_bat_init_zero_table(struct rk818_battery *di) +{ + int i, diff, min, max; + size_t ocv_size, length; + + ocv_size = di->pdata->ocv_size; + length = sizeof(di->pdata->zero_table) * ocv_size; + di->pdata->zero_table = + devm_kzalloc(di->dev, length, GFP_KERNEL); + if (!di->pdata->zero_table) { + di->pdata->zero_table = di->pdata->ocv_table; + dev_err(di->dev, "malloc zero table fail\n"); + return; + } + + min = di->pdata->pwroff_vol, + max = di->pdata->ocv_table[ocv_size - 4]; + diff = (max - min) / DIV(ocv_size - 1); + for (i = 0; i < ocv_size; i++) + di->pdata->zero_table[i] = min + (i * diff); + + for (i = 0; i < ocv_size; i++) + DBG("zero[%d] = %d\n", i, di->pdata->zero_table[i]); + + for (i = 0; i < ocv_size; i++) + DBG("ocv[%d] = %d\n", i, di->pdata->ocv_table[i]); +} + +static void rk818_bat_calc_sm_linek(struct rk818_battery *di) +{ + int linek, current_avg; + u8 diff, delta; + + delta = abs(di->dsoc - di->rsoc); + diff = delta * 3;/* speed:3/4 */ + current_avg = rk818_bat_get_avg_current(di); + if (current_avg >= 0) { + if (di->dsoc < di->rsoc) + linek = 1000 * (delta + diff) / DIV(diff); + else if (di->dsoc > di->rsoc) + linek = 1000 * diff / DIV(delta + diff); + else + linek = 1000; + di->dbg_meet_soc = (di->dsoc >= di->rsoc) ? + (di->dsoc + diff) : (di->rsoc + diff); + } else { + if (di->dsoc < di->rsoc) + linek = -1000 * diff / DIV(delta + diff); + else if (di->dsoc > di->rsoc) + linek = -1000 * (delta + diff) / DIV(diff); + else + linek = -1000; + di->dbg_meet_soc = (di->dsoc >= di->rsoc) ? + (di->dsoc - diff) : (di->rsoc - diff); + } + + di->sm_linek = linek; + di->sm_remain_cap = di->remain_cap; + di->dbg_calc_dsoc = di->dsoc; + di->dbg_calc_rsoc = di->rsoc; + + DBG("<%s>.diff=%d, k=%d, cur=%d\n", __func__, diff, linek, current_avg); +} + +static void rk818_bat_calc_zero_linek(struct rk818_battery *di) +{ + int dead_voltage, ocv_voltage; + int voltage_avg, current_avg, vsys; + int ocv_cap, dead_cap, xsoc; + int ocv_soc, dead_soc; + int pwroff_vol; + int i, cnt = 0, vol_old, vol_now; + int org_linek = 0, min_gap_xsoc; + + if ((abs(di->current_avg) < 500) && (di->dsoc > 10)) + pwroff_vol = di->pdata->pwroff_vol + 50; + else + pwroff_vol = di->pdata->pwroff_vol; + + do { + vol_old = rk818_bat_get_avg_voltage(di); + msleep(100); + vol_now = rk818_bat_get_avg_voltage(di); + cnt++; + } while ((vol_old == vol_now) && (cnt < 11)); + + voltage_avg = 0; + for (i = 0; i < 10; i++) { + voltage_avg += rk818_bat_get_avg_voltage(di); + msleep(100); + } + + /* calc estimate ocv voltage */ + voltage_avg /= 10; + current_avg = rk818_bat_get_avg_current(di); + vsys = voltage_avg + (current_avg * DEF_PWRPATH_RES) / 1000; + + DBG("ZERO0: shtd_vol: org = %d, now = %d, zero_reserve_dsoc = %d\n", + di->pdata->pwroff_vol, pwroff_vol, di->pdata->zero_reserve_dsoc); + + dead_voltage = pwroff_vol - current_avg * + (di->bat_res + DEF_PWRPATH_RES) / 1000; + ocv_voltage = voltage_avg - (current_avg * di->bat_res) / 1000; + DBG("ZERO0: dead_voltage(shtd) = %d, ocv_voltage(now) = %d\n", + dead_voltage, ocv_voltage); + + /* calc estimate soc and cap */ + dead_soc = rk818_bat_vol_to_zerosoc(di, dead_voltage); + dead_cap = rk818_bat_vol_to_zerocap(di, dead_voltage); + DBG("ZERO0: dead_soc = %d, dead_cap = %d\n", + dead_soc, dead_cap); + + ocv_soc = rk818_bat_vol_to_zerosoc(di, ocv_voltage); + ocv_cap = rk818_bat_vol_to_zerocap(di, ocv_voltage); + DBG("ZERO0: ocv_soc = %d, ocv_cap = %d\n", + ocv_soc, ocv_cap); + + /* xsoc: available rsoc */ + xsoc = ocv_soc - dead_soc; + + /* min_gap_xsoc: reserve xsoc */ + if (abs(current_avg) > ZERO_LOAD_LVL1) + min_gap_xsoc = ZERO_GAP_XSOC3; + else if (abs(current_avg) > ZERO_LOAD_LVL2) + min_gap_xsoc = ZERO_GAP_XSOC2; + else + min_gap_xsoc = ZERO_GAP_XSOC1; + + if ((xsoc <= 30) && (di->dsoc >= di->pdata->zero_reserve_dsoc)) + min_gap_xsoc = min_gap_xsoc + ZERO_GAP_CALIB; + + di->zero_remain_cap = di->remain_cap; + di->zero_timeout_cnt = 0; + if ((di->dsoc <= 1) && (xsoc > 0)) { + di->zero_linek = 400; + di->zero_drop_sec = 0; + } else if (xsoc >= 0) { + di->zero_drop_sec = 0; + di->zero_linek = (di->zero_dsoc + xsoc / 2) / DIV(xsoc); + org_linek = di->zero_linek; + /* battery energy mode to use up voltage */ + if ((di->pdata->energy_mode) && + (xsoc - di->dsoc >= ZERO_GAP_XSOC3) && + (di->dsoc <= 10) && (di->zero_linek < 300)) { + di->zero_linek = 300; + DBG("ZERO-new: zero_linek adjust step0...\n"); + /* reserve enough power yet, slow down any way */ + } else if ((xsoc - di->dsoc >= min_gap_xsoc) || + ((xsoc - di->dsoc >= ZERO_GAP_XSOC2) && + (di->dsoc <= 10) && (xsoc > 15))) { + if (xsoc <= 20 && + di->dsoc >= di->pdata->zero_reserve_dsoc) + di->zero_linek = 1200; + else if (xsoc - di->dsoc >= 2 * min_gap_xsoc) + di->zero_linek = 400; + else if (xsoc - di->dsoc >= 3 + min_gap_xsoc) + di->zero_linek = 600; + else + di->zero_linek = 800; + DBG("ZERO-new: zero_linek adjust step1...\n"); + /* control zero mode beginning enter */ + } else if ((di->zero_linek > 1800) && (di->dsoc > 70)) { + di->zero_linek = 1800; + DBG("ZERO-new: zero_linek adjust step2...\n"); + /* dsoc close to xsoc: it must reserve power */ + } else if ((di->zero_linek > 1000) && (di->zero_linek < 1200)) { + di->zero_linek = 1200; + DBG("ZERO-new: zero_linek adjust step3...\n"); + /* dsoc[5~15], dsoc < xsoc */ + } else if ((di->dsoc <= 15 && di->dsoc > 5) && + (di->zero_linek <= 1200)) { + /* slow down */ + if (xsoc - di->dsoc >= min_gap_xsoc) + di->zero_linek = 800; + /* reserve power */ + else + di->zero_linek = 1200; + DBG("ZERO-new: zero_linek adjust step4...\n"); + /* dsoc[5, 100], dsoc < xsoc */ + } else if ((di->zero_linek < 1000) && (di->dsoc >= 5)) { + if ((xsoc - di->dsoc) < min_gap_xsoc) { + /* reserve power */ + di->zero_linek = 1200; + } else { + if (abs(di->current_avg) > 500)/* heavy */ + di->zero_linek = 900; + else + di->zero_linek = 1000; + } + DBG("ZERO-new: zero_linek adjust step5...\n"); + /* dsoc[0~5], dsoc < xsoc */ + } else if ((di->zero_linek < 1000) && (di->dsoc <= 5)) { + if ((xsoc - di->dsoc) <= 3) + di->zero_linek = 1200; + else + di->zero_linek = 800; + DBG("ZERO-new: zero_linek adjust step6...\n"); + } + } else { + /* xsoc < 0 */ + di->zero_linek = 1000; + if (!di->zero_drop_sec) + di->zero_drop_sec = get_boot_sec(); + if (base2sec(di->zero_drop_sec) >= WAIT_DSOC_DROP_SEC) { + DBG("ZERO0: t=%lu\n", base2sec(di->zero_drop_sec)); + di->zero_drop_sec = 0; + di->dsoc--; + di->zero_dsoc = (di->dsoc + 1) * 1000 - + MIN_ACCURACY; + } + } + + if (voltage_avg < pwroff_vol - 70) { + if (!di->shtd_drop_sec) + di->shtd_drop_sec = get_boot_sec(); + if (base2sec(di->shtd_drop_sec) > WAIT_SHTD_DROP_SEC) { + BAT_INFO("voltage extreme low...soc:%d->0\n", di->dsoc); + di->shtd_drop_sec = 0; + di->dsoc = 0; + } + } else { + di->shtd_drop_sec = 0; + } + + DBG("ZERO-new: org_linek=%d, zero_linek=%d, dsoc=%d, Xsoc=%d, " + "rsoc=%d, gap=%d, v=%d, vsys=%d\n" + "ZERO-new: di->zero_dsoc=%d, zero_remain_cap=%d, zero_drop=%ld, " + "sht_drop=%ld\n\n", + org_linek, di->zero_linek, di->dsoc, xsoc, di->rsoc, + min_gap_xsoc, voltage_avg, vsys, di->zero_dsoc, di->zero_remain_cap, + base2sec(di->zero_drop_sec), base2sec(di->shtd_drop_sec)); +} + +static void rk818_bat_finish_algo_prepare(struct rk818_battery *di) +{ + di->finish_base = get_boot_sec(); + if (!di->finish_base) + di->finish_base = 1; +} + +static void rk818_bat_smooth_algo_prepare(struct rk818_battery *di) +{ + int tmp_soc; + + tmp_soc = di->sm_chrg_dsoc / 1000; + if (tmp_soc != di->dsoc) + di->sm_chrg_dsoc = di->dsoc * 1000; + + tmp_soc = di->sm_dischrg_dsoc / 1000; + if (tmp_soc != di->dsoc) + di->sm_dischrg_dsoc = + (di->dsoc + 1) * 1000 - MIN_ACCURACY; + + DBG("<%s>. tmp_soc=%d, dsoc=%d, dsoc:sm_dischrg=%d, sm_chrg=%d\n", + __func__, tmp_soc, di->dsoc, di->sm_dischrg_dsoc, di->sm_chrg_dsoc); + + rk818_bat_calc_sm_linek(di); +} + +static void rk818_bat_zero_algo_prepare(struct rk818_battery *di) +{ + int tmp_dsoc; + + di->zero_timeout_cnt = 0; + tmp_dsoc = di->zero_dsoc / 1000; + if (tmp_dsoc != di->dsoc) + di->zero_dsoc = (di->dsoc + 1) * 1000 - MIN_ACCURACY; + + DBG("<%s>. first calc, reinit linek\n", __func__); + + rk818_bat_calc_zero_linek(di); +} + +static void rk818_bat_calc_zero_algorithm(struct rk818_battery *di) +{ + int tmp_soc = 0, sm_delta_dsoc = 0; + + tmp_soc = di->zero_dsoc / 1000; + if (tmp_soc == di->dsoc) + goto out; + + DBG("<%s>. enter: dsoc=%d, rsoc=%d\n", __func__, di->dsoc, di->rsoc); + /* when discharge slow down, take sm chrg into calc */ + if (di->dsoc < di->rsoc) { + /* take sm charge rest into calc */ + tmp_soc = di->sm_chrg_dsoc / 1000; + if (tmp_soc == di->dsoc) { + sm_delta_dsoc = di->sm_chrg_dsoc - di->dsoc * 1000; + di->sm_chrg_dsoc = di->dsoc * 1000; + di->zero_dsoc += sm_delta_dsoc; + DBG("ZERO1: take sm chrg,delta=%d\n", sm_delta_dsoc); + } + } + + /* when discharge speed up, take sm dischrg into calc */ + if (di->dsoc > di->rsoc) { + /* take sm discharge rest into calc */ + tmp_soc = di->sm_dischrg_dsoc / 1000; + if (tmp_soc == di->dsoc) { + sm_delta_dsoc = di->sm_dischrg_dsoc - + ((di->dsoc + 1) * 1000 - MIN_ACCURACY); + di->sm_dischrg_dsoc = (di->dsoc + 1) * 1000 - + MIN_ACCURACY; + di->zero_dsoc += sm_delta_dsoc; + DBG("ZERO1: take sm dischrg,delta=%d\n", sm_delta_dsoc); + } + } + + /* check overflow */ + if (di->zero_dsoc > (di->dsoc + 1) * 1000 - MIN_ACCURACY) { + DBG("ZERO1: zero dsoc overflow: %d\n", di->zero_dsoc); + di->zero_dsoc = (di->dsoc + 1) * 1000 - MIN_ACCURACY; + } + + /* check new dsoc */ + tmp_soc = di->zero_dsoc / 1000; + if (tmp_soc != di->dsoc) { + /* avoid dsoc jump when heavy load */ + if ((di->dsoc - tmp_soc) > 1) { + di->dsoc--; + di->zero_dsoc = (di->dsoc + 1) * 1000 - MIN_ACCURACY; + DBG("ZERO1: heavy load...\n"); + } else { + di->dsoc = tmp_soc; + } + di->zero_drop_sec = 0; + } + +out: + DBG("ZERO1: zero_dsoc(Y0)=%d, dsoc=%d, rsoc=%d, tmp_soc=%d\n", + di->zero_dsoc, di->dsoc, di->rsoc, tmp_soc); + DBG("ZERO1: sm_dischrg_dsoc=%d, sm_chrg_dsoc=%d\n", + di->sm_dischrg_dsoc, di->sm_chrg_dsoc); +} + +static void rk818_bat_zero_algorithm(struct rk818_battery *di) +{ + int delta_cap = 0, delta_soc = 0; + + di->zero_timeout_cnt++; + delta_cap = di->zero_remain_cap - di->remain_cap; + delta_soc = di->zero_linek * (delta_cap * 100) / DIV(di->fcc); + + DBG("ZERO1: zero_linek=%d, zero_dsoc(Y0)=%d, dsoc=%d, rsoc=%d\n" + "ZERO1: delta_soc(X0)=%d, delta_cap=%d, zero_remain_cap = %d\n" + "ZERO1: timeout_cnt=%d, sm_dischrg=%d, sm_chrg=%d\n\n", + di->zero_linek, di->zero_dsoc, di->dsoc, di->rsoc, + delta_soc, delta_cap, di->zero_remain_cap, + di->zero_timeout_cnt, di->sm_dischrg_dsoc, di->sm_chrg_dsoc); + + if ((delta_soc >= MIN_ZERO_DSOC_ACCURACY) || + (di->zero_timeout_cnt > MIN_ZERO_OVERCNT) || + (di->zero_linek == 0)) { + DBG("ZERO1:--------- enter calc -----------\n"); + di->zero_timeout_cnt = 0; + di->zero_dsoc -= delta_soc; + rk818_bat_calc_zero_algorithm(di); + rk818_bat_calc_zero_linek(di); + } +} + +static void rk818_bat_dump_time_table(struct rk818_battery *di) +{ + u8 i; + static int old_index; + static int old_min; + int mod = di->dsoc % 10; + int index = di->dsoc / 10; + u32 time; + + if (rk818_bat_chrg_online(di)) + time = base2min(di->plug_in_base); + else + time = base2min(di->plug_out_base); + + if ((mod == 0) && (index > 0) && (old_index != index)) { + di->dbg_chrg_min[index - 1] = time - old_min; + old_min = time; + old_index = index; + } + + for (i = 1; i < 11; i++) + DBG("Time[%d]=%d, ", (i * 10), di->dbg_chrg_min[i - 1]); + DBG("\n"); +} + +static void rk818_bat_debug_info(struct rk818_battery *di) +{ + u8 sup_tst, ggcon, ggsts, vb_mod, ts_ctrl, reboot_cnt; + u8 usb_ctrl, chrg_ctrl1, thermal; + u8 int_sts1, int_sts2; + u8 int_msk1, int_msk2; + u8 chrg_ctrl2, chrg_ctrl3, rtc, misc, dcdc_en; + char *work_mode[] = {"ZERO", "FINISH", "UN", "UN", "SMOOTH"}; + char *bat_mode[] = {"BAT", "VIRTUAL"}; + + if (rk818_bat_chrg_online(di)) + di->plug_out_base = get_boot_sec(); + else + di->plug_in_base = get_boot_sec(); + + rk818_bat_dump_time_table(di); + + if (!dbg_enable) + return; + + ts_ctrl = rk818_bat_read(di, RK818_TS_CTRL_REG); + misc = rk818_bat_read(di, RK818_MISC_MARK_REG); + ggcon = rk818_bat_read(di, RK818_GGCON_REG); + ggsts = rk818_bat_read(di, RK818_GGSTS_REG); + sup_tst = rk818_bat_read(di, RK818_SUP_STS_REG); + vb_mod = rk818_bat_read(di, RK818_VB_MON_REG); + usb_ctrl = rk818_bat_read(di, RK818_USB_CTRL_REG); + chrg_ctrl1 = rk818_bat_read(di, RK818_CHRG_CTRL_REG1); + chrg_ctrl2 = rk818_bat_read(di, RK818_CHRG_CTRL_REG2); + chrg_ctrl3 = rk818_bat_read(di, RK818_CHRG_CTRL_REG3); + rtc = rk818_bat_read(di, 0); + thermal = rk818_bat_read(di, RK818_THERMAL_REG); + int_sts1 = rk818_bat_read(di, RK818_INT_STS_REG1); + int_sts2 = rk818_bat_read(di, RK818_INT_STS_REG2); + int_msk1 = rk818_bat_read(di, RK818_INT_STS_MSK_REG1); + int_msk2 = rk818_bat_read(di, RK818_INT_STS_MSK_REG2); + dcdc_en = rk818_bat_read(di, RK818_DCDC_EN_REG); + reboot_cnt = rk818_bat_read(di, RK818_REBOOT_CNT_REG); + + DBG("\n------- DEBUG REGS, [Ver: %s] -------------------\n" + "GGCON=0x%2x, GGSTS=0x%2x, RTC=0x%2x, DCDC_EN2=0x%2x\n" + "SUP_STS= 0x%2x, VB_MOD=0x%2x, USB_CTRL=0x%2x\n" + "THERMAL=0x%2x, MISC_MARK=0x%2x, TS_CTRL=0x%2x\n" + "CHRG_CTRL:REG1=0x%2x, REG2=0x%2x, REG3=0x%2x\n" + "INT_STS: REG1=0x%2x, REG2=0x%2x\n" + "INT_MSK: REG1=0x%2x, REG2=0x%2x\n", + DRIVER_VERSION, ggcon, ggsts, rtc, dcdc_en, + sup_tst, vb_mod, usb_ctrl, + thermal, misc, ts_ctrl, + chrg_ctrl1, chrg_ctrl2, chrg_ctrl3, + int_sts1, int_sts2, int_msk1, int_msk2 + ); + + DBG("###############################################################\n" + "Dsoc=%d, Rsoc=%d, Vavg=%d, Iavg=%d, Cap=%d, Fcc=%d, d=%d\n" + "K=%d, Mode=%s, Oldcap=%d, Is=%d, Ip=%d, Vs=%d\n" + "fb_temp=%d, bat_temp=%d, sample_res=%d, USB=%d, DC=%d\n" + "off:i=0x%x, c=0x%x, p=%d, Rbat=%d, age_ocv_cap=%d, hot=%d\n" + "adp:finish=%lu, boot_min=%lu, sleep_min=%lu, adc=%d, Vsys=%d\n" + "bat:%s, meet: soc=%d, calc: dsoc=%d, rsoc=%d, Vocv=%d\n" + "pwr: dsoc=%d, rsoc=%d, vol=%d, halt: st=%d, cnt=%d, reboot=%d\n" + "ocv_c=%d: %d -> %d; max_c=%d: %d -> %d; force_c=%d: %d -> %d\n" + "min=%d, init=%d, sw=%d, below0=%d, first=%d, changed=%d\n" + "###############################################################\n", + di->dsoc, di->rsoc, di->voltage_avg, di->current_avg, + di->remain_cap, di->fcc, di->rsoc - di->dsoc, + di->sm_linek, work_mode[di->work_mode], di->sm_remain_cap, + di->res_div * chrg_cur_sel_array[chrg_ctrl1 & 0x0f], + chrg_cur_input_array[usb_ctrl & 0x0f], + chrg_vol_sel_array[(chrg_ctrl1 & 0x70) >> 4], + feedback_temp_array[(thermal & 0x0c) >> 2], di->temperature, + di->pdata->sample_res, di->usb_in, di->ac_in, + rk818_bat_get_ioffset(di), + rk818_bat_get_coffset(di), di->poffset, di->bat_res, + di->age_adjust_cap, !!(thermal & HOTDIE_STS), + base2min(di->finish_base), + base2min(di->boot_base), di->sleep_sum_sec / 60, + di->adc_allow_update, + di->voltage_avg + di->current_avg * DEF_PWRPATH_RES / 1000, + bat_mode[di->pdata->bat_mode], di->dbg_meet_soc, di->dbg_calc_dsoc, + di->dbg_calc_rsoc, di->voltage_ocv, di->dbg_pwr_dsoc, + di->dbg_pwr_rsoc, di->dbg_pwr_vol, di->is_halt, di->halt_cnt, + reboot_cnt, di->is_ocv_calib, di->ocv_pre_dsoc, di->ocv_new_dsoc, + di->is_max_soc_offset, di->max_pre_dsoc, di->max_new_dsoc, + di->is_force_calib, di->force_pre_dsoc, di->force_new_dsoc, + di->pwroff_min, di->is_initialized, di->is_sw_reset, + di->dbg_cap_low0, di->is_first_on, di->last_dsoc + ); +} + +static void rk818_bat_init_capacity(struct rk818_battery *di, u32 cap) +{ + int delta_cap; + + delta_cap = cap - di->remain_cap; + if (!delta_cap) + return; + + di->age_adjust_cap += delta_cap; + rk818_bat_init_coulomb_cap(di, cap); + rk818_bat_smooth_algo_prepare(di); + rk818_bat_zero_algo_prepare(di); +} + +static void rk818_bat_update_age_fcc(struct rk818_battery *di) +{ + int fcc, remain_cap, age_keep_min, lock_fcc; + + lock_fcc = rk818_bat_get_coulomb_cap(di); + remain_cap = lock_fcc - di->age_ocv_cap - di->age_adjust_cap; + age_keep_min = base2min(di->age_keep_sec); + + DBG("%s: lock_fcc=%d, age_ocv_cap=%d, age_adjust_cap=%d, remain_cap=%d," + "age_allow_update=%d, age_keep_min=%d\n", + __func__, lock_fcc, di->age_ocv_cap, di->age_adjust_cap, remain_cap, + di->age_allow_update, age_keep_min); + + if ((di->chrg_status == CHARGE_FINISH) && (di->age_allow_update) && + (age_keep_min < 1200)) { + di->age_allow_update = false; + fcc = remain_cap * 100 / DIV(100 - di->age_ocv_soc); + BAT_INFO("lock_fcc=%d, calc_cap=%d, age: soc=%d, cap=%d, " + "level=%d, fcc:%d->%d?\n", + lock_fcc, remain_cap, di->age_ocv_soc, + di->age_ocv_cap, di->age_level, di->fcc, fcc); + + if ((fcc < di->qmax) && (fcc > MIN_FCC)) { + BAT_INFO("fcc:%d->%d!\n", di->fcc, fcc); + di->fcc = fcc; + rk818_bat_init_capacity(di, di->fcc); + rk818_bat_save_fcc(di, di->fcc); + rk818_bat_save_age_level(di, di->age_level); + } + } +} + +static void rk818_bat_wait_finish_sig(struct rk818_battery *di) +{ + int chrg_finish_vol = di->pdata->max_chrg_voltage; + + if (!rk818_bat_chrg_online(di)) + return; + + if ((di->chrg_status == CHARGE_FINISH) && (di->adc_allow_update) && + (di->voltage_avg > chrg_finish_vol - 150)) { + rk818_bat_update_age_fcc(di); + if (rk818_bat_adc_calib(di)) + di->adc_allow_update = false; + } +} + +static void rk818_bat_finish_algorithm(struct rk818_battery *di) +{ + unsigned long finish_sec, soc_sec; + int plus_soc, finish_current, rest = 0; + + /* rsoc */ + if ((di->remain_cap != di->fcc) && + (rk818_bat_get_chrg_status(di) == CHARGE_FINISH)) { + di->age_adjust_cap += (di->fcc - di->remain_cap); + rk818_bat_init_coulomb_cap(di, di->fcc); + } + + /* dsoc */ + if (di->dsoc < 100) { + if (!di->finish_base) + di->finish_base = get_boot_sec(); + finish_current = (di->rsoc - di->dsoc) > FINISH_MAX_SOC_DELAY ? + FINISH_CHRG_CUR2 : FINISH_CHRG_CUR1; + finish_sec = base2sec(di->finish_base); + soc_sec = di->fcc * 3600 / 100 / DIV(finish_current); + plus_soc = finish_sec / DIV(soc_sec); + if (finish_sec > soc_sec) { + rest = finish_sec % soc_sec; + di->dsoc += plus_soc; + di->finish_base = get_boot_sec(); + if (di->finish_base > rest) + di->finish_base = get_boot_sec() - rest; + } + DBG("<%s>.CHARGE_FINISH:dsoc<100,dsoc=%d\n" + "soc_time=%lu, sec_finish=%lu, plus_soc=%d, rest=%d\n", + __func__, di->dsoc, soc_sec, finish_sec, plus_soc, rest); + } +} + +static void rk818_bat_calc_smooth_dischrg(struct rk818_battery *di) +{ + int tmp_soc = 0, sm_delta_dsoc = 0, zero_delta_dsoc = 0; + + tmp_soc = di->sm_dischrg_dsoc / 1000; + if (tmp_soc == di->dsoc) + goto out; + + DBG("<%s>. enter: dsoc=%d, rsoc=%d\n", __func__, di->dsoc, di->rsoc); + /* when dischrge slow down, take sm charge rest into calc */ + if (di->dsoc < di->rsoc) { + tmp_soc = di->sm_chrg_dsoc / 1000; + if (tmp_soc == di->dsoc) { + sm_delta_dsoc = di->sm_chrg_dsoc - di->dsoc * 1000; + di->sm_chrg_dsoc = di->dsoc * 1000; + di->sm_dischrg_dsoc += sm_delta_dsoc; + DBG("<%s>. take sm dischrg, delta=%d\n", + __func__, sm_delta_dsoc); + } + } + + /* when discharge speed up, take zero discharge rest into calc */ + if (di->dsoc > di->rsoc) { + tmp_soc = di->zero_dsoc / 1000; + if (tmp_soc == di->dsoc) { + zero_delta_dsoc = di->zero_dsoc - ((di->dsoc + 1) * + 1000 - MIN_ACCURACY); + di->zero_dsoc = (di->dsoc + 1) * 1000 - MIN_ACCURACY; + di->sm_dischrg_dsoc += zero_delta_dsoc; + DBG("<%s>. take zero schrg, delta=%d\n", + __func__, zero_delta_dsoc); + } + } + + /* check up overflow */ + if ((di->sm_dischrg_dsoc) > ((di->dsoc + 1) * 1000 - MIN_ACCURACY)) { + DBG("<%s>. dischrg_dsoc up overflow\n", __func__); + di->sm_dischrg_dsoc = (di->dsoc + 1) * + 1000 - MIN_ACCURACY; + } + + /* check new dsoc */ + tmp_soc = di->sm_dischrg_dsoc / 1000; + if (tmp_soc != di->dsoc) { + di->dsoc = tmp_soc; + di->sm_chrg_dsoc = di->dsoc * 1000; + } +out: + DBG("<%s>. dsoc=%d, rsoc=%d, dsoc:sm_dischrg=%d, sm_chrg=%d, zero=%d\n", + __func__, di->dsoc, di->rsoc, di->sm_dischrg_dsoc, di->sm_chrg_dsoc, + di->zero_dsoc); + +} + +static void rk818_bat_calc_smooth_chrg(struct rk818_battery *di) +{ + int tmp_soc = 0, sm_delta_dsoc = 0, zero_delta_dsoc = 0; + + tmp_soc = di->sm_chrg_dsoc / 1000; + if (tmp_soc == di->dsoc) + goto out; + + DBG("<%s>. enter: dsoc=%d, rsoc=%d\n", __func__, di->dsoc, di->rsoc); + /* when charge slow down, take zero & sm dischrg into calc */ + if (di->dsoc > di->rsoc) { + /* take sm discharge rest into calc */ + tmp_soc = di->sm_dischrg_dsoc / 1000; + if (tmp_soc == di->dsoc) { + sm_delta_dsoc = di->sm_dischrg_dsoc - + ((di->dsoc + 1) * 1000 - MIN_ACCURACY); + di->sm_dischrg_dsoc = (di->dsoc + 1) * 1000 - + MIN_ACCURACY; + di->sm_chrg_dsoc += sm_delta_dsoc; + DBG("<%s>. take sm dischrg, delta=%d\n", + __func__, sm_delta_dsoc); + } + + /* take zero discharge rest into calc */ + tmp_soc = di->zero_dsoc / 1000; + if (tmp_soc == di->dsoc) { + zero_delta_dsoc = di->zero_dsoc - + ((di->dsoc + 1) * 1000 - MIN_ACCURACY); + di->zero_dsoc = (di->dsoc + 1) * 1000 - MIN_ACCURACY; + di->sm_chrg_dsoc += zero_delta_dsoc; + DBG("<%s>. take zero dischrg, delta=%d\n", + __func__, zero_delta_dsoc); + } + } + + /* check down overflow */ + if (di->sm_chrg_dsoc < di->dsoc * 1000) { + DBG("<%s>. chrg_dsoc down overflow\n", __func__); + di->sm_chrg_dsoc = di->dsoc * 1000; + } + + /* check new dsoc */ + tmp_soc = di->sm_chrg_dsoc / 1000; + if (tmp_soc != di->dsoc) { + di->dsoc = tmp_soc; + di->sm_dischrg_dsoc = (di->dsoc + 1) * 1000 - MIN_ACCURACY; + } +out: + DBG("<%s>.dsoc=%d, rsoc=%d, dsoc: sm_dischrg=%d, sm_chrg=%d, zero=%d\n", + __func__, di->dsoc, di->rsoc, di->sm_dischrg_dsoc, di->sm_chrg_dsoc, + di->zero_dsoc); +} + +static void rk818_bat_smooth_algorithm(struct rk818_battery *di) +{ + int ydsoc = 0, delta_cap = 0, old_cap = 0; + unsigned long tgt_sec = 0; + + di->remain_cap = rk818_bat_get_coulomb_cap(di); + + /* full charge: slow down */ + if ((di->dsoc == 99) && (di->chrg_status == CC_OR_CV) && + (di->current_avg > 0)) { + di->sm_linek = FULL_CHRG_K; + /* terminal charge, slow down */ + } else if ((di->current_avg >= TERM_CHRG_CURR) && + (di->chrg_status == CC_OR_CV) && (di->dsoc >= TERM_CHRG_DSOC)) { + di->sm_linek = TERM_CHRG_K; + DBG("<%s>. terminal mode..\n", __func__); + /* simulate charge, speed up */ + } else if ((di->current_avg <= SIMULATE_CHRG_CURR) && + (di->current_avg > 0) && (di->chrg_status == CC_OR_CV) && + (di->dsoc < TERM_CHRG_DSOC) && + ((di->rsoc - di->dsoc) >= SIMULATE_CHRG_INTV)) { + di->sm_linek = SIMULATE_CHRG_K; + DBG("<%s>. simulate mode..\n", __func__); + } else { + /* charge and discharge switch */ + if ((di->sm_linek * di->current_avg <= 0) || + (di->sm_linek == TERM_CHRG_K) || + (di->sm_linek == FULL_CHRG_K) || + (di->sm_linek == SIMULATE_CHRG_K)) { + DBG("<%s>. linek mode, retinit sm linek..\n", __func__); + rk818_bat_calc_sm_linek(di); + } + } + + old_cap = di->sm_remain_cap; + /* + * when dsoc equal rsoc(not include full, term, simulate case), + * sm_linek should change to -1000/1000 smoothly to avoid dsoc+1/-1 + * right away, so change it after flat seconds + */ + if ((di->dsoc == di->rsoc) && (abs(di->sm_linek) != 1000) && + (di->sm_linek != FULL_CHRG_K && di->sm_linek != TERM_CHRG_K && + di->sm_linek != SIMULATE_CHRG_K)) { + if (!di->flat_match_sec) + di->flat_match_sec = get_boot_sec(); + tgt_sec = di->fcc * 3600 / 100 / DIV(abs(di->current_avg)) / 3; + if (base2sec(di->flat_match_sec) >= tgt_sec) { + di->flat_match_sec = 0; + di->sm_linek = (di->current_avg >= 0) ? 1000 : -1000; + } + DBG("<%s>. flat_sec=%ld, tgt_sec=%ld, sm_k=%d\n", __func__, + base2sec(di->flat_match_sec), tgt_sec, di->sm_linek); + } else { + di->flat_match_sec = 0; + } + + /* abs(k)=1000 or dsoc=100, stop calc */ + if ((abs(di->sm_linek) == 1000) || (di->current_avg >= 0 && + di->chrg_status == CC_OR_CV && di->dsoc >= 100)) { + DBG("<%s>. sm_linek=%d\n", __func__, di->sm_linek); + if (abs(di->sm_linek) == 1000) { + di->dsoc = di->rsoc; + di->sm_linek = (di->sm_linek > 0) ? 1000 : -1000; + DBG("<%s>. dsoc == rsoc, sm_linek=%d\n", + __func__, di->sm_linek); + } + di->sm_remain_cap = di->remain_cap; + di->sm_chrg_dsoc = di->dsoc * 1000; + di->sm_dischrg_dsoc = (di->dsoc + 1) * 1000 - MIN_ACCURACY; + DBG("<%s>. sm_dischrg_dsoc=%d, sm_chrg_dsoc=%d\n", + __func__, di->sm_dischrg_dsoc, di->sm_chrg_dsoc); + } else { + delta_cap = di->remain_cap - di->sm_remain_cap; + if (delta_cap == 0) { + DBG("<%s>. delta_cap = 0\n", __func__); + return; + } + ydsoc = di->sm_linek * abs(delta_cap) * 100 / DIV(di->fcc); + if (ydsoc == 0) { + DBG("<%s>. ydsoc = 0\n", __func__); + return; + } + di->sm_remain_cap = di->remain_cap; + + DBG("<%s>. k=%d, ydsoc=%d; cap:old=%d, new:%d; delta_cap=%d\n", + __func__, di->sm_linek, ydsoc, old_cap, + di->sm_remain_cap, delta_cap); + + /* discharge mode */ + if (ydsoc < 0) { + di->sm_dischrg_dsoc += ydsoc; + rk818_bat_calc_smooth_dischrg(di); + /* charge mode */ + } else { + di->sm_chrg_dsoc += ydsoc; + rk818_bat_calc_smooth_chrg(di); + } + + if (di->s2r) { + di->s2r = false; + rk818_bat_calc_sm_linek(di); + } + } +} + +/* + * cccv and finish switch all the time will cause dsoc freeze, + * if so, do finish chrg, 100ma is less than min finish_ma. + */ +static bool rk818_bat_fake_finish_mode(struct rk818_battery *di) +{ + if ((di->rsoc == 100) && (rk818_bat_get_chrg_status(di) == CC_OR_CV) && + (abs(di->current_avg) <= 100)) + return true; + else + return false; +} + +static void rk818_bat_display_smooth(struct rk818_battery *di) +{ + /* discharge: reinit "zero & smooth" algorithm to avoid handling dsoc */ + if (di->s2r && !di->sleep_chrg_online) { + DBG("s2r: discharge, reset algorithm...\n"); + di->s2r = false; + rk818_bat_zero_algo_prepare(di); + rk818_bat_smooth_algo_prepare(di); + return; + } + + if (di->work_mode == MODE_FINISH) { + DBG("step1: charge finish...\n"); + rk818_bat_finish_algorithm(di); + if ((rk818_bat_get_chrg_status(di) != CHARGE_FINISH) && + !rk818_bat_fake_finish_mode(di)) { + if ((di->current_avg < 0) && + (di->voltage_avg < di->pdata->zero_algorithm_vol)) { + DBG("step1: change to zero mode...\n"); + rk818_bat_zero_algo_prepare(di); + di->work_mode = MODE_ZERO; + } else { + DBG("step1: change to smooth mode...\n"); + rk818_bat_smooth_algo_prepare(di); + di->work_mode = MODE_SMOOTH; + } + } + } else if (di->work_mode == MODE_ZERO) { + DBG("step2: zero algorithm...\n"); + rk818_bat_zero_algorithm(di); + if ((di->voltage_avg >= di->pdata->zero_algorithm_vol + 50) || + (di->current_avg >= 0)) { + DBG("step2: change to smooth mode...\n"); + rk818_bat_smooth_algo_prepare(di); + di->work_mode = MODE_SMOOTH; + } else if ((rk818_bat_get_chrg_status(di) == CHARGE_FINISH) || + rk818_bat_fake_finish_mode(di)) { + DBG("step2: change to finish mode...\n"); + rk818_bat_finish_algo_prepare(di); + di->work_mode = MODE_FINISH; + } + } else { + DBG("step3: smooth algorithm...\n"); + rk818_bat_smooth_algorithm(di); + if ((di->current_avg < 0) && + (di->voltage_avg < di->pdata->zero_algorithm_vol)) { + DBG("step3: change to zero mode...\n"); + rk818_bat_zero_algo_prepare(di); + di->work_mode = MODE_ZERO; + } else if ((rk818_bat_get_chrg_status(di) == CHARGE_FINISH) || + rk818_bat_fake_finish_mode(di)) { + DBG("step3: change to finish mode...\n"); + rk818_bat_finish_algo_prepare(di); + di->work_mode = MODE_FINISH; + } + } +} + +static void rk818_bat_relax_vol_calib(struct rk818_battery *di) +{ + int soc, cap, vol; + + vol = di->voltage_relax; + soc = rk818_bat_vol_to_ocvsoc(di, vol); + cap = rk818_bat_vol_to_ocvcap(di, vol); + rk818_bat_init_capacity(di, cap); + BAT_INFO("sleep ocv calib: rsoc=%d, cap=%d\n", soc, cap); +} + +static void rk818_bat_relife_age_flag(struct rk818_battery *di) +{ + u8 ocv_soc, ocv_cap, soc_level; + + if (di->voltage_relax <= 0) + return; + + ocv_soc = rk818_bat_vol_to_ocvsoc(di, di->voltage_relax); + ocv_cap = rk818_bat_vol_to_ocvcap(di, di->voltage_relax); + DBG("<%s>. ocv_soc=%d, min=%lu, vol=%d\n", __func__, + ocv_soc, di->sleep_dischrg_sec / 60, di->voltage_relax); + + /* sleep enough time and ocv_soc enough low */ + if (!di->age_allow_update && ocv_soc <= 10) { + di->age_voltage = di->voltage_relax; + di->age_ocv_cap = ocv_cap; + di->age_ocv_soc = ocv_soc; + di->age_adjust_cap = 0; + + if (ocv_soc <= 1) + di->age_level = 100; + else if (ocv_soc < 5) + di->age_level = 90; + else + di->age_level = 80; + + soc_level = rk818_bat_get_age_level(di); + if (soc_level > di->age_level) { + di->age_allow_update = false; + } else { + di->age_allow_update = true; + di->age_keep_sec = get_boot_sec(); + } + + BAT_INFO("resume: age_vol:%d, age_ocv_cap:%d, age_ocv_soc:%d, " + "soc_level:%d, age_allow_update:%d, " + "age_level:%d\n", + di->age_voltage, di->age_ocv_cap, ocv_soc, soc_level, + di->age_allow_update, di->age_level); + } +} + +static int rk818_bat_sleep_dischrg(struct rk818_battery *di) +{ + bool ocv_soc_updated = false; + int tgt_dsoc, gap_soc, sleep_soc = 0; + int pwroff_vol = di->pdata->pwroff_vol; + unsigned long sleep_sec = di->sleep_dischrg_sec; + + DBG("<%s>. enter: dsoc=%d, rsoc=%d, rv=%d, v=%d, sleep_min=%lu\n", + __func__, di->dsoc, di->rsoc, di->voltage_relax, + di->voltage_avg, sleep_sec / 60); + + if (di->voltage_relax >= di->voltage_avg) { + rk818_bat_relax_vol_calib(di); + rk818_bat_restart_relax(di); + rk818_bat_relife_age_flag(di); + ocv_soc_updated = true; + } + + /* handle dsoc */ + if (di->dsoc <= di->rsoc) { + di->sleep_sum_cap = (SLP_CURR_MIN * sleep_sec / 3600); + sleep_soc = di->sleep_sum_cap * 100 / DIV(di->fcc); + tgt_dsoc = di->dsoc - sleep_soc; + if (sleep_soc > 0) { + BAT_INFO("calib0: rl=%d, dl=%d, intval=%d\n", + di->rsoc, di->dsoc, sleep_soc); + if (di->dsoc < 5) { + di->dsoc--; + } else if ((tgt_dsoc < 5) && (di->dsoc >= 5)) { + if (di->dsoc == 5) + di->dsoc--; + else + di->dsoc = 5; + } else if (tgt_dsoc > 5) { + di->dsoc = tgt_dsoc; + } + } + + DBG("%s: dsoc<=rsoc, sum_cap=%d==>sleep_soc=%d, tgt_dsoc=%d\n", + __func__, di->sleep_sum_cap, sleep_soc, tgt_dsoc); + } else { + /* di->dsoc > di->rsoc */ + di->sleep_sum_cap = (SLP_CURR_MAX * sleep_sec / 3600); + sleep_soc = di->sleep_sum_cap / DIV(di->fcc / 100); + gap_soc = di->dsoc - di->rsoc; + + BAT_INFO("calib1: rsoc=%d, dsoc=%d, intval=%d\n", + di->rsoc, di->dsoc, sleep_soc); + if (gap_soc > sleep_soc) { + if ((gap_soc - 5) > (sleep_soc * 2)) + di->dsoc -= (sleep_soc * 2); + else + di->dsoc -= sleep_soc; + } else { + di->dsoc = di->rsoc; + } + + DBG("%s: dsoc>rsoc, sum_cap=%d=>sleep_soc=%d, gap_soc=%d\n", + __func__, di->sleep_sum_cap, sleep_soc, gap_soc); + } + + if (di->voltage_avg <= pwroff_vol - 70) { + di->dsoc = 0; + rk_send_wakeup_key(); + BAT_INFO("low power sleeping, shutdown... %d\n", di->dsoc); + } + + if (ocv_soc_updated && sleep_soc && (di->rsoc - di->dsoc) < 5 && + di->dsoc < 40) { + di->dsoc--; + BAT_INFO("low power sleeping, reserved... %d\n", di->dsoc); + } + + if (di->dsoc <= 0) { + di->dsoc = 0; + rk_send_wakeup_key(); + BAT_INFO("sleep dsoc is %d...\n", di->dsoc); + } + + DBG("<%s>. out: dsoc=%d, rsoc=%d, sum_cap=%d\n", + __func__, di->dsoc, di->rsoc, di->sleep_sum_cap); + + return sleep_soc; +} + +static void rk818_bat_power_supply_changed(struct rk818_battery *di) +{ + u8 status, thermal; + static int old_soc = -1; + + if (di->dsoc > 100) + di->dsoc = 100; + else if (di->dsoc < 0) + di->dsoc = 0; + + if (di->dsoc == old_soc) + return; + + thermal = rk818_bat_read(di, RK818_THERMAL_REG); + status = rk818_bat_read(di, RK818_SUP_STS_REG); + status = (status & CHRG_STATUS_MSK) >> 4; + old_soc = di->dsoc; + di->last_dsoc = di->dsoc; + + if (di->bat) + power_supply_changed(di->bat); + + BAT_INFO("changed: dsoc=%d, rsoc=%d, v=%d, ov=%d c=%d, " + "cap=%d, f=%d, st=%s, hotdie=%d\n", + di->dsoc, di->rsoc, di->voltage_avg, di->voltage_ocv, + di->current_avg, di->remain_cap, di->fcc, bat_status[status], + !!(thermal & HOTDIE_STS)); + + BAT_INFO("dl=%d, rl=%d, v=%d, halt=%d, halt_n=%d, max=%d, " + "init=%d, sw=%d, calib=%d, below0=%d, force=%d\n", + di->dbg_pwr_dsoc, di->dbg_pwr_rsoc, di->dbg_pwr_vol, + di->is_halt, di->halt_cnt, di->is_max_soc_offset, + di->is_initialized, di->is_sw_reset, di->is_ocv_calib, + di->dbg_cap_low0, di->is_force_calib); +} + +static u8 rk818_bat_check_reboot(struct rk818_battery *di) +{ + u8 cnt; + + cnt = rk818_bat_read(di, RK818_REBOOT_CNT_REG); + cnt++; + + if (cnt >= REBOOT_MAX_CNT) { + BAT_INFO("reboot: %d --> %d\n", di->dsoc, di->rsoc); + di->dsoc = di->rsoc; + if (di->dsoc > 100) + di->dsoc = 100; + else if (di->dsoc < 0) + di->dsoc = 0; + rk818_bat_save_dsoc(di, di->dsoc); + cnt = REBOOT_MAX_CNT; + } + + rk818_bat_save_reboot_cnt(di, cnt); + DBG("reboot cnt: %d\n", cnt); + + return cnt; +} + +static void rk818_bat_rsoc_daemon(struct rk818_battery *di) +{ + int est_vol, remain_cap; + static unsigned long sec; + + if (di->remain_cap < 0) { + if (!sec) + sec = get_boot_sec(); + + DBG("sec=%ld, hold_sec=%ld\n", sec, base2sec(sec)); + + if (base2sec(sec) >= 60) { + sec = 0; + di->dbg_cap_low0++; + est_vol = di->voltage_avg - + (di->bat_res * di->current_avg) / 1000; + remain_cap = rk818_bat_vol_to_ocvcap(di, est_vol); + rk818_bat_init_capacity(di, remain_cap); + BAT_INFO("adjust cap below 0 --> %d, rsoc=%d\n", + di->remain_cap, di->rsoc); + } + } else { + sec = 0; + } +} + +static void rk818_bat_update_info(struct rk818_battery *di) +{ + int is_charging; + + di->voltage_avg = rk818_bat_get_avg_voltage(di); + di->current_avg = rk818_bat_get_avg_current(di); + di->voltage_relax = rk818_bat_get_relax_voltage(di); + di->rsoc = rk818_bat_get_rsoc(di); + di->remain_cap = rk818_bat_get_coulomb_cap(di); + di->chrg_status = rk818_bat_get_chrg_status(di); + is_charging = rk818_bat_get_charge_state(di); + if (is_charging != di->is_charging) { + di->is_charging = is_charging; + if (is_charging) + di->charge_count++; + } + if (di->voltage_avg > di->voltage_max) + di->voltage_max = di->voltage_avg; + if (di->current_avg > di->current_max) + di->current_max = di->current_avg; + + /* smooth charge */ + if (di->remain_cap > di->fcc) { + di->sm_remain_cap -= (di->remain_cap - di->fcc); + DBG("<%s>. cap: remain=%d, sm_remain=%d\n", + __func__, di->remain_cap, di->sm_remain_cap); + rk818_bat_init_coulomb_cap(di, di->fcc); + } + + if (di->chrg_status != CHARGE_FINISH) + di->finish_base = get_boot_sec(); + + /* + * we need update fcc in continuous charging state, if discharge state + * keep at least 2 hour, we decide not to update fcc, so clear the + * fcc update flag: age_allow_update. + */ + if (base2min(di->plug_out_base) > 120) + di->age_allow_update = false; + + /* do adc calib: status must from cccv mode to finish mode */ + if (di->chrg_status == CC_OR_CV) { + di->adc_allow_update = true; + di->adc_calib_cnt = 0; + } +} + +static void rk818_bat_init_ts1_detect(struct rk818_battery *di) +{ + u8 buf; + u32 *ntc_table = di->pdata->ntc_table; + + if (!di->pdata->ntc_size) + return; + + /* select ua */ + buf = rk818_bat_read(di, RK818_TS_CTRL_REG); + buf &= ~TS1_CUR_MSK; + /* chose suitable UA for temperature detect */ + if (ntc_table[0] < NTC_80UA_MAX_MEASURE) { + di->pdata->ntc_factor = NTC_CALC_FACTOR_80UA; + di->pdata->ntc_uA = 80; + buf |= ADC_CUR_80UA; + } else if (ntc_table[0] < NTC_60UA_MAX_MEASURE) { + di->pdata->ntc_factor = NTC_CALC_FACTOR_60UA; + di->pdata->ntc_uA = 60; + buf |= ADC_CUR_60UA; + } else if (ntc_table[0] < NTC_40UA_MAX_MEASURE) { + di->pdata->ntc_factor = NTC_CALC_FACTOR_40UA; + di->pdata->ntc_uA = 40; + buf |= ADC_CUR_40UA; + } else { + di->pdata->ntc_factor = NTC_CALC_FACTOR_20UA; + di->pdata->ntc_uA = 20; + buf |= ADC_CUR_20UA; + } + rk818_bat_write(di, RK818_TS_CTRL_REG, buf); + + /* enable ADC_TS1_EN */ + buf = rk818_bat_read(di, RK818_ADC_CTRL_REG); + buf |= ADC_TS1_EN; + rk818_bat_write(di, RK818_ADC_CTRL_REG, buf); +} + +/* + * Due to hardware design issue, Vdelta = "(R_sample + R_other) * I_avg" will be + * included into TS1 adc value. We must subtract it to get correct adc value. + * The solution: + * + * (1) calculate Vdelta: + * + * adc1 - Vdelta ua1 (adc2 * ua1) - (adc1 * ua2) + * ------------- = ----- ==> equals: Vdelta = ----------------------------- + * adc2 - Vdelta ua2 ua1 - ua2 + * + * + * (2) calculate correct ADC value: + * + * charging: ADC = adc1 - abs(Vdelta); + * discharging: ADC = adc1 + abs(Vdelta); + */ +static int rk818_bat_get_ntc_res(struct rk818_battery *di) +{ + int adc1 = 0, adc2 = 0; + int ua1, ua2, v_delta, res, val; + u8 buf; + + /* read sample ua1 */ + buf = rk818_bat_read(di, RK818_TS_CTRL_REG); + DBG("<%s>. read adc1, sample uA=%d\n", + __func__, ((buf & 0x03) + 1) * 20); + + /* read adc adc1 */ + ua1 = di->pdata->ntc_uA; + adc1 |= rk818_bat_read(di, RK818_TS1_ADC_REGL) << 0; + adc1 |= rk818_bat_read(di, RK818_TS1_ADC_REGH) << 8; + + /* chose reference UA for adc2 */ + ua2 = (ua1 != 20) ? 20 : 40; + buf = rk818_bat_read(di, RK818_TS_CTRL_REG); + buf &= ~TS1_CUR_MSK; + buf |= ((ua2 - 20) / 20); + rk818_bat_write(di, RK818_TS_CTRL_REG, buf); + + /* read adc adc2 */ + msleep(1000); + + /* read sample ua2 */ + buf = rk818_bat_read(di, RK818_TS_CTRL_REG); + DBG("<%s>. read adc2, sample uA=%d\n", + __func__, ((buf & 0x03) + 1) * 20); + + adc2 |= rk818_bat_read(di, RK818_TS1_ADC_REGL) << 0; + adc2 |= rk818_bat_read(di, RK818_TS1_ADC_REGH) << 8; + + DBG("<%s>. ua1=%d, ua2=%d, adc1=%d, adc2=%d\n", + __func__, ua1, ua2, adc1, adc2); + + /* calculate delta voltage */ + if (adc2 != adc1) + v_delta = abs((adc2 * ua1 - adc1 * ua2) / (ua2 - ua1)); + else + v_delta = 0; + + /* considering current avg direction, calcuate real adc value */ + val = (di->current_avg >= 0) ? (adc1 - v_delta) : (adc1 + v_delta); + + DBG("<%s>. Iavg=%d, Vdelta=%d, Vadc=%d\n", + __func__, di->current_avg, v_delta, val); + + res = val * di->pdata->ntc_factor; + + DBG("<%s>. val=%d, ntc_res=%d, ntc_factor=%d, Rdelta=%d\n", + __func__, val, res, di->pdata->ntc_factor, + v_delta * di->pdata->ntc_factor); + + DBG("<%s>. t=[%d'C(%d) ~ %dC(%d)]\n", __func__, + di->pdata->ntc_degree_from, di->pdata->ntc_table[0], + di->pdata->ntc_degree_from + di->pdata->ntc_size - 1, + di->pdata->ntc_table[di->pdata->ntc_size - 1]); + + rk818_bat_init_ts1_detect(di); + + return res; +} + +static BLOCKING_NOTIFIER_HEAD(rk818_bat_notifier_chain); + +int rk818_bat_temp_notifier_register(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&rk818_bat_notifier_chain, nb); +} + +int rk818_bat_temp_notifier_unregister(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&rk818_bat_notifier_chain, nb); +} + +static void rk818_bat_temp_notifier_callback(int temp) +{ + blocking_notifier_call_chain(&rk818_bat_notifier_chain, temp, NULL); +} + +static void rk818_bat_update_temperature(struct rk818_battery *di) +{ + static int old_temp, first_time = 1; + u32 ntc_size, *ntc_table; + int i, res, temp; + + ntc_table = di->pdata->ntc_table; + ntc_size = di->pdata->ntc_size; + di->temperature = VIRTUAL_TEMPERATURE; + + if (ntc_size) { + res = rk818_bat_get_ntc_res(di); + if (res < ntc_table[ntc_size - 1]) { + di->temperature = di->pdata->ntc_degree_from + + di->pdata->ntc_size - 1; + BAT_INFO("bat ntc upper max degree: R=%d\n", res); + } else if (res > ntc_table[0]) { + di->temperature = di->pdata->ntc_degree_from; + BAT_INFO("bat ntc lower min degree: R=%d\n", res); + } else { + for (i = 0; i < ntc_size; i++) { + if (res >= ntc_table[i]) + break; + } + + /* if first in, init old_temp */ + temp = (i + di->pdata->ntc_degree_from) * 10; + if (first_time == 1) { + di->temperature = temp; + old_temp = temp; + first_time = 0; + } + + /* + * compare with old one, it's invalid when over 50 + * and we should use old data. + */ + if (abs(temp - old_temp) > 50) + temp = old_temp; + else + old_temp = temp; + + di->temperature = temp; + DBG("<%s>. temperature = %d\n", + __func__, di->temperature); + rk818_bat_temp_notifier_callback(di->temperature / 10); + } + } +} + +static void rk818_bat_init_dsoc_algorithm(struct rk818_battery *di) +{ + u8 buf; + int16_t rest = 0; + unsigned long soc_sec; + const char *mode_name[] = { "MODE_ZERO", "MODE_FINISH", + "MODE_SMOOTH_CHRG", "MODE_SMOOTH_DISCHRG", "MODE_SMOOTH", }; + + /* get rest */ + rest |= rk818_bat_read(di, RK818_CALC_REST_REGH) << 8; + rest |= rk818_bat_read(di, RK818_CALC_REST_REGL) << 0; + + /* get mode */ + buf = rk818_bat_read(di, RK818_MISC_MARK_REG); + di->algo_rest_mode = (buf & ALGO_REST_MODE_MSK) >> ALGO_REST_MODE_SHIFT; + + if (rk818_bat_get_chrg_status(di) == CHARGE_FINISH) { + if (di->algo_rest_mode == MODE_FINISH) { + soc_sec = di->fcc * 3600 / 100 / FINISH_CHRG_CUR1; + if ((rest / DIV(soc_sec)) > 0) { + if (di->dsoc < 100) { + di->dsoc++; + di->algo_rest_val = rest % soc_sec; + BAT_INFO("algorithm rest(%d) dsoc " + "inc: %d\n", + rest, di->dsoc); + } else { + di->algo_rest_val = 0; + } + } else { + di->algo_rest_val = rest; + } + } else { + di->algo_rest_val = rest; + } + } else { + /* charge speed up */ + if ((rest / 1000) > 0 && rk818_bat_chrg_online(di)) { + if (di->dsoc < di->rsoc) { + di->dsoc++; + di->algo_rest_val = rest % 1000; + BAT_INFO("algorithm rest(%d) dsoc inc: %d\n", + rest, di->dsoc); + } else { + di->algo_rest_val = 0; + } + /* discharge speed up */ + } else if (((rest / 1000) < 0) && !rk818_bat_chrg_online(di)) { + if (di->dsoc > di->rsoc) { + di->dsoc--; + di->algo_rest_val = rest % 1000; + BAT_INFO("algorithm rest(%d) dsoc sub: %d\n", + rest, di->dsoc); + } else { + di->algo_rest_val = 0; + } + } else { + di->algo_rest_val = rest; + } + } + + if (di->dsoc >= 100) + di->dsoc = 100; + else if (di->dsoc <= 0) + di->dsoc = 0; + + /* init current mode */ + di->voltage_avg = rk818_bat_get_avg_voltage(di); + di->current_avg = rk818_bat_get_avg_current(di); + if (rk818_bat_get_chrg_status(di) == CHARGE_FINISH) { + rk818_bat_finish_algo_prepare(di); + di->work_mode = MODE_FINISH; + } else { + rk818_bat_smooth_algo_prepare(di); + di->work_mode = MODE_SMOOTH; + } + + DBG("<%s>. init: org_rest=%d, rest=%d, mode=%s; " + "doc(x1000): zero=%d, chrg=%d, dischrg=%d, finish=%lu\n", + __func__, rest, di->algo_rest_val, mode_name[di->algo_rest_mode], + di->zero_dsoc, di->sm_chrg_dsoc, di->sm_dischrg_dsoc, + di->finish_base); +} + +static void rk818_bat_save_algo_rest(struct rk818_battery *di) +{ + u8 buf, mode; + int16_t algo_rest = 0; + int tmp_soc; + int zero_rest = 0, sm_chrg_rest = 0; + int sm_dischrg_rest = 0, finish_rest = 0; + const char *mode_name[] = { "MODE_ZERO", "MODE_FINISH", + "MODE_SMOOTH_CHRG", "MODE_SMOOTH_DISCHRG", "MODE_SMOOTH", }; + + /* zero dischrg */ + tmp_soc = (di->zero_dsoc) / 1000; + if (tmp_soc == di->dsoc) + zero_rest = di->zero_dsoc - ((di->dsoc + 1) * 1000 - + MIN_ACCURACY); + + /* sm chrg */ + tmp_soc = di->sm_chrg_dsoc / 1000; + if (tmp_soc == di->dsoc) + sm_chrg_rest = di->sm_chrg_dsoc - di->dsoc * 1000; + + /* sm dischrg */ + tmp_soc = (di->sm_dischrg_dsoc) / 1000; + if (tmp_soc == di->dsoc) + sm_dischrg_rest = di->sm_dischrg_dsoc - ((di->dsoc + 1) * 1000 - + MIN_ACCURACY); + + /* last time is also finish chrg, then add last rest */ + if (di->algo_rest_mode == MODE_FINISH && di->algo_rest_val) + finish_rest = base2sec(di->finish_base) + di->algo_rest_val; + else + finish_rest = base2sec(di->finish_base); + + /* total calc */ + if ((rk818_bat_chrg_online(di) && (di->dsoc > di->rsoc)) || + (!rk818_bat_chrg_online(di) && (di->dsoc < di->rsoc)) || + (di->dsoc == di->rsoc)) { + di->algo_rest_val = 0; + algo_rest = 0; + DBG("<%s>. step1..\n", __func__); + } else if (di->work_mode == MODE_FINISH) { + algo_rest = finish_rest; + DBG("<%s>. step2..\n", __func__); + } else if (di->algo_rest_mode == MODE_FINISH) { + algo_rest = zero_rest + sm_dischrg_rest + sm_chrg_rest; + DBG("<%s>. step3..\n", __func__); + } else { + if (rk818_bat_chrg_online(di) && (di->dsoc < di->rsoc)) + algo_rest = sm_chrg_rest + di->algo_rest_val; + else if (!rk818_bat_chrg_online(di) && (di->dsoc > di->rsoc)) + algo_rest = zero_rest + sm_dischrg_rest + + di->algo_rest_val; + else + algo_rest = zero_rest + sm_dischrg_rest + sm_chrg_rest + + di->algo_rest_val; + DBG("<%s>. step4..\n", __func__); + } + + /* check mode */ + if ((di->work_mode == MODE_FINISH) || (di->work_mode == MODE_ZERO)) { + mode = di->work_mode; + } else {/* MODE_SMOOTH */ + if (di->sm_linek > 0) + mode = MODE_SMOOTH_CHRG; + else + mode = MODE_SMOOTH_DISCHRG; + } + + /* save mode */ + buf = rk818_bat_read(di, RK818_MISC_MARK_REG); + buf &= ~ALGO_REST_MODE_MSK; + buf |= (mode << ALGO_REST_MODE_SHIFT); + rk818_bat_write(di, RK818_MISC_MARK_REG, buf); + + /* save rest */ + buf = (algo_rest >> 8) & 0xff; + rk818_bat_write(di, RK818_CALC_REST_REGH, buf); + buf = (algo_rest >> 0) & 0xff; + rk818_bat_write(di, RK818_CALC_REST_REGL, buf); + + DBG("<%s>. rest: algo=%d, mode=%s, last_rest=%d; zero=%d, " + "chrg=%d, dischrg=%d, finish=%lu\n", + __func__, algo_rest, mode_name[mode], di->algo_rest_val, zero_rest, + sm_chrg_rest, sm_dischrg_rest, base2sec(di->finish_base)); +} + +static void rk818_bat_save_data(struct rk818_battery *di) +{ + rk818_bat_save_dsoc(di, di->dsoc); + rk818_bat_save_cap(di, di->remain_cap); + rk818_bat_save_algo_rest(di); +} + +static void rk818_battery_work(struct work_struct *work) +{ + struct rk818_battery *di = + container_of(work, struct rk818_battery, bat_delay_work.work); + + rk818_bat_update_info(di); + rk818_bat_wait_finish_sig(di); + rk818_bat_rsoc_daemon(di); + rk818_bat_update_temperature(di); + rk818_bat_display_smooth(di); + rk818_bat_power_supply_changed(di); + rk818_bat_save_data(di); + rk818_bat_debug_info(di); + + queue_delayed_work(di->bat_monitor_wq, &di->bat_delay_work, + msecs_to_jiffies(di->monitor_ms)); +} + +static irqreturn_t rk818_vb_low_irq(int irq, void *bat) +{ + struct rk818_battery *di = (struct rk818_battery *)bat; + + rk_send_wakeup_key(); + BAT_INFO("lower power yet, power off system! v=%d, c=%d, dsoc=%d\n", + di->voltage_avg, di->current_avg, di->dsoc); + + return IRQ_HANDLED; +} + +static void rk818_bat_init_sysfs(struct rk818_battery *di) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(rk818_bat_attr); i++) { + ret = sysfs_create_file(&di->dev->kobj, + &rk818_bat_attr[i].attr); + if (ret) + dev_err(di->dev, "create bat node(%s) error\n", + rk818_bat_attr[i].attr.name); + } +} + +static int rk818_bat_init_irqs(struct rk818_battery *di) +{ + struct rk808 *rk818 = di->rk818; + struct platform_device *pdev = di->pdev; + int ret, vb_lo_irq; + + vb_lo_irq = regmap_irq_get_virq(rk818->irq_data, RK818_IRQ_VB_LO); + if (vb_lo_irq < 0) { + dev_err(di->dev, "vb_lo_irq request failed!\n"); + return vb_lo_irq; + } + + ret = devm_request_threaded_irq(di->dev, vb_lo_irq, NULL, + rk818_vb_low_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "rk818_vb_low", di); + if (ret) { + dev_err(&pdev->dev, "vb_lo_irq request failed!\n"); + return ret; + } + enable_irq_wake(vb_lo_irq); + + return 0; +} + +static void rk818_bat_init_info(struct rk818_battery *di) +{ + di->design_cap = di->pdata->design_capacity; + di->qmax = di->pdata->design_qmax; + di->bat_res = di->pdata->bat_res; + di->monitor_ms = di->pdata->monitor_sec * TIMER_MS_COUNTS; + di->boot_base = POWER_ON_SEC_BASE; + di->res_div = (di->pdata->sample_res == SAMPLE_RES_20MR) ? + SAMPLE_RES_DIV1 : SAMPLE_RES_DIV2; +} + +static time64_t rk818_get_rtc_sec(void) +{ + int err; + struct rtc_time tm; + struct rtc_device *rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); + + err = rtc_read_time(rtc, &tm); + if (err) { + dev_err(rtc->dev.parent, "read hardware clk failed\n"); + return 0; + } + + err = rtc_valid_tm(&tm); + if (err) { + dev_err(rtc->dev.parent, "invalid date time\n"); + return 0; + } + + return rtc_tm_to_time64(&tm); +} + +static int rk818_bat_rtc_sleep_sec(struct rk818_battery *di) +{ + int interval_sec; + + interval_sec = rk818_get_rtc_sec() - di->rtc_base; + + return (interval_sec > 0) ? interval_sec : 0; +} + +static void rk818_bat_set_shtd_vol(struct rk818_battery *di) +{ + u8 val; + +#if 0 + /* set vbat lowest 3.0v shutdown */ + val = rk818_bat_read(di, RK818_VB_MON_REG); + val &= ~(VBAT_LOW_VOL_MASK | VBAT_LOW_ACT_MASK); + val |= (RK818_VBAT_LOW_3V0 | EN_VABT_LOW_SHUT_DOWN); + rk818_bat_write(di, RK818_VB_MON_REG, val); + + /* disable low irq */ + rk818_bat_set_bits(di, RK818_INT_STS_MSK_REG1, + VB_LOW_INT_EN, VB_LOW_INT_EN); +#endif + + val = rk818_bat_read(di, RK818_VB_MON_REG); + val &= (~(VBAT_LOW_VOL_MASK | VBAT_LOW_ACT_MASK)); + val |= (RK818_VBAT_LOW_3V4 | EN_VBAT_LOW_IRQ); + rk818_bat_write(di, RK818_VB_MON_REG, val); + rk818_bat_set_bits(di, RK818_INT_STS_MSK_REG1, VB_LOW_INT_EN, 0); +} + +static void rk818_bat_init_fg(struct rk818_battery *di) +{ + rk818_bat_enable_gauge(di); + rk818_bat_init_voltage_kb(di); + rk818_bat_init_coffset(di); + rk818_bat_set_relax_sample(di); + rk818_bat_set_ioffset_sample(di); + rk818_bat_set_ocv_sample(di); + rk818_bat_init_ts1_detect(di); + rk818_bat_init_rsoc(di); + rk818_bat_init_coulomb_cap(di, di->nac); + rk818_bat_init_age_algorithm(di); + rk818_bat_init_chrg_config(di); + rk818_bat_set_shtd_vol(di); + rk818_bat_init_zero_table(di); + rk818_bat_init_caltimer(di); + rk818_bat_init_dsoc_algorithm(di); + + di->voltage_avg = rk818_bat_get_avg_voltage(di); + di->voltage_ocv = rk818_bat_get_ocv_voltage(di); + di->voltage_relax = rk818_bat_get_relax_voltage(di); + di->current_avg = rk818_bat_get_avg_current(di); + di->remain_cap = rk818_bat_get_coulomb_cap(di); + di->dbg_pwr_dsoc = di->dsoc; + di->dbg_pwr_rsoc = di->rsoc; + di->dbg_pwr_vol = di->voltage_avg; + + rk818_bat_dump_regs(di, 0x99, 0xee); + DBG("nac=%d cap=%d ov=%d v=%d rv=%d dl=%d rl=%d c=%d\n", + di->nac, di->remain_cap, di->voltage_ocv, di->voltage_avg, + di->voltage_relax, di->dsoc, di->rsoc, di->current_avg); +} + +#ifdef CONFIG_OF +static int rk818_bat_parse_dt(struct rk818_battery *di) +{ + u32 out_value; + int length, ret; + size_t size; + struct device_node *np = di->dev->of_node; + struct battery_platform_data *pdata; + struct device *dev = di->dev; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + di->pdata = pdata; + /* init default param */ + pdata->bat_res = DEFAULT_BAT_RES; + pdata->monitor_sec = DEFAULT_MONITOR_SEC; + pdata->pwroff_vol = DEFAULT_PWROFF_VOL_THRESD; + pdata->sleep_exit_current = DEFAULT_SLP_EXIT_CUR; + pdata->sleep_enter_current = DEFAULT_SLP_ENTER_CUR; + pdata->bat_mode = MODE_BATTARY; + pdata->max_soc_offset = DEFAULT_MAX_SOC_OFFSET; + pdata->sample_res = DEFAULT_SAMPLE_RES; + pdata->energy_mode = DEFAULT_ENERGY_MODE; + pdata->fb_temp = DEFAULT_FB_TEMP; + pdata->zero_reserve_dsoc = DEFAULT_ZERO_RESERVE_DSOC; + + /* parse necessary param */ + if (!of_find_property(np, "ocv_table", &length)) { + dev_err(dev, "ocv_table not found!\n"); + return -EINVAL; + } + + pdata->ocv_size = length / sizeof(u32); + if (pdata->ocv_size <= 0) { + dev_err(dev, "invalid ocv table\n"); + return -EINVAL; + } + + size = sizeof(*pdata->ocv_table) * pdata->ocv_size; + pdata->ocv_table = devm_kzalloc(di->dev, size, GFP_KERNEL); + if (!pdata->ocv_table) + return -ENOMEM; + + ret = of_property_read_u32_array(np, "ocv_table", + pdata->ocv_table, + pdata->ocv_size); + if (ret < 0) + return ret; + + ret = of_property_read_u32(np, "design_capacity", &out_value); + if (ret < 0) { + dev_err(dev, "design_capacity not found!\n"); + return ret; + } + pdata->design_capacity = out_value; + + ret = of_property_read_u32(np, "design_qmax", &out_value); + if (ret < 0) { + dev_err(dev, "design_qmax not found!\n"); + return ret; + } + pdata->design_qmax = out_value; + ret = of_property_read_u32(np, "max_chrg_voltage", &out_value); + if (ret < 0) { + dev_err(dev, "max_chrg_voltage missing!\n"); + return ret; + } + pdata->max_chrg_voltage = out_value; + if (out_value >= 4300) + pdata->zero_algorithm_vol = DEFAULT_ALGR_VOL_THRESD2; + else + pdata->zero_algorithm_vol = DEFAULT_ALGR_VOL_THRESD1; + + ret = of_property_read_u32(np, "fb_temperature", &pdata->fb_temp); + if (ret < 0) + dev_err(dev, "fb_temperature missing!\n"); + + ret = of_property_read_u32(np, "sample_res", &pdata->sample_res); + if (ret < 0) + dev_err(dev, "sample_res missing!\n"); + + ret = of_property_read_u32(np, "energy_mode", &pdata->energy_mode); + if (ret < 0) + dev_err(dev, "energy_mode missing!\n"); + + ret = of_property_read_u32(np, "max_soc_offset", + &pdata->max_soc_offset); + if (ret < 0) + dev_err(dev, "max_soc_offset missing!\n"); + + ret = of_property_read_u32(np, "monitor_sec", &pdata->monitor_sec); + if (ret < 0) + dev_err(dev, "monitor_sec missing!\n"); + + ret = of_property_read_u32(np, "zero_algorithm_vol", + &pdata->zero_algorithm_vol); + if (ret < 0) + dev_err(dev, "zero_algorithm_vol missing!\n"); + + ret = of_property_read_u32(np, "zero_reserve_dsoc", + &pdata->zero_reserve_dsoc); + + ret = of_property_read_u32(np, "virtual_power", &pdata->bat_mode); + if (ret < 0) + dev_err(dev, "virtual_power missing!\n"); + + ret = of_property_read_u32(np, "bat_res", &pdata->bat_res); + if (ret < 0) + dev_err(dev, "bat_res missing!\n"); + + ret = of_property_read_u32(np, "sleep_enter_current", + &pdata->sleep_enter_current); + if (ret < 0) + dev_err(dev, "sleep_enter_current missing!\n"); + + ret = of_property_read_u32(np, "sleep_exit_current", + &pdata->sleep_exit_current); + if (ret < 0) + dev_err(dev, "sleep_exit_current missing!\n"); + + ret = of_property_read_u32(np, "power_off_thresd", &pdata->pwroff_vol); + if (ret < 0) + dev_err(dev, "power_off_thresd missing!\n"); + + if (!of_find_property(np, "ntc_table", &length)) { + pdata->ntc_size = 0; + } else { + /* get ntc degree base value */ + ret = of_property_read_u32_index(np, "ntc_degree_from", 1, + &pdata->ntc_degree_from); + if (ret) { + dev_err(dev, "invalid ntc_degree_from\n"); + return -EINVAL; + } + + of_property_read_u32_index(np, "ntc_degree_from", 0, + &out_value); + if (out_value) + pdata->ntc_degree_from = -pdata->ntc_degree_from; + + pdata->ntc_size = length / sizeof(u32); + } + + if (pdata->ntc_size) { + size = sizeof(*pdata->ntc_table) * pdata->ntc_size; + pdata->ntc_table = devm_kzalloc(di->dev, size, GFP_KERNEL); + if (!pdata->ntc_table) + return -ENOMEM; + + ret = of_property_read_u32_array(np, "ntc_table", + pdata->ntc_table, + pdata->ntc_size); + if (ret < 0) + return ret; + } + + DBG("the battery dts info dump:\n" + "bat_res:%d\n" + "design_capacity:%d\n" + "design_qmax :%d\n" + "sleep_enter_current:%d\n" + "sleep_exit_current:%d\n" + "zero_algorithm_vol:%d\n" + "zero_reserve_dsoc:%d\n" + "monitor_sec:%d\n" + "max_soc_offset:%d\n" + "virtual_power:%d\n" + "pwroff_vol:%d\n" + "sample_res:%d\n" + "ntc_size=%d\n" + "ntc_degree_from:%d\n" + "ntc_degree_to:%d\n", + pdata->bat_res, pdata->design_capacity, pdata->design_qmax, + pdata->sleep_enter_current, pdata->sleep_exit_current, + pdata->zero_algorithm_vol, pdata->zero_reserve_dsoc, + pdata->monitor_sec, + pdata->max_soc_offset, pdata->bat_mode, pdata->pwroff_vol, + pdata->sample_res, pdata->ntc_size, pdata->ntc_degree_from, + pdata->ntc_degree_from + pdata->ntc_size - 1 + ); + + return 0; +} +#else +static int rk818_bat_parse_dt(struct rk818_battery *di) +{ + return -ENODEV; +} +#endif + +static const struct of_device_id rk818_battery_of_match[] = { + { .compatible = "rockchip,rk818-battery", }, + { }, +}; + +static struct rk818_battery* bat; + +struct rk818_battery* rk818_battery_get(void) +{ + return bat; +} +EXPORT_SYMBOL_GPL(rk818_battery_get); + +static int rk818_battery_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(rk818_battery_of_match, &pdev->dev); + struct rk818_battery *di; + struct rk808 *rk818 = dev_get_drvdata(pdev->dev.parent); + int ret; + + if (!of_id) { + dev_err(&pdev->dev, "Failed to find matching dt id\n"); + return -ENODEV; + } + + di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); + if (!di) + return -ENOMEM; + + di->rk818 = rk818; + di->pdev = pdev; + di->dev = &pdev->dev; + di->regmap = rk818->regmap; + platform_set_drvdata(pdev, di); + + ret = rk818_bat_parse_dt(di); + if (ret < 0) { + dev_err(di->dev, "rk818 battery parse dt failed!\n"); + return ret; + } + + if (!is_rk818_bat_exist(di)) { + di->pdata->bat_mode = MODE_VIRTUAL; + dev_err(di->dev, "no battery, virtual power mode\n"); + } + + ret = rk818_bat_init_irqs(di); + if (ret != 0) { + dev_err(di->dev, "rk818 bat init irqs failed!\n"); + return ret; + } + + /* + ret = rk818_bat_init_power_supply(di); + if (ret) { + dev_err(di->dev, "rk818 power supply register failed!\n"); + return ret; + } + */ + + rk818_bat_init_info(di); + rk818_bat_init_fg(di); + rk818_bat_init_sysfs(di); + //wake_lock_init(&di->wake_lock, WAKE_LOCK_SUSPEND, "rk818_bat_lock"); + di->bat_monitor_wq = alloc_ordered_workqueue("%s", + WQ_MEM_RECLAIM | WQ_FREEZABLE, "rk818-bat-monitor-wq"); + INIT_DELAYED_WORK(&di->bat_delay_work, rk818_battery_work); + queue_delayed_work(di->bat_monitor_wq, &di->bat_delay_work, + msecs_to_jiffies(TIMER_MS_COUNTS * 5)); + + BAT_INFO("driver version %s\n", DRIVER_VERSION); + + bat = di; + return ret; +} + +static int rk818_battery_suspend(struct platform_device *dev, + pm_message_t state) +{ + struct rk818_battery *di = platform_get_drvdata(dev); + u8 val, st; + + cancel_delayed_work_sync(&di->bat_delay_work); + + di->s2r = false; + di->sleep_chrg_online = rk818_bat_chrg_online(di); + di->sleep_chrg_status = rk818_bat_get_chrg_status(di); + di->current_avg = rk818_bat_get_avg_current(di); + di->remain_cap = rk818_bat_get_coulomb_cap(di); + di->rsoc = rk818_bat_get_rsoc(di); + di->rtc_base = rk818_get_rtc_sec(); + rk818_bat_save_data(di); + st = (rk818_bat_read(di, RK818_SUP_STS_REG) & CHRG_STATUS_MSK) >> 4; + + /* if not CHARGE_FINISH, reinit finish_base. + * avoid sleep loop between suspend and resume + */ + if (di->sleep_chrg_status != CHARGE_FINISH) + di->finish_base = get_boot_sec(); + + /* avoid: enter suspend from MODE_ZERO: load from heavy to light */ + if ((di->work_mode == MODE_ZERO) && + (di->sleep_chrg_online) && (di->current_avg >= 0)) { + DBG("suspend: MODE_ZERO exit...\n"); + /* it need't do prepare for mode finish and smooth, it will + * be done in display_smooth + */ + if (di->sleep_chrg_status == CHARGE_FINISH) { + di->work_mode = MODE_FINISH; + di->finish_base = get_boot_sec(); + } else { + di->work_mode = MODE_SMOOTH; + rk818_bat_smooth_algo_prepare(di); + } + } + + /* set vbat low than 3.4v to generate a wakeup irq */ + val = rk818_bat_read(di, RK818_VB_MON_REG); + val &= (~(VBAT_LOW_VOL_MASK | VBAT_LOW_ACT_MASK)); + val |= (RK818_VBAT_LOW_3V4 | EN_VBAT_LOW_IRQ); + rk818_bat_write(di, RK818_VB_MON_REG, val); + rk818_bat_set_bits(di, RK818_INT_STS_MSK_REG1, VB_LOW_INT_EN, 0); + + BAT_INFO("suspend: dl=%d rl=%d c=%d v=%d cap=%d at=%ld ch=%d st=%s\n", + di->dsoc, di->rsoc, di->current_avg, + rk818_bat_get_avg_voltage(di), rk818_bat_get_coulomb_cap(di), + di->sleep_dischrg_sec, di->sleep_chrg_online, bat_status[st]); + + return 0; +} + +static int rk818_battery_resume(struct platform_device *dev) +{ + struct rk818_battery *di = platform_get_drvdata(dev); + int interval_sec, time_step, pwroff_vol; + u8 val, st; + + di->s2r = true; + di->current_avg = rk818_bat_get_avg_current(di); + di->voltage_relax = rk818_bat_get_relax_voltage(di); + di->voltage_avg = rk818_bat_get_avg_voltage(di); + di->remain_cap = rk818_bat_get_coulomb_cap(di); + di->rsoc = rk818_bat_get_rsoc(di); + interval_sec = rk818_bat_rtc_sleep_sec(di); + di->sleep_sum_sec += interval_sec; + pwroff_vol = di->pdata->pwroff_vol; + st = (rk818_bat_read(di, RK818_SUP_STS_REG) & CHRG_STATUS_MSK) >> 4; + + if (!di->sleep_chrg_online) { + /* only add up discharge sleep seconds */ + di->sleep_dischrg_sec += interval_sec; + if (di->voltage_avg <= pwroff_vol + 50) + time_step = DISCHRG_TIME_STEP1; + else + time_step = DISCHRG_TIME_STEP2; + } + + BAT_INFO("resume: dl=%d rl=%d c=%d v=%d rv=%d " + "cap=%d dt=%d at=%ld ch=%d st=%s\n", + di->dsoc, di->rsoc, di->current_avg, di->voltage_avg, + di->voltage_relax, rk818_bat_get_coulomb_cap(di), interval_sec, + di->sleep_dischrg_sec, di->sleep_chrg_online, bat_status[st]); + + /* sleep: enough time and discharge */ + if ((di->sleep_dischrg_sec > time_step) && (!di->sleep_chrg_online)) { + if (rk818_bat_sleep_dischrg(di)) + di->sleep_dischrg_sec = 0; + } + + rk818_bat_save_data(di); + + /* set vbat lowest 3.0v shutdown */ + rk818_bat_set_shtd_vol(di); + + /* charge/lowpower lock: for battery work to update dsoc and rsoc */ + // if ((di->sleep_chrg_online) || + // (!di->sleep_chrg_online && di->voltage_avg < di->pdata->pwroff_vol)) + // wake_lock_timeout(&di->wake_lock, msecs_to_jiffies(2000)); + + queue_delayed_work(di->bat_monitor_wq, &di->bat_delay_work, + msecs_to_jiffies(1000)); + + return 0; +} + +static void rk818_battery_shutdown(struct platform_device *dev) +{ + u8 cnt = 0; + struct rk818_battery *di = platform_get_drvdata(dev); + + cancel_delayed_work_sync(&di->bat_delay_work); + cancel_delayed_work_sync(&di->calib_delay_work); + del_timer(&di->caltimer); + if (base2sec(di->boot_base) < REBOOT_PERIOD_SEC) + cnt = rk818_bat_check_reboot(di); + else + rk818_bat_save_reboot_cnt(di, 0); + + BAT_INFO("shutdown: dl=%d rl=%d c=%d v=%d cap=%d f=%d ch=%d n=%d " + "mode=%d rest=%d\n", + di->dsoc, di->rsoc, di->current_avg, di->voltage_avg, + di->remain_cap, di->fcc, rk818_bat_chrg_online(di), cnt, + di->algo_rest_mode, di->algo_rest_val); +} + +static struct platform_driver rk818_battery_driver = { + .probe = rk818_battery_probe, + .suspend = rk818_battery_suspend, + .resume = rk818_battery_resume, + .shutdown = rk818_battery_shutdown, + .driver = { + .name = "rk818-battery", + .of_match_table = rk818_battery_of_match, + }, +}; + +static int __init battery_init(void) +{ + return platform_driver_register(&rk818_battery_driver); +} +fs_initcall_sync(battery_init); + +static void __exit battery_exit(void) +{ + platform_driver_unregister(&rk818_battery_driver); +} +module_exit(battery_exit); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rk818-battery"); +MODULE_AUTHOR("chenjh"); diff -rupN linux.orig/drivers/power/supply/rk818_battery.h linux/drivers/power/supply/rk818_battery.h --- linux.orig/drivers/power/supply/rk818_battery.h 1970-01-01 00:00:00.000000000 +0000 +++ linux/drivers/power/supply/rk818_battery.h 2023-09-12 12:03:27.490291628 +0000 @@ -0,0 +1,168 @@ +/* + * rk818_battery.h: fuel gauge driver structures + * + * Copyright (C) 2016 Rockchip Electronics Co., Ltd + * Author: chenjh + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#ifndef RK818_BATTERY +#define RK818_BATTERY + +/* RK818_INT_STS_MSK_REG2 */ +#define PLUG_IN_MSK BIT(0) +#define PLUG_OUT_MSK BIT(1) +#define CHRG_CVTLMT_INT_MSK BIT(6) + +/* RK818_TS_CTRL_REG */ +#define GG_EN BIT(7) +#define ADC_CUR_EN BIT(6) +#define ADC_TS1_EN BIT(5) +#define ADC_TS2_EN BIT(4) +#define TS1_CUR_MSK 0x03 + +/* RK818_GGCON */ +#define OCV_SAMP_MIN_MSK 0x0c +#define OCV_SAMP_8MIN (0x00 << 2) + +#define ADC_CAL_MIN_MSK 0x30 +#define ADC_CAL_8MIN (0x00 << 4) +#define ADC_CUR_MODE BIT(1) + +/* RK818_GGSTS */ +#define BAT_CON BIT(4) +#define RELAX_VOL1_UPD BIT(3) +#define RELAX_VOL2_UPD BIT(2) +#define RELAX_VOL12_UPD_MSK (RELAX_VOL1_UPD | RELAX_VOL2_UPD) + +/* RK818_SUP_STS_REG */ +#define CHRG_STATUS_MSK 0x70 +#define BAT_EXS BIT(7) +#define CHARGE_OFF (0x0 << 4) +#define DEAD_CHARGE (0x1 << 4) +#define TRICKLE_CHARGE (0x2 << 4) +#define CC_OR_CV (0x3 << 4) +#define CHARGE_FINISH (0x4 << 4) +#define USB_OVER_VOL (0x5 << 4) +#define BAT_TMP_ERR (0x6 << 4) +#define TIMER_ERR (0x7 << 4) +#define USB_VLIMIT_EN BIT(3) +#define USB_CLIMIT_EN BIT(2) +#define USB_EXIST BIT(1) +#define USB_EFF BIT(0) + +/* RK818_USB_CTRL_REG */ +#define CHRG_CT_EN BIT(7) +#define FINISH_CUR_MSK 0xc0 +#define TEMP_105C (0x02 << 2) +#define FINISH_100MA (0x00 << 6) +#define FINISH_150MA (0x01 << 6) +#define FINISH_200MA (0x02 << 6) +#define FINISH_250MA (0x03 << 6) + +/* RK818_CHRG_CTRL_REG3 */ +#define CHRG_TERM_MODE_MSK BIT(5) +#define CHRG_TERM_ANA_SIGNAL (0 << 5) +#define CHRG_TERM_DIG_SIGNAL BIT(5) +#define CHRG_TIMER_CCCV_EN BIT(2) +#define CHRG_EN BIT(7) + +/* RK818_VB_MON_REG */ +#define RK818_VBAT_LOW_3V0 0x02 +#define RK818_VBAT_LOW_3V4 0x06 +#define PLUG_IN_STS BIT(6) + +/* RK818_THERMAL_REG */ +#define FB_TEMP_MSK 0x0c +#define HOTDIE_STS BIT(1) + +/* RK818_INT_STS_MSK_REG1 */ +#define VB_LOW_INT_EN BIT(1) + +/* RK818_MISC_MARK_REG */ +#define FG_INIT BIT(5) +#define FG_RESET_LATE BIT(4) +#define FG_RESET_NOW BIT(3) +#define ALGO_REST_MODE_MSK (0xc0) +#define ALGO_REST_MODE_SHIFT 6 + +/* bit shift */ +#define FB_TEMP_SHIFT 2 + +/* parse ocv table param */ +#define TIMER_MS_COUNTS 1000 +#define MAX_PERCENTAGE 100 +#define MAX_INTERPOLATE 1000 +#define MAX_INT 0x7FFF + +#define DRIVER_VERSION "7.1" + +struct battery_platform_data { + u32 *ocv_table; + u32 *zero_table; + u32 *ntc_table; + u32 ocv_size; + u32 max_chrg_voltage; + u32 ntc_size; + int ntc_degree_from; + u32 pwroff_vol; + u32 monitor_sec; + u32 zero_algorithm_vol; + u32 zero_reserve_dsoc; + u32 bat_res; + u32 design_capacity; + u32 design_qmax; + u32 sleep_enter_current; + u32 sleep_exit_current; + u32 max_soc_offset; + u32 sample_res; + u32 bat_mode; + u32 fb_temp; + u32 energy_mode; + u32 cccv_hour; + u32 ntc_uA; + u32 ntc_factor; +}; + +enum work_mode { + MODE_ZERO = 0, + MODE_FINISH, + MODE_SMOOTH_CHRG, + MODE_SMOOTH_DISCHRG, + MODE_SMOOTH, +}; + +enum bat_mode { + MODE_BATTARY = 0, + MODE_VIRTUAL, +}; + +static const u16 feedback_temp_array[] = { + 85, 95, 105, 115 +}; + +static const u16 chrg_vol_sel_array[] = { + 4050, 4100, 4150, 4200, 4250, 4300, 4350 +}; + +static const u16 chrg_cur_sel_array[] = { + 1000, 1200, 1400, 1600, 1800, 2000, 2250, 2400, 2600, 2800, 3000 +}; + +static const u16 chrg_cur_input_array[] = { + 450, 80, 850, 1000, 1250, 1500, 1750, 2000, 2250, 2500, 2750, 3000 +}; + +void kernel_power_off(void); +int rk818_bat_temp_notifier_register(struct notifier_block *nb); +int rk818_bat_temp_notifier_unregister(struct notifier_block *nb); + +#endif diff -rupN linux.orig/drivers/power/supply/rk818_charger.c linux/drivers/power/supply/rk818_charger.c --- linux.orig/drivers/power/supply/rk818_charger.c 1970-01-01 00:00:00.000000000 +0000 +++ linux/drivers/power/supply/rk818_charger.c 2023-09-12 12:03:27.490291628 +0000 @@ -0,0 +1,726 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * rk818 usb power driver + * + * Copyright (c) 2021 Ondřej Jirman + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define RK818_CHG_STS_MASK (7u << 4) /* charger status */ +#define RK818_CHG_STS_NONE (0u << 4) +#define RK818_CHG_STS_WAKEUP_CUR (1u << 4) +#define RK818_CHG_STS_TRICKLE_CUR (2u << 4) +#define RK818_CHG_STS_CC_OR_CV (3u << 4) +#define RK818_CHG_STS_TERMINATED (4u << 4) +#define RK818_CHG_STS_USB_OV (5u << 4) +#define RK818_CHG_STS_BAT_TEMP_FAULT (6u << 4) +#define RK818_CHG_STS_TIMEOUT (7u << 4) + +/* RK818_SUP_STS_REG */ +#define RK818_SUP_STS_USB_VLIM_EN BIT(3) /* input voltage limit enable */ +#define RK818_SUP_STS_USB_ILIM_EN BIT(2) /* input current limit enable */ +#define RK818_SUP_STS_USB_EXS BIT(1) /* USB power connected */ +#define RK818_SUP_STS_USB_EFF BIT(0) /* USB fault */ + +/* RK818_USB_CTRL_REG */ +#define RK818_USB_CTRL_USB_ILIM_MASK (0xfu) +#define RK818_USB_CTRL_USB_CHG_SD_VSEL_OFFSET 4 +#define RK818_USB_CTRL_USB_CHG_SD_VSEL_MASK (0x7u << 4) + +/* RK818_CHRG_CTRL_REG1 */ +#define RK818_CHRG_CTRL_REG1_CHRG_EN BIT(7) +#define RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_OFFSET 4 +#define RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_MASK (0x7u << 4) +#define RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_OFFSET 0 +#define RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_MASK (0xfu << 0) + +/* RK818_CHRG_CTRL_REG3 */ +#define RK818_CHRG_CTRL_REG3_CHRG_TERM_DIGITAL BIT(5) + +struct rk818_charger { + struct device *dev; + struct rk808 *rk818; + struct regmap *regmap; + + struct power_supply *usb_psy; + struct power_supply *charger_psy; + + bool apply_ilim; +}; + +// {{{ USB supply + +static int rk818_usb_set_input_current_max(struct rk818_charger *cg, + int val) +{ + int ret; + unsigned reg; + + if (val < 450000) + reg = 1; + else if (val < 850000) + reg = 0; + else if (val < 1000000) + reg = 2; + else if (val < 3000000) + reg = 3 + (val - 1000000) / 250000; + else + reg = 11; + + dev_info(cg->dev, "applying input current limit %d mA\n", val / 1000); + + ret = regmap_update_bits(cg->regmap, RK818_USB_CTRL_REG, + RK818_USB_CTRL_USB_ILIM_MASK, reg); + if (ret) + dev_err(cg->dev, + "USB input current limit setting failed (%d)\n", ret); + + return ret; +} + +static int rk818_usb_get_input_current_max(struct rk818_charger *cg, + int *val) +{ + unsigned reg; + int ret; + + ret = regmap_read(cg->regmap, RK818_USB_CTRL_REG, ®); + if (ret) { + dev_err(cg->dev, + "USB input current limit getting failed (%d)\n", ret); + return ret; + } + + reg &= RK818_USB_CTRL_USB_ILIM_MASK; + if (reg == 0) + *val = 450000; + else if (reg == 1) + *val = 80000; + else if (reg == 2) + *val = 850000; + else if (reg < 11) + *val = 1000000 + (reg - 3) * 250000; + else + *val = 3000000; + + return 0; +} + +static int rk818_usb_set_input_voltage_min(struct rk818_charger *cg, + int val) +{ + unsigned reg; + int ret; + + if (val < 2780000) + reg = 0; + else if (val < 3270000) + reg = (val - 2780000) / 70000; + else + reg = 7; + + ret = regmap_update_bits(cg->regmap, RK818_USB_CTRL_REG, + RK818_USB_CTRL_USB_CHG_SD_VSEL_MASK, + reg << RK818_USB_CTRL_USB_CHG_SD_VSEL_OFFSET); + if (ret) + dev_err(cg->dev, + "USB input voltage limit setting failed (%d)\n", ret); + + return ret; +} + +static int rk818_usb_get_input_voltage_min(struct rk818_charger *cg, + int *val) +{ + unsigned reg; + int ret; + + ret = regmap_read(cg->regmap, RK818_USB_CTRL_REG, ®); + if (ret) { + dev_err(cg->dev, + "USB input voltage limit getting failed (%d)\n", ret); + return ret; + } + + reg &= RK818_USB_CTRL_USB_CHG_SD_VSEL_MASK; + reg >>= RK818_USB_CTRL_USB_CHG_SD_VSEL_OFFSET; + + *val = 2780000 + (reg * 70000); + + return 0; +} + +static int rk818_usb_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rk818_charger *cg = power_supply_get_drvdata(psy); + unsigned reg; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); + if (ret) + return ret; + + val->intval = !!(reg & RK818_SUP_STS_USB_EXS); + break; + + case POWER_SUPPLY_PROP_HEALTH: + ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); + if (ret) + return ret; + + if (!(reg & RK818_SUP_STS_USB_EXS)) { + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + } else if (reg & RK818_SUP_STS_USB_EFF) { + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } else { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + } + + break; + + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + return rk818_usb_get_input_voltage_min(cg, &val->intval); + + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return rk818_usb_get_input_current_max(cg, &val->intval); + + default: + return -EINVAL; + } + + return 0; +} + +static int rk818_usb_power_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct rk818_charger *cg = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + return rk818_usb_set_input_voltage_min(cg, val->intval); + + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return rk818_usb_set_input_current_max(cg, val->intval); + + default: + return -EINVAL; + } +} + +static int rk818_usb_power_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + return 1; + + default: + return 0; + } +} + +/* Sync the input-current-limit with our parent supply (if we have one) */ +static void rk818_usb_power_external_power_changed(struct power_supply *psy) +{ + struct rk818_charger *cg = power_supply_get_drvdata(psy); + union power_supply_propval val; + int ret; + + ret = power_supply_get_property_from_supplier(cg->usb_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, + &val); + if (ret) + return; + + /* + * We only want to start applying input current limit after we get first + * non-0 value from the supplier. Until then, we keep the limit applied + * by the bootloader. If we lower the limit before the charger is properly + * detected, we risk boot failure due to insufficient power. + */ + if (!cg->apply_ilim) { + if (!val.intval) + return; + + cg->apply_ilim = true; + } + + if (val.intval < 500000) + val.intval = 500000; + + rk818_usb_power_set_property(cg->usb_psy, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + &val); +} + +static enum power_supply_property rk818_usb_power_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, +}; + +static const struct power_supply_desc rk818_usb_desc = { + .name = "rk818-usb-charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = rk818_usb_power_props, + .num_properties = ARRAY_SIZE(rk818_usb_power_props), + .property_is_writeable = rk818_usb_power_prop_writeable, + .get_property = rk818_usb_power_get_property, + .set_property = rk818_usb_power_set_property, + .external_power_changed = rk818_usb_power_external_power_changed, +}; + +// }}} +// {{{ Charger supply + +static int rk818_charger_set_current_max(struct rk818_charger *cg, int val) +{ + unsigned reg; + int ret; + + if (val < 1000000) + reg = 0; + else if (val < 3000000) + reg = (val - 1000000) / 200000; + else + reg = 10; + + ret = regmap_update_bits(cg->regmap, RK818_CHRG_CTRL_REG1, + RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_MASK, + reg << RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_OFFSET); + if (ret) + dev_err(cg->dev, + "Charging max current setting failed (%d)\n", ret); + + return ret; +} + +static int rk818_charger_get_current_max(struct rk818_charger *cg, int *val) +{ + unsigned reg; + int ret; + + ret = regmap_read(cg->regmap, RK818_CHRG_CTRL_REG1, ®); + if (ret) { + dev_err(cg->dev, + "Charging max current getting failed (%d)\n", ret); + return ret; + } + + reg &= RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_MASK; + reg >>= RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_OFFSET; + + *val = 1000000 + reg * 200000; + + return 0; +} + +static int rk818_charger_set_voltage_max(struct rk818_charger *cg, int val) +{ + unsigned reg; + int ret; + + if (val < 4050000) + reg = 0; + else if (val < 4350000) + reg = (val - 4050000) / 50000; + else + reg = 6; + + ret = regmap_update_bits(cg->regmap, RK818_CHRG_CTRL_REG1, + RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_MASK, + reg << RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_OFFSET); + if (ret) + dev_err(cg->dev, + "Charging end voltage setting failed (%d)\n", ret); + + return ret; +} + +static int rk818_charger_get_voltage_max(struct rk818_charger *cg, int *val) +{ + unsigned reg; + int ret; + + ret = regmap_read(cg->regmap, RK818_CHRG_CTRL_REG1, ®); + if (ret) { + dev_err(cg->dev, + "Charging end voltage getting failed (%d)\n", ret); + return ret; + } + + reg &= RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_MASK; + reg >>= RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_OFFSET; + + *val = 4050000 + reg * 50000; + + return 0; +} + +struct rk818_battery; +struct rk818_battery* rk818_battery_get(void); +int rk818_battery_get_property(struct rk818_battery *di, + enum power_supply_property psp, + union power_supply_propval *val); + +static int rk818_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rk818_charger *cg = power_supply_get_drvdata(psy); + struct rk818_battery* di = rk818_battery_get(); + unsigned reg; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_TEMP: + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (!di) + return -ENODEV; + return rk818_battery_get_property(di, psp, val); + default:; + } + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = regmap_read(cg->regmap, RK818_CHRG_CTRL_REG1, ®); + if (ret) { + dev_err(cg->dev, "failed to read the charger state (%d)\n", ret); + return ret; + } + + val->intval = !!(reg & RK818_CHRG_CTRL_REG1_CHRG_EN); + break; + + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + ret = regmap_read(cg->regmap, RK818_CHRG_CTRL_REG1, ®); + if (ret) { + dev_err(cg->dev, "failed to read the charger state (%d)\n", ret); + return ret; + } + + if (reg & RK818_CHRG_CTRL_REG1_CHRG_EN) + val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; + else + val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; + + return 0; + + case POWER_SUPPLY_PROP_STATUS: + ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); + if (ret) + return ret; + + switch (reg & RK818_CHG_STS_MASK) { + case RK818_CHG_STS_WAKEUP_CUR: + case RK818_CHG_STS_TRICKLE_CUR: + case RK818_CHG_STS_CC_OR_CV: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case RK818_CHG_STS_TERMINATED: + default: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + + break; + + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); + if (ret) + return ret; + + switch (reg & RK818_CHG_STS_MASK) { + case RK818_CHG_STS_WAKEUP_CUR: + case RK818_CHG_STS_TRICKLE_CUR: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case RK818_CHG_STS_CC_OR_CV: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case RK818_CHG_STS_TERMINATED: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + default: + val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + break; + } + + break; + + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + ret = regmap_read(cg->regmap, RK818_CHRG_CTRL_REG2, ®); + if (ret) + return ret; + + val->intval = 100000 + ((reg >> 6) & 3) * 50000; + break; + + case POWER_SUPPLY_PROP_HEALTH: + ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); + if (ret) + return ret; + + switch (reg & RK818_CHG_STS_MASK) { + case RK818_CHG_STS_USB_OV: + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + case RK818_CHG_STS_BAT_TEMP_FAULT: + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + case RK818_CHG_STS_TIMEOUT: + val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + break; + default: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + } + + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + return rk818_charger_get_voltage_max(cg, &val->intval); + + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + ret = rk818_charger_get_current_max(cg, &val->intval); + val->intval /= 10; + return ret; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + return rk818_charger_get_current_max(cg, &val->intval); + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = 4350000; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = 3000000; + break; + + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + val->intval = 11400000; + return 0; + + case POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN: + val->intval = 0; + return 0; + + default: + return -EINVAL; + } + + return 0; +} + +static int rk818_charger_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct rk818_charger *cg = power_supply_get_drvdata(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + switch (val->intval) { + case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: + return regmap_update_bits(cg->regmap, RK818_CHRG_CTRL_REG1, + RK818_CHRG_CTRL_REG1_CHRG_EN, + RK818_CHRG_CTRL_REG1_CHRG_EN); + case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: + return regmap_update_bits(cg->regmap, RK818_CHRG_CTRL_REG1, + RK818_CHRG_CTRL_REG1_CHRG_EN, 0); + default: + return -EINVAL; + } + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + return rk818_charger_set_voltage_max(cg, val->intval); + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + return rk818_charger_set_current_max(cg, val->intval); + + default: + return -EINVAL; + } +} + +static int rk818_charger_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + return 1; + + default: + return 0; + } +} + +static enum power_supply_property rk818_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_PRECHARGE_CURRENT, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN, + + // inherited from BSP battery driver + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CHARGE_FULL, +}; + +/* + * We import some capacity tracking functionality from the BSP battery driver. + * Some poor soul will have to understand and clean up the BSP battery driver, + * but not me, not now. :) + */ +static const struct power_supply_desc rk818_charger_desc = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = rk818_charger_props, + .num_properties = ARRAY_SIZE(rk818_charger_props), + .property_is_writeable = rk818_charger_prop_writeable, + .get_property = rk818_charger_get_property, + .set_property = rk818_charger_set_property, +}; + +// }}} + +static int rk818_charger_probe(struct platform_device *pdev) +{ + struct rk808 *rk818 = dev_get_drvdata(pdev->dev.parent); + struct power_supply_config psy_cfg = { }; + struct device *dev = &pdev->dev; + struct rk818_charger *cg; + int ret; + + cg = devm_kzalloc(dev, sizeof(*cg), GFP_KERNEL); + if (!cg) + return -ENOMEM; + + cg->rk818 = rk818; + cg->dev = dev; + cg->regmap = rk818->regmap; + platform_set_drvdata(pdev, cg); + + psy_cfg.drv_data = cg; + psy_cfg.of_node = dev->of_node; + + cg->usb_psy = devm_power_supply_register(dev, &rk818_usb_desc, + &psy_cfg); + if (IS_ERR(cg->usb_psy)) + return dev_err_probe(dev, PTR_ERR(cg->usb_psy), + "register usb power supply fail\n"); + + cg->charger_psy = devm_power_supply_register(dev, &rk818_charger_desc, + &psy_cfg); + if (IS_ERR(cg->charger_psy)) + return dev_err_probe(dev, PTR_ERR(cg->charger_psy), + "register charger power supply fail\n"); + + /* disable voltage limit and enable input current limit */ + ret = regmap_update_bits(cg->regmap, RK818_SUP_STS_REG, + RK818_SUP_STS_USB_ILIM_EN | RK818_SUP_STS_USB_VLIM_EN, + RK818_SUP_STS_USB_ILIM_EN); + if (ret) + dev_warn(cg->dev, "failed to enable input current limit (%d)\n", ret); + + /* make sure analog control loop is enabled */ + ret = regmap_update_bits(cg->regmap, RK818_CHRG_CTRL_REG3, + RK818_CHRG_CTRL_REG3_CHRG_TERM_DIGITAL, + 0); + if (ret) + dev_warn(cg->dev, "failed to enable analog control loop (%d)\n", ret); + + /* enable charger and set some reasonable limits on each boot */ + ret = regmap_write(cg->regmap, RK818_CHRG_CTRL_REG1, + RK818_CHRG_CTRL_REG1_CHRG_EN + | (1) /* 1.2A */ + | (5 << 4) /* 4.3V */); + if (ret) + dev_warn(cg->dev, "failed to enable charger (%d)\n", ret); + + rk818_usb_power_external_power_changed(cg->usb_psy); + + return 0; +} + +static int rk818_charger_remove(struct platform_device *pdev) +{ + //struct rk818_charger *cg = platform_get_drvdata(pdev); + + return 0; +} + +static void rk818_charger_shutdown(struct platform_device *pdev) +{ +} + +static int rk818_charger_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return 0; +} + +static int rk818_charger_resume(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id rk818_charger_of_match[] = { + { .compatible = "rockchip,rk818-charger", }, + { }, +}; + +static struct platform_driver rk818_charger_driver = { + .probe = rk818_charger_probe, + .remove = rk818_charger_remove, + .suspend = rk818_charger_suspend, + .resume = rk818_charger_resume, + .shutdown = rk818_charger_shutdown, + .driver = { + .name = "rk818-charger", + .of_match_table = rk818_charger_of_match, + }, +}; + +module_platform_driver(rk818_charger_driver); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rk818-charger"); +MODULE_AUTHOR("Ondřej Jirman "); diff -rupN linux.orig/include/linux/input-polldev.h linux/include/linux/input-polldev.h --- linux.orig/include/linux/input-polldev.h 1970-01-01 00:00:00.000000000 +0000 +++ linux/include/linux/input-polldev.h 2023-09-12 12:03:27.490291628 +0000 @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _INPUT_POLLDEV_H +#define _INPUT_POLLDEV_H + +/* + * Copyright (c) 2007 Dmitry Torokhov + */ + +#include +#include + +/** + * struct input_polled_dev - simple polled input device + * @private: private driver data. + * @open: driver-supplied method that prepares device for polling + * (enabled the device and maybe flushes device state). + * @close: driver-supplied method that is called when device is no + * longer being polled. Used to put device into low power mode. + * @poll: driver-supplied method that polls the device and posts + * input events (mandatory). + * @poll_interval: specifies how often the poll() method should be called. + * Defaults to 500 msec unless overridden when registering the device. + * @poll_interval_max: specifies upper bound for the poll interval. + * Defaults to the initial value of @poll_interval. + * @poll_interval_min: specifies lower bound for the poll interval. + * Defaults to 0. + * @input: input device structure associated with the polled device. + * Must be properly initialized by the driver (id, name, phys, bits). + * + * Polled input device provides a skeleton for supporting simple input + * devices that do not raise interrupts but have to be periodically + * scanned or polled to detect changes in their state. + */ +struct input_polled_dev { + void *private; + + void (*open)(struct input_polled_dev *dev); + void (*close)(struct input_polled_dev *dev); + void (*poll)(struct input_polled_dev *dev); + unsigned int poll_interval; /* msec */ + unsigned int poll_interval_max; /* msec */ + unsigned int poll_interval_min; /* msec */ + + struct input_dev *input; + +/* private: */ + struct delayed_work work; + + bool devres_managed; +}; + +struct input_polled_dev *input_allocate_polled_device(void); +struct input_polled_dev *devm_input_allocate_polled_device(struct device *dev); +void input_free_polled_device(struct input_polled_dev *dev); +int input_register_polled_device(struct input_polled_dev *dev); +void input_unregister_polled_device(struct input_polled_dev *dev); + +#endif diff -rupN linux.orig/include/linux/mfd/rk808.h linux/include/linux/mfd/rk808.h --- linux.orig/include/linux/mfd/rk808.h 2023-09-12 12:02:59.205653098 +0000 +++ linux/include/linux/mfd/rk808.h 2023-09-12 12:03:27.490291628 +0000 @@ -138,6 +138,8 @@ enum rk818_reg { RK818_ID_OTG_SWITCH, }; +#define RK818_VB_MON_REG 0x21 +#define RK818_THERMAL_REG 0x22 #define RK818_DCDC_EN_REG 0x23 #define RK818_LDO_EN_REG 0x24 #define RK818_SLEEP_SET_OFF_REG1 0x25 @@ -184,13 +186,90 @@ enum rk818_reg { #define RK818_INT_STS_REG2 0x4e #define RK818_INT_STS_MSK_REG2 0x4f #define RK818_IO_POL_REG 0x50 +#define RK818_OTP_VDD_EN_REG 0x51 #define RK818_H5V_EN_REG 0x52 #define RK818_SLEEP_SET_OFF_REG3 0x53 #define RK818_BOOST_LDO9_ON_VSEL_REG 0x54 #define RK818_BOOST_LDO9_SLP_VSEL_REG 0x55 #define RK818_BOOST_CTRL_REG 0x56 -#define RK818_DCDC_ILMAX 0x90 +#define RK818_DCDC_ILMAX_REG 0x90 +#define RK818_CHRG_COMP_REG 0x9a +#define RK818_SUP_STS_REG 0xa0 #define RK818_USB_CTRL_REG 0xa1 +#define RK818_CHRG_CTRL_REG1 0xa3 +#define RK818_CHRG_CTRL_REG2 0xa4 +#define RK818_CHRG_CTRL_REG3 0xa5 +#define RK818_BAT_CTRL_REG 0xa6 +#define RK818_BAT_HTS_TS1_REG 0xa8 +#define RK818_BAT_LTS_TS1_REG 0xa9 +#define RK818_BAT_HTS_TS2_REG 0xaa +#define RK818_BAT_LTS_TS2_REG 0xab +#define RK818_TS_CTRL_REG 0xac +#define RK818_ADC_CTRL_REG 0xad +#define RK818_ON_SOURCE_REG 0xae +#define RK818_OFF_SOURCE_REG 0xaf +#define RK818_GGCON_REG 0xb0 +#define RK818_GGSTS_REG 0xb1 +#define RK818_FRAME_SMP_INTERV_REG 0xb2 +#define RK818_AUTO_SLP_CUR_THR_REG 0xb3 +#define RK818_GASCNT_CAL_REG3 0xb4 +#define RK818_GASCNT_CAL_REG2 0xb5 +#define RK818_GASCNT_CAL_REG1 0xb6 +#define RK818_GASCNT_CAL_REG0 0xb7 +#define RK818_GASCNT3_REG 0xb8 +#define RK818_GASCNT2_REG 0xb9 +#define RK818_GASCNT1_REG 0xba +#define RK818_GASCNT0_REG 0xbb +#define RK818_BAT_CUR_AVG_REGH 0xbc +#define RK818_BAT_CUR_AVG_REGL 0xbd +#define RK818_TS1_ADC_REGH 0xbe +#define RK818_TS1_ADC_REGL 0xbf +#define RK818_TS2_ADC_REGH 0xc0 +#define RK818_TS2_ADC_REGL 0xc1 +#define RK818_BAT_OCV_REGH 0xc2 +#define RK818_BAT_OCV_REGL 0xc3 +#define RK818_BAT_VOL_REGH 0xc4 +#define RK818_BAT_VOL_REGL 0xc5 +#define RK818_RELAX_ENTRY_THRES_REGH 0xc6 +#define RK818_RELAX_ENTRY_THRES_REGL 0xc7 +#define RK818_RELAX_EXIT_THRES_REGH 0xc8 +#define RK818_RELAX_EXIT_THRES_REGL 0xc9 +#define RK818_RELAX_VOL1_REGH 0xca +#define RK818_RELAX_VOL1_REGL 0xcb +#define RK818_RELAX_VOL2_REGH 0xcc +#define RK818_RELAX_VOL2_REGL 0xcd +#define RK818_BAT_CUR_R_CALC_REGH 0xce +#define RK818_BAT_CUR_R_CALC_REGL 0xcf +#define RK818_BAT_VOL_R_CALC_REGH 0xd0 +#define RK818_BAT_VOL_R_CALC_REGL 0xd1 +#define RK818_CAL_OFFSET_REGH 0xd2 +#define RK818_CAL_OFFSET_REGL 0xd3 +#define RK818_NON_ACT_TIMER_CNT_REG 0xd4 +#define RK818_VCALIB0_REGH 0xd5 +#define RK818_VCALIB0_REGL 0xd6 +#define RK818_VCALIB1_REGH 0xd7 +#define RK818_VCALIB1_REGL 0xd8 +#define RK818_IOFFSET_REGH 0xdd +#define RK818_IOFFSET_REGL 0xde +#define RK818_SOC_REG 0xe0 +#define RK818_REMAIN_CAP_REG3 0xe1 +#define RK818_REMAIN_CAP_REG2 0xe2 +#define RK818_REMAIN_CAP_REG1 0xe3 +#define RK818_REMAIN_CAP_REG0 0xe4 +#define RK818_UPDAT_LEVE_REG 0xe5 +#define RK818_NEW_FCC_REG3 0xe6 +#define RK818_NEW_FCC_REG2 0xe7 +#define RK818_NEW_FCC_REG1 0xe8 +#define RK818_NEW_FCC_REG0 0xe9 +#define RK818_NON_ACT_TIMER_CNT_SAVE_REG 0xea +#define RK818_OCV_VOL_VALID_REG 0xeb +#define RK818_REBOOT_CNT_REG 0xec +#define RK818_POFFSET_REG 0xed +#define RK818_MISC_MARK_REG 0xee +#define RK818_HALT_CNT_REG 0xef +#define RK818_CALC_REST_REGH 0xf0 +#define RK818_CALC_REST_REGL 0xf1 +#define RK818_SAVE_DATA19 0xf2 #define RK818_H5V_EN BIT(0) #define RK818_REF_RDY_CTRL BIT(1) @@ -382,6 +461,8 @@ enum rk805_reg { #define VOUT_LO_INT BIT(0) #define CLK32KOUT2_EN BIT(0) +#define CLK32KOUT2_FUNC (0 << 1) +#define CLK32KOUT2_FUNC_MASK BIT(1) #define TEMP115C 0x0c #define TEMP_HOTDIE_MSK 0x0c diff -rupN linux.orig/sound/soc/meson/axg-card.c linux/sound/soc/meson/axg-card.c --- linux.orig/sound/soc/meson/axg-card.c 2023-09-12 12:02:59.597661952 +0000 +++ linux/sound/soc/meson/axg-card.c 2023-09-12 12:03:27.490291628 +0000 @@ -59,6 +59,13 @@ static int axg_card_tdm_dai_init(struct (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; struct snd_soc_dai *codec_dai; int ret, i; + struct snd_soc_card *card = rtd->card; + + /* Go-Ultra : Digital volume is limited to -2dB */ + ret = snd_soc_limit_volume(card, "Master Playback Volume", 252); + if (ret < 0) + dev_dbg(codec_dai->dev, + "Not found mixer : 'Master Playback Volume'\n"); for_each_rtd_codec_dais(rtd, i, codec_dai) { ret = snd_soc_dai_set_tdm_slot(codec_dai,