diff --git a/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt b/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt index 31e0b6b62ff6..f9b5760de3f0 100644 --- a/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt +++ b/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt @@ -129,6 +129,10 @@ Optional properties: "dfps_immediate_porch_mode" = FPS change request is implemented immediately by changing panel porch values. +- qcom,min-refresh-rate: Minimum refresh rate supported by the panel. +- qcom,max-refresh-rate: Maximum refresh rate supported by the panel. If max refresh + rate is not specified, then the frame rate of the panel in + qcom,mdss-dsi-panel-framerate is used. - qcom,mdss-dsi-bl-pmic-control-type: A string that specifies the implementation of backlight control for this panel. "bl_ctrl_pwm" = Backlight controlled by PWM gpio. @@ -409,6 +413,8 @@ Example: qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled"; qcom,mdss-dsi-pan-enable-dynamic-fps; qcom,mdss-dsi-pan-fps-update = "dfps_suspend_resume_mode"; + qcom,min-refresh-rate = <30>; + qcom,max-refresh-rate = <60>; qcom,mdss-dsi-bl-pmic-bank-select = <0>; qcom,mdss-dsi-bl-pmic-pwm-frequency = <0>; qcom,mdss-dsi-pwm-gpio = <&pm8941_mpps 5 0>; diff --git a/Documentation/security/keys.txt b/Documentation/security/keys.txt index d389acd31e19..aa0dbd74b71b 100644 --- a/Documentation/security/keys.txt +++ b/Documentation/security/keys.txt @@ -805,6 +805,23 @@ The keyctl syscall functions are: kernel and resumes executing userspace. + (*) Invalidate a key. + + long keyctl(KEYCTL_INVALIDATE, key_serial_t key); + + This function marks a key as being invalidated and then wakes up the + garbage collector. The garbage collector immediately removes invalidated + keys from all keyrings and deletes the key when its reference count + reaches zero. + + Keys that are marked invalidated become invisible to normal key operations + immediately, though they are still visible in /proc/keys until deleted + (they're marked with an 'i' flag). + + A process must have search permission on the key for this function to be + successful. + + =============== KERNEL SERVICES =============== diff --git a/Makefile b/Makefile index e97ef64938cf..3de37d3d039e 100644 --- a/Makefile +++ b/Makefile @@ -243,10 +243,14 @@ CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \ else if [ -x /bin/bash ]; then echo /bin/bash; \ else echo sh; fi ; fi) -HOSTCC = gcc -HOSTCXX = g++ -HOSTCFLAGS = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -HOSTCXXFLAGS = -O2 +HOSTCC = gcc +HOSTCXX = g++ +HOSTCFLAGS = -Wall -Wmissing-prototypes -Wstrict-prototypes -Ofast -fomit-frame-pointer -fgcse-las +HOSTCXXFLAGS = -Ofast -fgcse-las + +# Optimizations +HOSTCXXFLAGS += -fgraphite -floop-flatten -floop-parallelize-all -ftree-loop-linear -floop-interchange -floop-strip-mine -floop-block +HOSTCFLAGS += -fgraphite -floop-flatten -floop-parallelize-all -ftree-loop-linear -floop-interchange -floop-strip-mine -floop-block # Decide whether to build built-in, modular, or both. # Normally, just do built-in. @@ -347,10 +351,10 @@ CHECK = sparse CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \ -Wbitwise -Wno-return-void $(CF) -CFLAGS_MODULE = +CFLAGS_MODULE = -mcpu=cortex-a15 -mtune=cortex-a15 -mfpu=neon-vfpv4 AFLAGS_MODULE = LDFLAGS_MODULE = -CFLAGS_KERNEL = +CFLAGS_KERNEL = -mcpu=cortex-a15 -mtune=cortex-a15 -mfpu=neon-vfpv4 AFLAGS_KERNEL = CFLAGS_GCOV = -fprofile-arcs -ftest-coverage diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 930081ead675..cb292f359c9c 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1493,6 +1493,7 @@ config KSAPI endmenu source "arch/arm/common/Kconfig" +source "arch/arm/hotplug/Kconfig" menu "Bus support" @@ -2250,6 +2251,32 @@ config ATAGS_PROC Should the atags used to boot the kernel be exported in an "atags" file in procfs. Useful with kexec. +config KEXEC_HARDBOOT + bool "Support hard booting to a kexec kernel" + depends on KEXEC + help + Allows hard booting (i.e., with a full hardware reboot) to a kernel + previously loaded in memory by kexec. This works around the problem of + soft-booted kernel hangs due to improper device shutdown and/or + reinitialization. Support is comprised of two components: + + First, a "hardboot" flag is added to the kexec syscall to force a hard + reboot in relocate_new_kernel() (which requires machine-specific assembly + code). This also requires the kexec userspace tool to load the kexec'd + kernel in memory region left untouched by the bootloader (i.e., not + explicitly cleared and not overwritten by the boot kernel). Just prior + to reboot, the kexec kernel arguments are stashed in a machine-specific + memory page that must also be preserved. Note that this hardboot page + need not be reserved during regular kernel execution. + + Second, the zImage decompresor of the boot (bootloader-loaded) kernel is + modified to check the hardboot page for fresh kexec arguments, and if + present, attempts to jump to the kexec'd kernel preserved in memory. + + Note that hardboot support is only required in the boot kernel and any + kernel capable of performing a hardboot kexec. It is _not_ required by a + kexec'd kernel. + config CRASH_DUMP bool "Build kdump crash kernel (EXPERIMENTAL)" depends on EXPERIMENTAL diff --git a/arch/arm/Makefile b/arch/arm/Makefile index e62078676f91..2c837bd758e4 100644 --- a/arch/arm/Makefile +++ b/arch/arm/Makefile @@ -265,6 +265,7 @@ core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/ core-y += arch/arm/net/ core-y += arch/arm/crypto/ core-y += $(machdirs) $(platdirs) +core-$(CONFIG_INTELLI_PLUG) += arch/arm/hotplug/ drivers-$(CONFIG_OPROFILE) += arch/arm/oprofile/ core-y += arch/arm/perfmon/ diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S index aa6f96ed2abb..346609393b64 100644 --- a/arch/arm/boot/compressed/head.S +++ b/arch/arm/boot/compressed/head.S @@ -11,6 +11,11 @@ #include .arch armv7-a +#ifdef CONFIG_KEXEC_HARDBOOT + #include + #include +#endif + /* * Debugging stuff * @@ -136,6 +141,64 @@ start: 1: mov r7, r1 @ save architecture ID mov r8, r2 @ save atags pointer +#ifdef CONFIG_KEXEC_HARDBOOT + /* Check hardboot page for a kexec kernel. */ + ldr r3, =KEXEC_HB_PAGE_ADDR + ldr r0, [r3] + ldr r1, =KEXEC_HB_PAGE_MAGIC + teq r0, r1 + bne not_booting_other + + /* Clear hardboot page magic to avoid boot loop. */ + mov r0, #0 + str r0, [r3] + + /* + * Copy dtb from location up high in memory to default location. + * Kernel freezes if this is not done. + */ + ldr r1, [r3, #12] @ kexec_boot_atags + ldr r2, [r3, #16] @ kexec_boot_atags_len + mov r5, #0 @ iterator +catags_cpy: + ldr r0, [r1, r5] @ from kexec_boot_atags + str r0, [r8, r5] @ to atags_pointer + add r5, r5, #4 + cmp r5, r2 + blo catags_cpy + +#ifdef KEXEC_HB_KERNEL_LOC + /* + * Copy kernel from location up high in memory to location in first 128MB. + * Bootloader on hammerhead erases first 128MB of ram on reboot, so it can't + * be in there before reboot, but decompressing in location above 128MB takes + * a long time. This memcpy is much quicker, for some reason. + */ + ldr r2, [r3, #4] @ kexec_start_address + ldr r4, [r3, #20] @ kexec_kernel_len + ldr r6, =KEXEC_HB_KERNEL_LOC @ target + mov r5, #0 @ iterator +kernel_cpy: + ldr r0, [r2, r5] @ from kexec_start_address + str r0, [r6, r5] @ to KEXEC_HB_KERNEL_LOC + add r5, r5, #4 + cmp r5, r4 + blo kernel_cpy +#else + ldr r6, [r3, #4] @ kexec_start_address +#endif + + /* set registers and boot kexecd' kernel */ + mov r0, #0 + ldr r1, [r3, #8] @ kexec_mach_type + mov r2, r8 @ atags pointer + mov pc, r6 + + .ltorg + +not_booting_other: +#endif + #ifndef __ARM_ARCH_2__ /* * Booting from Angel - need to enter SVC mode and disable diff --git a/arch/arm/boot/dts/dsi-panel-nt35590-720p-video.dtsi b/arch/arm/boot/dts/dsi-panel-nt35590-720p-video.dtsi index b5189d43e996..9fa963ba881e 100644 --- a/arch/arm/boot/dts/dsi-panel-nt35590-720p-video.dtsi +++ b/arch/arm/boot/dts/dsi-panel-nt35590-720p-video.dtsi @@ -1,4 +1,4 @@ -/* Copyright (c) 2014, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014-2015, The Linux Foundation. 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 version 2 and @@ -532,6 +532,9 @@ qcom,mdss-dsi-dma-trigger = "trigger_sw"; qcom,mdss-dsi-mdp-trigger = "none"; qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled"; + qcom,mdss-dsi-pan-enable-dynamic-fps; + qcom,mdss-dsi-pan-fps-update = "dfps_immediate_porch_mode"; + qcom,mdss-dsi-min-refresh-rate = <45>; qcom,mdss-dsi-reset-sequence = <1 20>, <0 1>, <1 20>; qcom,mdss-pan-physical-width-dimension = <59>; qcom,mdss-pan-physical-height-dimension = <104>; diff --git a/arch/arm/boot/dts/dsi-panel-toshiba-720p-video.dtsi b/arch/arm/boot/dts/dsi-panel-toshiba-720p-video.dtsi index 10f53b999d7e..327909b7c59b 100644 --- a/arch/arm/boot/dts/dsi-panel-toshiba-720p-video.dtsi +++ b/arch/arm/boot/dts/dsi-panel-toshiba-720p-video.dtsi @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. +/* Copyright (c) 2012-2013,2015 The Linux Foundation. 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 version 2 and @@ -89,7 +89,8 @@ qcom,mdss-dsi-mdp-trigger = "none"; qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled"; qcom,mdss-dsi-pan-enable-dynamic-fps; - qcom,mdss-dsi-pan-fps-update = "dfps_suspend_resume_mode"; + qcom,mdss-dsi-pan-fps-update = "dfps_immediate_porch_mode"; + qcom,mdss-dsi-min-refresh-rate = <45>; qcom,mdss-dsi-reset-sequence = <1 20>, <0 200>, <1 20>; }; }; diff --git a/arch/arm/boot/dts/msm8226.dtsi b/arch/arm/boot/dts/msm8226.dtsi index 63d54ae96b4b..86e6c2251437 100644 --- a/arch/arm/boot/dts/msm8226.dtsi +++ b/arch/arm/boot/dts/msm8226.dtsi @@ -33,6 +33,7 @@ adsp_mem: adsp_region { linux,contiguous-region; + linux,memory-limit = <0x0>; reg = <0 0x2a00000>; label = "adsp_mem"; }; diff --git a/arch/arm/boot/dts/msm8974-rhine_togari_row.dtsi b/arch/arm/boot/dts/msm8974-rhine_togari_row.dtsi index 06b4442a9cfe..6b06a5d076a8 100644 --- a/arch/arm/boot/dts/msm8974-rhine_togari_row.dtsi +++ b/arch/arm/boot/dts/msm8974-rhine_togari_row.dtsi @@ -1113,7 +1113,7 @@ qcom,mode = "pwm"; qcom,pwm-channel = <6>; qcom,pwm-us = <1000>; - qcom,pwm-max-value = <31>; + qcom,pwm-max-value = <400>; qcom,max-current = <12>; qcom,default-state = "off"; qcom,id = <3>; @@ -1126,7 +1126,7 @@ qcom,mode = "pwm"; qcom,pwm-channel = <5>; qcom,pwm-us = <1000>; - qcom,pwm-max-value = <32>; + qcom,pwm-max-value = <400>; qcom,max-current = <12>; qcom,default-state = "off"; qcom,id = <4>; @@ -1139,7 +1139,7 @@ qcom,mode = "pwm"; qcom,pwm-channel = <4>; qcom,pwm-us = <1000>; - qcom,pwm-max-value = <24>; + qcom,pwm-max-value = <400>; qcom,max-current = <12>; qcom,default-state = "off"; qcom,id = <5>; diff --git a/arch/arm/boot/dts/msm8974.dtsi b/arch/arm/boot/dts/msm8974.dtsi index 337bd9ef5700..19c4fbb250c3 100644 --- a/arch/arm/boot/dts/msm8974.dtsi +++ b/arch/arm/boot/dts/msm8974.dtsi @@ -1573,6 +1573,7 @@ reg = <0 4>; compatible = "qcom,msm-cpufreq"; qcom,cpufreq-table = + < 268800 300000 572 >, < 300000 300000 572 >, < 422400 422400 1144 >, < 652800 499200 1525 >, diff --git a/arch/arm/configs/cm_rhine_amami_row_defconfig b/arch/arm/configs/cm_rhine_amami_row_defconfig index b411294538c9..2e1c30d2baeb 100644 --- a/arch/arm/configs/cm_rhine_amami_row_defconfig +++ b/arch/arm/configs/cm_rhine_amami_row_defconfig @@ -277,6 +277,17 @@ CONFIG_BLK_DEV_RAM=y CONFIG_BLK_DEV_RAM_SIZE=8192 CONFIG_UID_STAT=y CONFIG_PM8941_FLASH=y + +# Kexec-HardBoot +CONFIG_KEXEC=y +CONFIG_ATAGS_PROC=n +CONFIG_KEXEC_HARDBOOT=y +CONFIG_PROC_DEVICETREE=y +CONFIG_IKCONFIG_PROC=y +CONFIG_IKCONFIG=y +CONFIG_PROC_FS=y +CONFIG_ANDROID_PERSISTENT_RAM=y + CONFIG_QSEECOM=y CONFIG_QPNP_MISC=y CONFIG_NFC_PN544=y @@ -385,8 +396,10 @@ CONFIG_SENSORS_EPM_ADC=y CONFIG_SENSORS_QPNP_ADC_VOLTAGE=y CONFIG_SENSORS_QPNP_ADC_CURRENT=y CONFIG_THERMAL=y +CONFIG_THERMAL_HWMON=y CONFIG_THERMAL_TSENS8974=y -CONFIG_THERMAL_MONITOR=y +CONFIG_INTELLI_THERMAL_V2=y +# CONFIG_THERMAL_MONITOR is not set CONFIG_THERMAL_QPNP=y CONFIG_THERMAL_QPNP_ADC_TM=y CONFIG_WCD9320_CODEC=y @@ -612,3 +625,7 @@ CONFIG_CRYPTO_AES_ARM_BS=y CONFIG_CRYPTO_TWOFISH=y CONFIG_CRYPTO_DEV_QCE=y CONFIG_CRYPTO_DEV_QCEDEV=y + +#BlackSheep +CONFIG_FB_MSM_MDSS_KCAL_CTRL=y +CONFIG_SOUND_CONTROL_HAX_3_GPL=y diff --git a/arch/arm/configs/cm_rhine_honami_row_defconfig b/arch/arm/configs/cm_rhine_honami_row_defconfig index 7ea6614c2f38..6f7301c16324 100644 --- a/arch/arm/configs/cm_rhine_honami_row_defconfig +++ b/arch/arm/configs/cm_rhine_honami_row_defconfig @@ -8,7 +8,6 @@ CONFIG_TASK_XACCT=y CONFIG_TASK_IO_ACCOUNTING=y CONFIG_AUDIT=y CONFIG_RCU_FAST_NO_HZ=y -CONFIG_IKCONFIG=y CONFIG_CGROUPS=y CONFIG_CGROUP_DEBUG=y CONFIG_CGROUP_FREEZER=y @@ -277,6 +276,18 @@ CONFIG_BLK_DEV_RAM=y CONFIG_BLK_DEV_RAM_SIZE=8192 CONFIG_UID_STAT=y CONFIG_PM8941_FLASH=y + +# Kexec-HardBoot +CONFIG_KEXEC=y +CONFIG_ATAGS_PROC=n +CONFIG_KEXEC_HARDBOOT=y +CONFIG_PROC_DEVICETREE=y +CONFIG_IKCONFIG_PROC=y +CONFIG_IKCONFIG=y +CONFIG_PROC_FS=y +CONFIG_ANDROID_PERSISTENT_RAM=y + + CONFIG_QSEECOM=y CONFIG_QPNP_MISC=y CONFIG_NFC_PN544=y @@ -385,8 +396,10 @@ CONFIG_SENSORS_EPM_ADC=y CONFIG_SENSORS_QPNP_ADC_VOLTAGE=y CONFIG_SENSORS_QPNP_ADC_CURRENT=y CONFIG_THERMAL=y +CONFIG_THERMAL_HWMON=y CONFIG_THERMAL_TSENS8974=y -CONFIG_THERMAL_MONITOR=y +CONFIG_INTELLI_THERMAL_V2=y +# CONFIG_THERMAL_MONITOR is not set CONFIG_THERMAL_QPNP=y CONFIG_THERMAL_QPNP_ADC_TM=y CONFIG_WCD9320_CODEC=y @@ -612,3 +625,7 @@ CONFIG_CRYPTO_AES_ARM_BS=y CONFIG_CRYPTO_TWOFISH=y CONFIG_CRYPTO_DEV_QCE=y CONFIG_CRYPTO_DEV_QCEDEV=y + +#BlackSheep +CONFIG_FB_MSM_MDSS_KCAL_CTRL=y +CONFIG_SOUND_CONTROL_HAX_3_GPL=y diff --git a/arch/arm/configs/cm_rhine_togari_row_defconfig b/arch/arm/configs/cm_rhine_togari_row_defconfig index ebbe05cef891..937b4affe21b 100644 --- a/arch/arm/configs/cm_rhine_togari_row_defconfig +++ b/arch/arm/configs/cm_rhine_togari_row_defconfig @@ -1,6 +1,7 @@ # CONFIG_ARM_PATCH_PHYS_VIRT is not set CONFIG_EXPERIMENTAL=y -CONFIG_LOCALVERSION="-cm" +CONFIG_LOCALVERSION="-BlackSheep-v1.0" +# CONFIG_LOCALVERSION_AUTO is not set CONFIG_KERNEL_XZ=y CONFIG_SYSVIPC=y CONFIG_TASKSTATS=y @@ -8,7 +9,6 @@ CONFIG_TASK_XACCT=y CONFIG_TASK_IO_ACCOUNTING=y CONFIG_AUDIT=y CONFIG_RCU_FAST_NO_HZ=y -CONFIG_IKCONFIG=y CONFIG_CGROUPS=y CONFIG_CGROUP_DEBUG=y CONFIG_CGROUP_FREEZER=y @@ -383,8 +383,10 @@ CONFIG_SENSORS_EPM_ADC=y CONFIG_SENSORS_QPNP_ADC_VOLTAGE=y CONFIG_SENSORS_QPNP_ADC_CURRENT=y CONFIG_THERMAL=y +CONFIG_THERMAL_HWMON=y CONFIG_THERMAL_TSENS8974=y -CONFIG_THERMAL_MONITOR=y +# CONFIG_THERMAL_MONITOR is not set +CONFIG_INTELLI_THERMAL_V2=y CONFIG_THERMAL_QPNP=y CONFIG_THERMAL_QPNP_ADC_TM=y CONFIG_WCD9320_CODEC=y @@ -519,6 +521,18 @@ CONFIG_MMC_PARANOID_SD_INIT=y CONFIG_MMC_BLOCK_MINORS=32 # CONFIG_MMC_BLOCK_BOUNCE is not set CONFIG_MMC_BLOCK_DEFERRED_RESUME=y + +# Kexec-HardBoot +CONFIG_KEXEC=y +CONFIG_ATAGS_PROC=n +CONFIG_KEXEC_HARDBOOT=y +CONFIG_PROC_DEVICETREE=y +CONFIG_IKCONFIG_PROC=y +CONFIG_IKCONFIG=y +CONFIG_PROC_FS=y +CONFIG_ANDROID_PERSISTENT_RAM=y + +CONFIG_MMC_BLOCK_TEST=y CONFIG_MMC_SDHCI=y CONFIG_MMC_SDHCI_PLTFM=y CONFIG_MMC_MSM=y @@ -610,3 +624,21 @@ CONFIG_CRYPTO_AES_ARM_BS=y CONFIG_CRYPTO_TWOFISH=y CONFIG_CRYPTO_DEV_QCE=y CONFIG_CRYPTO_DEV_QCEDEV=y + +#BlackSheep +CONFIG_ADRENO_IDLER=y +CONFIG_FB_MSM_MDSS_KCAL_CTRL=y +CONFIG_FORCE_FAST_CHARGE=y +CONFIG_HAS_EARLYSUSPEND=y +CONFIG_INTELLI_PLUG=y +CONFIG_MSM_DEVFREQ_CPUBW=y +CONFIG_PM_SLEEP=y +CONFIG_PM_SLEEP_SMP=y +CONFIG_PM_AUTOSLEEP=y +CONFIG_PM_WAKELOCKS=y +CONFIG_POWERSUSPEND=y +CONFIG_QUICK_WAKEUP=y +CONFIG_SOUND_CONTROL_HAX_3_GPL=y +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +MSM_MPDEC=y diff --git a/arch/arm/configs/cm_shinano_castor_windy_defconfig b/arch/arm/configs/cm_shinano_castor_windy_defconfig index 5c7689b75f36..f7eabe46fbf4 100644 --- a/arch/arm/configs/cm_shinano_castor_windy_defconfig +++ b/arch/arm/configs/cm_shinano_castor_windy_defconfig @@ -619,3 +619,4 @@ CONFIG_CRYPTO_TWOFISH=y CONFIG_CRYPTO_ANSI_CPRNG=y CONFIG_CRYPTO_DEV_QCE=y CONFIG_CRYPTO_DEV_QCEDEV=y +CONFIG_MMC_ENABLE_CACHECTRL_SKHYNIX=y diff --git a/arch/arm/configs/cm_shinano_leo_dsds_defconfig b/arch/arm/configs/cm_shinano_leo_dsds_defconfig new file mode 100644 index 000000000000..81c7519aba54 --- /dev/null +++ b/arch/arm/configs/cm_shinano_leo_dsds_defconfig @@ -0,0 +1,625 @@ +# CONFIG_ARM_PATCH_PHYS_VIRT is not set +CONFIG_EXPERIMENTAL=y +CONFIG_LOCALVERSION="-cm" +CONFIG_KERNEL_XZ=y +CONFIG_SYSVIPC=y +CONFIG_TASKSTATS=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_AUDIT=y +CONFIG_RCU_FAST_NO_HZ=y +CONFIG_IKCONFIG=y +CONFIG_CGROUPS=y +CONFIG_CGROUP_DEBUG=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_RESOURCE_COUNTERS=y +CONFIG_CGROUP_MEM_RES_CTLR=y +CONFIG_CGROUP_MEM_RES_CTLR_SWAP=y +CONFIG_CGROUP_SCHED=y +CONFIG_RT_GROUP_SCHED=y +CONFIG_NAMESPACES=y +# CONFIG_UTS_NS is not set +# CONFIG_IPC_NS is not set +# CONFIG_USER_NS is not set +# CONFIG_PID_NS is not set +CONFIG_RELAY=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_RD_BZIP2=y +CONFIG_RD_LZMA=y +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_KALLSYMS_ALL=y +CONFIG_EMBEDDED=y +# CONFIG_SLUB_DEBUG is not set +CONFIG_PROFILING=y +CONFIG_OPROFILE=y +CONFIG_KPROBES=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_EFI_PARTITION=y +CONFIG_IOSCHED_TEST=y +CONFIG_IOSCHED_BFQ=y +CONFIG_CGROUP_BFQIO=y +CONFIG_DEFAULT_NOOP=y +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM8974=y +CONFIG_MSM_KRAIT_TBB_ABORT_HANDLER=y +CONFIG_MACH_SONY_LEO_DSDS=y +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_CPU_HAS_L2_PMU=y +# CONFIG_MSM_FIQ_SUPPORT is not set +# CONFIG_MSM_PROC_COMM is not set +CONFIG_MSM_SMD=y +CONFIG_MSM_SMD_PKG4=y +CONFIG_MSM_BAM_DMUX=y +CONFIG_MSM_SMP2P=y +CONFIG_MSM_SMP2P_TEST=y +CONFIG_MSM_RESET_MODEM=y +CONFIG_MSM_IPC_LOGGING=y +CONFIG_MSM_IPC_ROUTER=y +CONFIG_MSM_IPC_ROUTER_SMD_XPRT=y +CONFIG_MSM_IPC_ROUTER_SECURITY=y +CONFIG_MSM_QMI_INTERFACE=y +# CONFIG_MSM_DMA_TEST is not set +CONFIG_MSM_SUBSYSTEM_RESTART=y +CONFIG_MSM_SYSMON_COMM=y +CONFIG_MSM_PIL_LPASS_QDSP6V5=y +CONFIG_MSM_PIL_MSS_QDSP6V5=y +CONFIG_MSM_PIL_VENUS=y +CONFIG_MSM_TZ_LOG=y +CONFIG_MSM_RPM_RBCPR_STATS_V2_LOG=y +CONFIG_MSM_DIRECT_SCLK_ACCESS=y +CONFIG_MSM_EVENT_TIMER=y +CONFIG_MSM_BUS_SCALING=y +CONFIG_MSM_WATCHDOG_V2=y +CONFIG_MSM_MEMORY_DUMP=y +CONFIG_MSM_DLOAD_MODE=y +CONFIG_MSM_ADSP_LOADER=y +CONFIG_MSM_OCMEM=y +CONFIG_MSM_OCMEM_LOCAL_POWER_CTRL=y +CONFIG_MSM_OCMEM_DEBUG=y +CONFIG_SENSORS_ADSP=y +CONFIG_MSM_RTB=y +CONFIG_MSM_RTB_SEPARATE_CPUS=y +CONFIG_MSM_CACHE_ERP=y +CONFIG_MSM_L1_ERR_PANIC=y +CONFIG_MSM_L1_ERR_LOG=y +CONFIG_MSM_L2_ERP_2BIT_PANIC=y +CONFIG_MSM_ENABLE_WDOG_DEBUG_CONTROL=y +CONFIG_MSM_BOOT_STATS=y +CONFIG_MSM_MODEM_SUBSYSTEM_RESTART_MONITOR=y +CONFIG_STRICT_MEMORY_RWX=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_SMP=y +# CONFIG_SMP_ON_UP is not set +CONFIG_SCHED_MC=y +CONFIG_SCHED_SMT=y +CONFIG_ARM_ARCH_TIMER=y +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_HIGHMEM=y +CONFIG_COMPACTION=y +CONFIG_KSM=y +CONFIG_ENABLE_VMALLOC_SAVING=y +CONFIG_CC_STACKPROTECTOR=y +CONFIG_ARM_FLUSH_CONSOLE_ON_RESTART=y +CONFIG_CP_ACCESS=y +CONFIG_USE_OF=y +CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_INTERACTIVE=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_IDLE=y +CONFIG_VFP=y +CONFIG_NEON=y +CONFIG_KERNEL_MODE_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_PM_AUTOSLEEP=y +CONFIG_PM_WAKELOCKS=y +CONFIG_PM_WAKELOCKS_LIMIT=0 +# CONFIG_PM_WAKELOCKS_GC is not set +CONFIG_PM_RUNTIME=y +CONFIG_PM_DEBUG=y +CONFIG_SUSPEND_TIME=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_XFRM_USER=y +CONFIG_NET_KEY=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_INET_AH=y +CONFIG_INET_ESP=y +CONFIG_INET_IPCOMP=y +# CONFIG_INET_LRO is not set +CONFIG_IPV6=y +CONFIG_IPV6_PRIVACY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_IPV6_OPTIMISTIC_DAD=y +CONFIG_INET6_AH=y +CONFIG_INET6_ESP=y +CONFIG_INET6_IPCOMP=y +CONFIG_IPV6_MIP6=y +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_SECMARK=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CT_PROTO_DCCP=y +CONFIG_NF_CT_PROTO_SCTP=y +CONFIG_NF_CT_PROTO_UDPLITE=y +CONFIG_NF_CONNTRACK_AMANDA=y +CONFIG_NF_CONNTRACK_FTP=y +CONFIG_NF_CONNTRACK_H323=y +CONFIG_NF_CONNTRACK_IRC=y +CONFIG_NF_CONNTRACK_NETBIOS_NS=y +CONFIG_NF_CONNTRACK_PPTP=y +CONFIG_NF_CONNTRACK_SANE=y +CONFIG_NF_CONNTRACK_TFTP=y +CONFIG_NF_CT_NETLINK=y +CONFIG_NETFILTER_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_CONNSECMARK=y +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=y +CONFIG_NETFILTER_XT_TARGET_LOG=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_TARGET_NOTRACK=y +CONFIG_NETFILTER_XT_TARGET_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_TRACE=y +CONFIG_NETFILTER_XT_TARGET_SECMARK=y +CONFIG_NETFILTER_XT_TARGET_TCPMSS=y +CONFIG_NETFILTER_XT_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_DSCP=y +CONFIG_NETFILTER_XT_MATCH_ESP=y +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y +CONFIG_NETFILTER_XT_MATCH_HELPER=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QTAGUID=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +CONFIG_NETFILTER_XT_MATCH_STRING=y +CONFIG_NETFILTER_XT_MATCH_TCPMSS=y +CONFIG_NETFILTER_XT_MATCH_TIME=y +CONFIG_NETFILTER_XT_MATCH_U32=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_IP_NF_TARGET_REJECT_SKERR=y +CONFIG_NF_NAT=y +CONFIG_IP_NF_TARGET_MASQUERADE=y +CONFIG_IP_NF_TARGET_NETMAP=y +CONFIG_IP_NF_TARGET_REDIRECT=y +CONFIG_IP_NF_MANGLE=y +CONFIG_IP_NF_RAW=y +CONFIG_IP_NF_SECURITY=y +CONFIG_IP_NF_ARPTABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARP_MANGLE=y +CONFIG_NF_CONNTRACK_IPV6=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_MATCH_AH=y +CONFIG_IP6_NF_MATCH_EUI64=y +CONFIG_IP6_NF_MATCH_FRAG=y +CONFIG_IP6_NF_MATCH_OPTS=y +CONFIG_IP6_NF_MATCH_HL=y +CONFIG_IP6_NF_MATCH_IPV6HEADER=y +CONFIG_IP6_NF_MATCH_MH=y +CONFIG_IP6_NF_MATCH_RT=y +CONFIG_IP6_NF_TARGET_HL=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_TARGET_REJECT=y +CONFIG_IP6_NF_TARGET_REJECT_SKERR=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_IP6_NF_RAW=y +CONFIG_BRIDGE_NF_EBTABLES=y +CONFIG_BRIDGE_EBT_BROUTE=y +CONFIG_L2TP=y +CONFIG_BRIDGE=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_CLS_FW=y +CONFIG_NET_CLS_U32=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=y +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_CMP=y +CONFIG_NET_EMATCH_NBYTE=y +CONFIG_NET_EMATCH_U32=y +CONFIG_NET_EMATCH_META=y +CONFIG_NET_EMATCH_TEXT=y +CONFIG_NET_CLS_ACT=y +CONFIG_BT=y +CONFIG_BT_RFCOMM=y +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=y +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_BT_HCIUART=y +CONFIG_BT_MSM_SLEEP=y +# CONFIG_MSM_BT_POWER is not set +CONFIG_CFG80211=y +CONFIG_NL80211_TESTMODE=y +CONFIG_RFKILL=y +CONFIG_GENLOCK=y +CONFIG_GENLOCK_MISCDEVICE=y +CONFIG_CMA=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_UID_STAT=y +CONFIG_PM8941_FLASH=y +CONFIG_QSEECOM=y +CONFIG_QPNP_MISC=y +CONFIG_NFC_PN547=y +CONFIG_NFC_PN547_PMC8974_CLK_REQ=y +CONFIG_SCSI=y +CONFIG_SCSI_TGT=y +CONFIG_BLK_DEV_SD=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_MD=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_CRYPT=y +CONFIG_DM_UEVENT=y +CONFIG_DM_VERITY=y +CONFIG_NETDEVICES=y +CONFIG_DUMMY=y +CONFIG_TUN=y +CONFIG_KS8851=y +# CONFIG_MSM_RMNET is not set +CONFIG_MSM_RMNET_BAM=y +CONFIG_PPP=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_DEFLATE=y +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=y +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOL2TP=y +CONFIG_PPPOLAC=y +CONFIG_PPPOPNS=y +CONFIG_PPP_ASYNC=y +CONFIG_PPP_SYNC_TTY=y +CONFIG_SLIP=y +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_MODE_SLIP6=y +CONFIG_USB_USBNET=y +# CONFIG_USB_NET_CDCETHER is not set +CONFIG_USB_NET_CDC_EEM=y +# CONFIG_USB_NET_CDC_NCM is not set +CONFIG_USB_NET_DM9601=y +CONFIG_USB_NET_SMSC75XX=y +CONFIG_USB_NET_SMSC95XX=y +CONFIG_USB_NET_GL620A=y +# CONFIG_USB_NET_NET1080 is not set +CONFIG_USB_NET_PLUSB=y +CONFIG_USB_NET_MCS7830=y +# CONFIG_USB_NET_CDC_SUBSET is not set +# CONFIG_USB_NET_ZAURUS is not set +CONFIG_USB_NET_CX82310_ETH=y +CONFIG_USB_NET_KALMIA=y +CONFIG_USB_NET_INT51X1=y +CONFIG_USB_SIERRA_NET=y +CONFIG_BCMDHD=m +CONFIG_BCM4354=y +CONFIG_BCMDHD_FW_PATH="/system/etc/firmware/wlan/bcmdhd/fw_bcmdhd.bin" +CONFIG_BCMDHD_NVRAM_PATH="/system/etc/firmware/wlan/bcmdhd/bcmdhd.cal" +# CONFIG_INPUT_MOUSEDEV is not set +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_KEYRESET=y +CONFIG_KEYBOARD_GPIO=y +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_JOYSTICK=y +CONFIG_JOYSTICK_XPAD=y +CONFIG_JOYSTICK_XPAD_FF=y +CONFIG_JOYSTICK_XPAD_LEDS=y +CONFIG_INPUT_TABLET=y +CONFIG_TABLET_USB_ACECAD=y +CONFIG_TABLET_USB_AIPTEK=y +CONFIG_TABLET_USB_GTCO=y +CONFIG_TABLET_USB_HANWANG=y +CONFIG_TABLET_USB_KBTAB=y +CONFIG_TABLET_USB_WACOM=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_CLEARPAD=y +CONFIG_TOUCHSCREEN_CLEARPAD_I2C=y +CONFIG_TOUCHSCREEN_CLEARPAD_RMI_DEV=y +CONFIG_TOUCHSCREEN_GEN_VKEYS=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_HBTP_INPUT=y +CONFIG_INPUT_KEYCHORD=y +CONFIG_INPUT_UINPUT=y +CONFIG_INPUT_GPIO=y +CONFIG_INPUT_BU520X1NVX=y +# CONFIG_VT is not set +# CONFIG_LEGACY_PTYS is not set +# CONFIG_DEVMEM is not set +# CONFIG_DEVKMEM is not set +CONFIG_SERIAL_MSM_HS=y +# CONFIG_DIAG_CHAR is not set +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_MSM=y +CONFIG_MSM_ADSPRPC=y +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_QUP=y +CONFIG_SPI=y +CONFIG_SPI_QUP=y +CONFIG_SPMI=y +CONFIG_SPMI_MSM_PMIC_ARB=y +CONFIG_MSM_QPNP_INT=y +CONFIG_SLIMBUS_MSM_NGD=y +CONFIG_DEBUG_GPIO=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_QPNP_PIN=y +CONFIG_GPIO_QPNP_PIN_DEBUG=y +CONFIG_BATTERY_BQ28400=y +CONFIG_QPNP_CHARGER=y +CONFIG_BATTERY_BCL=y +CONFIG_QPNP_BMS=y +CONFIG_SENSORS_EPM_ADC=y +CONFIG_SENSORS_QPNP_ADC_VOLTAGE=y +CONFIG_SENSORS_QPNP_ADC_CURRENT=y +CONFIG_THERMAL=y +CONFIG_THERMAL_TSENS8974=y +CONFIG_THERMAL_MONITOR=y +CONFIG_THERMAL_QPNP=y +CONFIG_THERMAL_QPNP_ADC_TM=y +CONFIG_WCD9320_CODEC=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_STUB=y +CONFIG_REGULATOR_QPNP=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_MEDIA_CONTROLLER=y +CONFIG_VIDEO_DEV=y +CONFIG_VIDEO_V4L2_SUBDEV_API=y +# CONFIG_RC_CORE is not set +# CONFIG_MSM_CAMERA is not set +CONFIG_MSMB_CAMERA=y +CONFIG_MSM_CAMERA_SENSOR=y +CONFIG_MSM_CPP=y +CONFIG_MSM_CCI=y +CONFIG_MSM_CSI30_HEADER=y +CONFIG_MSM_CSIPHY=y +CONFIG_MSM_CSID=y +CONFIG_MSM_EEPROM=y +CONFIG_MSM_ISPIF=y +CONFIG_SONY_CAM_V4L2=y +CONFIG_MSMB_JPEG=y +CONFIG_MSM_VIDC_V4L2=y +CONFIG_MSM_WFD=y +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_VIDEOBUF2_MSM_MEM=y +# CONFIG_V4L_USB_DRIVERS is not set +CONFIG_V4L_PLATFORM_DRIVERS=y +# CONFIG_RADIO_ADAPTERS is not set +CONFIG_ION=y +CONFIG_ION_MSM=y +CONFIG_MSM_KGSL=y +CONFIG_KGSL_PER_PROCESS_PAGE_TABLE=y +CONFIG_FB=y +CONFIG_FB_MSM=y +# CONFIG_FB_MSM_BACKLIGHT is not set +CONFIG_FB_MSM_LOGO=y +CONFIG_FB_MSM_MDSS=y +CONFIG_FB_MSM_MDSS_WRITEBACK=y +CONFIG_FB_MSM_MDSS_SPECIFIC_PANEL=y +CONFIG_FB_MSM_MDSS_HDMI_PANEL=y +CONFIG_FB_MSM_MDSS_HDMI_MHL_SII8620_8061=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_LCD_CLASS_DEVICE is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_GENERIC is not set +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_USB_AUDIO=y +CONFIG_SND_SOC=y +CONFIG_SND_SOC_MSM8974=y +CONFIG_SND_SOC_APQ8074=y +CONFIG_HIDRAW=y +CONFIG_UHID=y +CONFIG_USB_HIDDEV=y +CONFIG_HID_A4TECH=y +CONFIG_HID_ACRUX=y +CONFIG_HID_ACRUX_FF=y +CONFIG_HID_APPLE=y +CONFIG_HID_BELKIN=y +CONFIG_HID_CHERRY=y +CONFIG_HID_CHICONY=y +CONFIG_HID_PRODIKEYS=y +CONFIG_HID_CYPRESS=y +CONFIG_HID_DRAGONRISE=y +CONFIG_DRAGONRISE_FF=y +CONFIG_HID_EMS_FF=y +CONFIG_HID_ELECOM=y +CONFIG_HID_EZKEY=y +CONFIG_HID_HOLTEK=y +CONFIG_HOLTEK_FF=y +CONFIG_HID_KEYTOUCH=y +CONFIG_HID_KYE=y +CONFIG_HID_UCLOGIC=y +CONFIG_HID_WALTOP=y +CONFIG_HID_GYRATION=y +CONFIG_HID_TWINHAN=y +CONFIG_HID_KENSINGTON=y +CONFIG_HID_LCPOWER=y +CONFIG_HID_LOGITECH=y +CONFIG_HID_LOGITECH_DJ=y +CONFIG_LOGITECH_FF=y +CONFIG_LOGIRUMBLEPAD2_FF=y +CONFIG_LOGIG940_FF=y +CONFIG_HID_MAGICMOUSE=y +CONFIG_HID_MICROSOFT=y +CONFIG_HID_MONTEREY=y +CONFIG_HID_MULTITOUCH=y +CONFIG_HID_NTRIG=y +CONFIG_HID_ORTEK=y +CONFIG_HID_PANTHERLORD=y +CONFIG_PANTHERLORD_FF=y +CONFIG_HID_PETALYNX=y +CONFIG_HID_PICOLCD=y +CONFIG_HID_PRIMAX=y +CONFIG_HID_ROCCAT=y +CONFIG_HID_SAITEK=y +CONFIG_HID_SAMSUNG=y +CONFIG_HID_SONY=y +CONFIG_HID_SPEEDLINK=y +CONFIG_HID_SUNPLUS=y +CONFIG_HID_GREENASIA=y +CONFIG_GREENASIA_FF=y +CONFIG_HID_SMARTJOYPLUS=y +CONFIG_SMARTJOYPLUS_FF=y +CONFIG_HID_TIVO=y +CONFIG_HID_TOPSEED=y +CONFIG_HID_THRUSTMASTER=y +CONFIG_HID_WACOM=y +CONFIG_HID_WIIMOTE=y +CONFIG_HID_ZEROPLUS=y +CONFIG_ZEROPLUS_FF=y +CONFIG_HID_ZYDACRON=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_SUSPEND=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_EHSET=y +CONFIG_USB_EHCI_MSM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_EHSET_TEST_FIXTURE=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_DEBUG_FILES=y +CONFIG_USB_DWC3_MSM=y +CONFIG_USB_G_ANDROID=y +CONFIG_MMC=y +CONFIG_MMC_PERF_PROFILING=y +CONFIG_MMC_CACHE_FEATURE=y +CONFIG_MMC_AWAKE_HS200=y +CONFIG_MMC_UNSAFE_RESUME=y +CONFIG_MMC_CLKGATE=y +CONFIG_MMC_PARANOID_SD_INIT=y +CONFIG_MMC_DEV_DRV_STR_TYPE4=y +CONFIG_MMC_DISABLE_STOP_REQUEST_SKHYNIX=y +CONFIG_MMC_BLOCK_MINORS=32 +# CONFIG_MMC_BLOCK_BOUNCE is not set +CONFIG_MMC_BLOCK_DEFERRED_RESUME=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_MSM=y +CONFIG_MMC_SDHCI_MSM=y +CONFIG_MMC_MSM_SPS_SUPPORT=y +CONFIG_LEDS_QPNP=y +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_BACKLIGHT=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_SWITCH=y +CONFIG_RTC_CLASS=y +# CONFIG_RTC_DRV_MSM is not set +CONFIG_RTC_DRV_QPNP=y +CONFIG_UIO=y +CONFIG_UIO_MSM_SHAREDMEM=y +CONFIG_STAGING=y +CONFIG_ZRAM=y +CONFIG_ZSMALLOC=y +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ASHMEM=y +CONFIG_ANDROID_LOGGER=y +CONFIG_ANDROID_TIMED_GPIO=y +CONFIG_ANDROID_LOW_MEMORY_KILLER=y +CONFIG_SPS=y +CONFIG_USB_BAM=y +CONFIG_SPS_SUPPORT_BAMDMA=y +CONFIG_SPS_SUPPORT_NDP_BAM=y +CONFIG_QPNP_PWM=y +CONFIG_QPNP_POWER_ON=y +CONFIG_QPNP_CLKDIV=y +CONFIG_QPNP_VIBRATOR=y +CONFIG_QPNP_REVID=y +CONFIG_QPNP_COINCELL=y +CONFIG_POWERKEY_FORCECRASH=y +CONFIG_MSM_IOMMU_V1=y +CONFIG_IOMMU_PGTABLES_L2=y +CONFIG_MSM_IOMMU_VBIF_CHECK=y +CONFIG_BIF=y +CONFIG_BIF_QPNP=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_FUSE_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_ECRYPT_FS=y +CONFIG_PSTORE=y +CONFIG_PSTORE_CONSOLE=y +CONFIG_PSTORE_RAM=y +CONFIG_PSTORE_RAM_ANNOTATION_APPEND=y +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_SECURITY=y +CONFIG_CIFS=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_UTF8=y +CONFIG_PRINTK_TIME=y +CONFIG_MAGIC_SYSRQ=y +# CONFIG_SYSRQ_SCHED_DEBUG is not set +CONFIG_SCHEDSTATS=y +CONFIG_TIMER_STATS=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_ENABLE_DEFAULT_TRACERS=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_STRICT_DEVMEM=y +CONFIG_PID_IN_CONTEXTIDR=y +CONFIG_KEYS=y +CONFIG_SECURITY=y +CONFIG_SECURITY_NETWORK=y +CONFIG_LSM_MMAP_MIN_ADDR=4096 +CONFIG_SECURITY_SELINUX=y +CONFIG_SECURITY_SELINUX_BOOTPARAM=y +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_XCBC=y +CONFIG_CRYPTO_SHA1_ARM_NEON=y +CONFIG_CRYPTO_SHA512_ARM_NEON=y +CONFIG_CRYPTO_AES_ARM_BS=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRYPTO_ANSI_CPRNG=y +CONFIG_CRYPTO_DEV_QCE=y +CONFIG_CRYPTO_DEV_QCEDEV=y diff --git a/arch/arm/hotplug/Kconfig b/arch/arm/hotplug/Kconfig new file mode 100644 index 000000000000..d0938ad229f9 --- /dev/null +++ b/arch/arm/hotplug/Kconfig @@ -0,0 +1,6 @@ +config INTELLI_PLUG + bool "Enable intelli-plug cpu hotplug driver" + default n + help + Generic Intelli-plug cpu hotplug driver for ARM SOCs + diff --git a/arch/arm/hotplug/Makefile b/arch/arm/hotplug/Makefile new file mode 100644 index 000000000000..cbf77347d56b --- /dev/null +++ b/arch/arm/hotplug/Makefile @@ -0,0 +1,2 @@ +obj-y := intelli_plug.o + diff --git a/arch/arm/hotplug/intelli_plug.c b/arch/arm/hotplug/intelli_plug.c new file mode 100644 index 000000000000..11b09daac3aa --- /dev/null +++ b/arch/arm/hotplug/intelli_plug.c @@ -0,0 +1,585 @@ +/* + * Author: Paul Reioux aka Faux123 + * + * Copyright 2012~2014 Paul Reioux + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_POWERSUSPEND +#include +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + +//#define DEBUG_INTELLI_PLUG +#undef DEBUG_INTELLI_PLUG + +#define INTELLI_PLUG_MAJOR_VERSION 3 +#define INTELLI_PLUG_MINOR_VERSION 8 + +#define DEF_SAMPLING_MS (268) + +#define DUAL_PERSISTENCE (2500 / DEF_SAMPLING_MS) +#define TRI_PERSISTENCE (1700 / DEF_SAMPLING_MS) +#define QUAD_PERSISTENCE (1000 / DEF_SAMPLING_MS) + +#define BUSY_PERSISTENCE (3500 / DEF_SAMPLING_MS) + +static DEFINE_MUTEX(intelli_plug_mutex); + +static struct delayed_work intelli_plug_work; +static struct delayed_work intelli_plug_boost; + +static struct workqueue_struct *intelliplug_wq; +static struct workqueue_struct *intelliplug_boost_wq; + +static unsigned int intelli_plug_active = 0; +module_param(intelli_plug_active, uint, 0644); + +static unsigned int touch_boost_active = 1; +module_param(touch_boost_active, uint, 0644); + +static unsigned int nr_run_profile_sel = 0; +module_param(nr_run_profile_sel, uint, 0644); + +//default to something sane rather than zero +static unsigned int sampling_time = DEF_SAMPLING_MS; + +static int persist_count = 0; + +static bool suspended = false; + +struct ip_cpu_info { + unsigned int sys_max; + unsigned int cur_max; + unsigned long cpu_nr_running; +}; + +static DEFINE_PER_CPU(struct ip_cpu_info, ip_info); + +static unsigned int screen_off_max = UINT_MAX; +module_param(screen_off_max, uint, 0644); + +#define CAPACITY_RESERVE 50 + +#if defined(CONFIG_ARCH_MSM8960) || defined(CONFIG_ARCH_APQ8064) || \ +defined(CONFIG_ARCH_MSM8974) +#define THREAD_CAPACITY (339 - CAPACITY_RESERVE) +#elif defined(CONFIG_ARCH_MSM8226) || defined (CONFIG_ARCH_MSM8926) || \ +defined (CONFIG_ARCH_MSM8610) || defined (CONFIG_ARCH_MSM8228) +#define THREAD_CAPACITY (190 - CAPACITY_RESERVE) +#else +#define THREAD_CAPACITY (250 - CAPACITY_RESERVE) +#endif + +#define MULT_FACTOR 4 +#define DIV_FACTOR 100000 +#define NR_FSHIFT 3 + +static unsigned int nr_fshift = NR_FSHIFT; + +static unsigned int nr_run_thresholds_balance[] = { + (THREAD_CAPACITY * 625 * MULT_FACTOR) / DIV_FACTOR, + (THREAD_CAPACITY * 875 * MULT_FACTOR) / DIV_FACTOR, + (THREAD_CAPACITY * 1125 * MULT_FACTOR) / DIV_FACTOR, + UINT_MAX +}; + +static unsigned int nr_run_thresholds_performance[] = { + (THREAD_CAPACITY * 380 * MULT_FACTOR) / DIV_FACTOR, + (THREAD_CAPACITY * 625 * MULT_FACTOR) / DIV_FACTOR, + (THREAD_CAPACITY * 875 * MULT_FACTOR) / DIV_FACTOR, + UINT_MAX +}; + +static unsigned int nr_run_thresholds_conservative[] = { + (THREAD_CAPACITY * 875 * MULT_FACTOR) / DIV_FACTOR, + (THREAD_CAPACITY * 1625 * MULT_FACTOR) / DIV_FACTOR, + (THREAD_CAPACITY * 2125 * MULT_FACTOR) / DIV_FACTOR, + UINT_MAX +}; + +static unsigned int nr_run_thresholds_eco[] = { + (THREAD_CAPACITY * 380 * MULT_FACTOR) / DIV_FACTOR, + UINT_MAX +}; + +static unsigned int nr_run_thresholds_eco_extreme[] = { + (THREAD_CAPACITY * 750 * MULT_FACTOR) / DIV_FACTOR, + UINT_MAX +}; + +static unsigned int nr_run_thresholds_disable[] = { + 0, 0, 0, UINT_MAX +}; + +static unsigned int *nr_run_profiles[] = { + nr_run_thresholds_balance, + nr_run_thresholds_performance, + nr_run_thresholds_conservative, + nr_run_thresholds_eco, + nr_run_thresholds_eco_extreme, + nr_run_thresholds_disable, +}; + +#define NR_RUN_ECO_MODE_PROFILE 3 +#define NR_RUN_HYSTERESIS_QUAD 8 +#define NR_RUN_HYSTERESIS_DUAL 4 + +#define CPU_NR_THRESHOLD ((THREAD_CAPACITY << 1) + (THREAD_CAPACITY / 2)) + +static unsigned int nr_possible_cores; +module_param(nr_possible_cores, uint, 0444); + +static unsigned int cpu_nr_run_threshold = CPU_NR_THRESHOLD; +module_param(cpu_nr_run_threshold, uint, 0644); + +static unsigned int nr_run_hysteresis = NR_RUN_HYSTERESIS_QUAD; +module_param(nr_run_hysteresis, uint, 0644); + +static unsigned int nr_run_last; + +extern unsigned long avg_nr_running(void); +extern unsigned long avg_cpu_nr_running(unsigned int cpu); + +static unsigned int calculate_thread_stats(void) +{ + unsigned int avg_nr_run = avg_nr_running(); + unsigned int nr_run; + unsigned int threshold_size; + unsigned int *current_profile; + + current_profile = nr_run_profiles[nr_run_profile_sel]; + if (num_possible_cpus() > 2) { + if (nr_run_profile_sel >= NR_RUN_ECO_MODE_PROFILE) + threshold_size = + ARRAY_SIZE(nr_run_thresholds_eco); + else + threshold_size = + ARRAY_SIZE(nr_run_thresholds_balance); + } else + threshold_size = + ARRAY_SIZE(nr_run_thresholds_eco); + + if (nr_run_profile_sel >= NR_RUN_ECO_MODE_PROFILE) + nr_fshift = 1; + else + nr_fshift = num_possible_cpus() - 1; + + for (nr_run = 1; nr_run < threshold_size; nr_run++) { + unsigned int nr_threshold; + nr_threshold = current_profile[nr_run - 1]; + + if (nr_run_last <= nr_run) + nr_threshold += nr_run_hysteresis; + if (avg_nr_run <= (nr_threshold << (FSHIFT - nr_fshift))) + break; + } + nr_run_last = nr_run; + + return nr_run; +} + +static void __cpuinit intelli_plug_boost_fn(struct work_struct *work) +{ + + int nr_cpus = num_online_cpus(); + + if (intelli_plug_active) + if (touch_boost_active) + if (nr_cpus < 2) + cpu_up(1); +} + +/* +static int cmp_nr_running(const void *a, const void *b) +{ + return *(unsigned long *)a - *(unsigned long *)b; +} +*/ + +static void update_per_cpu_stat(void) +{ + unsigned int cpu; + struct ip_cpu_info *l_ip_info; + + for_each_online_cpu(cpu) { + l_ip_info = &per_cpu(ip_info, cpu); + l_ip_info->cpu_nr_running = avg_cpu_nr_running(cpu); +#ifdef DEBUG_INTELLI_PLUG + pr_info("cpu %u nr_running => %lu\n", cpu, + l_ip_info->cpu_nr_running); +#endif + } +} + +static void unplug_cpu(int min_active_cpu) +{ + unsigned int cpu; + struct ip_cpu_info *l_ip_info; + int l_nr_threshold; + + for_each_online_cpu(cpu) { + l_nr_threshold = + cpu_nr_run_threshold << 1 / (num_online_cpus()); + if (cpu == 0) + continue; + l_ip_info = &per_cpu(ip_info, cpu); + if (cpu > min_active_cpu) + if (l_ip_info->cpu_nr_running < l_nr_threshold) + cpu_down(cpu); + } +} + +static void __cpuinit intelli_plug_work_fn(struct work_struct *work) +{ + unsigned int nr_run_stat; + unsigned int cpu_count = 0; + unsigned int nr_cpus = 0; + + int i; + + if (intelli_plug_active) { + nr_run_stat = calculate_thread_stats(); + update_per_cpu_stat(); +#ifdef DEBUG_INTELLI_PLUG + pr_info("nr_run_stat: %u\n", nr_run_stat); +#endif + cpu_count = nr_run_stat; + nr_cpus = num_online_cpus(); + + if (!suspended) { + + if (persist_count > 0) + persist_count--; + + switch (cpu_count) { + case 1: + if (persist_count == 0) { + //take down everyone + unplug_cpu(0); + } +#ifdef DEBUG_INTELLI_PLUG + pr_info("case 1: %u\n", persist_count); +#endif + break; + case 2: + if (persist_count == 0) + persist_count = DUAL_PERSISTENCE; + if (nr_cpus < 2) { + for (i = 1; i < cpu_count; i++) + cpu_up(i); + } else { + unplug_cpu(1); + } +#ifdef DEBUG_INTELLI_PLUG + pr_info("case 2: %u\n", persist_count); +#endif + break; + case 3: + if (persist_count == 0) + persist_count = TRI_PERSISTENCE; + if (nr_cpus < 3) { + for (i = 1; i < cpu_count; i++) + cpu_up(i); + } else { + unplug_cpu(2); + } +#ifdef DEBUG_INTELLI_PLUG + pr_info("case 3: %u\n", persist_count); +#endif + break; + case 4: + if (persist_count == 0) + persist_count = QUAD_PERSISTENCE; + if (nr_cpus < 4) + for (i = 1; i < cpu_count; i++) + cpu_up(i); +#ifdef DEBUG_INTELLI_PLUG + pr_info("case 4: %u\n", persist_count); +#endif + break; + default: + pr_err("Run Stat Error: Bad value %u\n", nr_run_stat); + break; + } + } +#ifdef DEBUG_INTELLI_PLUG + else + pr_info("intelli_plug is suspened!\n"); +#endif + } + queue_delayed_work_on(0, intelliplug_wq, &intelli_plug_work, + msecs_to_jiffies(sampling_time)); +} + +#if defined(CONFIG_POWERSUSPEND) || defined(CONFIG_HAS_EARLYSUSPEND) +static void screen_off_limit(bool on) +{ + unsigned int cpu; + struct cpufreq_policy *policy; + struct ip_cpu_info *l_ip_info; + + /* not active, so exit */ + if (screen_off_max == UINT_MAX) + return; + + for_each_online_cpu(cpu) { + l_ip_info = &per_cpu(ip_info, cpu); + policy = cpufreq_cpu_get(0); + + if (on) { + /* save current instance */ + l_ip_info->cur_max = policy->max; + policy->max = screen_off_max; + policy->cpuinfo.max_freq = screen_off_max; +#ifdef DEBUG_INTELLI_PLUG + pr_info("cpuinfo max is (on): %u %u\n", + policy->cpuinfo.max_freq, l_ip_info->sys_max); +#endif + } else { + /* restore */ + if (cpu != 0) { + l_ip_info = &per_cpu(ip_info, 0); + } + policy->cpuinfo.max_freq = l_ip_info->sys_max; + policy->max = l_ip_info->cur_max; +#ifdef DEBUG_INTELLI_PLUG + pr_info("cpuinfo max is (off): %u %u\n", + policy->cpuinfo.max_freq, l_ip_info->sys_max); +#endif + } + cpufreq_update_policy(cpu); + } +} + +#ifdef CONFIG_POWERSUSPEND +static void intelli_plug_suspend(struct power_suspend *handler) +#else +static void intelli_plug_suspend(struct early_suspend *handler) +#endif +{ + if (intelli_plug_active) { + int cpu; + + flush_workqueue(intelliplug_wq); + + mutex_lock(&intelli_plug_mutex); + suspended = true; + screen_off_limit(true); + mutex_unlock(&intelli_plug_mutex); + + // put rest of the cores to sleep unconditionally! + for_each_online_cpu(cpu) { + if (cpu != 0) + cpu_down(cpu); + } + } +} + +static void wakeup_boost(void) +{ + unsigned int cpu; + struct cpufreq_policy *policy; + struct ip_cpu_info *l_ip_info; + + for_each_online_cpu(cpu) { + policy = cpufreq_cpu_get(0); + l_ip_info = &per_cpu(ip_info, 0); + policy->cur = l_ip_info->cur_max; + cpufreq_update_policy(cpu); + } +} + +#ifdef CONFIG_POWERSUSPEND +static void __cpuinit intelli_plug_resume(struct power_suspend *handler) +#else +static void __cpuinit intelli_plug_resume(struct early_suspend *handler) +#endif +{ + + if (intelli_plug_active) { + int cpu; + + mutex_lock(&intelli_plug_mutex); + /* keep cores awake long enough for faster wake up */ + persist_count = BUSY_PERSISTENCE; + suspended = false; + mutex_unlock(&intelli_plug_mutex); + + for_each_possible_cpu(cpu) { + if (cpu == 0) + continue; + cpu_up(cpu); + } + + wakeup_boost(); + screen_off_limit(false); + } + queue_delayed_work_on(0, intelliplug_wq, &intelli_plug_work, + msecs_to_jiffies(10)); +} +#endif + +#ifdef CONFIG_POWERSUSPEND +static struct power_suspend intelli_plug_power_suspend_driver = { + .suspend = intelli_plug_suspend, + .resume = intelli_plug_resume, +}; +#endif /* CONFIG_POWERSUSPEND */ + +#ifdef CONFIG_HAS_EARLYSUSPEND +static struct early_suspend intelli_plug_early_suspend_driver = { + .level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 10, + .suspend = intelli_plug_suspend, + .resume = intelli_plug_resume, +}; +#endif /* CONFIG_HAS_EARLYSUSPEND */ + +static void intelli_plug_input_event(struct input_handle *handle, + unsigned int type, unsigned int code, int value) +{ +#ifdef DEBUG_INTELLI_PLUG + pr_info("intelli_plug touched!\n"); +#endif + queue_delayed_work_on(0, intelliplug_wq, &intelli_plug_boost, + msecs_to_jiffies(10)); +} + +static int intelli_plug_input_connect(struct input_handler *handler, + struct input_dev *dev, const struct input_device_id *id) +{ + struct input_handle *handle; + int error; + + handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "intelliplug"; + + error = input_register_handle(handle); + if (error) + goto err2; + + error = input_open_device(handle); + if (error) + goto err1; + pr_info("%s found and connected!\n", dev->name); + return 0; +err1: + input_unregister_handle(handle); +err2: + kfree(handle); + return error; +} + +static void intelli_plug_input_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id intelli_plug_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT_MASK(EV_ABS) }, + .absbit = { [BIT_WORD(ABS_MT_POSITION_X)] = + BIT_MASK(ABS_MT_POSITION_X) | + BIT_MASK(ABS_MT_POSITION_Y) }, + }, /* multi-touch touchscreen */ + { + .flags = INPUT_DEVICE_ID_MATCH_KEYBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, + .keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) }, + .absbit = { [BIT_WORD(ABS_X)] = + BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) }, + }, /* touchpad */ + { }, +}; + +static struct input_handler intelli_plug_input_handler = { + .event = intelli_plug_input_event, + .connect = intelli_plug_input_connect, + .disconnect = intelli_plug_input_disconnect, + .name = "intelliplug_handler", + .id_table = intelli_plug_ids, +}; + +int __init intelli_plug_init(void) +{ + int rc; +#if defined (CONFIG_POWERSUSPEND) || defined(CONFIG_HAS_EARLYSUSPEND) + struct cpufreq_policy *policy; + struct ip_cpu_info *l_ip_info; +#endif + + nr_possible_cores = num_possible_cpus(); + + pr_info("intelli_plug: version %d.%d by faux123\n", + INTELLI_PLUG_MAJOR_VERSION, + INTELLI_PLUG_MINOR_VERSION); + + if (nr_possible_cores > 2) { + nr_run_hysteresis = NR_RUN_HYSTERESIS_QUAD; + nr_run_profile_sel = 0; + } else { + nr_run_hysteresis = NR_RUN_HYSTERESIS_DUAL; + nr_run_profile_sel = NR_RUN_ECO_MODE_PROFILE; + } + +#if defined (CONFIG_POWERSUSPEND) || defined(CONFIG_HAS_EARLYSUSPEND) + l_ip_info = &per_cpu(ip_info, 0); + policy = cpufreq_cpu_get(0); + l_ip_info->sys_max = policy->cpuinfo.max_freq; + l_ip_info->cur_max = policy->max; +#endif + + rc = input_register_handler(&intelli_plug_input_handler); +#ifdef CONFIG_POWERSUSPEND + register_power_suspend(&intelli_plug_power_suspend_driver); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + register_early_suspend(&intelli_plug_early_suspend_driver); +#endif + intelliplug_wq = alloc_workqueue("intelliplug", + WQ_HIGHPRI | WQ_UNBOUND, 1); + intelliplug_boost_wq = alloc_workqueue("iplug_boost", + WQ_HIGHPRI | WQ_UNBOUND, 1); + INIT_DELAYED_WORK(&intelli_plug_work, intelli_plug_work_fn); + INIT_DELAYED_WORK(&intelli_plug_boost, intelli_plug_boost_fn); + queue_delayed_work_on(0, intelliplug_wq, &intelli_plug_work, + msecs_to_jiffies(10)); + + return 0; +} + +MODULE_AUTHOR("Paul Reioux "); +MODULE_DESCRIPTION("'intell_plug' - An intelligent cpu hotplug driver for " + "Low Latency Frequency Transition capable processors"); +MODULE_LICENSE("GPL"); + +late_initcall(intelli_plug_init); diff --git a/arch/arm/include/asm/kexec.h b/arch/arm/include/asm/kexec.h index c2b9b4bdec00..564c55b394e5 100644 --- a/arch/arm/include/asm/kexec.h +++ b/arch/arm/include/asm/kexec.h @@ -17,6 +17,10 @@ #define KEXEC_ARM_ATAGS_OFFSET 0x1000 #define KEXEC_ARM_ZIMAGE_OFFSET 0x8000 +#ifdef CONFIG_KEXEC_HARDBOOT + #define KEXEC_HB_PAGE_MAGIC 0x4a5db007 +#endif + #ifndef __ASSEMBLY__ /** @@ -53,6 +57,10 @@ static inline void crash_setup_regs(struct pt_regs *newregs, /* Function pointer to optional machine-specific reinitialization */ extern void (*kexec_reinit)(void); +#ifdef CONFIG_KEXEC_HARDBOOT +extern void (*kexec_hardboot_hook)(void); +#endif + #endif /* __ASSEMBLY__ */ #endif /* CONFIG_KEXEC */ diff --git a/arch/arm/kernel/machine_kexec.c b/arch/arm/kernel/machine_kexec.c index c355aebdf2d0..519f107af283 100644 --- a/arch/arm/kernel/machine_kexec.c +++ b/arch/arm/kernel/machine_kexec.c @@ -14,6 +14,9 @@ #include #include #include +#include +#include +#include extern const unsigned char relocate_new_kernel[]; extern const unsigned int relocate_new_kernel_size; @@ -23,6 +26,13 @@ extern unsigned long kexec_indirection_page; extern unsigned long kexec_mach_type; extern unsigned long kexec_boot_atags; +#ifdef CONFIG_KEXEC_HARDBOOT +extern unsigned long kexec_hardboot; +extern unsigned long kexec_boot_atags_len; +extern unsigned long kexec_kernel_len; +void (*kexec_hardboot_hook)(void); +#endif + static atomic_t waiting_for_crash_ipi; /* @@ -32,6 +42,37 @@ static atomic_t waiting_for_crash_ipi; int machine_kexec_prepare(struct kimage *image) { + struct kexec_segment *current_segment; + __be32 header; + int i, err; + + /* No segment at default ATAGs address. try to locate + * a dtb using magic */ + for (i = 0; i < image->nr_segments; i++) { + current_segment = &image->segment[i]; + + err = memblock_is_region_memory(current_segment->mem, + current_segment->memsz); + if (!err) + return - EINVAL; + +#ifdef CONFIG_KEXEC_HARDBOOT + if(current_segment->mem == image->start) + mem_text_write_kernel_word(&kexec_kernel_len, current_segment->memsz); +#endif + + err = get_user(header, (__be32*)current_segment->buf); + if (err) + return err; + + if (be32_to_cpu(header) == OF_DT_HEADER) + { + mem_text_write_kernel_word(&kexec_boot_atags, current_segment->mem); +#ifdef CONFIG_KEXEC_HARDBOOT + mem_text_write_kernel_word(&kexec_boot_atags_len, current_segment->memsz); +#endif + } + } return 0; } @@ -120,10 +161,14 @@ void machine_kexec(struct kimage *image) reboot_code_buffer = page_address(image->control_code_page); /* Prepare parameters for reboot_code_buffer*/ - kexec_start_address = image->start; - kexec_indirection_page = page_list; - kexec_mach_type = machine_arch_type; - kexec_boot_atags = image->start - KEXEC_ARM_ZIMAGE_OFFSET + KEXEC_ARM_ATAGS_OFFSET; + mem_text_write_kernel_word(&kexec_start_address, image->start); + mem_text_write_kernel_word(&kexec_indirection_page, page_list); + mem_text_write_kernel_word(&kexec_mach_type, machine_arch_type); + if (!kexec_boot_atags) + mem_text_write_kernel_word(&kexec_boot_atags, image->start - KEXEC_ARM_ZIMAGE_OFFSET + KEXEC_ARM_ATAGS_OFFSET); +#ifdef CONFIG_KEXEC_HARDBOOT + mem_text_write_kernel_word(&kexec_hardboot, image->hardboot); +#endif /* copy our kernel relocation code to the control code page */ memcpy(reboot_code_buffer, @@ -137,5 +182,11 @@ void machine_kexec(struct kimage *image) if (kexec_reinit) kexec_reinit(); +#ifdef CONFIG_KEXEC_HARDBOOT + /* Run any final machine-specific shutdown code. */ + if (image->hardboot && kexec_hardboot_hook) + kexec_hardboot_hook(); +#endif + soft_restart(reboot_code_buffer_phys); } diff --git a/arch/arm/kernel/relocate_kernel.S b/arch/arm/kernel/relocate_kernel.S index d0cdedf4864d..ba02dc81e954 100644 --- a/arch/arm/kernel/relocate_kernel.S +++ b/arch/arm/kernel/relocate_kernel.S @@ -4,6 +4,11 @@ #include +#ifdef CONFIG_KEXEC_HARDBOOT +#include +#include +#endif + .globl relocate_new_kernel relocate_new_kernel: @@ -52,6 +57,12 @@ relocate_new_kernel: b 0b 2: +#ifdef CONFIG_KEXEC_HARDBOOT + ldr r0, kexec_hardboot + teq r0, #0 + bne hardboot +#endif + /* Jump to relocated kernel */ mov lr,r1 mov r0,#0 @@ -60,6 +71,40 @@ relocate_new_kernel: ARM( mov pc, lr ) THUMB( bx lr ) +#ifdef CONFIG_KEXEC_HARDBOOT +hardboot: + /* Stash boot arguments in hardboot page: + * 0: KEXEC_HB_PAGE_MAGIC + * 4: kexec_start_address + * 8: kexec_mach_type + * 12: kexec_boot_atags + * 16: kexec_boot_atags_len + * 20: kexec_kernel_len */ + ldr r0, =KEXEC_HB_PAGE_ADDR + str r1, [r0, #4] + ldr r1, kexec_mach_type + str r1, [r0, #8] + ldr r1, kexec_boot_atags + str r1, [r0, #12] + ldr r1, kexec_boot_atags_len + str r1, [r0, #16] + ldr r1, kexec_kernel_len + str r1, [r0, #20] + ldr r1, =KEXEC_HB_PAGE_MAGIC + str r1, [r0] + +#if defined(CONFIG_ARCH_MSM8974) + /* Restart using the PMIC chip, see mach-msm/restart.c */ + ldr r0, =MSM8974_MPM2_PSHOLD_PHYS + mov r1, #0 + str r1, [r0, #0] +loop: b loop +#else +#error "No reboot method defined for hardboot." +#endif + + .ltorg +#endif .align .globl kexec_start_address @@ -79,6 +124,20 @@ kexec_mach_type: kexec_boot_atags: .long 0x0 +#ifdef CONFIG_KEXEC_HARDBOOT + .globl kexec_boot_atags_len +kexec_boot_atags_len: + .long 0x0 + + .globl kexec_kernel_len +kexec_kernel_len: + .long 0x0 + + .globl kexec_hardboot +kexec_hardboot: + .long 0x0 +#endif + relocate_new_kernel_end: .globl relocate_new_kernel_size diff --git a/arch/arm/mach-msm/Kconfig b/arch/arm/mach-msm/Kconfig index 65a393740f1b..c167434faca4 100644 --- a/arch/arm/mach-msm/Kconfig +++ b/arch/arm/mach-msm/Kconfig @@ -1223,6 +1223,16 @@ config MACH_SONY_LEO If you enable this config, Please use SONY Mobile source tree. +config MACH_SONY_LEO_DSDS + depends on ARCH_MSM8974 + bool "Sony Mobile Leo DSDS" + select MACH_SONY_SHINANO + help + Support for the SONY Mobile Leo DSDS device. + It is based on QCOM MSM8974 AC chipset. + If you enable this config, + Please use SONY Mobile source tree. + config MACH_SONY_ARIES depends on ARCH_MSM8974 bool "Sony Mobile Aries" @@ -2017,6 +2027,14 @@ config MSM_DALRPC_TEST help Exercises DAL RPC calls to QDSP6. +config MSM_MPDEC + bool "Enable kernel based mpdecision" + depends on MSM_SMP + default n + help + This enables kernel based multi core control. + (up/down hotplug based on load) + if CPU_FREQ_MSM config MSM_CPU_FREQ_SET_MIN_MAX diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile index 497e78214af8..8223f04d04b5 100644 --- a/arch/arm/mach-msm/Makefile +++ b/arch/arm/mach-msm/Makefile @@ -325,6 +325,8 @@ else ifdef CONFIG_MACH_SONY_CASTOR_WINDY obj-$(CONFIG_MACH_SONY_CASTOR_WINDY) += board-sony_castor_windy-gpiomux.o board-sony_shinano-nfc.o bms-batterydata-vega.o else ifdef CONFIG_MACH_SONY_LEO obj-$(CONFIG_MACH_SONY_LEO) += board-sony_leo-gpiomux.o board-sony_leo_samba-gpiomux-diff.o bms-batterydata-leo.o board-sony_shinano-nfc.o +else ifdef CONFIG_MACH_SONY_LEO_DSDS + obj-$(CONFIG_MACH_SONY_LEO_DSDS) += board-sony_leo_dsds-gpiomux.o bms-batterydata-leo.o board-sony_shinano-nfc.o else ifdef CONFIG_MACH_SONY_ARIES obj-$(CONFIG_MACH_SONY_ARIES) += board-sony_aries-gpiomux.o bms-batterydata-aries.o board-sony_shinano-nfc.o else ifdef CONFIG_MACH_SONY_SCORPION @@ -402,7 +404,14 @@ obj-$(CONFIG_ARCH_APQ8084) += gpiomux-v2.o gpiomux.o obj-$(CONFIG_ARCH_FSM9900) += gpiomux-v2.o gpiomux.o obj-$(CONFIG_MSM_SLEEP_STATS_DEVICE) += idle_stats_device.o -obj-$(CONFIG_MSM_DCVS) += msm_dcvs_scm.o msm_dcvs.o msm_mpdecision.o +obj-$(CONFIG_MSM_DCVS) += msm_dcvs_scm.o msm_dcvs.o + +ifdef CONFIG_MSM_MPDEC +obj-y += msm_mpdecision.o +else +obj-$(CONFIG_MSM_DCVS) += msm_mpdecision_qcom.o +endif + obj-$(CONFIG_MSM_RUN_QUEUE_STATS) += msm_rq_stats.o obj-$(CONFIG_MSM_SHOW_RESUME_IRQ) += msm_show_resume_irq.o obj-$(CONFIG_BT_MSM_PINTEST) += btpintest.o diff --git a/arch/arm/mach-msm/Makefile.boot b/arch/arm/mach-msm/Makefile.boot index 2d0c6d34062d..656abc7c06c1 100644 --- a/arch/arm/mach-msm/Makefile.boot +++ b/arch/arm/mach-msm/Makefile.boot @@ -52,6 +52,7 @@ endif dtb-$(CONFIG_MACH_SONY_CASTOR) += msm8974pro-ab-shinano_castor.dtb dtb-$(CONFIG_MACH_SONY_CASTOR_WINDY) += apq8074pro-ab-shinano_castor_windy.dtb dtb-$(CONFIG_MACH_SONY_LEO) += msm8974pro-ac-shinano_leo.dtb + dtb-$(CONFIG_MACH_SONY_LEO_DSDS) += msm8974pro-ac-shinano_leo.dtb dtb-$(CONFIG_MACH_SONY_ARIES) += msm8974pro-ac-shinano_aries.dtb dtb-$(CONFIG_MACH_SONY_SCORPION) += msm8974pro-ac-shinano_scorpion.dtb dtb-$(CONFIG_MACH_SONY_SCORPION_WINDY)+= apq8074pro-ac-shinano_scorpion_windy.dtb diff --git a/arch/arm/mach-msm/board-8974.c b/arch/arm/mach-msm/board-8974.c index 996e0d765216..9e822a7f7e49 100644 --- a/arch/arm/mach-msm/board-8974.c +++ b/arch/arm/mach-msm/board-8974.c @@ -48,7 +48,6 @@ #include "modem_notifier.h" #include "platsmp.h" - static struct memtype_reserve msm8974_reserve_table[] __initdata = { [MEMTYPE_SMI] = { }, @@ -72,6 +71,9 @@ static struct reserve_info msm8974_reserve_info __initdata = { void __init msm_8974_reserve(void) { +#ifdef CONFIG_ANDROID_PERSISTENT_RAM + reserve_persistent_ram(); +#endif reserve_info = &msm8974_reserve_info; of_scan_flat_dt(dt_scan_for_memory_reserve, msm8974_reserve_table); msm_reserve(); diff --git a/arch/arm/mach-msm/board-sony_leo_dsds-gpiomux.c b/arch/arm/mach-msm/board-sony_leo_dsds-gpiomux.c new file mode 100644 index 000000000000..910a3b4d9bcb --- /dev/null +++ b/arch/arm/mach-msm/board-sony_leo_dsds-gpiomux.c @@ -0,0 +1,1124 @@ +/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * Copyright (C) 2014 Sony Mobile Communications Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + * + */ + +#include +#include +#include +#include +#include "sony_gpiomux.h" + +static struct gpiomux_setting unused_gpio = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + .dir = GPIOMUX_OUT_LOW, +}; + +static struct gpiomux_setting gpio_2ma_no_pull_out_low = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + .dir = GPIOMUX_OUT_LOW, +}; + +static struct gpiomux_setting gpio_2ma_no_pull_out_high = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + .dir = GPIOMUX_OUT_HIGH, +}; + +static struct gpiomux_setting gpio_2ma_no_pull_in = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + .dir = GPIOMUX_IN, +}; + +static struct gpiomux_setting gpio_2ma_pull_down_in = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, + .dir = GPIOMUX_IN, +}; + +static struct gpiomux_setting gpio_2ma_pull_up_in = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, + .dir = GPIOMUX_IN, +}; + +static struct gpiomux_setting gpio_follow_qct = { + .func = GPIOMUX_FOLLOW_QCT, + .drv = GPIOMUX_FOLLOW_QCT, + .pull = GPIOMUX_FOLLOW_QCT, + .dir = GPIOMUX_FOLLOW_QCT, +}; + +static struct gpiomux_setting gpio_2ma_follow_qct = { + .func = GPIOMUX_FOLLOW_QCT, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_FOLLOW_QCT, + .dir = GPIOMUX_FOLLOW_QCT, +}; + +static struct gpiomux_setting mhl_spi = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting debug_uart_tx = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_4MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting debug_uart_rx = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting cam_mclk = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cam_i2c = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting nfc_i2c = { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting wlan_sdio_active = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_10MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting wlan_sdio_suspend = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting wlan_sdio_clk_active = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_10MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting wlan_sdio_clk_suspend = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting bt_uart_tx = { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting bt_uart_rx = { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting bt_uart_cts = { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting bt_uart_rts = { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting ts_i2c = { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting damp_i2s = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting bt_pcm = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting peripheral_i2c = { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting sensors_i2c = { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting hsic = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting mdp_vsync_p = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct msm_gpiomux_config touch_config __initdata = { + .gpio = 85, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_high, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_high, + }, +}; + +static struct msm_gpiomux_config shinano_all_configs[] __initdata = { + { /* MHL_SPI_MOSI */ + .gpio = 0, + .settings = { + [GPIOMUX_ACTIVE] = &mhl_spi, + [GPIOMUX_SUSPENDED] = &mhl_spi, + }, + }, + { /* MHL_SPI_MISO */ + .gpio = 1, + .settings = { + [GPIOMUX_ACTIVE] = &mhl_spi, + [GPIOMUX_SUSPENDED] = &mhl_spi, + }, + }, + { /* MHL_SPI_CS_N */ + .gpio = 2, + .settings = { + [GPIOMUX_ACTIVE] = &mhl_spi, + [GPIOMUX_SUSPENDED] = &mhl_spi, + }, + }, + { /* MHL_SPI_CLK */ + .gpio = 3, + .settings = { + [GPIOMUX_ACTIVE] = &mhl_spi, + [GPIOMUX_SUSPENDED] = &mhl_spi, + }, + }, + { /* UART_TX_DFMS */ + .gpio = 4, + .settings = { + [GPIOMUX_ACTIVE] = &debug_uart_tx, + [GPIOMUX_SUSPENDED] = &debug_uart_tx, + }, + }, + { /* UART_RX_DTMS */ + .gpio = 5, + .settings = { + [GPIOMUX_ACTIVE] = &debug_uart_rx, + [GPIOMUX_SUSPENDED] = &debug_uart_rx, + }, + }, + { /* NC */ + .gpio = 6, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 7, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* SW_SERVICE */ + .gpio = 8, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_pull_up_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_up_in, + }, + }, + { /* UIM1_DETECT */ + .gpio = 9, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_in, + }, + }, + { /* MHL_SWITCH_SEL_1 */ + .gpio = 10, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* MHL_SWITCH_SEL_2 */ + .gpio = 11, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* MDP_VSYNC_P */ + .gpio = 12, + .settings = { + [GPIOMUX_ACTIVE] = &mdp_vsync_p, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_down_in, + }, + }, + { /* NC */ + .gpio = 13, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 14, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* CAM0_MCLK0 */ + .gpio = 15, + .settings = { + [GPIOMUX_ACTIVE] = &cam_mclk, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* MHL_RST_N */ + .gpio = 16, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* CAM1_MCLK2 */ + .gpio = 17, + .settings = { + [GPIOMUX_ACTIVE] = &cam_mclk, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* CHATCAM_RESET_N */ + .gpio = 18, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* CAM0_I2C_SDA0 */ + .gpio = 19, + .settings = { + [GPIOMUX_ACTIVE] = &cam_i2c, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_down_in, + }, + }, + { /* CAM0_I2C_SCL0 */ + .gpio = 20, + .settings = { + [GPIOMUX_ACTIVE] = &cam_i2c, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_down_in, + }, + }, + { /* CAM1_I2C_SDA1 */ + .gpio = 21, + .settings = { + [GPIOMUX_ACTIVE] = &cam_i2c, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_down_in, + }, + }, + { /* CAM1_I2C_SCL1 */ + .gpio = 22, + .settings = { + [GPIOMUX_ACTIVE] = &cam_i2c, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_down_in, + }, + }, + { /* MHL_PWR_EN */ + .gpio = 23, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* NFC_IRQ_FELICA_INT_N */ + .gpio = 24, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_in, + }, + }, + { /* PON_VOLTAGE_SEL */ + .gpio = 25, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_in, + }, + }, + { /* LCD_ID */ + .gpio = 26, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_pull_up_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_up_in, + }, + }, + { /* NC */ + .gpio = 27, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 28, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NFC_I2C_SDA */ + .gpio = 29, + .settings = { + [GPIOMUX_ACTIVE] = &nfc_i2c, + [GPIOMUX_SUSPENDED] = &nfc_i2c, + }, + }, + { /* NFC_I2C_SCL */ + .gpio = 30, + .settings = { + [GPIOMUX_ACTIVE] = &nfc_i2c, + [GPIOMUX_SUSPENDED] = &nfc_i2c, + }, + }, + { /* MHL_FW_WAKE */ + .gpio = 31, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* HDMI_DDC_CLK */ + .gpio = 32, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* HDMI_DDC_DATA */ + .gpio = 33, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* HDMI_HOT_PLUG_DET */ + .gpio = 34, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* WLAN_SDIO_DATA_3 */ + .gpio = 35, + .settings = { + [GPIOMUX_ACTIVE] = &wlan_sdio_active, + [GPIOMUX_SUSPENDED] = &wlan_sdio_suspend, + }, + }, + { /* WLAN_SDIO_DATA_2 */ + .gpio = 36, + .settings = { + [GPIOMUX_ACTIVE] = &wlan_sdio_active, + [GPIOMUX_SUSPENDED] = &wlan_sdio_suspend, + }, + }, + { /* WLAN_SDIO_DATA_1 */ + .gpio = 37, + .settings = { + [GPIOMUX_ACTIVE] = &wlan_sdio_active, + [GPIOMUX_SUSPENDED] = &wlan_sdio_suspend, + }, + }, + { /* WLAN_SDIO_DATA_0 */ + .gpio = 38, + .settings = { + [GPIOMUX_ACTIVE] = &wlan_sdio_active, + [GPIOMUX_SUSPENDED] = &wlan_sdio_suspend, + }, + }, + { /* WLAN_SDIO_CMD */ + .gpio = 39, + .settings = { + [GPIOMUX_ACTIVE] = &wlan_sdio_active, + [GPIOMUX_SUSPENDED] = &wlan_sdio_suspend, + }, + }, + { /* WLAN_SDIO_CLK */ + .gpio = 40, + .settings = { + [GPIOMUX_ACTIVE] = &wlan_sdio_clk_active, + [GPIOMUX_SUSPENDED] = &wlan_sdio_clk_suspend, + }, + }, + { /* BT_UART_TX */ + .gpio = 41, + .settings = { + [GPIOMUX_ACTIVE] = &bt_uart_tx, + [GPIOMUX_SUSPENDED] = &bt_uart_tx, + }, + }, + { /* BT_UART_RX */ + .gpio = 42, + .settings = { + [GPIOMUX_ACTIVE] = &bt_uart_rx, + [GPIOMUX_SUSPENDED] = &bt_uart_rx, + }, + }, + { /* BT_UART_CTS_N */ + .gpio = 43, + .settings = { + [GPIOMUX_ACTIVE] = &bt_uart_cts, + [GPIOMUX_SUSPENDED] = &bt_uart_cts, + }, + }, + { /* BT_UART_RTS_N */ + .gpio = 44, + .settings = { + [GPIOMUX_ACTIVE] = &bt_uart_rts, + [GPIOMUX_SUSPENDED] = &bt_uart_rts, + }, + }, + { /* NC */ + .gpio = 45, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 46, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* TS_I2C_SDA */ + .gpio = 47, + .settings = { + [GPIOMUX_ACTIVE] = &ts_i2c, + [GPIOMUX_SUSPENDED] = &ts_i2c, + }, + }, + { /* TS_I2C_SCL */ + .gpio = 48, + .settings = { + [GPIOMUX_ACTIVE] = &ts_i2c, + [GPIOMUX_SUSPENDED] = &ts_i2c, + }, + }, + { /* UIM2_DATA */ + .gpio = 49, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* UIM2_CLK */ + .gpio = 50, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* UIM2_RST */ + .gpio = 51, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* UIM2_DETECT */ + .gpio = 52, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* NC */ + .gpio = 53, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 54, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* DEBUG_GPIO0 */ + .gpio = 55, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* DEBUG_GPIO1 */ + .gpio = 56, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* NFC_DWLD_EN */ + .gpio = 57, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* DAMP_I2S_CLK */ + .gpio = 58, + .settings = { + [GPIOMUX_ACTIVE] = &damp_i2s, + [GPIOMUX_SUSPENDED] = &damp_i2s, + }, + }, + { /* DAMP_I2S_WS */ + .gpio = 59, + .settings = { + [GPIOMUX_ACTIVE] = &damp_i2s, + [GPIOMUX_SUSPENDED] = &damp_i2s, + }, + }, + { /* DAMP_I2S_D0 */ + .gpio = 60, + .settings = { + [GPIOMUX_ACTIVE] = &damp_i2s, + [GPIOMUX_SUSPENDED] = &damp_i2s, + }, + }, + { /* DAMP_I2S_D1 */ + .gpio = 61, + .settings = { + [GPIOMUX_ACTIVE] = &damp_i2s, + [GPIOMUX_SUSPENDED] = &damp_i2s, + }, + }, + { /* SD_CARD_DET_N */ + .gpio = 62, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_in, + }, + }, + { /* CODEC_RESET_N */ + .gpio = 63, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* MHL_INT */ + .gpio = 64, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_pull_up_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_up_in, + }, + }, + { /* ACCEL_INT2 */ + .gpio = 65, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_in, + }, + }, + { /* GYRO_INT1 */ + .gpio = 66, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_pull_down_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_down_in, + }, + }, + { /* WL_HOST_WAKE */ + .gpio = 67, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_pull_down_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_down_in, + }, + }, + { /* NC */ + .gpio = 68, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 69, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* SLIMBUS_CLK */ + .gpio = 70, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_follow_qct, + }, + }, + { /* SLIMBUS_DATA */ + .gpio = 71, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_follow_qct, + }, + }, + { /* CODEC_INT1 */ + .gpio = 72, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_follow_qct, + }, + }, + { /* ACCEL_INT1 */ + .gpio = 73, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_in, + }, + }, + { /* PROX_ALS_INT_N */ + .gpio = 74, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_pull_up_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_up_in, + }, + }, + { /* GYRO_INT2 */ + .gpio = 75, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_pull_down_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_down_in, + }, + }, + { /* NC */ + .gpio = 76, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 77, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 78, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* BT_PCM_SCLK */ + .gpio = 79, + .settings = { + [GPIOMUX_ACTIVE] = &bt_pcm, + [GPIOMUX_SUSPENDED] = &bt_pcm, + }, + }, + { /* BT_PCM_SYNC */ + .gpio = 80, + .settings = { + [GPIOMUX_ACTIVE] = &bt_pcm, + [GPIOMUX_SUSPENDED] = &bt_pcm, + }, + }, + { /* BT_PCM_DIN */ + .gpio = 81, + .settings = { + [GPIOMUX_ACTIVE] = &bt_pcm, + [GPIOMUX_SUSPENDED] = &bt_pcm, + }, + }, + { /* BT_PCM_DOUT */ + .gpio = 82, + .settings = { + [GPIOMUX_ACTIVE] = &bt_pcm, + [GPIOMUX_SUSPENDED] = &bt_pcm, + }, + }, + { /* PERIPHERAL_I2C_SDA */ + .gpio = 83, + .settings = { + [GPIOMUX_ACTIVE] = &peripheral_i2c, + [GPIOMUX_SUSPENDED] = &peripheral_i2c, + }, + }, + { /* PERIPHERAL_I2C_SCL */ + .gpio = 84, + .settings = { + [GPIOMUX_ACTIVE] = &peripheral_i2c, + [GPIOMUX_SUSPENDED] = &peripheral_i2c, + }, + }, + { /* TS_INT_N */ + .gpio = 86, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_in, + }, + }, + { /* SENSORS_I2C_SDA */ + .gpio = 87, + .settings = { + [GPIOMUX_ACTIVE] = &sensors_i2c, + [GPIOMUX_SUSPENDED] = &sensors_i2c, + }, + }, + { /* SENSORS_I2C_SCL */ + .gpio = 88, + .settings = { + [GPIOMUX_ACTIVE] = &sensors_i2c, + [GPIOMUX_SUSPENDED] = &sensors_i2c, + }, + }, + { /* NC */ + .gpio = 89, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 90, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 91, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 92, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* CODEC_INT2 */ + .gpio = 93, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_follow_qct, + }, + }, + { /* CAM0_RST_N */ + .gpio = 94, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* BT_HOST_WAKE */ + .gpio = 95, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_down_in, + }, + }, + { /* BT_DEV_WAKE */ + .gpio = 96, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* UIM1_DATA */ + .gpio = 97, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* UIM1_CLK */ + .gpio = 98, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* UIM1_RST */ + .gpio = 99, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* NC */ + .gpio = 100, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_pull_up_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_down_in, + }, + }, + { /* BATT_REM_ALARM */ + .gpio = 101, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_follow_qct, + }, + }, + { /* MHL_SPI_DVLD */ + .gpio = 102, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_down_in, + }, + }, + { /* FORCED_USB_BOOT */ + .gpio = 103, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_pull_down_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_down_in, + }, + }, + { /* NC */ + .gpio = 104, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* PA_ON0 */ + .gpio = 105, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* PA_ON1 */ + .gpio = 106, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* NC */ + .gpio = 107, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* PA_ON2 */ + .gpio = 108, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* NC */ + .gpio = 109, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 110, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 111, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* GRFC8 [WDOG_DISABLE] */ + .gpio = 112, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* NC(BOOT_CONFIG_1) */ + .gpio = 113, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC([BOOT_CONFIG_2]) */ + .gpio = 114, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC([BOOT_CONFIG_3]) */ + .gpio = 115, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* TX_GTR_THRES [BOOT_CONFIG_4] */ + .gpio = 116, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* NC(Reserved for ANT_TUNE0) */ + .gpio = 117, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* PA_R0 */ + .gpio = 118, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* NC */ + .gpio = 119, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* ANT_SEL0 */ + .gpio = 120, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* ANT_SEL1 */ + .gpio = 121, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* NC */ + .gpio = 122, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 123, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 124, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 125, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 126, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 127, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* EXT_GPS_LNA_EN */ + .gpio = 128, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* NC */ + .gpio = 129, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* LTE_ACTIVE */ + .gpio = 130, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* LTE_TX_COEX_WCN */ + .gpio = 131, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_no_pull_out_low, + [GPIOMUX_SUSPENDED] = &gpio_2ma_no_pull_out_low, + }, + }, + { /* WCN_TX_COEX_LTE */ + .gpio = 132, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_pull_down_in, + [GPIOMUX_SUSPENDED] = &gpio_2ma_pull_down_in, + }, + }, + { /* WTR0_SSBI1_TX_GPS */ + .gpio = 133, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* WTR0_SSBI2_PRX_DRX */ + .gpio = 134, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* NC */ + .gpio = 135, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 136, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* NC */ + .gpio = 137, + .settings = { [GPIOMUX_SUSPENDED] = &unused_gpio, }, + }, + { /* GSM_TX_PHASE_D1 */ + .gpio = 138, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* GSM_TX_PHASE_D0 */ + .gpio = 139, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* RFFE1_CLK */ + .gpio = 140, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* RFFE1_DATA */ + .gpio = 141, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* RFFE2_CLK */ + .gpio = 142, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* RFFE2_DATA */ + .gpio = 143, + .settings = { + [GPIOMUX_ACTIVE] = &gpio_2ma_follow_qct, + [GPIOMUX_SUSPENDED] = &gpio_2ma_follow_qct, + }, + }, + { /* HSIC_STROBE */ + .gpio = 144, + .settings = { + [GPIOMUX_ACTIVE] = &hsic, + [GPIOMUX_SUSPENDED] = &hsic, + }, + }, + { /* HSIC_DATA */ + .gpio = 145, + .settings = { + [GPIOMUX_ACTIVE] = &hsic, + [GPIOMUX_SUSPENDED] = &hsic, + }, + }, +}; + +void __init msm_8974_init_gpiomux(void) +{ + int rc; + + rc = sony_init_gpiomux(shinano_all_configs, + ARRAY_SIZE(shinano_all_configs)); + if (rc) { + pr_err("%s failed %d\n", __func__, rc); + return; + } + + msm_gpiomux_install_nowrite(&touch_config, 1); +} diff --git a/arch/arm/mach-msm/include/mach/memory.h b/arch/arm/mach-msm/include/mach/memory.h index 6119a3c44000..a52b5b61f6a0 100644 --- a/arch/arm/mach-msm/include/mach/memory.h +++ b/arch/arm/mach-msm/include/mach/memory.h @@ -20,6 +20,18 @@ /* physical offset of RAM */ #define PLAT_PHYS_OFFSET UL(CONFIG_PHYS_OFFSET) +#if defined(CONFIG_KEXEC_HARDBOOT) +#if defined(CONFIG_MACH_SONY_SIRIUS) +#define KEXEC_HB_PAGE_ADDR UL(0x80200000) +#else +#if defined(CONFIG_MACH_SONY_AMAMI_ROW) || defined(CONFIG_MACH_SONY_HONAMI_ROW) || defined(CONFIG_MACH_SONY_TOGARI_ROW) +#define KEXEC_HB_PAGE_ADDR UL(0x40200000) +#else +#error "Adress for kexec hardboot page not defined" +#endif +#endif +#endif + #define MAX_PHYSMEM_BITS 32 #define SECTION_SIZE_BITS 28 diff --git a/arch/arm/mach-msm/include/mach/msm_dcvs_scm.h b/arch/arm/mach-msm/include/mach/msm_dcvs_scm.h index 7eefd549051b..222ba6a9bcef 100644 --- a/arch/arm/mach-msm/include/mach/msm_dcvs_scm.h +++ b/arch/arm/mach-msm/include/mach/msm_dcvs_scm.h @@ -23,6 +23,11 @@ enum msm_dcvs_algo_param_type { MSM_DCVS_ALGO_MPD_PARAM = 1, }; +enum msm_dcvs_algo_param_type { + MSM_DCVS_ALGO_DCVS_PARAM = 0, + MSM_DCVS_ALGO_MPD_PARAM = 1, +}; + enum msm_dcvs_scm_event { MSM_DCVS_SCM_IDLE_ENTER = 0, /* Core enters idle */ MSM_DCVS_SCM_IDLE_EXIT = 1, /* Core exits idle */ @@ -95,6 +100,18 @@ struct msm_mpd_algo_param { uint32_t slack_time_max_us; }; +struct msm_mpd_algo_param { + uint32_t em_win_size_min_us; + uint32_t em_win_size_max_us; + uint32_t em_max_util_pct; + uint32_t mp_em_rounding_point_min; + uint32_t mp_em_rounding_point_max; + uint32_t online_util_pct_min; + uint32_t online_util_pct_max; + uint32_t slack_time_min_us; + uint32_t slack_time_max_us; +}; + #ifdef CONFIG_MSM_DCVS /** * Initialize DCVS algorithm in TrustZone. @@ -164,6 +181,15 @@ extern int msm_dcvs_scm_set_power_params(uint32_t core_id, struct msm_dcvs_freq_entry *freq_entry, struct msm_dcvs_energy_curve_coeffs *coeffs); +/** + * Set MPDecision algorithm parameters + * + * @param: The param data structure + * 0 on success. + * -EINVAL: Invalid args. + */ +extern int msm_mpd_scm_set_algo_params(struct msm_mpd_algo_param *param); + /** * Do an SCM call. * @@ -252,6 +278,9 @@ static inline int msm_dcvs_set_power_params(uint32_t core_id, struct msm_dcvs_freq_entry *freq_entry, struct msm_dcvs_energy_curve_coeffs *coeffs) { return -ENOSYS; } +static inline int msm_mpd_scm_set_algo_params( + struct msm_mpd_algo_param *param) +{ return -ENOSYS; } static inline int msm_dcvs_scm_event(uint32_t core_id, enum msm_dcvs_scm_event event_id, uint32_t param0, uint32_t param1, diff --git a/arch/arm/mach-msm/msm_dcvs_scm.c b/arch/arm/mach-msm/msm_dcvs_scm.c index e03ac647bd1f..7927d4e17a1b 100644 --- a/arch/arm/mach-msm/msm_dcvs_scm.c +++ b/arch/arm/mach-msm/msm_dcvs_scm.c @@ -59,6 +59,14 @@ struct msm_algo_param { } u; }; +struct msm_algo_param { + enum msm_dcvs_algo_param_type type; + union { + struct msm_dcvs_algo_param dcvs_param; + struct msm_mpd_algo_param mpd_param; + } u; +}; + int msm_dcvs_scm_init(size_t size) { int ret = 0; @@ -232,6 +240,31 @@ int msm_dcvs_scm_set_power_params(uint32_t core_id, } EXPORT_SYMBOL(msm_dcvs_scm_set_power_params); +int msm_mpd_scm_set_algo_params(struct msm_mpd_algo_param *param) +{ + int ret = 0; + struct scm_algo algo; + struct msm_algo_param *p = NULL; + + p = kzalloc(PAGE_ALIGN(sizeof(struct msm_algo_param)), GFP_KERNEL); + if (!p) + return -ENOMEM; + + p->type = MSM_DCVS_ALGO_MPD_PARAM; + memcpy(&p->u.mpd_param, param, sizeof(struct msm_mpd_algo_param)); + + algo.core_id = 0; + algo.algo_phy = virt_to_phys(p); + + ret = scm_call(SCM_SVC_DCVS, DCVS_CMD_SET_ALGO_PARAM, + &algo, sizeof(algo), NULL, 0); + + kfree(p); + + return ret; +} +EXPORT_SYMBOL(msm_mpd_scm_set_algo_params); + int msm_dcvs_scm_event(uint32_t core_id, enum msm_dcvs_scm_event event_id, uint32_t param0, uint32_t param1, diff --git a/arch/arm/mach-msm/msm_mpdecision.c b/arch/arm/mach-msm/msm_mpdecision.c index 746bbe80621c..fc6423355e22 100644 --- a/arch/arm/mach-msm/msm_mpdecision.c +++ b/arch/arm/mach-msm/msm_mpdecision.c @@ -1,726 +1,1183 @@ - /* Copyright (c) 2012, The Linux Foundation. All rights reserved. +/* + * arch/arm/mach-msm/msm_mpdecision.c + * + * This program features: + * -cpu auto-hotplug/unplug based on system load for MSM multicore cpus + * -single core while screen is off + * -extensive sysfs tuneables + * + * Copyright (c) 2012-2013, Dennis Rassmann * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. + * 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. + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#define pr_fmt(fmt) "mpd %s: " fmt, __func__ - -#include -#include +#include "msm_mpdecision.h" +#include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#endif +#include "acpuclock.h" #define CREATE_TRACE_POINTS #include -#define DEFAULT_RQ_AVG_POLL_MS (1) -#define DEFAULT_RQ_AVG_DIVIDE (25) - -struct mpd_attrib { - struct kobj_attribute enabled; - struct kobj_attribute rq_avg_poll_ms; - struct kobj_attribute iowait_threshold_pct; - - struct kobj_attribute rq_avg_divide; - struct kobj_attribute em_win_size_min_us; - struct kobj_attribute em_win_size_max_us; - struct kobj_attribute em_max_util_pct; - struct kobj_attribute mp_em_rounding_point_min; - struct kobj_attribute mp_em_rounding_point_max; - struct kobj_attribute online_util_pct_min; - struct kobj_attribute online_util_pct_max; - struct kobj_attribute slack_time_min_us; - struct kobj_attribute slack_time_max_us; - struct kobj_attribute hp_up_max_ms; - struct kobj_attribute hp_up_ms; - struct kobj_attribute hp_up_count; - struct kobj_attribute hp_dw_max_ms; - struct kobj_attribute hp_dw_ms; - struct kobj_attribute hp_dw_count; - struct attribute_group attrib_group; +#define DEBUG 1 + +DEFINE_PER_CPU(struct msm_mpdec_cpudata_t, msm_mpdec_cpudata); +EXPORT_PER_CPU_SYMBOL_GPL(msm_mpdec_cpudata); + +static bool mpdec_suspended = false; +static struct delayed_work msm_mpdec_work; +static struct workqueue_struct *msm_mpdec_workq; +static DEFINE_MUTEX(mpdec_msm_cpu_lock); +static DEFINE_MUTEX(mpdec_msm_susres_lock); +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN +static struct workqueue_struct *mpdec_input_wq; +static DEFINE_PER_CPU(struct work_struct, mpdec_input_work); +static struct workqueue_struct *msm_mpdec_revib_workq; +static DEFINE_PER_CPU(struct delayed_work, msm_mpdec_revib_work); +#endif + +static struct msm_mpdec_tuners { + unsigned int startdelay; + unsigned int delay; + unsigned int pause; + bool scroff_single_core; + unsigned long int idle_freq; + unsigned int max_cpus; + unsigned int min_cpus; +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN + bool boost_enabled; + unsigned int boost_time; + unsigned long int boost_freq[4]; +#endif +} msm_mpdec_tuners_ins = { + .startdelay = MSM_MPDEC_STARTDELAY, + .delay = MSM_MPDEC_DELAY, + .pause = MSM_MPDEC_PAUSE, + .scroff_single_core = true, + .idle_freq = MSM_MPDEC_IDLE_FREQ, + .max_cpus = CONFIG_NR_CPUS, + .min_cpus = 1, +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN + .boost_enabled = true, + .boost_time = MSM_MPDEC_BOOSTTIME, + .boost_freq = { + MSM_MPDEC_BOOSTFREQ_CPU0, + MSM_MPDEC_BOOSTFREQ_CPU1, + MSM_MPDEC_BOOSTFREQ_CPU2, + MSM_MPDEC_BOOSTFREQ_CPU3 + }, +#endif }; -struct msm_mpd_scm_data { - enum msm_dcvs_scm_event event; - int nr; -}; +static unsigned int NwNs_Threshold[8] = {12, 0, 20, 7, 25, 10, 0, 18}; +static unsigned int TwTs_Threshold[8] = {140, 0, 140, 190, 140, 190, 0, 190}; -struct mpdecision { - uint32_t enabled; - atomic_t algo_cpu_mask; - uint32_t rq_avg_poll_ms; - uint32_t iowait_threshold_pct; - uint32_t rq_avg_divide; - ktime_t next_update; - uint32_t slack_us; - struct msm_mpd_algo_param mp_param; - struct mpd_attrib attrib; - struct mutex lock; - struct task_struct *task; - struct task_struct *hptask; - struct hrtimer slack_timer; - struct msm_mpd_scm_data data; - int hpupdate; - wait_queue_head_t wait_q; - wait_queue_head_t wait_hpq; -}; +extern unsigned int get_rq_info(void); +extern unsigned long acpuclk_get_rate(int); -struct hp_latency { - int hp_up_max_ms; - int hp_up_ms; - int hp_up_count; - int hp_dw_max_ms; - int hp_dw_ms; - int hp_dw_count; -}; +unsigned int state = MSM_MPDEC_IDLE; +bool was_paused = false; +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN +bool is_screen_on = true; +static int update_cpu_min_freq(struct cpufreq_policy *cpu_policy, + int cpu, int new_freq); +static void unboost_cpu(int cpu); +#endif +static cputime64_t mpdec_paused_until = 0; -static DEFINE_PER_CPU(struct hrtimer, rq_avg_poll_timer); -static DEFINE_SPINLOCK(rq_avg_lock); +static unsigned long get_rate(int cpu) { + return acpuclk_get_rate(cpu); +} -enum { - MSM_MPD_DEBUG_NOTIFIER = BIT(0), - MSM_MPD_CORE_STATUS = BIT(1), - MSM_MPD_SLACK_TIMER = BIT(2), -}; +static int get_slowest_cpu(void) { + int i, cpu = 0; + unsigned long rate, slow_rate = 0; + + for (i = 1; i < CONFIG_NR_CPUS; i++) { + if (!cpu_online(i)) + continue; + rate = get_rate(i); + if (slow_rate == 0) { + cpu = i; + slow_rate = rate; + continue; + } + if ((rate <= slow_rate) && (slow_rate != 0)) { + cpu = i; + slow_rate = rate; + } + } -enum { - HPUPDATE_WAITING = 0, /* we are waiting for cpumask update */ - HPUPDATE_SCHEDULED = 1, /* we are in the process of hotplugging */ - HPUPDATE_IN_PROGRESS = 2, /* we are in the process of hotplugging */ -}; + return cpu; +} -static int msm_mpd_enabled = 1; -module_param_named(enabled, msm_mpd_enabled, int, S_IRUGO | S_IWUSR | S_IWGRP); +static unsigned long get_slowest_cpu_rate(void) { + int i = 0; + unsigned long rate, slow_rate = 0; + + for (i = 0; i < CONFIG_NR_CPUS; i++) { + if (!cpu_online(i)) + continue; + rate = get_rate(i); + if ((rate < slow_rate) && (slow_rate != 0)) { + slow_rate = rate; + continue; + } + if (slow_rate == 0) { + slow_rate = rate; + } + } + + return slow_rate; +} + +static void mpdec_cpu_up(int cpu) { + if (!cpu_online(cpu)) { + mutex_lock(&per_cpu(msm_mpdec_cpudata, cpu).hotplug_mutex); + cpu_up(cpu); + per_cpu(msm_mpdec_cpudata, cpu).on_time = ktime_to_ms(ktime_get()); + per_cpu(msm_mpdec_cpudata, cpu).online = true; + per_cpu(msm_mpdec_cpudata, cpu).times_cpu_hotplugged += 1; + pr_info(MPDEC_TAG"CPU[%d] off->on | Mask=[%d%d%d%d]\n", + cpu, cpu_online(0), cpu_online(1), cpu_online(2), cpu_online(3)); + mutex_unlock(&per_cpu(msm_mpdec_cpudata, cpu).hotplug_mutex); + } +} +EXPORT_SYMBOL_GPL(mpdec_cpu_up); + +static void mpdec_cpu_down(int cpu) { + cputime64_t on_time = 0; + if (cpu_online(cpu)) { + mutex_lock(&per_cpu(msm_mpdec_cpudata, cpu).hotplug_mutex); + cpu_down(cpu); + on_time = (ktime_to_ms(ktime_get()) - per_cpu(msm_mpdec_cpudata, cpu).on_time); + per_cpu(msm_mpdec_cpudata, cpu).online = false; + per_cpu(msm_mpdec_cpudata, cpu).on_time_total += on_time; + per_cpu(msm_mpdec_cpudata, cpu).times_cpu_unplugged += 1; + pr_info(MPDEC_TAG"CPU[%d] on->off | Mask=[%d%d%d%d] | time online: %llu\n", + cpu, cpu_online(0), cpu_online(1), cpu_online(2), cpu_online(3), on_time); + mutex_unlock(&per_cpu(msm_mpdec_cpudata, cpu).hotplug_mutex); + } +} +EXPORT_SYMBOL_GPL(mpdec_cpu_down); + +static int mp_decision(void) { + static bool first_call = true; + int new_state = MSM_MPDEC_IDLE; + int nr_cpu_online; + int index; + unsigned int rq_depth; + static cputime64_t total_time = 0; + static cputime64_t last_time; + cputime64_t current_time; + cputime64_t this_time = 0; + + if (state == MSM_MPDEC_DISABLED) + return MSM_MPDEC_DISABLED; + + current_time = ktime_to_ms(ktime_get()); + + if (first_call) { + first_call = false; + } else { + this_time = current_time - last_time; + } + total_time += this_time; + + rq_depth = get_rq_info(); + nr_cpu_online = num_online_cpus(); + + if (nr_cpu_online) { + index = (nr_cpu_online - 1) * 2; + if ((nr_cpu_online < CONFIG_NR_CPUS) && (rq_depth >= NwNs_Threshold[index])) { + if ((total_time >= TwTs_Threshold[index]) && + (nr_cpu_online < msm_mpdec_tuners_ins.max_cpus)) { + new_state = MSM_MPDEC_UP; + pr_info(MPDEC_TAG"Slowest cpu rate : %lu\n", get_slowest_cpu_rate()); + // TODO : Temporarilly disabled idle freq because get_slowest_cpu_rate doesn't work. +// if (get_slowest_cpu_rate() <= msm_mpdec_tuners_ins.idle_freq) +// new_state = MSM_MPDEC_IDLE; + } + } else if ((nr_cpu_online > 1) && (rq_depth <= NwNs_Threshold[index+1])) { + if ((total_time >= TwTs_Threshold[index+1]) && + (nr_cpu_online > msm_mpdec_tuners_ins.min_cpus)) { + new_state = MSM_MPDEC_DOWN; + if (get_slowest_cpu_rate() > msm_mpdec_tuners_ins.idle_freq) + new_state = MSM_MPDEC_IDLE; + } + } else { + new_state = MSM_MPDEC_IDLE; + total_time = 0; + } + } else { + total_time = 0; + } -static struct dentry *debugfs_base; -static struct mpdecision msm_mpd; + if (new_state != MSM_MPDEC_IDLE) { + total_time = 0; + } -static struct hp_latency hp_latencies; + last_time = ktime_to_ms(ktime_get()); +#if DEBUG + pr_info(MPDEC_TAG"[DEBUG] rq: %u, new_state: %i | Mask=[%d%d%d%d]\n", + rq_depth, new_state, cpu_online(0), cpu_online(1), cpu_online(2), cpu_online(3)); +#endif + return new_state; +} -static unsigned long last_nr; -static int num_present_hundreds; -static ktime_t last_down_time; +static void msm_mpdec_work_thread(struct work_struct *work) { + unsigned int cpu = nr_cpu_ids; -static bool ok_to_update_tz(int nr, int last_nr) -{ - /* - * Exclude unnecessary TZ reports if run queue haven't changed much from - * the last reported value. The divison by rq_avg_divide is to - * filter out small changes in the run queue average which won't cause - * a online cpu mask change. Also if the cpu online count does not match - * the count requested by TZ and we are not in the process of bringing - * cpus online as indicated by a HPUPDATE_IN_PROGRESS in msm_mpd.hpdata - */ - return - (((nr / msm_mpd.rq_avg_divide) - != (last_nr / msm_mpd.rq_avg_divide)) - || ((hweight32(atomic_read(&msm_mpd.algo_cpu_mask)) - != num_online_cpus()) - && (msm_mpd.hpupdate != HPUPDATE_IN_PROGRESS))); -} - -static enum hrtimer_restart msm_mpd_rq_avg_poll_timer(struct hrtimer *timer) -{ - int nr, nr_iowait; - ktime_t curr_time = ktime_get(); - unsigned long flags; - int cpu = smp_processor_id(); - enum hrtimer_restart restart = HRTIMER_RESTART; + /* Check if we are paused */ + if (mpdec_paused_until >= ktime_to_ms(ktime_get())) + goto out; - spin_lock_irqsave(&rq_avg_lock, flags); - /* If running on the wrong cpu, don't restart */ - if (&per_cpu(rq_avg_poll_timer, cpu) != timer) - restart = HRTIMER_NORESTART; + if (mpdec_suspended == true) + goto out; - if (ktime_to_ns(ktime_sub(curr_time, msm_mpd.next_update)) < 0) + if (!mutex_trylock(&mpdec_msm_cpu_lock)) goto out; - msm_mpd.next_update = ktime_add_ns(curr_time, - (msm_mpd.rq_avg_poll_ms * NSEC_PER_MSEC)); + /* if sth messed with the cpus, update the check vars so we can proceed */ + if (was_paused) { + for_each_possible_cpu(cpu) { + if (cpu_online(cpu)) + per_cpu(msm_mpdec_cpudata, cpu).online = true; + else if (!cpu_online(cpu)) + per_cpu(msm_mpdec_cpudata, cpu).online = false; + } + was_paused = false; + } + + state = mp_decision(); + switch (state) { + case MSM_MPDEC_DISABLED: + case MSM_MPDEC_IDLE: + break; + case MSM_MPDEC_DOWN: + cpu = get_slowest_cpu(); + if (cpu < nr_cpu_ids) { + if ((per_cpu(msm_mpdec_cpudata, cpu).online == true) && (cpu_online(cpu))) { +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN + unboost_cpu(cpu); +#endif + mpdec_cpu_down(cpu); + } else if (per_cpu(msm_mpdec_cpudata, cpu).online != cpu_online(cpu)) { + pr_info(MPDEC_TAG"CPU[%d] was controlled outside of mpdecision! | pausing [%d]ms\n", + cpu, msm_mpdec_tuners_ins.pause); + mpdec_paused_until = ktime_to_ms(ktime_get()) + msm_mpdec_tuners_ins.pause; + was_paused = true; + } + } + break; + case MSM_MPDEC_UP: + cpu = cpumask_next_zero(0, cpu_online_mask); + pr_info(MPDEC_TAG"We try to put cpu %d up", cpu); + if (cpu < nr_cpu_ids) { + if ((per_cpu(msm_mpdec_cpudata, cpu).online == false) && (!cpu_online(cpu))) { + mpdec_cpu_up(cpu); +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN + unboost_cpu(cpu); +#endif + } else if (per_cpu(msm_mpdec_cpudata, cpu).online != cpu_online(cpu)) { + pr_info(MPDEC_TAG"CPU[%d] was controlled outside of mpdecision! | pausing [%d]ms\n", + cpu, msm_mpdec_tuners_ins.pause); + mpdec_paused_until = ktime_to_ms(ktime_get()) + msm_mpdec_tuners_ins.pause; + was_paused = true; + } + } + break; + default: + pr_err(MPDEC_TAG"%s: invalid mpdec hotplug state %d\n", + __func__, state); + } + mutex_unlock(&mpdec_msm_cpu_lock); + +out: + if (state != MSM_MPDEC_DISABLED) + queue_delayed_work(msm_mpdec_workq, &msm_mpdec_work, + msecs_to_jiffies(msm_mpdec_tuners_ins.delay)); + return; +} + +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN +static int update_cpu_min_freq(struct cpufreq_policy *cpu_policy, + int cpu, int new_freq) { + int ret = 0; - sched_get_nr_running_avg(&nr, &nr_iowait); + if (!cpu_policy) + return -EINVAL; - if ((nr_iowait >= msm_mpd.iowait_threshold_pct) && (nr < last_nr)) - nr = last_nr; + cpufreq_verify_within_limits(cpu_policy, new_freq, cpu_policy->max); + cpu_policy->user_policy.min = new_freq; - if (nr > num_present_hundreds) - nr = num_present_hundreds; + ret = cpufreq_update_policy(cpu); + if (!ret) { + pr_debug(MPDEC_TAG"Touch event! Setting CPU%d min frequency to %d\n", + cpu, new_freq); + } + return ret; +} - trace_msm_mp_runq("nr_running", nr); +static void unboost_cpu(int cpu) { + struct cpufreq_policy *cpu_policy = NULL; - if (ok_to_update_tz(nr, last_nr)) { - hrtimer_try_to_cancel(&msm_mpd.slack_timer); - msm_mpd.data.nr = nr; - msm_mpd.data.event = MSM_DCVS_SCM_RUNQ_UPDATE; - wake_up(&msm_mpd.wait_q); - last_nr = nr; + if (cpu_online(cpu)) { + if (per_cpu(msm_mpdec_cpudata, cpu).is_boosted) { + if (mutex_trylock(&per_cpu(msm_mpdec_cpudata, cpu).unboost_mutex)) { + cpu_policy = cpufreq_cpu_get(cpu); + if (!cpu_policy) { + pr_debug(MPDEC_TAG"NULL policy on cpu %d\n", cpu); + return; + } +#if DEBUG + pr_info(MPDEC_TAG"un boosted cpu%i to %lu", cpu, per_cpu(msm_mpdec_cpudata, cpu).norm_min_freq); +#endif + per_cpu(msm_mpdec_cpudata, cpu).is_boosted = false; + per_cpu(msm_mpdec_cpudata, cpu).revib_wq_running = false; + if ((cpu_policy->min != per_cpu(msm_mpdec_cpudata, cpu).boost_freq) && + (cpu_policy->min != per_cpu(msm_mpdec_cpudata, cpu).norm_min_freq)) { + pr_info(MPDEC_TAG"cpu%u min was changed while boosted (%lu->%u), using new min", + cpu, per_cpu(msm_mpdec_cpudata, cpu).norm_min_freq, cpu_policy->min); + per_cpu(msm_mpdec_cpudata, cpu).norm_min_freq = cpu_policy->min; + } + update_cpu_min_freq(cpu_policy, cpu, per_cpu(msm_mpdec_cpudata, cpu).norm_min_freq); + cpufreq_cpu_put(cpu_policy); + mutex_unlock(&per_cpu(msm_mpdec_cpudata, cpu).unboost_mutex); + } + } } -out: - hrtimer_set_expires(timer, msm_mpd.next_update); - spin_unlock_irqrestore(&rq_avg_lock, flags); - /* set next expiration */ - return restart; + return; } -static void bring_up_cpu(int cpu) -{ - int cpu_action_time_ms; - int time_taken_ms; - int ret, ret1, ret2; - - cpu_action_time_ms = ktime_to_ms(ktime_get()); - ret = cpu_up(cpu); - if (ret) { - pr_debug("Error %d online core %d\n", ret, cpu); +static void msm_mpdec_revib_work_thread(struct work_struct *work) { + int cpu = smp_processor_id(); + + if (per_cpu(msm_mpdec_cpudata, cpu).is_boosted) { + per_cpu(msm_mpdec_cpudata, cpu).revib_wq_running = true; + if (ktime_to_ms(ktime_get()) > per_cpu(msm_mpdec_cpudata, cpu).boost_until) { + unboost_cpu(cpu); + } else { + queue_delayed_work_on( + cpu, + msm_mpdec_revib_workq, + &per_cpu(msm_mpdec_revib_work, cpu), + msecs_to_jiffies((per_cpu(msm_mpdec_cpudata, cpu).boost_until - ktime_to_ms(ktime_get()))) + ); + } } else { - time_taken_ms = ktime_to_ms(ktime_get()) - cpu_action_time_ms; - if (time_taken_ms > hp_latencies.hp_up_max_ms) - hp_latencies.hp_up_max_ms = time_taken_ms; - hp_latencies.hp_up_ms += time_taken_ms; - hp_latencies.hp_up_count++; - ret = msm_dcvs_scm_event( - CPU_OFFSET + cpu, - MSM_DCVS_SCM_CORE_ONLINE, - cpufreq_get(cpu), - (uint32_t) time_taken_ms * USEC_PER_MSEC, - &ret1, &ret2); - if (ret) - pr_err("Error sending hotplug scm event err=%d\n", ret); - } -} - -static void bring_down_cpu(int cpu) -{ - int cpu_action_time_ms; - int time_taken_ms; - int ret, ret1, ret2; - - BUG_ON(cpu == 0); - cpu_action_time_ms = ktime_to_ms(ktime_get()); - ret = cpu_down(cpu); - if (ret) { - pr_debug("Error %d offline" "core %d\n", ret, cpu); + per_cpu(msm_mpdec_cpudata, cpu).revib_wq_running = false; + } + return; +} + +static void mpdec_input_callback(struct work_struct *unused) { + struct cpufreq_policy *cpu_policy = NULL; + int cpu = smp_processor_id(); + bool boosted = false; + + if (!per_cpu(msm_mpdec_cpudata, cpu).is_boosted) { + if (mutex_trylock(&per_cpu(msm_mpdec_cpudata, cpu).boost_mutex)) { + cpu_policy = cpufreq_cpu_get(cpu); + if (!cpu_policy) { + pr_debug(MPDEC_TAG"NULL policy on cpu %d\n", cpu); + return; + } + per_cpu(msm_mpdec_cpudata, cpu).norm_min_freq = cpu_policy->min; + + /* check if boost freq is > minfreq */ + cpufreq_verify_within_limits(cpu_policy, cpu_policy->min, per_cpu(msm_mpdec_cpudata, cpu).boost_freq); + + update_cpu_min_freq(cpu_policy, cpu, per_cpu(msm_mpdec_cpudata, cpu).boost_freq); +#if DEBUG + pr_info(MPDEC_TAG"boosted cpu%i to %lu", cpu, per_cpu(msm_mpdec_cpudata, cpu).boost_freq); +#endif + per_cpu(msm_mpdec_cpudata, cpu).is_boosted = true; + per_cpu(msm_mpdec_cpudata, cpu).boost_until = ktime_to_ms(ktime_get()) + msm_mpdec_tuners_ins.boost_time; + boosted = true; + cpufreq_cpu_put(cpu_policy); + mutex_unlock(&per_cpu(msm_mpdec_cpudata, cpu).boost_mutex); + } } else { - time_taken_ms = ktime_to_ms(ktime_get()) - cpu_action_time_ms; - if (time_taken_ms > hp_latencies.hp_dw_max_ms) - hp_latencies.hp_dw_max_ms = time_taken_ms; - hp_latencies.hp_dw_ms += time_taken_ms; - hp_latencies.hp_dw_count++; - ret = msm_dcvs_scm_event( - CPU_OFFSET + cpu, - MSM_DCVS_SCM_CORE_OFFLINE, - (uint32_t) time_taken_ms * USEC_PER_MSEC, - 0, - &ret1, &ret2); - if (ret) - pr_err("Error sending hotplug scm event err=%d\n", ret); - } -} - -static int __ref msm_mpd_update_scm(enum msm_dcvs_scm_event event, int nr) -{ - int ret = 0; - uint32_t req_cpu_mask = 0; - uint32_t slack_us = 0; - uint32_t param0 = 0; + boosted = true; + } + if (boosted && !per_cpu(msm_mpdec_cpudata, cpu).revib_wq_running) { + per_cpu(msm_mpdec_cpudata, cpu).revib_wq_running = true; + queue_delayed_work_on( + cpu, + msm_mpdec_revib_workq, + &per_cpu(msm_mpdec_revib_work, cpu), + msecs_to_jiffies(msm_mpdec_tuners_ins.boost_time) + ); + } else if (boosted && per_cpu(msm_mpdec_cpudata, cpu).revib_wq_running) { + per_cpu(msm_mpdec_cpudata, cpu).boost_until = ktime_to_ms(ktime_get()) + msm_mpdec_tuners_ins.boost_time; + } + + return; +} + +#ifdef CONFIG_BRICKED_THERMAL +extern int bricked_thermal_throttled; +#endif + +static void mpdec_input_event(struct input_handle *handle, unsigned int type, + unsigned int code, int value) { + int i = 0; + +#ifdef CONFIG_BRICKED_THERMAL + if (bricked_thermal_throttled > 0) + return; +#endif - if (event == MSM_DCVS_SCM_RUNQ_UPDATE) - param0 = nr; + if (!msm_mpdec_tuners_ins.boost_enabled) + return; - ret = msm_dcvs_scm_event(0, event, param0, 0, - &req_cpu_mask, &slack_us); + if (!is_screen_on) + return; - if (ret) { - pr_err("Error (%d) sending event %d, param %d\n", ret, event, - param0); - return ret; + for_each_online_cpu(i) { + queue_work_on(i, mpdec_input_wq, &per_cpu(mpdec_input_work, i)); } +} + +static int input_dev_filter(const char *input_dev_name) { + if (strstr(input_dev_name, "touch") || + strstr(input_dev_name, "key") || + strstr(input_dev_name, "power") || + strstr(input_dev_name, "pwr") || + strstr(input_dev_name, "lid")) { + return 0; + } else { + return 1; + } +} + +static int mpdec_input_connect(struct input_handler *handler, + struct input_dev *dev, const struct input_device_id *id) { + struct input_handle *handle; + int error; + + if (input_dev_filter(dev->name)) + return -ENODEV; - trace_msm_mp_cpusonline("cpu_online_mp", req_cpu_mask); - trace_msm_mp_slacktime("slack_time_mp", slack_us); - msm_mpd.slack_us = slack_us; - atomic_set(&msm_mpd.algo_cpu_mask, req_cpu_mask); - msm_mpd.hpupdate = HPUPDATE_SCHEDULED; - wake_up(&msm_mpd.wait_hpq); + handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "mpdec"; + + error = input_register_handle(handle); + if (error) + goto err2; + + error = input_open_device(handle); + if (error) + goto err1; + + return 0; +err1: + input_unregister_handle(handle); +err2: + kfree(handle); + return error; +} + +static void mpdec_input_disconnect(struct input_handle *handle) { + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} - /* Start MP Decision slack timer */ - if (slack_us) { - hrtimer_cancel(&msm_mpd.slack_timer); - ret = hrtimer_start(&msm_mpd.slack_timer, - ktime_set(0, slack_us * NSEC_PER_USEC), - HRTIMER_MODE_REL_PINNED); - if (ret) - pr_err("Failed to register slack timer (%d) %d\n", - slack_us, ret); +static const struct input_device_id mpdec_ids[] = { + { .driver_info = 1 }, + { }, +}; + +static struct input_handler mpdec_input_handler = { + .event = mpdec_input_event, + .connect = mpdec_input_connect, + .disconnect = mpdec_input_disconnect, + .name = "mpdec_inputreq", + .id_table = mpdec_ids, +}; +#endif + +static void msm_mpdec_suspend(struct work_struct * msm_mpdec_suspend_work) { + int cpu = nr_cpu_ids; +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN + is_screen_on = false; +#endif + + if (!msm_mpdec_tuners_ins.scroff_single_core) { + pr_info(MPDEC_TAG"Screen -> off\n"); + return; } - return ret; + /* main work thread can sleep now */ + cancel_delayed_work_sync(&msm_mpdec_work); + + for_each_possible_cpu(cpu) { +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN + unboost_cpu(cpu); +#endif + if ((cpu >= 1) && (cpu_online(cpu))) { + mpdec_cpu_down(cpu); + } + } + mpdec_suspended = true; + + pr_info(MPDEC_TAG"Screen -> off. Deactivated mpdecision.\n"); } +static DECLARE_WORK(msm_mpdec_suspend_work, msm_mpdec_suspend); + +static void msm_mpdec_resume(struct work_struct * msm_mpdec_suspend_work) { + int cpu = nr_cpu_ids; +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN + is_screen_on = true; +#endif + + if (!mpdec_suspended) + return; + + mpdec_suspended = false; + + if (msm_mpdec_tuners_ins.scroff_single_core) { + /* wake up main work thread */ + was_paused = true; + queue_delayed_work(msm_mpdec_workq, &msm_mpdec_work, 0); + /* restore min/max cpus limits */ + for (cpu=1; cpu msm_mpdec_tuners_ins.max_cpus) { + if (cpu_online(cpu)) + mpdec_cpu_down(cpu); + } + } + pr_info(MPDEC_TAG"Screen -> on. Activated mpdecision. | Mask=[%d%d%d%d]\n", + cpu_online(0), cpu_online(1), cpu_online(2), cpu_online(3)); + } else { + pr_info(MPDEC_TAG"Screen -> on\n"); + } +} +static DECLARE_WORK(msm_mpdec_resume_work, msm_mpdec_resume); -static enum hrtimer_restart msm_mpd_slack_timer(struct hrtimer *timer) -{ - unsigned long flags; +static void msm_mpdec_early_suspend(struct power_suspend *h) { + mutex_lock(&mpdec_msm_susres_lock); + schedule_work(&msm_mpdec_suspend_work); + mutex_unlock(&mpdec_msm_susres_lock); +} - trace_printk("mpd:slack_timer_fired!\n"); +static void msm_mpdec_late_resume(struct power_suspend *h) { + mutex_lock(&mpdec_msm_susres_lock); + schedule_work(&msm_mpdec_resume_work); + mutex_unlock(&mpdec_msm_susres_lock); +} - spin_lock_irqsave(&rq_avg_lock, flags); - if (msm_mpd.data.event == MSM_DCVS_SCM_RUNQ_UPDATE) - goto out; +static struct power_suspend msm_mpdec_early_suspend_handler = { + .suspend = msm_mpdec_early_suspend, + .resume = msm_mpdec_late_resume, +}; - msm_mpd.data.nr = 0; - msm_mpd.data.event = MSM_DCVS_SCM_MPD_QOS_TIMER_EXPIRED; - wake_up(&msm_mpd.wait_q); -out: - spin_unlock_irqrestore(&rq_avg_lock, flags); - return HRTIMER_NORESTART; +/**************************** SYSFS START ****************************/ +struct kobject *msm_mpdec_kobject; + +#define show_one(file_name, object) \ +static ssize_t show_##file_name \ +(struct kobject *kobj, struct attribute *attr, char *buf) \ +{ \ + return sprintf(buf, "%u\n", msm_mpdec_tuners_ins.object); \ } -static int msm_mpd_idle_notifier(struct notifier_block *self, - unsigned long cmd, void *v) +show_one(startdelay, startdelay); +show_one(delay, delay); +show_one(pause, pause); +show_one(scroff_single_core, scroff_single_core); +show_one(min_cpus, min_cpus); +show_one(max_cpus, max_cpus); +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN +show_one(boost_enabled, boost_enabled); +show_one(boost_time, boost_time); +#endif + +#define show_one_twts(file_name, arraypos) \ +static ssize_t show_##file_name \ +(struct kobject *kobj, struct attribute *attr, char *buf) \ +{ \ + return sprintf(buf, "%u\n", TwTs_Threshold[arraypos]); \ +} +show_one_twts(twts_threshold_0, 0); +show_one_twts(twts_threshold_1, 1); +show_one_twts(twts_threshold_2, 2); +show_one_twts(twts_threshold_3, 3); +show_one_twts(twts_threshold_4, 4); +show_one_twts(twts_threshold_5, 5); +show_one_twts(twts_threshold_6, 6); +show_one_twts(twts_threshold_7, 7); + +#define store_one_twts(file_name, arraypos) \ +static ssize_t store_##file_name \ +(struct kobject *a, struct attribute *b, const char *buf, size_t count) \ +{ \ + unsigned int input; \ + int ret; \ + ret = sscanf(buf, "%u", &input); \ + if (ret != 1) \ + return -EINVAL; \ + TwTs_Threshold[arraypos] = input; \ + return count; \ +} \ +define_one_global_rw(file_name); +store_one_twts(twts_threshold_0, 0); +store_one_twts(twts_threshold_1, 1); +store_one_twts(twts_threshold_2, 2); +store_one_twts(twts_threshold_3, 3); +store_one_twts(twts_threshold_4, 4); +store_one_twts(twts_threshold_5, 5); +store_one_twts(twts_threshold_6, 6); +store_one_twts(twts_threshold_7, 7); + +#define show_one_nwns(file_name, arraypos) \ +static ssize_t show_##file_name \ +(struct kobject *kobj, struct attribute *attr, char *buf) \ +{ \ + return sprintf(buf, "%u\n", NwNs_Threshold[arraypos]); \ +} +show_one_nwns(nwns_threshold_0, 0); +show_one_nwns(nwns_threshold_1, 1); +show_one_nwns(nwns_threshold_2, 2); +show_one_nwns(nwns_threshold_3, 3); +show_one_nwns(nwns_threshold_4, 4); +show_one_nwns(nwns_threshold_5, 5); +show_one_nwns(nwns_threshold_6, 6); +show_one_nwns(nwns_threshold_7, 7); + +#define store_one_nwns(file_name, arraypos) \ +static ssize_t store_##file_name \ +(struct kobject *a, struct attribute *b, const char *buf, size_t count) \ +{ \ + unsigned int input; \ + int ret; \ + ret = sscanf(buf, "%u", &input); \ + if (ret != 1) \ + return -EINVAL; \ + NwNs_Threshold[arraypos] = input; \ + return count; \ +} \ +define_one_global_rw(file_name); +store_one_nwns(nwns_threshold_0, 0); +store_one_nwns(nwns_threshold_1, 1); +store_one_nwns(nwns_threshold_2, 2); +store_one_nwns(nwns_threshold_3, 3); +store_one_nwns(nwns_threshold_4, 4); +store_one_nwns(nwns_threshold_5, 5); +store_one_nwns(nwns_threshold_6, 6); +store_one_nwns(nwns_threshold_7, 7); + +static ssize_t show_idle_freq (struct kobject *kobj, struct attribute *attr, + char *buf) { - int cpu = smp_processor_id(); - unsigned long flags; - - switch (cmd) { - case CPU_PM_EXIT: - spin_lock_irqsave(&rq_avg_lock, flags); - hrtimer_start(&per_cpu(rq_avg_poll_timer, cpu), - msm_mpd.next_update, - HRTIMER_MODE_ABS_PINNED); - spin_unlock_irqrestore(&rq_avg_lock, flags); + return sprintf(buf, "%lu\n", msm_mpdec_tuners_ins.idle_freq); +} + +static ssize_t show_enabled(struct kobject *a, struct attribute *b, + char *buf) +{ + unsigned int enabled; + switch (state) { + case MSM_MPDEC_DISABLED: + enabled = 0; break; - case CPU_PM_ENTER: - hrtimer_cancel(&per_cpu(rq_avg_poll_timer, cpu)); + case MSM_MPDEC_IDLE: + case MSM_MPDEC_DOWN: + case MSM_MPDEC_UP: + enabled = 1; break; default: - break; + enabled = 333; } + return sprintf(buf, "%u\n", enabled); +} + +static ssize_t store_startdelay(struct kobject *a, struct attribute *b, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf(buf, "%u", &input); + if (ret != 1) + return -EINVAL; - return NOTIFY_OK; + msm_mpdec_tuners_ins.startdelay = input; + + return count; } -static int msm_mpd_hotplug_notifier(struct notifier_block *self, - unsigned long action, void *hcpu) +static ssize_t store_delay(struct kobject *a, struct attribute *b, + const char *buf, size_t count) { - int cpu = (int)hcpu; - unsigned long flags; - - switch (action & (~CPU_TASKS_FROZEN)) { - case CPU_STARTING: - spin_lock_irqsave(&rq_avg_lock, flags); - hrtimer_start(&per_cpu(rq_avg_poll_timer, cpu), - msm_mpd.next_update, - HRTIMER_MODE_ABS_PINNED); - spin_unlock_irqrestore(&rq_avg_lock, flags); - break; - default: - break; - } + unsigned int input; + int ret; + ret = sscanf(buf, "%u", &input); + if (ret != 1) + return -EINVAL; - return NOTIFY_OK; + msm_mpdec_tuners_ins.delay = input; + + return count; } -static struct notifier_block msm_mpd_idle_nb = { - .notifier_call = msm_mpd_idle_notifier, -}; +static ssize_t store_pause(struct kobject *a, struct attribute *b, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf(buf, "%u", &input); + if (ret != 1) + return -EINVAL; -static struct notifier_block msm_mpd_hotplug_nb = { - .notifier_call = msm_mpd_hotplug_notifier, -}; + msm_mpdec_tuners_ins.pause = input; -static int __cpuinit msm_mpd_do_hotplug(void *data) + return count; +} + +static ssize_t store_idle_freq(struct kobject *a, struct attribute *b, + const char *buf, size_t count) { - int *event = (int *)data; - int cpu; + long unsigned int input; + int ret; + ret = sscanf(buf, "%lu", &input); + if (ret != 1) + return -EINVAL; + + msm_mpdec_tuners_ins.idle_freq = input; - while (1) { - msm_dcvs_update_algo_params(); - wait_event(msm_mpd.wait_hpq, *event || kthread_should_stop()); - if (kthread_should_stop()) + return count; +} + +static ssize_t store_scroff_single_core(struct kobject *a, struct attribute *b, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf(buf, "%u", &input); + if (ret != 1) + return -EINVAL; + switch (buf[0]) { + case '0': + case '1': + msm_mpdec_tuners_ins.scroff_single_core = input; break; + default: + ret = -EINVAL; + } + return count; +} - msm_mpd.hpupdate = HPUPDATE_IN_PROGRESS; - /* - * Bring online any offline cores, then offline any online - * cores. Whenever a core is off/onlined restart the procedure - * in case a new core is desired to be brought online in the - * mean time. - */ -restart: - for_each_possible_cpu(cpu) { - if ((atomic_read(&msm_mpd.algo_cpu_mask) & (1 << cpu)) - && !cpu_online(cpu)) { - bring_up_cpu(cpu); - if (cpu_online(cpu)) - goto restart; - } +static ssize_t store_max_cpus(struct kobject *a, struct attribute *b, + const char *buf, size_t count) +{ + unsigned int input; + int ret, cpu; + ret = sscanf(buf, "%u", &input); + if ((ret != 1) || input > CONFIG_NR_CPUS || input < msm_mpdec_tuners_ins.min_cpus) + return -EINVAL; + + msm_mpdec_tuners_ins.max_cpus = input; + if (num_online_cpus() > input) { + for (cpu=CONFIG_NR_CPUS; cpu>0; cpu--) { + if (num_online_cpus() <= input) + break; + if (!cpu_online(cpu)) + continue; + mpdec_cpu_down(cpu); } - - if (ktime_to_ns(ktime_sub(ktime_get(), last_down_time)) > - 100 * NSEC_PER_MSEC) - for_each_possible_cpu(cpu) - if (!(atomic_read(&msm_mpd.algo_cpu_mask) & - (1 << cpu)) && cpu_online(cpu)) { - bring_down_cpu(cpu); - last_down_time = ktime_get(); - break; - } - msm_mpd.hpupdate = HPUPDATE_WAITING; - msm_dcvs_apply_gpu_floor(0); + pr_info(MPDEC_TAG"max_cpus set to %u. Affected CPUs were unplugged!\n", input); } - return 0; + return count; } -static int msm_mpd_do_update_scm(void *data) +static ssize_t store_min_cpus(struct kobject *a, struct attribute *b, + const char *buf, size_t count) { - struct msm_mpd_scm_data *scm_data = (struct msm_mpd_scm_data *)data; - unsigned long flags; - enum msm_dcvs_scm_event event; - int nr; - - while (1) { - wait_event(msm_mpd.wait_q, - msm_mpd.data.event == MSM_DCVS_SCM_MPD_QOS_TIMER_EXPIRED - || msm_mpd.data.event == MSM_DCVS_SCM_RUNQ_UPDATE - || kthread_should_stop()); - - if (kthread_should_stop()) - break; - - spin_lock_irqsave(&rq_avg_lock, flags); - event = scm_data->event; - nr = scm_data->nr; - scm_data->event = 0; - scm_data->nr = 0; - spin_unlock_irqrestore(&rq_avg_lock, flags); + unsigned int input; + int ret, cpu; + ret = sscanf(buf, "%u", &input); + if ((ret != 1) || input < 1 || input > msm_mpdec_tuners_ins.max_cpus) + return -EINVAL; - msm_mpd_update_scm(event, nr); + msm_mpdec_tuners_ins.min_cpus = input; + if (num_online_cpus() < input) { + for (cpu=1; cpu= input) + break; + if (cpu_online(cpu)) + continue; + mpdec_cpu_up(cpu); + } + pr_info(MPDEC_TAG"min_cpus set to %u. Affected CPUs were hotplugged!\n", input); } - return 0; + + return count; } -static int __ref msm_mpd_set_enabled(uint32_t enable) +static ssize_t store_enabled(struct kobject *a, struct attribute *b, + const char *buf, size_t count) { - int ret = 0; - int ret0 = 0; - int ret1 = 0; - int cpu; - static uint32_t last_enable; - - enable = (enable > 0) ? 1 : 0; - if (last_enable == enable) - return ret; - - if (enable) { - ret = msm_mpd_scm_set_algo_params(&msm_mpd.mp_param); - if (ret) { - pr_err("Error(%d): msm_mpd_scm_set_algo_params failed\n", - ret); - return ret; - } + unsigned int cpu, input, enabled; + int ret; + ret = sscanf(buf, "%u", &input); + if (ret != 1) + return -EINVAL; + + switch (state) { + case MSM_MPDEC_DISABLED: + enabled = 0; + break; + case MSM_MPDEC_IDLE: + case MSM_MPDEC_DOWN: + case MSM_MPDEC_UP: + enabled = 1; + break; + default: + enabled = 333; } - ret = msm_dcvs_scm_event(0, MSM_DCVS_SCM_MPD_ENABLE, enable, 0, - &ret0, &ret1); - if (ret) { - pr_err("Error(%d) %s MP Decision\n", - ret, (enable ? "enabling" : "disabling")); - } else { - last_enable = enable; - last_nr = 0; - } - if (enable) { - msm_mpd.next_update = ktime_add_ns(ktime_get(), - (msm_mpd.rq_avg_poll_ms * NSEC_PER_MSEC)); - msm_mpd.task = kthread_run(msm_mpd_do_update_scm, - &msm_mpd.data, "msm_mpdecision"); - if (IS_ERR(msm_mpd.task)) - return -EFAULT; - - msm_mpd.hptask = kthread_run(msm_mpd_do_hotplug, - &msm_mpd.hpupdate, "msm_hp"); - if (IS_ERR(msm_mpd.hptask)) - return -EFAULT; - - for_each_online_cpu(cpu) - hrtimer_start(&per_cpu(rq_avg_poll_timer, cpu), - msm_mpd.next_update, - HRTIMER_MODE_ABS_PINNED); - cpu_pm_register_notifier(&msm_mpd_idle_nb); - register_cpu_notifier(&msm_mpd_hotplug_nb); - msm_mpd.enabled = 1; - } else { - for_each_online_cpu(cpu) - hrtimer_cancel(&per_cpu(rq_avg_poll_timer, cpu)); - kthread_stop(msm_mpd.hptask); - kthread_stop(msm_mpd.task); - cpu_pm_unregister_notifier(&msm_mpd_idle_nb); - unregister_cpu_notifier(&msm_mpd_hotplug_nb); - msm_mpd.enabled = 0; + if (buf[0] == enabled) + return -EINVAL; + + switch (buf[0]) { + case '0': + state = MSM_MPDEC_DISABLED; + pr_info(MPDEC_TAG"nap time... Hot plugging offline CPUs...\n"); + for (cpu = 1; cpu < CONFIG_NR_CPUS; cpu++) + if (!cpu_online(cpu)) + mpdec_cpu_up(cpu); + break; + case '1': + state = MSM_MPDEC_IDLE; + was_paused = true; + queue_delayed_work(msm_mpdec_workq, &msm_mpdec_work, + msecs_to_jiffies(msm_mpdec_tuners_ins.delay)); + pr_info(MPDEC_TAG"firing up mpdecision...\n"); + break; + default: + ret = -EINVAL; } + return count; +} - return ret; +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN +static ssize_t store_boost_enabled(struct kobject *a, struct attribute *b, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf(buf, "%u", &input); + if (ret != 1) + return -EINVAL; + + msm_mpdec_tuners_ins.boost_enabled = input; + + return count; } -static int msm_mpd_set_rq_avg_poll_ms(uint32_t val) +static ssize_t store_boost_time(struct kobject *a, struct attribute *b, + const char *buf, size_t count) { - /* - * No need to do anything. Just let the timer set its own next poll - * interval when it next fires. - */ - msm_mpd.rq_avg_poll_ms = val; - return 0; + unsigned int input; + int ret; + ret = sscanf(buf, "%u", &input); + if (ret != 1) + return -EINVAL; + + msm_mpdec_tuners_ins.boost_time = input; + + return count; } -static int msm_mpd_set_iowait_threshold_pct(uint32_t val) +static ssize_t show_boost_freqs(struct kobject *a, struct attribute *b, + char *buf) { - /* - * No need to do anything. Just let the timer set its own next poll - * interval when it next fires. - */ - msm_mpd.iowait_threshold_pct = val; - return 0; + ssize_t len = 0; + int cpu = 0; + + for_each_present_cpu(cpu) { + len += sprintf(buf + len, "%lu\n", per_cpu(msm_mpdec_cpudata, cpu).boost_freq); + } + return len; } +static ssize_t store_boost_freqs(struct kobject *a, struct attribute *b, + const char *buf, size_t count) +{ + int i = 0; + unsigned int cpu = 0; + long unsigned int hz = 0; + const char *chz = NULL; + + for (i=0; idev.platform_data; - - module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); - if (!module_kobj) { - pr_err("Cannot find kobject for module %s\n", KBUILD_MODNAME); - ret = -ENOENT; - goto done; - } - - msm_mpd.attrib.attrib_group.attrs = - kzalloc(attr_count * sizeof(struct attribute *), GFP_KERNEL); - if (!msm_mpd.attrib.attrib_group.attrs) { - ret = -ENOMEM; - goto done; - } - - MPD_RW_ATTRIB(0, enabled); - MPD_RW_ATTRIB(1, rq_avg_poll_ms); - MPD_RW_ATTRIB(2, iowait_threshold_pct); - MPD_RW_ATTRIB(3, rq_avg_divide); - MPD_RW_ATTRIB(4, em_win_size_min_us); - MPD_RW_ATTRIB(5, em_win_size_max_us); - MPD_RW_ATTRIB(6, em_max_util_pct); - MPD_RW_ATTRIB(7, mp_em_rounding_point_min); - MPD_RW_ATTRIB(8, mp_em_rounding_point_max); - MPD_RW_ATTRIB(9, online_util_pct_min); - MPD_RW_ATTRIB(10, online_util_pct_max); - MPD_RW_ATTRIB(11, slack_time_min_us); - MPD_RW_ATTRIB(12, slack_time_max_us); - MPD_RW_ATTRIB(13, hp_up_max_ms); - MPD_RW_ATTRIB(14, hp_up_ms); - MPD_RW_ATTRIB(15, hp_up_count); - MPD_RW_ATTRIB(16, hp_dw_max_ms); - MPD_RW_ATTRIB(17, hp_dw_ms); - MPD_RW_ATTRIB(18, hp_dw_count); - - msm_mpd.attrib.attrib_group.attrs[19] = NULL; - ret = sysfs_create_group(module_kobj, &msm_mpd.attrib.attrib_group); - if (ret) - pr_err("Unable to create sysfs objects :%d\n", ret); - - msm_mpd.rq_avg_poll_ms = DEFAULT_RQ_AVG_POLL_MS; - msm_mpd.rq_avg_divide = DEFAULT_RQ_AVG_DIVIDE; - - memcpy(&msm_mpd.mp_param, param, sizeof(struct msm_mpd_algo_param)); - - debugfs_base = debugfs_create_dir("msm_mpdecision", NULL); - if (!debugfs_base) { - pr_err("Cannot create debugfs base msm_mpdecision\n"); - ret = -ENOENT; - goto done; - } - -done: - if (ret && debugfs_base) - debugfs_remove(debugfs_base); + ssize_t len = 0; + int cpu = 0; - return ret; + for_each_possible_cpu(cpu) { + len += sprintf(buf + len, "%i %llu\n", cpu, per_cpu(msm_mpdec_cpudata, cpu).times_cpu_hotplugged); + } + + return len; } +define_one_global_ro(times_cpus_hotplugged); -static int __devexit msm_mpd_remove(struct platform_device *pdev) +static ssize_t show_times_cpus_unplugged(struct kobject *a, struct attribute *b, + char *buf) { - platform_set_drvdata(pdev, NULL); + ssize_t len = 0; + int cpu = 0; - return 0; + for_each_possible_cpu(cpu) { + len += sprintf(buf + len, "%i %llu\n", cpu, per_cpu(msm_mpdec_cpudata, cpu).times_cpu_unplugged); + } + + return len; } +define_one_global_ro(times_cpus_unplugged); -static struct platform_driver msm_mpd_driver = { - .probe = msm_mpd_probe, - .remove = __devexit_p(msm_mpd_remove), - .driver = { - .name = "msm_mpdecision", - .owner = THIS_MODULE, - }, +static struct attribute *msm_mpdec_stats_attributes[] = { + &time_cpus_on.attr, + ×_cpus_hotplugged.attr, + ×_cpus_unplugged.attr, + NULL }; -static int __init msm_mpdecision_init(void) -{ - int cpu; - if (!msm_mpd_enabled) { - pr_info("Not enabled\n"); - return 0; - } - num_present_hundreds = 100 * num_present_cpus(); +static struct attribute_group msm_mpdec_stats_attr_group = { + .attrs = msm_mpdec_stats_attributes, + .name = "stats", +}; +/**************************** SYSFS END ****************************/ - hrtimer_init(&msm_mpd.slack_timer, CLOCK_MONOTONIC, - HRTIMER_MODE_REL_PINNED); - msm_mpd.slack_timer.function = msm_mpd_slack_timer; +static int __init msm_mpdec_init(void) { + int cpu, rc, err = 0; +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN + int i; + unsigned long int boost_freq = 0; +#endif + mpdec_suspended = false; for_each_possible_cpu(cpu) { - hrtimer_init(&per_cpu(rq_avg_poll_timer, cpu), - CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED); - per_cpu(rq_avg_poll_timer, cpu).function - = msm_mpd_rq_avg_poll_timer; + mutex_init(&(per_cpu(msm_mpdec_cpudata, cpu).hotplug_mutex)); + per_cpu(msm_mpdec_cpudata, cpu).online = true; + per_cpu(msm_mpdec_cpudata, cpu).on_time_total = 0; + per_cpu(msm_mpdec_cpudata, cpu).times_cpu_unplugged = 0; + per_cpu(msm_mpdec_cpudata, cpu).times_cpu_hotplugged = 0; +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN + per_cpu(msm_mpdec_cpudata, cpu).norm_min_freq = CONFIG_MSM_CPU_FREQ_MIN; + switch (cpu) { + case 0: + case 1: + case 2: + boost_freq = msm_mpdec_tuners_ins.boost_freq[cpu]; + break; + default: + boost_freq = msm_mpdec_tuners_ins.boost_freq[3]; + break; + } + per_cpu(msm_mpdec_cpudata, cpu).boost_freq = boost_freq; + per_cpu(msm_mpdec_cpudata, cpu).is_boosted = false; + per_cpu(msm_mpdec_cpudata, cpu).revib_wq_running = false; + per_cpu(msm_mpdec_cpudata, cpu).boost_until = 0; + mutex_init(&(per_cpu(msm_mpdec_cpudata, cpu).boost_mutex)); + mutex_init(&(per_cpu(msm_mpdec_cpudata, cpu).unboost_mutex)); +#endif + } + + was_paused = true; + + msm_mpdec_workq = alloc_workqueue( + "mpdec", + WQ_UNBOUND | WQ_RESCUER | WQ_FREEZABLE, + 1 + ); + if (!msm_mpdec_workq) + return -ENOMEM; + INIT_DELAYED_WORK(&msm_mpdec_work, msm_mpdec_work_thread); + +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN + mpdec_input_wq = create_workqueue("mpdeciwq"); + if (!mpdec_input_wq) { + printk(KERN_ERR "%s: Failed to create mpdeciwq workqueue\n", __func__); + return -EFAULT; + } + msm_mpdec_revib_workq = create_workqueue("mpdecribwq"); + if (!msm_mpdec_revib_workq) { + printk(KERN_ERR "%s: Failed to create mpdecrevibwq workqueue\n", __func__); + return -EFAULT; + } + for_each_possible_cpu(i) { + INIT_WORK(&per_cpu(mpdec_input_work, i), mpdec_input_callback); + INIT_DELAYED_WORK(&per_cpu(msm_mpdec_revib_work, i), msm_mpdec_revib_work_thread); } - mutex_init(&msm_mpd.lock); - init_waitqueue_head(&msm_mpd.wait_q); - init_waitqueue_head(&msm_mpd.wait_hpq); - return platform_driver_register(&msm_mpd_driver); + rc = input_register_handler(&mpdec_input_handler); +#endif + + if (state != MSM_MPDEC_DISABLED) + queue_delayed_work(msm_mpdec_workq, &msm_mpdec_work, + msecs_to_jiffies(msm_mpdec_tuners_ins.startdelay)); + + msm_mpdec_kobject = kobject_create_and_add("msm_mpdecision", kernel_kobj); + if (msm_mpdec_kobject) { + rc = sysfs_create_group(msm_mpdec_kobject, + &msm_mpdec_attr_group); + if (rc) { + pr_warn(MPDEC_TAG"sysfs: ERROR, could not create sysfs group"); + } + rc = sysfs_create_group(msm_mpdec_kobject, + &msm_mpdec_stats_attr_group); + if (rc) { + pr_warn(MPDEC_TAG"sysfs: ERROR, could not create sysfs stats group"); + } + } else + pr_warn(MPDEC_TAG"sysfs: ERROR, could not create sysfs kobj"); + + pr_info(MPDEC_TAG"%s init complete.", __func__); + + + register_power_suspend(&msm_mpdec_early_suspend_handler); + + return err; +} +late_initcall(msm_mpdec_init); + +void msm_mpdec_exit(void) { +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN + input_unregister_handler(&mpdec_input_handler); + destroy_workqueue(msm_mpdec_revib_workq); + destroy_workqueue(mpdec_input_wq); +#endif + destroy_workqueue(msm_mpdec_workq); } -late_initcall(msm_mpdecision_init); diff --git a/arch/arm/mach-msm/msm_mpdecision.h b/arch/arm/mach-msm/msm_mpdecision.h new file mode 100644 index 000000000000..6b47a58e9628 --- /dev/null +++ b/arch/arm/mach-msm/msm_mpdecision.h @@ -0,0 +1,85 @@ +/* + * arch/arm/mach-msm/msm_mpdecision.h + * + * Copyright (c) 2012-2013, Dennis Rassmann + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __MSM_MPDEC_H__ +#define __MSM_MPDEC_H__ + +#include +#include + +#define MPDEC_TAG "[MPDEC]: " +#define MSM_MPDEC_STARTDELAY 20000 +#define MSM_MPDEC_DELAY 130 +#define MSM_MPDEC_PAUSE 10000 + +#ifdef CONFIG_ARCH_MSM8974 +#define MSM_MPDEC_IDLE_FREQ 422400 +#elif defined CONFIG_ARCH_MSM8X60 || defined CONFIG_ARCH_MSM8960 || defined CONFIG_ARCH_MSM8930 +#define MSM_MPDEC_IDLE_FREQ 486000 +#else +#define MSM_MPDEC_IDLE_FREQ 486000 +#endif + +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN +#define MSM_MPDEC_BOOSTTIME 1000 +#ifdef CONFIG_ARCH_MSM8974 +#define MSM_MPDEC_BOOSTFREQ_CPU0 960000 +#define MSM_MPDEC_BOOSTFREQ_CPU1 960000 +#define MSM_MPDEC_BOOSTFREQ_CPU2 729600 +#define MSM_MPDEC_BOOSTFREQ_CPU3 576000 +#elif defined CONFIG_ARCH_MSM8X60 || defined CONFIG_ARCH_MSM8960 || defined CONFIG_ARCH_MSM8930 +#define MSM_MPDEC_BOOSTFREQ_CPU0 918000 +#define MSM_MPDEC_BOOSTFREQ_CPU1 918000 +#define MSM_MPDEC_BOOSTFREQ_CPU2 702000 +#define MSM_MPDEC_BOOSTFREQ_CPU3 594000 +#else +#define MSM_MPDEC_BOOSTFREQ_CPU0 918000 +#define MSM_MPDEC_BOOSTFREQ_CPU1 918000 +#define MSM_MPDEC_BOOSTFREQ_CPU2 702000 +#define MSM_MPDEC_BOOSTFREQ_CPU3 594000 +#endif +#endif + +enum { + MSM_MPDEC_DISABLED = 0, + MSM_MPDEC_IDLE, + MSM_MPDEC_DOWN, + MSM_MPDEC_UP, +}; + +struct msm_mpdec_cpudata_t { + struct mutex hotplug_mutex; + int online; + cputime64_t on_time; + cputime64_t on_time_total; + long long unsigned int times_cpu_hotplugged; + long long unsigned int times_cpu_unplugged; +#ifdef CONFIG_MSM_MPDEC_INPUTBOOST_CPUMIN + struct mutex boost_mutex; + struct mutex unboost_mutex; + unsigned long int norm_min_freq; + unsigned long int boost_freq; + cputime64_t boost_until; + bool is_boosted; + bool revib_wq_running; +#endif +}; +#endif //__MSM_MPDEC_H__ + diff --git a/arch/arm/mach-msm/msm_rq_stats.c b/arch/arm/mach-msm/msm_rq_stats.c index af4a6d23d70b..590b1b7a27b4 100644 --- a/arch/arm/mach-msm/msm_rq_stats.c +++ b/arch/arm/mach-msm/msm_rq_stats.c @@ -236,6 +236,22 @@ static ssize_t hotplug_disable_show(struct kobject *kobj, static struct kobj_attribute hotplug_disabled_attr = __ATTR_RO(hotplug_disable); +unsigned int get_rq_info(void) +{ +unsigned long flags = 0; + unsigned int rq = 0; + + spin_lock_irqsave(&rq_lock, flags); + + rq = rq_info.rq_avg; + rq_info.rq_avg = 0; + + spin_unlock_irqrestore(&rq_lock, flags); + + return rq; +} +EXPORT_SYMBOL(get_rq_info); + static void def_work_fn(struct work_struct *work) { int64_t diff; diff --git a/arch/arm/mach-msm/restart.c b/arch/arm/mach-msm/restart.c index b98f4499c60b..8895c1a45545 100644 --- a/arch/arm/mach-msm/restart.c +++ b/arch/arm/mach-msm/restart.c @@ -38,6 +38,10 @@ #include "timer.h" #include "wdog_debug.h" +#ifdef CONFIG_KEXEC_HARDBOOT +#include +#endif + #define WDT0_RST 0x38 #define WDT0_EN 0x40 #define WDT0_BARK_TIME 0x4C @@ -348,6 +352,26 @@ static int __init msm_pmic_restart_init(void) late_initcall(msm_pmic_restart_init); +#ifdef CONFIG_KEXEC_HARDBOOT +static void msm_kexec_hardboot_hook(void) +{ + set_dload_mode(0); + + // Set PMIC to restart-on-poweroff + pm8xxx_reset_pwr_off(1); + + // These are executed on normal reboot, but with kexec-hardboot, + // they reboot/panic the system immediately. +#if 0 + qpnp_pon_system_pwr_off(PON_POWER_OFF_WARM_RESET); + + /* Needed to bypass debug image on some chips */ + msm_disable_wdog_debug(); + halt_spmi_pmic_arbiter(); +#endif +} +#endif + static int msm_reboot_call(struct notifier_block *this, unsigned long code, void *_cmd) { @@ -378,6 +402,10 @@ static int __init msm_restart_init(void) if (scm_is_call_available(SCM_SVC_PWR, SCM_IO_DISABLE_PMIC_ARBITER) > 0) scm_pmic_arbiter_disable_supported = true; +#ifdef CONFIG_KEXEC_HARDBOOT + kexec_hardboot_hook = msm_kexec_hardboot_hook; +#endif + return 0; } early_initcall(msm_restart_init); diff --git a/arch/mips/power/hibernate.S b/arch/mips/power/hibernate.S index f8a751c03282..5bf34ec89669 100644 --- a/arch/mips/power/hibernate.S +++ b/arch/mips/power/hibernate.S @@ -44,6 +44,7 @@ LEAF(swsusp_arch_resume) bne t1, t3, 1b PTR_L t0, PBE_NEXT(t0) bnez t0, 0b + jal local_flush_tlb_all /* Avoid TLB mismatch after kernel resume */ PTR_LA t0, saved_regs PTR_L ra, PT_R31(t0) PTR_L sp, PT_R29(t0) diff --git a/arch/powerpc/lib/crtsavres.S b/arch/powerpc/lib/crtsavres.S index 1c893f05d224..21ecdf5e55f9 100644 --- a/arch/powerpc/lib/crtsavres.S +++ b/arch/powerpc/lib/crtsavres.S @@ -230,6 +230,87 @@ _GLOBAL(_rest32gpr_31_x) mr 1,11 blr +#ifdef CONFIG_ALTIVEC +/* Called with r0 pointing just beyond the end of the vector save area. */ + +_GLOBAL(_savevr_20) + li r11,-192 + stvx vr20,r11,r0 +_GLOBAL(_savevr_21) + li r11,-176 + stvx vr21,r11,r0 +_GLOBAL(_savevr_22) + li r11,-160 + stvx vr22,r11,r0 +_GLOBAL(_savevr_23) + li r11,-144 + stvx vr23,r11,r0 +_GLOBAL(_savevr_24) + li r11,-128 + stvx vr24,r11,r0 +_GLOBAL(_savevr_25) + li r11,-112 + stvx vr25,r11,r0 +_GLOBAL(_savevr_26) + li r11,-96 + stvx vr26,r11,r0 +_GLOBAL(_savevr_27) + li r11,-80 + stvx vr27,r11,r0 +_GLOBAL(_savevr_28) + li r11,-64 + stvx vr28,r11,r0 +_GLOBAL(_savevr_29) + li r11,-48 + stvx vr29,r11,r0 +_GLOBAL(_savevr_30) + li r11,-32 + stvx vr30,r11,r0 +_GLOBAL(_savevr_31) + li r11,-16 + stvx vr31,r11,r0 + blr + +_GLOBAL(_restvr_20) + li r11,-192 + lvx vr20,r11,r0 +_GLOBAL(_restvr_21) + li r11,-176 + lvx vr21,r11,r0 +_GLOBAL(_restvr_22) + li r11,-160 + lvx vr22,r11,r0 +_GLOBAL(_restvr_23) + li r11,-144 + lvx vr23,r11,r0 +_GLOBAL(_restvr_24) + li r11,-128 + lvx vr24,r11,r0 +_GLOBAL(_restvr_25) + li r11,-112 + lvx vr25,r11,r0 +_GLOBAL(_restvr_26) + li r11,-96 + lvx vr26,r11,r0 +_GLOBAL(_restvr_27) + li r11,-80 + lvx vr27,r11,r0 +_GLOBAL(_restvr_28) + li r11,-64 + lvx vr28,r11,r0 +_GLOBAL(_restvr_29) + li r11,-48 + lvx vr29,r11,r0 +_GLOBAL(_restvr_30) + li r11,-32 + lvx vr30,r11,r0 +_GLOBAL(_restvr_31) + li r11,-16 + lvx vr31,r11,r0 + blr + +#endif /* CONFIG_ALTIVEC */ + #else /* CONFIG_PPC64 */ .globl _savegpr0_14 @@ -353,6 +434,111 @@ _restgpr0_31: mtlr r0 blr +#ifdef CONFIG_ALTIVEC +/* Called with r0 pointing just beyond the end of the vector save area. */ + +.globl _savevr_20 +_savevr_20: + li r12,-192 + stvx vr20,r12,r0 +.globl _savevr_21 +_savevr_21: + li r12,-176 + stvx vr21,r12,r0 +.globl _savevr_22 +_savevr_22: + li r12,-160 + stvx vr22,r12,r0 +.globl _savevr_23 +_savevr_23: + li r12,-144 + stvx vr23,r12,r0 +.globl _savevr_24 +_savevr_24: + li r12,-128 + stvx vr24,r12,r0 +.globl _savevr_25 +_savevr_25: + li r12,-112 + stvx vr25,r12,r0 +.globl _savevr_26 +_savevr_26: + li r12,-96 + stvx vr26,r12,r0 +.globl _savevr_27 +_savevr_27: + li r12,-80 + stvx vr27,r12,r0 +.globl _savevr_28 +_savevr_28: + li r12,-64 + stvx vr28,r12,r0 +.globl _savevr_29 +_savevr_29: + li r12,-48 + stvx vr29,r12,r0 +.globl _savevr_30 +_savevr_30: + li r12,-32 + stvx vr30,r12,r0 +.globl _savevr_31 +_savevr_31: + li r12,-16 + stvx vr31,r12,r0 + blr + +.globl _restvr_20 +_restvr_20: + li r12,-192 + lvx vr20,r12,r0 +.globl _restvr_21 +_restvr_21: + li r12,-176 + lvx vr21,r12,r0 +.globl _restvr_22 +_restvr_22: + li r12,-160 + lvx vr22,r12,r0 +.globl _restvr_23 +_restvr_23: + li r12,-144 + lvx vr23,r12,r0 +.globl _restvr_24 +_restvr_24: + li r12,-128 + lvx vr24,r12,r0 +.globl _restvr_25 +_restvr_25: + li r12,-112 + lvx vr25,r12,r0 +.globl _restvr_26 +_restvr_26: + li r12,-96 + lvx vr26,r12,r0 +.globl _restvr_27 +_restvr_27: + li r12,-80 + lvx vr27,r12,r0 +.globl _restvr_28 +_restvr_28: + li r12,-64 + lvx vr28,r12,r0 +.globl _restvr_29 +_restvr_29: + li r12,-48 + lvx vr29,r12,r0 +.globl _restvr_30 +_restvr_30: + li r12,-32 + lvx vr30,r12,r0 +.globl _restvr_31 +_restvr_31: + li r12,-16 + lvx vr31,r12,r0 + blr + +#endif /* CONFIG_ALTIVEC */ + #endif /* CONFIG_PPC64 */ #endif diff --git a/arch/x86/crypto/ghash-clmulni-intel_asm.S b/arch/x86/crypto/ghash-clmulni-intel_asm.S index 1eb7f90cb7b9..eb4d2a254b35 100644 --- a/arch/x86/crypto/ghash-clmulni-intel_asm.S +++ b/arch/x86/crypto/ghash-clmulni-intel_asm.S @@ -24,10 +24,6 @@ .align 16 .Lbswap_mask: .octa 0x000102030405060708090a0b0c0d0e0f -.Lpoly: - .octa 0xc2000000000000000000000000000001 -.Ltwo_one: - .octa 0x00000001000000000000000000000001 #define DATA %xmm0 #define SHASH %xmm1 @@ -131,27 +127,3 @@ ENTRY(clmul_ghash_update) movups DATA, (%rdi) .Lupdate_just_ret: ret - -/* - * void clmul_ghash_setkey(be128 *shash, const u8 *key); - * - * Calculate hash_key << 1 mod poly - */ -ENTRY(clmul_ghash_setkey) - movaps .Lbswap_mask, BSWAP - movups (%rsi), %xmm0 - PSHUFB_XMM BSWAP %xmm0 - movaps %xmm0, %xmm1 - psllq $1, %xmm0 - psrlq $63, %xmm1 - movaps %xmm1, %xmm2 - pslldq $8, %xmm1 - psrldq $8, %xmm2 - por %xmm1, %xmm0 - # reduction - pshufd $0b00100100, %xmm2, %xmm1 - pcmpeqd .Ltwo_one, %xmm1 - pand .Lpoly, %xmm1 - pxor %xmm1, %xmm0 - movups %xmm0, (%rdi) - ret diff --git a/arch/x86/crypto/ghash-clmulni-intel_glue.c b/arch/x86/crypto/ghash-clmulni-intel_glue.c index b4bf0a63b520..c07446d17463 100644 --- a/arch/x86/crypto/ghash-clmulni-intel_glue.c +++ b/arch/x86/crypto/ghash-clmulni-intel_glue.c @@ -30,8 +30,6 @@ void clmul_ghash_mul(char *dst, const be128 *shash); void clmul_ghash_update(char *dst, const char *src, unsigned int srclen, const be128 *shash); -void clmul_ghash_setkey(be128 *shash, const u8 *key); - struct ghash_async_ctx { struct cryptd_ahash *cryptd_tfm; }; @@ -58,13 +56,23 @@ static int ghash_setkey(struct crypto_shash *tfm, const u8 *key, unsigned int keylen) { struct ghash_ctx *ctx = crypto_shash_ctx(tfm); + be128 *x = (be128 *)key; + u64 a, b; if (keylen != GHASH_BLOCK_SIZE) { crypto_shash_set_flags(tfm, CRYPTO_TFM_RES_BAD_KEY_LEN); return -EINVAL; } - clmul_ghash_setkey(&ctx->shash, key); + /* perform multiplication by 'x' in GF(2^128) */ + a = be64_to_cpu(x->a); + b = be64_to_cpu(x->b); + + ctx->shash.a = (__be64)((b << 1) | (a >> 63)); + ctx->shash.b = (__be64)((a << 1) | (b >> 63)); + + if (a >> 63) + ctx->shash.b ^= cpu_to_be64(0xc2); return 0; } diff --git a/block/blk-core.c b/block/blk-core.c index 8d2f43400f05..45f15b2e7076 100644 --- a/block/blk-core.c +++ b/block/blk-core.c @@ -2233,7 +2233,7 @@ bool blk_update_request(struct request *req, int error, unsigned int nr_bytes) if (!req->bio) return false; - trace_block_rq_complete(req->q, req); + trace_block_rq_complete(req->q, req, nr_bytes); /* * For fs requests, rq is just carrier of independent bio's diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 23763a1ec570..3d67e21d6b7c 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -4677,21 +4677,26 @@ void swap_buf_le16(u16 *buf, unsigned int buf_words) static struct ata_queued_cmd *ata_qc_new(struct ata_port *ap) { struct ata_queued_cmd *qc = NULL; - unsigned int i; + unsigned int i, tag; /* no command while frozen */ if (unlikely(ap->pflags & ATA_PFLAG_FROZEN)) return NULL; - /* the last tag is reserved for internal command. */ - for (i = 0; i < ATA_MAX_QUEUE - 1; i++) - if (!test_and_set_bit(i, &ap->qc_allocated)) { - qc = __ata_qc_from_tag(ap, i); + for (i = 0; i < ATA_MAX_QUEUE; i++) { + tag = (i + ap->last_tag + 1) % ATA_MAX_QUEUE; + + /* the last tag is reserved for internal command. */ + if (tag == ATA_TAG_INTERNAL) + continue; + + if (!test_and_set_bit(tag, &ap->qc_allocated)) { + qc = __ata_qc_from_tag(ap, tag); + qc->tag = tag; + ap->last_tag = tag; break; } - - if (qc) - qc->tag = i; + } return qc; } diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c index b0b00d70c166..ba150c7c67ee 100644 --- a/drivers/block/floppy.c +++ b/drivers/block/floppy.c @@ -3058,7 +3058,10 @@ static int raw_cmd_copyout(int cmd, void __user *param, int ret; while (ptr) { - ret = copy_to_user(param, ptr, sizeof(*ptr)); + struct floppy_raw_cmd cmd = *ptr; + cmd.next = NULL; + cmd.kernel_data = NULL; + ret = copy_to_user(param, &cmd, sizeof(cmd)); if (ret) return -EFAULT; param += sizeof(struct floppy_raw_cmd); @@ -3112,10 +3115,11 @@ static int raw_cmd_copyin(int cmd, void __user *param, return -ENOMEM; *rcmd = ptr; ret = copy_from_user(ptr, param, sizeof(*ptr)); - if (ret) - return -EFAULT; ptr->next = NULL; ptr->buffer_length = 0; + ptr->kernel_data = NULL; + if (ret) + return -EFAULT; param += sizeof(struct floppy_raw_cmd); if (ptr->cmd_count > 33) /* the command may now also take up the space @@ -3131,7 +3135,6 @@ static int raw_cmd_copyin(int cmd, void __user *param, for (i = 0; i < 16; i++) ptr->reply[i] = 0; ptr->resultcode = 0; - ptr->kernel_data = NULL; if (ptr->flags & (FD_RAW_READ | FD_RAW_WRITE)) { if (ptr->length <= 0) diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index ca7953d26309..2aaf1035b620 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -71,6 +71,15 @@ config DEVFREQ_GOV_MSM_ADRENO_TZ Sets the frequency using a "on-demand" algorithm. This governor is unlikely to be useful for other devices. +config ADRENO_IDLER + tristate "MSM Adreno idler" + depends on DEVFREQ_GOV_MSM_ADRENO_TZ + help + Uses a different calculation method on top of Adreno TZ + just for calculating frequency for idle to reduce the + wasted power coming from stock Adreno TZ while + maintaining high-performance. + config DEVFREQ_GOV_MSM_CPUFREQ bool "MSM CPUfreq" depends on CPU_FREQ_MSM diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index e06ed9f55357..7b362ce2b166 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o obj-$(CONFIG_DEVFREQ_GOV_MSM_ADRENO_TZ) += governor_msm_adreno_tz.o +obj-$(CONFIG_ADRENO_IDLER) += adreno_idler.o obj-$(CONFIG_DEVFREQ_GOV_MSM_CPUFREQ) += governor_msm_cpufreq.o obj-$(CONFIG_DEVFREQ_GOV_MSM_CPUBW_HWMON) += governor_cpubw_hwmon.o diff --git a/drivers/devfreq/adreno_idler.c b/drivers/devfreq/adreno_idler.c new file mode 100644 index 000000000000..0e6cd27b1b1c --- /dev/null +++ b/drivers/devfreq/adreno_idler.c @@ -0,0 +1,113 @@ +/* + * Author: Park Ju Hyung aka arter97 + * + * Copyright 2015 Park Ju Hyung + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +/* + * Adreno idler - Idling algorithm, + * an efficient workaround for msm-adreno-tz's overheads. + * + * Main goal is to lower the power consumptions while maintaining high-performance. + * + * Since msm-adreno-tz tends to *not* use the lowest frequency even on idle, + * Adreno idler replaces msm-adreno-tz's algorithm when it comes to + * calculating idle frequency(mostly by ondemand's method). + * The higher frequencies are not touched with this algorithm, so high-demanding + * games will (most likely) not suffer from worsened performance. + */ + +#include +#include +#include + +#define ADRENO_IDLER_MAJOR_VERSION 1 +#define ADRENO_IDLER_MINOR_VERSION 1 + +/* stats.busy_time threshold for determining if the given workload is idle. + Any workload higher than this will be treated as a non-idle workload. + Adreno idler will more actively try to ramp down the frequency + if this is set to a higher value. */ +static unsigned long idleworkload = 5000; +module_param_named(adreno_idler_idleworkload, idleworkload, ulong, 0664); + +/* Number of events to wait before ramping down the frequency. + The idlewait'th events before current one must be all idle before + Adreno idler ramps down the frequency. + This implementation is to prevent micro-lags on scrolling or playing games. + Adreno idler will more actively try to ramp down the frequency + if this is set to a lower value. */ +static unsigned int idlewait = 20; +module_param_named(adreno_idler_idlewait, idlewait, uint, 0664); + +/* Taken from ondemand */ +static unsigned int downdifferential = 20; +module_param_named(adreno_idler_downdifferential, downdifferential, uint, 0664); + +/* Master switch to activate the whole routine */ +static bool adreno_idler_active = true; +module_param_named(adreno_idler_active, adreno_idler_active, bool, 0664); + +static unsigned int idlecount = 0; + +int adreno_idler(struct devfreq_dev_status stats, struct devfreq *devfreq, + unsigned long *freq) +{ + if (!adreno_idler_active) + return 0; + + if (stats.busy_time < idleworkload) { + /* busy_time >= idleworkload should be considered as a non-idle workload. */ + idlecount++; + if (*freq == devfreq->profile->freq_table[devfreq->profile->max_state - 1]) { + /* Frequency is already at its lowest. + No need to calculate things, so bail out. */ + return 1; + } + if (idlecount >= idlewait && + stats.busy_time * 100 < stats.total_time * downdifferential) { + /* We are idle for (idlewait + 1)'th time! Ramp down the frequency now. */ + *freq = devfreq->profile->freq_table[devfreq->profile->max_state - 1]; + idlecount--; + return 1; + } + } else { + idlecount = 0; + /* Do not return 1 here and allow rest of the algorithm to + figure out the appropriate frequency for current workload. + It can even set it back to the lowest frequency. */ + } + return 0; +} +EXPORT_SYMBOL(adreno_idler); + +static int __init adreno_idler_init(void) +{ + pr_info("adreno_idler: version %d.%d by arter97\n", + ADRENO_IDLER_MAJOR_VERSION, + ADRENO_IDLER_MINOR_VERSION); + + return 0; +} +subsys_initcall(adreno_idler_init); + +static void __exit adreno_idler_exit(void) +{ + return; +} +module_exit(adreno_idler_exit); + +MODULE_AUTHOR("Park Ju Hyung "); +MODULE_DESCRIPTION("'adreno_idler - A powersaver for Adreno TZ" + "Control idle algorithm for Adreno GPU series"); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index e13fb5f44f9f..771c52d2277f 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -935,30 +935,14 @@ static ssize_t show_available_freqs(struct device *d, char *buf) { struct devfreq *df = to_devfreq(d); - struct device *dev = df->dev.parent; - struct opp *opp; - ssize_t count = 0; - unsigned long freq = 0; - - rcu_read_lock(); - do { - opp = opp_find_freq_ceil(dev, &freq); - if (IS_ERR(opp)) - break; - - count += scnprintf(&buf[count], (PAGE_SIZE - count - 2), - "%lu ", freq); - freq++; - } while (1); - rcu_read_unlock(); + int index, num_chars = 0; - /* Truncate the trailing space */ - if (count) - count--; + for (index = 0; index < df->profile->max_state; index++) + num_chars += snprintf(buf + num_chars, PAGE_SIZE, "%d ", + df->profile->freq_table[index]); + buf[num_chars++] = '\n'; - count += sprintf(&buf[count], "\n"); - - return count; + return num_chars; } static ssize_t show_trans_table(struct device *dev, struct device_attribute *attr, @@ -1024,7 +1008,10 @@ static int __init devfreq_init(void) return PTR_ERR(devfreq_class); } - devfreq_wq = create_freezable_workqueue("devfreq_wq"); + devfreq_wq = + alloc_workqueue("devfreq_wq", + WQ_HIGHPRI | WQ_UNBOUND | WQ_FREEZABLE | + WQ_MEM_RECLAIM, 0); if (IS_ERR(devfreq_wq)) { class_destroy(devfreq_class); pr_err("%s: couldn't create workqueue\n", __FILE__); diff --git a/drivers/devfreq/governor_msm_adreno_tz.c b/drivers/devfreq/governor_msm_adreno_tz.c index 8c97fe9182c8..337ed5ff3716 100644 --- a/drivers/devfreq/governor_msm_adreno_tz.c +++ b/drivers/devfreq/governor_msm_adreno_tz.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "governor.h" @@ -34,6 +35,14 @@ static DEFINE_SPINLOCK(tz_lock); #define TARGET 80 #define CAP 75 +/* + * Use BUSY_BIN to check for fully busy rendering + * intervals that may need early intervention when + * seen with LONG_FRAME lengths + */ +#define BUSY_BIN 95 +#define LONG_FRAME 25000 + /* * CEILING is 50msec, larger than any standard * frame length, but less than the idle timer. @@ -45,6 +54,12 @@ static DEFINE_SPINLOCK(tz_lock); #define TAG "msm_adreno_tz: " +static unsigned int tz_target = TARGET; +static unsigned int tz_cap = CAP; + +/* Boolean to detect if pm has entered suspend mode */ +static bool suspended = false; + /* Trap into the TrustZone, and call funcs there. */ static int __secure_tz_entry2(u32 cmd, u32 val1, u32 val2) { @@ -80,6 +95,17 @@ static void _update_cutoff(struct devfreq_msm_adreno_tz_data *priv, } } +#ifdef CONFIG_ADRENO_IDLER +extern int adreno_idler(struct devfreq_dev_status stats, struct devfreq *devfreq, + unsigned long *freq); +#endif + +#ifdef CONFIG_SIMPLE_GPU_ALGORITHM +extern int simple_gpu_active; +extern int simple_gpu_algorithm(int level, + struct devfreq_msm_adreno_tz_data *priv); +#endif + static int tz_get_target_freq(struct devfreq *devfreq, unsigned long *freq, u32 *flag) { @@ -91,19 +117,44 @@ static int tz_get_target_freq(struct devfreq *devfreq, unsigned long *freq, int act_level; int norm_cycles; int gpu_percent; + static int busy_bin, frame_flag; if (priv->bus.num) stats.private_data = &b; else stats.private_data = NULL; + result = devfreq->profile->get_dev_status(devfreq->dev.parent, &stats); if (result) { pr_err(TAG "get_status failed %d\n", result); return result; } + /* Prevent overflow */ + if (stats.busy_time >= (1 << 24) || stats.total_time >= (1 << 24)) { + stats.busy_time >>= 7; + stats.total_time >>= 7; + } + *freq = stats.current_frequency; *flag = 0; + + /* + * Force to use & record as min freq when system has + * entered pm-suspend or screen-off state. + */ + if (suspended || power_suspended) { + *freq = devfreq->profile->freq_table[devfreq->profile->max_state - 1]; + return 0; + } + +#ifdef CONFIG_ADRENO_IDLER + if (adreno_idler(stats, devfreq, freq)) { + /* adreno_idler has asked to bail out now */ + return 0; + } +#endif + priv->bin.total_time += stats.total_time; priv->bin.busy_time += stats.busy_time; if (priv->bus.num) { @@ -123,6 +174,15 @@ static int tz_get_target_freq(struct devfreq *devfreq, unsigned long *freq, return 1; } + if ((stats.busy_time * 100 / stats.total_time) > BUSY_BIN) { + busy_bin += stats.busy_time; + if (stats.total_time > LONG_FRAME) + frame_flag = 1; + } else { + busy_bin = 0; + frame_flag = 0; + } + level = devfreq_get_freq_level(devfreq, stats.current_frequency); if (level < 0) { @@ -134,8 +194,11 @@ static int tz_get_target_freq(struct devfreq *devfreq, unsigned long *freq, * If there is an extended block of busy processing, * increase frequency. Otherwise run the normal algorithm. */ - if (priv->bin.busy_time > CEILING) { + if (priv->bin.busy_time > CEILING || + (busy_bin > CEILING && frame_flag)) { val = -1 * level; + busy_bin = 0; + frame_flag = 0; } else { val = __secure_tz_entry3(TZ_UPDATE_ID, level, @@ -152,7 +215,7 @@ static int tz_get_target_freq(struct devfreq *devfreq, unsigned long *freq, if (val) { level += val; level = max(level, 0); - level = min_t(int, level, devfreq->profile->max_state); + level = min_t(int, level, devfreq->profile->max_state - 1); goto clear; } @@ -283,6 +346,8 @@ static int tz_resume(struct devfreq *devfreq) struct devfreq_dev_profile *profile = devfreq->profile; unsigned long freq; + suspended = false; + freq = profile->initial_freq; return profile->target(devfreq->dev.parent, &freq, 0); @@ -292,6 +357,8 @@ static int tz_suspend(struct devfreq *devfreq) { struct devfreq_msm_adreno_tz_data *priv = devfreq->data; + suspended = true; + __secure_tz_entry2(TZ_RESET_ID, 0, 0); priv->bin.total_time = 0; diff --git a/drivers/gpio/gpio-mxs.c b/drivers/gpio/gpio-mxs.c index 385c58e8405b..0f8114de0877 100644 --- a/drivers/gpio/gpio-mxs.c +++ b/drivers/gpio/gpio-mxs.c @@ -167,7 +167,8 @@ static void __init mxs_gpio_init_gc(struct mxs_gpio_port *port) ct->regs.ack = PINCTRL_IRQSTAT(port->id) + MXS_CLR; ct->regs.mask = PINCTRL_IRQEN(port->id); - irq_setup_generic_chip(gc, IRQ_MSK(32), 0, IRQ_NOREQUEST, 0); + irq_setup_generic_chip(gc, IRQ_MSK(32), IRQ_GC_INIT_NESTED_LOCK, + IRQ_NOREQUEST, 0); } static int mxs_gpio_to_irq(struct gpio_chip *gc, unsigned offset) diff --git a/drivers/gpu/drm/i915/intel_crt.c b/drivers/gpu/drm/i915/intel_crt.c index 90b9793fd5da..18167d6bc81f 100644 --- a/drivers/gpu/drm/i915/intel_crt.c +++ b/drivers/gpu/drm/i915/intel_crt.c @@ -549,6 +549,14 @@ static const struct dmi_system_id intel_no_crt[] = { DMI_MATCH(DMI_PRODUCT_NAME, "ZGB"), }, }, + { + .callback = intel_no_crt_dmi_callback, + .ident = "DELL XPS 8700", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS 8700"), + }, + }, { } }; diff --git a/drivers/gpu/msm/adreno.c b/drivers/gpu/msm/adreno.c index 519210e78842..7b412a137354 100644 --- a/drivers/gpu/msm/adreno.c +++ b/drivers/gpu/msm/adreno.c @@ -2239,15 +2239,15 @@ int adreno_reset(struct kgsl_device *device) * * This is a common routine to write to FT sysfs files. */ -static int _ft_sysfs_store(const char *buf, size_t count, unsigned int *ptr) +static int _ft_sysfs_store(const char *buf, size_t count, int *ptr) { char temp[20]; - unsigned long val; + long val; int rc; snprintf(temp, sizeof(temp), "%.*s", (int)min(count, sizeof(temp) - 1), buf); - rc = kstrtoul(temp, 0, &val); + rc = kstrtol(temp, 0, &val); if (rc) return rc; @@ -2612,6 +2612,36 @@ static ssize_t _ft_hang_intr_status_show(struct device *dev, test_bit(ADRENO_DEVICE_HANG_INTR, &adreno_dev->priv) ? 1 : 0); } +/** + * _wake_nice_store() - Store nice level for the higher priority GPU start + * thread + * @dev: device ptr + * @attr: Device attribute + * @buf: value to write + * @count: size of the value to write + * + */ +static ssize_t _wake_nice_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return _ft_sysfs_store(buf, count, &_wake_nice); +} + +/** + * _wake_nice_show() - Show nice level for the higher priority GPU start + * thread + * @dev: device ptr + * @attr: Device attribute + * @buf: value read + */ +static ssize_t _wake_nice_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", _wake_nice); +} + #define FT_DEVICE_ATTR(name) \ DEVICE_ATTR(name, 0644, _ ## name ## _show, _ ## name ## _store); @@ -2621,7 +2651,7 @@ FT_DEVICE_ATTR(ft_fast_hang_detect); FT_DEVICE_ATTR(ft_long_ib_detect); FT_DEVICE_ATTR(ft_hang_intr_status); -static DEVICE_INT_ATTR(wake_nice, 0644, _wake_nice); +static FT_DEVICE_ATTR(wake_nice); static FT_DEVICE_ATTR(wake_timeout); const struct device_attribute *ft_attr_list[] = { @@ -2629,7 +2659,7 @@ const struct device_attribute *ft_attr_list[] = { &dev_attr_ft_pagefault_policy, &dev_attr_ft_fast_hang_detect, &dev_attr_ft_long_ib_detect, - &dev_attr_wake_nice.attr, + &dev_attr_wake_nice, &dev_attr_wake_timeout, &dev_attr_ft_hang_intr_status, NULL, diff --git a/drivers/gpu/msm/kgsl_pwrscale.c b/drivers/gpu/msm/kgsl_pwrscale.c index b0b1474290e5..4af12c0488b2 100644 --- a/drivers/gpu/msm/kgsl_pwrscale.c +++ b/drivers/gpu/msm/kgsl_pwrscale.c @@ -420,7 +420,11 @@ int kgsl_pwrscale_init(struct device *dev, const char *governor) for (i = 0; i < (pwr->num_pwrlevels - 1); i++) pwrscale->freq_table[out++] = pwr->pwrlevels[i].gpu_freq; - profile->max_state = out; + /* + * Max_state is the number of valid power levels. + * The valid power levels range from 0 - (max_state - 1) + */ + profile->max_state = pwr->num_pwrlevels - 1; /* link storage array to the devfreq profile pointer */ profile->freq_table = pwrscale->freq_table; diff --git a/drivers/input/touchscreen/clearpad_core.c b/drivers/input/touchscreen/clearpad_core.c index c8cc898e756d..098027b50c59 100644 --- a/drivers/input/touchscreen/clearpad_core.c +++ b/drivers/input/touchscreen/clearpad_core.c @@ -2541,6 +2541,8 @@ static void clearpad_funcarea_initialize(struct clearpad_t *this) pointer_area.y1 -= pointer_data->offset_y; pointer_area.y2 -= pointer_data->offset_y; } + input_mt_init_slots(this->input, + this->extents.n_fingers); input_set_abs_params(this->input, ABS_MT_TRACKING_ID, 0, this->extents.n_fingers, 0, 0); input_set_abs_params(this->input, ABS_MT_POSITION_X, @@ -2680,8 +2682,8 @@ static void clearpad_funcarea_down(struct clearpad_t *this, break; touch_major = max(cur->wx, cur->wy) + 1; touch_minor = min(cur->wx, cur->wy) + 1; - input_report_abs(idev, ABS_MT_TRACKING_ID, cur->id); - input_report_abs(idev, ABS_MT_TOOL_TYPE, cur->tool); + input_mt_slot(idev, cur->id); + input_mt_report_slot_state(idev, cur->tool, true); input_report_abs(idev, ABS_MT_POSITION_X, cur->x); input_report_abs(idev, ABS_MT_POSITION_Y, cur->y); if (this->touch_pressure_enabled) @@ -2693,7 +2695,6 @@ static void clearpad_funcarea_down(struct clearpad_t *this, if (this->touch_orientation_enabled) input_report_abs(idev, ABS_MT_ORIENTATION, (cur->wx > cur->wy)); - input_mt_sync(idev); break; case SYN_FUNCAREA_BUTTON: LOG_EVENT(this, "button\n"); @@ -2723,7 +2724,8 @@ static void clearpad_funcarea_up(struct clearpad_t *this, LOG_EVENT(this, "%s up\n", valid ? "pt" : "unused pt"); if (!valid) break; - input_mt_sync(idev); + input_mt_slot(idev, pointer->cur.id); + input_mt_report_slot_state(idev, pointer->cur.tool, false); break; case SYN_FUNCAREA_BUTTON: LOG_EVENT(this, "button up\n"); diff --git a/drivers/md/dm-thin.c b/drivers/md/dm-thin.c index eb3d138ff55a..86aac5cdb1ce 100644 --- a/drivers/md/dm-thin.c +++ b/drivers/md/dm-thin.c @@ -1442,9 +1442,9 @@ static void process_deferred_bios(struct pool *pool) */ if (ensure_next_mapping(pool)) { spin_lock_irqsave(&pool->lock, flags); + bio_list_add(&pool->deferred_bios, bio); bio_list_merge(&pool->deferred_bios, &bios); spin_unlock_irqrestore(&pool->lock, flags); - break; } diff --git a/drivers/md/md.c b/drivers/md/md.c index 01233d855eb2..ec459ae542e1 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -344,6 +344,10 @@ static void md_make_request(struct request_queue *q, struct bio *bio) bio_io_error(bio); return; } + if (mddev->ro == 1 && unlikely(rw == WRITE)) { + bio_endio(bio, bio_sectors(bio) == 0 ? 0 : -EROFS); + return; + } smp_rmb(); /* Ensure implications of 'active' are visible */ rcu_read_lock(); if (mddev->suspended) { @@ -452,7 +456,7 @@ static void submit_flushes(struct work_struct *ws) atomic_inc(&rdev->nr_pending); atomic_inc(&rdev->nr_pending); rcu_read_unlock(); - bi = bio_alloc_mddev(GFP_KERNEL, 0, mddev); + bi = bio_alloc_mddev(GFP_NOIO, 0, mddev); bi->bi_end_io = md_end_flush; bi->bi_private = rdev; bi->bi_bdev = rdev->bdev; @@ -1143,8 +1147,11 @@ static int super_90_load(struct md_rdev *rdev, struct md_rdev *refdev, int minor ret = 0; } rdev->sectors = rdev->sb_start; - /* Limit to 4TB as metadata cannot record more than that */ - if (rdev->sectors >= (2ULL << 32)) + /* Limit to 4TB as metadata cannot record more than that. + * (not needed for Linear and RAID0 as metadata doesn't + * record this size) + */ + if (rdev->sectors >= (2ULL << 32) && sb->level >= 1) rdev->sectors = (2ULL << 32) - 2; if (rdev->sectors < ((sector_t)sb->size) * 2 && sb->level >= 1) @@ -1426,7 +1433,7 @@ super_90_rdev_size_change(struct md_rdev *rdev, sector_t num_sectors) /* Limit to 4TB as metadata cannot record more than that. * 4TB == 2^32 KB, or 2*2^32 sectors. */ - if (num_sectors >= (2ULL << 32)) + if (num_sectors >= (2ULL << 32) && rdev->mddev->level >= 1) num_sectors = (2ULL << 32) - 2; md_super_write(rdev->mddev, rdev, rdev->sb_start, rdev->sb_size, rdev->sb_page); @@ -1802,10 +1809,10 @@ static void super_1_sync(struct mddev *mddev, struct md_rdev *rdev) memset(bbp, 0xff, PAGE_SIZE); for (i = 0 ; i < bb->count ; i++) { - u64 internal_bb = *p++; + u64 internal_bb = p[i]; u64 store_bb = ((BB_OFFSET(internal_bb) << 10) | BB_LEN(internal_bb)); - *bbp++ = cpu_to_le64(store_bb); + bbp[i] = cpu_to_le64(store_bb); } bb->changed = 0; if (read_seqretry(&bb->lock, seq)) @@ -2879,6 +2886,9 @@ rdev_size_store(struct md_rdev *rdev, const char *buf, size_t len) } else if (!sectors) sectors = (i_size_read(rdev->bdev->bd_inode) >> 9) - rdev->data_offset; + if (!my_mddev->pers->resize) + /* Cannot change size for RAID0 or Linear etc */ + return -EINVAL; } if (sectors < my_mddev->dev_sectors) return -EINVAL; /* component must fit device */ @@ -3744,8 +3754,8 @@ array_state_show(struct mddev *mddev, char *page) return sprintf(page, "%s\n", array_states[st]); } -static int do_md_stop(struct mddev * mddev, int ro, int is_open); -static int md_set_readonly(struct mddev * mddev, int is_open); +static int do_md_stop(struct mddev * mddev, int ro, struct block_device *bdev); +static int md_set_readonly(struct mddev * mddev, struct block_device *bdev); static int do_md_run(struct mddev * mddev); static int restart_array(struct mddev *mddev); @@ -3761,14 +3771,14 @@ array_state_store(struct mddev *mddev, const char *buf, size_t len) /* stopping an active array */ if (atomic_read(&mddev->openers) > 0) return -EBUSY; - err = do_md_stop(mddev, 0, 0); + err = do_md_stop(mddev, 0, NULL); break; case inactive: /* stopping an active array */ if (mddev->pers) { if (atomic_read(&mddev->openers) > 0) return -EBUSY; - err = do_md_stop(mddev, 2, 0); + err = do_md_stop(mddev, 2, NULL); } else err = 0; /* already inactive */ break; @@ -3776,7 +3786,7 @@ array_state_store(struct mddev *mddev, const char *buf, size_t len) break; /* not supported yet */ case readonly: if (mddev->pers) - err = md_set_readonly(mddev, 0); + err = md_set_readonly(mddev, NULL); else { mddev->ro = 1; set_disk_ro(mddev->gendisk, 1); @@ -3786,7 +3796,7 @@ array_state_store(struct mddev *mddev, const char *buf, size_t len) case read_auto: if (mddev->pers) { if (mddev->ro == 0) - err = md_set_readonly(mddev, 0); + err = md_set_readonly(mddev, NULL); else if (mddev->ro == 1) err = restart_array(mddev); if (err == 0) { @@ -5124,15 +5134,17 @@ void md_stop(struct mddev *mddev) } EXPORT_SYMBOL_GPL(md_stop); -static int md_set_readonly(struct mddev *mddev, int is_open) +static int md_set_readonly(struct mddev *mddev, struct block_device *bdev) { int err = 0; mutex_lock(&mddev->open_mutex); - if (atomic_read(&mddev->openers) > is_open) { + if (atomic_read(&mddev->openers) > !!bdev) { printk("md: %s still in use.\n",mdname(mddev)); err = -EBUSY; goto out; } + if (bdev) + sync_blockdev(bdev); if (mddev->pers) { __md_stop_writes(mddev); @@ -5154,18 +5166,26 @@ static int md_set_readonly(struct mddev *mddev, int is_open) * 0 - completely stop and dis-assemble array * 2 - stop but do not disassemble array */ -static int do_md_stop(struct mddev * mddev, int mode, int is_open) +static int do_md_stop(struct mddev * mddev, int mode, + struct block_device *bdev) { struct gendisk *disk = mddev->gendisk; struct md_rdev *rdev; mutex_lock(&mddev->open_mutex); - if (atomic_read(&mddev->openers) > is_open || + if (atomic_read(&mddev->openers) > !!bdev || mddev->sysfs_active) { printk("md: %s still in use.\n",mdname(mddev)); mutex_unlock(&mddev->open_mutex); return -EBUSY; } + if (bdev) + /* It is possible IO was issued on some other + * open file which was closed before we took ->open_mutex. + * As that was not the last close __blkdev_put will not + * have called sync_blockdev, so we must. + */ + sync_blockdev(bdev); if (mddev->pers) { if (mddev->ro) @@ -5239,7 +5259,7 @@ static void autorun_array(struct mddev *mddev) err = do_md_run(mddev); if (err) { printk(KERN_WARNING "md: do_md_run() returned %d\n", err); - do_md_stop(mddev, 0, 0); + do_md_stop(mddev, 0, NULL); } } @@ -5413,9 +5433,9 @@ static int get_bitmap_file(struct mddev * mddev, void __user * arg) int err = -ENOMEM; if (md_allow_write(mddev)) - file = kmalloc(sizeof(*file), GFP_NOIO); + file = kzalloc(sizeof(*file), GFP_NOIO); else - file = kmalloc(sizeof(*file), GFP_KERNEL); + file = kzalloc(sizeof(*file), GFP_KERNEL); if (!file) goto out; @@ -6237,11 +6257,11 @@ static int md_ioctl(struct block_device *bdev, fmode_t mode, goto done_unlock; case STOP_ARRAY: - err = do_md_stop(mddev, 0, 1); + err = do_md_stop(mddev, 0, bdev); goto done_unlock; case STOP_ARRAY_RO: - err = md_set_readonly(mddev, 1); + err = md_set_readonly(mddev, bdev); goto done_unlock; case BLKROSET: @@ -7407,6 +7427,8 @@ static int remove_and_add_spares(struct mddev *mddev) } } } + if (removed) + set_bit(MD_CHANGE_DEVS, &mddev->flags); return spares; } @@ -7420,9 +7442,11 @@ static void reap_sync_thread(struct mddev *mddev) !test_bit(MD_RECOVERY_REQUESTED, &mddev->recovery)) { /* success...*/ /* activate any spares */ - if (mddev->pers->spare_active(mddev)) + if (mddev->pers->spare_active(mddev)) { sysfs_notify(&mddev->kobj, NULL, "degraded"); + set_bit(MD_CHANGE_DEVS, &mddev->flags); + } } if (test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery) && mddev->pers->finish_reshape) @@ -7677,9 +7701,9 @@ int md_is_badblock(struct badblocks *bb, sector_t s, int sectors, sector_t *first_bad, int *bad_sectors) { int hi; - int lo = 0; + int lo; u64 *p = bb->page; - int rv = 0; + int rv; sector_t target = s + sectors; unsigned seq; @@ -7694,7 +7718,8 @@ int md_is_badblock(struct badblocks *bb, sector_t s, int sectors, retry: seq = read_seqbegin(&bb->lock); - + lo = 0; + rv = 0; hi = bb->count; /* Binary search between lo and hi for 'target' diff --git a/drivers/md/raid0.c b/drivers/md/raid0.c index de63a1fc3737..08e173478bef 100644 --- a/drivers/md/raid0.c +++ b/drivers/md/raid0.c @@ -402,7 +402,8 @@ static sector_t raid0_size(struct mddev *mddev, sector_t sectors, int raid_disks "%s does not support generic reshape\n", __func__); rdev_for_each(rdev, mddev) - array_sectors += rdev->sectors; + array_sectors += (rdev->sectors & + ~(sector_t)(mddev->chunk_sectors-1)); return array_sectors; } diff --git a/drivers/mfd/wcd9xxx-core.c b/drivers/mfd/wcd9xxx-core.c index 156ce24e0920..495ce2059489 100644 --- a/drivers/mfd/wcd9xxx-core.c +++ b/drivers/mfd/wcd9xxx-core.c @@ -119,6 +119,22 @@ int wcd9xxx_reg_read( } EXPORT_SYMBOL(wcd9xxx_reg_read); +#ifdef CONFIG_SOUND_CONTROL_HAX_3_GPL +int wcd9xxx_reg_read_safe(struct wcd9xxx *wcd9xxx, unsigned short reg) +{ + u8 val; + int ret; + + ret = wcd9xxx_read(wcd9xxx, reg, 1, &val, false); + + if (ret < 0) + return ret; + else + return val; +} +EXPORT_SYMBOL_GPL(wcd9xxx_reg_read_safe); +#endif + static int wcd9xxx_write(struct wcd9xxx *wcd9xxx, unsigned short reg, int bytes, void *src, bool interface_reg) { diff --git a/drivers/misc/qseecom.c b/drivers/misc/qseecom.c index 6e882911510c..10acb6fa396d 100644 --- a/drivers/misc/qseecom.c +++ b/drivers/misc/qseecom.c @@ -1183,7 +1183,7 @@ static int qseecom_unload_app(struct qseecom_dev_handle *data, return -EFAULT; } if (resp.result == QSEOS_RESULT_SUCCESS) - pr_info("App (%d) is unloaded!!\n", + pr_debug("App (%d) is unloaded!!\n", data->client.app_id); __qseecom_cleanup_app(data); if (resp.result == QSEOS_RESULT_INCOMPLETE) { @@ -1204,10 +1204,10 @@ static int qseecom_unload_app(struct qseecom_dev_handle *data, } else { if (ptr_app->ref_cnt == 1) { ptr_app->ref_cnt = 0; - pr_info("ref_count set to 0\n"); + pr_debug("ref_count set to 0\n"); } else { ptr_app->ref_cnt--; - pr_info("Can't unload app(%d) inuse\n", + pr_debug("Can't unload app(%d) inuse\n", ptr_app->app_id); } } @@ -4114,7 +4114,7 @@ static int qseecom_release(struct inode *inode, struct file *file) int ret = 0; if (data->released == false) { - pr_warn("data: released = false, type = %d, data = 0x%x\n", + pr_debug("data: released = false, type = %d, data = 0x%x\n", data->type, (u32)data); switch (data->type) { case QSEECOM_LISTENER_SERVICE: diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 0a154e61112b..0a8699f79626 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3243,14 +3243,12 @@ static void mmc_blk_shutdown(struct mmc_card *card) } } -#ifdef CONFIG_MMC_CACHE_FEATURE mmc_claim_host(card->host); /* send cache off control */ rc = mmc_cache_ctrl(card->host, 0); mmc_release_host(card->host); if (rc) goto cache_off_error; -#endif /* send power off notification */ if (mmc_card_mmc(card)) { @@ -3266,13 +3264,11 @@ static void mmc_blk_shutdown(struct mmc_card *card) suspend_error: pr_err("%s: mmc_queue_suspend returned error = %d", mmc_hostname(card->host), rc); -#ifdef CONFIG_MMC_CACHE_FEATURE return; cache_off_error: pr_err("%s: mmc_cache_ctrl returned error = %d", mmc_hostname(card->host), rc); -#endif } #ifdef CONFIG_PM diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig index 2a817abfdd82..92fd049509f7 100644 --- a/drivers/mmc/core/Kconfig +++ b/drivers/mmc/core/Kconfig @@ -60,3 +60,10 @@ config MMC_DISABLE_STOP_REQUEST_SKHYNIX help This selects disable stop request on the SkHynix-eMMC 4.5 and 5.0 devices. +config MMC_ENABLE_CACHECTRL_SKHYNIX + bool "Enable cache control on the SkHynix-eMMC 4.5" + depends on MMC + default n + help + This selects enable cache control on the SkHynix-eMMC 4.5 + devices. diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index c31d42c8706e..9767233a6fff 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1463,6 +1463,14 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, host->caps2 &= ~MMC_CAP2_STOP_REQUEST; } #endif + +#ifdef CONFIG_MMC_ENABLE_CACHECTRL_SKHYNIX + if (card->cid.manfid == CID_MANFID_HYNIX && + !strncmp(card->cid.prod_name, prod_name_hynix_HBG4e_05, + sizeof(prod_name_hynix_HBG4e_05))) { + host->caps2 |= MMC_CAP2_CACHE_CTRL; + } +#endif } /* @@ -1954,6 +1962,8 @@ static int mmc_resume(struct mmc_host *host) if (mmc_card_is_sleep(host->card)) { mmc_restore_ios(host); err = mmc_card_awake(host); + if (!err) + err = mmc_cache_ctrl(host, 1); } else err = mmc_init_card(host, host->ocr, host->card); #endif diff --git a/drivers/mtd/nand/nuc900_nand.c b/drivers/mtd/nand/nuc900_nand.c index 8febe46e1105..9f55d40ec69e 100644 --- a/drivers/mtd/nand/nuc900_nand.c +++ b/drivers/mtd/nand/nuc900_nand.c @@ -250,7 +250,7 @@ static void nuc900_nand_enable(struct nuc900_nand *nand) val = __raw_readl(nand->reg + REG_FMICSR); if (!(val & NAND_EN)) - __raw_writel(val | NAND_EN, REG_FMICSR); + __raw_writel(val | NAND_EN, nand->reg + REG_FMICSR); val = __raw_readl(nand->reg + REG_SMCSR); diff --git a/drivers/mtd/sm_ftl.c b/drivers/mtd/sm_ftl.c index 9e2dfd517aa5..539835fabe61 100644 --- a/drivers/mtd/sm_ftl.c +++ b/drivers/mtd/sm_ftl.c @@ -59,15 +59,12 @@ struct attribute_group *sm_create_sysfs_attributes(struct sm_ftl *ftl) struct attribute_group *attr_group; struct attribute **attributes; struct sm_sysfs_attribute *vendor_attribute; + char *vendor; - int vendor_len = strnlen(ftl->cis_buffer + SM_CIS_VENDOR_OFFSET, - SM_SMALL_PAGE - SM_CIS_VENDOR_OFFSET); - - char *vendor = kmalloc(vendor_len, GFP_KERNEL); + vendor = kstrndup(ftl->cis_buffer + SM_CIS_VENDOR_OFFSET, + SM_SMALL_PAGE - SM_CIS_VENDOR_OFFSET, GFP_KERNEL); if (!vendor) goto error1; - memcpy(vendor, ftl->cis_buffer + SM_CIS_VENDOR_OFFSET, vendor_len); - vendor[vendor_len] = 0; /* Initialize sysfs attributes */ vendor_attribute = @@ -78,7 +75,7 @@ struct attribute_group *sm_create_sysfs_attributes(struct sm_ftl *ftl) sysfs_attr_init(&vendor_attribute->dev_attr.attr); vendor_attribute->data = vendor; - vendor_attribute->len = vendor_len; + vendor_attribute->len = strlen(vendor); vendor_attribute->dev_attr.attr.name = "vendor"; vendor_attribute->dev_attr.attr.mode = S_IRUGO; vendor_attribute->dev_attr.show = sm_attr_show; diff --git a/drivers/net/wireless/b43/phy_n.c b/drivers/net/wireless/b43/phy_n.c index 108118820b36..cf5af65781f5 100644 --- a/drivers/net/wireless/b43/phy_n.c +++ b/drivers/net/wireless/b43/phy_n.c @@ -4598,22 +4598,22 @@ static void b43_nphy_channel_setup(struct b43_wldev *dev, int ch = new_channel->hw_value; u16 old_band_5ghz; - u32 tmp32; + u16 tmp16; old_band_5ghz = b43_phy_read(dev, B43_NPHY_BANDCTL) & B43_NPHY_BANDCTL_5GHZ; if (new_channel->band == IEEE80211_BAND_5GHZ && !old_band_5ghz) { - tmp32 = b43_read32(dev, B43_MMIO_PSM_PHY_HDR); - b43_write32(dev, B43_MMIO_PSM_PHY_HDR, tmp32 | 4); + tmp16 = b43_read16(dev, B43_MMIO_PSM_PHY_HDR); + b43_write16(dev, B43_MMIO_PSM_PHY_HDR, tmp16 | 4); b43_phy_set(dev, B43_PHY_B_BBCFG, 0xC000); - b43_write32(dev, B43_MMIO_PSM_PHY_HDR, tmp32); + b43_write16(dev, B43_MMIO_PSM_PHY_HDR, tmp16); b43_phy_set(dev, B43_NPHY_BANDCTL, B43_NPHY_BANDCTL_5GHZ); } else if (new_channel->band == IEEE80211_BAND_2GHZ && old_band_5ghz) { b43_phy_mask(dev, B43_NPHY_BANDCTL, ~B43_NPHY_BANDCTL_5GHZ); - tmp32 = b43_read32(dev, B43_MMIO_PSM_PHY_HDR); - b43_write32(dev, B43_MMIO_PSM_PHY_HDR, tmp32 | 4); + tmp16 = b43_read16(dev, B43_MMIO_PSM_PHY_HDR); + b43_write16(dev, B43_MMIO_PSM_PHY_HDR, tmp16 | 4); b43_phy_mask(dev, B43_PHY_B_BBCFG, 0x3FFF); - b43_write32(dev, B43_MMIO_PSM_PHY_HDR, tmp32); + b43_write16(dev, B43_MMIO_PSM_PHY_HDR, tmp16); } b43_chantab_phy_upload(dev, e); diff --git a/drivers/net/wireless/bcmdhd/Makefile b/drivers/net/wireless/bcmdhd/Makefile index 8d77a8f5c4eb..3e012a30a57d 100644 --- a/drivers/net/wireless/bcmdhd/Makefile +++ b/drivers/net/wireless/bcmdhd/Makefile @@ -116,6 +116,9 @@ DHDCFLAGS += -DCONNECTION_STATISTICS DHDCFLAGS += -DSUPPORT_P2P_GO_PS +# Disable TXBF sending +DHDCFLAGS += -DDISABLE_TXBFR + ############################## # Android Platform Definition ############################## @@ -230,6 +233,9 @@ endif DHDCFLAGS += -DCUSTOM_PSPRETEND_THR=30 DHDCFLAGS += -DDISABLE_WL_FRAMEBURST_SOFTAP DHDCFLAGS += -DDISABLE_IF_COUNTERS + + # For setting custom short & long retry limit + DHDCFLAGS += -DSET_RETRY_LIMIT -DCUSTOM_SRL_SETTING=13 -DCUSTOM_LRL_SETTING=13 endif ifeq ($(CONFIG_BCM4339),y) @@ -271,6 +277,12 @@ endif DHDCFLAGS += -DCUSTOM_PSPRETEND_THR=30 DHDCFLAGS += -DDISABLE_WL_FRAMEBURST_SOFTAP DHDCFLAGS += -DDISABLE_IF_COUNTERS + + # for 4339 only currently - need test for 4354, 43455 + DHDCFLAGS += -DDHD_LOSSLESS_ROAMING + + # For setting custom short & long retry limit + DHDCFLAGS += -DSET_RETRY_LIMIT -DCUSTOM_SRL_SETTING=13 -DCUSTOM_LRL_SETTING=13 endif ifneq ($(CONFIG_BCM43455),) @@ -313,6 +325,9 @@ endif DHDCFLAGS += -DCUSTOM_PSPRETEND_THR=30 DHDCFLAGS += -DDISABLE_WL_FRAMEBURST_SOFTAP # DHDCFLAGS += -DDISABLE_IF_COUNTERS + + # For setting custom short & long retry limit + DHDCFLAGS += -DSET_RETRY_LIMIT -DCUSTOM_SRL_SETTING=13 -DCUSTOM_LRL_SETTING=13 endif ifneq ($(CONFIG_SEMC_WLAN_BAND),) diff --git a/drivers/net/wireless/bcmdhd/bcmutils.c b/drivers/net/wireless/bcmdhd/bcmutils.c index 263f3f3dab06..8e2e96400026 100644 --- a/drivers/net/wireless/bcmdhd/bcmutils.c +++ b/drivers/net/wireless/bcmdhd/bcmutils.c @@ -20,7 +20,7 @@ * Notwithstanding the above, under no circumstances may you combine this * software in any way with any other Broadcom software provided under a license * other than the GPL, without Broadcom's express prior written consent. - * $Id: bcmutils.c 516345 2014-11-19 11:58:57Z $ + * $Id: bcmutils.c 567982 2015-07-01 06:24:39Z $ */ #include @@ -1265,6 +1265,11 @@ pktsetprio(void *pkt, bool update_vtag) evh->vlan_tag = hton16(vlan_tag); rc |= PKTPRIO_UPD; } +#ifdef DHD_LOSSLESS_ROAMING + } else if (eh->ether_type == hton16(ETHER_TYPE_802_1X)) { + priority = PRIO_8021D_NC; + rc = PKTPRIO_DSCP; +#endif /* DHD_LOSSLESS_ROAMING */ } else if (eh->ether_type == hton16(ETHER_TYPE_IP)) { uint8 *ip_body = pktdata + sizeof(struct ether_header); uint8 tos_tc = IP_TOS46(ip_body); diff --git a/drivers/net/wireless/bcmdhd/dhd.h b/drivers/net/wireless/bcmdhd/dhd.h index b7a6c886c7b7..812a5a92c68c 100644 --- a/drivers/net/wireless/bcmdhd/dhd.h +++ b/drivers/net/wireless/bcmdhd/dhd.h @@ -25,7 +25,7 @@ * software in any way with any other Broadcom software provided under a license * other than the GPL, without Broadcom's express prior written consent. * - * $Id: dhd.h 548462 2015-04-13 09:02:48Z $ + * $Id: dhd.h 571132 2015-07-14 12:09:10Z $ */ /**************** @@ -343,6 +343,9 @@ typedef struct dhd_pub { #endif uint8 *soc_ram; uint32 soc_ram_length; +#ifdef DHD_LOSSLESS_ROAMING + uint8 dequeue_prec_map; +#endif /* DHD_LOSSLESS_ROAMING */ } dhd_pub_t; #if defined(CUSTOMER_HW5) #define MAX_RESCHED_CNT 600 @@ -838,6 +841,18 @@ extern int dhd_set_scan_timeout(dhd_pub_t *pub, uint32 timeout); extern uint32 dhd_get_scan_timeout(dhd_pub_t *pub); #endif /* DHD_DEBUG */ +#ifdef SET_RETRY_LIMIT +#define DEFAULT_SHORT_RETRY_LIMIT 13 +#ifndef CUSTOM_SRL_SETTING +#define CUSTOM_SRL_SETTING DEFAULT_SHORT_RETRY_LIMIT +#endif + +#define DEFAULT_LONG_RETRY_LIMIT 13 +#ifndef CUSTOM_LRL_SETTING +#define CUSTOM_LRL_SETTING DEFAULT_LONG_RETRY_LIMIT +#endif +#endif /* SET_RETRY_LIMIT */ + #define MAX_DTIM_SKIP_BEACON_INTERVAL 100 /* max allowed associated AP beacon for DTIM skip */ #ifndef MAX_DTIM_ALLOWED_INTERVAL #define MAX_DTIM_ALLOWED_INTERVAL 600 /* max allowed total beacon interval for DTIM skip */ diff --git a/drivers/net/wireless/bcmdhd/dhd_linux.c b/drivers/net/wireless/bcmdhd/dhd_linux.c index fd6e1e2d8fc7..c7f39078f5aa 100644 --- a/drivers/net/wireless/bcmdhd/dhd_linux.c +++ b/drivers/net/wireless/bcmdhd/dhd_linux.c @@ -23,7 +23,7 @@ * software in any way with any other Broadcom software provided under a license * other than the GPL, without Broadcom's express prior written consent. * - * $Id: dhd_linux.c 548462 2015-04-13 09:02:48Z $ + * $Id: dhd_linux.c 571132 2015-07-14 12:09:10Z $ */ #include @@ -1851,6 +1851,13 @@ dhd_txflowcontrol(dhd_pub_t *dhdp, int ifidx, bool state) ASSERT(dhd); +#ifdef DHD_LOSSLESS_ROAMING + /* block flowcontrol during roaming */ + if ((dhdp->dequeue_prec_map == 1 << PRIO_8021D_NC) && state == ON) { + return; + } +#endif /* DHD_LOSSLESS_ROAMING */ + if (ifidx == ALL_INTERFACES) { /* Flow control on all active interfaces */ dhdp->txoff = state; @@ -3272,6 +3279,9 @@ dhd_open(struct net_device *net) DHD_OS_WAKE_LOCK(&dhd->pub); dhd->pub.dongle_trap_occured = 0; dhd->pub.hang_was_sent = 0; +#ifdef DHD_LOSSLESS_ROAMING + dhd->pub.dequeue_prec_map = ALLPRIO; +#endif /* DHD_LOSSLESS_ROAMING */ #ifdef ENABLE_CONTROL_SCHED ret = dhd_sysfs_create_node(net); @@ -4227,8 +4237,11 @@ dhd_preinit_ioctls(dhd_pub_t *dhd) char eventmask[WL_EVENTING_MASK_LEN]; char iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for "event_msgs" + '\0' + bitvec */ uint32 buf_key_b4_m4 = 1; + uint8 msglen; + eventmsgs_ext_t *eventmask_msg = NULL; + char* iov_buf = NULL; + int ret2 = 0; #ifdef WLAIBSS - char iov_buf[WLC_IOCTL_SMLEN]; aibss_bcn_force_config_t bcn_config; uint32 aibss; #endif @@ -4244,7 +4257,6 @@ dhd_preinit_ioctls(dhd_pub_t *dhd) int wlfc_enable = TRUE; #ifndef DISABLE_11N uint32 hostreorder = 1; - int ret2 = 0; #endif /* DISABLE_11N */ #endif /* PROP_TXSTATUS */ @@ -4306,6 +4318,10 @@ dhd_preinit_ioctls(dhd_pub_t *dhd) uint32 ccx = 1; #endif +#ifdef SET_RETRY_LIMIT + uint srl = CUSTOM_SRL_SETTING; + uint lrl = CUSTOM_LRL_SETTING; +#endif /* SET_RETRY_LIMIT */ #if defined(AP) || defined(WLP2P) uint32 apsta = 1; /* Enable APSTA mode */ #endif /* defined(AP) || defined(WLP2P) */ @@ -4323,6 +4339,9 @@ dhd_preinit_ioctls(dhd_pub_t *dhd) #ifdef USE_WL_TXBF uint32 txbf = 1; #endif /* USE_WL_TXBF */ +#ifdef DISABLE_TXBFR + uint32 txbf_bfr_cap = 0; +#endif /* DISABLE_TXBFR */ #ifdef AMPDU_VO_ENABLE struct ampdu_tid_control tid; #endif @@ -4336,9 +4355,15 @@ dhd_preinit_ioctls(dhd_pub_t *dhd) #ifdef SUPPORT_2G_VHT uint32 vht_features = 0x3; /* 2G enable | rates all */ #endif /* SUPPORT_2G_VHT */ +#ifdef DISABLE_11N_PROPRIETARY_RATES + uint32 ht_features = 0; +#endif /* DISABLE_11N_PROPRIETARY_RATES */ #ifdef CUSTOM_PSPRETEND_THR uint32 pspretend_thr = CUSTOM_PSPRETEND_THR; #endif +#ifdef DISABLE_BCN_DLY + uint bcn_to_dly = 0; +#endif #ifdef PKT_FILTER_SUPPORT dhd_pkt_filter_enable = TRUE; #endif /* PKT_FILTER_SUPPORT */ @@ -4432,6 +4457,16 @@ dhd_preinit_ioctls(dhd_pub_t *dhd) DHD_ERROR(("%s mpc for HostAPD failed %d\n", __FUNCTION__, ret)); } #endif +#ifdef SET_RETRY_LIMIT + if ((ret = dhd_wl_ioctl_cmd(dhd, WLC_SET_SRL, (char *)&srl, + sizeof(srl), TRUE, 0)) < 0) { + DHD_ERROR(("%s Set SRL failed %d\n", __FUNCTION__, ret)); + } + if ((ret = dhd_wl_ioctl_cmd(dhd, WLC_SET_LRL, (char *)&lrl, + sizeof(lrl), TRUE, 0)) < 0) { + DHD_ERROR(("%s Set LRL failed %d\n", __FUNCTION__, ret)); + } +#endif /* SET_RETRY_LIMIT */ } else if ((!op_mode && dhd_get_fw_mode(dhd->info) == DHD_FLAG_MFG_MODE) || (op_mode == DHD_FLAG_MFG_MODE)) { #if defined(ARP_OFFLOAD_SUPPORT) @@ -4598,6 +4633,12 @@ dhd_preinit_ioctls(dhd_pub_t *dhd) DHD_ERROR(("%s: set scan_nprobes failed %d\n", __FUNCTION__, ret)); } #endif + +#ifdef DISABLE_BCN_DLY + /* Set bcn_to_dly to delay link down until roam complete */ + bcm_mkiovar("bcn_to_dly", (char *)&bcn_to_dly, 4, iovbuf, sizeof(iovbuf)); + dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0); +#endif /* Setup assoc_retry_max count to reconnect target AP in dongle */ bcm_mkiovar("assoc_retry_max", (char *)&retry_max, 4, iovbuf, sizeof(iovbuf)); dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0); @@ -4640,6 +4681,13 @@ dhd_preinit_ioctls(dhd_pub_t *dhd) DHD_ERROR(("%s Set txbf failed %d\n", __FUNCTION__, ret)); } #endif /* USE_WL_TXBF */ +#ifdef DISABLE_TXBFR + bcm_mkiovar("txbf_bfr_cap", (char *)&txbf_bfr_cap, 4, iovbuf, sizeof(iovbuf)); + if ((ret = dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, iovbuf, + sizeof(iovbuf), TRUE, 0)) < 0) { + DHD_ERROR(("%s Clear txbf_bfr_cap failed %d\n", __FUNCTION__, ret)); + } +#endif /* DISABLE_TXBFR */ #ifdef USE_WL_FRAMEBURST #ifdef DISABLE_WL_FRAMEBURST_SOFTAP /* Disable Framebursting for SofAP */ @@ -4688,6 +4736,12 @@ dhd_preinit_ioctls(dhd_pub_t *dhd) } #endif /* CUSTOM_AMPDU_BA_WSIZE || (WLAIBSS && CUSTOM_IBSS_AMPDU_BA_WSIZE) */ + iov_buf = (char*)kmalloc(WLC_IOCTL_SMLEN, GFP_KERNEL); + if (iov_buf == NULL) { + DHD_ERROR(("failed to allocate %d bytes for iov_buf\n", WLC_IOCTL_SMLEN)); + ret = BCME_NOMEM; + goto done; + } #ifdef WLAIBSS /* Configure custom IBSS beacon transmission */ if (dhd->op_mode & DHD_FLAG_IBSS_MODE) @@ -4735,6 +4789,12 @@ dhd_preinit_ioctls(dhd_pub_t *dhd) DHD_ERROR(("%s vht_features set failed %d\n", __FUNCTION__, ret)); } #endif /* SUPPORT_2G_VHT */ +#ifdef DISABLE_11N_PROPRIETARY_RATES + bcm_mkiovar("ht_features", (char *)&ht_features, 4, iovbuf, sizeof(iovbuf)); + if ((ret = dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0)) < 0) { + DHD_ERROR(("%s ht_features set failed %d\n", __FUNCTION__, ret)); + } +#endif /* DISABLE_11N_PROPRIETARY_RATES */ #ifdef CUSTOM_PSPRETEND_THR /* Turn off MPC in AP mode */ bcm_mkiovar("pspretend_threshold", (char *)&pspretend_thr, 4, @@ -4822,6 +4882,9 @@ dhd_preinit_ioctls(dhd_pub_t *dhd) setbit(eventmask, WLC_E_AIBSS_TXFAIL); #endif /* WLAIBSS */ setbit(eventmask, WLC_E_TRACE); +#ifdef DHD_LOSSLESS_ROAMING + setbit(eventmask, WLC_E_ROAM_PREP); +#endif /* DHD_LOSSLESS_ROAMING */ /* Write updated Event mask */ bcm_mkiovar("event_msgs", eventmask, WL_EVENTING_MASK_LEN, iovbuf, sizeof(iovbuf)); @@ -4830,6 +4893,46 @@ dhd_preinit_ioctls(dhd_pub_t *dhd) goto done; } + /* make up event mask ext message iovar for event larger than 128 */ + msglen = ROUNDUP(WLC_E_LAST, NBBY)/NBBY + EVENTMSGS_EXT_STRUCT_SIZE; + eventmask_msg = (eventmsgs_ext_t*)kmalloc(msglen, GFP_KERNEL); + if (eventmask_msg == NULL) { + DHD_ERROR(("failed to allocate %d bytes for event_msg_ext\n", msglen)); + ret = BCME_NOMEM; + goto done; + } + bzero(eventmask_msg, msglen); + eventmask_msg->ver = EVENTMSGS_VER; + eventmask_msg->len = ROUNDUP(WLC_E_LAST, NBBY)/NBBY; + + /* Read event_msgs_ext mask */ + bcm_mkiovar("event_msgs_ext", (char *)eventmask_msg, msglen, iov_buf, WLC_IOCTL_SMLEN); + ret2 = dhd_wl_ioctl_cmd(dhd, WLC_GET_VAR, iov_buf, WLC_IOCTL_SMLEN, FALSE, 0); + if (ret2 != BCME_UNSUPPORTED) + ret = ret2; + if (ret2 == 0) { /* event_msgs_ext must be supported */ + bcopy(iov_buf, eventmask_msg, msglen); +#ifdef GSCAN_SUPPORT + setbit(eventmask_msg->mask, WLC_E_PFN_GSCAN_FULL_RESULT); + setbit(eventmask_msg->mask, WLC_E_PFN_SCAN_COMPLETE); + setbit(eventmask_msg->mask, WLC_E_PFN_SWC); +#endif /* GSCAN_SUPPORT */ + + /* Write updated Event mask */ + eventmask_msg->ver = EVENTMSGS_VER; + eventmask_msg->command = EVENTMSGS_SET_MASK; + eventmask_msg->len = ROUNDUP(WLC_E_LAST, NBBY)/NBBY; + bcm_mkiovar("event_msgs_ext", (char *)eventmask_msg, + msglen, iov_buf, WLC_IOCTL_SMLEN); + if ((ret = dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, + iov_buf, WLC_IOCTL_SMLEN, TRUE, 0)) < 0) { + DHD_ERROR(("%s write event mask ext failed %d\n", __FUNCTION__, ret)); + goto done; + } + } else if (ret2 < 0) { + DHD_ERROR(("%s read event mask ext unsupported %d\n", __FUNCTION__, ret2)); + } /* unsupported is ok */ + dhd_wl_ioctl_cmd(dhd, WLC_SET_SCAN_CHANNEL_TIME, (char *)&scan_assoc_time, sizeof(scan_assoc_time), TRUE, 0); dhd_wl_ioctl_cmd(dhd, WLC_SET_SCAN_UNASSOC_TIME, (char *)&scan_unassoc_time, @@ -4960,6 +5063,12 @@ dhd_preinit_ioctls(dhd_pub_t *dhd) #endif /* WL11U */ done: + + if (eventmask_msg) + kfree(eventmask_msg); + if (iov_buf) + kfree(iov_buf); + return ret; } @@ -6659,14 +6768,6 @@ dhd_dev_retrieve_batch_scan(struct net_device *dev) return (dhd_retreive_batch_scan_results(&dhd->pub)); } - -void -dhd_dev_end_one_shot_gscan(struct net_device *dev) -{ - dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev); - - return (dhd_end_one_shot_gscan(&dhd->pub)); -} #endif /* GSCAN_SUPPORT */ #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) @@ -7040,10 +7141,10 @@ int dhd_os_wake_lock_timeout(dhd_pub_t *pub) #ifdef CONFIG_HAS_WAKELOCK if (dhd->wakelock_rx_timeout_enable) wake_lock_timeout(&dhd->wl_rxwake, - msecs_to_jiffies(dhd->wakelock_rx_timeout_enable)); + msecs_to_jiffies(dhd->wakelock_rx_timeout_enable)/4); if (dhd->wakelock_ctrl_timeout_enable) wake_lock_timeout(&dhd->wl_ctrlwake, - msecs_to_jiffies(dhd->wakelock_ctrl_timeout_enable)); + msecs_to_jiffies(dhd->wakelock_ctrl_timeout_enable)/4); #endif dhd->wakelock_rx_timeout_enable = 0; dhd->wakelock_ctrl_timeout_enable = 0; diff --git a/drivers/net/wireless/bcmdhd/dhd_pno.c b/drivers/net/wireless/bcmdhd/dhd_pno.c index 01cd10c7b2c0..929067ccb999 100644 --- a/drivers/net/wireless/bcmdhd/dhd_pno.c +++ b/drivers/net/wireless/bcmdhd/dhd_pno.c @@ -24,6 +24,11 @@ * * $Id: dhd_pno.c 423669 2013-09-18 13:01:55Z yangj$ */ + +#if defined(GSCAN_SUPPORT) && !defined(PNO_SUPPORT) +#error "GSCAN needs PNO to be enabled!" +#endif + #ifdef PNO_SUPPORT #include #include @@ -44,6 +49,9 @@ #include #include #include +#ifdef GSCAN_SUPPORT +#include +#endif /* GSCAN_SUPPORT */ #ifdef __BIG_ENDIAN #include @@ -195,32 +203,6 @@ _dhd_pno_gscan_cfg(dhd_pub_t *dhd, wl_pfn_gscan_cfg_t *pfncfg_gscan_param, int s return err; } -static bool -is_one_shot_gscan(struct dhd_pno_gscan_params *gscan_params) -{ - int i; - dhd_pno_gscan_channel_bucket_t *gscan_bucket; - - gscan_bucket = gscan_params->channel_bucket; - for (i = 0; i < gscan_params->nchannel_buckets; i++) { - if (gscan_bucket[i].report_flag & CH_BUCKET_REPORT_ONE_SHOT) - return TRUE; - } - return FALSE; -} - -static -uint32 find_gcf(uint32 num1, uint32 num2) -{ - uint32 tmp; - /* Euclidean algo */ - while ((tmp = (num1 % num2))) { - num1 = num2; - num2 = tmp; - } - return num2; -} - static bool is_batch_retrieval_complete(struct dhd_pno_gscan_params *gscan_params) { @@ -449,8 +431,8 @@ _dhd_pno_set(dhd_pub_t *dhd, const dhd_pno_params_t *pno_params, dhd_pno_mode_t _params = &(_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS]); - pfn_param.scan_freq = find_gcf(pno_params->params_gscan.scan_fr, - _params->params_legacy.scan_fr); + pfn_param.scan_freq = gcd(pno_params->params_gscan.scan_fr, + _params->params_legacy.scan_fr); if ((_params->params_legacy.pno_repeat != 0) || (_params->params_legacy.pno_freq_expo_max != 0)) { @@ -1532,21 +1514,6 @@ static void dhd_pno_reset_cfg_gscan(dhd_pno_params_t *_params, return; } -void -dhd_end_one_shot_gscan(dhd_pub_t *dhd) -{ - dhd_pno_status_info_t *_pno_state; - dhd_pno_params_t *_params; - - _pno_state = PNO_GET_PNOSTATE(dhd); - _params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; - - if (_pno_state->pno_mode & DHD_PNO_GSCAN_MODE && - is_one_shot_gscan(&_params->params_gscan)) { - dhd_pno_initiate_gscan_request(dhd, 0, 1); - } -} - void dhd_pno_lock_batch_results(dhd_pub_t *dhd) { @@ -1721,6 +1688,7 @@ dhd_pno_get_gscan(dhd_pub_t *dhd, dhd_pno_gscan_cmd_cfg_t type, break; default: + DHD_ERROR(("%s: Unrecognized cmd type - %d\n", __FUNCTION__, type)); break; } @@ -1921,6 +1889,7 @@ dhd_pno_set_cfg_gscan(dhd_pub_t *dhd, dhd_pno_gscan_cmd_cfg_t type, break; default: err = BCME_BADARG; + DHD_ERROR(("%s: Unrecognized cmd type - %d\n", __FUNCTION__, type)); break; } exit: @@ -2060,8 +2029,7 @@ dhd_pno_set_for_gscan(dhd_pub_t *dhd, struct dhd_pno_gscan_params *gscan_params) pfn_gscan_cfg_t->swc_rssi_window_size = 0; pfn_gscan_cfg_t->lost_ap_window = 0; } - if (is_one_shot_gscan(gscan_params)) - gscan_params->send_all_results_flag = GSCAN_SEND_ALL_RESULTS_MASK; + pfn_gscan_cfg_t->flags = (gscan_params->send_all_results_flag & GSCAN_SEND_ALL_RESULTS_MASK); pfn_gscan_cfg_t->count_of_channel_buckets = num_buckets_to_fw; @@ -2135,7 +2103,7 @@ dhd_pno_set_for_gscan(dhd_pub_t *dhd, struct dhd_pno_gscan_params *gscan_params) DHD_ERROR(("%s : failed to allocate wl_pfn_bssid_t array" " (count: %d)", __FUNCTION__, _params->params_hotlist.nbssid)); - err = BCME_ERROR; + err = BCME_NOMEM; _pno_state->pno_mode &= ~DHD_PNO_HOTLIST_MODE; goto exit; } @@ -2237,6 +2205,7 @@ dhd_pno_gscan_create_channel_list(dhd_pub_t *dhd, ch_bucket[i].bucket_end_index = num_channels - 1; ch_bucket[i].bucket_freq_multiple = gscan_buckets[i].bucket_freq_multiple; ch_bucket[i].flag = gscan_buckets[i].report_flag; + ch_bucket[i].flag |= CH_BUCKET_GSCAN; if (max < gscan_buckets[i].bucket_freq_multiple) max = gscan_buckets[i].bucket_freq_multiple; nchan = WL_NUMCHANNELS - num_channels; @@ -2254,8 +2223,8 @@ dhd_pno_gscan_create_channel_list(dhd_pub_t *dhd, uint16 common_freq; uint32 legacy_bucket_idx = _params->params_gscan.nchannel_buckets; - common_freq = find_gcf(_params->params_gscan.scan_fr, - _params1->params_legacy.scan_fr); + common_freq = gcd(_params->params_gscan.scan_fr, + _params1->params_legacy.scan_fr); max = gscan_buckets[0].bucket_freq_multiple; /* GSCAN buckets */ for (i = 0; i < _params->params_gscan.nchannel_buckets; i++) { @@ -3682,6 +3651,7 @@ dhd_pno_event_handler(dhd_pub_t *dhd, wl_event_msg_t *event, void *event_data) } else DHD_PNO(("%s : WLC_E_PFN_BEST_BATCHING" "will skip this event\n", __FUNCTION__)); + break; } #else break; diff --git a/drivers/net/wireless/bcmdhd/dhd_pno.h b/drivers/net/wireless/bcmdhd/dhd_pno.h index efe37759ec5f..c40c773c0614 100644 --- a/drivers/net/wireless/bcmdhd/dhd_pno.h +++ b/drivers/net/wireless/bcmdhd/dhd_pno.h @@ -113,9 +113,7 @@ enum index_mode { /* GSCAN includes hotlist scan and they do not run * independent of each other */ -#ifdef GSCAN_SUPPORT INDEX_OF_GSCAN_PARAMS = INDEX_OF_HOTLIST_PARAMS, -#endif /* GSCAN_SUPPORT */ INDEX_MODE_MAX }; enum dhd_pno_status { @@ -155,6 +153,7 @@ typedef enum dhd_pno_gscan_cmd_cfg { DHD_PNO_GET_BATCH_RESULTS, DHD_PNO_GET_CHANNEL_LIST } dhd_pno_gscan_cmd_cfg_t; +#endif /* GSCAN_SUPPORT */ typedef enum dhd_pno_mode { /* Wi-Fi Legacy PNO Mode */ @@ -167,17 +166,6 @@ typedef enum dhd_pno_mode { /* Wi-Fi Google Android SCAN Mode */ DHD_PNO_GSCAN_MODE = (1 << (3)) } dhd_pno_mode_t; -#else -typedef enum dhd_pno_mode { - /* Wi-Fi Legacy PNO Mode */ - DHD_PNO_NONE_MODE = 0, - DHD_PNO_LEGACY_MODE = (1 << (0)), - /* Wi-Fi Android BATCH SCAN Mode */ - DHD_PNO_BATCH_MODE = (1 << (1)), - /* Wi-Fi Android Hotlist SCAN Mode */ - DHD_PNO_HOTLIST_MODE = (1 << (2)) -} dhd_pno_mode_t; -#endif /* GSCAN_SUPPORT */ struct dhd_pno_ssid { bool hidden; @@ -452,7 +440,6 @@ void * dhd_dev_process_full_gscan_result(struct net_device *dev, extern int dhd_dev_gscan_batch_cache_cleanup(struct net_device *dev); extern void dhd_dev_gscan_hotlist_cache_cleanup(struct net_device *dev, hotlist_type_t type); extern void dhd_dev_wait_batch_results_complete(struct net_device *dev); -extern void dhd_dev_end_one_shot_gscan(struct net_device *dev); #endif /* GSCAN_SUPPORT */ /* dhd pno fuctions */ extern int dhd_pno_stop_for_ssid(dhd_pub_t *dhd); @@ -496,7 +483,6 @@ extern void *dhd_process_full_gscan_result(dhd_pub_t *dhd, const void *event_dat extern int dhd_gscan_batch_cache_cleanup(dhd_pub_t *dhd); extern void dhd_gscan_hotlist_cache_cleanup(dhd_pub_t *dhd, hotlist_type_t type); extern void dhd_wait_batch_results_complete(dhd_pub_t *dhd); -extern void dhd_end_one_shot_gscan(dhd_pub_t *dhd); #endif /* GSCAN_SUPPORT */ #endif diff --git a/drivers/net/wireless/bcmdhd/dhd_sdio.c b/drivers/net/wireless/bcmdhd/dhd_sdio.c index 905728f3286e..a4ecbf0ef927 100644 --- a/drivers/net/wireless/bcmdhd/dhd_sdio.c +++ b/drivers/net/wireless/bcmdhd/dhd_sdio.c @@ -22,7 +22,7 @@ * software in any way with any other Broadcom software provided under a license * other than the GPL, without Broadcom's express prior written consent. * - * $Id: dhd_sdio.c 548462 2015-04-13 09:02:48Z $ + * $Id: dhd_sdio.c 567982 2015-07-01 06:24:39Z $ */ #include @@ -2205,6 +2205,9 @@ dhdsdio_sendfromq(dhd_bus_t *bus, uint maxframes) osh = dhd->osh; tx_prec_map = ~bus->flowcontrol; +#ifdef DHD_LOSSLESS_ROAMING + tx_prec_map &= dhd->dequeue_prec_map; +#endif /* DHD_LOSSLESS_ROAMING */ for (cnt = 0; (cnt < maxframes) && DATAOK(bus);) { int i; int num_pkt = 1; @@ -2218,7 +2221,7 @@ dhdsdio_sendfromq(dhd_bus_t *bus, uint maxframes) } num_pkt = MIN(num_pkt, pktq_mlen(&bus->txq, tx_prec_map)); for (i = 0; i < num_pkt; i++) { - pkts[i] = pktq_mdeq(&bus->txq, ~bus->flowcontrol, &prec_out); + pkts[i] = pktq_mdeq(&bus->txq, tx_prec_map, &prec_out); datalen += PKTLEN(osh, pkts[i]); } dhd_os_sdunlock_txq(bus->dhd); diff --git a/drivers/net/wireless/bcmdhd/include/bcmsdstd.h b/drivers/net/wireless/bcmdhd/include/bcmsdstd.h index 0a02ff13a695..6c71a1e75ecb 100644 --- a/drivers/net/wireless/bcmdhd/include/bcmsdstd.h +++ b/drivers/net/wireless/bcmdhd/include/bcmsdstd.h @@ -21,7 +21,7 @@ * software in any way with any other Broadcom software provided under a license * other than the GPL, without Broadcom's express prior written consent. * - * $Id: bcmsdstd.h 455427 2014-02-14 00:11:19Z $ + * $Id: bcmsdstd.h 528504 2015-01-22 11:15:31Z $ */ #ifndef _BCM_SD_STD_H #define _BCM_SD_STD_H diff --git a/drivers/net/wireless/bcmdhd/include/epivers.h b/drivers/net/wireless/bcmdhd/include/epivers.h index 63634e47c62a..eebc57baaec0 100644 --- a/drivers/net/wireless/bcmdhd/include/epivers.h +++ b/drivers/net/wireless/bcmdhd/include/epivers.h @@ -32,17 +32,17 @@ #define EPI_RC_NUMBER 67 -#define EPI_INCREMENTAL_NUMBER 8 +#define EPI_INCREMENTAL_NUMBER 12 #define EPI_BUILD_NUMBER 0 -#define EPI_VERSION 1, 141, 67, 8 +#define EPI_VERSION 1, 141, 67, 12 -#define EPI_VERSION_NUM 0x018d4308 +#define EPI_VERSION_NUM 0x018d430c #define EPI_VERSION_DEV 1.141.67 /* Driver Version String, ASCII, 32 chars max */ -#define EPI_VERSION_STR "1.141.67.8 (r)" +#define EPI_VERSION_STR "1.141.67.12 (r)" #endif /* _epivers_h_ */ diff --git a/drivers/net/wireless/bcmdhd/include/wlioctl.h b/drivers/net/wireless/bcmdhd/include/wlioctl.h index baf747dd62e8..64091ff8c8c5 100644 --- a/drivers/net/wireless/bcmdhd/include/wlioctl.h +++ b/drivers/net/wireless/bcmdhd/include/wlioctl.h @@ -24,7 +24,7 @@ * software in any way with any other Broadcom software provided under a license * other than the GPL, without Broadcom's express prior written consent. * - * $Id: wlioctl.h 537632 2015-02-27 02:51:28Z $ + * $Id: wlioctl.h 569188 2015-07-07 12:04:12Z $ */ #ifndef _wlioctl_h_ @@ -2550,7 +2550,7 @@ typedef struct wl_pfn_cfg { #define CH_BUCKET_REPORT_REGULAR 0 #define CH_BUCKET_REPORT_FULL_RESULT 2 -#define CH_BUCKET_REPORT_ONE_SHOT 4 +#define CH_BUCKET_GSCAN 4 typedef struct wl_pfn_gscan_channel_bucket { uint16 bucket_end_index; @@ -3253,6 +3253,28 @@ typedef struct nbr_element { uint8 pad; } nbr_element_t; +typedef enum event_msgs_ext_command { + EVENTMSGS_NONE = 0, + EVENTMSGS_SET_BIT = 1, + EVENTMSGS_RESET_BIT = 2, + EVENTMSGS_SET_MASK = 3 +} event_msgs_ext_command_t; + +#define EVENTMSGS_VER 1 +#define EVENTMSGS_EXT_STRUCT_SIZE OFFSETOF(eventmsgs_ext_t, mask[0]) + +/* len- for SET it would be mask size from the application to the firmware */ +/* for GET it would be actual firmware mask size */ +/* maxgetsize - is only used for GET. indicate max mask size that the */ +/* application can read from the firmware */ +typedef struct eventmsgs_ext +{ + uint8 ver; + uint8 command; + uint8 len; + uint8 maxgetsize; + uint8 mask[1]; +} eventmsgs_ext_t; /* no default structure packing */ #include diff --git a/drivers/net/wireless/bcmdhd/wl_cfg80211.c b/drivers/net/wireless/bcmdhd/wl_cfg80211.c index 976b262e0c84..2d2ff7d0be4d 100644 --- a/drivers/net/wireless/bcmdhd/wl_cfg80211.c +++ b/drivers/net/wireless/bcmdhd/wl_cfg80211.c @@ -22,7 +22,7 @@ * software in any way with any other Broadcom software provided under a license * other than the GPL, without Broadcom's express prior written consent. * - * $Id: wl_cfg80211.c 547972 2015-04-10 03:41:41Z $ + * $Id: wl_cfg80211.c 571419 2015-07-15 03:53:57Z $ */ /* */ #include @@ -398,7 +398,7 @@ static void wl_cfg80211_scan_abort(struct bcm_cfg80211 *cfg); static s32 wl_notify_escan_complete(struct bcm_cfg80211 *cfg, struct net_device *ndev, bool aborted, bool fw_abort); #if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 2, 0)) -#ifdef TDLS_MGMT_VERSION2 +#if defined(CONFIG_ARCH_MSM) && defined(TDLS_MGMT_VERSION2) static s32 wl_cfg80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, u8 *peer, u8 action_code, u8 dialog_token, u16 status_code, u32 peer_capability, const u8 *data, size_t len); @@ -406,7 +406,7 @@ static s32 wl_cfg80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, static s32 wl_cfg80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, u8 *peer, u8 action_code, u8 dialog_token, u16 status_code, const u8 *data, size_t len); -#endif /* TDLS_MGMT_VERSION2 */ +#endif /* CONFIG_ARCH_MSM && TDLS_MGMT_VERSION2 */ static s32 wl_cfg80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, u8 *peer, enum nl80211_tdls_operation oper); #endif @@ -461,6 +461,13 @@ static s32 wl_notify_gscan_event(struct bcm_cfg80211 *wl, bcm_struct_cfgdev *cfg static s32 wl_notifier_change_state(struct bcm_cfg80211 *cfg, struct net_info *_net_info, enum wl_status state, bool set); +#ifdef DHD_LOSSLESS_ROAMING +static s32 wl_notify_roam_prep_status(struct bcm_cfg80211 *cfg, + bcm_struct_cfgdev *cfgdev, const wl_event_msg_t *e, void *data); +static void wl_del_roam_timeout(struct bcm_cfg80211 *cfg); +#endif /* DHD_LOSSLESS_ROAMING */ + + #ifdef WLTDLS static s32 wl_tdls_event_handler(struct bcm_cfg80211 *cfg, bcm_struct_cfgdev *cfgdev, const wl_event_msg_t *e, void *data); @@ -626,15 +633,6 @@ static int wl_cfg80211_delayed_roam(struct bcm_cfg80211 *cfg, struct net_device const struct ether_addr *bssid); -#define RETURN_EIO_IF_NOT_UP(wlpriv) \ -do { \ - struct net_device *checkSysUpNDev = bcmcfg_to_prmry_ndev(wlpriv); \ - if (unlikely(!wl_get_drv_status(wlpriv, READY, checkSysUpNDev))) { \ - WL_INFO(("device is not ready\n")); \ - return -EIO; \ - } \ -} while (0) - #ifdef RSSI_OFFSET static s32 wl_rssi_offset(s32 rssi) { @@ -2625,28 +2623,28 @@ static s32 wl_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) (cfg->conf->rts_threshold != wiphy->rts_threshold)) { cfg->conf->rts_threshold = wiphy->rts_threshold; err = wl_set_rts(ndev, cfg->conf->rts_threshold); - if (!err) + if (err != BCME_OK) return err; } if (changed & WIPHY_PARAM_FRAG_THRESHOLD && (cfg->conf->frag_threshold != wiphy->frag_threshold)) { cfg->conf->frag_threshold = wiphy->frag_threshold; err = wl_set_frag(ndev, cfg->conf->frag_threshold); - if (!err) + if (err != BCME_OK) return err; } if (changed & WIPHY_PARAM_RETRY_LONG && (cfg->conf->retry_long != wiphy->retry_long)) { cfg->conf->retry_long = wiphy->retry_long; err = wl_set_retry(ndev, cfg->conf->retry_long, true); - if (!err) + if (err != BCME_OK) return err; } if (changed & WIPHY_PARAM_RETRY_SHORT && (cfg->conf->retry_short != wiphy->retry_short)) { cfg->conf->retry_short = wiphy->retry_short; err = wl_set_retry(ndev, cfg->conf->retry_short, false); - if (!err) { + if (err != BCME_OK) { return err; } } @@ -6797,6 +6795,9 @@ wl_cfg80211_change_station( err = wldev_ioctl(primary_ndev, WLC_SCB_AUTHORIZE, mac, ETH_ALEN, true); if (err) WL_ERR(("WLC_SCB_AUTHORIZE error (%d)\n", err)); +#ifdef DHD_LOSSLESS_ROAMING + wl_del_roam_timeout(cfg); +#endif /* DHD_LOSSLESS_ROAMING */ return err; } #endif /* WL_SUPPORT_BACKPORTED_KPATCHES || KERNEL_VER >= KERNEL_VERSION(3, 2, 0)) */ @@ -7473,9 +7474,9 @@ static s32 wl_setup_wiphy(struct wireless_dev *wdev, struct device *sdiofunc_dev wdev->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_ADHOC) -#if !defined(WL_ENABLE_P2P_IF) +#if !defined(WL_ENABLE_P2P_IF) && !defined(WL_CFG80211_P2P_DEV_IF) | BIT(NL80211_IFTYPE_MONITOR) -#endif /* !WL_ENABLE_P2P_IF */ +#endif /* !WL_ENABLE_P2P_IF && !WL_CFG80211_P2P_DEV_IF */ #if defined(WL_IFACE_COMB_NUM_CHANNELS) || defined(WL_CFG80211_P2P_DEV_IF) | BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO) @@ -8105,17 +8106,29 @@ wl_notify_connect_status(struct bcm_cfg80211 *cfg, bcm_struct_cfgdev *cfgdev, wl_link_up(cfg); act = true; if (!wl_get_drv_status(cfg, DISCONNECTING, ndev)) { +#ifdef DHD_LOSSLESS_ROAMING + bool is_connected = wl_get_drv_status(cfg, CONNECTED, ndev); +#endif /* DHD_LOSSLESS_ROAMING */ printk("wl_bss_connect_done succeeded with " MACDBG "\n", MAC2STRDBG((u8*)(&e->addr))); wl_bss_connect_done(cfg, ndev, e, data, true); WL_DBG(("joined in BSS network \"%s\"\n", ((struct wlc_ssid *) wl_read_prof(cfg, ndev, WL_PROF_SSID))->SSID)); +#ifdef DHD_LOSSLESS_ROAMING + if (event == WLC_E_LINK && is_connected && + !cfg->roam_offload) { + wl_bss_roaming_done(cfg, ndev, e, data); + } +#endif /* DHD_LOSSLESS_ROAMING */ } wl_update_prof(cfg, ndev, e, &act, WL_PROF_ACT); wl_update_prof(cfg, ndev, NULL, (void *)&e->addr, WL_PROF_BSSID); } else if (wl_is_linkdown(cfg, e)) { +#ifdef DHD_LOSSLESS_ROAMING + wl_del_roam_timeout(cfg); +#endif /* DHD_LOSSLESS_ROAMING */ if (cfg->scan_request) wl_notify_escan_complete(cfg, ndev, true, true); if (wl_get_drv_status(cfg, CONNECTED, ndev)) { @@ -8260,6 +8273,9 @@ wl_notify_roaming_status(struct bcm_cfg80211 *cfg, bcm_struct_cfgdev *cfgdev, s32 err = 0; u32 event = be32_to_cpu(e->event_type); u32 status = be32_to_cpu(e->status); +#ifdef DHD_LOSSLESS_ROAMING + struct wl_security *sec; +#endif WL_DBG(("Enter \n")); ndev = cfgdev_to_wlc_ndev(cfgdev, cfg); @@ -8273,16 +8289,67 @@ wl_notify_roaming_status(struct bcm_cfg80211 *cfg, bcm_struct_cfgdev *cfgdev, return err; if ((event == WLC_E_ROAM || event == WLC_E_BSSID) && status == WLC_E_STATUS_SUCCESS) { - if (wl_get_drv_status(cfg, CONNECTED, ndev)) + if (wl_get_drv_status(cfg, CONNECTED, ndev)) { +#ifdef DHD_LOSSLESS_ROAMING + if (cfg->roam_offload) { + wl_bss_roaming_done(cfg, ndev, e, data); + wl_del_roam_timeout(cfg); + } + else { + sec = wl_read_prof(cfg, ndev, WL_PROF_SEC); + /* In order to reduce roaming delay, wl_bss_roaming_done is + * early called with WLC_E_LINK event. It is called from + * here only if WLC_E_LINK event is blocked for specific + * security type. + */ + if (IS_AKM_SUITE_FT(sec)) { + wl_bss_roaming_done(cfg, ndev, e, data); + } + /* Roam timer is deleted mostly from wl_cfg80211_change_station + * after roaming is finished successfully. We need to delete + * the timer from here only for some security types that aren't + * using wl_cfg80211_change_station to authorize SCB + */ + if (IS_AKM_SUITE_FT(sec) || IS_AKM_SUITE_CCKM(sec)) { + wl_del_roam_timeout(cfg); + } + } +#else /* DHD_LOSSLESS_ROAMING */ wl_bss_roaming_done(cfg, ndev, e, data); - else +#endif /* DHD_LOSSLESS_ROAMING */ + } else { wl_bss_connect_done(cfg, ndev, e, data, true); + } act = true; wl_update_prof(cfg, ndev, e, &act, WL_PROF_ACT); wl_update_prof(cfg, ndev, NULL, (void *)&e->addr, WL_PROF_BSSID); } +#ifdef DHD_LOSSLESS_ROAMING + else if ((event == WLC_E_ROAM || event == WLC_E_BSSID) && status != WLC_E_STATUS_SUCCESS) { + wl_del_roam_timeout(cfg); + } +#endif /* DHD_LOSSLESS_ROAMING */ + + return err; +} + +#ifdef DHD_LOSSLESS_ROAMING +static s32 +wl_notify_roam_prep_status(struct bcm_cfg80211 *cfg, bcm_struct_cfgdev *cfgdev, + const wl_event_msg_t *e, void *data) +{ + s32 err = 0; + dhd_pub_t *dhdp = (dhd_pub_t *)(cfg->pub); + + dhdp->dequeue_prec_map = 1 << PRIO_8021D_NC; + /* Restore flow control */ + dhd_txflowcontrol(dhdp, ALL_INTERFACES, OFF); + + mod_timer(&cfg->roam_timeout, jiffies + msecs_to_jiffies(WL_ROAM_TIMEOUT_MS)); + return err; } +#endif /* DHD_LOSSLESS_ROAMING */ static s32 wl_get_assoc_ies(struct bcm_cfg80211 *cfg, struct net_device *ndev) { @@ -8721,8 +8788,7 @@ wl_notify_gscan_event(struct bcm_cfg80211 *cfg, bcm_struct_cfgdev *cfgdev, break; case WLC_E_PFN_SCAN_COMPLETE: batch_event_result_dummy = WIFI_SCAN_COMPLETE; - /* Tmp: Until API is fixed for One shot scan */ - dhd_dev_end_one_shot_gscan(ndev); + #ifdef WL_VENDOR_EXT_SUPPORT wl_cfgvendor_send_async_event(wiphy, ndev, GOOGLE_SCAN_COMPLETE_EVENT, @@ -8766,6 +8832,9 @@ wl_notify_gscan_event(struct bcm_cfg80211 *cfg, bcm_struct_cfgdev *cfgdev, kfree(ptr); } break; + default: + WL_ERR(("%s: Unexpected event! - %d\n", __FUNCTION__, event)); + } return err; } @@ -9330,6 +9399,10 @@ static void wl_init_event_handler(struct bcm_cfg80211 *cfg) cfg->evt_handler[WLC_E_NAN] = wl_cfgnan_notify_nan_status; cfg->evt_handler[WLC_E_PROXD] = wl_cfgnan_notify_proxd_status; #endif /* WL_NAN */ +#ifdef DHD_LOSSLESS_ROAMING + cfg->evt_handler[WLC_E_ROAM_PREP] = wl_notify_roam_prep_status; +#endif + } #if defined(STATIC_WL_PRIV_STRUCT) @@ -9507,6 +9580,32 @@ static void wl_scan_timeout(unsigned long data) wl_cfg80211_event(bcmcfg_to_prmry_ndev(cfg), &msg, NULL); } +#ifdef DHD_LOSSLESS_ROAMING +static void wl_del_roam_timeout(struct bcm_cfg80211 *cfg) +{ + dhd_pub_t *dhdp = (dhd_pub_t *)(cfg->pub); + + /* restore prec_map to ALLPRIO */ + dhdp->dequeue_prec_map = ALLPRIO; + if (timer_pending(&cfg->roam_timeout)) { + del_timer_sync(&cfg->roam_timeout); + } + +} + +static void wl_roam_timeout(unsigned long data) +{ + struct bcm_cfg80211 *cfg = (struct bcm_cfg80211 *)data; + dhd_pub_t *dhdp = (dhd_pub_t *)(cfg->pub); + + WL_ERR(("roam timer expired\n")); + + /* restore prec_map to ALLPRIO */ + dhdp->dequeue_prec_map = ALLPRIO; +} + +#endif /* DHD_LOSSLESS_ROAMING */ + static s32 wl_cfg80211_netdev_notifier_call(struct notifier_block * nb, unsigned long state, @@ -10190,6 +10289,20 @@ static s32 wl_init_scan(struct bcm_cfg80211 *cfg) return err; } +#ifdef DHD_LOSSLESS_ROAMING +static s32 wl_init_roam_timeout(struct bcm_cfg80211 *cfg) +{ + int err = 0; + + /* Init roam timer */ + init_timer(&cfg->roam_timeout); + cfg->roam_timeout.data = (unsigned long) cfg; + cfg->roam_timeout.function = wl_roam_timeout; + + return err; +} +#endif /* DHD_LOSSLESS_ROAMING */ + static s32 wl_init_priv(struct bcm_cfg80211 *cfg) { struct wiphy *wiphy = bcmcfg_to_wiphy(cfg); @@ -10224,6 +10337,13 @@ static s32 wl_init_priv(struct bcm_cfg80211 *cfg) err = wl_init_scan(cfg); if (err) return err; +#ifdef DHD_LOSSLESS_ROAMING + err = wl_init_roam_timeout(cfg); + if (err) { + return err; + } +#endif /* DHD_LOSSLESS_ROAMING */ + wl_init_conf(cfg->conf); wl_init_prof(cfg, ndev); wl_link_down(cfg); @@ -10239,6 +10359,9 @@ static void wl_deinit_priv(struct bcm_cfg80211 *cfg) wl_flush_eq(cfg); wl_link_down(cfg); del_timer_sync(&cfg->scan_timeout); +#ifdef DHD_LOSSLESS_ROAMING + del_timer_sync(&cfg->roam_timeout); +#endif wl_deinit_priv_mem(cfg); if (wl_cfg80211_netdev_notifier_registered) { wl_cfg80211_netdev_notifier_registered = FALSE; @@ -10466,6 +10589,11 @@ void wl_cfg80211_detach(void *para) if (timer_pending(&cfg->scan_timeout)) del_timer_sync(&cfg->scan_timeout); +#ifdef DHD_LOSSLESS_ROAMING + if (timer_pending(&cfg->roam_timeout)) { + del_timer_sync(&cfg->roam_timeout); + } +#endif /* DHD_LOSSLESS_ROAMING */ #if defined(WL_CFG80211_P2P_DEV_IF) wl_cfgp2p_del_p2p_disc_if(cfg->p2p_wdev, cfg); @@ -11351,6 +11479,13 @@ static s32 __wl_cfg80211_down(struct bcm_cfg80211 *cfg) wl_link_down(cfg); if (cfg->p2p_supported) wl_cfgp2p_down(cfg); + +#ifdef DHD_LOSSLESS_ROAMING + if (timer_pending(&cfg->roam_timeout)) { + del_timer_sync(&cfg->roam_timeout); + } +#endif /* DHD_LOSSLESS_ROAMING */ + if (cfg->ap_info) { kfree(cfg->ap_info->wpa_ie); kfree(cfg->ap_info->rsn_ie); @@ -11802,17 +11937,16 @@ wl_tdls_event_handler(struct bcm_cfg80211 *cfg, bcm_struct_cfgdev *cfgdev, #endif /* WLTDLS */ #if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 2, 0)) -#ifdef TDLS_MGMT_VERSION2 static s32 +#if defined(CONFIG_ARCH_MSM) && defined(TDLS_MGMT_VERSION2) wl_cfg80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, - u8 *peer, u8 action_code, u8 dialog_token, u16 status_code, - u32 peer_capability, const u8 *data, size_t len) + u8 *peer, u8 action_code, u8 dialog_token, u16 status_code, + u32 peer_capability, const u8 *data, size_t len) #else -static s32 wl_cfg80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, u8 *peer, u8 action_code, u8 dialog_token, u16 status_code, const u8 *data, size_t len) -#endif /* TDLS_MGMT_VERSION2 */ +#endif /* CONFIG_ARCH_MSM && TDLS_MGMT_VERSION2 */ { s32 ret = 0; #ifdef WLTDLS @@ -11820,6 +11954,14 @@ wl_cfg80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, tdls_wfd_ie_iovar_t info; memset(&info, 0, sizeof(tdls_wfd_ie_iovar_t)); cfg = g_bcm_cfg; + +#if defined(CONFIG_ARCH_MSM) && defined(TDLS_MGMT_VERSION2) + /* Some customer platform back ported this feature from kernel 3.15 to kernel 3.10 + * and that cuases build error + */ + BCM_REFERENCE(peer_capability); +#endif /* CONFIG_ARCH_MSM && TDLS_MGMT_VERSION2 */ + switch (action_code) { /* We need to set TDLS Wifi Display IE to firmware * using tdls_wfd_ie iovar diff --git a/drivers/net/wireless/bcmdhd/wl_cfg80211.h b/drivers/net/wireless/bcmdhd/wl_cfg80211.h index 1b97d6492a41..bf5d916ba48c 100644 --- a/drivers/net/wireless/bcmdhd/wl_cfg80211.h +++ b/drivers/net/wireless/bcmdhd/wl_cfg80211.h @@ -21,7 +21,7 @@ * software in any way with any other Broadcom software provided under a license * other than the GPL, without Broadcom's express prior written consent. * - * $Id: wl_cfg80211.h 537581 2015-02-27 00:58:37Z $ + * $Id: wl_cfg80211.h 571419 2015-07-15 03:53:57Z $ */ #ifndef _wl_cfg80211_h_ @@ -159,8 +159,12 @@ do { \ #define WL_AF_TX_EXTRA_TIME_MAX 200 #define WL_SCAN_TIMER_INTERVAL_MS 10000 /* Scan timeout */ -#define WL_CHANNEL_SYNC_RETRY 5 -#define WL_INVALID -1 +#define WL_CHANNEL_SYNC_RETRY 5 +#define WL_INVALID -1 + +#ifdef DHD_LOSSLESS_ROAMING +#define WL_ROAM_TIMEOUT_MS 1000 /* Roam timeout */ +#endif /* DHD_LOSSLESS_ROAMING */ /* Bring down SCB Timeout to 20secs from 60secs default */ #ifndef WL_SCB_TIMEOUT @@ -599,6 +603,9 @@ struct bcm_cfg80211 { #endif /* WLTDLS */ bool nan_running; bool need_wait_afrx; +#ifdef DHD_LOSSLESS_ROAMING + struct timer_list roam_timeout; +#endif /* DHD_LOSSLESS_ROAMING */ }; @@ -867,6 +874,28 @@ wl_get_netinfo_by_netdev(struct bcm_cfg80211 *cfg, struct net_device *ndev) ((wl_cfgp2p_find_wpsie((u8 *)_sme->ie, _sme->ie_len) != NULL) && \ (!_sme->crypto.n_ciphers_pairwise) && \ (!_sme->crypto.cipher_group)) + +#ifdef WLFBT +#if defined(WLAN_AKM_SUITE_FT_8021X) && defined(WLAN_AKM_SUITE_FT_PSK) +#define IS_AKM_SUITE_FT(sec) (sec->wpa_auth == WLAN_AKM_SUITE_FT_8021X || \ + sec->wpa_auth == WLAN_AKM_SUITE_FT_PSK) +#elif defined(WLAN_AKM_SUITE_FT_8021X) +#define IS_AKM_SUITE_FT(sec) (sec->wpa_auth == WLAN_AKM_SUITE_FT_8021X) +#elif defined(WLAN_AKM_SUITE_FT_PSK) +#define IS_AKM_SUITE_FT(sec) (sec->wpa_auth == WLAN_AKM_SUITE_FT_PSK) +#else +#define IS_AKM_SUITE_FT(sec) false +#endif /* WLAN_AKM_SUITE_FT_8021X && WLAN_AKM_SUITE_FT_PSK */ +#else +#define IS_AKM_SUITE_FT(sec) false +#endif /* WLFBT */ + +#ifdef BCMCCX +#define IS_AKM_SUITE_CCKM(sec) (sec->wpa_auth == WLAN_AKM_SUITE_CCKM) +#else +#define IS_AKM_SUITE_CCKM(sec) false +#endif /* BCMCCX */ + extern s32 wl_cfg80211_attach(struct net_device *ndev, void *context); extern s32 wl_cfg80211_attach_post(struct net_device *ndev); extern void wl_cfg80211_detach(void *para); @@ -974,4 +1003,13 @@ struct net_device *wl_cfg80211_get_remain_on_channel_ndev(struct bcm_cfg80211 *c extern int wl_cfg80211_get_ioctl_version(void); +#define RETURN_EIO_IF_NOT_UP(wlpriv) \ + do { \ + struct net_device *checkSysUpNDev = bcmcfg_to_prmry_ndev(wlpriv); \ + if (unlikely(!wl_get_drv_status(wlpriv, READY, checkSysUpNDev))) { \ + WL_INFO(("device is not ready\n")); \ + return -EIO; \ + } \ + } while (0) + #endif /* _wl_cfg80211_h_ */ diff --git a/drivers/net/wireless/bcmdhd/wl_cfgvendor.c b/drivers/net/wireless/bcmdhd/wl_cfgvendor.c index 04fc5b51f5cf..1e512843ff29 100644 --- a/drivers/net/wireless/bcmdhd/wl_cfgvendor.c +++ b/drivers/net/wireless/bcmdhd/wl_cfgvendor.c @@ -556,10 +556,17 @@ wl_cfgvendor_set_scan_cfg(struct wiphy *wiphy, ch_bucket[j].report_flag = (uint8) nla_get_u32(iter1); break; + default: + WL_ERR(("bucket attribute type error %d\n", + type)); + break; } } j++; break; + default: + WL_ERR(("Unknown type %d\n", type)); + break; } } @@ -615,6 +622,10 @@ wl_cfgvendor_hotlist_cfg(struct wiphy *wiphy, case GSCAN_ATTRIBUTE_RSSI_HIGH: dummy = (int8) nla_get_u8(inner); break; + default: + WL_ERR(("ATTR unknown %d\n", + type)); + break; } } j++; @@ -627,7 +638,11 @@ wl_cfgvendor_hotlist_cfg(struct wiphy *wiphy, case GSCAN_ATTRIBUTE_LOST_AP_SAMPLE_SIZE: hotlist_params->lost_ap_window = nla_get_u32(iter); break; - } + default: + WL_ERR(("Unknown type %d\n", type)); + break; + } + } if (dhd_dev_pno_set_cfg_gscan(bcmcfg_to_prmry_ndev(cfg), @@ -666,6 +681,9 @@ wl_cfgvendor_set_batch_scan_cfg(struct wiphy *wiphy, case GSCAN_ATTRIBUTE_REPORT_THRESHOLD: batch_param.buffer_threshold = nla_get_u32(iter); break; + default: + WL_ERR(("Unknown type %d\n", type)); + break; } } @@ -731,11 +749,18 @@ wl_cfgvendor_significant_change_cfg(struct wiphy *wiphy, pbssid[j].rssi_low_threshold = (int8) nla_get_u8(inner); break; + default: + WL_ERR(("ATTR unknown %d\n", + type)); + break; } } j++; } break; + default: + WL_ERR(("Unknown type %d\n", type)); + break; } } significant_params->nbssid = j; @@ -1039,10 +1064,11 @@ static int wl_cfgvendor_priv_string_handler(struct wiphy *wiphy, #define NUM_RATE 32 #define NUM_PEER 1 #define NUM_CHAN 11 -static int wl_cfgvendor_lstats_get_info(struct wiphy *wiphy, +static int +wl_cfgvendor_lstats_get_info(struct wiphy *wiphy, struct wireless_dev *wdev, void *data, int len) { - static char iovar_buf[WLC_IOCTL_MAXLEN]; + char *iovar_buf = 0; struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); int err = 0; wifi_iface_stat *iface; @@ -1050,34 +1076,45 @@ static int wl_cfgvendor_lstats_get_info(struct wiphy *wiphy, wl_wme_cnt_t *wl_wme_cnt; wl_cnt_t *wl_cnt; char *output; + int radstat_buflen = sizeof(wifi_radio_stat) + NUM_CHAN * sizeof(wifi_channel_stat); + int iface_buflen = sizeof(wifi_iface_stat) + NUM_PEER * sizeof(wifi_peer_info); + int ratestat_buflen = NUM_RATE * sizeof(wifi_rate_stat); - WL_INFO(("%s: Enter \n", __func__)); + WL_TRACE(("%s: Enter \n", __FUNCTION__)); - bzero(cfg->ioctl_buf, WLC_IOCTL_MAXLEN); - bzero(iovar_buf, WLC_IOCTL_MAXLEN); + RETURN_EIO_IF_NOT_UP(cfg); - output = cfg->ioctl_buf; + iovar_buf = kzalloc(sizeof(char) * WLC_IOCTL_MAXLEN, GFP_KERNEL); + if (unlikely(!iovar_buf)) { + WL_ERR(("%s: link stats buf alloc fails\n", __FUNCTION__)); + return -ENOMEM; + } + + ASSERT((radstat_buflen + iface_buflen + ratestat_buflen) <= + (sizeof(char) * WLC_IOCTL_MAXLEN)); + + output = iovar_buf; radio = (wifi_radio_stat *)output; err = wldev_iovar_getbuf(bcmcfg_to_prmry_ndev(cfg), "radiostat", NULL, 0, - iovar_buf, WLC_IOCTL_MAXLEN, NULL); + cfg->ioctl_buf, WLC_IOCTL_MAXLEN, &cfg->ioctl_buf_sync); if (unlikely(err)) { - WL_ERR(("error (%d) - size = %zu\n", err, sizeof(wifi_radio_stat))); - return err; + WL_ERR(("%s: ioctl error(%d) for radiostat - size = %zu\n", + __FUNCTION__, err, sizeof(wifi_radio_stat))); + goto exit; } - memcpy(output, iovar_buf, sizeof(wifi_radio_stat)); + memcpy(output, cfg->ioctl_buf, sizeof(wifi_radio_stat)); radio->num_channels = NUM_CHAN; - output += sizeof(wifi_radio_stat); - output += (NUM_CHAN*sizeof(wifi_channel_stat)); + output += radstat_buflen; err = wldev_iovar_getbuf(bcmcfg_to_prmry_ndev(cfg), "wme_counters", NULL, 0, - iovar_buf, WLC_IOCTL_MAXLEN, NULL); + cfg->ioctl_buf, WLC_IOCTL_MAXLEN, &cfg->ioctl_buf_sync); if (unlikely(err)) { - WL_ERR(("error (%d)\n", err)); - return err; + WL_ERR(("%s: ioctl error(%d) for wme_counters\n", __FUNCTION__, err)); + goto exit; } - wl_wme_cnt = (wl_wme_cnt_t *)iovar_buf; + wl_wme_cnt = (wl_wme_cnt_t *)cfg->ioctl_buf; iface = (wifi_iface_stat *)output; iface->ac[WIFI_AC_VO].ac = WIFI_AC_VO; @@ -1099,44 +1136,53 @@ static int wl_cfgvendor_lstats_get_info(struct wiphy *wiphy, iface->ac[WIFI_AC_BK].tx_mpdu = wl_wme_cnt->tx[AC_BK].packets; iface->ac[WIFI_AC_BK].rx_mpdu = wl_wme_cnt->rx[AC_BK].packets; iface->ac[WIFI_AC_BK].mpdu_lost = wl_wme_cnt->tx_failed[WIFI_AC_BK].packets; - bzero(iovar_buf, WLC_IOCTL_MAXLEN); err = wldev_iovar_getbuf(bcmcfg_to_prmry_ndev(cfg), "counters", NULL, 0, - iovar_buf, WLC_IOCTL_MAXLEN, NULL); + cfg->ioctl_buf, WLC_IOCTL_MAXLEN, &cfg->ioctl_buf_sync); if (unlikely(err)) { - WL_ERR(("error (%d) - size = %zu\n", err, sizeof(wl_cnt_t))); - return err; + WL_ERR(("%s: ioctl error(%d) for counters - size = %zu\n", + __FUNCTION__, err, sizeof(wl_cnt_t))); + goto exit; } - wl_cnt = (wl_cnt_t *)iovar_buf; + wl_cnt = (wl_cnt_t *)cfg->ioctl_buf; iface->ac[WIFI_AC_BE].retries = wl_cnt->txretry; iface->beacon_rx = wl_cnt->rxbeaconmbss; err = wldev_get_rssi(bcmcfg_to_prmry_ndev(cfg), &iface->rssi_mgmt); if (unlikely(err)) { - WL_ERR(("get_rssi error (%d)\n", err)); - return err; + WL_ERR(("%s: get_rssi error(%d)\n", __FUNCTION__, err)); + goto exit; } iface->num_peers = NUM_PEER; iface->peer_info->num_rate = NUM_RATE; - bzero(iovar_buf, WLC_IOCTL_MAXLEN); - output = (char *)iface + sizeof(wifi_iface_stat) + NUM_PEER*sizeof(wifi_peer_info); + output = (char *)iface + iface_buflen; err = wldev_iovar_getbuf(bcmcfg_to_prmry_ndev(cfg), "ratestat", NULL, 0, - iovar_buf, WLC_IOCTL_MAXLEN, NULL); + cfg->ioctl_buf, WLC_IOCTL_MAXLEN, &cfg->ioctl_buf_sync); if (unlikely(err)) { - WL_ERR(("error (%d) - size = %zu\n", err, NUM_RATE*sizeof(wifi_rate_stat))); - return err; + WL_ERR(("%s: ioctl error(%d) for ratestat - size = %d\n", + __FUNCTION__, err, ratestat_buflen)); + goto exit; } - memcpy(output, iovar_buf, NUM_RATE*sizeof(wifi_rate_stat)); + memcpy(output, cfg->ioctl_buf, ratestat_buflen); + if (unlikely(radio->num_channels != NUM_CHAN)) { + WL_ERR(("%s: error! num_channels corrupted. value=%d \n", + __FUNCTION__, (int)radio->num_channels)); + goto exit; + } err = wl_cfgvendor_send_cmd_reply(wiphy, bcmcfg_to_prmry_ndev(cfg), - cfg->ioctl_buf, sizeof(wifi_radio_stat)+NUM_CHAN*sizeof(wifi_channel_stat)+ - sizeof(wifi_iface_stat)+NUM_PEER*sizeof(wifi_peer_info)+ - NUM_RATE*sizeof(wifi_rate_stat)); - if (unlikely(err)) - WL_ERR(("Vendor Command reply failed ret:%d \n", err)); + iovar_buf, radstat_buflen + iface_buflen + ratestat_buflen); + if (unlikely(err)) { + WL_ERR(("%s: Vendor Command reply failed ret:%d \n", __FUNCTION__, err)); + } + +exit: + if (iovar_buf) { + kfree(iovar_buf); + } return err; } diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/hw.c b/drivers/net/wireless/rtlwifi/rtl8192cu/hw.c index 0c74d4f2eeb4..88f73503b6f9 100644 --- a/drivers/net/wireless/rtlwifi/rtl8192cu/hw.c +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/hw.c @@ -985,6 +985,17 @@ int rtl92cu_hw_init(struct ieee80211_hw *hw) struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); int err = 0; static bool iqk_initialized; + unsigned long flags; + + /* As this function can take a very long time (up to 350 ms) + * and can be called with irqs disabled, reenable the irqs + * to let the other devices continue being serviced. + * + * It is safe doing so since our own interrupts will only be enabled + * in a subsequent step. + */ + local_save_flags(flags); + local_irq_enable(); rtlhal->hw_type = HARDWARE_TYPE_RTL8192CU; err = _rtl92cu_init_mac(hw); @@ -997,7 +1008,7 @@ int rtl92cu_hw_init(struct ieee80211_hw *hw) RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, "Failed to download FW. Init HW without FW now..\n"); err = 1; - return err; + goto exit; } rtlhal->last_hmeboxnum = 0; /* h2c */ _rtl92cu_phy_param_tab_init(hw); @@ -1034,6 +1045,8 @@ int rtl92cu_hw_init(struct ieee80211_hw *hw) _InitPABias(hw); _update_mac_setting(hw); rtl92c_dm_init(hw); +exit: + local_irq_restore(flags); return err; } diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/hw.c b/drivers/net/wireless/rtlwifi/rtl8192se/hw.c index b141c35bf926..f90eb0cd7ae5 100644 --- a/drivers/net/wireless/rtlwifi/rtl8192se/hw.c +++ b/drivers/net/wireless/rtlwifi/rtl8192se/hw.c @@ -922,7 +922,7 @@ int rtl92se_hw_init(struct ieee80211_hw *hw) struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); u8 tmp_byte = 0; - + unsigned long flags; bool rtstatus = true; u8 tmp_u1b; int err = false; @@ -934,6 +934,16 @@ int rtl92se_hw_init(struct ieee80211_hw *hw) rtlpci->being_init_adapter = true; + /* As this function can take a very long time (up to 350 ms) + * and can be called with irqs disabled, reenable the irqs + * to let the other devices continue being serviced. + * + * It is safe doing so since our own interrupts will only be enabled + * in a subsequent step. + */ + local_save_flags(flags); + local_irq_enable(); + rtlpriv->intf_ops->disable_aspm(hw); /* 1. MAC Initialize */ @@ -951,7 +961,8 @@ int rtl92se_hw_init(struct ieee80211_hw *hw) RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, "Failed to download FW. Init HW without FW now... " "Please copy FW into /lib/firmware/rtlwifi\n"); - return 1; + err = 1; + goto exit; } /* After FW download, we have to reset MAC register */ @@ -964,7 +975,8 @@ int rtl92se_hw_init(struct ieee80211_hw *hw) /* 3. Initialize MAC/PHY Config by MACPHY_reg.txt */ if (!rtl92s_phy_mac_config(hw)) { RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "MAC Config failed\n"); - return rtstatus; + err = rtstatus; + goto exit; } /* Make sure BB/RF write OK. We should prevent enter IPS. radio off. */ @@ -974,7 +986,8 @@ int rtl92se_hw_init(struct ieee80211_hw *hw) /* 4. Initialize BB After MAC Config PHY_reg.txt, AGC_Tab.txt */ if (!rtl92s_phy_bb_config(hw)) { RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, "BB Config failed\n"); - return rtstatus; + err = rtstatus; + goto exit; } /* 5. Initiailze RF RAIO_A.txt RF RAIO_B.txt */ @@ -1010,7 +1023,8 @@ int rtl92se_hw_init(struct ieee80211_hw *hw) if (!rtl92s_phy_rf_config(hw)) { RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "RF Config failed\n"); - return rtstatus; + err = rtstatus; + goto exit; } /* After read predefined TXT, we must set BB/MAC/RF @@ -1084,8 +1098,9 @@ int rtl92se_hw_init(struct ieee80211_hw *hw) rtlpriv->cfg->ops->led_control(hw, LED_CTL_POWER_ON); rtl92s_dm_init(hw); +exit: + local_irq_restore(flags); rtlpci->being_init_adapter = false; - return err; } diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index e744d04e663f..001af499dafe 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -8,6 +8,13 @@ menuconfig POWER_SUPPLY if POWER_SUPPLY +config FORCE_FAST_CHARGE + bool "Increase input current limit to 900mA when charging via USB" + default y + help + A simple sysfs interface to increase input current limit to + 900mA when charging via USB. + config POWER_SUPPLY_DEBUG bool "Power supply debug" help diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 8f152aa40a65..361a7b6dcb05 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -65,3 +65,5 @@ obj-$(CONFIG_QPNP_CHARGER) += qpnp-charger.o obj-$(CONFIG_LTC4088_CHARGER) += ltc4088-charger.o obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o obj-$(CONFIG_BATTERY_BCL) += battery_current_limit.o +obj-$(CONFIG_POWER_RESET) += reset/ +obj-$(CONFIG_FORCE_FAST_CHARGE) += fastcharge.o diff --git a/drivers/power/fastcharge.c b/drivers/power/fastcharge.c new file mode 100644 index 000000000000..c7b130a0c0cc --- /dev/null +++ b/drivers/power/fastcharge.c @@ -0,0 +1,65 @@ +/* + * + * USB Fastcharge + * + * 0 - disabled (default) + * 1 - increase charge current limit to 900mA + * +*/ + +#include +#include +#include + +int force_fast_charge = 0; + +static ssize_t force_fast_charge_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + size_t count = 0; + count += sprintf(buf, "%d\n", force_fast_charge); + return count; +} + +static ssize_t force_fast_charge_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) +{ + if (buf[0] >= '0' && buf[0] <= '1' && buf[1] == '\n') + if (force_fast_charge != buf[0] - '0') + force_fast_charge = buf[0] - '0'; + + return count; +} + +static struct kobj_attribute force_fast_charge_attribute = +__ATTR(force_fast_charge, 0666, force_fast_charge_show, force_fast_charge_store); + +static struct attribute *force_fast_charge_attrs[] = { + &force_fast_charge_attribute.attr, + NULL, +}; + +static struct attribute_group force_fast_charge_attr_group = { + .attrs = force_fast_charge_attrs, +}; + +static struct kobject *force_fast_charge_kobj; + +int force_fast_charge_init(void) +{ + int ret; + + force_fast_charge_kobj = kobject_create_and_add("fast_charge", kernel_kobj); + if (!force_fast_charge_kobj) { + return -ENOMEM; + } + + ret = sysfs_create_group(force_fast_charge_kobj, &force_fast_charge_attr_group); + + if (ret) + kobject_put(force_fast_charge_kobj); + + return ret; +} + + +module_init(force_fast_charge_init); + diff --git a/drivers/power/smb135x-charger.c b/drivers/power/smb135x-charger.c new file mode 100644 index 000000000000..308c6af36dee --- /dev/null +++ b/drivers/power/smb135x-charger.c @@ -0,0 +1,5589 @@ +/* Copyright (c) 2013-2014 The Linux Foundation. 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 version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_FORCE_FAST_CHARGE +#include +#endif + +#define SMB135X_BITS_PER_REG 8 + +/* Mask/Bit helpers */ +#define _SMB135X_MASK(BITS, POS) \ + ((unsigned char)(((1 << (BITS)) - 1) << (POS))) +#define SMB135X_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \ + _SMB135X_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \ + (RIGHT_BIT_POS)) + +/* Config registers */ +#define CFG_3_REG 0x03 +#define CHG_ITERM_50MA 0x08 +#define CHG_ITERM_100MA 0x10 +#define CHG_ITERM_150MA 0x18 +#define CHG_ITERM_200MA 0x20 +#define CHG_ITERM_250MA 0x28 +#define CHG_ITERM_300MA 0x00 +#define CHG_ITERM_500MA 0x30 +#define CHG_ITERM_600MA 0x38 +#define CHG_ITERM_MASK SMB135X_MASK(5, 3) +#define CHG_IR_COMP_DIS 0x00 +#define CHG_IR_COMP_25MV 0x01 +#define CHG_IR_COMP_50MV 0x02 +#define CHG_IR_COMP_75MV 0x03 +#define CHG_IR_COMP_100MV 0x04 +#define CHG_IR_COMP_125MV 0x05 +#define CHG_IR_COMP_150MV 0x06 +#define CHG_IR_COMP_175MV 0x07 +#define CHG_IR_COMP_MASK SMB135X_MASK(2, 0) + +#define CFG_4_REG 0x04 +#define CHG_INHIBIT_MASK SMB135X_MASK(7, 6) +#define CHG_INHIBIT_50MV_VAL 0x00 +#define CHG_INHIBIT_100MV_VAL 0x40 +#define CHG_INHIBIT_200MV_VAL 0x80 +#define CHG_INHIBIT_300MV_VAL 0xC0 + +#define CFG_5_REG 0x05 +#define RECHARGE_200MV_BIT BIT(2) +#define USB_2_3_BIT BIT(5) + +#define CFG_A_REG 0x0A +#define DCIN_VOLT_SEL SMB135X_MASK(7, 5) +#define DCIN_INPUT_MASK SMB135X_MASK(4, 0) +#define DCIN_INPUT_FAC 0x06 + +#define CFG_B_REG 0x0B +#define DCIN_AICL_BIT BIT(2) + +#define CFG_C_REG 0x0C +#define USBIN_VOLT_MODE_5V 0x00 +#define USBIN_VOLT_MODE_9V 0x60 +#define USBIN_VOLT_MODE_5V_TO_9V 0x20 +#define USBIN_VOLT_MODE_MASK SMB135X_MASK(7, 5) +#define USBIN_INPUT_MASK SMB135X_MASK(4, 0) + +#define CFG_D_REG 0x0D +#define AICL_GLITCH BIT(3) +#define AICL_ENABLE BIT(2) + +#define CFG_E_REG 0x0E +#define POLARITY_100_500_BIT BIT(2) +#define USB_CTRL_BY_PIN_BIT BIT(1) + +#define CFG_11_REG 0x11 +#define PRIORITY_BIT BIT(7) +#define AUTO_SRC_DET_BIT BIT(0) + +#define USBIN_DCIN_CFG_REG 0x12 +#define USBIN_SUSPEND_VIA_COMMAND_BIT BIT(6) +#define OTG_CURRENT_LIMIT_1000MA 0x0C +#define OTG_CURRENT_LIMIT_MASK SMB135X_MASK(3, 2) + +#define CFG_14_REG 0x14 +#define CHG_EN_BY_PIN_BIT BIT(7) +#define CHG_EN_ACTIVE_LOW_BIT BIT(6) +#define PRE_TO_FAST_REQ_CMD_BIT BIT(5) +#define DISABLE_CURRENT_TERM_BIT BIT(3) +#define DISABLE_AUTO_RECHARGE_BIT BIT(2) +#define EN_CHG_INHIBIT_BIT BIT(0) + +#define CFG_16_REG 0x16 +#define SAFETY_TIME_EN_BIT BIT(4) +#define SAFETY_TIME_MINUTES_MASK SMB135X_MASK(3, 2) +#define SAFETY_TIME_MINUTES_SHIFT 2 + +#define CFG_17_REG 0x17 +#define CHG_STAT_DISABLE_BIT BIT(0) +#define CHG_STAT_ACTIVE_HIGH_BIT BIT(1) +#define CHG_STAT_IRQ_ONLY_BIT BIT(4) + +#define CFG_18_REG 0x18 +#define DCIN_CMD_BIT BIT(3) + +#define CFG_19_REG 0x19 +#define BATT_MISSING_ALGO_BIT BIT(2) +#define BATT_MISSING_THERM_BIT BIT(1) + +#define CFG_1A_REG 0x1A +#define HOT_SOFT_VFLOAT_COMP_EN_BIT BIT(3) +#define COLD_SOFT_VFLOAT_COMP_EN_BIT BIT(2) + +#define CFG_1C_REG 0x1C +#define BATT_CURR_MASK SMB135X_MASK(4, 0) +#define TRICK_CURR_MASK SMB135X_MASK(7, 5) + +#define VFLOAT_REG 0x1E +#define VFLOAT_MASK SMB135X_MASK(5, 0) + +#define VERSION1_REG 0x2A +#define VERSION1_MASK SMB135X_MASK(7, 6) +#define VERSION1_SHIFT 6 +#define VERSION2_REG 0x32 +#define VERSION2_MASK SMB135X_MASK(1, 0) +#define VERSION3_REG 0x34 + +/* Irq Config registers */ +#define IRQ_CFG_REG 0x07 +#define IRQ_BAT_HOT_COLD_HARD_BIT BIT(7) +#define IRQ_BAT_HOT_COLD_SOFT_BIT BIT(6) +#define IRQ_OTG_OVER_CURRENT_BIT BIT(4) +#define IRQ_USBIN_UV_BIT BIT(2) +#define IRQ_INTERNAL_TEMPERATURE_BIT BIT(0) + +#define IRQ2_CFG_REG 0x08 +#define IRQ2_SAFETY_TIMER_BIT BIT(7) +#define IRQ2_CHG_ERR_BIT BIT(6) +#define IRQ2_CHG_PHASE_CHANGE_BIT BIT(4) +#define IRQ2_CHG_INHIBIT_BIT BIT(3) +#define IRQ2_POWER_OK_BIT BIT(2) +#define IRQ2_BATT_MISSING_BIT BIT(1) +#define IRQ2_VBAT_LOW_BIT BIT(0) + +#define IRQ3_CFG_REG 0x09 +#define IRQ3_SRC_DETECT_BIT BIT(2) +#define IRQ3_DCIN_UV_BIT BIT(0) + +/* Command Registers */ +#define CMD_I2C_REG 0x40 +#define ALLOW_VOLATILE_BIT BIT(6) + +#define CMD_INPUT_LIMIT 0x41 +#define USB_SHUTDOWN_BIT BIT(6) +#define DC_SHUTDOWN_BIT BIT(5) +#define USE_REGISTER_FOR_CURRENT BIT(2) +#define USB_100_500_AC_MASK SMB135X_MASK(1, 0) +#define USB_100_VAL 0x02 +#define USB_500_VAL 0x00 +#define USB_AC_VAL 0x01 + +#define CMD_CHG_REG 0x42 +#define CMD_CHG_EN BIT(1) +#define OTG_EN BIT(0) + +/* Status registers */ +#define STATUS_0_REG 0x46 +#define AICL_DONE_BIT BIT(7) + +#define STATUS_1_REG 0x47 +#define USING_USB_BIT BIT(1) +#define USING_DC_BIT BIT(0) + +#define STATUS_4_REG 0x4A +#define BATT_NET_CHG_CURRENT_BIT BIT(7) +#define BATT_LESS_THAN_2V BIT(4) +#define CHG_HOLD_OFF_BIT BIT(3) +#define CHG_TYPE_MASK SMB135X_MASK(2, 1) +#define CHG_TYPE_SHIFT 1 +#define BATT_NOT_CHG_VAL 0x0 +#define BATT_PRE_CHG_VAL 0x1 +#define BATT_FAST_CHG_VAL 0x2 +#define BATT_TAPER_CHG_VAL 0x3 +#define CHG_EN_BIT BIT(0) + +#define STATUS_5_REG 0x4B +#define CDP_BIT BIT(7) +#define DCP_BIT BIT(6) +#define OTHER_BIT BIT(5) +#define SDP_BIT BIT(4) +#define ACA_A_BIT BIT(3) +#define ACA_B_BIT BIT(2) +#define ACA_C_BIT BIT(1) +#define ACA_DOCK_BIT BIT(0) + +#define STATUS_6_REG 0x4D +#define HVDCP_BIT BIT(4) + +#define STATUS_8_REG 0x4E +#define USBIN_9V BIT(5) +#define USBIN_UNREG BIT(4) +#define USBIN_LV BIT(3) +#define DCIN_9V BIT(2) +#define DCIN_UNREG BIT(1) +#define DCIN_LV BIT(0) + +#define STATUS_9_REG 0x4F +#define REV_MASK SMB135X_MASK(3, 0) + +/* Irq Status registers */ +#define IRQ_A_REG 0x50 +#define IRQ_A_HOT_HARD_BIT BIT(6) +#define IRQ_A_COLD_HARD_BIT BIT(4) +#define IRQ_A_HOT_SOFT_BIT BIT(2) +#define IRQ_A_COLD_SOFT_BIT BIT(0) + +#define IRQ_B_REG 0x51 +#define IRQ_B_BATT_TERMINAL_BIT BIT(6) +#define IRQ_B_BATT_MISSING_BIT BIT(4) +#define IRQ_B_VBAT_LOW_BIT BIT(2) +#define IRQ_B_TEMPERATURE_BIT BIT(0) + +#define IRQ_C_REG 0x52 +#define IRQ_C_TERM_BIT BIT(0) +#define IRQ_C_FAST_CHG_BIT BIT(6) + +#define IRQ_D_REG 0x53 +#define IRQ_D_AICL_DONE_BIT BIT(4) +#define IRQ_D_TIMEOUT_BIT BIT(2) + +#define IRQ_E_REG 0x54 +#define IRQ_E_DC_OV_BIT BIT(6) +#define IRQ_E_DC_UV_BIT BIT(4) +#define IRQ_E_USB_OV_BIT BIT(2) +#define IRQ_E_USB_UV_BIT BIT(0) + +#define IRQ_F_REG 0x55 +#define IRQ_F_OTG_OC_BIT BIT(6) +#define IRQ_F_POWER_OK_BIT BIT(0) + +#define IRQ_G_REG 0x56 +#define IRQ_G_SRC_DETECT_BIT BIT(6) + +static int ignore_disconnect; +module_param(ignore_disconnect, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(ignore_disconnect, "Ignore Disconnects due to" + " Under/Over Voltage"); +enum { + WRKARND_USB100_BIT = BIT(0), + WRKARND_APSD_FAIL = BIT(1), +}; + +enum { + REV_1 = 1, /* Rev 1.0 */ + REV_1_1 = 2, /* Rev 1.1 */ + REV_2 = 3, /* Rev 2 */ + REV_2_1 = 5, /* Rev 2.1 */ + REV_MAX, +}; + +static char *revision_str[] = { + [REV_1] = "rev1", + [REV_1_1] = "rev1.1", + [REV_2] = "rev2", + [REV_2_1] = "rev2.1", +}; + +enum { + V_SMB1356, + V_SMB1357, + V_SMB1358, + V_SMB1359, + V_MAX, +}; + +static char *version_str[] = { + [V_SMB1356] = "smb1356", + [V_SMB1357] = "smb1357", + [V_SMB1358] = "smb1358", + [V_SMB1359] = "smb1359", +}; + +enum { + USER = BIT(0), + THERMAL = BIT(1), + CURRENT = BIT(2), +}; + +enum path_type { + USB, + DC, +}; + +static int chg_time[] = { + 192, + 384, + 768, + 1536, +}; + +struct smb135x_regulator { + struct regulator_desc rdesc; + struct regulator_dev *rdev; +}; + +struct smb_wakeup_source { + struct wakeup_source source; + unsigned long disabled; +}; + +struct smb135x_chg { + struct i2c_client *client; + struct device *dev; + struct mutex read_write_lock; + + u8 revision; + int version; + + bool chg_enabled; + + bool usb_present; + bool dc_present; + bool dc_ov; + + bool bmd_algo_disabled; + bool iterm_disabled; + int iterm_ma; + int vfloat_mv; + int safety_time; + int resume_delta_mv; + int fake_battery_soc; + struct dentry *debug_root; + int usb_current_arr_size; + int *usb_current_table; + int dc_current_arr_size; + int *dc_current_table; + u8 irq_cfg_mask[3]; + + /* psy */ + struct power_supply *usb_psy; + int usb_psy_ma; + struct power_supply batt_psy; + struct power_supply dc_psy; + struct power_supply *bms_psy; + int dc_psy_type; + int dc_psy_ma; + const char *bms_psy_name; + + /* status tracking */ + bool chg_done_batt_full; + bool batt_present; + bool batt_hot; + bool batt_cold; + bool batt_warm; + bool batt_cool; + + bool resume_completed; + bool irq_waiting; + u32 usb_suspended; + u32 dc_suspended; + struct mutex path_suspend_lock; + + u32 peek_poke_address; + struct smb135x_regulator otg_vreg; + int skip_writes; + int skip_reads; + u32 workaround_flags; + bool soft_vfloat_comp_disabled; + struct mutex irq_complete; + struct regulator *therm_bias_vreg; + struct delayed_work wireless_insertion_work; + struct delayed_work usb_insertion_work; + + int thermal_levels; + int dc_thermal_levels; + int therm_lvl_sel; + int dc_therm_lvl_sel; + unsigned int *thermal_mitigation; + unsigned int *dc_thermal_mitigation; + struct mutex current_change_lock; + struct mutex batti_change_lock; + bool factory_mode; + int batt_current_ma; + int apsd_rerun_cnt; + struct smb_wakeup_source smb_wake_source; + struct delayed_work heartbeat_work; + int ext_temp_volt_mv; + int ext_temp_soc; + int ext_high_temp; + int temp_check; + int bms_check; + unsigned long float_charge_start_time; + struct delayed_work aicl_check_work; + struct delayed_work src_removal_work; + struct delayed_work ocp_clear_work; + bool aicl_disabled; + bool aicl_weak_detect; + int charger_rate; + struct delayed_work rate_check_work; + int rate_check_count; + struct notifier_block smb_reboot; + int ir_comp_mv; + bool invalid_battery; + struct qpnp_adc_tm_btm_param vbat_monitor_params; + struct qpnp_adc_tm_chip *adc_tm_dev; + struct qpnp_vadc_chip *vadc_dev; + unsigned int low_voltage_uv; + unsigned int max_voltage_uv; + unsigned int low_gauge_mv; + bool shutdown_voltage_tripped; + bool poll_fast; + bool hvdcp_powerup; + int prev_batt_health; +}; + +static struct smb135x_chg *the_chip; + +static int smb135x_float_voltage_set(struct smb135x_chg *chip, int vfloat_mv); +static int handle_usb_removal(struct smb135x_chg *chip); +static int notify_usb_removal(struct smb135x_chg *chip); +static int smb135x_setup_vbat_monitoring(struct smb135x_chg *chip); + +static void smb_stay_awake(struct smb_wakeup_source *source) +{ + if (__test_and_clear_bit(0, &source->disabled)) { + __pm_stay_awake(&source->source); + pr_debug("enabled source %s\n", source->source.name); + } +} + +static void smb_relax(struct smb_wakeup_source *source) +{ + if (!__test_and_set_bit(0, &source->disabled)) { + __pm_relax(&source->source); + pr_debug("disabled source %s\n", source->source.name); + } +} + +static int __smb135x_read(struct smb135x_chg *chip, int reg, + u8 *val) +{ + s32 ret; + int i; + + ret = i2c_smbus_read_byte_data(chip->client, reg); + for (i = 0; ((ret < 0) && (i < 5)); i++) { + mdelay(5); + ret = i2c_smbus_read_byte_data(chip->client, reg); + } + if (ret < 0) { + dev_err(chip->dev, + "i2c read fail: can't read from %02x: %d\n", reg, ret); + return ret; + } else { + *val = ret; + } + + return 0; +} + +static int __smb135x_write(struct smb135x_chg *chip, int reg, + u8 val) +{ + s32 ret; + + if (chip->factory_mode) + return 0; + + ret = i2c_smbus_write_byte_data(chip->client, reg, val); + if (ret < 0) { + dev_err(chip->dev, + "i2c write fail: can't write %02x to %02x: %d\n", + val, reg, ret); + return ret; + } + pr_debug("Writing 0x%02x=0x%02x\n", reg, val); + return 0; +} + +static int __smb135x_write_fac(struct smb135x_chg *chip, int reg, + u8 val) +{ + s32 ret; + + ret = i2c_smbus_write_byte_data(chip->client, reg, val); + if (ret < 0) { + dev_err(chip->dev, + "i2c write fail: can't write %02x to %02x: %d\n", + val, reg, ret); + return ret; + } + pr_debug("Writing 0x%02x=0x%02x\n", reg, val); + return 0; +} + +static int smb135x_read(struct smb135x_chg *chip, int reg, + u8 *val) +{ + int rc; + + if (chip->skip_reads) { + *val = 0; + return 0; + } + mutex_lock(&chip->read_write_lock); + rc = __smb135x_read(chip, reg, val); + mutex_unlock(&chip->read_write_lock); + + return rc; +} + +static int smb135x_write(struct smb135x_chg *chip, int reg, + u8 val) +{ + int rc; + + if (chip->skip_writes) + return 0; + + mutex_lock(&chip->read_write_lock); + rc = __smb135x_write(chip, reg, val); + mutex_unlock(&chip->read_write_lock); + + return rc; +} + +static int smb135x_masked_write(struct smb135x_chg *chip, int reg, + u8 mask, u8 val) +{ + s32 rc; + u8 temp; + + if (chip->skip_writes || chip->skip_reads) + return 0; + + mutex_lock(&chip->read_write_lock); + rc = __smb135x_read(chip, reg, &temp); + if (rc < 0) { + dev_err(chip->dev, "read failed: reg=%03X, rc=%d\n", reg, rc); + goto out; + } + temp &= ~mask; + temp |= val & mask; + rc = __smb135x_write(chip, reg, temp); + if (rc < 0) { + dev_err(chip->dev, + "write failed: reg=%03X, rc=%d\n", reg, rc); + } +out: + mutex_unlock(&chip->read_write_lock); + return rc; +} + +static int smb135x_masked_write_fac(struct smb135x_chg *chip, int reg, + u8 mask, u8 val) +{ + s32 rc; + u8 temp; + + if (chip->skip_writes || chip->skip_reads) + return 0; + + mutex_lock(&chip->read_write_lock); + rc = __smb135x_read(chip, reg, &temp); + if (rc < 0) { + dev_err(chip->dev, "read failed: reg=%03X, rc=%d\n", reg, rc); + goto out; + } + temp &= ~mask; + temp |= val & mask; + rc = __smb135x_write_fac(chip, reg, temp); + if (rc < 0) { + dev_err(chip->dev, + "write failed: reg=%03X, rc=%d\n", reg, rc); + } +out: + mutex_unlock(&chip->read_write_lock); + return rc; +} + +static int is_usb_plugged_in(struct smb135x_chg *chip) +{ + int rc; + u8 reg = 0; + u8 reg2 = 0; + + rc = smb135x_read(chip, IRQ_E_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read irq E rc = %d\n", rc); + return 0; + } + + rc = smb135x_read(chip, IRQ_G_REG, ®2); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read irq G rc = %d\n", rc); + return 0; + } + + return !(reg & IRQ_E_USB_UV_BIT) || !!(reg2 & IRQ_G_SRC_DETECT_BIT); +} + +static int is_dc_plugged_in(struct smb135x_chg *chip) +{ + int rc; + u8 reg = 0; + + rc = smb135x_read(chip, IRQ_E_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read irq E rc = %d\n", rc); + return 0; + } + return !(reg & IRQ_E_DC_OV_BIT) && !(reg & IRQ_E_DC_UV_BIT); +} + +static int read_revision(struct smb135x_chg *chip, u8 *revision) +{ + int rc; + u8 reg; + + rc = smb135x_read(chip, STATUS_9_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read status 9 rc = %d\n", rc); + return rc; + } + *revision = (reg & REV_MASK); + return 0; +} + +static int read_version1(struct smb135x_chg *chip, u8 *version) +{ + int rc; + u8 reg; + + rc = smb135x_read(chip, VERSION1_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read version 1 rc = %d\n", rc); + return rc; + } + *version = (reg & VERSION1_MASK) >> VERSION1_SHIFT; + return 0; +} + +static int read_version2(struct smb135x_chg *chip, u8 *version) +{ + int rc; + u8 reg; + + rc = smb135x_read(chip, VERSION2_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read version 2 rc = %d\n", rc); + return rc; + } + *version = (reg & VERSION2_MASK); + return 0; +} + +static int read_version3(struct smb135x_chg *chip, u8 *version) +{ + int rc; + u8 reg; + + rc = smb135x_read(chip, VERSION3_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read version 3 rc = %d\n", rc); + return rc; + } + *version = reg; + return 0; +} + +#define TRIM_23_REG 0x23 +#define CHECK_USB100_GOOD_BIT BIT(1) +static bool is_usb100_broken(struct smb135x_chg *chip) +{ + int rc; + u8 reg; + + rc = smb135x_read(chip, TRIM_23_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read status 9 rc = %d\n", rc); + return rc; + } + return !!(reg & CHECK_USB100_GOOD_BIT); +} + +static char *usb_type_str[] = { + "ACA_DOCK", /* bit 0 */ + "ACA_C", /* bit 1 */ + "ACA_B", /* bit 2 */ + "ACA_A", /* bit 3 */ + "SDP", /* bit 4 */ + "OTHER", /* bit 5 */ + "DCP", /* bit 6 */ + "CDP", /* bit 7 */ + "NONE", /* bit 8 error case */ +}; + +/* helper to return the string of USB type */ +static char *get_usb_type_name(u8 stat_5) +{ + unsigned long stat = stat_5; + + return usb_type_str[find_first_bit(&stat, SMB135X_BITS_PER_REG)]; +} + +static enum power_supply_type usb_type_enum[] = { + POWER_SUPPLY_TYPE_USB_ACA, /* bit 0 */ + POWER_SUPPLY_TYPE_USB_ACA, /* bit 1 */ + POWER_SUPPLY_TYPE_USB_ACA, /* bit 2 */ + POWER_SUPPLY_TYPE_USB_ACA, /* bit 3 */ + POWER_SUPPLY_TYPE_USB, /* bit 4 */ + POWER_SUPPLY_TYPE_UNKNOWN, /* bit 5 */ + POWER_SUPPLY_TYPE_USB_DCP, /* bit 6 */ + POWER_SUPPLY_TYPE_USB_CDP, /* bit 7 */ + POWER_SUPPLY_TYPE_UNKNOWN, /* bit 8 error case, report UNKNWON */ +}; + +/* helper to return enum power_supply_type of USB type */ +static enum power_supply_type get_usb_supply_type(u8 stat_5) +{ + unsigned long stat = stat_5; + + return usb_type_enum[find_first_bit(&stat, SMB135X_BITS_PER_REG)]; +} + +static enum power_supply_property smb135x_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL, + POWER_SUPPLY_PROP_NUM_SYSTEM_TEMP_LEVELS, + POWER_SUPPLY_PROP_CHARGE_RATE, + /* Block from Fuel Gauge */ + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_HOTSPOT, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_TAPER_REACHED, + /* Notification from Fuel Gauge */ + POWER_SUPPLY_PROP_CAPACITY_LEVEL, +}; + +static int smb135x_force_apsd(struct smb135x_chg *chip) +{ + int rc; + + dev_info(chip->dev, "Start APSD Rerun!\n"); + rc = smb135x_masked_write_fac(chip, CFG_C_REG, + USBIN_VOLT_MODE_MASK, + USBIN_VOLT_MODE_5V); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set cfg C rc = %d\n", rc); + goto force_apsd_err; + } + + rc = smb135x_masked_write_fac(chip, CFG_C_REG, + USBIN_VOLT_MODE_MASK, + USBIN_VOLT_MODE_9V); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set cfg C rc = %d\n", rc); + goto force_apsd_err; + } + + rc = smb135x_masked_write_fac(chip, CFG_11_REG, + AUTO_SRC_DET_BIT, + 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set cfg 11 rc = %d\n", rc); + goto force_apsd_err; + } + + rc = smb135x_masked_write_fac(chip, CFG_11_REG, + AUTO_SRC_DET_BIT, + AUTO_SRC_DET_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set cfg 11 rc = %d\n", rc); + goto force_apsd_err; + } + + rc = smb135x_masked_write_fac(chip, CFG_C_REG, + USBIN_VOLT_MODE_MASK, + USBIN_VOLT_MODE_5V); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set cfg C rc = %d\n", rc); + goto force_apsd_err; + } + + /* RESET to Default 5V to 9V */ + rc = smb135x_masked_write_fac(chip, CFG_C_REG, + USBIN_VOLT_MODE_MASK, + USBIN_VOLT_MODE_5V_TO_9V); + if (rc < 0) + dev_err(chip->dev, "Couldn't set cfg C rc = %d\n", rc); + +force_apsd_err: + + return rc; +} + +static int smb135x_reset_vbat_monitoring(struct smb135x_chg *chip) +{ + int rc = 0; + + chip->vbat_monitor_params.state_request = ADC_TM_HIGH_LOW_THR_DISABLE; + + rc = qpnp_adc_tm_channel_measure(chip->adc_tm_dev, + &chip->vbat_monitor_params); + + if (rc) + dev_err(chip->dev, "tm disable failed: %d\n", rc); + + return rc; +} + +static int smb135x_get_prop_batt_status(struct smb135x_chg *chip, + int *batt_stat) +{ + int rc; + int status = POWER_SUPPLY_STATUS_DISCHARGING; + u8 reg = 0; + u8 chg_type; + + if ((is_usb_plugged_in(chip) || is_dc_plugged_in(chip)) && + (chip->chg_done_batt_full || chip->float_charge_start_time)) { + *batt_stat = POWER_SUPPLY_STATUS_FULL; + return 0; + } + + rc = smb135x_read(chip, STATUS_4_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Unable to read STATUS_4_REG rc = %d\n", rc); + *batt_stat = POWER_SUPPLY_STATUS_UNKNOWN; + return rc; + } + + if (reg & CHG_HOLD_OFF_BIT) { + /* + * when chg hold off happens the battery is + * not charging + */ + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + goto out; + } + + chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT; + + if (chg_type == BATT_NOT_CHG_VAL) + status = POWER_SUPPLY_STATUS_DISCHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; +out: + pr_debug("STATUS_4_REG=%x\n", reg); + *batt_stat = status; + return 0; +} + +static int smb135x_get_prop_batt_present(struct smb135x_chg *chip) +{ + int rc; + u8 reg; + bool prev_batt_status; + + prev_batt_status = chip->batt_present; + + rc = smb135x_read(chip, STATUS_4_REG, ®); + if (rc < 0) + return chip->batt_present; + + /* treat battery gone if less than 2V */ + if (reg & BATT_LESS_THAN_2V) + chip->batt_present = false; + else + chip->batt_present = true; + + if ((prev_batt_status != chip->batt_present) && + (!prev_batt_status)) + smb135x_reset_vbat_monitoring(chip); + + return chip->batt_present; +} + +static int smb135x_get_prop_charge_type(struct smb135x_chg *chip, + int *charge_type) +{ + int rc; + u8 reg; + u8 chg_type; + + rc = smb135x_read(chip, STATUS_4_REG, ®); + if (rc < 0) { + *charge_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + return rc; + } + + chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT; + if (chg_type == BATT_NOT_CHG_VAL) + *charge_type = POWER_SUPPLY_CHARGE_TYPE_NONE; + else if (chg_type == BATT_FAST_CHG_VAL) + *charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST; + else if (chg_type == BATT_PRE_CHG_VAL) + *charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + + *charge_type = POWER_SUPPLY_CHARGE_TYPE_NONE; + return 0; +} + +#define DEFAULT_BATT_CAPACITY 50 +static int smb135x_get_prop_batt_capacity(struct smb135x_chg *chip, + int *batt_cap) +{ + int rc = 0; + union power_supply_propval ret = {0, }; + + if (!chip->bms_psy && chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + + if (chip->fake_battery_soc >= 0) { + *batt_cap = chip->fake_battery_soc; + return rc; + } + + if (chip->shutdown_voltage_tripped && !chip->factory_mode) { + if ((chip->usb_psy) && (!chip->hvdcp_powerup) && + chip->usb_present) { + power_supply_set_present(chip->usb_psy, false); + power_supply_set_online(chip->usb_psy, false); + chip->usb_present = false; + } + *batt_cap = 0; + return rc; + } + + if (chip->bms_psy) { + rc = chip->bms_psy->get_property(chip->bms_psy, + POWER_SUPPLY_PROP_CAPACITY, &ret); + if (rc < 0) + ret.intval = DEFAULT_BATT_CAPACITY; + + if ((rc == 0) && (ret.intval == 0) && !chip->factory_mode) { + chip->shutdown_voltage_tripped = true; + if ((chip->usb_psy) && (!chip->hvdcp_powerup) && + chip->usb_present) { + power_supply_set_present(chip->usb_psy, false); + power_supply_set_online(chip->usb_psy, false); + chip->usb_present = false; + } + } + *batt_cap = ret.intval; + return rc; + } + *batt_cap = DEFAULT_BATT_CAPACITY; + return rc; +} + +static int smb135x_get_prop_batt_health(struct smb135x_chg *chip, + int *batt_health) +{ + if (chip->invalid_battery) + *batt_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (chip->batt_hot) + *batt_health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (chip->batt_cold) + *batt_health = POWER_SUPPLY_HEALTH_COLD; + else if (chip->batt_warm) + *batt_health = POWER_SUPPLY_HEALTH_WARM; + else if (chip->batt_cool) + *batt_health = POWER_SUPPLY_HEALTH_COOL; + else + *batt_health = POWER_SUPPLY_HEALTH_GOOD; + + return 0; +} + +static int smb135x_set_prop_batt_health(struct smb135x_chg *chip, int health) +{ + switch (health) { + case POWER_SUPPLY_HEALTH_OVERHEAT: + chip->batt_hot = true; + chip->batt_cold = false; + chip->batt_warm = false; + chip->batt_cool = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + chip->batt_cold = true; + chip->batt_hot = false; + chip->batt_warm = false; + chip->batt_cool = false; + break; + case POWER_SUPPLY_HEALTH_WARM: + chip->batt_warm = true; + chip->batt_hot = false; + chip->batt_cold = false; + chip->batt_cool = false; + break; + case POWER_SUPPLY_HEALTH_COOL: + chip->batt_cool = true; + chip->batt_hot = false; + chip->batt_cold = false; + chip->batt_warm = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + default: + chip->batt_hot = false; + chip->batt_cold = false; + chip->batt_warm = false; + chip->batt_cool = false; + } + + return 0; +} + +#define DEFAULT_BATT_VOLT_MV 4000 +static int smb135x_get_prop_batt_voltage_now(struct smb135x_chg *chip, + int *volt_mv) +{ + int rc = 0; + union power_supply_propval ret = {0, }; + + if (!chip->bms_psy && chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + + if (chip->bms_psy) { + rc = chip->bms_psy->get_property(chip->bms_psy, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &ret); + if (rc < 0) { + *volt_mv = DEFAULT_BATT_VOLT_MV; + return rc; + } + + *volt_mv = ret.intval / 1000; + return 0; + } + *volt_mv = DEFAULT_BATT_VOLT_MV; + return -EINVAL; +} + +static bool smb135x_get_prop_taper_reached(struct smb135x_chg *chip) +{ + int rc = 0; + union power_supply_propval ret = {0, }; + + if (!chip->bms_psy && chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + + if (chip->bms_psy) { + rc = chip->bms_psy->get_property(chip->bms_psy, + POWER_SUPPLY_PROP_TAPER_REACHED, &ret); + if (rc < 0) { + dev_err(chip->dev, + "couldn't read Taper Reached property, rc=%d\n", + rc); + return false; + } + + if (ret.intval) + return true; + } + return false; +} + +static int smb135x_enable_volatile_writes(struct smb135x_chg *chip) +{ + int rc; + + rc = smb135x_masked_write(chip, CMD_I2C_REG, + ALLOW_VOLATILE_BIT, ALLOW_VOLATILE_BIT); + if (rc < 0) + dev_err(chip->dev, + "Couldn't set VOLATILE_W_PERM_BIT rc=%d\n", rc); + + return rc; +} + +static int usb_current_table_smb1356[] = { + 180, + 240, + 270, + 285, + 300, + 330, + 360, + 390, + 420, + 540, + 570, + 600, + 660, + 720, + 840, + 900, + 960, + 1080, + 1110, + 1128, + 1146, + 1170, + 1182, + 1200, + 1230, + 1260, + 1380, + 1440, + 1560, + 1620, + 1680, + 1800 +}; + +static int usb_current_table_smb1357_smb1358[] = { + 300, + 400, + 450, + 475, + 500, + 550, + 600, + 650, + 700, + 900, + 950, + 1000, + 1100, + 1200, + 1400, + 1450, + 1500, + 1600, + 1800, + 1850, + 1880, + 1910, + 1930, + 1950, + 1970, + 2000, + 2050, + 2100, + 2300, + 2400, + 2500, + 3000 +}; + +static int usb_current_table_smb1359[] = { + 300, + 400, + 450, + 475, + 500, + 550, + 600, + 650, + 700, + 900, + 950, + 1000, + 1100, + 1200, + 1400, + 1450, + 1500, + 1600, + 1800, + 1850, + 1880, + 1910, + 1930, + 1950, + 1970, + 2000, + 2050, + 2100, + 2300, + 2400, + 2500 +}; + +static int dc_current_table_smb1356[] = { + 180, + 240, + 270, + 285, + 300, + 330, + 360, + 390, + 420, + 540, + 570, + 600, + 660, + 720, + 840, + 870, + 900, + 960, + 1080, + 1110, + 1128, + 1146, + 1158, + 1170, + 1182, + 1200, +}; + +static int dc_current_table[] = { + 300, + 400, + 450, + 475, + 500, + 550, + 600, + 650, + 700, + 900, + 950, + 1000, + 1100, + 1200, + 1400, + 1450, + 1500, + 1600, + 1800, + 1850, + 1880, + 1910, + 1930, + 1950, + 1970, + 2000, +}; + +#define CURRENT_100_MA 100 +#define CURRENT_150_MA 150 +#define CURRENT_500_MA 500 +#define CURRENT_900_MA 900 +#define SUSPEND_CURRENT_MA 2 + +static int __smb135x_usb_suspend(struct smb135x_chg *chip, bool suspend) +{ + int rc; + + rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USB_SHUTDOWN_BIT, suspend ? USB_SHUTDOWN_BIT : 0); + if (rc < 0) + dev_err(chip->dev, "Couldn't set cfg 11 rc = %d\n", rc); + return rc; +} + +static int __smb135x_dc_suspend(struct smb135x_chg *chip, bool suspend) +{ + int rc = 0; + + rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT, + DC_SHUTDOWN_BIT, suspend ? DC_SHUTDOWN_BIT : 0); + if (rc < 0) + dev_err(chip->dev, "Couldn't set cfg 11 rc = %d\n", rc); + return rc; +} + +static int smb135x_path_suspend(struct smb135x_chg *chip, enum path_type path, + int reason, bool suspend) +{ + int rc = 0; + int suspended; + int *path_suspended; + int (*func)(struct smb135x_chg *chip, bool suspend); + + mutex_lock(&chip->path_suspend_lock); + if (path == USB) { + suspended = chip->usb_suspended; + path_suspended = &chip->usb_suspended; + func = __smb135x_usb_suspend; + } else { + suspended = chip->dc_suspended; + path_suspended = &chip->dc_suspended; + func = __smb135x_dc_suspend; + } + + if (suspend == false) + suspended &= ~reason; + else + suspended |= reason; + + if (*path_suspended && !suspended) + rc = func(chip, 0); + if (!(*path_suspended) && suspended) + rc = func(chip, 1); + + if (rc) + dev_err(chip->dev, "Couldn't set/unset suspend for %s path rc = %d\n", + path == USB ? "usb" : "dc", + rc); + else + *path_suspended = suspended; + + mutex_unlock(&chip->path_suspend_lock); + return rc; +} +static int smb135x_temp_charging(struct smb135x_chg *chip, int enable) +{ + int rc = 0; + + pr_debug("charging enable = %d\n", enable); + + if (chip->invalid_battery) + enable = false; + + rc = smb135x_masked_write(chip, CMD_CHG_REG, + CMD_CHG_EN, enable ? CMD_CHG_EN : 0); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set CHG_ENABLE_BIT enable = %d rc = %d\n", + enable, rc); + return rc; + } + chip->chg_enabled = enable; + + return 0; +} + +static void smb135x_set_chrg_path_temp(struct smb135x_chg *chip) +{ + if (chip->batt_cool && !chip->ext_high_temp) + smb135x_float_voltage_set(chip, + chip->ext_temp_volt_mv); + else + smb135x_float_voltage_set(chip, chip->vfloat_mv); + + if (chip->ext_high_temp || + chip->batt_cold || + chip->batt_hot || + chip->chg_done_batt_full) + smb135x_temp_charging(chip, 0); + else + smb135x_temp_charging(chip, 1); +} + +static bool smb135x_is_max_thermal_level(struct smb135x_chg *chip) +{ + if (((chip->thermal_levels > 0) && + ((chip->usb_present) && + ((chip->therm_lvl_sel >= (chip->thermal_levels - 1)) || + (chip->therm_lvl_sel == -EINVAL)))) || + ((chip->dc_thermal_levels > 0) && + (chip->dc_present) && + ((chip->dc_therm_lvl_sel >= (chip->dc_thermal_levels - 1)) || + (chip->dc_therm_lvl_sel == -EINVAL)))) + return true; + else + return false; +} + +static int smb135x_check_temp_range(struct smb135x_chg *chip) +{ + int batt_volt; + int batt_soc; + int batt_health; + int ext_high_temp = 0; + + if (smb135x_get_prop_batt_voltage_now(chip, &batt_volt)) + return 0; + + if (smb135x_get_prop_batt_capacity(chip, &batt_soc)) + return 0; + + if (smb135x_get_prop_batt_health(chip, &batt_health)) + return 0; + + if (((chip->batt_cool) && + (batt_volt > chip->ext_temp_volt_mv)) || + ((chip->batt_warm) && + (batt_soc > chip->ext_temp_soc) && + (smb135x_is_max_thermal_level(chip)))) + ext_high_temp = 1; + + if ((chip->prev_batt_health == POWER_SUPPLY_HEALTH_COOL) && + (batt_health == POWER_SUPPLY_HEALTH_COOL) && + !chip->ext_high_temp) + ext_high_temp = 0; + + chip->prev_batt_health = batt_health; + + if (chip->ext_high_temp != ext_high_temp) { + chip->ext_high_temp = ext_high_temp; + + dev_warn(chip->dev, "Ext High = %s\n", + chip->ext_high_temp ? "High" : "Low"); + + return 1; + } + + return 0; +} + +static int smb135x_set_high_usb_chg_current(struct smb135x_chg *chip, + int current_ma) +{ + int i, rc; + u8 usb_cur_val; + + for (i = chip->usb_current_arr_size - 1; i >= 0; i--) { + if (current_ma >= chip->usb_current_table[i]) + break; + } + if (i < 0) { + dev_err(chip->dev, + "Cannot find %dma current_table using %d\n", + current_ma, CURRENT_150_MA); + rc = smb135x_masked_write(chip, CFG_5_REG, + USB_2_3_BIT, USB_2_3_BIT); + rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USB_100_500_AC_MASK, USB_100_VAL); + if (rc < 0) + dev_err(chip->dev, "Couldn't set %dmA rc=%d\n", + CURRENT_150_MA, rc); + return rc; + } + + usb_cur_val = i & USBIN_INPUT_MASK; + rc = smb135x_masked_write(chip, CFG_C_REG, + USBIN_INPUT_MASK, usb_cur_val); + if (rc < 0) { + dev_err(chip->dev, "cannot write to config c rc = %d\n", rc); + return rc; + } + + rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USB_100_500_AC_MASK, USB_AC_VAL); + if (rc < 0) + dev_err(chip->dev, "Couldn't write cfg 5 rc = %d\n", rc); + return rc; +} + +#define MAX_VERSION 0xF +#define USB_100_PROBLEM_VERSION 0x2 +/* if APSD results are used + * if SDP is detected it will look at 500mA setting + * if set it will draw 500mA + * if unset it will draw 100mA + * if CDP/DCP it will look at 0x0C setting + * i.e. values in 0x41[1, 0] does not matter + */ +static int smb135x_set_usb_chg_current(struct smb135x_chg *chip, + int current_ma) +{ + int rc; + + pr_debug("USB current_ma = %d\n", current_ma); + + if (chip->workaround_flags & WRKARND_USB100_BIT) { + pr_info("USB requested = %dmA using %dmA\n", current_ma, + CURRENT_500_MA); + current_ma = CURRENT_500_MA; + } + + if (current_ma == 0) + /* choose the lowest available value of 100mA */ + current_ma = CURRENT_100_MA; + + if (current_ma == SUSPEND_CURRENT_MA) { + /* force suspend bit */ + rc = smb135x_path_suspend(chip, USB, CURRENT, true); + goto out; + } + if (current_ma < CURRENT_150_MA) { + /* force 100mA */ + rc = smb135x_masked_write(chip, CFG_5_REG, USB_2_3_BIT, 0); + rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USB_100_500_AC_MASK, USB_100_VAL); + rc |= smb135x_path_suspend(chip, USB, CURRENT, false); + goto out; + } + /* specific current values */ + if (current_ma == CURRENT_150_MA) { + rc = smb135x_masked_write(chip, CFG_5_REG, + USB_2_3_BIT, USB_2_3_BIT); + rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USB_100_500_AC_MASK, USB_100_VAL); + rc |= smb135x_path_suspend(chip, USB, CURRENT, false); + goto out; + } + if (current_ma == CURRENT_500_MA) { + +#ifdef CONFIG_FORCE_FAST_CHARGE + if (force_fast_charge) + rc = smb135x_masked_write(chip, CFG_5_REG, USB_2_3_BIT, USB_2_3_BIT); + else +#endif + rc = smb135x_masked_write(chip, CFG_5_REG, USB_2_3_BIT, 0); + + rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USB_100_500_AC_MASK, USB_500_VAL); + rc |= smb135x_path_suspend(chip, USB, CURRENT, false); + goto out; + } + if (current_ma == CURRENT_900_MA) { + rc = smb135x_masked_write(chip, CFG_5_REG, + USB_2_3_BIT, USB_2_3_BIT); + rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USB_100_500_AC_MASK, USB_500_VAL); + rc |= smb135x_path_suspend(chip, USB, CURRENT, false); + goto out; + } + + rc = smb135x_set_high_usb_chg_current(chip, current_ma); + rc |= smb135x_path_suspend(chip, USB, CURRENT, false); +out: + if (rc < 0) + dev_err(chip->dev, + "Couldn't set %dmA rc = %d\n", current_ma, rc); + return rc; +} + +static int smb135x_set_dc_chg_current(struct smb135x_chg *chip, + int current_ma) +{ + int i, rc; + u8 dc_cur_val; + + for (i = chip->dc_current_arr_size - 1; i >= 0; i--) { + if (current_ma >= chip->dc_current_table[i]) + break; + } + dc_cur_val = i & DCIN_INPUT_MASK; + rc = smb135x_masked_write(chip, CFG_A_REG, + DCIN_INPUT_MASK, dc_cur_val); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set dc charge current rc = %d\n", + rc); + return rc; + } + return 0; +} + +static int smb135x_set_appropriate_current(struct smb135x_chg *chip, + enum path_type path) +{ + int therm_ma, current_ma; + int path_current = (path == USB) ? chip->usb_psy_ma : chip->dc_psy_ma; + int (*func)(struct smb135x_chg *chip, int current_ma); + int rc = 0; + + /* + * If battery is absent do not modify the current at all, these + * would be some appropriate values set by the bootloader or default + * configuration and since it is the only source of power we should + * not change it + */ + if (!chip->batt_present) { + pr_debug("ignoring current request since battery is absent\n"); + return 0; + } + + if (path == USB) { + path_current = chip->usb_psy_ma; + func = smb135x_set_usb_chg_current; + } else { + path_current = chip->dc_psy_ma; + func = smb135x_set_dc_chg_current; + if (chip->dc_psy_type == -EINVAL) + func = NULL; + } + + if ((path == DC) && + (chip->dc_thermal_mitigation) && + (chip->dc_therm_lvl_sel > 0) && + (chip->dc_therm_lvl_sel < chip->dc_thermal_levels)) + /* + * consider thermal limit only when it is active and not at + * the highest level + */ + therm_ma = chip->dc_thermal_mitigation[chip->dc_therm_lvl_sel]; + else + therm_ma = path_current; + + current_ma = min(therm_ma, path_current); + if (func != NULL) + rc = func(chip, current_ma); + if (rc < 0) + dev_err(chip->dev, "Couldn't set %s current to min(%d, %d)rc = %d\n", + path == USB ? "usb" : "dc", + therm_ma, path_current, + rc); + return rc; +} + +int batt_current_table[] = { + 300, + 400, + 450, + 475, + 500, + 550, + 600, + 650, + 700, + 900, + 950, + 1000, + 1100, + 1200, + 1400, + 1450, + 1500, + 1600, + 1800, + 1850, + 1880, + 1910, + 1930, + 1950, + 1970, + 2000, + 2050, + 2100, + 2300, + 2400, + 2500 +}; + +static int smb135x_set_batt_current(struct smb135x_chg *chip, + int current_ma) +{ + int i, rc; + u8 batt_cur_val; + + for (i = ARRAY_SIZE(batt_current_table) - 1; i >= 0; i--) { + if (current_ma >= batt_current_table[i]) + break; + } + batt_cur_val = i & BATT_CURR_MASK; + rc = smb135x_masked_write(chip, CFG_1C_REG, + BATT_CURR_MASK, batt_cur_val); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set Battery current rc = %d\n", + rc); + return rc; + } + return 0; +} + +static int __smb135x_charging(struct smb135x_chg *chip, int enable) +{ + int rc = 0; + + pr_debug("charging enable = %d\n", enable); + + if (chip->invalid_battery) + enable = false; + + rc = smb135x_masked_write(chip, CMD_CHG_REG, + CMD_CHG_EN, enable ? CMD_CHG_EN : 0); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set CHG_ENABLE_BIT enable = %d rc = %d\n", + enable, rc); + return rc; + } + chip->chg_enabled = enable; + + /* set the suspended status */ + rc = smb135x_path_suspend(chip, DC, USER, !enable); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set dc suspend to %d rc = %d\n", + enable, rc); + return rc; + } + rc = smb135x_path_suspend(chip, USB, USER, !enable); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set usb suspend to %d rc = %d\n", + enable, rc); + return rc; + } + + pr_debug("charging %s\n", + enable ? "enabled" : "disabled running from batt"); + return rc; +} + +static int smb135x_charging(struct smb135x_chg *chip, int enable) +{ + int rc = 0; + + pr_debug("charging enable = %d\n", enable); + + __smb135x_charging(chip, enable); + + if (chip->usb_psy) { + pr_debug("usb psy changed\n"); + power_supply_changed(chip->usb_psy); + } + if (chip->dc_psy_type != -EINVAL) { + pr_debug("dc psy changed\n"); + power_supply_changed(&chip->dc_psy); + } + pr_debug("charging %s\n", + enable ? "enabled" : "disabled running from batt"); + return rc; +} + +static int smb135x_system_temp_level_set(struct smb135x_chg *chip, + int lvl_sel) +{ + int rc = 0; + int prev_therm_lvl; + + if (!chip->thermal_mitigation) { + pr_err("Thermal mitigation not supported\n"); + return -EINVAL; + } + + if (lvl_sel < 0) { + pr_err("Unsupported level selected %d\n", lvl_sel); + return -EINVAL; + } + + if (lvl_sel >= chip->thermal_levels) { + pr_err("Unsupported level selected %d forcing %d\n", lvl_sel, + chip->thermal_levels - 1); + lvl_sel = chip->thermal_levels - 1; + } + + if (lvl_sel == chip->therm_lvl_sel) + return 0; + + mutex_lock(&chip->current_change_lock); + prev_therm_lvl = chip->therm_lvl_sel; + chip->therm_lvl_sel = lvl_sel; + if ((chip->therm_lvl_sel == (chip->thermal_levels - 1)) && + (chip->thermal_mitigation[lvl_sel] == 0)) { + /* + * Disable charging if highest value selected by + * setting the USB path in suspend + */ + rc = smb135x_path_suspend(chip, USB, THERMAL, true); + if (rc < 0) + dev_err(chip->dev, + "Couldn't set usb suspend rc %d\n", rc); + goto out; + } + + rc = smb135x_set_batt_current(chip, chip->thermal_mitigation[lvl_sel]); + if (rc < 0) + dev_err(chip->dev, + "Couldn't set batt current rc %d\n", rc); + + if (prev_therm_lvl == chip->thermal_levels - 1) { + /* + * If previously highest value was selected charging must have + * been disabed. Enable charging by taking the USB path out of + * suspend. + */ + rc = smb135x_path_suspend(chip, USB, THERMAL, false); + if (rc < 0) + dev_err(chip->dev, + "Couldn't set usb suspend rc %d\n", rc); + } +out: + mutex_unlock(&chip->current_change_lock); + return rc; +} + +static int smb135x_dc_system_temp_level_set(struct smb135x_chg *chip, + int lvl_sel) +{ + int rc = 0; + int prev_therm_lvl; + + if (!chip->dc_thermal_mitigation) { + pr_err("Thermal mitigation not supported\n"); + return -EINVAL; + } + + if (lvl_sel < 0) { + pr_err("Unsupported level selected %d\n", lvl_sel); + return -EINVAL; + } + + if (lvl_sel >= chip->dc_thermal_levels) { + pr_err("Unsupported DC level selected %d forcing %d\n", lvl_sel, + chip->dc_thermal_levels - 1); + lvl_sel = chip->dc_thermal_levels - 1; + } + + if (lvl_sel == chip->dc_therm_lvl_sel) + return 0; + + mutex_lock(&chip->current_change_lock); + prev_therm_lvl = chip->dc_therm_lvl_sel; + chip->dc_therm_lvl_sel = lvl_sel; + if ((chip->dc_therm_lvl_sel == (chip->dc_thermal_levels - 1)) && + (chip->dc_thermal_mitigation[lvl_sel] == 0)) { + /* + * Disable charging if highest value selected by + * setting the DC and USB path in suspend + */ + rc = smb135x_path_suspend(chip, DC, THERMAL, true); + if (rc < 0) + dev_err(chip->dev, + "Couldn't set dc suspend rc %d\n", rc); + goto out; + } + + smb135x_set_appropriate_current(chip, DC); + + if (rc < 0) + dev_err(chip->dev, + "Couldn't set dc current rc %d\n", rc); + + if (prev_therm_lvl == chip->dc_thermal_levels - 1) { + /* + * If previously highest value was selected charging must have + * been disabed. Enable charging by taking the DC out of + * suspend. + */ + rc = smb135x_path_suspend(chip, DC, THERMAL, false); + if (rc < 0) + dev_err(chip->dev, + "Couldn't set dc suspend rc %d\n", rc); + } +out: + mutex_unlock(&chip->current_change_lock); + return rc; +} + +static int smb135x_bms_set_property(struct smb135x_chg *chip, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + if (!chip->bms_psy && chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + + if (chip->bms_psy) { + chip->bms_psy->set_property(chip->bms_psy, + prop, val); + return 0; + } + + return -EINVAL; +} +static int smb135x_battery_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct smb135x_chg *chip = container_of(psy, + struct smb135x_chg, batt_psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + smb135x_charging(chip, val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY: + chip->fake_battery_soc = val->intval; + power_supply_changed(&chip->batt_psy); + break; + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + smb135x_system_temp_level_set(chip, val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + smb_stay_awake(&chip->smb_wake_source); + chip->bms_check = 1; + cancel_delayed_work(&chip->heartbeat_work); + schedule_delayed_work(&chip->heartbeat_work, + msecs_to_jiffies(0)); + break; + case POWER_SUPPLY_PROP_HEALTH: + smb_stay_awake(&chip->smb_wake_source); + smb135x_set_prop_batt_health(chip, val->intval); + smb135x_check_temp_range(chip); + smb135x_set_chrg_path_temp(chip); + chip->temp_check = 1; + cancel_delayed_work(&chip->heartbeat_work); + schedule_delayed_work(&chip->heartbeat_work, + msecs_to_jiffies(0)); + break; + /* Block from Fuel Gauge */ + case POWER_SUPPLY_PROP_TEMP_HOTSPOT: + smb135x_bms_set_property(chip, prop, val); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int smb135x_battery_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + int rc; + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + case POWER_SUPPLY_PROP_HEALTH: + case POWER_SUPPLY_PROP_TEMP_HOTSPOT: + rc = 1; + break; + default: + rc = 0; + break; + } + return rc; +} + +static int smb135x_bms_get_property(struct smb135x_chg *chip, + enum power_supply_property prop, + int *bms_prop) +{ + int rc; + union power_supply_propval ret = {0, }; + + if (!chip->bms_psy && chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + + if (chip->bms_psy) { + rc = chip->bms_psy->get_property(chip->bms_psy, + prop, &ret); + *bms_prop = ret.intval; + return rc; + } + + return -EINVAL; +} + +static int smb135x_battery_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + int stat_val; + int rc; + struct smb135x_chg *chip = container_of(psy, + struct smb135x_chg, batt_psy); + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + rc = smb135x_get_prop_batt_status(chip, &stat_val); + val->intval = stat_val; + if (rc < 0) + return rc; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = smb135x_get_prop_batt_present(chip); + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + val->intval = chip->chg_enabled; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + rc = smb135x_get_prop_charge_type(chip, &stat_val); + val->intval = stat_val; + if (rc < 0) + return rc; + break; + case POWER_SUPPLY_PROP_CAPACITY: + rc = smb135x_get_prop_batt_capacity(chip, &stat_val); + val->intval = stat_val; + if (rc < 0) + return rc; + break; + case POWER_SUPPLY_PROP_HEALTH: + rc = smb135x_get_prop_batt_health(chip, &stat_val); + val->intval = stat_val; + if (rc < 0) + return rc; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + val->intval = chip->therm_lvl_sel; + break; + case POWER_SUPPLY_PROP_NUM_SYSTEM_TEMP_LEVELS: + val->intval = chip->thermal_levels; + break; + case POWER_SUPPLY_PROP_CHARGE_RATE: + val->intval = chip->charger_rate; + break; + /* Block from Fuel Gauge */ + case POWER_SUPPLY_PROP_CYCLE_COUNT: + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + case POWER_SUPPLY_PROP_TEMP: + case POWER_SUPPLY_PROP_TEMP_HOTSPOT: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CURRENT_AVG: + case POWER_SUPPLY_PROP_TAPER_REACHED: + rc = smb135x_bms_get_property(chip, prop, &stat_val); + val->intval = stat_val; + if (rc < 0) + return rc; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = 0; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property smb135x_dc_properties[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL, + POWER_SUPPLY_PROP_NUM_SYSTEM_TEMP_LEVELS, +}; + +static int smb135x_dc_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct smb135x_chg *chip = container_of(psy, + struct smb135x_chg, dc_psy); + switch (prop) { + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + smb135x_dc_system_temp_level_set(chip, val->intval); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int smb135x_dc_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + int rc; + + switch (prop) { + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + rc = 1; + break; + default: + rc = 0; + break; + } + return rc; +} + +static int smb135x_dc_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb135x_chg *chip = container_of(psy, + struct smb135x_chg, dc_psy); + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = chip->dc_present; + if (chip->shutdown_voltage_tripped && !chip->factory_mode) + val->intval = 0; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = chip->chg_enabled ? chip->dc_present : 0; + if (chip->shutdown_voltage_tripped && !chip->factory_mode) + val->intval = 0; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = chip->dc_present; + break; + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + val->intval = chip->dc_therm_lvl_sel; + break; + case POWER_SUPPLY_PROP_NUM_SYSTEM_TEMP_LEVELS: + val->intval = chip->dc_thermal_levels; + break; + default: + return -EINVAL; + } + return 0; +} + +static void smb135x_external_power_changed(struct power_supply *psy) +{ + struct smb135x_chg *chip = container_of(psy, + struct smb135x_chg, batt_psy); + union power_supply_propval prop = {0,}; + int rc, current_limit = 0; + + if (chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + + rc = chip->usb_psy->get_property(chip->usb_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, &prop); + if (rc < 0) + dev_err(chip->dev, + "could not read USB current_max property, rc=%d\n", rc); + else + current_limit = prop.intval / 1000; + + pr_debug("current_limit = %d\n", current_limit); + + if (chip->usb_psy_ma != current_limit) { + mutex_lock(&chip->current_change_lock); + chip->usb_psy_ma = current_limit; + rc = smb135x_set_appropriate_current(chip, USB); + mutex_unlock(&chip->current_change_lock); + if (rc < 0) + dev_err(chip->dev, "Couldn't set usb current rc = %d\n", + rc); + } +} + +#define MIN_FLOAT_MV 3600 +#define MAX_FLOAT_MV 4400 + +#define MID_RANGE_FLOAT_MV_MIN 3600 +#define MID_RANGE_FLOAT_MIN_VAL 0x05 +#define MID_RANGE_FLOAT_STEP_MV 20 + +#define HIGH_RANGE_FLOAT_MIN_MV 4340 +#define HIGH_RANGE_FLOAT_MIN_VAL 0x2A +#define HIGH_RANGE_FLOAT_STEP_MV 10 + +#define VHIGH_RANGE_FLOAT_MIN_MV 4400 +#define VHIGH_RANGE_FLOAT_MIN_VAL 0x2E +#define VHIGH_RANGE_FLOAT_STEP_MV 20 +static int smb135x_float_voltage_set(struct smb135x_chg *chip, int vfloat_mv) +{ + u8 temp; + int rc; + + if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) { + dev_err(chip->dev, "bad float voltage mv =%d asked to set\n", + vfloat_mv); + vfloat_mv = 4350; + } + + if (vfloat_mv <= HIGH_RANGE_FLOAT_MIN_MV) { + /* mid range */ + temp = MID_RANGE_FLOAT_MIN_VAL + + (vfloat_mv - MID_RANGE_FLOAT_MV_MIN) + / MID_RANGE_FLOAT_STEP_MV; + } else if (vfloat_mv <= VHIGH_RANGE_FLOAT_MIN_MV) { + /* high range */ + temp = HIGH_RANGE_FLOAT_MIN_VAL + + (vfloat_mv - HIGH_RANGE_FLOAT_MIN_MV) + / HIGH_RANGE_FLOAT_STEP_MV; + } else { + /* very high range */ + temp = VHIGH_RANGE_FLOAT_MIN_VAL + + (vfloat_mv - VHIGH_RANGE_FLOAT_MIN_MV) + / VHIGH_RANGE_FLOAT_STEP_MV; + } + + rc = smb135x_write(chip, VFLOAT_REG, temp); + if (rc < 0) + rc = smb135x_write(chip, VFLOAT_REG, temp); + temp = 0; + rc = smb135x_read(chip, VFLOAT_REG, &temp); + if ((rc >= 0) && ((temp & VFLOAT_MASK) > VHIGH_RANGE_FLOAT_MIN_VAL)) { + dev_err(chip->dev, "bad float voltage set mv =%d\n", + temp); + chip->invalid_battery = true; + smb135x_temp_charging(chip, 0); + } + + return rc; +} + +static bool elapsed_msec_greater(struct timeval *start_time, + struct timeval *end_time, int ms) +{ + int msec_elapsed; + + msec_elapsed = (end_time->tv_sec - start_time->tv_sec) * 1000 + + DIV_ROUND_UP(end_time->tv_usec - start_time->tv_usec, 1000); + + return (msec_elapsed > ms); +} + +#define MAX_STEP_MS 10 +static int smb135x_chg_otg_enable(struct smb135x_chg *chip) +{ + int rc = 0; + int restart_count = 0; + struct timeval time_a, time_b, time_c, time_d; + + /* + * Workaround for a hardware bug where the OTG needs to be enabled + * disabled and enabled for it to be actually enabled. The time between + * each step should be atmost MAX_STEP_MS + * + * Note that if enable-disable executes within the timeframe + * but the final enable takes more than MAX_STEP_ME, we treat it as + * the first enable and try disabling again. We don't want + * to issue enable back to back. + * + * Notice the instances when time is captured and the successive + * steps. + * timeA-enable-timeC-disable-timeB-enable-timeD. + * When + * (timeB - timeA) < MAX_STEP_MS AND (timeC - timeD) < MAX_STEP_MS + * then it is guaranteed that the successive steps + * must have executed within MAX_STEP_MS + */ + do_gettimeofday(&time_a); +restart_from_enable: + /* first step - enable otg */ + rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, OTG_EN); + if (rc < 0) { + dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n", rc); + return rc; + } + +restart_from_disable: + /* second step - disable otg */ + do_gettimeofday(&time_c); + rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n", rc); + return rc; + } + do_gettimeofday(&time_b); + + if (elapsed_msec_greater(&time_a, &time_b, MAX_STEP_MS)) { + restart_count++; + if (restart_count > 10) { + dev_err(chip->dev, + "Couldn't enable OTG restart_count=%d\n", + restart_count); + return -EAGAIN; + } + time_a = time_b; + pr_debug("restarting from first enable\n"); + goto restart_from_enable; + } + + /* third step (first step in case of a failure) - enable otg */ + time_a = time_b; + rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, OTG_EN); + if (rc < 0) { + dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n", rc); + return rc; + } + do_gettimeofday(&time_d); + + if (elapsed_msec_greater(&time_c, &time_d, MAX_STEP_MS)) { + restart_count++; + if (restart_count > 10) { + dev_err(chip->dev, + "Couldn't enable OTG restart_count=%d\n", + restart_count); + return -EAGAIN; + } + pr_debug("restarting from disable\n"); + goto restart_from_disable; + } + return rc; +} + +static int smb135x_chg_otg_regulator_enable(struct regulator_dev *rdev) +{ + struct smb135x_chg *chip = rdev_get_drvdata(rdev); + return smb135x_chg_otg_enable(chip); +} + +static void ocp_clear_work(struct work_struct *work) +{ + struct smb135x_chg *chip = + container_of(work, struct smb135x_chg, + ocp_clear_work.work); + smb135x_chg_otg_enable(chip); +} + +static int smb135x_chg_otg_regulator_disable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smb135x_chg *chip = rdev_get_drvdata(rdev); + + cancel_delayed_work_sync(&chip->ocp_clear_work); + rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0); + if (rc < 0) + dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n", rc); + return rc; +} + +static int smb135x_chg_otg_regulator_is_enable(struct regulator_dev *rdev) +{ + int rc = 0; + u8 reg = 0; + struct smb135x_chg *chip = rdev_get_drvdata(rdev); + + rc = smb135x_read(chip, CMD_CHG_REG, ®); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read OTG enable bit rc=%d\n", rc); + return rc; + } + + return (reg & OTG_EN) ? 1 : 0; +} + +struct regulator_ops smb135x_chg_otg_reg_ops = { + .enable = smb135x_chg_otg_regulator_enable, + .disable = smb135x_chg_otg_regulator_disable, + .is_enabled = smb135x_chg_otg_regulator_is_enable, +}; + +#define SMB1356_VERSION3_BIT BIT(7) +#define SMB1357_VERSION1_VAL 0x01 +#define SMB1358_VERSION1_VAL 0x02 +#define SMB1359_VERSION1_VAL 0x00 +#define SMB1357_VERSION2_VAL 0x01 +#define SMB1358_VERSION2_VAL 0x02 +#define SMB1359_VERSION2_VAL 0x00 +static int smb135x_chip_version_and_revision(struct smb135x_chg *chip) +{ + int rc; + u8 version1, version2, version3; + + /* read the revision */ + rc = read_revision(chip, &chip->revision); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read revision rc = %d\n", rc); + return rc; + } + + if (chip->revision >= REV_MAX || revision_str[chip->revision] == NULL) { + dev_err(chip->dev, "Bad revision found = %d\n", chip->revision); + return -EINVAL; + } + + /* check if it is smb1356 */ + rc = read_version3(chip, &version3); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read version3 rc = %d\n", rc); + return rc; + } + + if (version3 & SMB1356_VERSION3_BIT) { + chip->version = V_SMB1356; + goto wrkarnd_and_input_current_values; + } + + /* check if it is smb1357, smb1358 or smb1359 based on revision */ + if (chip->revision <= REV_1_1) { + rc = read_version1(chip, &version1); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read version 1 rc = %d\n", rc); + return rc; + } + switch (version1) { + case SMB1357_VERSION1_VAL: + chip->version = V_SMB1357; + break; + case SMB1358_VERSION1_VAL: + chip->version = V_SMB1358; + break; + case SMB1359_VERSION1_VAL: + chip->version = V_SMB1359; + break; + default: + dev_err(chip->dev, + "Unknown version 1 = 0x%02x rc = %d\n", + version1, rc); + return rc; + } + } else { + rc = read_version2(chip, &version2); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read version 2 rc = %d\n", rc); + return rc; + } + switch (version2) { + case SMB1357_VERSION2_VAL: + chip->version = V_SMB1357; + break; + case SMB1358_VERSION2_VAL: + chip->version = V_SMB1358; + break; + case SMB1359_VERSION2_VAL: + chip->version = V_SMB1359; + break; + default: + dev_err(chip->dev, + "Unknown version 2 = 0x%02x rc = %d\n", + version2, rc); + return rc; + } + } + +wrkarnd_and_input_current_values: + if (is_usb100_broken(chip)) + chip->workaround_flags |= WRKARND_USB100_BIT; + /* + * Rev v1.0 and v1.1 of SMB135x fails charger type detection + * (apsd) due to interference on the D+/- lines by the USB phy. + * Set the workaround flag to disable charger type reporting + * for this revision. + */ + if (chip->revision <= REV_1_1) + chip->workaround_flags |= WRKARND_APSD_FAIL; + + pr_debug("workaround_flags = %x\n", chip->workaround_flags); + + switch (chip->version) { + case V_SMB1356: + chip->usb_current_table = usb_current_table_smb1356; + chip->usb_current_arr_size + = ARRAY_SIZE(usb_current_table_smb1356); + chip->dc_current_table = dc_current_table_smb1356; + chip->dc_current_arr_size + = ARRAY_SIZE(dc_current_table_smb1356); + break; + case V_SMB1357: + chip->usb_current_table = usb_current_table_smb1357_smb1358; + chip->usb_current_arr_size + = ARRAY_SIZE(usb_current_table_smb1357_smb1358); + chip->dc_current_table = dc_current_table; + chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table); + break; + case V_SMB1358: + chip->usb_current_table = usb_current_table_smb1357_smb1358; + chip->usb_current_arr_size + = ARRAY_SIZE(usb_current_table_smb1357_smb1358); + chip->dc_current_table = dc_current_table; + chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table); + break; + case V_SMB1359: + chip->usb_current_table = usb_current_table_smb1359; + chip->usb_current_arr_size + = ARRAY_SIZE(usb_current_table_smb1359); + chip->dc_current_table = dc_current_table; + chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table); + break; + } + + return 0; +} + +static int smb135x_regulator_init(struct smb135x_chg *chip) +{ + int rc = 0; + struct regulator_init_data *init_data; + struct regulator_config cfg = {}; + + init_data = of_get_regulator_init_data(chip->dev, chip->dev->of_node); + if (!init_data) { + dev_err(chip->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + if (init_data->constraints.name) { + chip->otg_vreg.rdesc.owner = THIS_MODULE; + chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE; + chip->otg_vreg.rdesc.ops = &smb135x_chg_otg_reg_ops; + chip->otg_vreg.rdesc.name = init_data->constraints.name; + + cfg.dev = chip->dev; + cfg.init_data = init_data; + cfg.driver_data = chip; + cfg.of_node = chip->dev->of_node; + + init_data->constraints.valid_ops_mask + |= REGULATOR_CHANGE_STATUS; + + chip->otg_vreg.rdev = regulator_register( + &chip->otg_vreg.rdesc, &cfg); + if (IS_ERR(chip->otg_vreg.rdev)) { + rc = PTR_ERR(chip->otg_vreg.rdev); + chip->otg_vreg.rdev = NULL; + if (rc != -EPROBE_DEFER) + dev_err(chip->dev, + "OTG reg failed, rc=%d\n", rc); + } + } + + return rc; +} + +static void smb135x_regulator_deinit(struct smb135x_chg *chip) +{ + if (chip->otg_vreg.rdev) + regulator_unregister(chip->otg_vreg.rdev); +} + +static void wireless_insertion_work(struct work_struct *work) +{ + struct smb135x_chg *chip = + container_of(work, struct smb135x_chg, + wireless_insertion_work.work); + + /* unsuspend dc */ + smb135x_path_suspend(chip, DC, CURRENT, false); +} + +static int drop_usbin_rate(struct smb135x_chg *chip) +{ + int rc; + u8 reg; + + rc = smb135x_read(chip, CFG_C_REG, ®); + if (rc < 0) + dev_err(chip->dev, "Failed to Read CFG C\n"); + + reg = reg & USBIN_INPUT_MASK; + if (reg > 0) + reg--; + dev_warn(chip->dev, "Input current Set 0x%x\n", reg); + rc = smb135x_masked_write(chip, + CFG_C_REG, USBIN_INPUT_MASK, + reg); + if (rc < 0) + dev_err(chip->dev, "Couldn't Lower Input Rate\n"); + + return reg; +} + +static void aicl_check_work(struct work_struct *work) +{ + struct smb135x_chg *chip = + container_of(work, struct smb135x_chg, + aicl_check_work.work); + int rc; + + dev_dbg(chip->dev, "Drop Rate!\n"); + + rc = drop_usbin_rate(chip); + if (!chip->aicl_weak_detect && (rc < 0x02)) + chip->aicl_weak_detect = true; + + cancel_delayed_work(&chip->src_removal_work); + schedule_delayed_work(&chip->src_removal_work, + msecs_to_jiffies(3000)); + if (!rc) { + dev_dbg(chip->dev, "Reached Bottom IC!\n"); + return; + } + + rc = smb135x_masked_write(chip, + IRQ_CFG_REG, + IRQ_USBIN_UV_BIT, + IRQ_USBIN_UV_BIT); + if (rc < 0) + dev_err(chip->dev, "Couldn't Unmask USBIN UV IRQ\n"); +} + +static void src_removal_work(struct work_struct *work) +{ + struct smb135x_chg *chip = + container_of(work, struct smb135x_chg, + src_removal_work.work); + bool usb_present = is_usb_plugged_in(chip); + if (chip->usb_present && !usb_present) { + /* USB removed */ + chip->usb_present = usb_present; + if (!usb_present && !is_dc_plugged_in(chip)) { + chip->chg_done_batt_full = false; + chip->float_charge_start_time = 0; + } + notify_usb_removal(chip); + } +} + +static int smb135x_get_charge_rate(struct smb135x_chg *chip) +{ + u8 reg; + int rc; + + if (!is_usb_plugged_in(chip)) + return POWER_SUPPLY_CHARGE_RATE_NONE; + + if (chip->aicl_weak_detect) + return POWER_SUPPLY_CHARGE_RATE_WEAK; + + rc = smb135x_read(chip, STATUS_6_REG, ®); + if (rc < 0) + return POWER_SUPPLY_CHARGE_RATE_NORMAL; + + if (reg & HVDCP_BIT) + return POWER_SUPPLY_CHARGE_RATE_TURBO; + + return POWER_SUPPLY_CHARGE_RATE_NORMAL; +} + +#define HVDCP_INPUT_CURRENT_MAX 1600 +static void rate_check_work(struct work_struct *work) +{ + struct smb135x_chg *chip = + container_of(work, struct smb135x_chg, + rate_check_work.work); + + chip->charger_rate = smb135x_get_charge_rate(chip); + + if (chip->charger_rate > POWER_SUPPLY_CHARGE_RATE_NORMAL) { + pr_warn("Charger Rate = %d\n", chip->charger_rate); + + if (chip->charger_rate == + POWER_SUPPLY_CHARGE_RATE_TURBO) { + mutex_lock(&chip->current_change_lock); + chip->usb_psy_ma = HVDCP_INPUT_CURRENT_MAX; + smb135x_set_appropriate_current(chip, USB); + mutex_unlock(&chip->current_change_lock); + } + + chip->rate_check_count = 0; + power_supply_changed(&chip->batt_psy); + return; + } + + chip->rate_check_count++; + if (chip->rate_check_count < 6) + schedule_delayed_work(&chip->rate_check_work, + msecs_to_jiffies(500)); +} + +static void usb_insertion_work(struct work_struct *work) +{ + struct smb135x_chg *chip = + container_of(work, struct smb135x_chg, + usb_insertion_work.work); + int rc; + + rc = smb135x_force_apsd(chip); + if (rc < 0) + dev_err(chip->dev, "Couldn't rerun apsd rc = %d\n", rc); + + smb_relax(&chip->smb_wake_source); +} + +static void toggle_usbin_aicl(struct smb135x_chg *chip) +{ + int rc; + + /* Set AICL OFF */ + rc = smb135x_masked_write(chip, CFG_D_REG, AICL_ENABLE, 0); + if (rc < 0) + dev_err(chip->dev, "Couldn't disable AICL\n"); + + /* Set AICL ON */ + rc = smb135x_masked_write(chip, CFG_D_REG, AICL_ENABLE, AICL_ENABLE); + if (rc < 0) + dev_err(chip->dev, "Couldn't enable AICL\n"); +} + +#define FLOAT_CHG_TIME_SECS 1800 +#define INPUT_CURR_CHECK_THRES 0x0C /* 1100 mA */ +static void heartbeat_work(struct work_struct *work) +{ + u8 reg; + int rc; + struct timespec bootup_time; + unsigned long float_timestamp; + bool usb_present; + bool dc_present; + int batt_health; + int batt_soc; + bool taper_reached; + struct smb135x_chg *chip = + container_of(work, struct smb135x_chg, + heartbeat_work.work); + bool poll_status = chip->poll_fast; + + if (smb135x_get_prop_batt_capacity(chip, &batt_soc) || + smb135x_get_prop_batt_health(chip, &batt_health)) { + schedule_delayed_work(&chip->heartbeat_work, + msecs_to_jiffies(1000)); + return; + } + + dev_dbg(chip->dev, "HB Pound!\n"); + cancel_delayed_work(&chip->src_removal_work); + + get_monotonic_boottime(&bootup_time); + float_timestamp = bootup_time.tv_sec; + + if (batt_soc < 20) + chip->poll_fast = true; + else + chip->poll_fast = false; + + if (poll_status != chip->poll_fast) + smb135x_setup_vbat_monitoring(chip); + + usb_present = is_usb_plugged_in(chip); + dc_present = is_dc_plugged_in(chip); + taper_reached = smb135x_get_prop_taper_reached(chip); + + if (chip->usb_present && !usb_present) { + dev_warn(chip->dev, "HB Caught Removal!\n"); + /* USB removed */ + chip->usb_present = usb_present; + if (!is_dc_plugged_in(chip)) { + chip->chg_done_batt_full = false; + chip->float_charge_start_time = 0; + } + notify_usb_removal(chip); + } else if (usb_present) { + if (!chip->aicl_disabled) { + rc = smb135x_read(chip, STATUS_0_REG, ®); + if (rc < 0) { + pr_info("Failed to Read Status 0x46\n"); + } else if (!reg) { + dev_warn(chip->dev, "HB Caught Low Rate!\n"); + toggle_usbin_aicl(chip); + } + } else { + rc = smb135x_read(chip, CFG_C_REG, ®); + if (rc < 0) + dev_err(chip->dev, "Failed to Read CFG C\n"); + + reg = reg & USBIN_INPUT_MASK; + if (reg < INPUT_CURR_CHECK_THRES) { + dev_warn(chip->dev, "Increase Input Rate\n"); + rc = smb135x_masked_write(chip, + IRQ_CFG_REG, + IRQ_USBIN_UV_BIT, + IRQ_USBIN_UV_BIT); + if (rc < 0) + dev_err(chip->dev, + "Failed to Write CFG Reg\n"); + smb135x_set_appropriate_current(chip, USB); + } + } + + if (!chip->chg_done_batt_full && + !chip->float_charge_start_time && + chip->iterm_disabled && + taper_reached) { + chip->float_charge_start_time = float_timestamp; + dev_warn(chip->dev, "Float Start!\n"); + } else if (chip->float_charge_start_time && + ((float_timestamp - chip->float_charge_start_time) + >= FLOAT_CHG_TIME_SECS)) { + chip->float_charge_start_time = 0; + chip->chg_done_batt_full = true; + dev_warn(chip->dev, "Float Done!\n"); + } else if (chip->chg_done_batt_full && (batt_soc < 100)) { + chip->chg_done_batt_full = false; + dev_warn(chip->dev, "SOC dropped, Charge Resume!\n"); + } + } + + if ((batt_health == POWER_SUPPLY_HEALTH_WARM) || + (batt_health == POWER_SUPPLY_HEALTH_COOL) || + (batt_health == POWER_SUPPLY_HEALTH_OVERHEAT) || + (batt_health == POWER_SUPPLY_HEALTH_COLD)) + chip->temp_check = smb135x_check_temp_range(chip); + + smb135x_set_chrg_path_temp(chip); + + if (chip->temp_check || + chip->bms_check || + chip->chg_done_batt_full || + chip->float_charge_start_time) { + chip->temp_check = 0; + chip->bms_check = 0; + } + + power_supply_changed(&chip->batt_psy); + + schedule_delayed_work(&chip->heartbeat_work, + msecs_to_jiffies(60000)); + + if (!usb_present && !dc_present) + smb_relax(&chip->smb_wake_source); +} + +static int hot_hard_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + chip->batt_hot = !!rt_stat; + return 0; +} +static int cold_hard_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + chip->batt_cold = !!rt_stat; + return 0; +} +static int hot_soft_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + chip->batt_warm = !!rt_stat; + return 0; +} +static int cold_soft_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + chip->batt_cool = !!rt_stat; + return 0; +} +static int battery_missing_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + chip->batt_present = !rt_stat; + return 0; +} +static int vbat_low_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_warn("vbat low\n"); + return 0; +} +static int chg_hot_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_warn("chg hot\n"); + return 0; +} +static int chg_term_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + if (!chip->iterm_disabled) + chip->chg_done_batt_full = !!rt_stat; + return 0; +} + +static int taper_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + return 0; +} + +static int fast_chg_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + if (rt_stat & IRQ_C_FAST_CHG_BIT) { + chip->chg_done_batt_full = false; + chip->float_charge_start_time = 0; + } + power_supply_changed(&chip->batt_psy); + return 0; +} + +static int recharge_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + return 0; +} + +static int safety_timeout_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_warn("safety timeout rt_stat = 0x%02x\n", rt_stat); + return 0; +} + +static int aicl_done_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + if (rt_stat & IRQ_D_AICL_DONE_BIT) + pr_warn("aicl done rt_stat = 0x%02x Rate = %d\n", rt_stat, + chip->charger_rate); + + return 0; +} + +/** + * power_ok_handler() - called when the switcher turns on or turns off + * @chip: pointer to smb135x_chg chip + * @rt_stat: the status bit indicating switcher turning on or off + */ +static int power_ok_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + return 0; +} + +#define MAX_OTG_RETRY 3 +static int otg_oc_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + if (!(rt_stat & IRQ_F_OTG_OC_BIT)) { + pr_err("Spurious OTG OC irq\n"); + return 0; + } + + schedule_delayed_work(&chip->ocp_clear_work, + msecs_to_jiffies(0)); + + pr_err("rt_stat = 0x%02x\n", rt_stat); + return 0; +} + +static int handle_dc_removal(struct smb135x_chg *chip) +{ + if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS) { + cancel_delayed_work_sync(&chip->wireless_insertion_work); + smb135x_path_suspend(chip, DC, CURRENT, true); + } + + if (chip->dc_psy_type != -EINVAL) + power_supply_set_online(&chip->dc_psy, chip->dc_present); + + smb_relax(&chip->smb_wake_source); + return 0; +} + +#define DCIN_UNSUSPEND_DELAY_MS 1000 +static int handle_dc_insertion(struct smb135x_chg *chip) +{ + if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS) + schedule_delayed_work(&chip->wireless_insertion_work, + msecs_to_jiffies(DCIN_UNSUSPEND_DELAY_MS)); + if (chip->dc_psy_type != -EINVAL) + power_supply_set_online(&chip->dc_psy, + chip->dc_present); + + smb_stay_awake(&chip->smb_wake_source); + return 0; +} +/** + * dcin_uv_handler() - called when the dc voltage crosses the uv threshold + * @chip: pointer to smb135x_chg chip + * @rt_stat: the status bit indicating whether dc voltage is uv + */ +static int dcin_uv_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + /* + * rt_stat indicates if dc is undervolted. If so dc_present + * should be marked removed + */ + bool dc_present = !rt_stat; + + pr_debug("chip->dc_present = %d dc_present = %d\n", + chip->dc_present, dc_present); + + if (chip->dc_present && !dc_present) { + /* dc removed */ + chip->dc_present = dc_present; + if (!dc_present && !is_usb_plugged_in(chip)) { + chip->chg_done_batt_full = false; + chip->float_charge_start_time = 0; + } + handle_dc_removal(chip); + } + + if (!chip->dc_present && dc_present) { + /* dc inserted */ + chip->dc_present = dc_present; + handle_dc_insertion(chip); + } + + return 0; +} + +static int dcin_ov_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + /* + * rt_stat indicates if dc is overvolted. If so dc_present + * should be marked removed + */ + bool dc_present = !rt_stat; + + pr_debug("chip->dc_present = %d dc_present = %d\n", + chip->dc_present, dc_present); + + chip->dc_ov = !!rt_stat; + + if (chip->dc_present && !dc_present) { + /* dc removed */ + chip->dc_present = dc_present; + handle_dc_removal(chip); + } + + if (!chip->dc_present && dc_present) { + /* dc inserted */ + chip->dc_present = dc_present; + handle_dc_insertion(chip); + } + return 0; +} + +static int handle_usb_removal(struct smb135x_chg *chip) +{ + int rc; + cancel_delayed_work(&chip->usb_insertion_work); + cancel_delayed_work(&chip->aicl_check_work); + cancel_delayed_work(&chip->rate_check_work); + chip->apsd_rerun_cnt = 0; + chip->aicl_weak_detect = false; + chip->charger_rate = POWER_SUPPLY_CHARGE_RATE_NONE; + + if (chip->hvdcp_powerup) + chip->hvdcp_powerup = false; + + rc = smb135x_masked_write(chip, + IRQ_CFG_REG, + IRQ_USBIN_UV_BIT, + IRQ_USBIN_UV_BIT); + if (rc < 0) + dev_err(chip->dev, + "Failed to Write CFG Reg\n"); + + if (!chip->aicl_disabled) { + /* Set AICL Glich to 20ms */ + rc = smb135x_masked_write(chip, CFG_D_REG, AICL_GLITCH, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set 20 ms AICL glitch\n"); + return rc; + } + } + + if (chip->usb_psy) { + pr_debug("setting usb psy type = %d\n", + POWER_SUPPLY_TYPE_UNKNOWN); + power_supply_set_supply_type(chip->usb_psy, + POWER_SUPPLY_TYPE_UNKNOWN); + pr_debug("setting usb psy present = %d\n", chip->usb_present); + power_supply_set_present(chip->usb_psy, chip->usb_present); + } + + smb_relax(&chip->smb_wake_source); + return 0; +} + +static int notify_usb_removal(struct smb135x_chg *chip) +{ + int rc; + + rc = handle_usb_removal(chip); + + if (rc >= 0) { + if (chip->usb_psy) + power_supply_changed(chip->usb_psy); + power_supply_changed(&chip->batt_psy); + } + + return rc; +} + +static int handle_usb_insertion(struct smb135x_chg *chip) +{ + u8 reg; + int rc; + char *usb_type_name = "null"; + enum power_supply_type usb_supply_type; + + /* usb inserted */ + rc = smb135x_read(chip, STATUS_5_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read status 5 rc = %d\n", rc); + return rc; + } + + cancel_delayed_work(&chip->usb_insertion_work); + + /* Rerun APSD 1 sec later */ + if ((reg & SDP_BIT) && !chip->apsd_rerun_cnt) { + dev_info(chip->dev, "HW Detected SDP!\n"); + smb_stay_awake(&chip->smb_wake_source); + chip->apsd_rerun_cnt++; + chip->usb_present = 0; + schedule_delayed_work(&chip->usb_insertion_work, + msecs_to_jiffies(1000)); + return 0; + } + + chip->apsd_rerun_cnt = 0; + + if (!chip->aicl_disabled) { + /* Set AICL Glich to 15 us */ + rc = smb135x_masked_write(chip, + CFG_D_REG, + AICL_GLITCH, + AICL_GLITCH); + if (rc < 0) + dev_err(chip->dev, "Couldn't set 15 us AICL glitch\n"); + } + + /* + * Report the charger type as UNKNOWN if the + * apsd-fail flag is set. This nofifies the USB driver + * to initiate a s/w based charger type detection. + */ + if (chip->workaround_flags & WRKARND_APSD_FAIL) + reg = 0; + + chip->aicl_weak_detect = false; + usb_type_name = get_usb_type_name(reg); + usb_supply_type = get_usb_supply_type(reg); + pr_debug("inserted %s, usb psy type = %d stat_5 = 0x%02x\n", + usb_type_name, usb_supply_type, reg); + if (chip->usb_psy) { + pr_debug("setting usb psy type = %d\n", usb_supply_type); + power_supply_set_supply_type(chip->usb_psy, usb_supply_type); + pr_debug("setting usb psy present = %d\n", chip->usb_present); + power_supply_set_present(chip->usb_psy, chip->usb_present); + } + smb_stay_awake(&chip->smb_wake_source); + + chip->charger_rate = POWER_SUPPLY_CHARGE_RATE_NORMAL; + chip->rate_check_count = 0; + cancel_delayed_work(&chip->rate_check_work); + schedule_delayed_work(&chip->rate_check_work, + msecs_to_jiffies(500)); + return 0; +} + +/** + * usbin_uv_handler() - this is called when USB charger is removed + * @chip: pointer to smb135x_chg chip + * @rt_stat: the status bit indicating chg insertion/removal + */ +static int usbin_uv_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + u8 reg; + int rc; + /* + * rt_stat indicates if usb is undervolted. If so usb_present + * should be marked removed + */ + bool usb_present = !rt_stat; + + pr_debug("chip->usb_present = %d usb_present = %d\n", + chip->usb_present, usb_present); + + if (ignore_disconnect) { + pr_info("Ignore usbin_uv - usb_present = %d\n", usb_present); + return 0; + } + + if (is_usb_plugged_in(chip)) { + if (!chip->aicl_disabled) { + rc = smb135x_read(chip, STATUS_0_REG, ®); + if (rc < 0) + pr_err("Failed to Read Status 0x46\n"); + else if (!reg) + toggle_usbin_aicl(chip); + } else if (!usb_present) { + rc = smb135x_masked_write(chip, + IRQ_CFG_REG, + IRQ_USBIN_UV_BIT, 0); + if (rc < 0) + pr_err("Failed to Disable USBIN UV IRQ\n"); + cancel_delayed_work(&chip->aicl_check_work); + schedule_delayed_work(&chip->aicl_check_work, + msecs_to_jiffies(0)); + } + return 0; + } + + if (chip->usb_present && !usb_present) { + /* USB removed */ + chip->usb_present = usb_present; + if (!usb_present && !is_dc_plugged_in(chip)) { + chip->chg_done_batt_full = false; + chip->float_charge_start_time = 0; + } + notify_usb_removal(chip); + } + return 0; +} + +static int usbin_ov_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + /* + * rt_stat indicates if usb is overvolted. If so usb_present + * should be marked removed + */ + bool usb_present = !rt_stat; + int health; + + pr_info("chip->usb_present = %d usb_present = %d\n", + chip->usb_present, usb_present); + + if (ignore_disconnect) { + pr_info("Ignore usbin_ov - usb_present = %d\n", usb_present); + return 0; + } + + if (is_usb_plugged_in(chip) && !chip->usb_present && usb_present) { + /* USB inserted */ + chip->usb_present = usb_present; + handle_usb_insertion(chip); + } else if (chip->usb_present && !usb_present) { + /* USB removed */ + chip->usb_present = usb_present; + if (!is_dc_plugged_in(chip)) { + chip->chg_done_batt_full = false; + chip->float_charge_start_time = 0; + } + notify_usb_removal(chip); + } + + if (chip->usb_psy) { + health = rt_stat ? POWER_SUPPLY_HEALTH_OVERVOLTAGE + : POWER_SUPPLY_HEALTH_GOOD; + power_supply_set_health_state(chip->usb_psy, health); + } + + return 0; +} + +static void smb135x_notify_vbat(enum qpnp_tm_state state, void *ctx) +{ + struct smb135x_chg *chip = ctx; + struct qpnp_vadc_result result; + int batt_volt; + int rc; + + pr_err("shutdown voltage tripped\n"); + + rc = qpnp_vadc_read(chip->vadc_dev, VSYS, &result); + pr_info("vbat = %lld, raw = 0x%x\n", + result.physical, result.adc_code); + + smb135x_get_prop_batt_voltage_now(chip, &batt_volt); + pr_info("vbat is at %d, state is at %d\n", + batt_volt, state); + + if (state == ADC_TM_LOW_STATE) { + if (batt_volt < chip->low_gauge_mv) { + chip->shutdown_voltage_tripped = true; + } else { + usleep_range(2000, 2100); + rc = qpnp_vadc_read(chip->vadc_dev, VSYS, &result); + pr_info("VSYS = %lld, raw = 0x%x\n", + result.physical, result.adc_code); + if (result.physical < chip->low_voltage_uv) { + chip->shutdown_voltage_tripped = true; + } else { + qpnp_adc_tm_channel_measure(chip->adc_tm_dev, + &chip->vbat_monitor_params); + } + } + } + else + qpnp_adc_tm_channel_measure(chip->adc_tm_dev, + &chip->vbat_monitor_params); + + power_supply_changed(&chip->batt_psy); +} + +static int smb135x_setup_vbat_monitoring(struct smb135x_chg *chip) +{ + int rc; + + chip->vbat_monitor_params.low_thr = chip->low_voltage_uv; + chip->vbat_monitor_params.high_thr = chip->max_voltage_uv * 2; + + chip->vbat_monitor_params.state_request = ADC_TM_HIGH_LOW_THR_ENABLE; + chip->vbat_monitor_params.channel = VSYS; + chip->vbat_monitor_params.btm_ctx = (void *)chip; + + if (chip->poll_fast) { /* the adc polling rate is higher*/ + chip->vbat_monitor_params.timer_interval = + ADC_MEAS1_INTERVAL_31P3MS; + } else /* adc polling rate is default*/ { + chip->vbat_monitor_params.timer_interval = + ADC_MEAS1_INTERVAL_1S; + } + + chip->vbat_monitor_params.threshold_notification = &smb135x_notify_vbat; + dev_info(chip->dev, "set low thr to %d and high to %d\n", + chip->vbat_monitor_params.low_thr, + chip->vbat_monitor_params.high_thr); + + if (!smb135x_get_prop_batt_present(chip)) { + pr_info("no battery inserted, vbat monitoring disabled\n"); + chip->vbat_monitor_params.state_request = + ADC_TM_HIGH_LOW_THR_DISABLE; + } else { + rc = qpnp_adc_tm_channel_measure(chip->adc_tm_dev, + &chip->vbat_monitor_params); + if (rc) { + dev_err(chip->dev, "tm setup failed: %d\n", rc); + return rc; + } + } + + pr_info("vbat monitoring setup complete\n"); + return 0; +} + +/** + * src_detect_handler() - this is called when USB charger type is detected, use + * it for handling USB charger insertion/removal + * @chip: pointer to smb135x_chg chip + * @rt_stat: the status bit indicating chg insertion/removal + */ +static int src_detect_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + bool usb_present = !!rt_stat; + + pr_info("chip->usb_present = %d usb_present = %d\n", + chip->usb_present, usb_present); + + if (!chip->usb_present && usb_present) { + /* USB inserted */ + chip->usb_present = usb_present; + handle_usb_insertion(chip); + } else if (chip->usb_present && !usb_present) { + /* USB removed */ + chip->usb_present = usb_present; + if (!is_dc_plugged_in(chip)) + chip->chg_done_batt_full = false; + notify_usb_removal(chip); + } + + return 0; +} + +static int chg_inhibit_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + /* + * charger is inserted when the battery voltage is high + * so h/w won't start charging just yet. Treat this as + * battery full + */ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + if (!chip->iterm_disabled) + chip->chg_done_batt_full = !!rt_stat; + return 0; +} + +struct smb_irq_info { + const char *name; + int (*smb_irq)(struct smb135x_chg *chip, + u8 rt_stat); + int high; + int low; +}; + +struct irq_handler_info { + u8 stat_reg; + u8 val; + u8 prev_val; + struct smb_irq_info irq_info[4]; +}; + +static struct irq_handler_info handlers[] = { + {IRQ_A_REG, 0, 0, + { + { + .name = "cold_soft", + .smb_irq = cold_soft_handler, + }, + { + .name = "hot_soft", + .smb_irq = hot_soft_handler, + }, + { + .name = "cold_hard", + .smb_irq = cold_hard_handler, + }, + { + .name = "hot_hard", + .smb_irq = hot_hard_handler, + }, + }, + }, + {IRQ_B_REG, 0, 0, + { + { + .name = "chg_hot", + .smb_irq = chg_hot_handler, + }, + { + .name = "vbat_low", + .smb_irq = vbat_low_handler, + }, + { + .name = "battery_missing", + .smb_irq = battery_missing_handler, + }, + { + .name = "battery_missing", + .smb_irq = battery_missing_handler, + }, + }, + }, + {IRQ_C_REG, 0, 0, + { + { + .name = "chg_term", + .smb_irq = chg_term_handler, + }, + { + .name = "taper", + .smb_irq = taper_handler, + }, + { + .name = "recharge", + .smb_irq = recharge_handler, + }, + { + .name = "fast_chg", + .smb_irq = fast_chg_handler, + }, + }, + }, + {IRQ_D_REG, 0, 0, + { + { + .name = "prechg_timeout", + }, + { + .name = "safety_timeout", + .smb_irq = safety_timeout_handler, + }, + { + .name = "aicl_done", + .smb_irq = aicl_done_handler, + }, + { + .name = "battery_ov", + }, + }, + }, + {IRQ_E_REG, 0, 0, + { + { + .name = "usbin_uv", + .smb_irq = usbin_uv_handler, + }, + { + .name = "usbin_ov", + .smb_irq = usbin_ov_handler, + }, + { + .name = "dcin_uv", + .smb_irq = dcin_uv_handler, + }, + { + .name = "dcin_ov", + .smb_irq = dcin_ov_handler, + }, + }, + }, + {IRQ_F_REG, 0, 0, + { + { + .name = "power_ok", + .smb_irq = power_ok_handler, + }, + { + .name = "unused", + }, + { + .name = "otg_fail", + }, + { + .name = "otg_oc", + .smb_irq = otg_oc_handler, + }, + }, + }, + {IRQ_G_REG, 0, 0, + { + { + .name = "chg_inhibit", + .smb_irq = chg_inhibit_handler, + }, + { + .name = "chg_error", + }, + { + .name = "wd_timeout", + }, + { + .name = "src_detect", + .smb_irq = src_detect_handler, + }, + }, + }, +}; + +static int smb135x_irq_read(struct smb135x_chg *chip) +{ + int rc, i; + + /* + * When dcin path is suspended the irq triggered status is not cleared + * causing a storm. To prevent this situation unsuspend dcin path while + * reading interrupts and restore its status back. + */ + mutex_lock(&chip->path_suspend_lock); + + if (chip->dc_suspended) + __smb135x_dc_suspend(chip, false); + + for (i = 0; i < ARRAY_SIZE(handlers); i++) { + rc = smb135x_read(chip, handlers[i].stat_reg, + &handlers[i].val); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read %d rc = %d\n", + handlers[i].stat_reg, rc); + handlers[i].val = 0; + continue; + } + } + + if (chip->dc_suspended) + __smb135x_dc_suspend(chip, true); + + mutex_unlock(&chip->path_suspend_lock); + + return rc; +} +#define IRQ_LATCHED_MASK 0x02 +#define IRQ_STATUS_MASK 0x01 +#define BITS_PER_IRQ 2 +static irqreturn_t smb135x_chg_stat_handler(int irq, void *dev_id) +{ + struct smb135x_chg *chip = dev_id; + int i, j; + u8 triggered; + u8 changed; + u8 rt_stat, prev_rt_stat; + int rc; + int handler_count = 0; + + mutex_lock(&chip->irq_complete); + chip->irq_waiting = true; + if (!chip->resume_completed) { + dev_dbg(chip->dev, "IRQ triggered before device-resume\n"); + disable_irq_nosync(irq); + mutex_unlock(&chip->irq_complete); + return IRQ_HANDLED; + } + chip->irq_waiting = false; + + smb135x_irq_read(chip); + for (i = 0; i < ARRAY_SIZE(handlers); i++) { + for (j = 0; j < ARRAY_SIZE(handlers[i].irq_info); j++) { + triggered = handlers[i].val + & (IRQ_LATCHED_MASK << (j * BITS_PER_IRQ)); + rt_stat = handlers[i].val + & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ)); + prev_rt_stat = handlers[i].prev_val + & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ)); + changed = prev_rt_stat ^ rt_stat; + + if (triggered || changed) + rt_stat ? handlers[i].irq_info[j].high++ : + handlers[i].irq_info[j].low++; + + if ((triggered || changed) + && handlers[i].irq_info[j].smb_irq != NULL) { + handler_count++; + rc = handlers[i].irq_info[j].smb_irq(chip, + rt_stat); + if (rc < 0) + dev_err(chip->dev, + "Couldn't handle %d irq for reg 0x%02x rc = %d\n", + j, handlers[i].stat_reg, rc); + } + } + handlers[i].prev_val = handlers[i].val; + } + + pr_debug("handler count = %d\n", handler_count); + + if (handler_count && !chip->aicl_disabled) { + pr_debug("batt psy changed\n"); + power_supply_changed(&chip->batt_psy); + if (chip->usb_psy) { + pr_debug("usb psy changed\n"); + power_supply_changed(chip->usb_psy); + } + if (chip->dc_psy_type != -EINVAL) { + pr_debug("dc psy changed\n"); + power_supply_changed(&chip->dc_psy); + } + } + + mutex_unlock(&chip->irq_complete); + + return IRQ_HANDLED; +} + +#define LAST_CNFG_REG 0x1F +static int show_cnfg_regs(struct seq_file *m, void *data) +{ + struct smb135x_chg *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = 0; addr <= LAST_CNFG_REG; addr++) { + rc = smb135x_read(chip, addr, ®); + if (!rc) + seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); + } + + return 0; +} + +static int cnfg_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb135x_chg *chip = inode->i_private; + + return single_open(file, show_cnfg_regs, chip); +} + +static const struct file_operations cnfg_debugfs_ops = { + .owner = THIS_MODULE, + .open = cnfg_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define FIRST_CMD_REG 0x40 +#define LAST_CMD_REG 0x42 +static int show_cmd_regs(struct seq_file *m, void *data) +{ + struct smb135x_chg *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) { + rc = smb135x_read(chip, addr, ®); + if (!rc) + seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); + } + + return 0; +} + +static int cmd_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb135x_chg *chip = inode->i_private; + + return single_open(file, show_cmd_regs, chip); +} + +static const struct file_operations cmd_debugfs_ops = { + .owner = THIS_MODULE, + .open = cmd_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define FIRST_STATUS_REG 0x46 +#define LAST_STATUS_REG 0x56 +static int show_status_regs(struct seq_file *m, void *data) +{ + struct smb135x_chg *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) { + rc = smb135x_read(chip, addr, ®); + if (!rc) + seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); + } + + return 0; +} + +static int status_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb135x_chg *chip = inode->i_private; + + return single_open(file, show_status_regs, chip); +} + +static const struct file_operations status_debugfs_ops = { + .owner = THIS_MODULE, + .open = status_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int show_irq_count(struct seq_file *m, void *data) +{ + int i, j, total = 0; + + for (i = 0; i < ARRAY_SIZE(handlers); i++) + for (j = 0; j < 4; j++) { + seq_printf(m, "%s=%d\t(high=%d low=%d)\n", + handlers[i].irq_info[j].name, + handlers[i].irq_info[j].high + + handlers[i].irq_info[j].low, + handlers[i].irq_info[j].high, + handlers[i].irq_info[j].low); + total += (handlers[i].irq_info[j].high + + handlers[i].irq_info[j].low); + } + + seq_printf(m, "\n\tTotal = %d\n", total); + + return 0; +} + +static int irq_count_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb135x_chg *chip = inode->i_private; + + return single_open(file, show_irq_count, chip); +} + +static const struct file_operations irq_count_debugfs_ops = { + .owner = THIS_MODULE, + .open = irq_count_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int get_reg(void *data, u64 *val) +{ + struct smb135x_chg *chip = data; + int rc; + u8 temp; + + rc = smb135x_read(chip, chip->peek_poke_address, &temp); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read reg %x rc = %d\n", + chip->peek_poke_address, rc); + return -EAGAIN; + } + *val = temp; + return 0; +} + +static int set_reg(void *data, u64 val) +{ + struct smb135x_chg *chip = data; + int rc; + u8 temp; + + temp = (u8) val; + rc = __smb135x_write_fac(chip, chip->peek_poke_address, temp); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't write 0x%02x to 0x%02x rc= %d\n", + chip->peek_poke_address, temp, rc); + return -EAGAIN; + } + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(poke_poke_debug_ops, get_reg, set_reg, "0x%02llx\n"); + +static int force_irq_set(void *data, u64 val) +{ + struct smb135x_chg *chip = data; + + smb135x_chg_stat_handler(chip->client->irq, data); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n"); + +static int force_rechg_set(void *data, u64 val) +{ + int rc; + struct smb135x_chg *chip = data; + + if (!chip->chg_enabled) { + pr_debug("Charging Disabled force recharge not allowed\n"); + return -EINVAL; + } + + rc = smb135x_masked_write(chip, CFG_14_REG, EN_CHG_INHIBIT_BIT, 0); + if (rc) + dev_err(chip->dev, + "Couldn't disable charge-inhibit rc=%d\n", rc); + /* delay for charge-inhibit to take affect */ + msleep(500); + rc |= smb135x_charging(chip, false); + rc |= smb135x_charging(chip, true); + rc |= smb135x_masked_write(chip, CFG_14_REG, EN_CHG_INHIBIT_BIT, + EN_CHG_INHIBIT_BIT); + if (rc) + dev_err(chip->dev, + "Couldn't enable charge-inhibit rc=%d\n", rc); + + return rc; +} +DEFINE_SIMPLE_ATTRIBUTE(force_rechg_ops, NULL, force_rechg_set, "0x%02llx\n"); + +#ifdef DEBUG +static void dump_regs(struct smb135x_chg *chip) +{ + int rc; + u8 reg; + u8 addr; + + for (addr = 0; addr <= LAST_CNFG_REG; addr++) { + rc = smb135x_read(chip, addr, ®); + if (rc < 0) + dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n", + addr, rc); + else + pr_debug("0x%02x = 0x%02x\n", addr, reg); + } + + for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) { + rc = smb135x_read(chip, addr, ®); + if (rc < 0) + dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n", + addr, rc); + else + pr_debug("0x%02x = 0x%02x\n", addr, reg); + } + + for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) { + rc = smb135x_read(chip, addr, ®); + if (rc < 0) + dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n", + addr, rc); + else + pr_debug("0x%02x = 0x%02x\n", addr, reg); + } +} +#else +static void dump_regs(struct smb135x_chg *chip) +{ +} +#endif +static int determine_initial_status(struct smb135x_chg *chip) +{ + int rc; + u8 reg; + + /* + * It is okay to read the interrupt status here since + * interrupts aren't requested. reading interrupt status + * clears the interrupt so be careful to read interrupt + * status only in interrupt handling code + */ + + chip->batt_present = true; + rc = smb135x_read(chip, IRQ_B_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read irq b rc = %d\n", rc); + return rc; + } + if (reg & IRQ_B_BATT_TERMINAL_BIT || reg & IRQ_B_BATT_MISSING_BIT) + chip->batt_present = false; + rc = smb135x_read(chip, STATUS_4_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read status 4 rc = %d\n", rc); + return rc; + } + /* treat battery gone if less than 2V */ + if (reg & BATT_LESS_THAN_2V) + chip->batt_present = false; + + rc = smb135x_read(chip, IRQ_A_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read irq A rc = %d\n", rc); + return rc; + } + + if (reg & IRQ_A_HOT_HARD_BIT) + chip->batt_hot = true; + if (reg & IRQ_A_COLD_HARD_BIT) + chip->batt_cold = true; + if (reg & IRQ_A_HOT_SOFT_BIT) + chip->batt_warm = true; + if (reg & IRQ_A_COLD_SOFT_BIT) + chip->batt_cool = true; + + rc = smb135x_read(chip, IRQ_C_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read irq A rc = %d\n", rc); + return rc; + } + if (reg & IRQ_C_TERM_BIT) + chip->chg_done_batt_full = true; + + rc = smb135x_read(chip, IRQ_E_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read irq E rc = %d\n", rc); + return rc; + } + chip->usb_present = !(reg & IRQ_E_USB_OV_BIT) + && !(reg & IRQ_E_USB_UV_BIT); + chip->dc_present = !(reg & IRQ_E_DC_OV_BIT) && !(reg & IRQ_E_DC_UV_BIT); + + if (chip->usb_present) { + handle_usb_insertion(chip); + if (smb135x_get_charge_rate(chip) == + POWER_SUPPLY_CHARGE_RATE_TURBO) + chip->hvdcp_powerup = true; + } else + handle_usb_removal(chip); + + if (chip->dc_psy_type != -EINVAL) { + if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS) { + /* + * put the dc path in suspend state if it is powered + * by wireless charger + */ + if (chip->dc_present) + smb135x_path_suspend(chip, DC, CURRENT, false); + else + smb135x_path_suspend(chip, DC, CURRENT, true); + } + } + return 0; +} + +static int smb135x_hw_init(struct smb135x_chg *chip) +{ + int rc; + int i; + u8 reg, mask; + + if (chip->therm_bias_vreg) { + rc = regulator_enable(chip->therm_bias_vreg); + if (rc) { + pr_err("Couldn't enable therm-bias rc = %d\n", rc); + return rc; + } + } + + rc = smb135x_enable_volatile_writes(chip); + if (rc < 0) { + dev_err(chip->dev, "Couldn't configure for volatile rc = %d\n", + rc); + return rc; + } + + /* + * force using current from the register i.e. ignore auto + * power source detect (APSD) mA ratings + */ + mask = USE_REGISTER_FOR_CURRENT; + + if (chip->workaround_flags & WRKARND_USB100_BIT) + reg = 0; + else + /* this ignores APSD results */ + reg = USE_REGISTER_FOR_CURRENT; + + if (chip->aicl_disabled) { + /* Disable AICL */ + rc = smb135x_masked_write(chip, CFG_D_REG, AICL_ENABLE, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't Disable AICL\n"); + return rc; + } + } else { + /* Set AICL Glich to 20 ms */ + rc = smb135x_masked_write(chip, CFG_D_REG, AICL_GLITCH, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set 20 ms AICL glitch\n"); + return rc; + } + } + + rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT, mask, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set input limit cmd rc=%d\n", rc); + return rc; + } + + /* set bit 0 = 100mA bit 1 = 500mA and set register control */ + rc = smb135x_masked_write(chip, CFG_E_REG, + POLARITY_100_500_BIT | USB_CTRL_BY_PIN_BIT, + POLARITY_100_500_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set usbin cfg rc=%d\n", rc); + return rc; + } + + /* + * set chg en by cmd register, set chg en by writing bit 1, + * enable auto pre to fast, enable current termination, enable + * auto recharge, disable chg inhibition + */ + rc = smb135x_masked_write(chip, CFG_14_REG, + CHG_EN_BY_PIN_BIT | CHG_EN_ACTIVE_LOW_BIT + | PRE_TO_FAST_REQ_CMD_BIT | DISABLE_AUTO_RECHARGE_BIT + | EN_CHG_INHIBIT_BIT, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set cfg 14 rc=%d\n", rc); + return rc; + } + + /* control USB suspend via command bits */ + rc = smb135x_masked_write(chip, USBIN_DCIN_CFG_REG, + USBIN_SUSPEND_VIA_COMMAND_BIT, USBIN_SUSPEND_VIA_COMMAND_BIT); + + /* Set the OTG Current Limit */ + rc = smb135x_masked_write(chip, USBIN_DCIN_CFG_REG, + OTG_CURRENT_LIMIT_MASK, OTG_CURRENT_LIMIT_1000MA); + + /* set the float voltage */ + if (chip->vfloat_mv != -EINVAL) { + rc = smb135x_float_voltage_set(chip, chip->vfloat_mv); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set float voltage rc = %d\n", rc); + return rc; + } + } + + /* set Battery Current */ + if (chip->batt_current_ma != -EINVAL) { + rc = smb135x_set_batt_current(chip, chip->batt_current_ma); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set Battery Current rc = %d\n", rc); + return rc; + } + } + + /* set iterm */ + if (chip->iterm_ma != -EINVAL) { + if (chip->iterm_disabled) { + dev_err(chip->dev, "Error: Both iterm_disabled and iterm_ma set\n"); + return -EINVAL; + } else { + if (chip->iterm_ma <= 50) + reg = CHG_ITERM_50MA; + else if (chip->iterm_ma <= 100) + reg = CHG_ITERM_100MA; + else if (chip->iterm_ma <= 150) + reg = CHG_ITERM_150MA; + else if (chip->iterm_ma <= 200) + reg = CHG_ITERM_200MA; + else if (chip->iterm_ma <= 250) + reg = CHG_ITERM_250MA; + else if (chip->iterm_ma <= 300) + reg = CHG_ITERM_300MA; + else if (chip->iterm_ma <= 500) + reg = CHG_ITERM_500MA; + else + reg = CHG_ITERM_600MA; + + rc = smb135x_masked_write(chip, CFG_3_REG, + CHG_ITERM_MASK, reg); + if (rc) { + dev_err(chip->dev, + "Couldn't set iterm rc = %d\n", rc); + return rc; + } + + rc = smb135x_masked_write(chip, CFG_14_REG, + DISABLE_CURRENT_TERM_BIT, 0); + if (rc) { + dev_err(chip->dev, + "Couldn't enable iterm rc = %d\n", rc); + return rc; + } + } + } else if (chip->iterm_disabled) { + rc = smb135x_masked_write(chip, CFG_14_REG, + DISABLE_CURRENT_TERM_BIT, + DISABLE_CURRENT_TERM_BIT); + if (rc) { + dev_err(chip->dev, "Couldn't set iterm rc = %d\n", + rc); + return rc; + } + } + + /* set ir comp */ + if (chip->ir_comp_mv != -EINVAL) { + if (chip->ir_comp_mv == 0) + reg = CHG_IR_COMP_DIS; + else if (chip->ir_comp_mv <= 25) + reg = CHG_IR_COMP_25MV; + else if (chip->ir_comp_mv <= 50) + reg = CHG_IR_COMP_50MV; + else if (chip->ir_comp_mv <= 75) + reg = CHG_IR_COMP_75MV; + else if (chip->ir_comp_mv <= 100) + reg = CHG_IR_COMP_100MV; + else if (chip->ir_comp_mv <= 125) + reg = CHG_IR_COMP_125MV; + else if (chip->ir_comp_mv <= 150) + reg = CHG_IR_COMP_150MV; + else + reg = CHG_IR_COMP_175MV; + + rc = smb135x_masked_write(chip, CFG_3_REG, + CHG_IR_COMP_MASK, reg); + if (rc) { + dev_err(chip->dev, + "Couldn't set ir comp rc = %d\n", rc); + return rc; + } + } + + /* set the safety time voltage */ + if (chip->safety_time != -EINVAL) { + if (chip->safety_time == 0) { + /* safety timer disabled */ + rc = smb135x_masked_write(chip, CFG_16_REG, + SAFETY_TIME_EN_BIT, 0); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't disable safety timer rc = %d\n", + rc); + return rc; + } + } else { + for (i = 0; i < ARRAY_SIZE(chg_time); i++) { + if (chip->safety_time <= chg_time[i]) { + reg = i << SAFETY_TIME_MINUTES_SHIFT; + break; + } + } + rc = smb135x_masked_write(chip, CFG_16_REG, + SAFETY_TIME_EN_BIT | SAFETY_TIME_MINUTES_MASK, + SAFETY_TIME_EN_BIT | reg); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set safety timer rc = %d\n", + rc); + return rc; + } + } + } + + /* battery missing detection */ + rc = smb135x_masked_write(chip, CFG_19_REG, + BATT_MISSING_ALGO_BIT | BATT_MISSING_THERM_BIT, + chip->bmd_algo_disabled ? BATT_MISSING_THERM_BIT : + BATT_MISSING_ALGO_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set batt_missing config = %d\n", + rc); + return rc; + } + + __smb135x_charging(chip, chip->chg_enabled); + + /* interrupt enabling - active low */ + if (chip->client->irq) { + mask = CHG_STAT_IRQ_ONLY_BIT | CHG_STAT_ACTIVE_HIGH_BIT + | CHG_STAT_DISABLE_BIT; + reg = CHG_STAT_IRQ_ONLY_BIT; + rc = smb135x_masked_write(chip, CFG_17_REG, mask, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set irq config rc = %d\n", + rc); + return rc; + } + + /* enabling only interesting interrupts */ + rc = smb135x_write(chip, IRQ_CFG_REG, + IRQ_BAT_HOT_COLD_HARD_BIT + | IRQ_BAT_HOT_COLD_SOFT_BIT + | IRQ_OTG_OVER_CURRENT_BIT + | IRQ_INTERNAL_TEMPERATURE_BIT + | IRQ_USBIN_UV_BIT); + + rc |= smb135x_write(chip, IRQ2_CFG_REG, + IRQ2_SAFETY_TIMER_BIT + | IRQ2_CHG_ERR_BIT + | IRQ2_CHG_PHASE_CHANGE_BIT + | IRQ2_BATT_MISSING_BIT + | IRQ2_VBAT_LOW_BIT); + + rc |= smb135x_write(chip, IRQ3_CFG_REG, IRQ3_SRC_DETECT_BIT + | IRQ3_DCIN_UV_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set irq enable rc = %d\n", + rc); + return rc; + } + } + + /* resume threshold */ + if (chip->resume_delta_mv != -EINVAL) { + if (chip->resume_delta_mv < 100) + reg = CHG_INHIBIT_50MV_VAL; + else if (chip->resume_delta_mv < 200) + reg = CHG_INHIBIT_100MV_VAL; + else if (chip->resume_delta_mv < 300) + reg = CHG_INHIBIT_200MV_VAL; + else + reg = CHG_INHIBIT_300MV_VAL; + + rc = smb135x_masked_write(chip, CFG_4_REG, + CHG_INHIBIT_MASK, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set inhibit val rc = %d\n", + rc); + return rc; + } + + if (chip->resume_delta_mv < 200) + reg = 0; + else + reg = RECHARGE_200MV_BIT; + + rc = smb135x_masked_write(chip, CFG_5_REG, + RECHARGE_200MV_BIT, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set recharge rc = %d\n", + rc); + return rc; + } + } + + /* DC path current settings */ + if (chip->dc_psy_type != -EINVAL) { + rc = smb135x_set_dc_chg_current(chip, chip->dc_psy_ma); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set dc charge current rc = %d\n", + rc); + return rc; + } + + /* Configure Command mode for DCIN */ + rc = smb135x_masked_write(chip, CFG_18_REG, + DCIN_CMD_BIT, 0); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set dc command mode rc = %d\n", + rc); + return rc; + } + + /* Configure DCIN 5V to 9V */ + rc = smb135x_masked_write(chip, CFG_A_REG, + DCIN_VOLT_SEL, 0x40); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set dc voltage mode rc = %d\n", + rc); + return rc; + } + + /* Configure DCIN AICL */ + rc = smb135x_masked_write(chip, CFG_B_REG, + DCIN_AICL_BIT, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set dc AICL rc = %d\n", + rc); + return rc; + } + } + + /* + * on some devices the battery is powered via external sources which + * could raise its voltage above the float voltage. smb135x chips go + * in to reverse boost in such a situation and the workaround is to + * disable float voltage compensation (note that the battery will appear + * hot/cold when powered via external source). + */ + + if (chip->soft_vfloat_comp_disabled) { + mask = HOT_SOFT_VFLOAT_COMP_EN_BIT + | COLD_SOFT_VFLOAT_COMP_EN_BIT; + rc = smb135x_masked_write(chip, CFG_1A_REG, mask, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't disable soft vfloat rc = %d\n", + rc); + return rc; + } + } + + return rc; +} + +static int smb135x_hw_init_fac(struct smb135x_chg *chip) +{ + int rc; + + pr_info("Factory Mode I2C Writes Disabled!\n"); + rc = smb135x_masked_write_fac(chip, CMD_I2C_REG, + ALLOW_VOLATILE_BIT, + ALLOW_VOLATILE_BIT); + if (rc < 0) + dev_err(chip->dev, + "Couldn't configure for volatile rc = %d\n", + rc); + /* interrupt enabling - active low */ + if (chip->client->irq) { + rc = smb135x_masked_write_fac(chip, CFG_17_REG, + CHG_STAT_IRQ_ONLY_BIT | + CHG_STAT_ACTIVE_HIGH_BIT + | CHG_STAT_DISABLE_BIT, + CHG_STAT_IRQ_ONLY_BIT); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set irq config rc = %d\n", + rc); + return rc; + } + + /* enabling only interesting interrupts */ + rc = __smb135x_write_fac(chip, IRQ_CFG_REG, + IRQ_BAT_HOT_COLD_HARD_BIT + | IRQ_BAT_HOT_COLD_SOFT_BIT + | IRQ_INTERNAL_TEMPERATURE_BIT + | IRQ_USBIN_UV_BIT); + + rc |= __smb135x_write_fac(chip, IRQ2_CFG_REG, + IRQ2_SAFETY_TIMER_BIT + | IRQ2_CHG_ERR_BIT + | IRQ2_CHG_PHASE_CHANGE_BIT + | IRQ2_BATT_MISSING_BIT + | IRQ2_VBAT_LOW_BIT); + + rc |= __smb135x_write_fac(chip, IRQ3_CFG_REG, + IRQ3_SRC_DETECT_BIT + | IRQ3_DCIN_UV_BIT); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set irq enable rc = %d\n", + rc); + return rc; + } + } + + if (chip->dc_psy_type != -EINVAL) { + rc = smb135x_masked_write_fac(chip, CFG_A_REG, + DCIN_INPUT_MASK, + DCIN_INPUT_FAC); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set dc current rc = %d\n", + rc); + return rc; + } + + /* Configure Command mode for DCIN */ + rc = smb135x_masked_write_fac(chip, CFG_18_REG, + DCIN_CMD_BIT, 0); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set dc command mode rc = %d\n", + rc); + return rc; + } + + /* Configure DCIN 5V to 9V */ + rc = smb135x_masked_write_fac(chip, CFG_A_REG, + DCIN_VOLT_SEL, 0x40); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set dc voltage mode rc = %d\n", + rc); + return rc; + } + + /* Configure DCIN AICL */ + rc = smb135x_masked_write_fac(chip, CFG_B_REG, + DCIN_AICL_BIT, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set dc AICL rc = %d\n", + rc); + return rc; + } + } + return rc; +} + +static struct of_device_id smb135x_match_table[] = { + { .compatible = "qcom,smb1356-charger", }, + { .compatible = "qcom,smb1357-charger", }, + { .compatible = "qcom,smb1358-charger", }, + { .compatible = "qcom,smb1359-charger", }, + { }, +}; + +#define DC_MA_MIN 300 +#define DC_MA_MAX 2000 +static int smb_parse_dt(struct smb135x_chg *chip) +{ + int rc; + struct device_node *node = chip->dev->of_node; + const struct of_device_id *match; + const char *dc_psy_type; + + if (!node) { + dev_err(chip->dev, "device tree info. missing\n"); + return -EINVAL; + } + + match = of_match_node(smb135x_match_table, node); + if (match == NULL) { + dev_err(chip->dev, "device tree match not found\n"); + return -EINVAL; + } + + chip->usb_current_arr_size = (int)match->data; + + rc = of_property_read_u32(node, "qcom,float-voltage-mv", + &chip->vfloat_mv); + if (rc < 0) + chip->vfloat_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,max-batt-curr-ma", + &chip->batt_current_ma); + if (rc < 0) + chip->batt_current_ma = -EINVAL; + + rc = of_property_read_u32(node, "qcom,charging-timeout", + &chip->safety_time); + if (rc < 0) + chip->safety_time = -EINVAL; + + if (!rc && + (chip->safety_time > chg_time[ARRAY_SIZE(chg_time) - 1])) { + dev_err(chip->dev, "Bad charging-timeout %d\n", + chip->safety_time); + return -EINVAL; + } + + chip->bmd_algo_disabled = of_property_read_bool(node, + "qcom,bmd-algo-disabled"); + + chip->dc_psy_type = -EINVAL; + dc_psy_type = of_get_property(node, "qcom,dc-psy-type", NULL); + if (dc_psy_type) { + if (strcmp(dc_psy_type, "Mains") == 0) + chip->dc_psy_type = POWER_SUPPLY_TYPE_MAINS; + else if (strcmp(dc_psy_type, "Wireless") == 0) + chip->dc_psy_type = POWER_SUPPLY_TYPE_WIRELESS; + } + + if (chip->dc_psy_type != -EINVAL) { + rc = of_property_read_u32(node, "qcom,dc-psy-ma", + &chip->dc_psy_ma); + if (rc < 0) { + dev_err(chip->dev, + "no mA current for dc rc = %d\n", rc); + return rc; + } + + if (chip->dc_psy_ma < DC_MA_MIN + || chip->dc_psy_ma > DC_MA_MAX) { + dev_err(chip->dev, "Bad dc mA %d\n", chip->dc_psy_ma); + return -EINVAL; + } + } + + rc = of_property_read_u32(node, "qcom,recharge-thresh-mv", + &chip->resume_delta_mv); + if (rc < 0) + chip->resume_delta_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,iterm-ma", &chip->iterm_ma); + if (rc < 0) + chip->iterm_ma = -EINVAL; + + rc = of_property_read_u32(node, "qcom,ir-comp-mv", &chip->ir_comp_mv); + if (rc < 0) + chip->ir_comp_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,ext-temp-volt-mv", + &chip->ext_temp_volt_mv); + + rc = of_property_read_u32(node, "qcom,ext-temp-volt-mv", + &chip->ext_temp_volt_mv); + if (rc < 0) + chip->ext_temp_volt_mv = 0; + + rc = of_property_read_u32(node, "qcom,ext-temp-soc", + &chip->ext_temp_soc); + if (rc < 0) + chip->ext_temp_soc = 0; + + chip->aicl_disabled = of_property_read_bool(node, + "qcom,aicl-disabled"); + + rc = of_property_read_u32(node, "qcom,low-voltage-uv", + &chip->low_voltage_uv); + if (rc < 0) + chip->low_voltage_uv = 2750000; + + rc = of_property_read_u32(node, "qcom,max-voltage-uv", + &chip->max_voltage_uv); + if (rc < 0) + chip->max_voltage_uv = 4350000; + + rc = of_property_read_u32(node, "qcom,low-gauge-mv", + &chip->low_gauge_mv); + if (rc < 0) + chip->low_gauge_mv = 2800; + chip->iterm_disabled = of_property_read_bool(node, + "qcom,iterm-disabled"); + + chip->chg_enabled = !(of_property_read_bool(node, + "qcom,charging-disabled")); + + rc = of_property_read_string(node, "qcom,bms-psy-name", + &chip->bms_psy_name); + if (rc) + chip->bms_psy_name = NULL; + + chip->soft_vfloat_comp_disabled = of_property_read_bool(node, + "qcom,soft-vfloat-comp-disabled"); + + if (of_find_property(node, "therm-bias-supply", NULL)) { + /* get the thermistor bias regulator */ + chip->therm_bias_vreg = devm_regulator_get(chip->dev, + "therm-bias"); + if (IS_ERR(chip->therm_bias_vreg)) + return PTR_ERR(chip->therm_bias_vreg); + } + + if (of_find_property(node, "qcom,thermal-mitigation", + &chip->thermal_levels)) { + chip->thermal_mitigation = devm_kzalloc(chip->dev, + chip->thermal_levels, + GFP_KERNEL); + + if (chip->thermal_mitigation == NULL) { + pr_err("thermal mitigation kzalloc() failed.\n"); + return -ENOMEM; + } + + chip->thermal_levels /= sizeof(int); + rc = of_property_read_u32_array(node, + "qcom,thermal-mitigation", + chip->thermal_mitigation, chip->thermal_levels); + if (rc) { + pr_err("Couldn't read threm limits rc = %d\n", rc); + return rc; + } + } else + chip->thermal_levels = 0; + + if (of_find_property(node, "qcom,dc-thermal-mitigation", + &chip->dc_thermal_levels)) { + chip->dc_thermal_mitigation = devm_kzalloc(chip->dev, + chip->dc_thermal_levels, + GFP_KERNEL); + + if (chip->dc_thermal_mitigation == NULL) { + pr_err("DC thermal mitigation kzalloc() failed.\n"); + return -ENOMEM; + } + + chip->dc_thermal_levels /= sizeof(int); + rc = of_property_read_u32_array(node, + "qcom,dc-thermal-mitigation", + chip->dc_thermal_mitigation, + chip->dc_thermal_levels); + if (rc) { + pr_err("Couldn't read DC therm limits rc = %d\n", rc); + return rc; + } + } else + chip->dc_thermal_levels = 0; + + return 0; +} + +#define CHG_SHOW_MAX_SIZE 50 +#define USB_SUSPEND_BIT BIT(4) +static ssize_t force_chg_usb_suspend_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long r; + unsigned long mode; + + r = kstrtoul(buf, 0, &mode); + if (r) { + pr_err("Invalid usb suspend mode value = %lu\n", mode); + return -EINVAL; + } + + if (!the_chip) { + pr_err("chip not valid\n"); + return -ENODEV; + } + + r = smb135x_masked_write_fac(the_chip, CFG_11_REG, + USB_SUSPEND_BIT, + mode ? USB_SUSPEND_BIT : 0); + + return r ? r : count; +} + +#define USB_SUSPEND_STATUS_BIT BIT(3) +static ssize_t force_chg_usb_suspend_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int state; + int ret; + u8 value; + + if (!the_chip) { + pr_err("chip not valid\n"); + return -ENODEV; + } + + ret = smb135x_read(the_chip, STATUS_1_REG, &value); + if (ret) { + pr_err("USB_SUSPEND_STATUS_BIT failed ret = %d\n", ret); + state = -EFAULT; + goto end; + } + + state = (USB_SUSPEND_STATUS_BIT & value) ? 1 : 0; + +end: + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state); +} + +static DEVICE_ATTR(force_chg_usb_suspend, 0664, + force_chg_usb_suspend_show, + force_chg_usb_suspend_store); + +static ssize_t force_chg_fail_clear_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long r; + unsigned long mode; + + r = kstrtoul(buf, 0, &mode); + if (r) { + pr_err("Invalid chg fail mode value = %lu\n", mode); + return -EINVAL; + } + + /* do nothing for SMB135X */ + r = 0; + + return r ? r : count; +} + +static ssize_t force_chg_fail_clear_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + /* do nothing for SMB135X */ + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "0\n"); +} + +static DEVICE_ATTR(force_chg_fail_clear, 0664, + force_chg_fail_clear_show, + force_chg_fail_clear_store); + +static ssize_t force_chg_auto_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long r; + unsigned long mode; + + r = kstrtoul(buf, 0, &mode); + if (r) { + pr_err("Invalid chrg enable value = %lu\n", mode); + return -EINVAL; + } + + if (!the_chip) { + pr_err("chip not valid\n"); + return -ENODEV; + } + + r = smb135x_masked_write_fac(the_chip, CMD_CHG_REG, + CMD_CHG_EN, mode ? 0 : CMD_CHG_EN); + if (r < 0) { + dev_err(the_chip->dev, + "Couldn't set CHG_ENABLE_BIT enable = %d r = %d\n", + (int)mode, (int)r); + return r; + } + + return r ? r : count; +} + +static ssize_t force_chg_auto_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int state; + int ret; + u8 value; + + if (!the_chip) { + pr_err("chip not valid\n"); + state = -ENODEV; + goto end; + } + + ret = smb135x_read(the_chip, STATUS_4_REG, &value); + if (ret) { + pr_err("CHG_EN_BIT failed ret = %d\n", ret); + state = -EFAULT; + goto end; + } + + state = (CHG_EN_BIT & value) ? 1 : 0; + +end: + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state); +} + +static DEVICE_ATTR(force_chg_auto_enable, 0664, + force_chg_auto_enable_show, + force_chg_auto_enable_store); + +#define MAX_IBATT_LEVELS 31 +static ssize_t force_chg_ibatt_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long r; + unsigned long chg_current; + int i; + + r = kstrtoul(buf, 0, &chg_current); + if (r) { + pr_err("Invalid ibatt value = %lu\n", chg_current); + return -EINVAL; + } + + if (!the_chip) { + pr_err("chip not valid\n"); + return -ENODEV; + } + + for (i = MAX_IBATT_LEVELS - 1; i >= 0; i--) { + if (chg_current >= batt_current_table[i]) + break; + } + + r = smb135x_masked_write_fac(the_chip, CFG_1C_REG, + BATT_CURR_MASK, i); + if (r < 0) { + dev_err(the_chip->dev, + "Couldn't set Fast Charge Current = %d r = %d\n", + (int)chg_current, (int)r); + return r; + } + + return r ? r : count; +} + +static ssize_t force_chg_ibatt_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int state; + int ret; + u8 value; + + if (!the_chip) { + pr_err("chip not valid\n"); + state = -ENODEV; + goto end; + } + + ret = smb135x_read(the_chip, 0x49, &value); + if (ret || + ((value & SMB135X_MASK(4, 0)) > (MAX_IBATT_LEVELS - 1))) { + pr_err("Fast Charge Current failed ret = %d\n", ret); + state = -EFAULT; + goto end; + } + + state = batt_current_table[(value & SMB135X_MASK(4, 0))]; + +end: + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state); +} + +static DEVICE_ATTR(force_chg_ibatt, 0664, + force_chg_ibatt_show, + force_chg_ibatt_store); + +static ssize_t force_chg_iusb_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long r; + unsigned long usb_curr; + int i; + + r = kstrtoul(buf, 0, &usb_curr); + if (r) { + pr_err("Invalid iusb value = %lu\n", usb_curr); + return -EINVAL; + } + + if (!the_chip) { + pr_err("chip not valid\n"); + return -ENODEV; + } + + for (i = the_chip->usb_current_arr_size - 1; i >= 0; i--) { + if (usb_curr >= the_chip->usb_current_table[i]) + break; + } + + r = smb135x_masked_write_fac(the_chip, CFG_C_REG, + USBIN_INPUT_MASK, i); + if (r < 0) { + dev_err(the_chip->dev, + "Couldn't set USBIN Current = %d r = %d\n", + (int)usb_curr, (int)r); + return r; + } + return r ? r : count; +} + +static ssize_t force_chg_iusb_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int state; + int ret; + u8 value; + + if (!the_chip) { + pr_err("chip not valid\n"); + ret = -ENODEV; + goto end; + } + + ret = smb135x_read(the_chip, 0x46, &value); + if (ret || + ((value & USBIN_INPUT_MASK) > + (the_chip->usb_current_arr_size - 1))) { + pr_err("USBIN Current failed ret = %d\n", ret); + state = -EFAULT; + goto end; + } + + state = the_chip->usb_current_table[(value & USBIN_INPUT_MASK)]; +end: + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state); +} + +static DEVICE_ATTR(force_chg_iusb, 0664, + force_chg_iusb_show, + force_chg_iusb_store); + + +#define PRECHG_OFFSET 100 +#define PRECHG_STEP 50 +#define PRECHG_MAX 250 +#define PRECHG_REG_SHIFT 5 +static ssize_t force_chg_itrick_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long r; + unsigned long chg_current; + int i; + + r = kstrtoul(buf, 0, &chg_current); + if (r) { + pr_err("Invalid pre-charge value = %lu\n", chg_current); + return -EINVAL; + } + + if (!the_chip) { + pr_err("chip not valid\n"); + return -ENODEV; + } + + for (i = PRECHG_MAX; i > PRECHG_OFFSET; i = i - PRECHG_STEP) { + if (chg_current >= i) + break; + } + + i = (i - PRECHG_OFFSET) / PRECHG_STEP; + + i = (i << PRECHG_REG_SHIFT) & SMB135X_MASK(7, 5); + + r = smb135x_masked_write_fac(the_chip, CFG_1C_REG, + BATT_CURR_MASK, i); + if (r < 0) { + dev_err(the_chip->dev, + "Couldn't set Pre-Charge Current = %d r = %d\n", + (int)chg_current, (int)r); + return r; + } + + + return r ? r : count; +} + +static ssize_t force_chg_itrick_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int state; + int ret; + u8 value; + + if (!the_chip) { + pr_err("chip not valid\n"); + state = -ENODEV; + goto end; + } + + ret = smb135x_read(the_chip, 0x49, &value); + if (ret) { + pr_err("Pre-Charge Current failed ret = %d\n", ret); + state = -EFAULT; + goto end; + } + + state = (value & SMB135X_MASK(7, 5)) >> PRECHG_REG_SHIFT; + + state = (state * PRECHG_STEP) + PRECHG_OFFSET; +end: + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state); +} + +static DEVICE_ATTR(force_chg_itrick, 0664, + force_chg_itrick_show, + force_chg_itrick_store); + +#define OTG_EN_BIT BIT(0) +static ssize_t force_chg_usb_otg_ctl_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int state; + int ret; + u8 value; + + if (!the_chip) { + pr_err("chip not valid\n"); + state = -ENODEV; + goto end; + } + + ret = smb135x_read(the_chip, CMD_CHG_REG, &value); + if (ret) { + pr_err("OTG_EN_BIT failed ret = %d\n", ret); + state = -EFAULT; + goto end; + } + + state = (OTG_EN_BIT & value) ? 1 : 0; +end: + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state); +} + +static ssize_t force_chg_usb_otg_ctl_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long r; + unsigned long mode; + + r = kstrtoul(buf, 0, &mode); + if (r) { + pr_err("Invalid otg ctl value = %lu\n", mode); + return -EINVAL; + } + + if (!the_chip) { + pr_err("chip not valid\n"); + return -ENODEV; + } + + if (mode) { + r = smb135x_masked_write_fac(the_chip, CMD_CHG_REG, + (CMD_CHG_EN | OTG_EN_BIT), + (CMD_CHG_EN | OTG_EN_BIT)); + } else { + r = smb135x_masked_write_fac(the_chip, CMD_CHG_REG, + (CMD_CHG_EN | OTG_EN_BIT), + 0); + } + + if (r < 0) + dev_err(the_chip->dev, + "Couldn't set OTG mode = %d r = %d\n", + (int)mode, (int)r); + + return r ? r : count; +} + +static DEVICE_ATTR(force_chg_usb_otg_ctl, 0664, + force_chg_usb_otg_ctl_show, + force_chg_usb_otg_ctl_store); + +static bool smb135x_charger_mmi_factory(void) +{ + struct device_node *np = of_find_node_by_path("/chosen"); + bool factory = false; + + if (np) + factory = of_property_read_bool(np, "mmi,factory-cable"); + + of_node_put(np); + + return factory; +} + +static int smb135x_charger_reboot(struct notifier_block *nb, + unsigned long event, void *unused) +{ + struct smb135x_chg *chip = + container_of(nb, struct smb135x_chg, smb_reboot); + int rc = 0; + + dev_dbg(chip->dev, "SMB Reboot\n"); + + rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0); + if (rc < 0) + dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n", rc); + + /* force usb/dc shutdown on halt */ + if (event == SYS_HALT) { + __smb135x_usb_suspend(chip, true); + __smb135x_dc_suspend(chip, true); + } + + return NOTIFY_DONE; +} + +static int smb135x_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc; + struct smb135x_chg *chip; + struct power_supply *usb_psy; + u8 reg = 0; + + usb_psy = power_supply_get_by_name("usb"); + if (!usb_psy) { + dev_dbg(&client->dev, "USB supply not found; defer probe\n"); + return -EPROBE_DEFER; + } + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) { + dev_err(&client->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + chip->factory_mode = smb135x_charger_mmi_factory(); + if (chip->factory_mode) + chip->apsd_rerun_cnt = 1; + + chip->client = client; + chip->dev = &client->dev; + chip->usb_psy = usb_psy; + chip->fake_battery_soc = -EINVAL; + chip->charger_rate = POWER_SUPPLY_CHARGE_RATE_NONE; + chip->aicl_weak_detect = false; + chip->invalid_battery = false; + chip->shutdown_voltage_tripped = false; + chip->poll_fast = false; + chip->prev_batt_health = POWER_SUPPLY_HEALTH_GOOD; + chip->therm_lvl_sel = -EINVAL; + chip->dc_therm_lvl_sel = -EINVAL; + chip->hvdcp_powerup = false; + + + wakeup_source_init(&chip->smb_wake_source.source, "smb135x_wake"); + INIT_DELAYED_WORK(&chip->wireless_insertion_work, + wireless_insertion_work); + INIT_DELAYED_WORK(&chip->usb_insertion_work, + usb_insertion_work); + INIT_DELAYED_WORK(&chip->heartbeat_work, + heartbeat_work); + INIT_DELAYED_WORK(&chip->aicl_check_work, + aicl_check_work); + INIT_DELAYED_WORK(&chip->src_removal_work, + src_removal_work); + INIT_DELAYED_WORK(&chip->rate_check_work, + rate_check_work); + INIT_DELAYED_WORK(&chip->ocp_clear_work, + ocp_clear_work); + + mutex_init(&chip->path_suspend_lock); + mutex_init(&chip->current_change_lock); + mutex_init(&chip->read_write_lock); + /* probe the device to check if its actually connected */ + rc = smb135x_read(chip, CMD_INPUT_LIMIT, ®); + if (rc) { + pr_err("Failed to detect SMB135x, device may be absent\n"); + return -ENODEV; + } else { + dev_info(&client->dev, " CMD_IL=%x\n", reg); + if (reg & USB_SHUTDOWN_BIT) + chip->usb_suspended = 0x1; /* USER bit */ + if (reg & DC_SHUTDOWN_BIT) + chip->dc_suspended = 0x1; /* USER bit */ + } + + rc = smb_parse_dt(chip); + if (rc < 0) { + dev_err(&client->dev, "Unable to parse DT nodes\n"); + return rc; + } + + i2c_set_clientdata(client, chip); + + rc = smb135x_chip_version_and_revision(chip); + if (rc) { + dev_err(&client->dev, + "Couldn't detect version/revision rc=%d\n", rc); + return rc; + } + + dump_regs(chip); + + chip->vadc_dev = qpnp_get_vadc(chip->dev, "smb135x"); + if (IS_ERR(chip->vadc_dev)) { + rc = PTR_ERR(chip->vadc_dev); + if (rc == -EPROBE_DEFER) + pr_err("vadc not ready, defer probe\n"); + wakeup_source_trash(&chip->smb_wake_source.source); + return rc; + } + + chip->adc_tm_dev = qpnp_get_adc_tm(chip->dev, "smb135x"); + if (IS_ERR(chip->adc_tm_dev)) { + rc = PTR_ERR(chip->adc_tm_dev); + if (rc == -EPROBE_DEFER) + pr_err("adc-tm not ready, defer probe\n"); + wakeup_source_trash(&chip->smb_wake_source.source); + return rc; + } + + rc = smb135x_regulator_init(chip); + if (rc) { + dev_err(&client->dev, + "Couldn't initialize regulator rc=%d\n", rc); + return rc; + } + + rc = smb135x_hw_init(chip); + if (rc < 0) { + dev_err(&client->dev, + "Unable to intialize hardware rc = %d\n", rc); + goto free_regulator; + } + + rc = determine_initial_status(chip); + if (rc < 0) { + dev_err(&client->dev, + "Unable to determine init status rc = %d\n", rc); + goto free_regulator; + } + + if (chip->factory_mode) { + rc = smb135x_hw_init_fac(chip); + if (rc < 0) { + dev_err(&client->dev, + "Unable to init hardware for factory status rc = %d\n", + rc); + goto free_regulator; + } + } + + chip->batt_psy.name = "battery"; + chip->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY; + chip->batt_psy.get_property = smb135x_battery_get_property; + chip->batt_psy.set_property = smb135x_battery_set_property; + chip->batt_psy.properties = smb135x_battery_properties; + chip->batt_psy.num_properties = ARRAY_SIZE(smb135x_battery_properties); + chip->batt_psy.external_power_changed = smb135x_external_power_changed; + chip->batt_psy.property_is_writeable = smb135x_battery_is_writeable; + + rc = power_supply_register(chip->dev, &chip->batt_psy); + if (rc < 0) { + dev_err(&client->dev, + "Unable to register batt_psy rc = %d\n", rc); + goto free_regulator; + } + + if (chip->dc_psy_type != -EINVAL) { + chip->dc_psy.name = "dc"; + chip->dc_psy.type = chip->dc_psy_type; + chip->dc_psy.get_property = smb135x_dc_get_property; + chip->dc_psy.set_property = smb135x_dc_set_property; + chip->dc_psy.properties = smb135x_dc_properties; + chip->dc_psy.num_properties = ARRAY_SIZE(smb135x_dc_properties); + chip->dc_psy.property_is_writeable = smb135x_dc_is_writeable; + + rc = power_supply_register(chip->dev, &chip->dc_psy); + if (rc < 0) { + dev_err(&client->dev, + "Unable to register dc_psy rc = %d\n", rc); + goto unregister_batt_psy; + } + } + + chip->resume_completed = true; + mutex_init(&chip->irq_complete); + + /* STAT irq configuration */ + if (client->irq) { + rc = devm_request_threaded_irq(&client->dev, client->irq, NULL, + smb135x_chg_stat_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "smb135x_chg_stat_irq", chip); + if (rc < 0) { + dev_err(&client->dev, + "request_irq for irq=%d failed rc = %d\n", + client->irq, rc); + goto unregister_dc_psy; + } + enable_irq_wake(client->irq); + } + + chip->smb_reboot.notifier_call = smb135x_charger_reboot; + chip->smb_reboot.next = NULL; + chip->smb_reboot.priority = 1; + rc = register_reboot_notifier(&chip->smb_reboot); + if (rc) + dev_err(chip->dev, "register for reboot failed\n"); + + chip->debug_root = debugfs_create_dir("smb135x", NULL); + if (!chip->debug_root) + dev_err(chip->dev, "Couldn't create debug dir\n"); + + if (chip->debug_root) { + struct dentry *ent; + + ent = debugfs_create_file("config_registers", S_IFREG | S_IRUGO, + chip->debug_root, chip, + &cnfg_debugfs_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create cnfg debug file rc = %d\n", + rc); + + ent = debugfs_create_file("status_registers", S_IFREG | S_IRUGO, + chip->debug_root, chip, + &status_debugfs_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create status debug file rc = %d\n", + rc); + + ent = debugfs_create_file("cmd_registers", S_IFREG | S_IRUGO, + chip->debug_root, chip, + &cmd_debugfs_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create cmd debug file rc = %d\n", + rc); + + ent = debugfs_create_x32("address", S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, + &(chip->peek_poke_address)); + if (!ent) + dev_err(chip->dev, + "Couldn't create address debug file rc = %d\n", + rc); + + ent = debugfs_create_file("data", S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, chip, + &poke_poke_debug_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create data debug file rc = %d\n", + rc); + + ent = debugfs_create_file("force_irq", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, chip, + &force_irq_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create data debug file rc = %d\n", + rc); + + ent = debugfs_create_x32("skip_writes", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, + &(chip->skip_writes)); + if (!ent) + dev_err(chip->dev, + "Couldn't create data debug file rc = %d\n", + rc); + + ent = debugfs_create_x32("skip_reads", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, + &(chip->skip_reads)); + if (!ent) + dev_err(chip->dev, + "Couldn't create data debug file rc = %d\n", + rc); + + ent = debugfs_create_file("irq_count", S_IFREG | S_IRUGO, + chip->debug_root, chip, + &irq_count_debugfs_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create count debug file rc = %d\n", + rc); + + ent = debugfs_create_file("force_recharge", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, chip, + &force_rechg_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create recharge debug file rc = %d\n", + rc); + + ent = debugfs_create_x32("usb_suspend_votes", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, + &(chip->usb_suspended)); + if (!ent) + dev_err(chip->dev, + "Couldn't create usb vote file rc = %d\n", + rc); + + ent = debugfs_create_x32("dc_suspend_votes", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, + &(chip->dc_suspended)); + if (!ent) + dev_err(chip->dev, + "Couldn't create dc vote file rc = %d\n", + rc); + } + + the_chip = chip; + + if (chip->factory_mode) { + rc = device_create_file(chip->dev, + &dev_attr_force_chg_usb_suspend); + if (rc) { + pr_err("couldn't create force_chg_usb_suspend\n"); + goto unregister_dc_psy; + } + + rc = device_create_file(chip->dev, + &dev_attr_force_chg_fail_clear); + if (rc) { + pr_err("couldn't create force_chg_fail_clear\n"); + goto unregister_dc_psy; + } + + rc = device_create_file(chip->dev, + &dev_attr_force_chg_auto_enable); + if (rc) { + pr_err("couldn't create force_chg_auto_enable\n"); + goto unregister_dc_psy; + } + + rc = device_create_file(chip->dev, + &dev_attr_force_chg_ibatt); + if (rc) { + pr_err("couldn't create force_chg_ibatt\n"); + goto unregister_dc_psy; + } + + rc = device_create_file(chip->dev, + &dev_attr_force_chg_iusb); + if (rc) { + pr_err("couldn't create force_chg_iusb\n"); + goto unregister_dc_psy; + } + + rc = device_create_file(chip->dev, + &dev_attr_force_chg_itrick); + if (rc) { + pr_err("couldn't create force_chg_itrick\n"); + goto unregister_dc_psy; + } + + rc = device_create_file(chip->dev, + &dev_attr_force_chg_usb_otg_ctl); + if (rc) { + pr_err("couldn't create force_chg_usb_otg_ctl\n"); + goto unregister_dc_psy; + } + + } + + rc = smb135x_setup_vbat_monitoring(chip); + if (rc < 0) + pr_err("failed to set up voltage notifications: %d\n", rc); + + schedule_delayed_work(&chip->heartbeat_work, + msecs_to_jiffies(60000)); + + dev_info(chip->dev, "SMB135X version = %s revision = %s successfully probed batt=%d dc = %d usb = %d\n", + version_str[chip->version], + revision_str[chip->revision], + smb135x_get_prop_batt_present(chip), + chip->dc_present, chip->usb_present); + return 0; + +unregister_dc_psy: + if (chip->dc_psy_type != -EINVAL) + power_supply_unregister(&chip->dc_psy); +unregister_batt_psy: + power_supply_unregister(&chip->batt_psy); +free_regulator: + smb135x_regulator_deinit(chip); + wakeup_source_trash(&chip->smb_wake_source.source); + return rc; +} + +static int smb135x_charger_remove(struct i2c_client *client) +{ + int rc; + struct smb135x_chg *chip = i2c_get_clientdata(client); + + if (chip->therm_bias_vreg) { + rc = regulator_disable(chip->therm_bias_vreg); + if (rc) + pr_err("Couldn't disable therm-bias rc = %d\n", rc); + } + + unregister_reboot_notifier(&chip->smb_reboot); + debugfs_remove_recursive(chip->debug_root); + + if (chip->dc_psy_type != -EINVAL) + power_supply_unregister(&chip->dc_psy); + + power_supply_unregister(&chip->batt_psy); + + mutex_destroy(&chip->irq_complete); + if (chip->factory_mode) { + device_remove_file(chip->dev, + &dev_attr_force_chg_usb_suspend); + device_remove_file(chip->dev, + &dev_attr_force_chg_fail_clear); + device_remove_file(chip->dev, + &dev_attr_force_chg_auto_enable); + device_remove_file(chip->dev, + &dev_attr_force_chg_ibatt); + device_remove_file(chip->dev, + &dev_attr_force_chg_iusb); + device_remove_file(chip->dev, + &dev_attr_force_chg_itrick); + device_remove_file(chip->dev, + &dev_attr_force_chg_usb_otg_ctl); + } + + smb135x_regulator_deinit(chip); + wakeup_source_trash(&chip->smb_wake_source.source); + + return 0; +} + +static int smb135x_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb135x_chg *chip = i2c_get_clientdata(client); + int i, rc; + + /* Save the current IRQ config */ + for (i = 0; i < 3; i++) { + rc = smb135x_read(chip, IRQ_CFG_REG + i, + &chip->irq_cfg_mask[i]); + if (rc) + dev_err(chip->dev, + "Couldn't save irq cfg regs rc=%d\n", rc); + } + + /* enable only important IRQs */ + rc = smb135x_write(chip, IRQ_CFG_REG, IRQ_USBIN_UV_BIT); + if (rc < 0) + dev_err(chip->dev, "Couldn't set irq_cfg rc = %d\n", rc); + + rc = smb135x_write(chip, IRQ2_CFG_REG, IRQ2_BATT_MISSING_BIT + | IRQ2_VBAT_LOW_BIT); + if (rc < 0) + dev_err(chip->dev, "Couldn't set irq2_cfg rc = %d\n", rc); + + rc = smb135x_write(chip, IRQ3_CFG_REG, IRQ3_SRC_DETECT_BIT + | IRQ3_DCIN_UV_BIT); + if (rc < 0) + dev_err(chip->dev, "Couldn't set irq3_cfg rc = %d\n", rc); + + mutex_lock(&chip->irq_complete); + chip->resume_completed = false; + mutex_unlock(&chip->irq_complete); + + return 0; +} + +static int smb135x_suspend_noirq(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb135x_chg *chip = i2c_get_clientdata(client); + + if (chip->irq_waiting) { + pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n"); + return -EBUSY; + } + return 0; +} + +static int smb135x_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb135x_chg *chip = i2c_get_clientdata(client); + int i, rc; + + /* Restore the IRQ config */ + for (i = 0; i < 3; i++) { + rc = smb135x_write(chip, IRQ_CFG_REG + i, + chip->irq_cfg_mask[i]); + if (rc) + dev_err(chip->dev, + "Couldn't restore irq cfg regs rc=%d\n", rc); + } + mutex_lock(&chip->irq_complete); + chip->resume_completed = true; + if (chip->irq_waiting) { + mutex_unlock(&chip->irq_complete); + smb135x_chg_stat_handler(client->irq, chip); + enable_irq(client->irq); + } else { + mutex_unlock(&chip->irq_complete); + } + return 0; +} + +static const struct dev_pm_ops smb135x_pm_ops = { + .resume = smb135x_resume, + .suspend_noirq = smb135x_suspend_noirq, + .suspend = smb135x_suspend, +}; + +static const struct i2c_device_id smb135x_charger_id[] = { + {"smb135x-charger", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, smb135x_charger_id); + +static struct i2c_driver smb135x_charger_driver = { + .driver = { + .name = "smb135x-charger", + .owner = THIS_MODULE, + .of_match_table = smb135x_match_table, + .pm = &smb135x_pm_ops, + }, + .probe = smb135x_charger_probe, + .remove = smb135x_charger_remove, + .id_table = smb135x_charger_id, +}; + +module_i2c_driver(smb135x_charger_driver); + +MODULE_DESCRIPTION("SMB135x Charger"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("i2c:smb135x-charger"); diff --git a/drivers/rtc/qpnp-rtc.c b/drivers/rtc/qpnp-rtc.c index e0d5c1e6c7b5..8ef15166b6a2 100644 --- a/drivers/rtc/qpnp-rtc.c +++ b/drivers/rtc/qpnp-rtc.c @@ -45,11 +45,6 @@ #define TO_SECS(arr) (arr[0] | (arr[1] << 8) | (arr[2] << 16) | \ (arr[3] << 24)) -/* Module parameter to control power-on-alarm */ -static bool poweron_alarm; -module_param(poweron_alarm, bool, 0644); -MODULE_PARM_DESC(poweron_alarm, "Enable/Disable power-on alarm"); - /* rtc driver internal structure */ struct qpnp_rtc { u8 rtc_ctrl_reg; @@ -639,7 +634,7 @@ static void qpnp_rtc_shutdown(struct spmi_device *spmi) struct qpnp_rtc *rtc_dd = dev_get_drvdata(&spmi->dev); bool rtc_alarm_powerup = rtc_dd->rtc_alarm_powerup; - if (!rtc_alarm_powerup && !poweron_alarm) { + if (!rtc_alarm_powerup) { spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags); dev_dbg(&spmi->dev, "Disabling alarm interrupts\n"); diff --git a/drivers/scsi/megaraid/megaraid_mm.c b/drivers/scsi/megaraid/megaraid_mm.c index 25506c777381..9bec1717047e 100644 --- a/drivers/scsi/megaraid/megaraid_mm.c +++ b/drivers/scsi/megaraid/megaraid_mm.c @@ -486,6 +486,8 @@ mimd_to_kioc(mimd_t __user *umimd, mraid_mmadp_t *adp, uioc_t *kioc) pthru32->dataxferaddr = kioc->buf_paddr; if (kioc->data_dir & UIOC_WR) { + if (pthru32->dataxferlen > kioc->xferlen) + return -EINVAL; if (copy_from_user(kioc->buf_vaddr, kioc->user_data, pthru32->dataxferlen)) { return (-EFAULT); diff --git a/drivers/scsi/mpt2sas/mpt2sas_scsih.c b/drivers/scsi/mpt2sas/mpt2sas_scsih.c index d953a57e779d..e1a6e7a86798 100644 --- a/drivers/scsi/mpt2sas/mpt2sas_scsih.c +++ b/drivers/scsi/mpt2sas/mpt2sas_scsih.c @@ -8090,7 +8090,6 @@ _scsih_suspend(struct pci_dev *pdev, pm_message_t state) mpt2sas_base_free_resources(ioc); pci_save_state(pdev); - pci_disable_device(pdev); pci_set_power_state(pdev, device_state); return 0; } diff --git a/drivers/staging/android/lowmemorykiller.c b/drivers/staging/android/lowmemorykiller.c index b5cd927109b8..ba6e10649f8a 100644 --- a/drivers/staging/android/lowmemorykiller.c +++ b/drivers/staging/android/lowmemorykiller.c @@ -306,6 +306,10 @@ static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc) if (test_task_flag(tsk, TIF_MM_RELEASED)) continue; + /* Ignore task if coredump in progress */ + if (tsk->mm && tsk->mm->core_state) + continue; + if (time_before_eq(jiffies, lowmem_deathpending_timeout)) { if (test_task_flag(tsk, TIF_MEMDIE)) { rcu_read_unlock(); diff --git a/drivers/staging/prima/CORE/HDD/src/wlan_hdd_wext.c b/drivers/staging/prima/CORE/HDD/src/wlan_hdd_wext.c index e249480838ac..506ef798c059 100644 --- a/drivers/staging/prima/CORE/HDD/src/wlan_hdd_wext.c +++ b/drivers/staging/prima/CORE/HDD/src/wlan_hdd_wext.c @@ -6896,6 +6896,9 @@ int wlan_hdd_set_filter(hdd_context_t *pHddCtx, tpPacketFilterCfg pRequest, hddLog(VOS_TRACE_LEVEL_INFO, "Data Offset %d Data Len %d", pRequest->paramsData[i].dataOffset, pRequest->paramsData[i].dataLength); + if ((sizeof(packetFilterSetReq.paramsData[i].compareData)) < + (pRequest->paramsData[i].dataLength)) + return -EINVAL; memcpy(&packetFilterSetReq.paramsData[i].compareData, pRequest->paramsData[i].compareData, pRequest->paramsData[i].dataLength); diff --git a/drivers/staging/prima/CORE/VOSS/inc/wlan_hdd_misc.h b/drivers/staging/prima/CORE/VOSS/inc/wlan_hdd_misc.h index cbb761403a98..7c2d62ad86c3 100644 --- a/drivers/staging/prima/CORE/VOSS/inc/wlan_hdd_misc.h +++ b/drivers/staging/prima/CORE/VOSS/inc/wlan_hdd_misc.h @@ -33,7 +33,7 @@ #define WLAN_INI_FILE "wlan/prima/WCNSS_qcom_cfg.ini" #define WLAN_CFG_FILE "wlan/prima/WCNSS_cfg.dat" #define WLAN_FW_FILE "" -#define WLAN_NV_FILE "../../../data/misc/wifi/prima/WCNSS_qcom_wlan_nv.bin" +#define WLAN_NV_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin" #define WLAN_DICT_FILE "wlan/prima/WCNSS_wlan_dictionary.dat" #define WLAN_COUNTRY_INFO_FILE "wlan/prima/WCNSS_wlan_country_info.dat" #define WLAN_HO_CFG_FILE "wlan/prima/WCNSS_wlan_ho_config" diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 347d9f2c64af..f66cf0b9a873 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -73,17 +73,30 @@ config THERMAL_PM8XXX shifting over temperature shutdown control of the PMIC from hardware to software. +choice + prompt "MSM Thermal Monitor Types" + default THERMAL_MONITOR + +config INTELLI_THERMAL_V2 + bool "Better thermal monitor for MSM" + depends on THERMAL_TSENS8974 + depends on CPU_FREQ_MSM + help + This enables thermal monitoring capability in the kernel replacing + userspace thermal monitoring altogether. + config THERMAL_MONITOR bool "Monitor thermal state and limit CPU Frequency" depends on THERMAL_TSENS8960 || THERMAL_TSENS8974 depends on CPU_FREQ_MSM - default n help This enables thermal monitoring capability in the kernel in the absence of a system wide thermal monitoring entity or until such an entity starts running in the userspace. Monitors TSENS temperature and limits the max frequency of the cores. +endchoice + config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 0d17026c539e..e967bd2d7cce 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_THERMAL_MSM_POPMEM) += msm_popmem-tm.o obj-$(CONFIG_THERMAL_TSENS) += msm_tsens.o obj-$(CONFIG_THERMAL_TSENS8960) += msm8960_tsens.o obj-$(CONFIG_THERMAL_PM8XXX) += pm8xxx-tm.o +obj-$(CONFIG_INTELLI_THERMAL_V2) += intelli/ obj-$(CONFIG_THERMAL_MONITOR) += msm_thermal.o msm_thermal-dev.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_THERMAL_TSENS8974) += msm8974-tsens.o diff --git a/drivers/thermal/intelli/Makefile b/drivers/thermal/intelli/Makefile new file mode 100644 index 000000000000..5bf62f8c391e --- /dev/null +++ b/drivers/thermal/intelli/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for intelli-thermal. +# + +obj-$(CONFIG_INTELLI_THERMAL_V2) += msm_thermal_v2.o diff --git a/drivers/thermal/intelli/msm_thermal_v2.c b/drivers/thermal/intelli/msm_thermal_v2.c new file mode 100644 index 000000000000..938c4ff4f2a2 --- /dev/null +++ b/drivers/thermal/intelli/msm_thermal_v2.c @@ -0,0 +1,2797 @@ +/* Copyright (c) 2012-2014, The Linux Foundation. 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 version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + * + * Added code to work as a standalone intelligent thermal throttling driver + * for newer Qualcomm SOCs based on 8974 and 8226 by Paul Reioux (Faux123) + * Modifications copyright (c) 2014 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_CURRENT_UA 1000000 +#define MAX_RAILS 5 +#define MAX_THRESHOLD 2 + +static struct msm_thermal_data msm_thermal_info; +static struct delayed_work check_temp_work; +static bool core_control_enabled; +static uint32_t cpus_offlined; +static DEFINE_MUTEX(core_control_mutex); +static uint32_t wakeup_ms; +static struct alarm thermal_rtc; +static struct kobject *tt_kobj; +static struct kobject *cc_kobj; +static struct work_struct timer_work; +static struct task_struct *hotplug_task; +static struct task_struct *freq_mitigation_task; +static struct completion hotplug_notify_complete; +static struct completion freq_mitigation_complete; + +static int enabled; +static int rails_cnt; +static int psm_rails_cnt; +static int ocr_rail_cnt; +static int limit_idx; +static int limit_idx_low; +static int limit_idx_high; +static int max_tsens_num; +static struct cpufreq_frequency_table *table; +static uint32_t usefreq; +static int freq_table_get; +static bool vdd_rstr_enabled; +static bool vdd_rstr_nodes_called; +static bool vdd_rstr_probed; +static bool psm_enabled; +static bool psm_nodes_called; +static bool psm_probed; +static bool hotplug_enabled; +static bool freq_mitigation_enabled; +static bool ocr_enabled; +static bool ocr_nodes_called; +static bool ocr_probed; +static int *tsens_id_map; +static DEFINE_MUTEX(vdd_rstr_mutex); +static DEFINE_MUTEX(psm_mutex); +static DEFINE_MUTEX(ocr_mutex); +static uint32_t min_freq_limit; + +enum thermal_threshold { + HOTPLUG_THRESHOLD_HIGH, + HOTPLUG_THRESHOLD_LOW, + FREQ_THRESHOLD_HIGH, + FREQ_THRESHOLD_LOW, + THRESHOLD_MAX_NR, +}; + +enum sensor_id_type { + THERM_ZONE_ID, + THERM_TSENS_ID, + THERM_ID_MAX_NR, +}; + +struct cpu_info { + uint32_t cpu; + const char *sensor_type; + enum sensor_id_type id_type; + uint32_t sensor_id; + bool offline; + bool user_offline; + bool hotplug_thresh_clear; + struct sensor_threshold threshold[THRESHOLD_MAX_NR]; + bool max_freq; + uint32_t user_max_freq; + uint32_t user_min_freq; + uint32_t limited_max_freq; + uint32_t limited_min_freq; + bool freq_thresh_clear; +}; + +struct rail { + const char *name; + uint32_t freq_req; + uint32_t min_level; + uint32_t num_levels; + int32_t curr_level; + uint32_t levels[3]; + struct kobj_attribute value_attr; + struct kobj_attribute level_attr; + struct regulator *reg; + struct attribute_group attr_gp; +}; + +struct psm_rail { + const char *name; + uint8_t init; + uint8_t mode; + struct kobj_attribute mode_attr; + struct rpm_regulator *reg; + struct regulator *phase_reg; + struct attribute_group attr_gp; +}; + +static struct psm_rail *psm_rails; +static struct psm_rail *ocr_rails; +static struct rail *rails; +static struct cpu_info cpus[NR_CPUS]; + +struct vdd_rstr_enable { + struct kobj_attribute ko_attr; + uint32_t enabled; +}; + +/* For SMPS only*/ +enum PMIC_SW_MODE { + PMIC_AUTO_MODE = RPM_REGULATOR_MODE_AUTO, + PMIC_IPEAK_MODE = RPM_REGULATOR_MODE_IPEAK, + PMIC_PWM_MODE = RPM_REGULATOR_MODE_HPM, +}; + +enum ocr_request { + OPTIMUM_CURRENT_MIN, + OPTIMUM_CURRENT_MAX, + OPTIMUM_CURRENT_NR, +}; + +#define VDD_RES_RO_ATTRIB(_rail, ko_attr, j, _name) \ + ko_attr.attr.name = __stringify(_name); \ + ko_attr.attr.mode = 0444; \ + ko_attr.show = vdd_rstr_reg_##_name##_show; \ + ko_attr.store = NULL; \ + sysfs_attr_init(&ko_attr.attr); \ + _rail.attr_gp.attrs[j] = &ko_attr.attr; + +#define VDD_RES_RW_ATTRIB(_rail, ko_attr, j, _name) \ + ko_attr.attr.name = __stringify(_name); \ + ko_attr.attr.mode = 0644; \ + ko_attr.show = vdd_rstr_reg_##_name##_show; \ + ko_attr.store = vdd_rstr_reg_##_name##_store; \ + sysfs_attr_init(&ko_attr.attr); \ + _rail.attr_gp.attrs[j] = &ko_attr.attr; + +#define VDD_RSTR_ENABLE_FROM_ATTRIBS(attr) \ + (container_of(attr, struct vdd_rstr_enable, ko_attr)); + +#define VDD_RSTR_REG_VALUE_FROM_ATTRIBS(attr) \ + (container_of(attr, struct rail, value_attr)); + +#define VDD_RSTR_REG_LEVEL_FROM_ATTRIBS(attr) \ + (container_of(attr, struct rail, level_attr)); + +#define OCR_RW_ATTRIB(_rail, ko_attr, j, _name) \ + ko_attr.attr.name = __stringify(_name); \ + ko_attr.attr.mode = 0644; \ + ko_attr.show = ocr_reg_##_name##_show; \ + ko_attr.store = ocr_reg_##_name##_store; \ + sysfs_attr_init(&ko_attr.attr); \ + _rail.attr_gp.attrs[j] = &ko_attr.attr; + +#define PSM_RW_ATTRIB(_rail, ko_attr, j, _name) \ + ko_attr.attr.name = __stringify(_name); \ + ko_attr.attr.mode = 0644; \ + ko_attr.show = psm_reg_##_name##_show; \ + ko_attr.store = psm_reg_##_name##_store; \ + sysfs_attr_init(&ko_attr.attr); \ + _rail.attr_gp.attrs[j] = &ko_attr.attr; + +#define PSM_REG_MODE_FROM_ATTRIBS(attr) \ + (container_of(attr, struct psm_rail, mode_attr)); + +#define DEFAULT_POLLING_MS 250 +/* last 3 minutes based on 250ms polling cycle */ +#define MAX_HISTORY_SZ ((3*60*1000) / DEFAULT_POLLING_MS) + +struct msm_thermal_stat_data { + int32_t temp_history[MAX_HISTORY_SZ]; + uint32_t throttled; + uint32_t warning; + uint32_t normal; +}; +static struct msm_thermal_stat_data msm_thermal_stats; + +/* module parameters */ +module_param_named(poll_ms, msm_thermal_info.poll_ms, uint, 0664); +module_param_named(limit_temp_degC, msm_thermal_info.limit_temp_degC, + int, 0664); +module_param_named(freq_control_mask, msm_thermal_info.bootup_freq_control_mask, + uint, 0664); +module_param_named(core_limit_temp_degC, msm_thermal_info.core_limit_temp_degC, + int, 0664); +module_param_named(core_control_mask, msm_thermal_info.core_control_mask, + uint, 0664); + +static int msm_thermal_cpufreq_callback(struct notifier_block *nfb, + unsigned long event, void *data) +{ + struct cpufreq_policy *policy = data; + uint32_t max_freq_req = cpus[policy->cpu].limited_max_freq; + uint32_t min_freq_req = cpus[policy->cpu].limited_min_freq; + + switch (event) { + case CPUFREQ_INCOMPATIBLE: + pr_debug("%s: mitigating cpu %d to freq max: %u min: %u\n", + KBUILD_MODNAME, policy->cpu, max_freq_req, min_freq_req); + + cpufreq_verify_within_limits(policy, min_freq_req, + max_freq_req); + + if (max_freq_req < min_freq_req) + pr_err("Invalid frequency request Max:%u Min:%u\n", + max_freq_req, min_freq_req); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block msm_thermal_cpufreq_notifier = { + .notifier_call = msm_thermal_cpufreq_callback, +}; + +/* If freq table exists, then we can send freq request */ +static int check_freq_table(void) +{ + int ret = 0; + struct cpufreq_frequency_table *table = NULL; + + table = cpufreq_frequency_get_table(0); + if (!table) { + pr_debug("%s: error reading cpufreq table\n", __func__); + return -EINVAL; + } + freq_table_get = 1; + + return ret; +} + +static void update_cpu_freq(int cpu) +{ + if (cpu_online(cpu)) { + if (cpufreq_update_policy(cpu)) + pr_err("Unable to update policy for cpu:%d\n", cpu); + } +} + +static int update_cpu_min_freq_all(uint32_t min) +{ + uint32_t cpu = 0; + int ret = 0; + + if (!freq_table_get) { + ret = check_freq_table(); + if (ret) { + pr_err("%s:Fail to get freq table\n", KBUILD_MODNAME); + return ret; + } + } + /* If min is larger than allowed max */ + min = min(min, table[limit_idx_high].frequency); + + if (freq_mitigation_task) { + min_freq_limit = min; + complete(&freq_mitigation_complete); + } else { + get_online_cpus(); + for_each_possible_cpu(cpu) { + cpus[cpu].limited_min_freq = min; + update_cpu_freq(cpu); + } + put_online_cpus(); + } + + return ret; +} + +static int vdd_restriction_apply_freq(struct rail *r, int level) +{ + int ret = 0; + + if (level == r->curr_level) + return ret; + + /* level = -1: disable, level = 0,1,2..n: enable */ + if (level == -1) { + ret = update_cpu_min_freq_all(r->min_level); + if (ret) + return ret; + else + r->curr_level = -1; + } else if (level >= 0 && level < (r->num_levels)) { + ret = update_cpu_min_freq_all(r->levels[level]); + if (ret) + return ret; + else + r->curr_level = level; + } else { + pr_err("level input:%d is not within range\n", level); + return -EINVAL; + } + + return ret; +} + +static int vdd_restriction_apply_voltage(struct rail *r, int level) +{ + int ret = 0; + + if (r->reg == NULL) { + pr_info("Do not have regulator handle:%s, can't apply vdd\n", + r->name); + return -EFAULT; + } + if (level == r->curr_level) + return ret; + + /* level = -1: disable, level = 0,1,2..n: enable */ + if (level == -1) { + ret = regulator_set_voltage(r->reg, r->min_level, + r->levels[r->num_levels - 1]); + if (!ret) + r->curr_level = -1; + } else if (level >= 0 && level < (r->num_levels)) { + ret = regulator_set_voltage(r->reg, r->levels[level], + r->levels[r->num_levels - 1]); + if (!ret) + r->curr_level = level; + } else { + pr_err("level input:%d is not within range\n", level); + return -EINVAL; + } + + return ret; +} + +/* Setting all rails the same mode */ +static int psm_set_mode_all(int mode) +{ + int i = 0; + int fail_cnt = 0; + int ret = 0; + + for (i = 0; i < psm_rails_cnt; i++) { + if (psm_rails[i].mode != mode) { + ret = rpm_regulator_set_mode(psm_rails[i].reg, mode); + if (ret) { + pr_err("Cannot set mode:%d for %s", + mode, psm_rails[i].name); + fail_cnt++; + } else + psm_rails[i].mode = mode; + } + } + + return fail_cnt ? (-EFAULT) : ret; +} + +static int vdd_rstr_en_show( + struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + struct vdd_rstr_enable *en = VDD_RSTR_ENABLE_FROM_ATTRIBS(attr); + + return snprintf(buf, PAGE_SIZE, "%d\n", en->enabled); +} + +static ssize_t vdd_rstr_en_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int ret = 0; + int i = 0; + uint8_t en_cnt = 0; + uint8_t dis_cnt = 0; + uint32_t val = 0; + struct kernel_param kp; + struct vdd_rstr_enable *en = VDD_RSTR_ENABLE_FROM_ATTRIBS(attr); + + mutex_lock(&vdd_rstr_mutex); + kp.arg = &val; + ret = param_set_bool(buf, &kp); + if (ret) { + pr_err("Invalid input %s for enabled\n", buf); + goto done_vdd_rstr_en; + } + + if ((val == 0) && (en->enabled == 0)) + goto done_vdd_rstr_en; + + for (i = 0; i < rails_cnt; i++) { + if (rails[i].freq_req == 1 && freq_table_get) + ret = vdd_restriction_apply_freq(&rails[i], + (val) ? 0 : -1); + else + ret = vdd_restriction_apply_voltage(&rails[i], + (val) ? 0 : -1); + + /* + * Even if fail to set one rail, still try to set the + * others. Continue the loop + */ + if (ret) + pr_err("Set vdd restriction for %s failed\n", + rails[i].name); + else { + if (val) + en_cnt++; + else + dis_cnt++; + } + } + /* As long as one rail is enabled, vdd rstr is enabled */ + if (val && en_cnt) + en->enabled = 1; + else if (!val && (dis_cnt == rails_cnt)) + en->enabled = 0; + +done_vdd_rstr_en: + mutex_unlock(&vdd_rstr_mutex); + return count; +} + +static struct vdd_rstr_enable vdd_rstr_en = { + .ko_attr.attr.name = __stringify(enabled), + .ko_attr.attr.mode = 0644, + .ko_attr.show = vdd_rstr_en_show, + .ko_attr.store = vdd_rstr_en_store, + .enabled = 1, +}; + +static struct attribute *vdd_rstr_en_attribs[] = { + &vdd_rstr_en.ko_attr.attr, + NULL, +}; + +static struct attribute_group vdd_rstr_en_attribs_gp = { + .attrs = vdd_rstr_en_attribs, +}; + +static int vdd_rstr_reg_value_show( + struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int val = 0; + struct rail *reg = VDD_RSTR_REG_VALUE_FROM_ATTRIBS(attr); + /* -1:disabled, -2:fail to get regualtor handle */ + if (reg->curr_level < 0) + val = reg->curr_level; + else + val = reg->levels[reg->curr_level]; + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static int vdd_rstr_reg_level_show( + struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + struct rail *reg = VDD_RSTR_REG_LEVEL_FROM_ATTRIBS(attr); + return snprintf(buf, PAGE_SIZE, "%d\n", reg->curr_level); +} + +static ssize_t vdd_rstr_reg_level_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int ret = 0; + int val = 0; + + struct rail *reg = VDD_RSTR_REG_LEVEL_FROM_ATTRIBS(attr); + + mutex_lock(&vdd_rstr_mutex); + if (vdd_rstr_en.enabled == 0) + goto done_store_level; + + ret = kstrtouint(buf, 10, &val); + if (ret) { + pr_err("Invalid input %s for level\n", buf); + goto done_store_level; + } + + if (val < 0 || val > reg->num_levels - 1) { + pr_err(" Invalid number %d for level\n", val); + goto done_store_level; + } + + if (val != reg->curr_level) { + if (reg->freq_req == 1 && freq_table_get) + update_cpu_min_freq_all(reg->levels[val]); + else { + ret = vdd_restriction_apply_voltage(reg, val); + if (ret) { + pr_err( \ + "Set vdd restriction for regulator %s failed\n", + reg->name); + goto done_store_level; + } + } + reg->curr_level = val; + } + +done_store_level: + mutex_unlock(&vdd_rstr_mutex); + return count; +} + +static int request_optimum_current(struct psm_rail *rail, enum ocr_request req) +{ + int ret = 0; + + if ((!rail) || (req >= OPTIMUM_CURRENT_NR) || + (req < 0)) { + pr_err("%s:%s Invalid input\n", KBUILD_MODNAME, __func__); + ret = -EINVAL; + goto request_ocr_exit; + } + + ret = regulator_set_optimum_mode(rail->phase_reg, + (req == OPTIMUM_CURRENT_MAX) ? MAX_CURRENT_UA : 0); + if (ret < 0) { + pr_err("%s: Optimum current request failed\n", KBUILD_MODNAME); + goto request_ocr_exit; + } + ret = 0; /*regulator_set_optimum_mode returns the mode on success*/ + pr_debug("%s: Requested optimum current mode: %d\n", + KBUILD_MODNAME, req); + +request_ocr_exit: + return ret; +} + +static int ocr_set_mode_all(enum ocr_request req) +{ + int ret = 0, i; + + for (i = 0; i < ocr_rail_cnt; i++) { + if (ocr_rails[i].mode == req) + continue; + ret = request_optimum_current(&ocr_rails[i], req); + if (ret) + goto ocr_set_mode_exit; + ocr_rails[i].mode = req; + } + +ocr_set_mode_exit: + return ret; +} + +static int ocr_reg_mode_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct psm_rail *reg = PSM_REG_MODE_FROM_ATTRIBS(attr); + return snprintf(buf, PAGE_SIZE, "%d\n", reg->mode); +} + +static ssize_t ocr_reg_mode_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int ret = 0; + int val = 0; + struct psm_rail *reg = PSM_REG_MODE_FROM_ATTRIBS(attr); + + if (!ocr_enabled) + return count; + + mutex_lock(&ocr_mutex); + ret = kstrtoint(buf, 10, &val); + if (ret) { + pr_err("%s: Invalid input %s for mode\n", + KBUILD_MODNAME, buf); + goto done_ocr_store; + } + + if ((val != OPTIMUM_CURRENT_MAX) && + (val != OPTIMUM_CURRENT_MIN)) { + pr_err("%s: Invalid value %d for mode\n", + KBUILD_MODNAME, val); + goto done_ocr_store; + } + + if (val != reg->mode) { + ret = request_optimum_current(reg, val); + if (ret) + goto done_ocr_store; + reg->mode = val; + } + +done_ocr_store: + mutex_unlock(&ocr_mutex); + return count; +} + +static int psm_reg_mode_show( + struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + struct psm_rail *reg = PSM_REG_MODE_FROM_ATTRIBS(attr); + return snprintf(buf, PAGE_SIZE, "%d\n", reg->mode); +} + +static ssize_t psm_reg_mode_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int ret = 0; + int val = 0; + struct psm_rail *reg = PSM_REG_MODE_FROM_ATTRIBS(attr); + + mutex_lock(&psm_mutex); + ret = kstrtoint(buf, 10, &val); + if (ret) { + pr_err("Invalid input %s for mode\n", buf); + goto done_psm_store; + } + + if ((val != PMIC_PWM_MODE) && (val != PMIC_AUTO_MODE)) { + pr_err(" Invalid number %d for mode\n", val); + goto done_psm_store; + } + + if (val != reg->mode) { + ret = rpm_regulator_set_mode(reg->reg, val); + if (ret) { + pr_err( \ + "Fail to set PMIC SW Mode:%d for %s\n", + val, reg->name); + goto done_psm_store; + } + reg->mode = val; + } + +done_psm_store: + mutex_unlock(&psm_mutex); + return count; +} + +static int check_sensor_id(int sensor_id) +{ + int i = 0; + bool hw_id_found = false; + int ret = 0; + + for (i = 0; i < max_tsens_num; i++) { + if (sensor_id == tsens_id_map[i]) { + hw_id_found = true; + break; + } + } + if (!hw_id_found) { + pr_err("%s: Invalid sensor hw id :%d\n", __func__, sensor_id); + return -EINVAL; + } + + return ret; +} + +static int create_sensor_id_map(void) +{ + int i = 0; + int ret = 0; + + tsens_id_map = kzalloc(sizeof(int) * max_tsens_num, + GFP_KERNEL); + if (!tsens_id_map) { + pr_err("%s: Cannot allocate memory for tsens_id_map\n", + __func__); + return -ENOMEM; + } + + for (i = 0; i < max_tsens_num; i++) { + ret = tsens_get_hw_id_mapping(i, &tsens_id_map[i]); + /* If return -ENXIO, hw_id is default in sequence */ + if (ret) { + if (ret == -ENXIO) { + tsens_id_map[i] = i; + ret = 0; + } else { + pr_err( \ + "%s: Failed to get hw id for sw id %d\n", + __func__, i); + goto fail; + } + } + } + + return ret; +fail: + kfree(tsens_id_map); + return ret; +} + +/* 1:enable, 0:disable */ +static int vdd_restriction_apply_all(int en) +{ + int i = 0; + int en_cnt = 0; + int dis_cnt = 0; + int fail_cnt = 0; + int ret = 0; + + for (i = 0; i < rails_cnt; i++) { + if (rails[i].freq_req == 1 && freq_table_get) + ret = vdd_restriction_apply_freq(&rails[i], + en ? 0 : -1); + else + ret = vdd_restriction_apply_voltage(&rails[i], + en ? 0 : -1); + if (ret) { + pr_err("Cannot set voltage for %s", rails[i].name); + fail_cnt++; + } else { + if (en) + en_cnt++; + else + dis_cnt++; + } + } + + /* As long as one rail is enabled, vdd rstr is enabled */ + if (en && en_cnt) + vdd_rstr_en.enabled = 1; + else if (!en && (dis_cnt == rails_cnt)) + vdd_rstr_en.enabled = 0; + + /* + * Check fail_cnt again to make sure all of the rails are applied + * restriction successfully or not + */ + if (fail_cnt) + return -EFAULT; + return ret; +} + +static int msm_thermal_get_freq_table(void) +{ + int ret = 0; + int i = 0; + + table = cpufreq_frequency_get_table(0); + if (table == NULL) { + pr_debug("%s: error reading cpufreq table\n", KBUILD_MODNAME); + ret = -EINVAL; + goto fail; + } + + while (table[i].frequency != CPUFREQ_TABLE_END) + i++; + + limit_idx_low = 0; + limit_idx_high = limit_idx = i - 1; + BUG_ON(limit_idx_high <= 0 || limit_idx_high <= limit_idx_low); +fail: + return ret; +} + +static int set_and_activate_threshold(uint32_t sensor_id, + struct sensor_threshold *threshold) +{ + int ret = 0; + + ret = sensor_set_trip(sensor_id, threshold); + if (ret != 0) { + pr_err("%s: Error in setting trip %d\n", + KBUILD_MODNAME, threshold->trip); + goto set_done; + } + + ret = sensor_activate_trip(sensor_id, threshold, true); + if (ret != 0) { + pr_err("%s: Error in enabling trip %d\n", + KBUILD_MODNAME, threshold->trip); + goto set_done; + } + +set_done: + return ret; +} + +static int therm_get_temp(uint32_t id, enum sensor_id_type type, long *temp) +{ + int ret = 0; + struct tsens_device tsens_dev; + + if (!temp) { + pr_err("Invalid value\n"); + ret = -EINVAL; + goto get_temp_exit; + } + + switch (type) { + case THERM_ZONE_ID: + tsens_dev.sensor_num = tsens_id_map[id]; + break; + case THERM_TSENS_ID: + tsens_dev.sensor_num = id; + break; + default: + pr_err("Invalid type\n"); + ret = -EINVAL; + goto get_temp_exit; + break; + } + + ret = tsens_get_temp(&tsens_dev, temp); + if (ret) { + pr_err("Unable to read TSENS sensor %d\n", + tsens_dev.sensor_num); + goto get_temp_exit; + } + +get_temp_exit: + return ret; +} + +static int set_threshold(uint32_t zone_id, + struct sensor_threshold *threshold) +{ + int i = 0, ret = 0; + long temp; + + if ((!threshold) || (zone_id >= max_tsens_num)) { + pr_err("%s: Invalid input for threshold: zone id: %u\n", + KBUILD_MODNAME, zone_id); + ret = -EINVAL; + goto set_threshold_exit; + } + + ret = therm_get_temp(zone_id, THERM_ZONE_ID, &temp); + if (ret) + goto set_threshold_exit; + + while (i < MAX_THRESHOLD) { + switch (threshold[i].trip) { + case THERMAL_TRIP_CONFIGURABLE_HI: + if (threshold[i].temp >= temp) { + ret = set_and_activate_threshold(zone_id, + &threshold[i]); + if (ret) + goto set_threshold_exit; + } + break; + case THERMAL_TRIP_CONFIGURABLE_LOW: + if (threshold[i].temp <= temp) { + ret = set_and_activate_threshold(zone_id, + &threshold[i]); + if (ret) + goto set_threshold_exit; + } + break; + default: + break; + } + i++; + } +set_threshold_exit: + return ret; +} + +#ifdef CONFIG_SMP +static void __ref do_core_control(long temp) +{ + int i = 0; + int ret = 0; + + if (!core_control_enabled) + return; + + mutex_lock(&core_control_mutex); + if (msm_thermal_info.core_control_mask && + temp >= msm_thermal_info.core_limit_temp_degC) { + for (i = num_possible_cpus(); i > 0; i--) { + if (!(msm_thermal_info.core_control_mask & BIT(i))) + continue; + if (cpus_offlined & BIT(i) && !cpu_online(i)) + continue; + pr_info("%s: Set Offline: CPU%d Temp: %ld\n", + KBUILD_MODNAME, i, temp); + ret = cpu_down(i); + if (ret) + pr_err("%s: Error %d offline core %d\n", + KBUILD_MODNAME, ret, i); + cpus_offlined |= BIT(i); + break; + } + } else if (msm_thermal_info.core_control_mask && cpus_offlined && + temp <= (msm_thermal_info.core_limit_temp_degC - + msm_thermal_info.core_temp_hysteresis_degC)) { + for (i = 0; i < num_possible_cpus(); i++) { + if (!(cpus_offlined & BIT(i))) + continue; + cpus_offlined &= ~BIT(i); + pr_info("%s: Allow Online CPU%d Temp: %ld\n", + KBUILD_MODNAME, i, temp); + /* + * If this core is already online, then bring up the + * next offlined core. + */ + if (cpu_online(i)) + continue; + ret = cpu_up(i); + if (ret) + pr_err("%s: Error %d online core %d\n", + KBUILD_MODNAME, ret, i); + break; + } + } + mutex_unlock(&core_control_mutex); +} +/* Call with core_control_mutex locked */ +static int __ref update_offline_cores(int val) +{ + uint32_t cpu = 0; + int ret = 0; + + if (!core_control_enabled) + return 0; + + cpus_offlined = msm_thermal_info.core_control_mask & val; + + for_each_possible_cpu(cpu) { + if (!(cpus_offlined & BIT(cpu))) + continue; + if (!cpu_online(cpu)) + continue; + ret = cpu_down(cpu); + if (ret) + pr_err("%s: Unable to offline cpu%d\n", + KBUILD_MODNAME, cpu); + } + return ret; +} + +static __ref int do_hotplug(void *data) +{ + int ret = 0; + uint32_t cpu = 0, mask = 0; + + if (!core_control_enabled) + return -EINVAL; + + while (!kthread_should_stop()) { + wait_for_completion(&hotplug_notify_complete); + INIT_COMPLETION(hotplug_notify_complete); + mask = 0; + + mutex_lock(&core_control_mutex); + for_each_possible_cpu(cpu) { + if (hotplug_enabled && + cpus[cpu].hotplug_thresh_clear) { + set_threshold(cpus[cpu].sensor_id, + &cpus[cpu].threshold[HOTPLUG_THRESHOLD_HIGH]); + + cpus[cpu].hotplug_thresh_clear = false; + } + if (cpus[cpu].offline || cpus[cpu].user_offline) + mask |= BIT(cpu); + } + if (mask != cpus_offlined) + update_offline_cores(mask); + mutex_unlock(&core_control_mutex); + sysfs_notify(cc_kobj, NULL, "cpus_offlined"); + } + + return ret; +} +#else +static void do_core_control(long temp) +{ + return; +} + +static __ref int do_hotplug(void *data) +{ + return 0; +} +#endif + +static int do_ocr(void) +{ + long temp = 0; + int ret = 0; + int i = 0, j = 0; + int auto_cnt = 0; + + if (!ocr_enabled) + return ret; + + mutex_lock(&ocr_mutex); + for (i = 0; i < max_tsens_num; i++) { + ret = therm_get_temp(tsens_id_map[i], THERM_TSENS_ID, &temp); + if (ret) { + pr_debug("%s: Unable to read TSENS sensor %d\n", + __func__, tsens_id_map[i]); + auto_cnt++; + continue; + } + + if (temp > msm_thermal_info.ocr_temp_degC) { + if (ocr_rails[0].init != OPTIMUM_CURRENT_NR) + for (j = 0; j < ocr_rail_cnt; j++) + ocr_rails[j].init = OPTIMUM_CURRENT_NR; + ret = ocr_set_mode_all(OPTIMUM_CURRENT_MAX); + if (ret) + pr_err("Error setting max optimum current\n"); + goto do_ocr_exit; + } else if (temp <= (msm_thermal_info.ocr_temp_degC - + msm_thermal_info.ocr_temp_hyst_degC)) + auto_cnt++; + } + + if (auto_cnt == max_tsens_num || + ocr_rails[0].init != OPTIMUM_CURRENT_NR) { + /* 'init' not equal to OPTIMUM_CURRENT_NR means this is the + ** first polling iteration after device probe. During first + ** iteration, if temperature is less than the set point, clear + ** the max current request made and reset the 'init'. + */ + if (ocr_rails[0].init != OPTIMUM_CURRENT_NR) + for (j = 0; j < ocr_rail_cnt; j++) + ocr_rails[j].init = OPTIMUM_CURRENT_NR; + ret = ocr_set_mode_all(OPTIMUM_CURRENT_MIN); + if (ret) { + pr_err("Error setting min optimum current\n"); + goto do_ocr_exit; + } + } + +do_ocr_exit: + mutex_unlock(&ocr_mutex); + return ret; +} + +static int do_vdd_restriction(void) +{ + long temp = 0; + int ret = 0; + int i = 0; + int dis_cnt = 0; + + if (!vdd_rstr_enabled) + return ret; + + if (usefreq && !freq_table_get) { + if (check_freq_table()) + return ret; + } + + mutex_lock(&vdd_rstr_mutex); + for (i = 0; i < max_tsens_num; i++) { + ret = therm_get_temp(tsens_id_map[i], THERM_TSENS_ID, &temp); + if (ret) { + pr_debug("Unable to read TSENS sensor %d\n", + tsens_id_map[i]); + dis_cnt++; + continue; + } + if (temp <= msm_thermal_info.vdd_rstr_temp_degC) { + ret = vdd_restriction_apply_all(1); + if (ret) { + pr_err( \ + "Enable vdd rstr votlage for all failed\n"); + goto exit; + } + goto exit; + } else if (temp > msm_thermal_info.vdd_rstr_temp_hyst_degC) + dis_cnt++; + } + if (dis_cnt == max_tsens_num) { + ret = vdd_restriction_apply_all(0); + if (ret) { + pr_err("Disable vdd rstr votlage for all failed\n"); + goto exit; + } + } +exit: + mutex_unlock(&vdd_rstr_mutex); + return ret; +} + +static int do_psm(void) +{ + long temp = 0; + int ret = 0; + int i = 0; + int auto_cnt = 0; + + mutex_lock(&psm_mutex); + for (i = 0; i < max_tsens_num; i++) { + ret = therm_get_temp(tsens_id_map[i], THERM_TSENS_ID, &temp); + if (ret) { + pr_debug("%s: Unable to read TSENS sensor %d\n", + __func__, tsens_id_map[i]); + auto_cnt++; + continue; + } + + /* + * As long as one sensor is above the threshold, set PWM mode + * on all rails, and loop stops. Set auto mode when all rails + * are below thershold + */ + if (temp > msm_thermal_info.psm_temp_degC) { + ret = psm_set_mode_all(PMIC_PWM_MODE); + if (ret) { + pr_err("Set pwm mode for all failed\n"); + goto exit; + } + break; + } else if (temp <= msm_thermal_info.psm_temp_hyst_degC) + auto_cnt++; + } + + if (auto_cnt == max_tsens_num) { + ret = psm_set_mode_all(PMIC_AUTO_MODE); + if (ret) { + pr_err("Set auto mode for all failed\n"); + goto exit; + } + } + +exit: + mutex_unlock(&psm_mutex); + return ret; +} + +static void __ref do_freq_control(long temp) +{ + uint32_t cpu = 0; + uint32_t max_freq = cpus[cpu].limited_max_freq; + + if (temp >= msm_thermal_info.limit_temp_degC) { + if (limit_idx == limit_idx_low) + return; + + limit_idx -= msm_thermal_info.bootup_freq_step; + if (limit_idx < limit_idx_low) + limit_idx = limit_idx_low; + max_freq = table[limit_idx].frequency; + } else if (temp < msm_thermal_info.limit_temp_degC - + msm_thermal_info.temp_hysteresis_degC) { + if (limit_idx == limit_idx_high) + return; + + limit_idx += msm_thermal_info.bootup_freq_step; + if (limit_idx >= limit_idx_high) { + limit_idx = limit_idx_high; + max_freq = UINT_MAX; + } else + max_freq = table[limit_idx].frequency; + } + + if (max_freq == cpus[cpu].limited_max_freq) + return; + + /* Update new limits */ + get_online_cpus(); + for_each_possible_cpu(cpu) { + if (!(msm_thermal_info.bootup_freq_control_mask & BIT(cpu))) + continue; + cpus[cpu].limited_max_freq = max_freq; + update_cpu_freq(cpu); + } + put_online_cpus(); +} + +static void __ref check_temp(struct work_struct *work) +{ + static int limit_init; + long temp = 0; + int ret = 0; + + ret = therm_get_temp(msm_thermal_info.sensor_id, THERM_TSENS_ID, &temp); + if (ret) { + pr_debug("Unable to read TSENS sensor %d\n", + msm_thermal_info.sensor_id); + goto reschedule; + } + + if (!limit_init) { + ret = msm_thermal_get_freq_table(); + if (ret) + goto reschedule; + else + limit_init = 1; + } + + do_core_control(temp); + do_vdd_restriction(); + do_psm(); + do_ocr(); + do_freq_control(temp); + //pr_info("%s: worker is alive!\n", KBUILD_MODNAME); +reschedule: + if (enabled) + schedule_delayed_work(&check_temp_work, + msecs_to_jiffies(msm_thermal_info.poll_ms)); +} + +static int __ref msm_thermal_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + uint32_t cpu = (uint32_t)hcpu; + + if (action == CPU_UP_PREPARE || action == CPU_UP_PREPARE_FROZEN) { + if (core_control_enabled && + (msm_thermal_info.core_control_mask & BIT(cpu)) && + (cpus_offlined & BIT(cpu))) { + pr_debug( + "%s: Preventing cpu%d from coming online.\n", + KBUILD_MODNAME, cpu); + return NOTIFY_BAD; + } + } + + + return NOTIFY_OK; +} + +static struct notifier_block __refdata msm_thermal_cpu_notifier = { + .notifier_call = msm_thermal_cpu_callback, +}; + +static void thermal_rtc_setup(void) +{ + ktime_t wakeup_time; + ktime_t curr_time; + + curr_time = alarm_get_elapsed_realtime(); + wakeup_time = ktime_add_us(curr_time, + (wakeup_ms * USEC_PER_MSEC)); + alarm_start_range(&thermal_rtc, wakeup_time, + wakeup_time); + pr_debug("%s: Current Time: %ld %ld, Alarm set to: %ld %ld\n", + KBUILD_MODNAME, + ktime_to_timeval(curr_time).tv_sec, + ktime_to_timeval(curr_time).tv_usec, + ktime_to_timeval(wakeup_time).tv_sec, + ktime_to_timeval(wakeup_time).tv_usec); + +} + +static void timer_work_fn(struct work_struct *work) +{ + sysfs_notify(tt_kobj, NULL, "wakeup_ms"); +} + +static void thermal_rtc_callback(struct alarm *al) +{ + struct timeval ts; + ts = ktime_to_timeval(alarm_get_elapsed_realtime()); + schedule_work(&timer_work); + pr_debug("%s: Time on alarm expiry: %ld %ld\n", KBUILD_MODNAME, + ts.tv_sec, ts.tv_usec); +} + +static int hotplug_notify(enum thermal_trip_type type, int temp, void *data) +{ + struct cpu_info *cpu_node = (struct cpu_info *)data; + + pr_info("%s: %s reach temp threshold: %d\n", KBUILD_MODNAME, + cpu_node->sensor_type, temp); + + if (!(msm_thermal_info.core_control_mask & BIT(cpu_node->cpu))) + return 0; + switch (type) { + case THERMAL_TRIP_CONFIGURABLE_HI: + if (!(cpu_node->offline)) + cpu_node->offline = 1; + break; + case THERMAL_TRIP_CONFIGURABLE_LOW: + if (cpu_node->offline) + cpu_node->offline = 0; + break; + default: + break; + } + if (hotplug_task) { + cpu_node->hotplug_thresh_clear = true; + complete(&hotplug_notify_complete); + } else { + pr_err("%s: Hotplug task is not initialized\n", KBUILD_MODNAME); + } + return 0; +} +/* Adjust cpus offlined bit based on temperature reading. */ +static int hotplug_init_cpu_offlined(void) +{ + long temp = 0; + uint32_t cpu = 0; + + if (!hotplug_enabled) + return 0; + + mutex_lock(&core_control_mutex); + for_each_possible_cpu(cpu) { + if (!(msm_thermal_info.core_control_mask & BIT(cpus[cpu].cpu))) + continue; + if (therm_get_temp(cpus[cpu].sensor_id, cpus[cpu].id_type, + &temp)) { + pr_err("%s: Unable to read TSENS sensor %d\n", + KBUILD_MODNAME, cpus[cpu].sensor_id); + mutex_unlock(&core_control_mutex); + return -EINVAL; + } + + if (temp >= msm_thermal_info.hotplug_temp_degC) + cpus[cpu].offline = 1; + else if (temp <= (msm_thermal_info.hotplug_temp_degC - + msm_thermal_info.hotplug_temp_hysteresis_degC)) + cpus[cpu].offline = 0; + } + mutex_unlock(&core_control_mutex); + + if (hotplug_task) + complete(&hotplug_notify_complete); + else { + pr_err("%s: Hotplug task is not initialized\n", + KBUILD_MODNAME); + return -EINVAL; + } + return 0; +} + +static void hotplug_init(void) +{ + uint32_t cpu = 0; + struct sensor_threshold *hi_thresh = NULL, *low_thresh = NULL; + + if (hotplug_task) + return; + + if (!hotplug_enabled) + goto init_kthread; + + for_each_possible_cpu(cpu) { + cpus[cpu].sensor_id = + sensor_get_id((char *)cpus[cpu].sensor_type); + cpus[cpu].id_type = THERM_ZONE_ID; + if (!(msm_thermal_info.core_control_mask & BIT(cpus[cpu].cpu))) + continue; + + hi_thresh = &cpus[cpu].threshold[HOTPLUG_THRESHOLD_HIGH]; + low_thresh = &cpus[cpu].threshold[HOTPLUG_THRESHOLD_LOW]; + hi_thresh->temp = msm_thermal_info.hotplug_temp_degC; + hi_thresh->trip = THERMAL_TRIP_CONFIGURABLE_HI; + low_thresh->temp = msm_thermal_info.hotplug_temp_degC - + msm_thermal_info.hotplug_temp_hysteresis_degC; + low_thresh->trip = THERMAL_TRIP_CONFIGURABLE_LOW; + hi_thresh->notify = low_thresh->notify = hotplug_notify; + hi_thresh->data = low_thresh->data = (void *)&cpus[cpu]; + + set_threshold(cpus[cpu].sensor_id, hi_thresh); + } +init_kthread: + init_completion(&hotplug_notify_complete); + hotplug_task = kthread_run(do_hotplug, NULL, "msm_thermal:hotplug"); + if (IS_ERR(hotplug_task)) { + pr_err("%s: Failed to create do_hotplug thread\n", + KBUILD_MODNAME); + return; + } + /* + * Adjust cpus offlined bit when hotplug intitializes so that the new + * cpus offlined state is based on hotplug threshold range + */ + if (hotplug_init_cpu_offlined()) + kthread_stop(hotplug_task); +} + +static __ref int do_freq_mitigation(void *data) +{ + int ret = 0; + uint32_t cpu = 0, max_freq_req = 0, min_freq_req = 0; + + while (!kthread_should_stop()) { + wait_for_completion(&freq_mitigation_complete); + INIT_COMPLETION(freq_mitigation_complete); + + get_online_cpus(); + for_each_possible_cpu(cpu) { + max_freq_req = (cpus[cpu].max_freq) ? + msm_thermal_info.freq_limit : + UINT_MAX; + max_freq_req = min(max_freq_req, + cpus[cpu].user_max_freq); + + min_freq_req = max(min_freq_limit, + cpus[cpu].user_min_freq); + + if ((max_freq_req == cpus[cpu].limited_max_freq) + && (min_freq_req == + cpus[cpu].limited_min_freq)) + goto reset_threshold; + + cpus[cpu].limited_max_freq = max_freq_req; + cpus[cpu].limited_min_freq = min_freq_req; + update_cpu_freq(cpu); +reset_threshold: + if (freq_mitigation_enabled && + cpus[cpu].freq_thresh_clear) { + set_threshold(cpus[cpu].sensor_id, + &cpus[cpu].threshold[FREQ_THRESHOLD_HIGH]); + + cpus[cpu].freq_thresh_clear = false; + } + } + put_online_cpus(); + } + return ret; +} + +static int freq_mitigation_notify(enum thermal_trip_type type, + int temp, void *data) +{ + struct cpu_info *cpu_node = (struct cpu_info *) data; + + pr_debug("%s: %s reached temp threshold: %d\n", KBUILD_MODNAME, + cpu_node->sensor_type, temp); + + if (!(msm_thermal_info.freq_mitig_control_mask & + BIT(cpu_node->cpu))) + return 0; + + switch (type) { + case THERMAL_TRIP_CONFIGURABLE_HI: + if (!cpu_node->max_freq) { + pr_info("%s: Mitigating cpu %d frequency to %d\n", + KBUILD_MODNAME, cpu_node->cpu, + msm_thermal_info.freq_limit); + + cpu_node->max_freq = true; + } + break; + case THERMAL_TRIP_CONFIGURABLE_LOW: + if (cpu_node->max_freq) { + pr_info("%s: Removing frequency mitigation for cpu%d\n", + KBUILD_MODNAME, cpu_node->cpu); + + cpu_node->max_freq = false; + } + break; + default: + break; + } + + if (freq_mitigation_task) { + cpu_node->freq_thresh_clear = true; + complete(&freq_mitigation_complete); + } else { + pr_err("%s: Frequency mitigation task is not initialized\n", + KBUILD_MODNAME); + } + + return 0; +} + +static void freq_mitigation_init(void) +{ + uint32_t cpu = 0; + struct sensor_threshold *hi_thresh = NULL, *low_thresh = NULL; + + if (freq_mitigation_task) + return; + if (!freq_mitigation_enabled) + goto init_freq_thread; + + for_each_possible_cpu(cpu) { + if (!(msm_thermal_info.freq_mitig_control_mask & BIT(cpu))) + continue; + hi_thresh = &cpus[cpu].threshold[FREQ_THRESHOLD_HIGH]; + low_thresh = &cpus[cpu].threshold[FREQ_THRESHOLD_LOW]; + + hi_thresh->temp = msm_thermal_info.freq_mitig_temp_degc; + hi_thresh->trip = THERMAL_TRIP_CONFIGURABLE_HI; + low_thresh->temp = msm_thermal_info.freq_mitig_temp_degc - + msm_thermal_info.freq_mitig_temp_hysteresis_degc; + low_thresh->trip = THERMAL_TRIP_CONFIGURABLE_LOW; + hi_thresh->notify = low_thresh->notify = + freq_mitigation_notify; + hi_thresh->data = low_thresh->data = (void *)&cpus[cpu]; + + set_threshold(cpus[cpu].sensor_id, hi_thresh); + } +init_freq_thread: + init_completion(&freq_mitigation_complete); + freq_mitigation_task = kthread_run(do_freq_mitigation, NULL, + "msm_thermal:freq_mitig"); + + if (IS_ERR(freq_mitigation_task)) { + pr_err("%s: Failed to create frequency mitigation thread\n", + KBUILD_MODNAME); + return; + } +} + +int msm_thermal_set_frequency(uint32_t cpu, uint32_t freq, bool is_max) +{ + int ret = 0; + + if (cpu >= num_possible_cpus()) { + pr_err("%s: Invalid input %u for frequency\n", KBUILD_MODNAME, + cpu); + ret = -EINVAL; + goto set_freq_exit; + } + + if (is_max) { + if (cpus[cpu].user_max_freq == freq) + goto set_freq_exit; + + cpus[cpu].user_max_freq = freq; + } else { + if (cpus[cpu].user_min_freq == freq) + goto set_freq_exit; + + cpus[cpu].user_min_freq = freq; + } + + if (freq_mitigation_task) { + complete(&freq_mitigation_complete); + } else { + pr_err("%s: Frequency mitigation task is not initialized\n", + KBUILD_MODNAME); + ret = -ESRCH; + goto set_freq_exit; + } + +set_freq_exit: + return ret; +} + +/* + * We will reset the cpu frequencies limits here. The core online/offline + * status will be carried over to the process stopping the msm_thermal, as + * we dont want to online a core and bring in the thermal issues. + */ +static void __ref disable_msm_thermal(void) +{ + uint32_t cpu = 0; + + /* make sure check_temp is no longer running */ + cancel_delayed_work_sync(&check_temp_work); + + get_online_cpus(); + for_each_possible_cpu(cpu) { + if (cpus[cpu].limited_max_freq == UINT_MAX && + cpus[cpu].limited_min_freq == 0) + continue; + cpus[cpu].limited_max_freq = UINT_MAX; + cpus[cpu].limited_min_freq = 0; + update_cpu_freq(cpu); + } + put_online_cpus(); +} + +static int __ref set_enabled(const char *val, const struct kernel_param *kp) +{ + int ret = 0; + + if (*val == '0' || *val == 'n' || *val == 'N') { + enabled = 0; + disable_msm_thermal(); + hotplug_init(); + freq_mitigation_init(); + pr_info("%s: intellithermal disabled!\n", KBUILD_MODNAME); + } else { + if (!enabled) { + enabled = 1; + schedule_delayed_work(&check_temp_work, + msecs_to_jiffies(msm_thermal_info.poll_ms)); + pr_info("%s: rescheduling...\n", KBUILD_MODNAME); + } else + pr_info("%s: already running...\n", KBUILD_MODNAME); + } + + pr_info("%s: enabled = %d\n", KBUILD_MODNAME, enabled); + + return ret; +} + +static struct kernel_param_ops module_ops = { + .set = set_enabled, + .get = param_get_bool, +}; + +module_param_cb(enabled, &module_ops, &enabled, 0644); +MODULE_PARM_DESC(enabled, "enforce thermal limit on cpu"); + +static ssize_t show_cc_enabled(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", core_control_enabled); +} + +static ssize_t __ref store_cc_enabled(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int ret = 0; + int val = 0; + + ret = kstrtoint(buf, 10, &val); + if (ret) { + pr_err("%s: Invalid input %s\n", KBUILD_MODNAME, buf); + goto done_store_cc; + } + + if (core_control_enabled == !!val) + goto done_store_cc; + + core_control_enabled = !!val; + if (core_control_enabled) { + pr_info("%s: Core control enabled\n", KBUILD_MODNAME); + register_cpu_notifier(&msm_thermal_cpu_notifier); + if (hotplug_task) + complete(&hotplug_notify_complete); + else + pr_err("%s: Hotplug task is not initialized\n", + KBUILD_MODNAME); + } else { + pr_info("%s: Core control disabled\n", KBUILD_MODNAME); + unregister_cpu_notifier(&msm_thermal_cpu_notifier); + } + +done_store_cc: + return count; +} + +static ssize_t show_cpus_offlined(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", cpus_offlined); +} + +static ssize_t __ref store_cpus_offlined(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int ret = 0; + uint32_t val = 0; + uint32_t cpu; + + mutex_lock(&core_control_mutex); + ret = kstrtouint(buf, 10, &val); + if (ret) { + pr_err("%s: Invalid input %s\n", KBUILD_MODNAME, buf); + goto done_cc; + } + + if (enabled) { + pr_err("%s: Ignoring request; polling thread is enabled.\n", + KBUILD_MODNAME); + goto done_cc; + } + + for_each_possible_cpu(cpu) { + if (!(msm_thermal_info.core_control_mask & BIT(cpu))) + continue; + cpus[cpu].user_offline = !!(val & BIT(cpu)); + } + + if (hotplug_task) + complete(&hotplug_notify_complete); + else + pr_err("%s: Hotplug task is not initialized\n", KBUILD_MODNAME); +done_cc: + mutex_unlock(&core_control_mutex); + return count; +} + +static __refdata struct kobj_attribute cc_enabled_attr = +__ATTR(enabled, 0644, show_cc_enabled, store_cc_enabled); + +static __refdata struct kobj_attribute cpus_offlined_attr = +__ATTR(cpus_offlined, 0644, show_cpus_offlined, store_cpus_offlined); + +static __refdata struct attribute *cc_attrs[] = { + &cc_enabled_attr.attr, + &cpus_offlined_attr.attr, + NULL, +}; + +static __refdata struct attribute_group cc_attr_group = { + .attrs = cc_attrs, +}; + +static ssize_t show_wakeup_ms(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", wakeup_ms); +} + +static ssize_t store_wakeup_ms(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int ret; + ret = kstrtouint(buf, 10, &wakeup_ms); + + if (ret) { + pr_err("%s: Trying to set invalid wakeup timer\n", + KBUILD_MODNAME); + return ret; + } + + if (wakeup_ms > 0) { + thermal_rtc_setup(); + pr_debug("%s: Timer started for %ums\n", KBUILD_MODNAME, + wakeup_ms); + } else { + ret = alarm_cancel(&thermal_rtc); + if (ret) + pr_debug("%s: Timer canceled\n", KBUILD_MODNAME); + else + pr_debug("%s: No active timer present to cancel\n", + KBUILD_MODNAME); + + } + return count; +} + +static __refdata struct kobj_attribute timer_attr = +__ATTR(wakeup_ms, 0644, show_wakeup_ms, store_wakeup_ms); + +static __refdata struct attribute *tt_attrs[] = { + &timer_attr.attr, + NULL, +}; + +static __refdata struct attribute_group tt_attr_group = { + .attrs = tt_attrs, +}; + +static __init int msm_thermal_add_cc_nodes(void) +{ + struct kobject *module_kobj = NULL; + int ret = 0; + + module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); + if (!module_kobj) { + pr_err("%s: cannot find kobject for module\n", + KBUILD_MODNAME); + ret = -ENOENT; + goto done_cc_nodes; + } + + cc_kobj = kobject_create_and_add("core_control", module_kobj); + if (!cc_kobj) { + pr_err("%s: cannot create core control kobj\n", + KBUILD_MODNAME); + ret = -ENOMEM; + goto done_cc_nodes; + } + + ret = sysfs_create_group(cc_kobj, &cc_attr_group); + if (ret) { + pr_err("%s: cannot create group\n", KBUILD_MODNAME); + goto done_cc_nodes; + } + + return 0; + +done_cc_nodes: + if (cc_kobj) + kobject_del(cc_kobj); + return ret; +} + +static __init int msm_thermal_add_timer_nodes(void) +{ + struct kobject *module_kobj = NULL; + int ret = 0; + + module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); + if (!module_kobj) { + pr_err("%s: cannot find kobject for module\n", + KBUILD_MODNAME); + ret = -ENOENT; + goto failed; + } + + tt_kobj = kobject_create_and_add("thermal_timer", module_kobj); + if (!tt_kobj) { + pr_err("%s: cannot create timer kobj\n", + KBUILD_MODNAME); + ret = -ENOMEM; + goto failed; + } + + ret = sysfs_create_group(tt_kobj, &tt_attr_group); + if (ret) { + pr_err("%s: cannot create group\n", KBUILD_MODNAME); + goto failed; + } + + return 0; + +failed: + if (tt_kobj) + kobject_del(tt_kobj); + return ret; +} + +static ssize_t show_thermal_stats(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + + int i = 0; + int tmp = 0; + + /* clear out old stats */ + msm_thermal_stats.throttled = 0; + msm_thermal_stats.warning = 0; + msm_thermal_stats.normal = 0; + + for (i = 0; i < MAX_HISTORY_SZ; i++) { + tmp = msm_thermal_stats.temp_history[i]; + if (tmp >= msm_thermal_info.limit_temp_degC) + msm_thermal_stats.throttled++; + else if (tmp < msm_thermal_info.limit_temp_degC && + tmp >= (msm_thermal_info.limit_temp_degC - + msm_thermal_info.temp_hysteresis_degC)) + msm_thermal_stats.warning++; + else + msm_thermal_stats.normal++; + } + return snprintf(buf, PAGE_SIZE, "%u %u %u\n", + msm_thermal_stats.throttled, + msm_thermal_stats.warning, + msm_thermal_stats.normal); +} + +static __refdata struct kobj_attribute msm_thermal_stat_attr = +__ATTR(statistics, 0444, show_thermal_stats, NULL); + +static __refdata struct attribute *msm_thermal_stat_attrs[] = { + &msm_thermal_stat_attr.attr, + NULL, +}; + +static __refdata struct attribute_group msm_thermal_stat_attr_group = { + .attrs = msm_thermal_stat_attrs, +}; + +static __init int msm_thermal_add_stat_nodes(void) +{ + struct kobject *module_kobj = NULL; + struct kobject *stat_kobj = NULL; + int ret = 0; + + module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); + if (!module_kobj) { + pr_err("%s: cannot find kobject for module\n", + KBUILD_MODNAME); + ret = -ENOENT; + goto done_stat_nodes; + } + + stat_kobj = kobject_create_and_add("thermal_stats", module_kobj); + if (!stat_kobj) { + pr_err("%s: cannot create core control kobj\n", + KBUILD_MODNAME); + ret = -ENOMEM; + goto done_stat_nodes; + } + + ret = sysfs_create_group(stat_kobj, &msm_thermal_stat_attr_group); + if (ret) { + pr_err("%s: cannot create group\n", KBUILD_MODNAME); + goto done_stat_nodes; + } + + return 0; + +done_stat_nodes: + if (stat_kobj) + kobject_del(stat_kobj); + return ret; +} + +int __devinit msm_thermal_init(struct msm_thermal_data *pdata) +{ + int ret = 0; + uint32_t cpu; + + for_each_possible_cpu(cpu) { + cpus[cpu].cpu = cpu; + cpus[cpu].offline = 0; + cpus[cpu].user_offline = 0; + cpus[cpu].hotplug_thresh_clear = false; + cpus[cpu].max_freq = false; + cpus[cpu].user_max_freq = UINT_MAX; + cpus[cpu].user_min_freq = 0; + cpus[cpu].limited_max_freq = UINT_MAX; + cpus[cpu].limited_min_freq = 0; + cpus[cpu].freq_thresh_clear = false; + } + BUG_ON(!pdata); + tsens_get_max_sensor_num(&max_tsens_num); + memcpy(&msm_thermal_info, pdata, sizeof(struct msm_thermal_data)); + + if (create_sensor_id_map()) + return -EINVAL; + if (check_sensor_id(msm_thermal_info.sensor_id)) + return -EINVAL; + + enabled = 1; + pr_info("%s: polling enabled!\n", KBUILD_MODNAME); + ret = cpufreq_register_notifier(&msm_thermal_cpufreq_notifier, + CPUFREQ_POLICY_NOTIFIER); + if (ret) + pr_err("%s: cannot register cpufreq notifier\n", + KBUILD_MODNAME); + INIT_DELAYED_WORK(&check_temp_work, check_temp); + schedule_delayed_work(&check_temp_work, 0); + + if (num_possible_cpus() > 1) + register_cpu_notifier(&msm_thermal_cpu_notifier); + + /* emulate default behavior */ + disable_msm_thermal(); + hotplug_init(); + freq_mitigation_init(); + enabled = 0; + + return ret; +} + +static int ocr_reg_init(struct platform_device *pdev) +{ + int ret = 0; + int i, j; + + for (i = 0; i < ocr_rail_cnt; i++) { + /* Check if vdd_restriction has already initialized any + * regualtor handle. If so use the same handle.*/ + for (j = 0; j < rails_cnt; j++) { + if (!strcmp(ocr_rails[i].name, rails[j].name)) { + if (rails[j].reg == NULL) + break; + ocr_rails[i].phase_reg = rails[j].reg; + goto reg_init; + } + + } + ocr_rails[i].phase_reg = devm_regulator_get(&pdev->dev, + ocr_rails[i].name); + if (IS_ERR_OR_NULL(ocr_rails[i].phase_reg)) { + ret = PTR_ERR(ocr_rails[i].phase_reg); + if (ret != -EPROBE_DEFER) { + pr_err("%s, could not get regulator: %s\n", + __func__, ocr_rails[i].name); + ocr_rails[i].phase_reg = NULL; + ocr_rails[i].mode = 0; + ocr_rails[i].init = 0; + } + return ret; + } +reg_init: + ocr_rails[i].mode = OPTIMUM_CURRENT_MIN; + } + return ret; +} + +static int vdd_restriction_reg_init(struct platform_device *pdev) +{ + int ret = 0; + int i; + + for (i = 0; i < rails_cnt; i++) { + if (rails[i].freq_req == 1) { + usefreq |= BIT(i); + check_freq_table(); + /* + * Restrict frequency by default until we have made + * our first temp reading + */ + if (freq_table_get) + ret = vdd_restriction_apply_freq(&rails[i], 0); + else + pr_info("%s:Defer vdd rstr freq init\n", + __func__); + } else { + rails[i].reg = devm_regulator_get(&pdev->dev, + rails[i].name); + if (IS_ERR_OR_NULL(rails[i].reg)) { + ret = PTR_ERR(rails[i].reg); + if (ret != -EPROBE_DEFER) { + pr_err( \ + "%s, could not get regulator: %s\n", + rails[i].name, __func__); + rails[i].reg = NULL; + rails[i].curr_level = -2; + return ret; + } + return ret; + } + /* + * Restrict votlage by default until we have made + * our first temp reading + */ + ret = vdd_restriction_apply_voltage(&rails[i], 0); + } + } + + return ret; +} + +static int psm_reg_init(struct platform_device *pdev) +{ + int ret = 0; + int i = 0; + int j = 0; + + for (i = 0; i < psm_rails_cnt; i++) { + psm_rails[i].reg = rpm_regulator_get(&pdev->dev, + psm_rails[i].name); + if (IS_ERR_OR_NULL(psm_rails[i].reg)) { + ret = PTR_ERR(psm_rails[i].reg); + if (ret != -EPROBE_DEFER) { + pr_err("%s, could not get rpm regulator: %s\n", + psm_rails[i].name, __func__); + psm_rails[i].reg = NULL; + goto psm_reg_exit; + } + return ret; + } + /* Apps default vote for PWM mode */ + psm_rails[i].init = PMIC_PWM_MODE; + ret = rpm_regulator_set_mode(psm_rails[i].reg, + psm_rails[i].init); + if (ret) { + pr_err("%s: Cannot set PMIC PWM mode\n", __func__); + return ret; + } else + psm_rails[i].mode = PMIC_PWM_MODE; + } + + return ret; + +psm_reg_exit: + if (ret) { + for (j = 0; j < i; j++) { + if (psm_rails[j].reg != NULL) + rpm_regulator_put(psm_rails[j].reg); + } + } + + return ret; +} + +static int msm_thermal_add_vdd_rstr_nodes(void) +{ + struct kobject *module_kobj = NULL; + struct kobject *vdd_rstr_kobj = NULL; + struct kobject *vdd_rstr_reg_kobj[MAX_RAILS] = {0}; + int rc = 0; + int i = 0; + + if (!vdd_rstr_probed) { + vdd_rstr_nodes_called = true; + return rc; + } + + if (vdd_rstr_probed && rails_cnt == 0) + return rc; + + module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); + if (!module_kobj) { + pr_err("%s: cannot find kobject for module %s\n", + __func__, KBUILD_MODNAME); + rc = -ENOENT; + goto thermal_sysfs_add_exit; + } + + vdd_rstr_kobj = kobject_create_and_add("vdd_restriction", module_kobj); + if (!vdd_rstr_kobj) { + pr_err("%s: cannot create vdd_restriction kobject\n", __func__); + rc = -ENOMEM; + goto thermal_sysfs_add_exit; + } + + rc = sysfs_create_group(vdd_rstr_kobj, &vdd_rstr_en_attribs_gp); + if (rc) { + pr_err("%s: cannot create kobject attribute group\n", __func__); + rc = -ENOMEM; + goto thermal_sysfs_add_exit; + } + + for (i = 0; i < rails_cnt; i++) { + vdd_rstr_reg_kobj[i] = kobject_create_and_add(rails[i].name, + vdd_rstr_kobj); + if (!vdd_rstr_reg_kobj[i]) { + pr_err("%s: cannot create for kobject for %s\n", + __func__, rails[i].name); + rc = -ENOMEM; + goto thermal_sysfs_add_exit; + } + + rails[i].attr_gp.attrs = kzalloc(sizeof(struct attribute *) * 3, + GFP_KERNEL); + if (!rails[i].attr_gp.attrs) { + rc = -ENOMEM; + goto thermal_sysfs_add_exit; + } + + VDD_RES_RW_ATTRIB(rails[i], rails[i].level_attr, 0, level); + VDD_RES_RO_ATTRIB(rails[i], rails[i].value_attr, 1, value); + rails[i].attr_gp.attrs[2] = NULL; + + rc = sysfs_create_group(vdd_rstr_reg_kobj[i], + &rails[i].attr_gp); + if (rc) { + pr_err("%s: cannot create attribute group for %s\n", + __func__, rails[i].name); + goto thermal_sysfs_add_exit; + } + } + + return rc; + +thermal_sysfs_add_exit: + if (rc) { + for (i = 0; i < rails_cnt; i++) { + kobject_del(vdd_rstr_reg_kobj[i]); + kfree(rails[i].attr_gp.attrs); + } + if (vdd_rstr_kobj) + kobject_del(vdd_rstr_kobj); + } + return rc; +} + +static int msm_thermal_add_ocr_nodes(void) +{ + struct kobject *module_kobj = NULL; + struct kobject *ocr_kobj = NULL; + struct kobject *ocr_reg_kobj[MAX_RAILS] = {0}; + int rc = 0; + int i = 0; + + if (!ocr_probed) { + ocr_nodes_called = true; + return rc; + } + + if (ocr_probed && ocr_rail_cnt == 0) + return rc; + + module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); + if (!module_kobj) { + pr_err("%s: cannot find kobject for module %s\n", + __func__, KBUILD_MODNAME); + rc = -ENOENT; + goto ocr_node_exit; + } + + ocr_kobj = kobject_create_and_add("opt_curr_req", module_kobj); + if (!ocr_kobj) { + pr_err("%s: cannot create ocr kobject\n", KBUILD_MODNAME); + rc = -ENOMEM; + goto ocr_node_exit; + } + + for (i = 0; i < ocr_rail_cnt; i++) { + ocr_reg_kobj[i] = kobject_create_and_add(ocr_rails[i].name, + ocr_kobj); + if (!ocr_reg_kobj[i]) { + pr_err("%s: cannot create for kobject for %s\n", + KBUILD_MODNAME, ocr_rails[i].name); + rc = -ENOMEM; + goto ocr_node_exit; + } + ocr_rails[i].attr_gp.attrs = kzalloc( \ + sizeof(struct attribute *) * 2, GFP_KERNEL); + if (!ocr_rails[i].attr_gp.attrs) { + rc = -ENOMEM; + goto ocr_node_exit; + } + + OCR_RW_ATTRIB(ocr_rails[i], ocr_rails[i].mode_attr, 0, mode); + ocr_rails[i].attr_gp.attrs[1] = NULL; + + rc = sysfs_create_group(ocr_reg_kobj[i], &ocr_rails[i].attr_gp); + if (rc) { + pr_err("%s: cannot create attribute group for %s\n", + KBUILD_MODNAME, ocr_rails[i].name); + goto ocr_node_exit; + } + } + +ocr_node_exit: + if (rc) { + for (i = 0; i < ocr_rail_cnt; i++) { + if (ocr_reg_kobj[i]) + kobject_del(ocr_reg_kobj[i]); + if (ocr_rails[i].attr_gp.attrs) { + kfree(ocr_rails[i].attr_gp.attrs); + ocr_rails[i].attr_gp.attrs = NULL; + } + } + if (ocr_kobj) + kobject_del(ocr_kobj); + } + return rc; +} + +static int msm_thermal_add_psm_nodes(void) +{ + struct kobject *module_kobj = NULL; + struct kobject *psm_kobj = NULL; + struct kobject *psm_reg_kobj[MAX_RAILS] = {0}; + int rc = 0; + int i = 0; + + if (!psm_probed) { + psm_nodes_called = true; + return rc; + } + + if (psm_probed && psm_rails_cnt == 0) + return rc; + + module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); + if (!module_kobj) { + pr_err("%s: cannot find kobject for module %s\n", + __func__, KBUILD_MODNAME); + rc = -ENOENT; + goto psm_node_exit; + } + + psm_kobj = kobject_create_and_add("pmic_sw_mode", module_kobj); + if (!psm_kobj) { + pr_err("%s: cannot create psm kobject\n", KBUILD_MODNAME); + rc = -ENOMEM; + goto psm_node_exit; + } + + for (i = 0; i < psm_rails_cnt; i++) { + psm_reg_kobj[i] = kobject_create_and_add(psm_rails[i].name, + psm_kobj); + if (!psm_reg_kobj[i]) { + pr_err("%s: cannot create for kobject for %s\n", + KBUILD_MODNAME, psm_rails[i].name); + rc = -ENOMEM; + goto psm_node_exit; + } + psm_rails[i].attr_gp.attrs = kzalloc( \ + sizeof(struct attribute *) * 2, GFP_KERNEL); + if (!psm_rails[i].attr_gp.attrs) { + rc = -ENOMEM; + goto psm_node_exit; + } + + PSM_RW_ATTRIB(psm_rails[i], psm_rails[i].mode_attr, 0, mode); + psm_rails[i].attr_gp.attrs[1] = NULL; + + rc = sysfs_create_group(psm_reg_kobj[i], &psm_rails[i].attr_gp); + if (rc) { + pr_err("%s: cannot create attribute group for %s\n", + KBUILD_MODNAME, psm_rails[i].name); + goto psm_node_exit; + } + } + + return rc; + +psm_node_exit: + if (rc) { + for (i = 0; i < psm_rails_cnt; i++) { + kobject_del(psm_reg_kobj[i]); + kfree(psm_rails[i].attr_gp.attrs); + } + if (psm_kobj) + kobject_del(psm_kobj); + } + return rc; +} + +static int probe_vdd_rstr(struct device_node *node, + struct msm_thermal_data *data, struct platform_device *pdev) +{ + int ret = 0; + int i = 0; + int arr_size; + char *key = NULL; + struct device_node *child_node = NULL; + + rails = NULL; + + key = "qcom,vdd-restriction-temp"; + ret = of_property_read_u32(node, key, &data->vdd_rstr_temp_degC); + if (ret) + goto read_node_fail; + + key = "qcom,vdd-restriction-temp-hysteresis"; + ret = of_property_read_u32(node, key, &data->vdd_rstr_temp_hyst_degC); + if (ret) + goto read_node_fail; + + for_each_child_of_node(node, child_node) { + rails_cnt++; + } + + if (rails_cnt == 0) + goto read_node_fail; + if (rails_cnt >= MAX_RAILS) { + pr_err("%s: Too many rails.\n", __func__); + return -EFAULT; + } + + rails = kzalloc(sizeof(struct rail) * rails_cnt, + GFP_KERNEL); + if (!rails) { + pr_err("%s: Fail to allocate memory for rails.\n", __func__); + return -ENOMEM; + } + + i = 0; + for_each_child_of_node(node, child_node) { + key = "qcom,vdd-rstr-reg"; + ret = of_property_read_string(child_node, key, &rails[i].name); + if (ret) + goto read_node_fail; + + key = "qcom,levels"; + if (!of_get_property(child_node, key, &arr_size)) + goto read_node_fail; + rails[i].num_levels = arr_size/sizeof(__be32); + if (rails[i].num_levels > + sizeof(rails[i].levels)/sizeof(uint32_t)) { + pr_err("%s: Array size too large\n", __func__); + return -EFAULT; + } + ret = of_property_read_u32_array(child_node, key, + rails[i].levels, rails[i].num_levels); + if (ret) + goto read_node_fail; + + key = "qcom,freq-req"; + rails[i].freq_req = of_property_read_bool(child_node, key); + if (rails[i].freq_req) + rails[i].min_level = 0; + else { + key = "qcom,min-level"; + ret = of_property_read_u32(child_node, key, + &rails[i].min_level); + if (ret) + goto read_node_fail; + } + + rails[i].curr_level = -1; + rails[i].reg = NULL; + i++; + } + + if (rails_cnt) { + ret = vdd_restriction_reg_init(pdev); + if (ret) { + pr_info("%s:Failed to get regulators. KTM continues.\n", + __func__); + goto read_node_fail; + } + vdd_rstr_enabled = true; + } +read_node_fail: + vdd_rstr_probed = true; + if (ret) { + dev_info(&pdev->dev, + "%s:Failed reading node=%s, key=%s. KTM continues\n", + __func__, node->full_name, key); + kfree(rails); + rails_cnt = 0; + } + if (ret == -EPROBE_DEFER) + vdd_rstr_probed = false; + return ret; +} + +static int probe_ocr(struct device_node *node, struct msm_thermal_data *data, + struct platform_device *pdev) +{ + int ret = 0; + int j = 0; + char *key = NULL; + + if (ocr_probed) { + pr_info("%s: Nodes already probed\n", + __func__); + goto read_ocr_exit; + } + ocr_rails = NULL; + + key = "qti,pmic-opt-curr-temp"; + ret = of_property_read_u32(node, key, &data->ocr_temp_degC); + if (ret) + goto read_ocr_fail; + + key = "qti,pmic-opt-curr-temp-hysteresis"; + ret = of_property_read_u32(node, key, &data->ocr_temp_hyst_degC); + if (ret) + goto read_ocr_fail; + + key = "qti,pmic-opt-curr-regs"; + ocr_rail_cnt = of_property_count_strings(node, key); + ocr_rails = kzalloc(sizeof(struct psm_rail) * ocr_rail_cnt, + GFP_KERNEL); + if (!ocr_rails) { + pr_err("%s: Fail to allocate memory for ocr rails\n", __func__); + ocr_rail_cnt = 0; + return -ENOMEM; + } + + for (j = 0; j < ocr_rail_cnt; j++) { + ret = of_property_read_string_index(node, key, j, + &ocr_rails[j].name); + if (ret) + goto read_ocr_fail; + ocr_rails[j].phase_reg = NULL; + ocr_rails[j].init = OPTIMUM_CURRENT_MAX; + } + + if (ocr_rail_cnt) { + ret = ocr_reg_init(pdev); + if (ret) { + pr_info("%s:Failed to get regulators. KTM continues.\n", + __func__); + goto read_ocr_fail; + } + ocr_enabled = true; + ocr_nodes_called = false; + /* + * Vote for max optimum current by default until we have made + * our first temp reading + */ + if (ocr_set_mode_all(OPTIMUM_CURRENT_MAX)) + pr_err("Set max optimum current failed\n"); + } + +read_ocr_fail: + ocr_probed = true; + if (ret) { + dev_info(&pdev->dev, + "%s:Failed reading node=%s, key=%s. KTM continues\n", + __func__, node->full_name, key); + if (ocr_rails) + kfree(ocr_rails); + ocr_rails = NULL; + ocr_rail_cnt = 0; + } + if (ret == -EPROBE_DEFER) + ocr_probed = false; +read_ocr_exit: + return ret; +} + +static int probe_psm(struct device_node *node, struct msm_thermal_data *data, + struct platform_device *pdev) +{ + int ret = 0; + int j = 0; + char *key = NULL; + + psm_rails = NULL; + + key = "qcom,pmic-sw-mode-temp"; + ret = of_property_read_u32(node, key, &data->psm_temp_degC); + if (ret) + goto read_node_fail; + + key = "qcom,pmic-sw-mode-temp-hysteresis"; + ret = of_property_read_u32(node, key, &data->psm_temp_hyst_degC); + if (ret) + goto read_node_fail; + + key = "qcom,pmic-sw-mode-regs"; + psm_rails_cnt = of_property_count_strings(node, key); + psm_rails = kzalloc(sizeof(struct psm_rail) * psm_rails_cnt, + GFP_KERNEL); + if (!psm_rails) { + pr_err("%s: Fail to allocate memory for psm rails\n", __func__); + psm_rails_cnt = 0; + return -ENOMEM; + } + + for (j = 0; j < psm_rails_cnt; j++) { + ret = of_property_read_string_index(node, key, j, + &psm_rails[j].name); + if (ret) + goto read_node_fail; + } + + if (psm_rails_cnt) { + ret = psm_reg_init(pdev); + if (ret) { + pr_info("%s:Failed to get regulators. KTM continues.\n", + __func__); + goto read_node_fail; + } + psm_enabled = true; + } + +read_node_fail: + psm_probed = true; + if (ret) { + dev_info(&pdev->dev, + "%s:Failed reading node=%s, key=%s. KTM continues\n", + __func__, node->full_name, key); + kfree(psm_rails); + psm_rails_cnt = 0; + } + if (ret == -EPROBE_DEFER) + psm_probed = false; + return ret; +} + +static int probe_cc(struct device_node *node, struct msm_thermal_data *data, + struct platform_device *pdev) +{ + char *key = NULL; + uint32_t cpu_cnt = 0; + int ret = 0; + uint32_t cpu = 0; + + if (num_possible_cpus() > 1) { + core_control_enabled = 1; + hotplug_enabled = 1; + } + + key = "qcom,core-limit-temp"; + ret = of_property_read_u32(node, key, &data->core_limit_temp_degC); + if (ret) + goto read_node_fail; + + key = "qcom,core-temp-hysteresis"; + ret = of_property_read_u32(node, key, &data->core_temp_hysteresis_degC); + if (ret) + goto read_node_fail; + + key = "qcom,core-control-mask"; + ret = of_property_read_u32(node, key, &data->core_control_mask); + if (ret) + goto read_node_fail; + + key = "qcom,hotplug-temp"; + ret = of_property_read_u32(node, key, &data->hotplug_temp_degC); + if (ret) + goto hotplug_node_fail; + + key = "qcom,hotplug-temp-hysteresis"; + ret = of_property_read_u32(node, key, + &data->hotplug_temp_hysteresis_degC); + if (ret) + goto hotplug_node_fail; + + key = "qcom,cpu-sensors"; + cpu_cnt = of_property_count_strings(node, key); + if (cpu_cnt < num_possible_cpus()) { + pr_err("%s: Wrong number of cpu sensors\n", KBUILD_MODNAME); + ret = -EINVAL; + goto hotplug_node_fail; + } + + for_each_possible_cpu(cpu) { + ret = of_property_read_string_index(node, key, cpu, + &cpus[cpu].sensor_type); + if (ret) + goto hotplug_node_fail; + } + +read_node_fail: + if (ret) { + dev_info(&pdev->dev, + "%s:Failed reading node=%s, key=%s. KTM continues\n", + KBUILD_MODNAME, node->full_name, key); + core_control_enabled = 0; + } + + return ret; + +hotplug_node_fail: + if (ret) { + dev_info(&pdev->dev, + "%s:Failed reading node=%s, key=%s. KTM continues\n", + KBUILD_MODNAME, node->full_name, key); + hotplug_enabled = 0; + } + + return ret; +} + +static int probe_freq_mitigation(struct device_node *node, + struct msm_thermal_data *data, + struct platform_device *pdev) +{ + char *key = NULL; + int ret = 0; + + key = "qcom,freq-mitigation-temp"; + ret = of_property_read_u32(node, key, &data->freq_mitig_temp_degc); + if (ret) + goto PROBE_FREQ_EXIT; + + key = "qcom,freq-mitigation-temp-hysteresis"; + ret = of_property_read_u32(node, key, + &data->freq_mitig_temp_hysteresis_degc); + if (ret) + goto PROBE_FREQ_EXIT; + + key = "qcom,freq-mitigation-value"; + ret = of_property_read_u32(node, key, &data->freq_limit); + if (ret) + goto PROBE_FREQ_EXIT; + + key = "qcom,freq-mitigation-control-mask"; + ret = of_property_read_u32(node, key, &data->freq_mitig_control_mask); + if (ret) + goto PROBE_FREQ_EXIT; + + freq_mitigation_enabled = 1; + +PROBE_FREQ_EXIT: + if (ret) { + dev_info(&pdev->dev, + "%s:Failed reading node=%s, key=%s. KTM continues\n", + __func__, node->full_name, key); + freq_mitigation_enabled = 0; + } + return ret; +} + +static int __devinit msm_thermal_dev_probe(struct platform_device *pdev) +{ + int ret = 0; + char *key = NULL; + struct device_node *node = pdev->dev.of_node; + struct msm_thermal_data data; + + pr_info("%s: msm_thermal_dev_probe begin...\n", KBUILD_MODNAME); + + memset(&data, 0, sizeof(struct msm_thermal_data)); + + key = "qcom,sensor-id"; + ret = of_property_read_u32(node, key, &data.sensor_id); + if (ret) + goto fail; + + key = "qcom,poll-ms"; + ret = of_property_read_u32(node, key, &data.poll_ms); + if (ret) + goto fail; + + key = "qcom,limit-temp"; + ret = of_property_read_u32(node, key, &data.limit_temp_degC); + if (ret) + goto fail; + + key = "qcom,temp-hysteresis"; + ret = of_property_read_u32(node, key, &data.temp_hysteresis_degC); + if (ret) + goto fail; + + key = "qcom,freq-step"; + ret = of_property_read_u32(node, key, &data.bootup_freq_step); + if (ret) + goto fail; + + key = "qcom,freq-control-mask"; + ret = of_property_read_u32(node, key, &data.bootup_freq_control_mask); + + ret = probe_cc(node, &data, pdev); + + ret = probe_freq_mitigation(node, &data, pdev); + /* + * Probe optional properties below. Call probe_psm before + * probe_vdd_rstr because rpm_regulator_get has to be called + * before devm_regulator_get + * probe_ocr should be called after probe_vdd_rstr to reuse the + * regualtor handle. calling devm_regulator_get more than once + * will fail. + */ + ret = probe_psm(node, &data, pdev); + if (ret == -EPROBE_DEFER) + goto fail; + ret = probe_vdd_rstr(node, &data, pdev); + if (ret == -EPROBE_DEFER) + goto fail; + ret = probe_ocr(node, &data, pdev); + if (ret == -EPROBE_DEFER) + goto fail; + + /* + * In case sysfs add nodes get called before probe function. + * Need to make sure sysfs node is created again + */ + if (psm_nodes_called) { + msm_thermal_add_psm_nodes(); + psm_nodes_called = false; + } + if (vdd_rstr_nodes_called) { + msm_thermal_add_vdd_rstr_nodes(); + vdd_rstr_nodes_called = false; + } + if (ocr_nodes_called) { + msm_thermal_add_ocr_nodes(); + ocr_nodes_called = false; + } + ret = msm_thermal_init(&data); + + pr_info("%s: msm_thermal_dev_probe completed!\n", KBUILD_MODNAME); + + return ret; +fail: + if (ret) + pr_err("%s: Failed reading node=%s, key=%s\n", + __func__, node->full_name, key); + + pr_info("%s: msm_thermal_dev_probe failed!\n", KBUILD_MODNAME); + + return ret; +} + +static int msm_thermal_dev_exit(struct platform_device *inp_dev) +{ + pr_info("msm_thermal_dev: removed!\n"); + return 0; +} + +static struct of_device_id msm_thermal_match_table[] = { + {.compatible = "qcom,msm-thermal"}, + {}, +}; + +static struct platform_driver msm_thermal_device_driver = { + .probe = msm_thermal_dev_probe, + .driver = { + .name = "msm-thermal", + .owner = THIS_MODULE, + .of_match_table = msm_thermal_match_table, + }, + .remove = msm_thermal_dev_exit, +}; + +int __init msm_thermal_device_init(void) +{ + return platform_driver_register(&msm_thermal_device_driver); +} + +int __init msm_thermal_late_init(void) +{ + if (num_possible_cpus() > 1) + msm_thermal_add_cc_nodes(); + msm_thermal_add_psm_nodes(); + msm_thermal_add_vdd_rstr_nodes(); + msm_thermal_add_ocr_nodes(); + alarm_init(&thermal_rtc, ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + thermal_rtc_callback); + INIT_WORK(&timer_work, timer_work_fn); + msm_thermal_add_timer_nodes(); + + msm_thermal_add_stat_nodes(); + return 0; +} +late_initcall(msm_thermal_late_init); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Praveen Chidambaram "); +MODULE_AUTHOR("Paul Reioux "); +MODULE_DESCRIPTION("intelligent thermal driver version 2 for Qualcomm based SOCs"); +MODULE_DESCRIPTION("originally from Qualcomm's open source repo"); diff --git a/drivers/tty/hvc/hvc_console.c b/drivers/tty/hvc/hvc_console.c index 8880adf5fc6f..6d9caab33d81 100644 --- a/drivers/tty/hvc/hvc_console.c +++ b/drivers/tty/hvc/hvc_console.c @@ -186,7 +186,7 @@ static struct tty_driver *hvc_console_device(struct console *c, int *index) return hvc_driver; } -static int __init hvc_console_setup(struct console *co, char *options) +static int hvc_console_setup(struct console *co, char *options) { if (co->index < 0 || co->index >= MAX_NR_HVC_CONSOLES) return -ENODEV; diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index b8f51490f88b..f9a58b3a8fd3 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -55,7 +55,7 @@ g_webcam-y := webcam.o g_ncm-y := ncm.o g_acm_ms-y := acm_ms.o g_tcm_usb_gadget-y := tcm_usb_gadget.o -g_android-y := android.o +g_android-y := android.o f_hid.o obj-$(CONFIG_USB_ZERO) += g_zero.o obj-$(CONFIG_USB_AUDIO) += g_audio.o diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c index 4de97f56ff81..01349917ccfc 100644 --- a/drivers/usb/gadget/android.c +++ b/drivers/usb/gadget/android.c @@ -74,6 +74,9 @@ #include "f_ccid.c" #include "f_mtp.c" #include "f_accessory.c" +#include "f_hid.h" +#include "f_hid_android_keyboard.c" +#include "f_hid_android_mouse.c" #define USB_ETH_RNDIS y #include "f_rndis.c" #include "rndis.c" @@ -2029,6 +2032,41 @@ static struct android_usb_function uasp_function = { .bind_config = uasp_function_bind_config, }; +static int hid_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) +{ + return ghid_setup(cdev->gadget, 2); +} + +static void hid_function_cleanup(struct android_usb_function *f) +{ + ghid_cleanup(); +} + +static int hid_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) +{ + int ret; + printk(KERN_INFO "hid keyboard\n"); + ret = hidg_bind_config(c, &ghid_device_android_keyboard, 0); + if (ret) { + pr_info("%s: hid_function_bind_config keyboard failed: %d\n", __func__, ret); + return ret; + } + printk(KERN_INFO "hid mouse\n"); + ret = hidg_bind_config(c, &ghid_device_android_mouse, 1); + if (ret) { + pr_info("%s: hid_function_bind_config mouse failed: %d\n", __func__, ret); + return ret; + } + return 0; +} + +static struct android_usb_function hid_function = { + .name = "hid", + .init = hid_function_init, + .cleanup = hid_function_cleanup, + .bind_config = hid_function_bind_config, +}; + static struct android_usb_function *supported_functions[] = { &mbim_function, &ecm_qc_function, @@ -2058,6 +2096,7 @@ static struct android_usb_function *supported_functions[] = { &audio_source_function, #endif &uasp_function, + &hid_function, NULL }; @@ -2385,6 +2424,8 @@ functions_store(struct device *pdev, struct device_attribute *attr, name); } } + /* HID driver always enabled, it's the whole point of this kernel patch */ + android_enable_function(dev, conf, "hid"); } /* Free uneeded configurations if exists */ diff --git a/drivers/usb/gadget/f_hid.c b/drivers/usb/gadget/f_hid.c index b2113420b806..de125185b7a7 100644 --- a/drivers/usb/gadget/f_hid.c +++ b/drivers/usb/gadget/f_hid.c @@ -18,7 +18,9 @@ #include #include #include +#include #include +#include "f_hid.h" static int major, minors; static struct class *hidg_class; @@ -52,6 +54,43 @@ struct f_hidg { struct usb_ep *in_ep; }; +/* Hacky device list to fix f_hidg_write being called after device destroyed. + It covers only most common race conditions, there will be rare crashes anyway. */ +enum { HACKY_DEVICE_LIST_SIZE = 4 }; +static struct f_hidg *hacky_device_list[HACKY_DEVICE_LIST_SIZE]; +static void hacky_device_list_add(struct f_hidg *hidg) +{ + int i; + for (i = 0; i < HACKY_DEVICE_LIST_SIZE; i++) { + if (!hacky_device_list[i]) { + hacky_device_list[i] = hidg; + return; + } + } + pr_err("%s: too many devices, not adding device %p\n", __func__, hidg); +} +static void hacky_device_list_remove(struct f_hidg *hidg) +{ + int i; + for (i = 0; i < HACKY_DEVICE_LIST_SIZE; i++) { + if (hacky_device_list[i] == hidg) { + hacky_device_list[i] = NULL; + return; + } + } + pr_err("%s: cannot find device %p\n", __func__, hidg); +} +static int hacky_device_list_check(struct f_hidg *hidg) +{ + int i; + for (i = 0; i < HACKY_DEVICE_LIST_SIZE; i++) { + if (hacky_device_list[i] == hidg) { + return 0; + } + } + return 1; +} + static inline struct f_hidg *func_to_hidg(struct usb_function *f) { return container_of(f, struct f_hidg, func); @@ -140,6 +179,11 @@ static ssize_t f_hidg_read(struct file *file, char __user *buffer, if (!access_ok(VERIFY_WRITE, buffer, count)) return -EFAULT; + if (hacky_device_list_check(hidg)) { + pr_err("%s: trying to read from device %p that was destroyed\n", __func__, hidg); + return -EIO; + } + spin_lock_irqsave(&hidg->spinlock, flags); #define READ_COND (hidg->set_report_buff != NULL) @@ -194,6 +238,11 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer, if (!access_ok(VERIFY_READ, buffer, count)) return -EFAULT; + if (hacky_device_list_check(hidg)) { + pr_err("%s: trying to write to device %p that was destroyed\n", __func__, hidg); + return -EIO; + } + mutex_lock(&hidg->lock); #define WRITE_COND (!hidg->write_pending) @@ -208,6 +257,11 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer, hidg->write_queue, WRITE_COND)) return -ERESTARTSYS; + if (hacky_device_list_check(hidg)) { + pr_err("%s: trying to write to device %p that was destroyed\n", __func__, hidg); + return -EIO; + } + mutex_lock(&hidg->lock); } @@ -248,7 +302,18 @@ static unsigned int f_hidg_poll(struct file *file, poll_table *wait) struct f_hidg *hidg = file->private_data; unsigned int ret = 0; + if (hacky_device_list_check(hidg)) { + pr_err("%s: trying to poll device %p that was destroyed\n", __func__, hidg); + return -EIO; + } + poll_wait(file, &hidg->read_queue, wait); + + if (hacky_device_list_check(hidg)) { + pr_err("%s: trying to poll device %p that was destroyed\n", __func__, hidg); + return -EIO; + } + poll_wait(file, &hidg->write_queue, wait); if (WRITE_COND) @@ -448,13 +513,15 @@ const struct file_operations f_hidg_fops = { .llseek = noop_llseek, }; -static int __init hidg_bind(struct usb_configuration *c, struct usb_function *f) +static int hidg_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_ep *ep; struct f_hidg *hidg = func_to_hidg(f); int status; dev_t dev; + pr_info("%s: creating device %p\n", __func__, hidg); + /* allocate instance-specific interface IDs, and patch descriptors */ status = usb_interface_id(c, f); if (status < 0) @@ -518,6 +585,7 @@ static int __init hidg_bind(struct usb_configuration *c, struct usb_function *f) goto fail; device_create(hidg_class, NULL, dev, NULL, "%s%d", "hidg", hidg->minor); + hacky_device_list_add(hidg); return 0; @@ -539,12 +607,21 @@ static void hidg_unbind(struct usb_configuration *c, struct usb_function *f) { struct f_hidg *hidg = func_to_hidg(f); + pr_info("%s: destroying device %p\n", __func__, hidg); + /* This does not cover all race conditions, only most common one */ + mutex_lock(&hidg->lock); + hacky_device_list_remove(hidg); + mutex_unlock(&hidg->lock); + device_destroy(hidg_class, MKDEV(major, hidg->minor)); cdev_del(&hidg->cdev); /* disable/free request and end point */ usb_ep_disable(hidg->in_ep); - usb_ep_dequeue(hidg->in_ep, hidg->req); + /* TODO: calling this function crash kernel, + not calling this funct ion crash kernel inside f_hidg_write */ + /* usb_ep_dequeue(hidg->in_ep, hidg->req); */ + kfree(hidg->req->buf); usb_ep_free_request(hidg->in_ep, hidg->req); @@ -580,7 +657,7 @@ static struct usb_gadget_strings *ct_func_strings[] = { /*-------------------------------------------------------------------------*/ /* usb_configuration */ -int __init hidg_bind_config(struct usb_configuration *c, +int hidg_bind_config(struct usb_configuration *c, struct hidg_func_descriptor *fdesc, int index) { struct f_hidg *hidg; @@ -631,7 +708,7 @@ int __init hidg_bind_config(struct usb_configuration *c, return status; } -int __init ghid_setup(struct usb_gadget *g, int count) +int ghid_setup(struct usb_gadget *g, int count) { int status; dev_t dev; diff --git a/drivers/usb/gadget/f_hid.h b/drivers/usb/gadget/f_hid.h new file mode 100644 index 000000000000..ad3527a6d81b --- /dev/null +++ b/drivers/usb/gadget/f_hid.h @@ -0,0 +1,16 @@ +#ifndef _GADGET_F_HID_H +#define _GADGET_F_HID_H + +#include +#include +#include +#include + +int hidg_bind_config(struct usb_configuration *c, + struct hidg_func_descriptor *fdesc, int index); + +int ghid_setup(struct usb_gadget *g, int count); + +void ghid_cleanup(void); + +#endif diff --git a/drivers/usb/gadget/f_hid_android_keyboard.c b/drivers/usb/gadget/f_hid_android_keyboard.c new file mode 100644 index 000000000000..1824bddec55d --- /dev/null +++ b/drivers/usb/gadget/f_hid_android_keyboard.c @@ -0,0 +1,44 @@ +#include +#include + +/* hid descriptor for a keyboard */ +static struct hidg_func_descriptor ghid_device_android_keyboard = { + .subclass = 1, /* Boot Interface Subclass */ + .protocol = 1, /* Keyboard */ + .report_length = 8, + .report_desc_length = 63, + .report_desc = { + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x06, /* USAGE (Keyboard) */ + 0xa1, 0x01, /* COLLECTION (Application) */ + 0x05, 0x07, /* USAGE_PAGE (Keyboard) */ + 0x19, 0xe0, /* USAGE_MINIMUM (Keyboard LeftControl) */ + 0x29, 0xe7, /* USAGE_MAXIMUM (Keyboard Right GUI) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x95, 0x08, /* REPORT_COUNT (8) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x08, /* REPORT_SIZE (8) */ + 0x81, 0x03, /* INPUT (Cnst,Var,Abs) */ + 0x95, 0x05, /* REPORT_COUNT (5) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x05, 0x08, /* USAGE_PAGE (LEDs) */ + 0x19, 0x01, /* USAGE_MINIMUM (Num Lock) */ + 0x29, 0x05, /* USAGE_MAXIMUM (Kana) */ + 0x91, 0x02, /* OUTPUT (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x03, /* REPORT_SIZE (3) */ + 0x91, 0x03, /* OUTPUT (Cnst,Var,Abs) */ + 0x95, 0x06, /* REPORT_COUNT (6) */ + 0x75, 0x08, /* REPORT_SIZE (8) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x65, /* LOGICAL_MAXIMUM (101) */ + 0x05, 0x07, /* USAGE_PAGE (Keyboard) */ + 0x19, 0x00, /* USAGE_MINIMUM (Reserved) */ + 0x29, 0x65, /* USAGE_MAXIMUM (Keyboard Application) */ + 0x81, 0x00, /* INPUT (Data,Ary,Abs) */ + 0xc0 /* END_COLLECTION */ + } +}; diff --git a/drivers/usb/gadget/f_hid_android_mouse.c b/drivers/usb/gadget/f_hid_android_mouse.c new file mode 100644 index 000000000000..075d12522705 --- /dev/null +++ b/drivers/usb/gadget/f_hid_android_mouse.c @@ -0,0 +1,40 @@ +#include +#include + +/* HID descriptor for a mouse */ +static struct hidg_func_descriptor ghid_device_android_mouse = { + .subclass = 1, /* Boot Interface Subclass */ + .protocol = 2, /* Mouse */ + .report_length = 4, + .report_desc_length = 52, + .report_desc = { + 0x05, 0x01, //Usage Page(Generic Desktop Controls) + 0x09, 0x02, //Usage (Mouse) + 0xa1, 0x01, //Collection (Application) + 0x09, 0x01, //Usage (pointer) + 0xa1, 0x00, //Collection (Physical) + 0x05, 0x09, //Usage Page (Button) + 0x19, 0x01, //Usage Minimum(1) + 0x29, 0x05, //Usage Maximum(5) + 0x15, 0x00, //Logical Minimum(1) + 0x25, 0x01, //Logical Maximum(1) + 0x95, 0x05, //Report Count(5) + 0x75, 0x01, //Report Size(1) + 0x81, 0x02, //Input(Data,Variable,Absolute,BitField) + 0x95, 0x01, //Report Count(1) + 0x75, 0x03, //Report Size(3) + 0x81, 0x01, //Input(Constant,Array,Absolute,BitField) + 0x05, 0x01, //Usage Page(Generic Desktop Controls) + 0x09, 0x30, //Usage(x) + 0x09, 0x31, //Usage(y) + 0x09, 0x38, //Usage(Wheel) + 0x15, 0x81, //Logical Minimum(-127) + 0x25, 0x7F, //Logical Maximum(127) + 0x75, 0x08, //Report Size(8) + 0x95, 0x03, //Report Count(3) + 0x81, 0x06, //Input(Data,Variable,Relative,BitField) + 0xc0, //End Collection + 0xc0 //End Collection + } +}; + diff --git a/drivers/video/aty/mach64_accel.c b/drivers/video/aty/mach64_accel.c index e45833ce975b..182bd680141f 100644 --- a/drivers/video/aty/mach64_accel.c +++ b/drivers/video/aty/mach64_accel.c @@ -4,6 +4,7 @@ */ #include +#include #include #include