diff options
| author | Linux Build Service Account <lnxbuild@localhost> | 2016-06-16 10:31:48 -0700 |
|---|---|---|
| committer | Linux Build Service Account <lnxbuild@localhost> | 2016-06-16 10:31:49 -0700 |
| commit | fa22bff01fb9199590e7c5cfe1ba87ed9d27b837 (patch) | |
| tree | ea683c39d53d436e1144ad44fef23ebbc8eaf6fe | |
| parent | f89e635bb12882f4c09f9d57fa0aec08c9207064 (diff) | |
| parent | 3a3f6a5cb21e391d57c2900a522943fcb3468b4d (diff) | |
Promotion of kernel.lnx.4.4-160615.
CRs Change ID Subject
--------------------------------------------------------------------------------------------------------------
1008549 I47ea7c22250264da206e1fb8691e77224c825ab0 msm: ipa: clear uC interrupt only before processing
1027456 Idc857357a87ba91cdb7a69deeaf05aa8d5d13628 ARM: msm: add 64-bit IO accessor support
1012001 I93f72f18145ddef6a0caf2c59a9af5f23e6e20a3 ASoC: wcd-mbhc: disable moisture detection for NC Jack
1027269 I6671bb51259515acb0733ce65be8084716d3bfbf mmc: sdhci: Fix command response INDEX/END bit error han
1026796 Id354f7cf6979aa58621408cfcfbd8ef62015fdbd ion: Fix DMA operations for ARM64
1015036 Ib0f6d0ecfa992e2b8aaae9ab6cea3a2e441f65dd msm: pcie: add stubs for !CONFIG_PCI_MSM
1023141 I8e367dbe7acf72471a5a474f0e2a00a4004fcbfb qcom-charger: pmic-voter: destroy only valid votables
1027651 Iff8e1c0367d13bb0d89946d81fb79427b6ef070e msm: sensor: Add support for downloading OIS coefficient
1027456 Ib8218135dc923d1ba4098d74dbd7da159368a188 ARM: build correct dtbs to append to zImage
1027750 Ia01a1ff42af29915093270b9591d97dcc2b2ce54 ARM: dts: msm: update touch panel resolution on msmcobal
1005685 Ia9e45f539bd5ec0d2edfe9bfb90942d88b6d30da msm:camera: Enable/Disable camera daemon
1027456 I3146989f6e73bfe101ac9363428bd0beecd09c32 ARM: dts: Make sure to add the dts files to compilation
997662 I311ad8f158b0be6e9d6481512860f9fac10afc1f ASoC: wcd9335: Infinite loop when routing DMIC for hands
1026732 Ic234c29b9c86e7095ab39f633eda57560b271c1f ARM: dts: msm: Add support for MSMCOBALT QRD
1027311 If6b4340b80b313fda87d648baaa1d78a588079ac spcom: increase max ion buffers per channel
1012546 Ibcbdde0ea59ff80a798de0b894c2239899260860 ASoC: msm: qdsp6v2: do not set cmd_interrupt flag in eos
1004850 Ia5679310fc59f25643e7c8d572cc230d262c5937 power: qcom: Move power table notification outside of cr
1027269 I73ac950f096fa2e81f29ecb40bdd01153c05891f mmc: sdhci: fix command response CRC error handling
1027269 987918 I41cf8908dd0f129c54b941c318e938ad7e9d36c9 mmc: core: Add deferred resume support to CQ
1027248 Iddb96afac87cf3e7a1cc48f04b3c550e81bdae4b scsi: ufs-qcom: Fix null pointer dereference
1027456 I412a83a2f756b02d6b521983501de780835dc118 arch: arm: generalise ARCH_QCOM platform
1018090 Ie947cc2c74550c98f64dd028c728afa57723c70f ARM: dts: msm: add QPNP QNOVO charger device to PMICOBAL
1027456 I4d52ad23cc40d03d2bae1d3942c8d35543a0d461 ARM: msm: add support for logged IO accessors
1027248 Ib37534eaf15ad76abb800fe3917f9c0a832bd30a ARM: dts: msm: enable UFS PM QoS for msmcobalt
1023141 975120 I5ebe6d0bcb7c097124ba9b35c56579815dda234f mfd: introduce I2C PMIC controller
1023141 I885289a1eec68335645912c3ecbbe91a85836647 qcom-charger: smb-lib: differentiate between parallel an
1015036 I69b1839d1afe8f65235e5c47f0d55abe75acb6a8 ARM: dts: msm: enable L1ss for PCIe on msmcobalt
1027456 I69017d73da1065a5eeb9c87c899b6a51be5ebfe6 Revert "ARM: dma-mapping: remove dmac_clean_range and dm
1018090 I2ddea8adf1aa9d999cc2fd3fd4f0e0f830147d4c qcom-charger: enable qnovo bit in the qpnp-smb2 driver
1026135 I68fa6f510d55822b01c2ea5062d4876c4420c5f7 icnss: Add statistics to know driver status
1026507 I4fae5f442e4cc2c2414a69e960d42c05c3062415 Revert "kernel/sysctl.c: detect overflows when convertin
1023141 I96a4877196be78c0eeecc3fc08419e8990572aaa qcom-charger: smb-lib: add get charge param and usb susp
1027456 I80bac808f2916e49a77be24e7a39d0839165d306 arm: io: Add readb and writeb relaxed_no_log variants
1027653 I843f35ee08159c59aaee7df4a23dfb4ae9c6b689 msm: vidc: Fix Low latency step size
1027836 I117348084e2bec49d0fcd7eb0b0149fc00ae639d ARM: dts: msm: add display panel configuration for msmco
1027269 I1eae7dffec97d34b066bb5738c84a7e5a82f68d7 mmc: Fix pm_notifier obeying deferred resume
1027269 I077d7dc9311ff12e6e16de631abeac965c8facd9 mmc: core: Add deferred bus resume policy.
1026738 If440e1ef4bdbcc73ac7a0569a6bbf093db8aefef ARM: dts: apq: Add initial device tree files for APQCOBA
971183 If3076f017d476cfb57fa22b75cc74ed615c8882e ASoC: wcd9335: Adjust DMIC clock based on sample rate
1018090 I2573f719f4b2c2fa9a169659a65433fb834ea74e qcom-charger: add qnovo driver
1024966 I4706b353e63b044368ea54a8ed74d61dc44dc95c defconfig: arm64: msmcortex: Add defconfigs for IPSec da
1024022 Ie713f020201cafe6d815c7da5e87ea1566ac36ad ARM: dts: msm: enable charging in msmcobalt
Change-Id: Ie27ccb268e107e8748e42fbfdd9bbb480561e1e1
CRs-Fixed: 1027456, 1012001, 1026796, 1026135, 971183, 1027750, 1027269, 1012546, 1026738, 1027248, 1005685, 1026732, 1024966, 1008549, 1024022, 975120, 1026507, 1015036, 997662, 1004850, 1027651, 1027653, 1027311, 1023141, 987918, 1027836, 1018090
69 files changed, 3721 insertions, 205 deletions
diff --git a/Documentation/devicetree/bindings/arm/msm/msm.txt b/Documentation/devicetree/bindings/arm/msm/msm.txt index d387336a2b5d..1c0dfa43952c 100644 --- a/Documentation/devicetree/bindings/arm/msm/msm.txt +++ b/Documentation/devicetree/bindings/arm/msm/msm.txt @@ -47,6 +47,9 @@ SoCs: - APQTITANIUM compatible = "qcom,apqtitanium" +- APQCOBALT + compatible = "qcom,apqcobalt" + - MDM9630 compatible = "qcom,mdm9630" @@ -177,6 +180,8 @@ compatible = "qcom,apq8037-cdp" compatible = "qcom,apq8037-mtp" compatible = "qcom,apqtitanium-cdp" compatible = "qcom,apqtitanium-mtp" +compatible = "qcom,apqcobalt-cdp" +compatible = "qcom,apqcobalt-mtp" compatible = "qcom,mdm9630-cdp" compatible = "qcom,mdm9630-mtp" compatible = "qcom,mdm9630-sim" @@ -245,6 +250,7 @@ compatible = "qcom,msm8996-mtp" compatible = "qcom,msm8996-adp" compatible = "qcom,msmcobalt-sim" compatible = "qcom,msmcobalt-rumi" +compatible = "qcom,msmcobalt-qrd" compatible = "qcom,msmhamster-rumi" compatible = "qcom,msmhamster-cdp" compatible = "qcom,msmhamster-mtp" diff --git a/Documentation/devicetree/bindings/mfd/qcom-i2c-pmic.txt b/Documentation/devicetree/bindings/mfd/qcom-i2c-pmic.txt new file mode 100644 index 000000000000..9c7c856ea42b --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/qcom-i2c-pmic.txt @@ -0,0 +1,98 @@ +Qualcomm Technologies, Inc. I2C PMIC Interrupt Controller +Platform Independent Bindings + +The I2C PMIC Controller is used by multi-function PMIC devices which communicate +over the I2C bus. An I2C PMIC controller node typically contains one or more +child nodes representing the device's peripherals. Each of the peripherals +typically has its own driver on the platform bus and will be enumerated by this +controller. The controller exposes a regmap to the peripherals to communicate +over the I2C bus. + +The controller also controls interrupts for all of the peripherals on the bus. +The controller takes a summary interrupt, deciphers which peripheral triggered +the interrupt, and which of the peripheral's interrupts were triggered. Finally, +it calls the handlers for each of the virtual interrupts that were registered. + +This document describes the common platform independent bindings that apply +to all I2C PMIC interrupt controllers. + +======================================== +First Level Nodes - I2C PMIC Controllers +======================================== + +Platform independent properties: +- compatible + Usage: required + Value type: <string> + Definition: Must be "qcom,i2c-pmic". + +- reg + Usage: required + Value type: <u32> + Definition: 7-bit I2C address of the device. + +- interrupt-parent + Usage: required + Value type: <phandle> + Definition: phandle of the interrupt controller which services the + summary interrupt. + +- interrupts + Usage: required + Value type: <prop-encoded-array> + Definition: Summary interrupt specifier. + +- interrupt-controller + Usage: required + Value type: <empty> + Definition: Boolean flag which indicates this device node is an + interrupt controller. + +- #interrupt-cells + Usage: required + Value type: <u32> + Definition: Number of cells to encode an interrupt source. + +- qcom,periph-map + Usage: required + Value type: <prop-encoded-array> + Definition: A list of u32 arrays. This provides a mapping between the + summary status register bits and peripheral addresses. + + The number of arrays should match the number of summary + registers with up to 8 elements each. One element per bit + of the summary status register in order from the least + sigificant bit to the most significant bit. + +- pinctrl-names + Usage: optional + Value type: <string-list> + Definition: Should be "default". + Please refer to pinctrl-bindings.txt + +- pinctrl-0 + Usage: optional + Value type: <phandle-list> + Definition: phandle of the pin configuration. + Please refer to pinctrl-bindings.txt + +======= +Example +======= + +&i2c_3 { + status = "ok"; + qcom,smb138x@8 { + compatible = "qcom,i2c-pmic"; + reg = <0x8>; + interrupt-parent = <&tlmm_pinmux>; + interrupts = <83 0>; + interrupt-controller; + #interrupt-cells = <3>; + pinctrl-names = "default"; + pinctrl-0 = <&smb_stat_active>; + #address-cells = <1>; + #size-cells = <0>; + qcom,periph-map = <0x10 0x11 0x12 0x13 0x14 0x16 0x36>; + }; +}; diff --git a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-qnovo.txt b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-qnovo.txt new file mode 100644 index 000000000000..438bd68b0e05 --- /dev/null +++ b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-qnovo.txt @@ -0,0 +1,32 @@ +QPNP Qnovo pulse engine + +QPNP Qnovo is a pulse charging engine which works in tandem with the QPNP SMB2 +Charger device. It configures the QPNP SMB2 charger to charge/discharge as per +pulse characteristics. + +The QPNP Qnovo pulse engine has a single peripheral assigned to it. + +Required properties: +- compatible: Must be "qcom,qpnp-qnovo" +- qcom,pmic-revid: Should specify the phandle of PMIC + revid module. This is used to identify + the PMIC subtype. + +- reg: The address for this peripheral +- interrupts: Specifies the interrupt associated with the peripheral. +- interrupt-names: Specifies the interrupt name for the peripheral. Qnovo + peripheral has only one interrupt "ptrain-done". + +Optional Properties: +- qcom,external-rsense: To indicate whether the platform uses external or + internal rsense for measuring battery current. + +Example: + + qcom,qpnp-qnovo@1500 { + compatible = "qcom,qpnp-qnovo"; + reg = <0x1500 0x100>; + interrupts = <0x2 0x15 0x0 IRQ_TYPE_NONE>; + interrupt-names = "ptrain-done"; + qcom,pmic-revid = <&pmicobalt_revid>; + }; diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index d3159ff4de5b..6b09a67c9a0d 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -632,6 +632,31 @@ config ARCH_PXA help Support for Intel/Marvell's PXA2xx/PXA3xx processor line. +config ARCH_QCOM + bool "Qualcomm MSM (non-multiplatform)" + select ARCH_REQUIRE_GPIOLIB + select CPU_V7 + select AUTO_ZRELADDR + select HAVE_SMP + select CLKDEV_LOOKUP + select GENERIC_CLOCKEVENTS + select GENERIC_ALLOCATOR + select ARM_PATCH_PHYS_VIRT + select ARM_HAS_SG_CHAIN + select ARCH_HAS_OPP + select SOC_BUS + select MULTI_IRQ_HANDLER + select PM_OPP + select SPARSE_IRQ + select USE_OF + select PINCTRL + help + Support for Qualcomm MSM/QSD based systems. This runs on the + apps processor of the MSM/QSD and depends on a shared memory + interface to the modem processor which runs the baseband + stack and controls some vital subsystems + (clock and power control, etc). + config ARCH_RPC bool "RiscPC" depends on MMU @@ -1505,7 +1530,7 @@ config ARM_PSCI config ARCH_NR_GPIO int default 1024 if ARCH_BRCMSTB || ARCH_SHMOBILE || ARCH_TEGRA || \ - ARCH_ZYNQ + ARCH_ZYNQ || ARCH_QCOM default 512 if ARCH_EXYNOS || ARCH_KEYSTONE || SOC_OMAP5 || \ SOC_DRA7XX || ARCH_S3C24XX || ARCH_S3C64XX || ARCH_S5PV210 default 416 if ARCH_SUNXI diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 0901c6bd458f..71afa854e1c4 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -1612,7 +1612,7 @@ config DEBUG_UNCOMPRESS config UNCOMPRESS_INCLUDE string default "debug/uncompress.h" if ARCH_MULTIPLATFORM || ARCH_MSM || \ - PLAT_SAMSUNG || ARM_SINGLE_ARMV7M + ARCH_QCOM || PLAT_SAMSUNG || ARM_SINGLE_ARMV7M default "mach/uncompress.h" config EARLY_PRINTK diff --git a/arch/arm/Makefile b/arch/arm/Makefile index 97f822a87c1c..24e19deb1f28 100644 --- a/arch/arm/Makefile +++ b/arch/arm/Makefile @@ -307,6 +307,8 @@ ifeq ($(CONFIG_USE_OF),y) KBUILD_DTBS := dtbs endif +DTSSUBDIR := qcom + all: $(KBUILD_IMAGE) $(KBUILD_DTBS) boot := arch/arm/boot @@ -334,22 +336,18 @@ $(INSTALL_TARGETS): %.dtb: | scripts $(Q)$(MAKE) $(build)=$(boot)/dts MACHINE=$(MACHINE) $(boot)/dts/$@ -PHONY += dtbs dtbs_install - -dtbs: prepare scripts - $(Q)$(MAKE) $(build)=$(boot)/dts - PHONY += vdso_install vdso_install: ifeq ($(CONFIG_VDSO),y) $(Q)$(MAKE) $(build)=arch/arm/vdso $@ endif -dtbs dtbs_install: prepare scripts - $(Q)$(MAKE) $(build)=$(boot)/dts/qcom MACHINE=$(MACHINE) $@ +dtbs: scripts + $(Q)$(MAKE) $(build)=$(boot)/dts MACHINE=$(MACHINE) dtbs + $(foreach DIR, $(DTSSUBDIR), $(Q)$(MAKE) $(build)=$(boot)/dts/$(DIR) MACHINE=$(MACHINE) dtbs) zImage-dtb: vmlinux scripts dtbs - $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@ + $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) DTSSUBDIR=$(DTSSUBDIR) $(boot)/$@ # We use MRPROPER_FILES and CLEAN_FILES now archclean: diff --git a/arch/arm/boot/Makefile b/arch/arm/boot/Makefile index 4a04ed3daf97..8849c2d20ac5 100644 --- a/arch/arm/boot/Makefile +++ b/arch/arm/boot/Makefile @@ -11,11 +11,14 @@ # Copyright (C) 1995-2002 Russell King # -ifneq ($(MACHINE),) -include $(MACHINE)/Makefile.boot -endif include $(srctree)/arch/arm/boot/dts/Makefile +include $(srctree)/arch/arm/boot/dts/Makefile +ifneq ($(DTSSUBDIR),) +DTSSUBDIR_INCS=$(foreach DIR, $(DTSSUBDIR), $(addsuffix /Makefile, $(addprefix $(srctree)/arch/arm/boot/dts/, $(DIR)))) +include $(DTSSUBDIR_INCS) +endif + # Note: the following conditions must always be true: # ZRELADDR == virt_to_phys(PAGE_OFFSET + TEXT_OFFSET) # PARAMS_PHYS must be within 4MB of ZRELADDR diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile index d83f20080c0a..67273ababc30 100644 --- a/arch/arm/boot/dts/Makefile +++ b/arch/arm/boot/dts/Makefile @@ -768,7 +768,6 @@ dtb-$(CONFIG_MACH_DOVE) += \ dove-cubox-es.dtb \ dove-d2plug.dtb \ dove-d3plug.dtb \ -<<<<<<< HEAD dove-dove-db.dtb \ dove-sbc-a510.dtb dtb-$(CONFIG_ARCH_MEDIATEK) += \ @@ -806,5 +805,10 @@ endif targets += dtbs dtbs_install targets += $(DTB_LIST) +ifeq ($(CONFIG_ARM64),y) always := $(DTB_LIST) +else +dtbs: $(addprefix $(obj)/, $(DTB_LIST)) + $(Q)rm -f $(obj)/../*.dtb +endif clean-files := *.dtb diff --git a/arch/arm/boot/dts/qcom/Makefile b/arch/arm/boot/dts/qcom/Makefile index faf39d2a8970..dd955be6c908 100644 --- a/arch/arm/boot/dts/qcom/Makefile +++ b/arch/arm/boot/dts/qcom/Makefile @@ -106,15 +106,28 @@ dtb-$(CONFIG_ARCH_MSMCOBALT) += msmcobalt-sim.dtb \ msmcobalt-rumi.dtb \ msmcobalt-cdp.dtb \ msmcobalt-mtp.dtb \ + msmcobalt-qrd.dtb \ msmcobalt-v2-sim.dtb \ msmcobalt-v2-rumi.dtb \ msmcobalt-v2-mtp.dtb \ - msmcobalt-v2-cdp.dtb + msmcobalt-v2-cdp.dtb \ + apqcobalt-mtp.dtb \ + apqcobalt-cdp.dtb dtb-$(CONFIG_ARCH_MSMHAMSTER) += msmhamster-rumi.dtb dtb-$(CONFIG_ARCH_MSMFALCON) += msmfalcon-sim.dtb -always := $(dtb-y) -subdir-y := $(dts-dirs) -clean-files := *.dtb +ifeq ($(CONFIG_ARM64),y) +always := $(dtb-y) +subdir-y := $(dts-dirs) +else +targets += dtbs +targets += $(addprefix ../, $(dtb-y)) + +$(obj)/../%.dtb: $(src)/%.dts FORCE + $(call if_changed_dep,dtc) + +dtbs: $(addprefix $(obj)/../,$(dtb-y)) +endif +clean-files := *.dtb diff --git a/arch/arm/boot/dts/qcom/apqcobalt-cdp.dts b/arch/arm/boot/dts/qcom/apqcobalt-cdp.dts new file mode 100644 index 000000000000..1f0066d15aaa --- /dev/null +++ b/arch/arm/boot/dts/qcom/apqcobalt-cdp.dts @@ -0,0 +1,22 @@ +/* Copyright (c) 2016, 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. + */ + +/dts-v1/; + +#include "apqcobalt.dtsi" +#include "msmcobalt-cdp.dtsi" + +/ { + model = "Qualcomm Technologies, Inc. APQ COBALT CDP"; + compatible = "qcom,apqcobalt-cdp", "qcom,apqcobalt", "qcom,cdp"; + qcom,board-id = <1 0>; +}; diff --git a/arch/arm/boot/dts/qcom/apqcobalt-mtp.dts b/arch/arm/boot/dts/qcom/apqcobalt-mtp.dts new file mode 100644 index 000000000000..b63e9e027797 --- /dev/null +++ b/arch/arm/boot/dts/qcom/apqcobalt-mtp.dts @@ -0,0 +1,22 @@ +/* Copyright (c) 2016, 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. + */ + +/dts-v1/; + +#include "apqcobalt.dtsi" +#include "msmcobalt-mtp.dtsi" + +/ { + model = "Qualcomm Technologies, Inc. APQ COBALT MTP"; + compatible = "qcom,apqcobalt-mtp", "qcom,apqcobalt", "qcom,mtp"; + qcom,board-id = <8 0>; +}; diff --git a/arch/arm/boot/dts/qcom/apqcobalt.dtsi b/arch/arm/boot/dts/qcom/apqcobalt.dtsi new file mode 100644 index 000000000000..6122915975a1 --- /dev/null +++ b/arch/arm/boot/dts/qcom/apqcobalt.dtsi @@ -0,0 +1,18 @@ +/* Copyright (c) 2016, 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. + */ + +#include "msmcobalt.dtsi" + +/ { + model = "Qualcomm Technologies, Inc. APQ COBALT"; + qcom,msm-id = <319 0x10000>; +}; diff --git a/arch/arm/boot/dts/qcom/msm-pmicobalt.dtsi b/arch/arm/boot/dts/qcom/msm-pmicobalt.dtsi index c0bae6c6e5f8..b612ec3f42b5 100644 --- a/arch/arm/boot/dts/qcom/msm-pmicobalt.dtsi +++ b/arch/arm/boot/dts/qcom/msm-pmicobalt.dtsi @@ -130,6 +130,14 @@ }; }; + qcom,qpnp-qnovo@1500 { + compatible = "qcom,qpnp-qnovo"; + reg = <0x1500 0x100>; + interrupts = <0x2 0x15 0x0 IRQ_TYPE_NONE>; + interrupt-names = "ptrain-done"; + qcom,pmic-revid = <&pmicobalt_revid>; + }; + pmicobalt_charger: qcom,qpnp-smb2 { compatible = "qcom,qpnp-smb2"; #address-cells = <1>; @@ -137,8 +145,6 @@ qcom,pmic-revid = <&pmicobalt_revid>; - /* do not draw current from USB or DC */ - qcom,suspend-input; dpdm-supply = <&qusb_phy0>; qcom,chgr@1000 { diff --git a/arch/arm/boot/dts/qcom/msmcobalt-cdp.dtsi b/arch/arm/boot/dts/qcom/msmcobalt-cdp.dtsi index acb4abb788f1..806610db5772 100644 --- a/arch/arm/boot/dts/qcom/msmcobalt-cdp.dtsi +++ b/arch/arm/boot/dts/qcom/msmcobalt-cdp.dtsi @@ -299,6 +299,10 @@ &mdss_hdmi_ddc_suspend &mdss_hdmi_cec_suspend>; }; +&pmicobalt_charger { + qcom,suspend-input; +}; + &pmicobalt_haptics { status = "okay"; }; diff --git a/arch/arm/boot/dts/qcom/msmcobalt-mtp.dtsi b/arch/arm/boot/dts/qcom/msmcobalt-mtp.dtsi index fa91ed674d2c..0b18de7d9e27 100644 --- a/arch/arm/boot/dts/qcom/msmcobalt-mtp.dtsi +++ b/arch/arm/boot/dts/qcom/msmcobalt-mtp.dtsi @@ -165,7 +165,7 @@ pinctrl-0 = <&ts_active>; pinctrl-1 = <&ts_suspend>; synaptics,display-coords = <0 0 1439 2559>; - synaptics,panel-coords = <0 0 1439 2703>; + synaptics,panel-coords = <0 0 1439 2559>; synaptics,reset-gpio = <&tlmm 89 0x00>; synaptics,irq-gpio = <&tlmm 125 0x2008>; synaptics,disable-gpios; @@ -210,6 +210,85 @@ &mdss_hdmi_ddc_suspend &mdss_hdmi_cec_suspend>; }; +&mdss_mdp { + qcom,mdss-pref-prim-intf = "dsi"; +}; + +&mdss_dsi { + hw-config = "split_dsi"; +}; + +&mdss_dsi0 { + qcom,dsi-pref-prim-pan = <&dsi_dual_nt35597_video>; + pinctrl-names = "mdss_default", "mdss_sleep"; + pinctrl-0 = <&mdss_dsi_active &mdss_te_active>; + pinctrl-1 = <&mdss_dsi_suspend &mdss_te_suspend>; + qcom,platform-reset-gpio = <&tlmm 94 0>; + qcom,platform-te-gpio = <&tlmm 10 0>; + qcom,panel-mode-gpio = <&tlmm 91 0>; +}; + +&mdss_dsi1 { + qcom,dsi-pref-prim-pan = <&dsi_dual_nt35597_video>; + pinctrl-names = "mdss_default", "mdss_sleep"; + pinctrl-0 = <&mdss_dsi_active &mdss_te_active>; + pinctrl-1 = <&mdss_dsi_suspend &mdss_te_suspend>; + qcom,platform-reset-gpio = <&tlmm 94 0>; + qcom,platform-te-gpio = <&tlmm 10 0>; + qcom,panel-mode-gpio = <&tlmm 91 0>; +}; + +&labibb { + status = "ok"; + qpnp,qpnp-labibb-mode = "lcd"; +}; + +&dsi_dual_nt35597_video { + qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled"; + qcom,mdss-dsi-bl-min-level = <1>; + qcom,mdss-dsi-bl-max-level = <4095>; + qcom,mdss-dsi-mode-sel-gpio-state = "dual_port"; + qcom,panel-supply-entries = <&dsi_panel_pwr_supply>; +}; + +&dsi_dual_nt35597_cmd { + qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled"; + qcom,mdss-dsi-bl-min-level = <1>; + qcom,mdss-dsi-bl-max-level = <4095>; + qcom,mdss-dsi-mode-sel-gpio-state = "dual_port"; + qcom,panel-supply-entries = <&dsi_panel_pwr_supply>; +}; + +&dsi_nt35597_dsc_video { + qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled"; + qcom,mdss-dsi-bl-min-level = <1>; + qcom,mdss-dsi-bl-max-level = <4095>; + qcom,mdss-dsi-mode-sel-gpio-state = "single_port"; + qcom,panel-supply-entries = <&dsi_panel_pwr_supply>; +}; + +&dsi_nt35597_dsc_cmd { + qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled"; + qcom,mdss-dsi-bl-min-level = <1>; + qcom,mdss-dsi-bl-max-level = <4095>; + qcom,mdss-dsi-mode-sel-gpio-state = "single_port"; + qcom,panel-supply-entries = <&dsi_panel_pwr_supply>; +}; + +&dsi_sharp_4k_dsc_video { + qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled"; + qcom,mdss-dsi-bl-min-level = <1>; + qcom,mdss-dsi-bl-max-level = <4095>; + qcom,panel-supply-entries = <&dsi_panel_pwr_supply>; +}; + +&dsi_sharp_4k_dsc_cmd { + qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled"; + qcom,mdss-dsi-bl-min-level = <1>; + qcom,mdss-dsi-bl-max-level = <4095>; + qcom,panel-supply-entries = <&dsi_panel_pwr_supply>; +}; + &pmicobalt_haptics { status = "okay"; }; diff --git a/arch/arm/boot/dts/qcom/msmcobalt-qrd.dts b/arch/arm/boot/dts/qcom/msmcobalt-qrd.dts new file mode 100644 index 000000000000..9848d6da33a5 --- /dev/null +++ b/arch/arm/boot/dts/qcom/msmcobalt-qrd.dts @@ -0,0 +1,23 @@ +/* Copyright (c) 2016, 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. + */ + + +/dts-v1/; + +#include "msmcobalt.dtsi" +#include "msmcobalt-mtp.dtsi" + +/ { + model = "Qualcomm Technologies, Inc. MSM COBALT QRD"; + compatible = "qcom,msmcobalt-qrd", "qcom,msmcobalt", "qcom,qrd"; + qcom,board-id = <11 0>; +}; diff --git a/arch/arm/boot/dts/qcom/msmcobalt-rumi.dtsi b/arch/arm/boot/dts/qcom/msmcobalt-rumi.dtsi index 27e9bd321b1b..20d38f5616cb 100644 --- a/arch/arm/boot/dts/qcom/msmcobalt-rumi.dtsi +++ b/arch/arm/boot/dts/qcom/msmcobalt-rumi.dtsi @@ -273,3 +273,7 @@ }; }; }; + +&pmicobalt_charger { + qcom,suspend-input; +}; diff --git a/arch/arm/boot/dts/qcom/msmcobalt-sim.dtsi b/arch/arm/boot/dts/qcom/msmcobalt-sim.dtsi index 0793f22a08b0..3b79cee0ae1e 100644 --- a/arch/arm/boot/dts/qcom/msmcobalt-sim.dtsi +++ b/arch/arm/boot/dts/qcom/msmcobalt-sim.dtsi @@ -110,3 +110,7 @@ #interrupt-cells = <1>; interrupt-names = "cdc-int"; }; + +&pmicobalt_charger { + qcom,suspend-input; +}; diff --git a/arch/arm/boot/dts/qcom/msmcobalt.dtsi b/arch/arm/boot/dts/qcom/msmcobalt.dtsi index 5e31da726464..366569cd81c9 100644 --- a/arch/arm/boot/dts/qcom/msmcobalt.dtsi +++ b/arch/arm/boot/dts/qcom/msmcobalt.dtsi @@ -1414,6 +1414,8 @@ RPM_SMD_REGULATOR_LEVEL_SVS 0>; qcom,l1-supported; + qcom,l1ss-supported; + qcom,aux-clk-sync; qcom,ep-latency = <10>; @@ -1635,8 +1637,10 @@ "HS_RB_G1_L1", "HS_RB_G2_L1", "HS_RB_G3_L1", "MAX"; - qcom,cpu-affinity = "affine_cores"; - qcom,cpu-dma-latency-us = <301>; + /* PM QoS */ + qcom,pm-qos-cpu-groups = <0x0F 0xF0>; + qcom,pm-qos-cpu-group-latency-us = <70 70>; + qcom,pm-qos-default-cpu = <0>; status = "disabled"; }; diff --git a/arch/arm/include/asm/cacheflush.h b/arch/arm/include/asm/cacheflush.h index d5525bfc7e3e..9623a8a87e18 100644 --- a/arch/arm/include/asm/cacheflush.h +++ b/arch/arm/include/asm/cacheflush.h @@ -94,6 +94,21 @@ * DMA Cache Coherency * =================== * + * dma_inv_range(start, end) + * + * Invalidate (discard) the specified virtual address range. + * May not write back any entries. If 'start' or 'end' + * are not cache line aligned, those lines must be written + * back. + * - start - virtual start address + * - end - virtual end address + * + * dma_clean_range(start, end) + * + * Clean (write back) the specified virtual address range. + * - start - virtual start address + * - end - virtual end address + * * dma_flush_range(start, end) * * Clean and invalidate the specified virtual address range. @@ -115,6 +130,8 @@ struct cpu_cache_fns { void (*dma_map_area)(const void *, size_t, int); void (*dma_unmap_area)(const void *, size_t, int); + void (*dma_inv_range)(const void *, const void *); + void (*dma_clean_range)(const void *, const void *); void (*dma_flush_range)(const void *, const void *); }; @@ -140,6 +157,8 @@ extern struct cpu_cache_fns cpu_cache; * is visible to DMA, or data written by DMA to system memory is * visible to the CPU. */ +#define dmac_inv_range cpu_cache.dma_inv_range +#define dmac_clean_range cpu_cache.dma_clean_range #define dmac_flush_range cpu_cache.dma_flush_range #else @@ -159,6 +178,8 @@ extern void __cpuc_flush_dcache_area(void *, size_t); * is visible to DMA, or data written by DMA to system memory is * visible to the CPU. */ +extern void dmac_inv_range(const void *, const void *); +extern void dmac_clean_range(const void *, const void *); extern void dmac_flush_range(const void *, const void *); #endif diff --git a/arch/arm/include/asm/glue-cache.h b/arch/arm/include/asm/glue-cache.h index cab07f69382d..1f442995cdbf 100644 --- a/arch/arm/include/asm/glue-cache.h +++ b/arch/arm/include/asm/glue-cache.h @@ -159,6 +159,8 @@ static inline void nop_dma_unmap_area(const void *s, size_t l, int f) { } #define __cpuc_flush_dcache_area __glue(_CACHE,_flush_kern_dcache_area) #define dmac_flush_range __glue(_CACHE,_dma_flush_range) +#define dmac_inv_range __glue(_CACHE, _dma_inv_range) +#define dmac_clean_range __glue(_CACHE, _dma_clean_range) #endif #endif diff --git a/arch/arm/include/asm/io.h b/arch/arm/include/asm/io.h index 485982084fe9..c5d7c8b995eb 100644 --- a/arch/arm/include/asm/io.h +++ b/arch/arm/include/asm/io.h @@ -29,6 +29,7 @@ #include <asm/byteorder.h> #include <asm/memory.h> #include <asm-generic/pci_iomap.h> +#include <linux/msm_rtb.h> #include <xen/xen.h> /* @@ -62,23 +63,21 @@ void __raw_readsl(const volatile void __iomem *addr, void *data, int longlen); * the bus. Rather than special-case the machine, just let the compiler * generate the access for CPUs prior to ARMv6. */ -#define __raw_readw(a) (__chk_io_ptr(a), *(volatile unsigned short __force *)(a)) -#define __raw_writew(v,a) ((void)(__chk_io_ptr(a), *(volatile unsigned short __force *)(a) = (v))) +#define __raw_readw_no_log(a) (__chk_io_ptr(a), *(volatile unsigned short __force *)(a)) +#define __raw_writew_no_log(v, a) ((void)(__chk_io_ptr(a), *(volatile unsigned short __force *)(a) = (v))) #else /* * When running under a hypervisor, we want to avoid I/O accesses with * writeback addressing modes as these incur a significant performance * overhead (the address generation must be emulated in software). */ -#define __raw_writew __raw_writew -static inline void __raw_writew(u16 val, volatile void __iomem *addr) +static inline void __raw_writew_no_log(u16 val, volatile void __iomem *addr) { asm volatile("strh %1, %0" : : "Q" (*(volatile u16 __force *)addr), "r" (val)); } -#define __raw_readw __raw_readw -static inline u16 __raw_readw(const volatile void __iomem *addr) +static inline u16 __raw_readw_no_log(const volatile void __iomem *addr) { u16 val; asm volatile("ldrh %0, %1" @@ -88,22 +87,30 @@ static inline u16 __raw_readw(const volatile void __iomem *addr) } #endif -#define __raw_writeb __raw_writeb -static inline void __raw_writeb(u8 val, volatile void __iomem *addr) +static inline void __raw_writeb_no_log(u8 val, volatile void __iomem *addr) { asm volatile("strb %1, %0" : : "Qo" (*(volatile u8 __force *)addr), "r" (val)); } -#define __raw_writel __raw_writel -static inline void __raw_writel(u32 val, volatile void __iomem *addr) +static inline void __raw_writel_no_log(u32 val, volatile void __iomem *addr) { asm volatile("str %1, %0" : : "Qo" (*(volatile u32 __force *)addr), "r" (val)); } -#define __raw_readb __raw_readb -static inline u8 __raw_readb(const volatile void __iomem *addr) +static inline void __raw_writeq_no_log(u64 val, volatile void __iomem *addr) +{ + register u64 v asm ("r2"); + + v = val; + + asm volatile("strd %1, %0" + : "+Qo" (*(volatile u64 __force *)addr) + : "r" (v)); +} + +static inline u8 __raw_readb_no_log(const volatile void __iomem *addr) { u8 val; asm volatile("ldrb %0, %1" @@ -112,8 +119,7 @@ static inline u8 __raw_readb(const volatile void __iomem *addr) return val; } -#define __raw_readl __raw_readl -static inline u32 __raw_readl(const volatile void __iomem *addr) +static inline u32 __raw_readl_no_log(const volatile void __iomem *addr) { u32 val; asm volatile("ldr %0, %1" @@ -122,6 +128,58 @@ static inline u32 __raw_readl(const volatile void __iomem *addr) return val; } +static inline u64 __raw_readq_no_log(const volatile void __iomem *addr) +{ + register u64 val asm ("r2"); + + asm volatile("ldrd %1, %0" + : "+Qo" (*(volatile u64 __force *)addr), + "=r" (val)); + return val; +} + +/* + * There may be cases when clients don't want to support or can't support the + * logging. The appropriate functions can be used but clients should carefully + * consider why they can't support the logging. + */ + +#define __raw_write_logged(v, a, _t) ({ \ + int _ret; \ + volatile void __iomem *_a = (a); \ + void *_addr = (void __force *)(_a); \ + _ret = uncached_logk(LOGK_WRITEL, _addr); \ + ETB_WAYPOINT; \ + __raw_write##_t##_no_log((v), _a); \ + if (_ret) \ + LOG_BARRIER; \ + }) + + +#define __raw_writeb(v, a) __raw_write_logged((v), (a), b) +#define __raw_writew(v, a) __raw_write_logged((v), (a), w) +#define __raw_writel(v, a) __raw_write_logged((v), (a), l) +#define __raw_writeq(v, a) __raw_write_logged((v), (a), q) + +#define __raw_read_logged(a, _l, _t) ({ \ + unsigned _t __a; \ + const volatile void __iomem *_a = (a); \ + void *_addr = (void __force *)(_a); \ + int _ret; \ + _ret = uncached_logk(LOGK_READL, _addr); \ + ETB_WAYPOINT; \ + __a = __raw_read##_l##_no_log(_a);\ + if (_ret) \ + LOG_BARRIER; \ + __a; \ + }) + + +#define __raw_readb(a) __raw_read_logged((a), b, char) +#define __raw_readw(a) __raw_read_logged((a), w, short) +#define __raw_readl(a) __raw_read_logged((a), l, int) +#define __raw_readq(a) __raw_read_logged((a), q, long long) + /* * Architecture ioremap implementation. */ @@ -291,18 +349,32 @@ extern void _memset_io(volatile void __iomem *, int, size_t); __raw_readw(c)); __r; }) #define readl_relaxed(c) ({ u32 __r = le32_to_cpu((__force __le32) \ __raw_readl(c)); __r; }) - -#define writeb_relaxed(v,c) __raw_writeb(v,c) -#define writew_relaxed(v,c) __raw_writew((__force u16) cpu_to_le16(v),c) -#define writel_relaxed(v,c) __raw_writel((__force u32) cpu_to_le32(v),c) +#define readq_relaxed(c) ({ u64 __r = le64_to_cpu((__force __le64) \ + __raw_readq(c)); __r; }) +#define readb_relaxed_no_log(c) ({ u8 __r = __raw_readb_no_log(c); __r; }) +#define readl_relaxed_no_log(c) ({ u32 __r = le32_to_cpu((__force __le32) \ + __raw_readl_no_log(c)); __r; }) +#define readq_relaxed_no_log(c) ({ u64 __r = le64_to_cpu((__force __le64) \ + __raw_readq_no_log(c)); __r; }) + + +#define writeb_relaxed(v, c) __raw_writeb(v, c) +#define writew_relaxed(v, c) __raw_writew((__force u16) cpu_to_le16(v), c) +#define writel_relaxed(v, c) __raw_writel((__force u32) cpu_to_le32(v), c) +#define writeq_relaxed(v, c) __raw_writeq((__force u64) cpu_to_le64(v), c) +#define writeb_relaxed_no_log(v, c) ((void)__raw_writeb_no_log((v), (c))) +#define writel_relaxed_no_log(v, c) __raw_writel_no_log((__force u32) cpu_to_le32(v), c) +#define writeq_relaxed_no_log(v, c) __raw_writeq_no_log((__force u64) cpu_to_le64(v), c) #define readb(c) ({ u8 __v = readb_relaxed(c); __iormb(); __v; }) #define readw(c) ({ u16 __v = readw_relaxed(c); __iormb(); __v; }) #define readl(c) ({ u32 __v = readl_relaxed(c); __iormb(); __v; }) +#define readq(c) ({ u64 __v = readq_relaxed(c); __iormb(); __v; }) #define writeb(v,c) ({ __iowmb(); writeb_relaxed(v,c); }) #define writew(v,c) ({ __iowmb(); writew_relaxed(v,c); }) #define writel(v,c) ({ __iowmb(); writel_relaxed(v,c); }) +#define writeq(v, c) ({ __iowmb(); writeq_relaxed(v, c); }) #define readsb(p,d,l) __raw_readsb(p,d,l) #define readsw(p,d,l) __raw_readsw(p,d,l) @@ -401,6 +473,23 @@ void __iomem *ioremap_wc(resource_size_t res_cookie, size_t size); void iounmap(volatile void __iomem *iomem_cookie); #define iounmap iounmap +/* + * io{read,write}{8,16,32,64} macros + */ +#ifndef ioread8 +#define ioread8(p) ({ unsigned int __v = __raw_readb(p); __iormb(); __v; }) +#define ioread16(p) ({ unsigned int __v = le16_to_cpu((__force __le16)__raw_readw(p)); __iormb(); __v; }) +#define ioread32(p) ({ unsigned int __v = le32_to_cpu((__force __le32)__raw_readl(p)); __iormb(); __v; }) +#define ioread64(p) ({ unsigned int __v = le64_to_cpu((__force __le64)__raw_readq(p)); __iormb(); __v; }) + +#define ioread64be(p) ({ unsigned int __v = be64_to_cpu((__force __be64)__raw_readq(p)); __iormb(); __v; }) + +#define iowrite8(v, p) ({ __iowmb(); __raw_writeb(v, p); }) +#define iowrite16(v, p) ({ __iowmb(); __raw_writew((__force __u16)cpu_to_le16(v), p); }) +#define iowrite32(v, p) ({ __iowmb(); __raw_writel((__force __u32)cpu_to_le32(v), p); }) +#define iowrite64(v, p) ({ __iowmb(); __raw_writeq((__force __u64)cpu_to_le64(v), p); }) + +#define iowrite64be(v, p) ({ __iowmb(); __raw_writeq((__force __u64)cpu_to_be64(v), p); }) /* * io{read,write}{16,32}be() macros @@ -419,6 +508,7 @@ extern void __iomem *ioport_map(unsigned long port, unsigned int nr); #define ioport_unmap ioport_unmap extern void ioport_unmap(void __iomem *addr); #endif +#endif struct pci_dev; diff --git a/arch/arm/mach-qcom/Kconfig b/arch/arm/mach-qcom/Kconfig index 2256cd1e25d1..918a3fa7e938 100644 --- a/arch/arm/mach-qcom/Kconfig +++ b/arch/arm/mach-qcom/Kconfig @@ -1,25 +1,35 @@ -menuconfig ARCH_QCOM - bool "Qualcomm Support" if ARCH_MULTI_V7 - select ARCH_SUPPORTS_BIG_ENDIAN - select ARM_GIC - select ARM_AMBA - select PINCTRL - select QCOM_SCM if SMP - help - Support for Qualcomm's devicetree based systems. - if ARCH_QCOM +menu "QCOM SoC Type" config ARCH_MSM8X60 bool "Enable support for MSM8X60" + select ARCH_SUPPORTS_BIG_ENDIAN + select ARM_GIC + select ARM_AMBA + select QCOM_SCM if SMP select CLKSRC_QCOM + select CLKSRC_OF + select COMMON_CLK config ARCH_MSM8960 bool "Enable support for MSM8960" select CLKSRC_QCOM + select ARCH_SUPPORTS_BIG_ENDIAN + select ARM_GIC + select ARM_AMBA + select QCOM_SCM if SMP + select CLKSRC_OF + select COMMON_CLK config ARCH_MSM8974 bool "Enable support for MSM8974" select HAVE_ARM_ARCH_TIMER + select ARCH_SUPPORTS_BIG_ENDIAN + select ARM_GIC + select ARM_AMBA + select QCOM_SCM if SMP + select CLKSRC_OF + select COMMON_CLK +endmenu endif diff --git a/arch/arm/mm/cache-v7.S b/arch/arm/mm/cache-v7.S index a134d8a13d00..2ddf36403d1c 100644 --- a/arch/arm/mm/cache-v7.S +++ b/arch/arm/mm/cache-v7.S @@ -349,7 +349,7 @@ ENDPROC(v7_flush_kern_dcache_area) * - start - virtual start address of region * - end - virtual end address of region */ -v7_dma_inv_range: +ENTRY(v7_dma_inv_range) dcache_line_size r2, r3 sub r3, r2, #1 tst r0, r3 @@ -377,7 +377,7 @@ ENDPROC(v7_dma_inv_range) * - start - virtual start address of region * - end - virtual end address of region */ -v7_dma_clean_range: +ENTRY(v7_dma_clean_range) dcache_line_size r2, r3 sub r3, r2, #1 bic r0, r0, r3 diff --git a/arch/arm/mm/proc-macros.S b/arch/arm/mm/proc-macros.S index c671f345266a..9d14cdd02e00 100644 --- a/arch/arm/mm/proc-macros.S +++ b/arch/arm/mm/proc-macros.S @@ -309,6 +309,8 @@ ENTRY(\name\()_cache_fns) .long \name\()_flush_kern_dcache_area .long \name\()_dma_map_area .long \name\()_dma_unmap_area + .long \name\()_dma_inv_range + .long \name\()_dma_clean_range .long \name\()_dma_flush_range .size \name\()_cache_fns, . - \name\()_cache_fns .endm diff --git a/arch/arm/mm/proc-syms.c b/arch/arm/mm/proc-syms.c index 054b491ff764..70e8b7d34434 100644 --- a/arch/arm/mm/proc-syms.c +++ b/arch/arm/mm/proc-syms.c @@ -30,6 +30,9 @@ EXPORT_SYMBOL(__cpuc_flush_user_all); EXPORT_SYMBOL(__cpuc_flush_user_range); EXPORT_SYMBOL(__cpuc_coherent_kern_range); EXPORT_SYMBOL(__cpuc_flush_dcache_area); +EXPORT_SYMBOL(dmac_inv_range); +EXPORT_SYMBOL(dmac_clean_range); +EXPORT_SYMBOL(dmac_flush_range); #else EXPORT_SYMBOL(cpu_cache); #endif diff --git a/arch/arm64/configs/msmcortex-perf_defconfig b/arch/arm64/configs/msmcortex-perf_defconfig index 63934efc42af..efee2e854d2f 100644 --- a/arch/arm64/configs/msmcortex-perf_defconfig +++ b/arch/arm64/configs/msmcortex-perf_defconfig @@ -82,6 +82,9 @@ 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_ROUTER_PREF=y CONFIG_IPV6_ROUTE_INFO=y @@ -238,6 +241,7 @@ CONFIG_DM_UEVENT=y CONFIG_DM_VERITY=y CONFIG_NETDEVICES=y CONFIG_BONDING=y +CONFIG_DUMMY=y CONFIG_TUN=y CONFIG_SKY2=y CONFIG_RNDIS_IPA=y @@ -510,7 +514,8 @@ CONFIG_MSDOS_FS=y CONFIG_VFAT_FS=y CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y -# CONFIG_MISC_FILESYSTEMS is not set +CONFIG_ECRYPT_FS=y +CONFIG_ECRYPT_FS_MESSAGING=y CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_ISO8859_1=y CONFIG_PRINTK_TIME=y @@ -526,9 +531,11 @@ CONFIG_DEBUG_SET_MODULE_RONX=y CONFIG_DEBUG_RODATA=y CONFIG_DEBUG_ALIGN_RODATA=y CONFIG_KEYS=y +CONFIG_PFK=y CONFIG_SECURITY=y CONFIG_SECURITY_SELINUX=y CONFIG_SECURITY_SMACK=y +CONFIG_CRYPTO_ECHAINIV=y CONFIG_CRYPTO_XCBC=y CONFIG_CRYPTO_MD4=y CONFIG_CRYPTO_TWOFISH=y @@ -537,6 +544,8 @@ CONFIG_CRYPTO_DEV_QCRYPTO=y CONFIG_CRYPTO_DEV_QCOM_MSM_QCE=y CONFIG_CRYPTO_DEV_QCEDEV=y CONFIG_CRYPTO_DEV_OTA_CRYPTO=y +CONFIG_CRYPTO_DEV_QCE=y +CONFIG_CRYPTO_DEV_QCOM_ICE=y CONFIG_ARM64_CRYPTO=y CONFIG_CRYPTO_SHA1_ARM64_CE=y CONFIG_CRYPTO_SHA2_ARM64_CE=y diff --git a/arch/arm64/configs/msmcortex_defconfig b/arch/arm64/configs/msmcortex_defconfig index 66a4f29c552c..5a0b5dd1a26b 100644 --- a/arch/arm64/configs/msmcortex_defconfig +++ b/arch/arm64/configs/msmcortex_defconfig @@ -83,6 +83,9 @@ 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_ROUTER_PREF=y CONFIG_IPV6_ROUTE_INFO=y @@ -243,6 +246,7 @@ CONFIG_DM_UEVENT=y CONFIG_DM_VERITY=y CONFIG_NETDEVICES=y CONFIG_BONDING=y +CONFIG_DUMMY=y CONFIG_TUN=y CONFIG_RNDIS_IPA=y CONFIG_PHYLIB=y @@ -542,7 +546,8 @@ CONFIG_VFAT_FS=y CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y CONFIG_EFIVAR_FS=y -# CONFIG_MISC_FILESYSTEMS is not set +CONFIG_ECRYPT_FS=y +CONFIG_ECRYPT_FS_MESSAGING=y # CONFIG_NETWORK_FILESYSTEMS is not set CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_ISO8859_1=y @@ -612,10 +617,12 @@ CONFIG_CORESIGHT_TPDM=y CONFIG_CORESIGHT_QPDI=y CONFIG_CORESIGHT_SOURCE_DUMMY=y CONFIG_KEYS=y +CONFIG_PFK=y CONFIG_SECURITY=y CONFIG_SECURITY_SELINUX=y CONFIG_SECURITY_SMACK=y -CONFIG_CRYPTO_XCBC=m +CONFIG_CRYPTO_ECHAINIV=y +CONFIG_CRYPTO_XCBC=y CONFIG_CRYPTO_MD4=y CONFIG_CRYPTO_TWOFISH=y CONFIG_CRYPTO_ANSI_CPRNG=y @@ -623,6 +630,8 @@ CONFIG_CRYPTO_DEV_QCRYPTO=y CONFIG_CRYPTO_DEV_QCOM_MSM_QCE=y CONFIG_CRYPTO_DEV_QCEDEV=y CONFIG_CRYPTO_DEV_OTA_CRYPTO=y +CONFIG_CRYPTO_DEV_QCE=y +CONFIG_CRYPTO_DEV_QCOM_ICE=y CONFIG_ARM64_CRYPTO=y CONFIG_CRYPTO_SHA1_ARM64_CE=y CONFIG_CRYPTO_SHA2_ARM64_CE=y diff --git a/drivers/media/platform/msm/camera_v2/camera/camera.c b/drivers/media/platform/msm/camera_v2/camera/camera.c index d88c993bcc2d..60a834588767 100644 --- a/drivers/media/platform/msm/camera_v2/camera/camera.c +++ b/drivers/media/platform/msm/camera_v2/camera/camera.c @@ -93,6 +93,9 @@ static int camera_v4l2_querycap(struct file *filep, void *fh, int rc; struct v4l2_event event; + if (msm_is_daemon_present() == false) + return 0; + /* can use cap->driver to make differentiation */ camera_pack_event(filep, MSM_CAMERA_GET_PARM, MSM_CAMERA_PRIV_QUERY_CAP, -1, &event); @@ -112,6 +115,9 @@ static int camera_v4l2_s_crop(struct file *filep, void *fh, int rc = 0; struct v4l2_event event; + if (msm_is_daemon_present() == false) + return 0; + if (crop->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { camera_pack_event(filep, MSM_CAMERA_SET_PARM, @@ -133,6 +139,9 @@ static int camera_v4l2_g_crop(struct file *filep, void *fh, int rc = 0; struct v4l2_event event; + if (msm_is_daemon_present() == false) + return 0; + if (crop->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { camera_pack_event(filep, MSM_CAMERA_GET_PARM, MSM_CAMERA_PRIV_G_CROP, -1, &event); @@ -153,6 +162,9 @@ static int camera_v4l2_queryctrl(struct file *filep, void *fh, int rc = 0; struct v4l2_event event; + if (msm_is_daemon_present() == false) + return 0; + if (ctrl->type == V4L2_CTRL_TYPE_MENU) { camera_pack_event(filep, MSM_CAMERA_GET_PARM, @@ -282,6 +294,10 @@ static int camera_v4l2_streamon(struct file *filep, void *fh, mutex_lock(&sp->lock); rc = vb2_streamon(&sp->vb2_q, buf_type); mutex_unlock(&sp->lock); + + if (msm_is_daemon_present() == false) + return 0; + camera_pack_event(filep, MSM_CAMERA_SET_PARM, MSM_CAMERA_PRIV_STREAM_ON, -1, &event); @@ -297,17 +313,18 @@ static int camera_v4l2_streamoff(struct file *filep, void *fh, enum v4l2_buf_type buf_type) { struct v4l2_event event; - int rc; + int rc = 0; struct camera_v4l2_private *sp = fh_to_private(fh); - camera_pack_event(filep, MSM_CAMERA_SET_PARM, - MSM_CAMERA_PRIV_STREAM_OFF, -1, &event); - - rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT); - if (rc < 0) - return rc; + if (msm_is_daemon_present() != false) { + camera_pack_event(filep, MSM_CAMERA_SET_PARM, + MSM_CAMERA_PRIV_STREAM_OFF, -1, &event); - rc = camera_check_event_status(&event); + rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT); + if (rc < 0) + return rc; + rc = camera_check_event_status(&event); + } mutex_lock(&sp->lock); vb2_streamoff(&sp->vb2_q, buf_type); mutex_unlock(&sp->lock); @@ -319,6 +336,9 @@ static int camera_v4l2_g_fmt_vid_cap_mplane(struct file *filep, void *fh, { int rc = -EINVAL; + if (msm_is_daemon_present() == false) + return 0; + if (pfmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { struct v4l2_event event; @@ -363,22 +383,22 @@ static int camera_v4l2_s_fmt_vid_cap_mplane(struct file *filep, void *fh, pr_debug("%s: plane size[%d]\n", __func__, user_fmt->plane_sizes[i]); - camera_pack_event(filep, MSM_CAMERA_SET_PARM, - MSM_CAMERA_PRIV_S_FMT, -1, &event); - - rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT); - if (rc < 0) - return rc; + if (msm_is_daemon_present() != false) { + camera_pack_event(filep, MSM_CAMERA_SET_PARM, + MSM_CAMERA_PRIV_S_FMT, -1, &event); - rc = camera_check_event_status(&event); - if (rc < 0) - return rc; + rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT); + if (rc < 0) + return rc; + rc = camera_check_event_status(&event); + if (rc < 0) + return rc; + } sp->is_vb2_valid = 1; } return rc; - } static int camera_v4l2_try_fmt_vid_cap_mplane(struct file *filep, void *fh, @@ -412,14 +432,15 @@ static int camera_v4l2_s_parm(struct file *filep, void *fh, if (rc < 0) return rc; - rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT); - if (rc < 0) - goto error; - - rc = camera_check_event_status(&event); - if (rc < 0) - goto error; + if (msm_is_daemon_present() != false) { + rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT); + if (rc < 0) + goto error; + rc = camera_check_event_status(&event); + if (rc < 0) + goto error; + } /* use stream_id as stream index */ parm->parm.capture.extendedmode = sp->stream_id; sp->stream_created = true; @@ -607,20 +628,19 @@ static int camera_v4l2_open(struct file *filep) goto command_ack_q_fail; } - camera_pack_event(filep, MSM_CAMERA_NEW_SESSION, 0, -1, &event); - rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT); - if (rc < 0) { - pr_err("%s : posting of NEW_SESSION event failed\n", - __func__); - pr_err("%s : Line %d rc %d\n", __func__, __LINE__, rc); - goto post_fail; - } + if (msm_is_daemon_present() != false) { + camera_pack_event(filep, MSM_CAMERA_NEW_SESSION, + 0, -1, &event); + rc = msm_post_event(&event, MSM_POST_EVT_TIMEOUT); + if (rc < 0) { + pr_err("%s : NEW_SESSION event failed,rc %d\n", + __func__, rc); + goto post_fail; + } - rc = camera_check_event_status(&event); - if (rc < 0) { - pr_err("%s : checking event status fails Line %d rc %d\n", - __func__, __LINE__, rc); - goto post_fail; + rc = camera_check_event_status(&event); + if (rc < 0) + goto post_fail; } /* Enable power collapse latency */ msm_pm_qos_update_request(CAMERA_ENABLE_PC_LATENCY); @@ -671,7 +691,6 @@ static unsigned int camera_v4l2_poll(struct file *filep, static int camera_v4l2_close(struct file *filep) { - int rc = 0; struct v4l2_event event; struct msm_video_device *pvdev = video_drvdata(filep); struct camera_v4l2_private *sp = fh_to_private(filep->private_data); @@ -688,18 +707,22 @@ static int camera_v4l2_close(struct file *filep) opn_idx &= ~mask; atomic_set(&pvdev->opened, opn_idx); - if (sp->stream_created == true) { + if (msm_is_daemon_present() != false && sp->stream_created == true) { pr_debug("%s: close stream_id=%d\n", __func__, sp->stream_id); camera_pack_event(filep, MSM_CAMERA_SET_PARM, MSM_CAMERA_PRIV_DEL_STREAM, -1, &event); msm_post_event(&event, MSM_POST_EVT_TIMEOUT); - sp->stream_created = false; } - if (atomic_read(&pvdev->opened) == 0) { - camera_pack_event(filep, MSM_CAMERA_DEL_SESSION, 0, -1, &event); - msm_post_event(&event, MSM_POST_EVT_TIMEOUT); + if (sp->stream_created == true) + sp->stream_created = false; + if (atomic_read(&pvdev->opened) == 0) { + if (msm_is_daemon_present() != false) { + camera_pack_event(filep, MSM_CAMERA_DEL_SESSION, + 0, -1, &event); + msm_post_event(&event, MSM_POST_EVT_TIMEOUT); + } msm_delete_command_ack_q(pvdev->vdev->num, 0); msm_delete_stream(pvdev->vdev->num, sp->stream_id); mutex_unlock(&session->close_lock); @@ -720,7 +743,7 @@ static int camera_v4l2_close(struct file *filep) camera_v4l2_fh_release(filep); - return rc; + return 0; } #ifdef CONFIG_COMPAT diff --git a/drivers/media/platform/msm/camera_v2/msm.c b/drivers/media/platform/msm/camera_v2/msm.c index 2f1b87148106..a34dbb80b468 100644 --- a/drivers/media/platform/msm/camera_v2/msm.c +++ b/drivers/media/platform/msm/camera_v2/msm.c @@ -40,6 +40,12 @@ static struct pm_qos_request msm_v4l2_pm_qos_request; static struct msm_queue_head *msm_session_q; +/* This variable represent daemon status + * true = daemon present (default state) + * false = daemon is NOT present + */ +bool is_daemon_status = true; + /* config node envent queue */ static struct v4l2_fh *msm_eventq; spinlock_t msm_eventq_lock; @@ -681,6 +687,12 @@ static long msm_private_ioctl(struct file *file, void *fh, unsigned long spin_flags = 0; struct msm_sd_subdev *msm_sd; + if (cmd == MSM_CAM_V4L2_IOCTL_DAEMON_DISABLED) { + is_daemon_status = false; + return 0; + } + + memset(&event, 0, sizeof(struct v4l2_event)); session_id = event_data->session_id; stream_id = event_data->stream_id; diff --git a/drivers/media/platform/msm/camera_v2/msm.h b/drivers/media/platform/msm/camera_v2/msm.h index 6920facef777..cab07df2a5bb 100644 --- a/drivers/media/platform/msm/camera_v2/msm.h +++ b/drivers/media/platform/msm/camera_v2/msm.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2012-2016, 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 @@ -40,6 +40,8 @@ #define CAMERA_DISABLE_PC_LATENCY 100 #define CAMERA_ENABLE_PC_LATENCY PM_QOS_DEFAULT_VALUE +extern bool is_daemon_status; + struct msm_video_device { struct video_device *vdev; atomic_t opened; @@ -110,8 +112,12 @@ struct msm_session { struct mutex close_lock; }; -void msm_pm_qos_update_request(int val); +static inline bool msm_is_daemon_present(void) +{ + return is_daemon_status; +} +void msm_pm_qos_update_request(int val); int msm_post_event(struct v4l2_event *event, int timeout); int msm_create_session(unsigned int session, struct video_device *vdev); int msm_destroy_session(unsigned int session_id); diff --git a/drivers/media/platform/msm/camera_v2/sensor/ois/msm_ois.c b/drivers/media/platform/msm/camera_v2/sensor/ois/msm_ois.c index 82c9e5c5befa..5a891592b44f 100644 --- a/drivers/media/platform/msm/camera_v2/sensor/ois/msm_ois.c +++ b/drivers/media/platform/msm/camera_v2/sensor/ois/msm_ois.c @@ -13,6 +13,7 @@ #define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__ #include <linux/module.h> +#include <linux/firmware.h> #include "msm_sd.h" #include "msm_ois.h" #include "msm_cci.h" @@ -32,6 +33,124 @@ static int32_t msm_ois_power_down(struct msm_ois_ctrl_t *o_ctrl); static struct i2c_driver msm_ois_i2c_driver; +static int32_t msm_ois_download(struct msm_ois_ctrl_t *o_ctrl) +{ + uint16_t bytes_in_tx = 0; + uint16_t total_bytes = 0; + uint8_t *ptr = NULL; + int32_t rc = 0; + const struct firmware *fw = NULL; + const char *fw_name_prog = NULL; + const char *fw_name_coeff = NULL; + char name_prog[MAX_SENSOR_NAME] = {0}; + char name_coeff[MAX_SENSOR_NAME] = {0}; + struct device *dev = &(o_ctrl->pdev->dev); + enum msm_camera_i2c_reg_addr_type save_addr_type; + + CDBG("Enter\n"); + save_addr_type = o_ctrl->i2c_client.addr_type; + o_ctrl->i2c_client.addr_type = MSM_CAMERA_I2C_BYTE_ADDR; + + snprintf(name_coeff, MAX_SENSOR_NAME, "%s.coeff", + o_ctrl->oboard_info->ois_name); + + snprintf(name_prog, MAX_SENSOR_NAME, "%s.prog", + o_ctrl->oboard_info->ois_name); + + /* cast pointer as const pointer*/ + fw_name_prog = name_prog; + fw_name_coeff = name_coeff; + + /* Load FW */ + rc = request_firmware(&fw, fw_name_prog, dev); + if (rc) { + dev_err(dev, "Failed to locate %s\n", fw_name_prog); + o_ctrl->i2c_client.addr_type = save_addr_type; + return rc; + } + + total_bytes = fw->size; + for (ptr = (uint8_t *)fw->data; total_bytes; + total_bytes -= bytes_in_tx, ptr += bytes_in_tx) { + bytes_in_tx = (total_bytes > 10) ? 10 : total_bytes; + rc = o_ctrl->i2c_client.i2c_func_tbl->i2c_write_seq( + &o_ctrl->i2c_client, o_ctrl->oboard_info->opcode.prog, + ptr, bytes_in_tx); + if (rc < 0) { + pr_err("Failed:remaining bytes to be downloaded:%d\n", + bytes_in_tx); + /* abort download fw and return error*/ + goto release_firmware; + } + } + release_firmware(fw); + + rc = request_firmware(&fw, fw_name_coeff, dev); + if (rc) { + dev_err(dev, "Failed to locate %s\n", fw_name_coeff); + o_ctrl->i2c_client.addr_type = save_addr_type; + return rc; + } + total_bytes = fw->size; + for (ptr = (uint8_t *)fw->data; total_bytes; + total_bytes -= bytes_in_tx, ptr += bytes_in_tx) { + bytes_in_tx = (total_bytes > 10) ? 10 : total_bytes; + rc = o_ctrl->i2c_client.i2c_func_tbl->i2c_write_seq( + &o_ctrl->i2c_client, o_ctrl->oboard_info->opcode.coeff, + ptr, bytes_in_tx); + if (rc < 0) { + pr_err("Failed:remaining bytes to be downloaded:%d\n", + total_bytes); + /* abort download fw*/ + break; + } + } +release_firmware: + release_firmware(fw); + o_ctrl->i2c_client.addr_type = save_addr_type; + + return rc; +} + +static int32_t msm_ois_data_config(struct msm_ois_ctrl_t *o_ctrl, + struct msm_ois_slave_info *slave_info) +{ + int rc = 0; + struct msm_camera_cci_client *cci_client = NULL; + + CDBG("Enter\n"); + if (!slave_info) { + pr_err("failed : invalid slave_info\n"); + return -EINVAL; + } + /* fill ois slave info*/ + if (strlcpy(o_ctrl->oboard_info->ois_name, slave_info->ois_name, + sizeof(o_ctrl->oboard_info->ois_name)) < 0) { + pr_err("failed: copy_from_user\n"); + return -EFAULT; + } + memcpy(&(o_ctrl->oboard_info->opcode), &(slave_info->opcode), + sizeof(struct msm_ois_opcode)); + o_ctrl->oboard_info->i2c_slaveaddr = slave_info->i2c_addr; + + /* config cci_client*/ + if (o_ctrl->ois_device_type == MSM_CAMERA_PLATFORM_DEVICE) { + cci_client = o_ctrl->i2c_client.cci_client; + cci_client->sid = + o_ctrl->oboard_info->i2c_slaveaddr >> 1; + cci_client->retries = 3; + cci_client->id_map = 0; + cci_client->cci_i2c_master = o_ctrl->cci_master; + } else { + o_ctrl->i2c_client.client->addr = + o_ctrl->oboard_info->i2c_slaveaddr; + } + o_ctrl->i2c_client.addr_type = MSM_CAMERA_I2C_WORD_ADDR; + + CDBG("Exit\n"); + return rc; +} + static int32_t msm_ois_write_settings(struct msm_ois_ctrl_t *o_ctrl, uint16_t size, struct reg_settings_ois_t *settings) { @@ -370,6 +489,40 @@ static int32_t msm_ois_config(struct msm_ois_ctrl_t *o_ctrl, return rc; } +static int32_t msm_ois_config_download(struct msm_ois_ctrl_t *o_ctrl, + void __user *argp) +{ + struct msm_ois_cfg_download_data *cdata = + (struct msm_ois_cfg_download_data *)argp; + int32_t rc = 0; + + if (!o_ctrl || !cdata) { + pr_err("failed: Invalid data\n"); + return -EINVAL; + } + mutex_lock(o_ctrl->ois_mutex); + CDBG("Enter\n"); + CDBG("%s type %d\n", __func__, cdata->cfgtype); + switch (cdata->cfgtype) { + case CFG_OIS_DATA_CONFIG: + rc = msm_ois_data_config(o_ctrl, &cdata->slave_info); + if (rc < 0) + pr_err("Failed ois data config %d\n", rc); + break; + case CFG_OIS_DOWNLOAD: + rc = msm_ois_download(o_ctrl); + if (rc < 0) + pr_err("Failed ois download %d\n", rc); + break; + default: + break; + } + mutex_unlock(o_ctrl->ois_mutex); + CDBG("Exit\n"); + return rc; +} + + static int32_t msm_ois_get_subdev_id(struct msm_ois_ctrl_t *o_ctrl, void *arg) { @@ -454,6 +607,8 @@ static long msm_ois_subdev_ioctl(struct v4l2_subdev *sd, return msm_ois_get_subdev_id(o_ctrl, argp); case VIDIOC_MSM_OIS_CFG: return msm_ois_config(o_ctrl, argp); + case VIDIOC_MSM_OIS_CFG_DOWNLOAD: + return msm_ois_config_download(o_ctrl, argp); case MSM_SD_SHUTDOWN: if (!o_ctrl->i2c_client.i2c_func_tbl) { pr_err("o_ctrl->i2c_client.i2c_func_tbl NULL\n"); @@ -696,22 +851,28 @@ static int32_t msm_ois_platform_probe(struct platform_device *pdev) pr_err("%s:%d failed no memory\n", __func__, __LINE__); return -ENOMEM; } + + msm_ois_t->oboard_info = kzalloc(sizeof( + struct msm_ois_board_info), GFP_KERNEL); + if (!msm_ois_t->oboard_info) { + kfree(msm_ois_t); + return -ENOMEM; + } + rc = of_property_read_u32((&pdev->dev)->of_node, "cell-index", &pdev->id); CDBG("cell-index %d, rc %d\n", pdev->id, rc); if (rc < 0) { - kfree(msm_ois_t); pr_err("failed rc %d\n", rc); - return rc; + goto release_memory; } rc = of_property_read_u32((&pdev->dev)->of_node, "qcom,cci-master", &msm_ois_t->cci_master); CDBG("qcom,cci-master %d, rc %d\n", msm_ois_t->cci_master, rc); if (rc < 0 || msm_ois_t->cci_master >= MASTER_MAX) { - kfree(msm_ois_t); pr_err("failed rc %d\n", rc); - return rc; + goto release_memory; } if (of_find_property((&pdev->dev)->of_node, @@ -720,9 +881,8 @@ static int32_t msm_ois_platform_probe(struct platform_device *pdev) rc = msm_camera_get_dt_vreg_data((&pdev->dev)->of_node, &vreg_cfg->cam_vreg, &vreg_cfg->num_vreg); if (rc < 0) { - kfree(msm_ois_t); pr_err("failed rc %d\n", rc); - return rc; + goto release_memory; } } @@ -753,9 +913,8 @@ static int32_t msm_ois_platform_probe(struct platform_device *pdev) struct msm_camera_cci_client), GFP_KERNEL); if (!msm_ois_t->i2c_client.cci_client) { kfree(msm_ois_t->vreg_cfg.cam_vreg); - kfree(msm_ois_t); - pr_err("failed no memory\n"); - return -ENOMEM; + rc = -ENOMEM; + goto release_memory; } cci_client = msm_ois_t->i2c_client.cci_client; @@ -784,6 +943,10 @@ static int32_t msm_ois_platform_probe(struct platform_device *pdev) CDBG("Exit\n"); return rc; +release_memory: + kfree(msm_ois_t->oboard_info); + kfree(msm_ois_t); + return rc; } static const struct of_device_id msm_ois_i2c_dt_match[] = { diff --git a/drivers/media/platform/msm/camera_v2/sensor/ois/msm_ois.h b/drivers/media/platform/msm/camera_v2/sensor/ois/msm_ois.h index b9f8d4a0c3c8..614cc6c8cd5d 100644 --- a/drivers/media/platform/msm/camera_v2/sensor/ois/msm_ois.h +++ b/drivers/media/platform/msm/camera_v2/sensor/ois/msm_ois.h @@ -41,6 +41,12 @@ struct msm_ois_vreg { int num_vreg; }; +struct msm_ois_board_info { + char ois_name[MAX_OIS_NAME_SIZE]; + uint32_t i2c_slaveaddr; + struct msm_ois_opcode opcode; +}; + struct msm_ois_ctrl_t { struct i2c_driver *i2c_driver; struct platform_driver *pdriver; @@ -61,6 +67,7 @@ struct msm_ois_ctrl_t { struct msm_camera_gpio_conf *gconf; struct msm_pinctrl_info pinctrl_info; uint8_t cam_pinctrl_status; + struct msm_ois_board_info *oboard_info; }; #endif diff --git a/drivers/media/platform/msm/vidc/msm_venc.c b/drivers/media/platform/msm/vidc/msm_venc.c index 1d5191373d36..14b62984eae9 100644 --- a/drivers/media/platform/msm/vidc/msm_venc.c +++ b/drivers/media/platform/msm/vidc/msm_venc.c @@ -1186,6 +1186,7 @@ static struct msm_vidc_ctrl msm_venc_ctrls[] = { .minimum = V4L2_CID_MPEG_VIDC_VIDEO_LOWLATENCY_DISABLE, .maximum = V4L2_CID_MPEG_VIDC_VIDEO_LOWLATENCY_ENABLE, .default_value = V4L2_CID_MPEG_VIDC_VIDEO_LOWLATENCY_DISABLE, + .step = 1, }, { .id = V4L2_CID_MPEG_VIDC_VIDEO_BLUR_WIDTH, diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 8e7812a89e67..0140c727e44d 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -721,6 +721,20 @@ config MFD_SPMI_PMIC Say M here if you want to include support for the SPMI PMIC series as a module. The module will be called "qcom-spmi-pmic". +config MFD_I2C_PMIC + tristate "QTI I2C PMICs" + depends on OF + depends on I2C + select IRQ_DOMAIN + select REGMAP_I2C + help + This enables support for controlling Qualcomm Technologies, Inc. + PMICs over I2C. The driver controls interrupts, and provides register + access for all of the device's peripherals. + + Say M here if you want to include support for the I2C PMIC series as + a module. The module will be called "qcom-i2c-pmic". + config MFD_RDC321X tristate "RDC R-321x southbridge" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 5bd54625c3e4..26c118f77484 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -161,6 +161,7 @@ obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o omap-usb-tll.o obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o ssbi.o obj-$(CONFIG_MFD_QCOM_RPM) += qcom_rpm.o obj-$(CONFIG_MFD_SPMI_PMIC) += qcom-spmi-pmic.o +obj-$(CONFIG_MFD_I2C_PMIC) += qcom-i2c-pmic.o obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o obj-$(CONFIG_MFD_TPS65090) += tps65090.o obj-$(CONFIG_MFD_AAT2870_CORE) += aat2870-core.o diff --git a/drivers/mfd/qcom-i2c-pmic.c b/drivers/mfd/qcom-i2c-pmic.c new file mode 100644 index 000000000000..4d0bdce755a6 --- /dev/null +++ b/drivers/mfd/qcom-i2c-pmic.c @@ -0,0 +1,658 @@ +/* Copyright (c) 2016 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) "I2C PMIC: %s: " fmt, __func__ + +#include <linux/bitops.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/pinctrl/consumer.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define I2C_INTR_STATUS_BASE 0x0550 +#define INT_RT_STS_OFFSET 0x10 +#define INT_SET_TYPE_OFFSET 0x11 +#define INT_POL_HIGH_OFFSET 0x12 +#define INT_POL_LOW_OFFSET 0x13 +#define INT_LATCHED_CLR_OFFSET 0x14 +#define INT_EN_SET_OFFSET 0x15 +#define INT_EN_CLR_OFFSET 0x16 +#define INT_LATCHED_STS_OFFSET 0x18 +#define INT_PENDING_STS_OFFSET 0x19 +#define INT_MID_SEL_OFFSET 0x1A +#define INT_MID_SEL_MASK GENMASK(1, 0) +#define INT_PRIORITY_OFFSET 0x1B +#define INT_PRIORITY_BIT BIT(0) + +enum { + IRQ_SET_TYPE = 0, + IRQ_POL_HIGH, + IRQ_POL_LOW, + IRQ_LATCHED_CLR, /* not needed but makes life easy */ + IRQ_EN_SET, + IRQ_MAX_REGS, +}; + +struct i2c_pmic_periph { + void *data; + u16 addr; + u8 cached[IRQ_MAX_REGS]; + u8 synced[IRQ_MAX_REGS]; + u8 wake; + struct mutex lock; +}; + +struct i2c_pmic { + struct device *dev; + struct i2c_client *client; + struct regmap *regmap; + struct irq_domain *domain; + struct i2c_pmic_periph *periph; + struct pinctrl *pinctrl; + const char *pinctrl_name; + int num_periphs; +}; + +static void i2c_pmic_irq_bus_lock(struct irq_data *d) +{ + struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d); + + mutex_lock(&periph->lock); +} + +static void i2c_pmic_sync_type_polarity(struct i2c_pmic *chip, + struct i2c_pmic_periph *periph) +{ + int rc; + + /* did any irq type change? */ + if (periph->cached[IRQ_SET_TYPE] ^ periph->synced[IRQ_SET_TYPE]) { + rc = regmap_write(chip->regmap, + periph->addr | INT_SET_TYPE_OFFSET, + periph->cached[IRQ_SET_TYPE]); + if (rc < 0) { + pr_err("Couldn't set periph 0x%04x irqs 0x%02x type rc=%d\n", + periph->addr, periph->cached[IRQ_SET_TYPE], rc); + return; + } + + periph->synced[IRQ_SET_TYPE] = periph->cached[IRQ_SET_TYPE]; + } + + /* did any polarity high change? */ + if (periph->cached[IRQ_POL_HIGH] ^ periph->synced[IRQ_POL_HIGH]) { + rc = regmap_write(chip->regmap, + periph->addr | INT_POL_HIGH_OFFSET, + periph->cached[IRQ_POL_HIGH]); + if (rc < 0) { + pr_err("Couldn't set periph 0x%04x irqs 0x%02x polarity high rc=%d\n", + periph->addr, periph->cached[IRQ_POL_HIGH], rc); + return; + } + + periph->synced[IRQ_POL_HIGH] = periph->cached[IRQ_POL_HIGH]; + } + + /* did any polarity low change? */ + if (periph->cached[IRQ_POL_LOW] ^ periph->synced[IRQ_POL_LOW]) { + rc = regmap_write(chip->regmap, + periph->addr | INT_POL_LOW_OFFSET, + periph->cached[IRQ_POL_LOW]); + if (rc < 0) { + pr_err("Couldn't set periph 0x%04x irqs 0x%02x polarity low rc=%d\n", + periph->addr, periph->cached[IRQ_POL_LOW], rc); + return; + } + + periph->synced[IRQ_POL_LOW] = periph->cached[IRQ_POL_LOW]; + } +} + +static void i2c_pmic_sync_enable(struct i2c_pmic *chip, + struct i2c_pmic_periph *periph) +{ + u8 en_set, en_clr; + int rc; + + /* determine which irqs were enabled and which were disabled */ + en_clr = periph->synced[IRQ_EN_SET] & ~periph->cached[IRQ_EN_SET]; + en_set = ~periph->synced[IRQ_EN_SET] & periph->cached[IRQ_EN_SET]; + + /* were any irqs disabled? */ + if (en_clr) { + rc = regmap_write(chip->regmap, + periph->addr | INT_EN_CLR_OFFSET, en_clr); + if (rc < 0) { + pr_err("Couldn't disable periph 0x%04x irqs 0x%02x rc=%d\n", + periph->addr, en_clr, rc); + return; + } + } + + /* were any irqs enabled? */ + if (en_set) { + rc = regmap_write(chip->regmap, + periph->addr | INT_EN_SET_OFFSET, en_set); + if (rc < 0) { + pr_err("Couldn't enable periph 0x%04x irqs 0x%02x rc=%d\n", + periph->addr, en_set, rc); + return; + } + } + + /* irq enabled status was written to hardware */ + periph->synced[IRQ_EN_SET] = periph->cached[IRQ_EN_SET]; +} + +static void i2c_pmic_irq_bus_sync_unlock(struct irq_data *d) +{ + struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d); + struct i2c_pmic *chip = periph->data; + + i2c_pmic_sync_type_polarity(chip, periph); + i2c_pmic_sync_enable(chip, periph); + mutex_unlock(&periph->lock); +} + +static void i2c_pmic_irq_disable(struct irq_data *d) +{ + struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d); + + periph->cached[IRQ_EN_SET] &= ~d->hwirq & 0xFF; +} + +static void i2c_pmic_irq_enable(struct irq_data *d) +{ + struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d); + + periph->cached[IRQ_EN_SET] |= d->hwirq & 0xFF; +} + +static int i2c_pmic_irq_set_type(struct irq_data *d, unsigned int irq_type) +{ + struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d); + + switch (irq_type) { + case IRQ_TYPE_EDGE_RISING: + periph->cached[IRQ_SET_TYPE] |= d->hwirq & 0xFF; + periph->cached[IRQ_POL_HIGH] |= d->hwirq & 0xFF; + periph->cached[IRQ_POL_LOW] &= ~d->hwirq & 0xFF; + break; + case IRQ_TYPE_EDGE_FALLING: + periph->cached[IRQ_SET_TYPE] |= d->hwirq & 0xFF; + periph->cached[IRQ_POL_HIGH] &= ~d->hwirq & 0xFF; + periph->cached[IRQ_POL_LOW] |= d->hwirq & 0xFF; + break; + case IRQ_TYPE_EDGE_BOTH: + periph->cached[IRQ_SET_TYPE] |= d->hwirq & 0xFF; + periph->cached[IRQ_POL_HIGH] |= d->hwirq & 0xFF; + periph->cached[IRQ_POL_LOW] |= d->hwirq & 0xFF; + break; + case IRQ_TYPE_LEVEL_HIGH: + periph->cached[IRQ_SET_TYPE] &= ~d->hwirq & 0xFF; + periph->cached[IRQ_POL_HIGH] |= d->hwirq & 0xFF; + periph->cached[IRQ_POL_LOW] &= ~d->hwirq & 0xFF; + break; + case IRQ_TYPE_LEVEL_LOW: + periph->cached[IRQ_SET_TYPE] &= ~d->hwirq & 0xFF; + periph->cached[IRQ_POL_HIGH] &= ~d->hwirq & 0xFF; + periph->cached[IRQ_POL_LOW] |= d->hwirq & 0xFF; + break; + default: + pr_err("irq type 0x%04x is not supported\n", irq_type); + return -EINVAL; + } + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int i2c_pmic_irq_set_wake(struct irq_data *d, unsigned int on) +{ + struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d); + + if (on) + periph->wake |= d->hwirq & 0xFF; + else + periph->wake &= ~d->hwirq & 0xFF; + + return 0; +} +#else +#define i2c_pmic_irq_set_wake NULL +#endif + +static struct irq_chip i2c_pmic_irq_chip = { + .name = "i2c_pmic_irq_chip", + .irq_bus_lock = i2c_pmic_irq_bus_lock, + .irq_bus_sync_unlock = i2c_pmic_irq_bus_sync_unlock, + .irq_disable = i2c_pmic_irq_disable, + .irq_enable = i2c_pmic_irq_enable, + .irq_set_type = i2c_pmic_irq_set_type, + .irq_set_wake = i2c_pmic_irq_set_wake, +}; + +static struct i2c_pmic_periph *i2c_pmic_find_periph(struct i2c_pmic *chip, + irq_hw_number_t hwirq) +{ + int i; + + for (i = 0; i < chip->num_periphs; i++) + if (chip->periph[i].addr == (hwirq & 0xFF00)) + return &chip->periph[i]; + + pr_err_ratelimited("Couldn't find periph struct for hwirq 0x%04lx\n", + hwirq); + return NULL; +} + +static int i2c_pmic_domain_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hwirq) +{ + struct i2c_pmic *chip = d->host_data; + struct i2c_pmic_periph *periph = i2c_pmic_find_periph(chip, hwirq); + + if (!periph) + return -ENODEV; + + irq_set_chip_data(virq, periph); + irq_set_chip_and_handler(virq, &i2c_pmic_irq_chip, handle_level_irq); + irq_set_nested_thread(virq, 1); + irq_set_noprobe(virq); + return 0; +} + +static int i2c_pmic_domain_xlate(struct irq_domain *d, + struct device_node *ctrlr, const u32 *intspec, + unsigned int intsize, unsigned long *out_hwirq, + unsigned int *out_type) +{ + if (intsize != 3) + return -EINVAL; + + if (intspec[0] > 0xFF || intspec[1] > 0x7 || + intspec[2] > IRQ_TYPE_SENSE_MASK) + return -EINVAL; + + /* + * Interrupt specifiers are triplets + * <peripheral-address, irq-number, IRQ_TYPE_*> + * + * peripheral-address - The base address of the peripheral + * irq-number - The zero based bit position of the peripheral's + * interrupt registers corresponding to the irq + * where the LSB is 0 and the MSB is 7 + * IRQ_TYPE_* - Please refer to linux/irq.h + */ + *out_hwirq = intspec[0] << 8 | BIT(intspec[1]); + *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; + + return 0; +} + +static const struct irq_domain_ops i2c_pmic_domain_ops = { + .map = i2c_pmic_domain_map, + .xlate = i2c_pmic_domain_xlate, +}; + +static void i2c_pmic_irq_ack_now(struct i2c_pmic *chip, u16 hwirq) +{ + int rc; + + rc = regmap_write(chip->regmap, + (hwirq & 0xFF00) | INT_LATCHED_CLR_OFFSET, + hwirq & 0xFF); + if (rc < 0) + pr_err_ratelimited("Couldn't ack 0x%04x rc=%d\n", hwirq, rc); +} + +static void i2c_pmic_irq_disable_now(struct i2c_pmic *chip, u16 hwirq) +{ + struct i2c_pmic_periph *periph = i2c_pmic_find_periph(chip, hwirq); + int rc; + + if (!periph) + return; + + mutex_lock(&periph->lock); + periph->cached[IRQ_EN_SET] &= ~hwirq & 0xFF; + + rc = regmap_write(chip->regmap, + (hwirq & 0xFF00) | INT_EN_CLR_OFFSET, + hwirq & 0xFF); + if (rc < 0) { + pr_err_ratelimited("Couldn't disable irq 0x%04x rc=%d\n", + hwirq, rc); + goto unlock; + } + + periph->synced[IRQ_EN_SET] = periph->cached[IRQ_EN_SET]; + +unlock: + mutex_unlock(&periph->lock); +} + +static void i2c_pmic_periph_status_handler(struct i2c_pmic *chip, + u16 periph_address, u8 periph_status) +{ + unsigned int hwirq, virq; + int i; + + while (periph_status) { + i = ffs(periph_status) - 1; + periph_status &= ~BIT(i); + hwirq = periph_address | BIT(i); + virq = irq_find_mapping(chip->domain, hwirq); + if (virq == 0) { + pr_err_ratelimited("Couldn't find mapping; disabling 0x%04x\n", + hwirq); + i2c_pmic_irq_disable_now(chip, hwirq); + continue; + } + + handle_nested_irq(virq); + i2c_pmic_irq_ack_now(chip, hwirq); + } +} + +static void i2c_pmic_summary_status_handler(struct i2c_pmic *chip, + struct i2c_pmic_periph *periph, + u8 summary_status) +{ + unsigned int periph_status; + int rc, i; + + while (summary_status) { + i = ffs(summary_status) - 1; + summary_status &= ~BIT(i); + + rc = regmap_read(chip->regmap, + periph[i].addr | INT_LATCHED_STS_OFFSET, + &periph_status); + if (rc < 0) { + pr_err_ratelimited("Couldn't read 0x%04x | INT_LATCHED_STS rc=%d\n", + periph[i].addr, rc); + continue; + } + + i2c_pmic_periph_status_handler(chip, periph[i].addr, + periph_status); + } +} + +static irqreturn_t i2c_pmic_irq_handler(int irq, void *dev_id) +{ + struct i2c_pmic *chip = dev_id; + struct i2c_pmic_periph *periph; + unsigned int summary_status; + int rc, i; + + for (i = 0; i < DIV_ROUND_UP(chip->num_periphs, BITS_PER_BYTE); i++) { + rc = regmap_read(chip->regmap, I2C_INTR_STATUS_BASE + i, + &summary_status); + if (rc < 0) { + pr_err_ratelimited("Couldn't read I2C_INTR_STATUS%d rc=%d\n", + i, rc); + continue; + } + + if (summary_status == 0) + continue; + + periph = &chip->periph[i * 8]; + i2c_pmic_summary_status_handler(chip, periph, summary_status); + } + + return IRQ_HANDLED; +} + +static int i2c_pmic_parse_dt(struct i2c_pmic *chip) +{ + struct device_node *node = chip->dev->of_node; + int rc, i; + u32 temp; + + if (!node) { + pr_err("missing device tree\n"); + return -EINVAL; + } + + chip->num_periphs = of_property_count_u32_elems(node, + "qcom,periph-map"); + if (chip->num_periphs < 0) { + pr_err("missing qcom,periph-map property rc=%d\n", + chip->num_periphs); + return chip->num_periphs; + } + + if (chip->num_periphs == 0) { + pr_err("qcom,periph-map must contain at least one address\n"); + return -EINVAL; + } + + chip->periph = devm_kcalloc(chip->dev, chip->num_periphs, + sizeof(*chip->periph), GFP_KERNEL); + if (!chip->periph) + return -ENOMEM; + + for (i = 0; i < chip->num_periphs; i++) { + rc = of_property_read_u32_index(node, "qcom,periph-map", + i, &temp); + if (rc < 0) { + pr_err("Couldn't read qcom,periph-map[%d] rc=%d\n", + i, rc); + return rc; + } + + chip->periph[i].addr = (u16)(temp << 8); + chip->periph[i].data = chip; + mutex_init(&chip->periph[i].lock); + } + + of_property_read_string(node, "pinctrl-names", &chip->pinctrl_name); + + return rc; +} + +static int i2c_pmic_determine_initial_status(struct i2c_pmic *chip) +{ + int rc, i; + + for (i = 0; i < chip->num_periphs; i++) { + rc = regmap_bulk_read(chip->regmap, + chip->periph[i].addr | INT_SET_TYPE_OFFSET, + chip->periph[i].cached, IRQ_MAX_REGS); + if (rc < 0) { + pr_err("Couldn't read irq data rc=%d\n", rc); + return rc; + } + + memcpy(chip->periph[i].synced, chip->periph[i].cached, + IRQ_MAX_REGS * sizeof(*chip->periph[i].synced)); + } + + return 0; +} + +static struct regmap_config i2c_pmic_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xFFFF, +}; + +static int i2c_pmic_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc; + struct i2c_pmic *chip; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->regmap = devm_regmap_init_i2c(client, &i2c_pmic_regmap_config); + if (!chip->regmap) + return -ENODEV; + + chip->domain = irq_domain_add_tree(client->dev.of_node, + &i2c_pmic_domain_ops, chip); + if (!chip->domain) + return -ENOMEM; + + chip->client = client; + chip->dev = &client->dev; + i2c_set_clientdata(client, chip); + + rc = i2c_pmic_parse_dt(chip); + if (rc < 0) { + pr_err("Couldn't parse device tree rc=%d\n", rc); + goto cleanup; + } + + rc = i2c_pmic_determine_initial_status(chip); + if (rc < 0) { + pr_err("Couldn't determine initial status rc=%d\n", rc); + goto cleanup; + } + + if (chip->pinctrl_name) { + chip->pinctrl = devm_pinctrl_get_select(chip->dev, + chip->pinctrl_name); + if (IS_ERR(chip->pinctrl)) { + pr_err("Couldn't select %s pinctrl rc=%ld\n", + chip->pinctrl_name, PTR_ERR(chip->pinctrl)); + rc = PTR_ERR(chip->pinctrl); + goto cleanup; + } + } + + rc = devm_request_threaded_irq(&client->dev, client->irq, NULL, + i2c_pmic_irq_handler, IRQF_ONESHOT, + "i2c_pmic_stat_irq", chip); + if (rc < 0) { + pr_err("Couldn't request irq %d rc=%d\n", client->irq, rc); + goto cleanup; + } + + enable_irq_wake(client->irq); + of_platform_populate(chip->dev->of_node, NULL, NULL, chip->dev); + pr_info("I2C PMIC probe successful\n"); + return rc; + +cleanup: + if (chip->domain) + irq_domain_remove(chip->domain); + i2c_set_clientdata(client, NULL); + return rc; +} + +static int i2c_pmic_remove(struct i2c_client *client) +{ + struct i2c_pmic *chip = i2c_get_clientdata(client); + + of_platform_depopulate(chip->dev); + irq_domain_remove(chip->domain); + i2c_set_clientdata(client, NULL); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int i2c_pmic_suspend(struct device *dev) +{ + struct i2c_pmic *chip = dev_get_drvdata(dev); + struct i2c_pmic_periph *periph; + int rc = 0, i; + + for (i = 0; i < chip->num_periphs; i++) { + periph = &chip->periph[i]; + + rc = regmap_write(chip->regmap, + periph->addr | INT_EN_CLR_OFFSET, 0xFF); + if (rc < 0) { + pr_err_ratelimited("Couldn't clear 0x%04x irqs rc=%d\n", + periph->addr, rc); + continue; + } + + rc = regmap_write(chip->regmap, + periph->addr | INT_EN_SET_OFFSET, + periph->wake); + if (rc < 0) + pr_err_ratelimited("Couldn't enable 0x%04x wake irqs 0x%02x rc=%d\n", + periph->addr, periph->wake, rc); + } + + return rc; +} + +static int i2c_pmic_resume(struct device *dev) +{ + struct i2c_pmic *chip = dev_get_drvdata(dev); + struct i2c_pmic_periph *periph; + int rc = 0, i; + + for (i = 0; i < chip->num_periphs; i++) { + periph = &chip->periph[i]; + + rc = regmap_write(chip->regmap, + periph->addr | INT_EN_CLR_OFFSET, 0xFF); + if (rc < 0) { + pr_err("Couldn't clear 0x%04x irqs rc=%d\n", + periph->addr, rc); + continue; + } + + rc = regmap_write(chip->regmap, + periph->addr | INT_EN_SET_OFFSET, + periph->synced[IRQ_EN_SET]); + if (rc < 0) + pr_err("Couldn't restore 0x%04x synced irqs 0x%02x rc=%d\n", + periph->addr, periph->synced[IRQ_EN_SET], rc); + } + + return rc; +} +#endif +static SIMPLE_DEV_PM_OPS(i2c_pmic_pm_ops, i2c_pmic_suspend, i2c_pmic_resume); + +static const struct of_device_id i2c_pmic_match_table[] = { + { .compatible = "qcom,i2c-pmic", }, + { }, +}; + +static const struct i2c_device_id i2c_pmic_id[] = { + { "i2c-pmic", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, i2c_pmic_id); + +static struct i2c_driver i2c_pmic_driver = { + .driver = { + .name = "i2c_pmic", + .owner = THIS_MODULE, + .pm = &i2c_pmic_pm_ops, + .of_match_table = i2c_pmic_match_table, + }, + .probe = i2c_pmic_probe, + .remove = i2c_pmic_remove, + .id_table = i2c_pmic_id, +}; + +module_i2c_driver(i2c_pmic_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("i2c:i2c_pmic"); diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 71caa588a019..dafdbd99ce71 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3570,6 +3570,10 @@ static int mmc_blk_cmdq_issue_rq(struct mmc_queue *mq, struct request *req) mmc_get_card(card); +#ifdef CONFIG_MMC_BLOCK_DEFERRED_RESUME + if (mmc_bus_needs_resume(card->host)) + mmc_resume_bus(card->host); +#endif if (!card->host->cmdq_ctx.active_reqs && mmc_card_doing_bkops(card)) { ret = mmc_cmdq_halt(card->host, true); if (ret) diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 9128d6fb6169..8101b77c2acf 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -164,6 +164,8 @@ static int mmc_bus_suspend(struct device *dev) if (ret) return ret; + if (mmc_bus_needs_resume(host)) + return 0; ret = host->bus_ops->suspend(host); return ret; } @@ -174,11 +176,17 @@ static int mmc_bus_resume(struct device *dev) struct mmc_host *host = card->host; int ret; + if (mmc_bus_manual_resume(host)) { + host->bus_resume_flags |= MMC_BUSRESUME_NEEDS_RESUME; + goto skip_full_resume; + } + ret = host->bus_ops->resume(host); if (ret) pr_warn("%s: error %d during resume (card was removed?)\n", mmc_hostname(host), ret); +skip_full_resume: ret = pm_generic_resume(dev); return ret; } @@ -190,6 +198,9 @@ static int mmc_runtime_suspend(struct device *dev) struct mmc_card *card = mmc_dev_to_card(dev); struct mmc_host *host = card->host; + if (mmc_bus_needs_resume(host)) + return 0; + return host->bus_ops->runtime_suspend(host); } @@ -198,6 +209,9 @@ static int mmc_runtime_resume(struct device *dev) struct mmc_card *card = mmc_dev_to_card(dev); struct mmc_host *host = card->host; + if (mmc_bus_needs_resume(host)) + host->bus_resume_flags &= ~MMC_BUSRESUME_NEEDS_RESUME; + return host->bus_ops->runtime_resume(host); } diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index add448035213..b76bb7a74049 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -3048,6 +3048,44 @@ static inline void mmc_bus_put(struct mmc_host *host) spin_unlock_irqrestore(&host->lock, flags); } +int mmc_resume_bus(struct mmc_host *host) +{ + unsigned long flags; + int err = 0; + + if (!mmc_bus_needs_resume(host)) + return -EINVAL; + + pr_debug("%s: Starting deferred resume\n", mmc_hostname(host)); + spin_lock_irqsave(&host->lock, flags); + host->bus_resume_flags &= ~MMC_BUSRESUME_NEEDS_RESUME; + host->rescan_disable = 0; + spin_unlock_irqrestore(&host->lock, flags); + + mmc_bus_get(host); + if (host->bus_ops && !host->bus_dead && host->card) { + mmc_power_up(host, host->card->ocr); + BUG_ON(!host->bus_ops->resume); + host->bus_ops->resume(host); + if (mmc_card_cmdq(host->card)) { + err = mmc_cmdq_halt(host, false); + if (err) + pr_err("%s: %s: unhalt failed: %d\n", + mmc_hostname(host), __func__, err); + else + mmc_card_clr_suspended(host->card); + } + } + + if (host->bus_ops->detect && !host->bus_dead) + host->bus_ops->detect(host); + + mmc_bus_put(host); + pr_debug("%s: Deferred resume completed\n", mmc_hostname(host)); + return 0; +} +EXPORT_SYMBOL(mmc_resume_bus); + /* * Assign a mmc bus handler to a host. Only one bus handler may control a * host at any given time. @@ -4226,6 +4264,10 @@ int mmc_pm_notify(struct notifier_block *notify_block, case PM_SUSPEND_PREPARE: case PM_RESTORE_PREPARE: spin_lock_irqsave(&host->lock, flags); + if (mmc_bus_needs_resume(host)) { + spin_unlock_irqrestore(&host->lock, flags); + break; + } host->rescan_disable = 1; spin_unlock_irqrestore(&host->lock, flags); cancel_delayed_work_sync(&host->detect); @@ -4254,6 +4296,10 @@ int mmc_pm_notify(struct notifier_block *notify_block, spin_lock_irqsave(&host->lock, flags); host->rescan_disable = 0; + if (mmc_bus_manual_resume(host)) { + spin_unlock_irqrestore(&host->lock, flags); + break; + } spin_unlock_irqrestore(&host->lock, flags); _mmc_detect_change(host, 0, false); diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index b32b87f44f92..6bfd95823da7 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2821,6 +2821,25 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *mask) } if (host->cmd->error) { + /* + * If this command initiates a data phase and a response + * CRC error is signalled, the card can start transferring + * data - the card may have received the command without + * error. We must not terminate the mmc_request early. + * + * If the card did not receive the command or returned an + * error which prevented it sending data, the data phase + * will time out. + * + * Even in case of cmd INDEX OR ENDBIT error we + * handle it the same way. + */ + if (host->cmd->data && + (((intmask & (SDHCI_INT_CRC | SDHCI_INT_TIMEOUT)) == + SDHCI_INT_CRC) || (host->cmd->error == -EILSEQ))) { + host->cmd = NULL; + return; + } tasklet_schedule(&host->finish_tasklet); return; } diff --git a/drivers/platform/msm/ipa/ipa_v2/ipa_interrupts.c b/drivers/platform/msm/ipa/ipa_v2/ipa_interrupts.c index f30fd4c60171..17f577ab6c4c 100644 --- a/drivers/platform/msm/ipa/ipa_v2/ipa_interrupts.c +++ b/drivers/platform/msm/ipa/ipa_v2/ipa_interrupts.c @@ -151,31 +151,58 @@ fail_alloc_work: return res; } +static inline bool is_uc_irq(int irq_num) +{ + if (ipa_interrupt_to_cb[irq_num].interrupt >= IPA_UC_IRQ_0 && + ipa_interrupt_to_cb[irq_num].interrupt <= IPA_UC_IRQ_3) + return true; + else + return false; +} + static void ipa_process_interrupts(bool isr_context) { u32 reg; u32 bmsk; u32 i = 0; u32 en; + bool uc_irq; en = ipa_read_reg(ipa_ctx->mmio, IPA_IRQ_EN_EE_n_ADDR(ipa_ee)); reg = ipa_read_reg(ipa_ctx->mmio, IPA_IRQ_STTS_EE_n_ADDR(ipa_ee)); while (en & reg) { - /* Clear interrupt before processing to avoid - clearing unhandled interrupts */ - ipa_write_reg(ipa_ctx->mmio, - IPA_IRQ_CLR_EE_n_ADDR(ipa_ee), reg); - - /* Process the interrupts */ bmsk = 1; for (i = 0; i < IPA_IRQ_NUM_MAX; i++) { - if (en & reg & bmsk) - handle_interrupt(i, isr_context); + if (!(en & reg & bmsk)) { + bmsk = bmsk << 1; + continue; + } + uc_irq = is_uc_irq(i); + /* + * Clear uC interrupt before processing to avoid + * clearing unhandled interrupts + */ + if (uc_irq) + ipa_write_reg(ipa_ctx->mmio, + IPA_IRQ_CLR_EE_n_ADDR(ipa_ee), bmsk); + + /* Process the interrupts */ + handle_interrupt(i, isr_context); + + /* + * Clear non uC interrupt after processing + * to avoid clearing interrupt data + */ + if (!uc_irq) + ipa_write_reg(ipa_ctx->mmio, + IPA_IRQ_CLR_EE_n_ADDR(ipa_ee), bmsk); + bmsk = bmsk << 1; } - - /* Check pending interrupts that may have - been raised since last read */ + /* + * Check pending interrupts that may have + * been raised since last read + */ reg = ipa_read_reg(ipa_ctx->mmio, IPA_IRQ_STTS_EE_n_ADDR(ipa_ee)); } diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_interrupts.c b/drivers/platform/msm/ipa/ipa_v3/ipa_interrupts.c index 45d3b13049bc..f0102a703812 100644 --- a/drivers/platform/msm/ipa/ipa_v3/ipa_interrupts.c +++ b/drivers/platform/msm/ipa/ipa_v3/ipa_interrupts.c @@ -235,6 +235,15 @@ static void ipa3_tx_suspend_interrupt_wa(void) IPADBG_LOW("Exit\n"); } +static inline bool is_uc_irq(int irq_num) +{ + if (ipa_interrupt_to_cb[irq_num].interrupt >= IPA_UC_IRQ_0 && + ipa_interrupt_to_cb[irq_num].interrupt <= IPA_UC_IRQ_3) + return true; + else + return false; +} + static void ipa3_process_interrupts(bool isr_context) { u32 reg; @@ -242,6 +251,7 @@ static void ipa3_process_interrupts(bool isr_context) u32 i = 0; u32 en; unsigned long flags; + bool uc_irq; IPADBG_LOW("Enter\n"); @@ -252,6 +262,16 @@ static void ipa3_process_interrupts(bool isr_context) bmsk = 1; for (i = 0; i < IPA_IRQ_NUM_MAX; i++) { if (en & reg & bmsk) { + uc_irq = is_uc_irq(i); + + /* + * Clear uC interrupt before processing to avoid + * clearing unhandled interrupts + */ + if (uc_irq) + ipa3_uc_rg10_write_reg(IPA_IRQ_CLR_EE_n, + ipa_ee, bmsk); + /* * handle the interrupt with spin_lock * unlocked to avoid calling client in atomic @@ -262,6 +282,14 @@ static void ipa3_process_interrupts(bool isr_context) spin_unlock_irqrestore(&suspend_wa_lock, flags); ipa3_handle_interrupt(i, isr_context); spin_lock_irqsave(&suspend_wa_lock, flags); + + /* + * Clear non uC interrupt after processing + * to avoid clearing interrupt data + */ + if (!uc_irq) + ipa3_uc_rg10_write_reg(IPA_IRQ_CLR_EE_n, + ipa_ee, bmsk); } bmsk = bmsk << 1; } @@ -272,7 +300,6 @@ static void ipa3_process_interrupts(bool isr_context) if (ipa3_ctx->apply_rg10_wa && ipa3_ctx->uc_ctx.uc_failed) break; - ipa3_uc_rg10_write_reg(IPA_IRQ_CLR_EE_n, ipa_ee, reg); reg = ipahal_read_reg_n(IPA_IRQ_STTS_EE_n, ipa_ee); /* since the suspend interrupt HW bug we must * read again the EN register, otherwise the while is endless diff --git a/drivers/power/qcom-charger/Kconfig b/drivers/power/qcom-charger/Kconfig index e36ecc66a04e..4a299b66e588 100644 --- a/drivers/power/qcom-charger/Kconfig +++ b/drivers/power/qcom-charger/Kconfig @@ -77,4 +77,14 @@ config QPNP_SMB2 help Enables support for the SMB2 charging peripheral +config QPNP_QNOVO + bool "QPNP QNOVO driver" + depends on SPMI + select POWER_SUPPLY + help + Say Y here to enable the Qnovo pulse charging engine. Qnovo driver + accepts pulse parameters via sysfs entries and programs the hardware + module. It also allows userspace code to read diagnostics of voltage + and current measured during certain phases of the pulses. + endmenu diff --git a/drivers/power/qcom-charger/Makefile b/drivers/power/qcom-charger/Makefile index 21af050d96f1..ee2a335079e3 100644 --- a/drivers/power/qcom-charger/Makefile +++ b/drivers/power/qcom-charger/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_MSM_BCL_CTL) += msm_bcl.o obj-$(CONFIG_MSM_BCL_PERIPHERAL_CTL) += bcl_peripheral.o obj-$(CONFIG_BATTERY_BCL) += battery_current_limit.o obj-$(CONFIG_QPNP_SMB2) += qpnp-smb2.o smb-lib.o pmic-voter.o +obj-$(CONFIG_QPNP_QNOVO) += qpnp-qnovo.o diff --git a/drivers/power/qcom-charger/pmic-voter.c b/drivers/power/qcom-charger/pmic-voter.c index e1b6ced4ea58..d0bad7dec094 100644 --- a/drivers/power/qcom-charger/pmic-voter.c +++ b/drivers/power/qcom-charger/pmic-voter.c @@ -544,6 +544,9 @@ void destroy_votable(struct votable *votable) unsigned long flags; int i; + if (!votable) + return; + spin_lock_irqsave(&votable_list_slock, flags); list_del(&votable->list); spin_unlock_irqrestore(&votable_list_slock, flags); diff --git a/drivers/power/qcom-charger/qpnp-qnovo.c b/drivers/power/qcom-charger/qpnp-qnovo.c new file mode 100644 index 000000000000..d50188a5efbf --- /dev/null +++ b/drivers/power/qcom-charger/qpnp-qnovo.c @@ -0,0 +1,1427 @@ +/* Copyright (c) 2016 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. + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/power_supply.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/qpnp/qpnp-revid.h> +#include "pmic-voter.h" + +#define QNOVO_REVISION1 0x00 +#define QNOVO_REVISION2 0x01 +#define QNOVO_PERPH_TYPE 0x04 +#define QNOVO_PERPH_SUBTYPE 0x05 +#define QNOVO_PTTIME_STS 0x07 +#define QNOVO_PTRAIN_STS 0x08 +#define QNOVO_ERROR_STS 0x09 +#define QNOVO_ERROR_BIT BIT(0) +#define QNOVO_INT_RT_STS 0x10 +#define QNOVO_INT_SET_TYPE 0x11 +#define QNOVO_INT_POLARITY_HIGH 0x12 +#define QNOVO_INT_POLARITY_LOW 0x13 +#define QNOVO_INT_LATCHED_CLR 0x14 +#define QNOVO_INT_EN_SET 0x15 +#define QNOVO_INT_EN_CLR 0x16 +#define QNOVO_INT_LATCHED_STS 0x18 +#define QNOVO_INT_PENDING_STS 0x19 +#define QNOVO_INT_MID_SEL 0x1A +#define QNOVO_INT_PRIORITY 0x1B +#define QNOVO_PE_CTRL 0x40 +#define QNOVO_PREST1_CTRL 0x41 +#define QNOVO_PPULS1_LSB_CTRL 0x42 +#define QNOVO_PPULS1_MSB_CTRL 0x43 +#define QNOVO_NREST1_CTRL 0x44 +#define QNOVO_NPULS1_CTRL 0x45 +#define QNOVO_PPCNT_CTRL 0x46 +#define QNOVO_VLIM1_LSB_CTRL 0x47 +#define QNOVO_VLIM1_MSB_CTRL 0x48 +#define QNOVO_PTRAIN_EN 0x49 +#define QNOVO_PTRAIN_EN_BIT BIT(0) +#define QNOVO_PE_CTRL2 0x4A +#define QNOVO_PREST2_LSB_CTRL 0x50 +#define QNOVO_PREST2_MSB_CTRL 0x51 +#define QNOVO_PPULS2_LSB_CTRL 0x52 +#define QNOVO_PPULS2_MSB_CTRL 0x53 +#define QNOVO_NREST2_CTRL 0x54 +#define QNOVO_NPULS2_CTRL 0x55 +#define QNOVO_VLIM2_LSB_CTRL 0x56 +#define QNOVO_VLIM2_MSB_CTRL 0x57 +#define QNOVO_PVOLT1_LSB 0x60 +#define QNOVO_PVOLT1_MSB 0x61 +#define QNOVO_PCUR1_LSB 0x62 +#define QNOVO_PCUR1_MSB 0x63 +#define QNOVO_PVOLT2_LSB 0x70 +#define QNOVO_PVOLT2_MSB 0x71 +#define QNOVO_RVOLT2_LSB 0x72 +#define QNOVO_RVOLT2_MSB 0x73 +#define QNOVO_PCUR2_LSB 0x74 +#define QNOVO_PCUR2_MSB 0x75 +#define QNOVO_SCNT 0x80 +#define QNOVO_VMAX_LSB 0x90 +#define QNOVO_VMAX_MSB 0x91 +#define QNOVO_SNUM 0x92 + +/* Registers ending in 0 imply external rsense */ +#define QNOVO_IADC_OFFSET_0 0xA0 +#define QNOVO_IADC_OFFSET_1 0xA1 +#define QNOVO_IADC_GAIN_0 0xA2 +#define QNOVO_IADC_GAIN_1 0xA3 +#define QNOVO_VADC_OFFSET 0xA4 +#define QNOVO_VADC_GAIN 0xA5 +#define QNOVO_IADC_GAIN_2 0xA6 +#define QNOVO_SPARE 0xA7 +#define QNOVO_STRM_CTRL 0xA8 +#define QNOVO_IADC_OFFSET_OVR_VAL 0xA9 +#define QNOVO_IADC_OFFSET_OVR 0xAA +#define QNOVO_DISABLE_CHARGING 0xAB + +#define QNOVO_TR_IADC_OFFSET_0 0xF1 +#define QNOVO_TR_IADC_OFFSET_1 0xF2 + +#define DRV_MAJOR_VERSION 1 +#define DRV_MINOR_VERSION 0 + +#define IADC_LSB_NA 2441400 +#define VADC_LSB_NA 1220700 +#define GAIN_LSB_FACTOR 976560 + +#define MIN_EN_UA 1000000 + +#define USER_VOTER "user_voter" +#define OK_TO_QNOVO_VOTER "ok_to_qnovo_voter" + +#define QNOVO_VOTER "qnovo_voter" + +struct qnovo_dt_props { + bool external_rsense; + struct device_node *revid_dev_node; +}; + +enum { + QNOVO_ERASE_OFFSET_WA_BIT = BIT(0), + QNOVO_NO_ERR_STS_BIT = BIT(1), +}; + +struct chg_props { + bool charging; + bool usb_online; + int usb_input_uA; + bool dc_online; + int dc_input_uA; +}; + +struct chg_status { + bool ok_to_qnovo; +}; + +struct qnovo { + int base; + struct mutex write_lock; + struct regmap *regmap; + struct qnovo_dt_props dt; + struct device *dev; + struct votable *disable_votable; + struct class qnovo_class; + struct pmic_revid_data *pmic_rev_id; + u32 wa_flags; + s64 external_offset_nA; + s64 internal_offset_nA; + s64 offset_nV; + s64 external_i_gain_mega; + s64 internal_i_gain_mega; + s64 v_gain_mega; + struct notifier_block nb; + struct power_supply *batt_psy; + struct power_supply *usb_psy; + struct power_supply *dc_psy; + struct chg_props cp; + struct chg_status cs; + struct work_struct status_change_work; + int fv_uV_request; + int fcc_uA_request; + struct votable *fcc_votable; + struct votable *fv_votable; +}; + +static int debug_mask; +module_param_named(debug_mask, debug_mask, int, S_IRUSR | S_IWUSR); + +#define qnovo_dbg(chip, reason, fmt, ...) \ + do { \ + if (debug_mask & (reason)) \ + dev_info(chip->dev, fmt, ##__VA_ARGS__); \ + else \ + dev_dbg(chip->dev, fmt, ##__VA_ARGS__); \ + } while (0) + +static bool is_secure(struct qnovo *chip, int addr) +{ + /* assume everything above 0x40 is secure */ + return (bool)(addr >= 0x40); +} + +static int qnovo_read(struct qnovo *chip, u16 addr, u8 *buf, int len) +{ + return regmap_bulk_read(chip->regmap, chip->base + addr, buf, len); +} + +static int qnovo_masked_write(struct qnovo *chip, u16 addr, u8 mask, u8 val) +{ + int rc = 0; + + mutex_lock(&chip->write_lock); + if (is_secure(chip, addr)) { + rc = regmap_write(chip->regmap, + ((chip->base + addr) & ~(0xFF)) | 0xD0, 0xA5); + if (rc < 0) + goto unlock; + } + + rc = regmap_update_bits(chip->regmap, chip->base + addr, mask, val); + +unlock: + mutex_unlock(&chip->write_lock); + return rc; +} + +static int qnovo_write(struct qnovo *chip, u16 addr, u8 *buf, int len) +{ + int i, rc = 0; + bool is_start_secure, is_end_secure; + + is_start_secure = is_secure(chip, addr); + is_end_secure = is_secure(chip, addr + len); + + if (!is_start_secure && !is_end_secure) { + mutex_lock(&chip->write_lock); + rc = regmap_bulk_write(chip->regmap, chip->base + addr, + buf, len); + goto unlock; + } + + mutex_lock(&chip->write_lock); + for (i = addr; i < addr + len; i++) { + if (is_secure(chip, i)) { + rc = regmap_write(chip->regmap, + ((chip->base + i) & ~(0xFF)) | 0xD0, 0xA5); + if (rc < 0) + goto unlock; + } + rc = regmap_write(chip->regmap, chip->base + i, buf[i - addr]); + if (rc < 0) + goto unlock; + } + +unlock: + mutex_unlock(&chip->write_lock); + return rc; +} + +static int qnovo_disable_cb(struct votable *votable, void *data, int disable, + const char *client) +{ + struct qnovo *chip = data; + int rc = 0; + + if (disable) { + if (chip->fv_uV_request != -EINVAL) { + if (chip->fv_votable) + vote(chip->fv_votable, QNOVO_VOTER, false, 0); + } + if (chip->fcc_uA_request != -EINVAL) { + if (chip->fcc_votable) + vote(chip->fcc_votable, QNOVO_VOTER, false, 0); + } + } + + rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT, + disable ? 0 : QNOVO_PTRAIN_EN_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't %s pulse train rc=%d\n", + disable ? "disable" : "enable", rc); + return rc; + } + + if (!disable) { + if (chip->fv_uV_request != -EINVAL) { + if (!chip->fv_votable) + chip->fv_votable = find_votable("FV"); + if (chip->fv_votable) + vote(chip->fv_votable, QNOVO_VOTER, + true, chip->fv_uV_request); + } + if (chip->fcc_uA_request != -EINVAL) { + if (!chip->fcc_votable) + chip->fcc_votable = find_votable("FCC"); + if (chip->fcc_votable) + vote(chip->fcc_votable, QNOVO_VOTER, + true, chip->fcc_uA_request); + } + } + + return rc; +} + +static int qnovo_parse_dt(struct qnovo *chip) +{ + struct device_node *node = chip->dev->of_node; + int rc; + + if (!node) { + pr_err("device tree node missing\n"); + return -EINVAL; + } + + rc = of_property_read_u32(node, "reg", &chip->base); + if (rc < 0) { + pr_err("Couldn't read base rc = %d\n", rc); + return rc; + } + + chip->dt.external_rsense = of_property_read_bool(node, + "qcom,external-rsense"); + + chip->dt.revid_dev_node = of_parse_phandle(node, "qcom,pmic-revid", 0); + if (!chip->dt.revid_dev_node) { + pr_err("Missing qcom,pmic-revid property - driver failed\n"); + return -EINVAL; + } + + return 0; +} + +static int qnovo_check_chg_version(struct qnovo *chip) +{ + int rc; + + chip->pmic_rev_id = get_revid_data(chip->dt.revid_dev_node); + if (IS_ERR(chip->pmic_rev_id)) { + rc = PTR_ERR(chip->pmic_rev_id); + if (rc != -EPROBE_DEFER) + pr_err("Unable to get pmic_revid rc=%d\n", rc); + return rc; + } + + if ((chip->pmic_rev_id->pmic_subtype == PMICOBALT_SUBTYPE) + && (chip->pmic_rev_id->rev4 < PMICOBALT_V2P0_REV4)) { + chip->wa_flags |= QNOVO_ERASE_OFFSET_WA_BIT; + chip->wa_flags |= QNOVO_NO_ERR_STS_BIT; + } + + return 0; +} + +enum { + VER = 0, + OK_TO_QNOVO, + ENABLE, + FV_REQUEST, + FCC_REQUEST, + PE_CTRL_REG, + PE_CTRL2_REG, + PTRAIN_STS_REG, + INT_RT_STS_REG, + PREST1, + PPULS1, + NREST1, + NPULS1, + PPCNT, + VLIM1, + PVOLT1, + PCUR1, + PTTIME, + PREST2, + PPULS2, + NREST2, + NPULS2, + VLIM2, + PVOLT2, + RVOLT2, + PCUR2, + SCNT, + VMAX, + SNUM, + VBATT, + IBATT, + BATTTEMP, + BATTSOC, +}; + +struct param_info { + char *name; + int start_addr; + int num_regs; + int reg_to_unit_multiplier; + int reg_to_unit_divider; + int reg_to_unit_offset; + int min_val; + int max_val; + char *units_str; +}; + +static struct param_info params[] = { + [FV_REQUEST] = { + .units_str = "uV", + }, + [FCC_REQUEST] = { + .units_str = "uA", + }, + [PE_CTRL_REG] = { + .name = "CTRL_REG", + .start_addr = QNOVO_PE_CTRL, + .num_regs = 1, + .units_str = "", + }, + [PE_CTRL2_REG] = { + .name = "PE_CTRL2_REG", + .start_addr = QNOVO_PE_CTRL2, + .num_regs = 1, + .units_str = "", + }, + [PTRAIN_STS_REG] = { + .name = "PTRAIN_STS", + .start_addr = QNOVO_PTRAIN_STS, + .num_regs = 1, + .units_str = "", + }, + [INT_RT_STS_REG] = { + .name = "INT_RT_STS", + .start_addr = QNOVO_INT_RT_STS, + .num_regs = 1, + .units_str = "", + }, + [PREST1] = { + .name = "PREST1", + .start_addr = QNOVO_PREST1_CTRL, + .num_regs = 1, + .reg_to_unit_multiplier = 5, + .reg_to_unit_divider = 1, + .min_val = 5, + .max_val = 1275, + .units_str = "mS", + }, + [PPULS1] = { + .name = "PPULS1", + .start_addr = QNOVO_PPULS1_LSB_CTRL, + .num_regs = 2, + .reg_to_unit_multiplier = 1600, /* converts to uC */ + .reg_to_unit_divider = 1, + .min_val = 0, + .max_val = 104856000, + .units_str = "uC", + }, + [NREST1] = { + .name = "NREST1", + .start_addr = QNOVO_NREST1_CTRL, + .num_regs = 1, + .reg_to_unit_multiplier = 5, + .reg_to_unit_divider = 1, + .min_val = 5, + .max_val = 1275, + .units_str = "mS", + }, + [NPULS1] = { + .name = "NPULS1", + .start_addr = QNOVO_NPULS1_CTRL, + .num_regs = 1, + .reg_to_unit_multiplier = 5, + .reg_to_unit_divider = 1, + .min_val = 5, + .max_val = 1275, + .units_str = "mS", + }, + [PPCNT] = { + .name = "PPCNT", + .start_addr = QNOVO_PPCNT_CTRL, + .num_regs = 1, + .reg_to_unit_multiplier = 1, + .reg_to_unit_divider = 1, + .units_str = "pulses", + }, + [VLIM1] = { + .name = "VLIM1", + .start_addr = QNOVO_VLIM1_LSB_CTRL, + .num_regs = 2, + .reg_to_unit_multiplier = 610350, /* converts to nV */ + .reg_to_unit_divider = 1, + .min_val = 0, + .max_val = 5000000, + .units_str = "uV", + }, + [PVOLT1] = { + .name = "PVOLT1", + .start_addr = QNOVO_PVOLT1_LSB, + .num_regs = 2, + .reg_to_unit_multiplier = 610350, /* converts to nV */ + .reg_to_unit_divider = 1, + .units_str = "uV", + }, + [PCUR1] = { + .name = "PCUR1", + .start_addr = QNOVO_PCUR1_LSB, + .num_regs = 2, + .reg_to_unit_multiplier = 1220700, /* converts to nA */ + .reg_to_unit_divider = 1, + .units_str = "uA", + }, + [PTTIME] = { + .name = "PTTIME", + .start_addr = QNOVO_PTTIME_STS, + .num_regs = 1, + .reg_to_unit_multiplier = 2, + .reg_to_unit_divider = 1, + .min_val = 5, + .max_val = 1275, + .units_str = "S", + }, + [PREST2] = { + .name = "PREST2", + .start_addr = QNOVO_PREST2_LSB_CTRL, + .num_regs = 2, + .reg_to_unit_multiplier = 5, + .reg_to_unit_divider = 1, + .min_val = 5, + .max_val = 327675, + .units_str = "mS", + }, + [PPULS2] = { + .name = "PPULS2", + .start_addr = QNOVO_PPULS2_LSB_CTRL, + .num_regs = 2, + .reg_to_unit_multiplier = 1600, /* converts to uC */ + .reg_to_unit_divider = 1, + .min_val = 0, + .max_val = 104856000, + .units_str = "uC", + }, + [NREST2] = { + .name = "NREST2", + .start_addr = QNOVO_NREST2_CTRL, + .num_regs = 1, + .reg_to_unit_multiplier = 5, + .reg_to_unit_divider = 1, + .reg_to_unit_offset = -5, + .min_val = 5, + .max_val = 1280, + .units_str = "mS", + }, + [NPULS2] = { + .name = "NPULS2", + .start_addr = QNOVO_NPULS2_CTRL, + .num_regs = 1, + .reg_to_unit_multiplier = 5, + .reg_to_unit_divider = 1, + .min_val = 5, + .max_val = 1275, + .units_str = "mS", + }, + [VLIM2] = { + .name = "VLIM1", + .start_addr = QNOVO_VLIM2_LSB_CTRL, + .num_regs = 2, + .reg_to_unit_multiplier = 610350, /* converts to nV */ + .reg_to_unit_divider = 1, + .min_val = 0, + .max_val = 5000000, + .units_str = "uV", + }, + [PVOLT2] = { + .name = "PVOLT2", + .start_addr = QNOVO_PVOLT2_LSB, + .num_regs = 2, + .reg_to_unit_multiplier = 610350, /* converts to nV */ + .reg_to_unit_divider = 1, + .units_str = "uV", + }, + [RVOLT2] = { + .name = "RVOLT2", + .start_addr = QNOVO_RVOLT2_LSB, + .num_regs = 2, + .reg_to_unit_multiplier = 610350, + .reg_to_unit_divider = 1, + .units_str = "uV", + }, + [PCUR2] = { + .name = "PCUR2", + .start_addr = QNOVO_PCUR2_LSB, + .num_regs = 2, + .reg_to_unit_multiplier = 1220700, /* converts to nA */ + .reg_to_unit_divider = 1, + .units_str = "uA", + }, + [SCNT] = { + .name = "SCNT", + .start_addr = QNOVO_SCNT, + .num_regs = 1, + .reg_to_unit_multiplier = 1, + .reg_to_unit_divider = 1, + .units_str = "pulses", + }, + [VMAX] = { + .name = "VMAX", + .start_addr = QNOVO_VMAX_LSB, + .num_regs = 2, + .reg_to_unit_multiplier = 814000, /* converts to nV */ + .reg_to_unit_divider = 1, + .units_str = "uV", + }, + [SNUM] = { + .name = "SNUM", + .start_addr = QNOVO_SNUM, + .num_regs = 1, + .reg_to_unit_multiplier = 1, + .reg_to_unit_divider = 1, + .units_str = "pulses", + }, + [VBATT] = { + .name = "POWER_SUPPLY_PROP_VOLTAGE_NOW", + .start_addr = POWER_SUPPLY_PROP_VOLTAGE_NOW, + .units_str = "uV", + }, + [IBATT] = { + .name = "POWER_SUPPLY_PROP_CURRENT_NOW", + .start_addr = POWER_SUPPLY_PROP_CURRENT_NOW, + .units_str = "uA", + }, + [BATTTEMP] = { + .name = "POWER_SUPPLY_PROP_TEMP", + .start_addr = POWER_SUPPLY_PROP_TEMP, + .units_str = "uV", + }, + [BATTSOC] = { + .name = "POWER_SUPPLY_PROP_CAPACITY", + .start_addr = POWER_SUPPLY_PROP_CAPACITY, + .units_str = "%", + }, +}; + +static struct class_attribute qnovo_attributes[]; + +static ssize_t version_show(struct class *c, struct class_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d.%d\n", + DRV_MAJOR_VERSION, DRV_MINOR_VERSION); +} + +static ssize_t ok_to_qnovo_show(struct class *c, struct class_attribute *attr, + char *buf) +{ + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + + return snprintf(buf, PAGE_SIZE, "%d\n", chip->cs.ok_to_qnovo); +} + +static ssize_t enable_show(struct class *c, struct class_attribute *attr, + char *ubuf) +{ + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + int val; + + val = get_client_vote(chip->disable_votable, USER_VOTER); + return snprintf(ubuf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t enable_store(struct class *c, struct class_attribute *attr, + const char *ubuf, size_t count) +{ + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + unsigned long val; + bool disable; + + if (kstrtoul(ubuf, 10, &val)) + return -EINVAL; + + disable = !val; + + vote(chip->disable_votable, USER_VOTER, disable, 0); + return count; +} + +static ssize_t val_show(struct class *c, struct class_attribute *attr, + char *ubuf) +{ + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + int i = attr - qnovo_attributes; + int val = 0; + + if (i == FV_REQUEST) + val = chip->fv_uV_request; + + if (i == FCC_REQUEST) + val = chip->fcc_uA_request; + + return snprintf(ubuf, PAGE_SIZE, "%d%s\n", val, params[i].units_str); +} + +static ssize_t val_store(struct class *c, struct class_attribute *attr, + const char *ubuf, size_t count) +{ + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + int i = attr - qnovo_attributes; + unsigned long val; + + if (kstrtoul(ubuf, 10, &val)) + return -EINVAL; + + if (i == FV_REQUEST) + chip->fv_uV_request = val; + + if (i == FCC_REQUEST) + chip->fcc_uA_request = val; + + return count; +} + +static ssize_t reg_show(struct class *c, struct class_attribute *attr, + char *ubuf) +{ + int i = attr - qnovo_attributes; + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + u8 buf[2] = {0, 0}; + u16 regval; + int rc; + + rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs); + if (rc < 0) { + pr_err("Couldn't read %s rc = %d\n", params[i].name, rc); + return -EINVAL; + } + regval = buf[1] << 8 | buf[0]; + + return snprintf(ubuf, PAGE_SIZE, "0x%04x%s\n", + regval, params[i].units_str); +} + +static ssize_t reg_store(struct class *c, struct class_attribute *attr, + const char *ubuf, size_t count) +{ + int i = attr - qnovo_attributes; + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + u8 buf[2] = {0, 0}; + unsigned long val; + int rc; + + if (kstrtoul(ubuf, 16, &val)) + return -EINVAL; + + buf[0] = val & 0xFF; + buf[1] = (val >> 8) & 0xFF; + + rc = qnovo_write(chip, params[i].start_addr, buf, params[i].num_regs); + if (rc < 0) { + pr_err("Couldn't write %s rc = %d\n", params[i].name, rc); + return -EINVAL; + } + return count; +} + +static ssize_t time_show(struct class *c, struct class_attribute *attr, + char *ubuf) +{ + int i = attr - qnovo_attributes; + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + u8 buf[2] = {0, 0}; + u16 regval; + int val; + int rc; + + rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs); + if (rc < 0) { + pr_err("Couldn't read %s rc = %d\n", params[i].name, rc); + return -EINVAL; + } + regval = buf[1] << 8 | buf[0]; + + val = ((regval * params[i].reg_to_unit_multiplier) + / params[i].reg_to_unit_divider) + - params[i].reg_to_unit_offset; + + return snprintf(ubuf, PAGE_SIZE, "%d%s\n", val, params[i].units_str); +} + +static ssize_t time_store(struct class *c, struct class_attribute *attr, + const char *ubuf, size_t count) +{ + int i = attr - qnovo_attributes; + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + u8 buf[2] = {0, 0}; + u16 regval; + unsigned long val; + int rc; + + if (kstrtoul(ubuf, 10, &val)) + return -EINVAL; + + if (val < params[i].min_val || val > params[i].max_val) { + pr_err("Out of Range %d%s for %s\n", (int)val, + params[i].units_str, + params[i].name); + return -ERANGE; + } + + regval = (((int)val + params[i].reg_to_unit_offset) + * params[i].reg_to_unit_divider) + / params[i].reg_to_unit_multiplier; + buf[0] = regval & 0xFF; + buf[1] = (regval >> 8) & 0xFF; + + rc = qnovo_write(chip, params[i].start_addr, buf, params[i].num_regs); + if (rc < 0) { + pr_err("Couldn't write %s rc = %d\n", params[i].name, rc); + return -EINVAL; + } + + return count; +} + +static ssize_t current_show(struct class *c, struct class_attribute *attr, + char *ubuf) +{ + int i = attr - qnovo_attributes; + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + u8 buf[2] = {0, 0}; + int rc; + int comp_val_uA; + s64 regval_nA; + s64 gain, offset_nA, comp_val_nA; + + rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs); + if (rc < 0) { + pr_err("Couldn't read %s rc = %d\n", params[i].name, rc); + return -EINVAL; + } + regval_nA = buf[1] << 8 | buf[0]; + regval_nA = div_s64(regval_nA * params[i].reg_to_unit_multiplier, + params[i].reg_to_unit_divider) + - params[i].reg_to_unit_offset; + + if (chip->dt.external_rsense) { + offset_nA = chip->external_offset_nA; + gain = chip->external_i_gain_mega; + } else { + offset_nA = chip->internal_offset_nA; + gain = chip->internal_i_gain_mega; + } + + comp_val_nA = div_s64(regval_nA * gain, 1000000) + offset_nA; + comp_val_uA = comp_val_nA / 1000; + + return snprintf(ubuf, PAGE_SIZE, "%d%s\n", + comp_val_uA, params[i].units_str); +} + +static ssize_t voltage_show(struct class *c, struct class_attribute *attr, + char *ubuf) +{ + int i = attr - qnovo_attributes; + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + u8 buf[2] = {0, 0}; + int rc; + int comp_val_uV; + s64 regval_nV; + s64 gain, offset_nV, comp_val_nV; + + rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs); + if (rc < 0) { + pr_err("Couldn't read %s rc = %d\n", params[i].name, rc); + return -EINVAL; + } + regval_nV = buf[1] << 8 | buf[0]; + regval_nV = div_s64(regval_nV * params[i].reg_to_unit_multiplier, + params[i].reg_to_unit_divider) + - params[i].reg_to_unit_offset; + + offset_nV = chip->offset_nV; + gain = chip->v_gain_mega; + + comp_val_nV = div_s64(regval_nV * gain, 1000000) + offset_nV; + comp_val_uV = comp_val_nV / 1000; + + return snprintf(ubuf, PAGE_SIZE, "%d%s\n", + comp_val_uV, params[i].units_str); +} + +static ssize_t voltage_store(struct class *c, struct class_attribute *attr, + const char *ubuf, size_t count) +{ + int i = attr - qnovo_attributes; + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + u8 buf[2] = {0, 0}; + int rc; + unsigned long val_uV; + s64 regval_nV; + s64 gain, offset_nV; + + if (kstrtoul(ubuf, 10, &val_uV)) + return -EINVAL; + + if (val_uV < params[i].min_val || val_uV > params[i].max_val) { + pr_err("Out of Range %d%s for %s\n", (int)val_uV, + params[i].units_str, + params[i].name); + return -ERANGE; + } + + offset_nV = chip->offset_nV; + gain = chip->v_gain_mega; + + regval_nV = (s64)val_uV * 1000 - offset_nV; + regval_nV = div_s64(regval_nV * 1000000, gain); + + regval_nV = div_s64((regval_nV + params[i].reg_to_unit_offset) + * params[i].reg_to_unit_divider, + params[i].reg_to_unit_multiplier); + buf[0] = regval_nV & 0xFF; + buf[1] = ((u64)regval_nV >> 8) & 0xFF; + + rc = qnovo_write(chip, params[i].start_addr, buf, params[i].num_regs); + if (rc < 0) { + pr_err("Couldn't write %s rc = %d\n", params[i].name, rc); + return -EINVAL; + } + + return count; +} + +static ssize_t coulomb_show(struct class *c, struct class_attribute *attr, + char *ubuf) +{ + int i = attr - qnovo_attributes; + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + u8 buf[2] = {0, 0}; + int rc; + int comp_val_uC; + s64 regval_uC, gain; + + rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs); + if (rc < 0) { + pr_err("Couldn't read %s rc = %d\n", params[i].name, rc); + return -EINVAL; + } + regval_uC = buf[1] << 8 | buf[0]; + regval_uC = div_s64(regval_uC * params[i].reg_to_unit_multiplier, + params[i].reg_to_unit_divider) + - params[i].reg_to_unit_offset; + + if (chip->dt.external_rsense) + gain = chip->external_i_gain_mega; + else + gain = chip->internal_i_gain_mega; + + comp_val_uC = div_s64(regval_uC * gain, 1000000); + return snprintf(ubuf, PAGE_SIZE, "%d%s\n", + comp_val_uC, params[i].units_str); +} + +static ssize_t coulomb_store(struct class *c, struct class_attribute *attr, + const char *ubuf, size_t count) +{ + int i = attr - qnovo_attributes; + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + u8 buf[2] = {0, 0}; + int rc; + unsigned long val_uC; + s64 regval; + s64 gain; + + if (kstrtoul(ubuf, 10, &val_uC)) + return -EINVAL; + + if (val_uC < params[i].min_val || val_uC > params[i].max_val) { + pr_err("Out of Range %d%s for %s\n", (int)val_uC, + params[i].units_str, + params[i].name); + return -ERANGE; + } + + if (chip->dt.external_rsense) + gain = chip->external_i_gain_mega; + else + gain = chip->internal_i_gain_mega; + + regval = div_s64((s64)val_uC * 1000000, gain); + + regval = div_s64((regval + params[i].reg_to_unit_offset) + * params[i].reg_to_unit_divider, + params[i].reg_to_unit_multiplier); + + buf[0] = regval & 0xFF; + buf[1] = ((u64)regval >> 8) & 0xFF; + + rc = qnovo_write(chip, params[i].start_addr, buf, params[i].num_regs); + if (rc < 0) { + pr_err("Couldn't write %s rc = %d\n", params[i].name, rc); + return -EINVAL; + } + + return count; +} + +static ssize_t batt_prop_show(struct class *c, struct class_attribute *attr, + char *ubuf) +{ + int i = attr - qnovo_attributes; + struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); + int rc = -EINVAL; + int prop = params[i].start_addr; + union power_supply_propval pval = {0}; + + if (!chip->batt_psy) + chip->batt_psy = power_supply_get_by_name("battery"); + + if (!chip->batt_psy) + return -EINVAL; + + rc = power_supply_get_property(chip->batt_psy, prop, &pval); + if (rc < 0) { + pr_err("Couldn't read battery prop %s rc = %d\n", + params[i].name, rc); + return -EINVAL; + } + + return snprintf(ubuf, PAGE_SIZE, "%d%s\n", + pval.intval, params[i].units_str); +} + +static struct class_attribute qnovo_attributes[] = { + [VER] = __ATTR_RO(version), + [OK_TO_QNOVO] = __ATTR_RO(ok_to_qnovo), + [ENABLE] = __ATTR(enable, S_IRUGO | S_IWUSR, + enable_show, enable_store), + [FV_REQUEST] = __ATTR(fv_uV_request, S_IRUGO | S_IWUSR, + val_show, val_store), + [FCC_REQUEST] = __ATTR(fcc_uA_request, S_IRUGO | S_IWUSR, + val_show, val_store), + [PE_CTRL_REG] = __ATTR(PE_CTRL_REG, S_IRUGO | S_IWUSR, + reg_show, reg_store), + [PE_CTRL2_REG] = __ATTR(PE_CTRL2_REG, S_IRUGO | S_IWUSR, + reg_show, reg_store), + [PTRAIN_STS_REG] = __ATTR(PTRAIN_STS_REG, S_IRUGO | S_IWUSR, + reg_show, reg_store), + [INT_RT_STS_REG] = __ATTR(INT_RT_STS_REG, S_IRUGO | S_IWUSR, + reg_show, reg_store), + [PREST1] = __ATTR(PREST1_mS, S_IRUGO | S_IWUSR, + time_show, time_store), + [PPULS1] = __ATTR(PPULS1_uC, S_IRUGO | S_IWUSR, + coulomb_show, coulomb_store), + [NREST1] = __ATTR(NREST1_mS, S_IRUGO | S_IWUSR, + time_show, time_store), + [NPULS1] = __ATTR(NPULS1_mS, S_IRUGO | S_IWUSR, + time_show, time_store), + [PPCNT] = __ATTR(PPCNT, S_IRUGO | S_IWUSR, + time_show, time_store), + [VLIM1] = __ATTR(VLIM1_uV, S_IRUGO | S_IWUSR, + voltage_show, voltage_store), + [PVOLT1] = __ATTR(PVOLT1_uV, S_IRUGO, + voltage_show, NULL), + [PCUR1] = __ATTR(PCUR1_uA, S_IRUGO, + current_show, NULL), + [PTTIME] = __ATTR(PTTIME_S, S_IRUGO, + time_show, NULL), + [PREST2] = __ATTR(PREST2_mS, S_IRUGO | S_IWUSR, + time_show, time_store), + [PPULS2] = __ATTR(PPULS2_mS, S_IRUGO | S_IWUSR, + coulomb_show, coulomb_store), + [NREST2] = __ATTR(NREST2_mS, S_IRUGO | S_IWUSR, + time_show, time_store), + [NPULS2] = __ATTR(NPULS2_mS, S_IRUGO | S_IWUSR, + time_show, time_store), + [VLIM2] = __ATTR(VLIM2_uV, S_IRUGO | S_IWUSR, + voltage_show, voltage_store), + [PVOLT2] = __ATTR(PVOLT2_uV, S_IRUGO, + voltage_show, NULL), + [RVOLT2] = __ATTR(RVOLT2_uV, S_IRUGO, + voltage_show, NULL), + [PCUR2] = __ATTR(PCUR2_uA, S_IRUGO, + current_show, NULL), + [SCNT] = __ATTR(SCNT, S_IRUGO | S_IWUSR, + time_show, time_store), + [VMAX] = __ATTR(VMAX_uV, S_IRUGO, + voltage_show, NULL), + [SNUM] = __ATTR(SNUM, S_IRUGO | S_IWUSR, + time_show, time_store), + [VBATT] = __ATTR(VBATT_uV, S_IRUGO, + batt_prop_show, NULL), + [IBATT] = __ATTR(IBATT_uA, S_IRUGO, + batt_prop_show, NULL), + [BATTTEMP] = __ATTR(BATTTEMP_deciDegC, S_IRUGO, + batt_prop_show, NULL), + [BATTSOC] = __ATTR(BATTSOC, S_IRUGO, + batt_prop_show, NULL), + __ATTR_NULL, +}; + +static void get_chg_props(struct qnovo *chip, struct chg_props *cp) +{ + union power_supply_propval pval; + u8 val = 0; + int rc; + + cp->charging = true; + rc = qnovo_read(chip, QNOVO_ERROR_STS, &val, 1); + if (rc < 0) { + pr_err("Couldn't read error sts rc = %d\n", rc); + cp->charging = false; + } else { + cp->charging = (!(val & QNOVO_ERROR_BIT)); + } + + if (chip->wa_flags & QNOVO_NO_ERR_STS_BIT) { + /* + * on v1.0 and v1.1 pmic's force charging to true + * if things are not good to charge s/w gets a PTRAIN_DONE + * interrupt + */ + cp->charging = true; + } + + cp->usb_online = false; + cp->usb_input_uA = 0; + if (!chip->usb_psy) + chip->usb_psy = power_supply_get_by_name("usb"); + if (chip->usb_psy) { + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_ONLINE, &pval); + if (rc < 0) + pr_err("Couldn't read usb online rc = %d\n", rc); + else + cp->usb_online = (bool)pval.intval; + + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, &pval); + if (rc < 0) + pr_err("Couldn't read usb current max rc = %d\n", rc); + else + cp->usb_input_uA = pval.intval; + } + + cp->dc_online = false; + cp->dc_input_uA = 0; + if (!chip->dc_psy) + chip->dc_psy = power_supply_get_by_name("dc"); + if (chip->dc_psy) { + rc = power_supply_get_property(chip->dc_psy, + POWER_SUPPLY_PROP_ONLINE, &pval); + if (rc < 0) + pr_err("Couldn't read dc online rc = %d\n", rc); + else + cp->dc_online = (bool)pval.intval; + + rc = power_supply_get_property(chip->dc_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, &pval); + if (rc < 0) + pr_err("Couldn't read dc current max rc = %d\n", rc); + else + cp->dc_input_uA = pval.intval; + } +} + +static void get_chg_status(struct qnovo *chip, const struct chg_props *cp, + struct chg_status *cs) +{ + cs->ok_to_qnovo = false; + + if (cp->charging && + ((cp->usb_online && cp->usb_input_uA >= MIN_EN_UA) + || (cp->dc_online && cp->dc_input_uA >= MIN_EN_UA))) + cs->ok_to_qnovo = true; +} + +static void status_change_work(struct work_struct *work) +{ + struct qnovo *chip = container_of(work, + struct qnovo, status_change_work); + bool notify_uevent = false; + struct chg_props cp; + struct chg_status cs; + + get_chg_props(chip, &cp); + get_chg_status(chip, &cp, &cs); + + if (cs.ok_to_qnovo ^ chip->cs.ok_to_qnovo) { + /* + * when it is not okay to Qnovo charge, disable both voters, + * so that when it becomes okay to Qnovo charge the user voter + * has to specifically enable its vote to being Qnovo charging + */ + if (!cs.ok_to_qnovo) { + vote(chip->disable_votable, OK_TO_QNOVO_VOTER, 1, 0); + vote(chip->disable_votable, USER_VOTER, 1, 0); + } else { + vote(chip->disable_votable, OK_TO_QNOVO_VOTER, 0, 0); + } + notify_uevent = true; + } + + memcpy(&chip->cp, &cp, sizeof(struct chg_props)); + memcpy(&chip->cs, &cs, sizeof(struct chg_status)); + + if (notify_uevent) + kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE); +} + +static int qnovo_notifier_call(struct notifier_block *nb, + unsigned long ev, void *v) +{ + struct power_supply *psy = v; + struct qnovo *chip = container_of(nb, struct qnovo, nb); + + if (ev != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + if ((strcmp(psy->desc->name, "battery") == 0) + || (strcmp(psy->desc->name, "usb") == 0)) + schedule_work(&chip->status_change_work); + + return NOTIFY_OK; +} + +static irqreturn_t handle_ptrain_done(int irq, void *data) +{ + struct qnovo *chip = data; + + /* disable user voter here */ + vote(chip->disable_votable, USER_VOTER, 0, 0); + kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE); + return IRQ_HANDLED; +} + +static int qnovo_hw_init(struct qnovo *chip) +{ + int rc; + u8 iadc_offset_external, iadc_offset_internal; + u8 iadc_gain_external, iadc_gain_internal; + u8 vadc_offset, vadc_gain; + u8 buf[2] = {0, 0}; + + vote(chip->disable_votable, USER_VOTER, 1, 0); + + rc = qnovo_read(chip, QNOVO_IADC_OFFSET_0, &iadc_offset_external, 1); + if (rc < 0) { + pr_err("Couldn't read iadc exernal offset rc = %d\n", rc); + return rc; + } + + rc = qnovo_read(chip, QNOVO_IADC_OFFSET_1, &iadc_offset_internal, 1); + if (rc < 0) { + pr_err("Couldn't read iadc internal offset rc = %d\n", rc); + return rc; + } + + rc = qnovo_read(chip, QNOVO_IADC_GAIN_0, &iadc_gain_external, 1); + if (rc < 0) { + pr_err("Couldn't read iadc external gain rc = %d\n", rc); + return rc; + } + + rc = qnovo_read(chip, QNOVO_IADC_GAIN_1, &iadc_gain_internal, 1); + if (rc < 0) { + pr_err("Couldn't read iadc internal gain rc = %d\n", rc); + return rc; + } + + rc = qnovo_read(chip, QNOVO_VADC_OFFSET, &vadc_offset, 1); + if (rc < 0) { + pr_err("Couldn't read vadc offset rc = %d\n", rc); + return rc; + } + + rc = qnovo_read(chip, QNOVO_VADC_GAIN, &vadc_gain, 1); + if (rc < 0) { + pr_err("Couldn't read vadc external gain rc = %d\n", rc); + return rc; + } + + chip->external_offset_nA = (s64)iadc_offset_external * IADC_LSB_NA; + chip->internal_offset_nA = (s64)iadc_offset_internal * IADC_LSB_NA; + chip->offset_nV = (s64)vadc_offset * VADC_LSB_NA; + chip->external_i_gain_mega + = 1000000000 + (s64)iadc_gain_external * GAIN_LSB_FACTOR; + chip->external_i_gain_mega + = div_s64(chip->external_i_gain_mega, 1000); + chip->internal_i_gain_mega + = 1000000000 + (s64)iadc_gain_internal * GAIN_LSB_FACTOR; + chip->internal_i_gain_mega + = div_s64(chip->internal_i_gain_mega, 1000); + chip->v_gain_mega = 1000000000 + (s64)vadc_gain * GAIN_LSB_FACTOR; + chip->v_gain_mega = div_s64(chip->v_gain_mega, 1000); + + if (chip->wa_flags & QNOVO_ERASE_OFFSET_WA_BIT) { + rc = qnovo_write(chip, QNOVO_TR_IADC_OFFSET_0, buf, 2); + if (rc < 0) { + pr_err("Couldn't erase offset rc = %d\n", rc); + return rc; + } + } + return 0; +} + +static int qnovo_register_notifier(struct qnovo *chip) +{ + int rc; + + chip->nb.notifier_call = qnovo_notifier_call; + rc = power_supply_reg_notifier(&chip->nb); + if (rc < 0) { + pr_err("Couldn't register psy notifier rc = %d\n", rc); + return rc; + } + + return 0; +} + +static int qnovo_determine_initial_status(struct qnovo *chip) +{ + status_change_work(&chip->status_change_work); + return 0; +} + +static int qnovo_request_interrupts(struct qnovo *chip) +{ + int rc = 0; + int irq_ptrain_done = of_irq_get_byname(chip->dev->of_node, + "ptrain-done"); + + rc = devm_request_threaded_irq(chip->dev, irq_ptrain_done, NULL, + handle_ptrain_done, + IRQF_ONESHOT, "ptrain-done", chip); + if (rc < 0) { + pr_err("Couldn't request irq %d rc = %d\n", + irq_ptrain_done, rc); + return rc; + } + return rc; +} + +static int qnovo_probe(struct platform_device *pdev) +{ + struct qnovo *chip; + int rc = 0; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->fv_uV_request = -EINVAL; + chip->fcc_uA_request = -EINVAL; + chip->dev = &pdev->dev; + mutex_init(&chip->write_lock); + + chip->regmap = dev_get_regmap(chip->dev->parent, NULL); + if (!chip->regmap) { + pr_err("parent regmap is missing\n"); + return -EINVAL; + } + + rc = qnovo_parse_dt(chip); + if (rc < 0) { + pr_err("Couldn't parse device tree rc=%d\n", rc); + return rc; + } + + rc = qnovo_check_chg_version(chip); + if (rc < 0) { + if (rc != -EPROBE_DEFER) + pr_err("Couldn't check version rc=%d\n", rc); + return rc; + } + + /* set driver data before resources request it */ + platform_set_drvdata(pdev, chip); + + chip->disable_votable = create_votable("QNOVO_DISABLE", VOTE_SET_ANY, + qnovo_disable_cb, chip); + if (IS_ERR(chip->disable_votable)) { + rc = PTR_ERR(chip->disable_votable); + goto cleanup; + } + + INIT_WORK(&chip->status_change_work, status_change_work); + + rc = qnovo_hw_init(chip); + if (rc < 0) { + pr_err("Couldn't initialize hardware rc=%d\n", rc); + goto destroy_votable; + } + + rc = qnovo_register_notifier(chip); + if (rc < 0) { + pr_err("Couldn't register psy notifier rc = %d\n", rc); + goto unreg_notifier; + } + + rc = qnovo_determine_initial_status(chip); + if (rc < 0) { + pr_err("Couldn't determine initial status rc=%d\n", rc); + goto unreg_notifier; + } + + rc = qnovo_request_interrupts(chip); + if (rc < 0) { + pr_err("Couldn't request interrupts rc=%d\n", rc); + goto unreg_notifier; + } + chip->qnovo_class.name = "qnovo", + chip->qnovo_class.owner = THIS_MODULE, + chip->qnovo_class.class_attrs = qnovo_attributes; + + rc = class_register(&chip->qnovo_class); + if (rc < 0) { + pr_err("couldn't register qnovo sysfs class rc = %d\n", rc); + goto unreg_notifier; + } + + return rc; + +unreg_notifier: + power_supply_unreg_notifier(&chip->nb); +destroy_votable: + destroy_votable(chip->disable_votable); +cleanup: + platform_set_drvdata(pdev, NULL); + return rc; +} + +static int qnovo_remove(struct platform_device *pdev) +{ + struct qnovo *chip = platform_get_drvdata(pdev); + + class_unregister(&chip->qnovo_class); + power_supply_unreg_notifier(&chip->nb); + destroy_votable(chip->disable_votable); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct of_device_id match_table[] = { + { .compatible = "qcom,qpnp-qnovo", }, + { }, +}; + +static struct platform_driver qnovo_driver = { + .driver = { + .name = "qcom,qnovo-driver", + .owner = THIS_MODULE, + .of_match_table = match_table, + }, + .probe = qnovo_probe, + .remove = qnovo_remove, +}; +module_platform_driver(qnovo_driver); + +MODULE_DESCRIPTION("QPNP Qnovo Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c index c22d70a6669e..bbd56caaf0a0 100644 --- a/drivers/power/qcom-charger/qpnp-smb2.c +++ b/drivers/power/qcom-charger/qpnp-smb2.c @@ -555,6 +555,13 @@ static int smb2_init_hw(struct smb2 *chip) return rc; } + rc = smblib_masked_write(chg, QNOVO_PT_ENABLE_CMD_REG, + QNOVO_PT_ENABLE_CMD_BIT, QNOVO_PT_ENABLE_CMD_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable qnovo rc=%d\n", rc); + return rc; + } + return rc; } diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index aea697417bda..c53659c07fc2 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -86,6 +86,41 @@ unlock: * REGISTER GETTERS * ********************/ +int smblib_get_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int *val_u) +{ + int rc = 0; + u8 val_raw; + + rc = smblib_read(chg, param->reg, &val_raw); + if (rc < 0) { + dev_err(chg->dev, "%s: Couldn't read from 0x%04x rc=%d\n", + param->name, param->reg, rc); + return rc; + } + + *val_u = val_raw * param->step_u + param->min_u; + smblib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n", + param->name, *val_u, val_raw); + + return rc; +} + +int smblib_get_usb_suspend(struct smb_charger *chg, int *suspend) +{ + int rc = 0; + u8 temp; + + rc = smblib_read(chg, USBIN_CMD_IL_REG, &temp); + if (rc < 0) { + dev_err(chg->dev, "Couldn't read USBIN_CMD_IL rc=%d\n", rc); + return rc; + } + *suspend = temp & USBIN_SUSPEND_BIT; + + return rc; +} + struct apsd_result { const char * const name; const u8 bit; @@ -156,8 +191,8 @@ int smblib_enable_charging(struct smb_charger *chg, bool enable) return rc; } -static int smblib_set_charge_param(struct smb_charger *chg, - struct smb_chg_param *param, int val_u) +int smblib_set_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int val_u) { int rc = 0; u8 val_raw; @@ -182,7 +217,7 @@ static int smblib_set_charge_param(struct smb_charger *chg, return rc; } -static int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend) +int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend) { int rc = 0; @@ -195,7 +230,7 @@ static int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend) return rc; } -static int smblib_set_dc_suspend(struct smb_charger *chg, bool suspend) +int smblib_set_dc_suspend(struct smb_charger *chg, bool suspend) { int rc = 0; @@ -1320,17 +1355,10 @@ static void smblib_hvdcp_detect_work(struct work_struct *work) } } -/******** - * INIT * - * ******/ - -int smblib_init(struct smb_charger *chg) +int smblib_create_votables(struct smb_charger *chg) { int rc = 0; - mutex_init(&chg->write_lock); - INIT_DELAYED_WORK(&chg->hvdcp_detect_work, smblib_hvdcp_detect_work); - chg->usb_suspend_votable = create_votable("INPUT_SUSPEND", VOTE_SET_ANY, smblib_usb_suspend_vote_callback, chg); @@ -1386,7 +1414,32 @@ int smblib_init(struct smb_charger *chg) return rc; } - return 0; + return rc; +} + +int smblib_init(struct smb_charger *chg) +{ + int rc = 0; + + mutex_init(&chg->write_lock); + INIT_DELAYED_WORK(&chg->hvdcp_detect_work, smblib_hvdcp_detect_work); + + switch (chg->mode) { + case PARALLEL_MASTER: + rc = smblib_create_votables(chg); + if (rc < 0) { + dev_err(chg->dev, "Couldn't create votables rc=%d\n", + rc); + } + break; + case PARALLEL_SLAVE: + break; + default: + dev_err(chg->dev, "Unsupported mode %d\n", chg->mode); + return -EINVAL; + } + + return rc; } int smblib_deinit(struct smb_charger *chg) diff --git a/drivers/power/qcom-charger/smb-lib.h b/drivers/power/qcom-charger/smb-lib.h index af3d29cc2d20..ac550eb09a0b 100644 --- a/drivers/power/qcom-charger/smb-lib.h +++ b/drivers/power/qcom-charger/smb-lib.h @@ -27,6 +27,12 @@ enum print_reason { #define USER_VOTER "USER_VOTER" #define PD_VOTER "PD_VOTER" +enum smb_mode { + PARALLEL_MASTER = 0, + PARALLEL_SLAVE, + NUM_MODES, +}; + struct smb_regulator { struct regulator_dev *rdev; struct regulator_desc rdesc; @@ -57,6 +63,7 @@ struct smb_charger { struct regmap *regmap; struct smb_params param; int *debug_mask; + enum smb_mode mode; /* locks */ struct mutex write_lock; @@ -96,7 +103,15 @@ int smblib_read(struct smb_charger *chg, u16 addr, u8 *val); int smblib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val); int smblib_write(struct smb_charger *chg, u16 addr, u8 val); +int smblib_get_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int *val_u); +int smblib_get_usb_suspend(struct smb_charger *chg, int *suspend); + int smblib_enable_charging(struct smb_charger *chg, bool enable); +int smblib_set_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int val_u); +int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend); +int smblib_set_dc_suspend(struct smb_charger *chg, bool suspend); int smblib_vbus_regulator_enable(struct regulator_dev *rdev); int smblib_vbus_regulator_disable(struct regulator_dev *rdev); diff --git a/drivers/power/qcom/msm-core.c b/drivers/power/qcom/msm-core.c index daa842498d96..49ed2eb3e40f 100644 --- a/drivers/power/qcom/msm-core.c +++ b/drivers/power/qcom/msm-core.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014-2016, 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 @@ -377,6 +377,7 @@ static int update_userspace_power(struct sched_params __user *argp) struct cpu_activity_info *node; struct cpu_static_info *sp, *clear_sp; int cpumask, cluster, mpidr; + bool pdata_valid[NR_CPUS] = {0}; get_user(cpumask, &argp->cpumask); get_user(cluster, &argp->cluster); @@ -448,13 +449,19 @@ static int update_userspace_power(struct sched_params __user *argp) } cpu_stats[cpu].ptable = per_cpu(ptable, cpu); repopulate_stats(cpu); - - blocking_notifier_call_chain( - &msm_core_stats_notifier_list, cpu, NULL); + pdata_valid[cpu] = true; } } spin_unlock(&update_lock); + for_each_possible_cpu(cpu) { + if (!pdata_valid[cpu]) + continue; + + blocking_notifier_call_chain( + &msm_core_stats_notifier_list, cpu, NULL); + } + activate_power_table = true; return 0; diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c index 52268783dd23..36fd682edab8 100644 --- a/drivers/scsi/ufs/ufs-qcom.c +++ b/drivers/scsi/ufs/ufs-qcom.c @@ -1829,6 +1829,9 @@ static void ufs_qcom_pm_qos_suspend(struct ufs_qcom_host *host) { int i; + if (!host->pm_qos.groups) + return; + for (i = 0; i < host->pm_qos.num_groups; i++) flush_work(&host->pm_qos.groups[i].unvote_work); } diff --git a/drivers/soc/qcom/icnss.c b/drivers/soc/qcom/icnss.c index d29fd5f3adcd..f4b63ec4b325 100644 --- a/drivers/soc/qcom/icnss.c +++ b/drivers/soc/qcom/icnss.c @@ -105,6 +105,7 @@ enum icnss_driver_event_type { ICNSS_DRIVER_EVENT_FW_READY_IND, ICNSS_DRIVER_EVENT_REGISTER_DRIVER, ICNSS_DRIVER_EVENT_UNREGISTER_DRIVER, + ICNSS_DRIVER_EVENT_MAX, }; struct icnss_driver_event { @@ -116,7 +117,7 @@ struct icnss_driver_event { void *data; }; -enum cnss_driver_state { +enum icnss_driver_state { ICNSS_WLFW_QMI_CONNECTED, ICNSS_POWER_ON, ICNSS_FW_READY, @@ -137,6 +138,44 @@ struct icnss_vreg_info { bool state; }; +struct icnss_stats { + struct { + uint32_t posted; + uint32_t processed; + } events[ICNSS_DRIVER_EVENT_MAX]; + + struct { + uint32_t request; + uint32_t free; + uint32_t enable; + uint32_t disable; + } ce_irqs[ICNSS_MAX_IRQ_REGISTRATIONS]; + + uint32_t ind_register_req; + uint32_t ind_register_resp; + uint32_t ind_register_err; + uint32_t msa_info_req; + uint32_t msa_info_resp; + uint32_t msa_info_err; + uint32_t msa_ready_req; + uint32_t msa_ready_resp; + uint32_t msa_ready_err; + uint32_t msa_ready_ind; + uint32_t cap_req; + uint32_t cap_resp; + uint32_t cap_err; + uint32_t pin_connect_result; + uint32_t cfg_req; + uint32_t cfg_resp; + uint32_t cfg_req_err; + uint32_t mode_req; + uint32_t mode_resp; + uint32_t mode_req_err; + uint32_t ini_req; + uint32_t ini_resp; + uint32_t ini_req_err; +}; + static struct icnss_data { struct platform_device *pdev; struct icnss_driver_ops *ops; @@ -173,6 +212,7 @@ static struct icnss_data { bool skip_qmi; struct dentry *root_dentry; spinlock_t on_off_lock; + struct icnss_stats stats; } *penv; static char *icnss_driver_event_to_str(enum icnss_driver_event_type type) @@ -188,6 +228,8 @@ static char *icnss_driver_event_to_str(enum icnss_driver_event_type type) return "REGISTER_DRIVER"; case ICNSS_DRIVER_EVENT_UNREGISTER_DRIVER: return "UNREGISTER_DRIVER"; + case ICNSS_DRIVER_EVENT_MAX: + return "EVENT_MAX"; } return "UNKNOWN"; @@ -196,7 +238,7 @@ static char *icnss_driver_event_to_str(enum icnss_driver_event_type type) static int icnss_driver_event_post(enum icnss_driver_event_type type, bool sync, void *data) { - struct icnss_driver_event *event = NULL; + struct icnss_driver_event *event; unsigned long flags; int gfp = GFP_KERNEL; int ret = 0; @@ -205,6 +247,11 @@ static int icnss_driver_event_post(enum icnss_driver_event_type type, icnss_driver_event_to_str(type), type, sync ? "-sync" : "", penv->state); + if (type >= ICNSS_DRIVER_EVENT_MAX) { + icnss_pr_err("Invalid Event type: %d, can't post", type); + return -EINVAL; + } + if (in_interrupt() || irqs_disabled()) gfp = GFP_ATOMIC; @@ -221,6 +268,7 @@ static int icnss_driver_event_post(enum icnss_driver_event_type type, list_add_tail(&event->list, &penv->event_list); spin_unlock_irqrestore(&penv->event_lock, flags); + penv->stats.events[type].posted++; queue_work(penv->event_wq, &penv->event_work); if (sync) { ret = wait_for_completion_interruptible(&event->complete); @@ -265,6 +313,8 @@ static int icnss_qmi_pin_connect_result_ind(void *msg, unsigned int msg_len) icnss_pr_dbg("Pin connect Result: pwr_pin: 0x%x phy_io_pin: 0x%x rf_io_pin: 0x%x\n", ind_msg.pwr_pin_result, ind_msg.phy_io_pin_result, ind_msg.rf_pin_result); + + penv->stats.pin_connect_result++; out: return ret; } @@ -485,7 +535,7 @@ static int wlfw_msa_mem_info_send_sync_msg(void) goto out; } - icnss_pr_dbg("Sending MSA info, state: 0x%lx\n", penv->state); + icnss_pr_dbg("Sending MSA mem info, state: 0x%lx\n", penv->state); memset(&req, 0, sizeof(req)); memset(&resp, 0, sizeof(resp)); @@ -501,6 +551,8 @@ static int wlfw_msa_mem_info_send_sync_msg(void) resp_desc.msg_id = QMI_WLFW_MSA_INFO_RESP_V01; resp_desc.ei_array = wlfw_msa_info_resp_msg_v01_ei; + penv->stats.msa_info_req++; + ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); if (ret < 0) { @@ -512,6 +564,7 @@ static int wlfw_msa_mem_info_send_sync_msg(void) icnss_pr_err("QMI request failed %d %d\n", resp.resp.result, resp.resp.error); ret = resp.resp.result; + penv->stats.msa_info_err++; goto out; } @@ -522,9 +575,11 @@ static int wlfw_msa_mem_info_send_sync_msg(void) icnss_pr_err("Invalid memory region length received%d\n", resp.mem_region_info_len); ret = -EINVAL; + penv->stats.msa_info_err++; goto out; } + penv->stats.msa_info_resp++; for (i = 0; i < resp.mem_region_info_len; i++) { penv->icnss_mem_region[i].reg_addr = resp.mem_region_info[i].region_addr; @@ -554,7 +609,8 @@ static int wlfw_msa_ready_send_sync_msg(void) goto out; } - icnss_pr_dbg("Sending MSA sync message, state: 0x%lx\n", penv->state); + icnss_pr_dbg("Sending MSA ready request message, state: 0x%lx\n", + penv->state); memset(&req, 0, sizeof(req)); memset(&resp, 0, sizeof(resp)); @@ -567,9 +623,11 @@ static int wlfw_msa_ready_send_sync_msg(void) resp_desc.msg_id = QMI_WLFW_MSA_READY_RESP_V01; resp_desc.ei_array = wlfw_msa_ready_resp_msg_v01_ei; + penv->stats.msa_ready_req++; ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); if (ret < 0) { + penv->stats.msa_ready_err++; icnss_pr_err("Send req failed %d\n", ret); goto out; } @@ -577,9 +635,11 @@ static int wlfw_msa_ready_send_sync_msg(void) if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { icnss_pr_err("QMI request failed %d %d\n", resp.resp.result, resp.resp.error); + penv->stats.msa_ready_err++; ret = resp.resp.result; goto out; } + penv->stats.msa_ready_resp++; out: return ret; } @@ -596,7 +656,8 @@ static int wlfw_ind_register_send_sync_msg(void) goto out; } - icnss_pr_dbg("Sending Sync message, state: 0x%lx\n", penv->state); + icnss_pr_dbg("Sending indication register message, state: 0x%lx\n", + penv->state); memset(&req, 0, sizeof(req)); memset(&resp, 0, sizeof(resp)); @@ -616,11 +677,15 @@ static int wlfw_ind_register_send_sync_msg(void) resp_desc.msg_id = QMI_WLFW_IND_REGISTER_RESP_V01; resp_desc.ei_array = wlfw_ind_register_resp_msg_v01_ei; + penv->stats.ind_register_req++; + ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); + penv->stats.ind_register_resp++; if (ret < 0) { icnss_pr_err("Send req failed %d\n", ret); + penv->stats.ind_register_err++; goto out; } @@ -628,6 +693,7 @@ static int wlfw_ind_register_send_sync_msg(void) icnss_pr_err("QMI request failed %d %d\n", resp.resp.result, resp.resp.error); ret = resp.resp.result; + penv->stats.ind_register_err++; goto out; } out: @@ -658,11 +724,13 @@ static int wlfw_cap_send_sync_msg(void) resp_desc.msg_id = QMI_WLFW_CAP_RESP_V01; resp_desc.ei_array = wlfw_cap_resp_msg_v01_ei; + penv->stats.cap_req++; ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); if (ret < 0) { icnss_pr_err("Send req failed %d\n", ret); + penv->stats.cap_err++; goto out; } @@ -670,9 +738,11 @@ static int wlfw_cap_send_sync_msg(void) icnss_pr_err("QMI request failed %d %d\n", resp.resp.result, resp.resp.error); ret = resp.resp.result; + penv->stats.cap_err++; goto out; } + penv->stats.cap_resp++; /* store cap locally */ if (resp.chip_info_valid) penv->chip_info = resp.chip_info; @@ -685,11 +755,11 @@ static int wlfw_cap_send_sync_msg(void) if (resp.fw_version_info_valid) penv->fw_version_info = resp.fw_version_info; - icnss_pr_dbg("Capability, chip_id: 0x%0x, chip_family: 0x%0x, board_id: 0x%0x, soc_id: 0x%0x, fw_version: 0x%0x, fw_build_timestamp: %s", - penv->chip_info.chip_id, penv->chip_info.chip_family, - penv->board_info.board_id, penv->soc_info.soc_id, - penv->fw_version_info.fw_version, - penv->fw_version_info.fw_build_timestamp); + icnss_pr_dbg("Capability, chip_id: 0x%x, chip_family: 0x%x, board_id: 0x%x, soc_id: 0x%x, fw_version: 0x%x, fw_build_timestamp: %s", + penv->chip_info.chip_id, penv->chip_info.chip_family, + penv->board_info.board_id, penv->soc_info.soc_id, + penv->fw_version_info.fw_version, + penv->fw_version_info.fw_build_timestamp); out: return ret; } @@ -722,11 +792,13 @@ static int wlfw_wlan_mode_send_sync_msg(enum wlfw_driver_mode_enum_v01 mode) resp_desc.msg_id = QMI_WLFW_WLAN_MODE_RESP_V01; resp_desc.ei_array = wlfw_wlan_mode_resp_msg_v01_ei; + penv->stats.mode_req++; ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); if (ret < 0) { icnss_pr_err("Send req failed %d\n", ret); + penv->stats.mode_req_err++; goto out; } @@ -734,8 +806,10 @@ static int wlfw_wlan_mode_send_sync_msg(enum wlfw_driver_mode_enum_v01 mode) icnss_pr_err("QMI request failed %d %d\n", resp.resp.result, resp.resp.error); ret = resp.resp.result; + penv->stats.mode_req_err++; goto out; } + penv->stats.mode_resp++; out: return ret; } @@ -767,11 +841,13 @@ static int wlfw_wlan_cfg_send_sync_msg(struct wlfw_wlan_cfg_req_msg_v01 *data) resp_desc.msg_id = QMI_WLFW_WLAN_CFG_RESP_V01; resp_desc.ei_array = wlfw_wlan_cfg_resp_msg_v01_ei; + penv->stats.cfg_req++; ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); if (ret < 0) { icnss_pr_err("Send req failed %d\n", ret); + penv->stats.cfg_req_err++; goto out; } @@ -779,8 +855,10 @@ static int wlfw_wlan_cfg_send_sync_msg(struct wlfw_wlan_cfg_req_msg_v01 *data) icnss_pr_err("QMI request failed %d %d\n", resp.resp.result, resp.resp.error); ret = resp.resp.result; + penv->stats.cfg_req_err++; goto out; } + penv->stats.cfg_resp++; out: return ret; } @@ -814,10 +892,13 @@ static int wlfw_ini_send_sync_msg(bool enable_fw_log) resp_desc.msg_id = QMI_WLFW_INI_RESP_V01; resp_desc.ei_array = wlfw_ini_resp_msg_v01_ei; + penv->stats.ini_req++; + ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); if (ret < 0) { icnss_pr_err("send req failed %d\n", ret); + penv->stats.ini_req_err++; goto out; } @@ -825,8 +906,10 @@ static int wlfw_ini_send_sync_msg(bool enable_fw_log) icnss_pr_err("QMI request failed %d %d\n", resp.resp.result, resp.resp.error); ret = resp.resp.result; + penv->stats.ini_req_err++; goto out; } + penv->stats.ini_resp++; out: return ret; } @@ -884,6 +967,7 @@ static void icnss_qmi_wlfw_clnt_ind(struct qmi_handle *handle, case QMI_WLFW_MSA_READY_IND_V01: icnss_pr_dbg("Received MSA Ready Indication msg_id 0x%x\n", msg_id); + penv->stats.msa_ready_ind++; break; case QMI_WLFW_PIN_CONNECT_RESULT_IND_V01: icnss_pr_dbg("Received Pin Connect Test Result msg_id 0x%x\n", @@ -1131,13 +1215,18 @@ static void icnss_driver_event_work(struct work_struct *work) break; default: icnss_pr_err("Invalid Event type: %d", event->type); - break; + kfree(event); + continue; } + + penv->stats.events[event->type].processed++; + if (event->sync) { event->ret = ret; complete(&event->complete); } else kfree(event); + spin_lock_irqsave(&penv->event_lock, flags); } spin_unlock_irqrestore(&penv->event_lock, flags); @@ -1245,7 +1334,7 @@ int icnss_ce_request_irq(unsigned int ce_id, icnss_pr_dbg("CE request IRQ: %d, state: 0x%lx\n", ce_id, penv->state); if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) { - icnss_pr_err("Invalid CE ID %d\n", ce_id); + icnss_pr_err("Invalid CE ID, ce_id: %d\n", ce_id); ret = -EINVAL; goto out; } @@ -1267,7 +1356,10 @@ int icnss_ce_request_irq(unsigned int ce_id, } irq_entry->irq = irq; irq_entry->handler = handler; + icnss_pr_dbg("IRQ requested: %d, ce_id: %d\n", irq, ce_id); + + penv->stats.ce_irqs[ce_id].request++; out: return ret; } @@ -1286,6 +1378,12 @@ int icnss_ce_free_irq(unsigned int ce_id, void *ctx) icnss_pr_dbg("CE free IRQ: %d, state: 0x%lx\n", ce_id, penv->state); + if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) { + icnss_pr_err("Invalid CE ID to free, ce_id: %d\n", ce_id); + ret = -EINVAL; + goto out; + } + irq = penv->ce_irqs[ce_id]; irq_entry = &penv->ce_irq_list[ce_id]; if (!irq_entry->handler || !irq_entry->irq) { @@ -1296,6 +1394,8 @@ int icnss_ce_free_irq(unsigned int ce_id, void *ctx) free_irq(irq, ctx); irq_entry->irq = 0; irq_entry->handler = NULL; + + penv->stats.ce_irqs[ce_id].free++; out: return ret; } @@ -1313,6 +1413,13 @@ void icnss_enable_irq(unsigned int ce_id) icnss_pr_dbg("Enable IRQ: ce_id: %d, state: 0x%lx\n", ce_id, penv->state); + if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) { + icnss_pr_err("Invalid CE ID to enable IRQ, ce_id: %d\n", ce_id); + return; + } + + penv->stats.ce_irqs[ce_id].enable++; + irq = penv->ce_irqs[ce_id]; enable_irq(irq); } @@ -1330,8 +1437,16 @@ void icnss_disable_irq(unsigned int ce_id) icnss_pr_dbg("Disable IRQ: ce_id: %d, state: 0x%lx\n", ce_id, penv->state); + if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) { + icnss_pr_err("Invalid CE ID to disable IRQ, ce_id: %d\n", + ce_id); + return; + } + irq = penv->ce_irqs[ce_id]; disable_irq(irq); + + penv->stats.ce_irqs[ce_id].disable++; } EXPORT_SYMBOL(icnss_disable_irq); @@ -1806,6 +1921,9 @@ static ssize_t icnss_test_mode_write(struct file *fp, const char __user *buf, if (ret) return ret; + if (ret == 0) + memset(&priv->stats, 0, sizeof(priv->stats)); + return count; } @@ -1823,6 +1941,172 @@ static const struct file_operations icnss_test_mode_fops = { .llseek = seq_lseek, }; +static ssize_t icnss_stats_write(struct file *fp, const char __user *buf, + size_t count, loff_t *off) +{ + struct icnss_data *priv = + ((struct seq_file *)fp->private_data)->private; + int ret; + u32 val; + + ret = kstrtou32_from_user(buf, count, 0, &val); + if (ret) + return ret; + + if (ret == 0) + memset(&priv->stats, 0, sizeof(priv->stats)); + + return count; +} + +static int icnss_stats_show_state(struct seq_file *s, struct icnss_data *priv) +{ + int i; + int skip = 0; + unsigned long state; + + seq_printf(s, "\nState: 0x%lx(", priv->state); + for (i = 0, state = priv->state; state != 0; state >>= 1, i++) { + + if (!(state & 0x1)) + continue; + + if (skip++) + seq_puts(s, " | "); + + switch (i) { + case ICNSS_WLFW_QMI_CONNECTED: + seq_puts(s, "QMI CONN"); + continue; + case ICNSS_POWER_ON: + seq_puts(s, "POWER ON"); + continue; + case ICNSS_FW_READY: + seq_puts(s, "FW READY"); + continue; + case ICNSS_DRIVER_PROBED: + seq_puts(s, "DRIVER PROBED"); + continue; + case ICNSS_FW_TEST_MODE: + seq_puts(s, "FW TEST MODE"); + continue; + } + + seq_printf(s, "UNKNOWN-%d", i); + } + seq_puts(s, ")\n"); + + return 0; +} + +static int icnss_stats_show_capability(struct seq_file *s, + struct icnss_data *priv) +{ + if (test_bit(ICNSS_FW_READY, &priv->state)) { + seq_puts(s, "\n<---------------- FW Capability ----------------->\n"); + seq_printf(s, "Chip ID: 0x%x\n", priv->chip_info.chip_id); + seq_printf(s, "Chip family: 0x%x\n", + priv->chip_info.chip_family); + seq_printf(s, "Board ID: 0x%x\n", priv->board_info.board_id); + seq_printf(s, "SOC Info: 0x%x\n", priv->soc_info.soc_id); + seq_printf(s, "Firmware Version: 0x%x\n", + priv->fw_version_info.fw_version); + seq_printf(s, "Firmware Build Timestamp: %s\n", + priv->fw_version_info.fw_build_timestamp); + } + + return 0; +} + +static int icnss_stats_show_events(struct seq_file *s, struct icnss_data *priv) +{ + int i; + + seq_puts(s, "\n<----------------- Events stats ------------------->\n"); + seq_printf(s, "%24s %16s %16s\n", "Events", "Posted", "Processed"); + for (i = 0; i < ICNSS_DRIVER_EVENT_MAX; i++) + seq_printf(s, "%24s %16u %16u\n", + icnss_driver_event_to_str(i), + priv->stats.events[i].posted, + priv->stats.events[i].processed); + + return 0; +} + +static int icnss_stats_show_irqs(struct seq_file *s, struct icnss_data *priv) +{ + int i; + + seq_puts(s, "\n<------------------ IRQ stats ------------------->\n"); + seq_printf(s, "%4s %4s %8s %8s %8s %8s\n", "CE_ID", "IRQ", "Request", + "Free", "Enable", "Disable"); + for (i = 0; i < ICNSS_MAX_IRQ_REGISTRATIONS; i++) + seq_printf(s, "%4d: %4u %8u %8u %8u %8u\n", i, + priv->ce_irqs[i], priv->stats.ce_irqs[i].request, + priv->stats.ce_irqs[i].free, + priv->stats.ce_irqs[i].enable, + priv->stats.ce_irqs[i].disable); + + return 0; +} + +static int icnss_stats_show(struct seq_file *s, void *data) +{ +#define ICNSS_STATS_DUMP(_s, _priv, _x) \ + seq_printf(_s, "%24s: %u\n", #_x, _priv->stats._x) + + struct icnss_data *priv = s->private; + + ICNSS_STATS_DUMP(s, priv, ind_register_req); + ICNSS_STATS_DUMP(s, priv, ind_register_resp); + ICNSS_STATS_DUMP(s, priv, ind_register_err); + ICNSS_STATS_DUMP(s, priv, msa_info_req); + ICNSS_STATS_DUMP(s, priv, msa_info_resp); + ICNSS_STATS_DUMP(s, priv, msa_info_err); + ICNSS_STATS_DUMP(s, priv, msa_ready_req); + ICNSS_STATS_DUMP(s, priv, msa_ready_resp); + ICNSS_STATS_DUMP(s, priv, msa_ready_err); + ICNSS_STATS_DUMP(s, priv, msa_ready_ind); + ICNSS_STATS_DUMP(s, priv, cap_req); + ICNSS_STATS_DUMP(s, priv, cap_resp); + ICNSS_STATS_DUMP(s, priv, cap_err); + ICNSS_STATS_DUMP(s, priv, pin_connect_result); + ICNSS_STATS_DUMP(s, priv, cfg_req); + ICNSS_STATS_DUMP(s, priv, cfg_resp); + ICNSS_STATS_DUMP(s, priv, cfg_req_err); + ICNSS_STATS_DUMP(s, priv, mode_req); + ICNSS_STATS_DUMP(s, priv, mode_resp); + ICNSS_STATS_DUMP(s, priv, mode_req_err); + ICNSS_STATS_DUMP(s, priv, ini_req); + ICNSS_STATS_DUMP(s, priv, ini_resp); + ICNSS_STATS_DUMP(s, priv, ini_req_err); + + icnss_stats_show_irqs(s, priv); + + icnss_stats_show_capability(s, priv); + + icnss_stats_show_events(s, priv); + + icnss_stats_show_state(s, priv); + + return 0; +#undef ICNSS_STATS_DUMP +} + +static int icnss_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, icnss_stats_show, inode->i_private); +} + +static const struct file_operations icnss_stats_fops = { + .read = seq_read, + .write = icnss_stats_write, + .release = single_release, + .open = icnss_stats_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + static int icnss_debugfs_create(struct icnss_data *priv) { int ret = 0; @@ -1838,8 +2122,11 @@ static int icnss_debugfs_create(struct icnss_data *priv) priv->root_dentry = root_dentry; - debugfs_create_file("test_mode", S_IRUSR | S_IWUSR, - root_dentry, priv, &icnss_test_mode_fops); + debugfs_create_file("test_mode", 0644, root_dentry, priv, + &icnss_test_mode_fops); + + debugfs_create_file("stats", 0644, root_dentry, priv, + &icnss_stats_fops); out: return ret; diff --git a/drivers/soc/qcom/spcom.c b/drivers/soc/qcom/spcom.c index 584936c3910d..d2204b9b0f6f 100644 --- a/drivers/soc/qcom/spcom.c +++ b/drivers/soc/qcom/spcom.c @@ -96,7 +96,13 @@ /* SPCOM driver name */ #define DEVICE_NAME "spcom" -#define SPCOM_MAX_CHANNELS 20 +#define SPCOM_MAX_CHANNELS 0x20 + +/* maximum ION buffers should be >= SPCOM_MAX_CHANNELS */ +#define SPCOM_MAX_ION_BUF_PER_CH (SPCOM_MAX_CHANNELS + 4) + +/* maximum ION buffer per send request/response command */ +#define SPCOM_MAX_ION_BUF_PER_CMD SPCOM_MAX_ION_BUF /* Maximum command size */ #define SPCOM_MAX_COMMAND_SIZE (PAGE_SIZE) @@ -215,8 +221,8 @@ struct spcom_channel { const void *glink_rx_buf; /* ION lock/unlock support */ - int ion_fd_table[SPCOM_MAX_ION_BUF]; - struct ion_handle *ion_handle_table[SPCOM_MAX_ION_BUF]; + int ion_fd_table[SPCOM_MAX_ION_BUF_PER_CH]; + struct ion_handle *ion_handle_table[SPCOM_MAX_ION_BUF_PER_CH]; }; /** @@ -607,13 +613,10 @@ static struct spcom_channel *spcom_find_channel_by_name(const char *name) struct spcom_channel *ch = &spcom_dev->channels[i]; if (strcmp(ch->name, name) == 0) { - pr_debug("channel [%s] found.\n", name); return ch; } } - pr_err("failed to find channel [%s].\n", name); - return NULL; } @@ -1441,7 +1444,7 @@ static int spcom_handle_send_modified_command(struct spcom_channel *ch, void *tx_buf; int tx_buf_size; uint32_t timeout_msec; - struct spcom_ion_info ion_info[SPCOM_MAX_ION_BUF]; + struct spcom_ion_info ion_info[SPCOM_MAX_ION_BUF_PER_CMD]; int i; pr_debug("send req/resp ch [%s] size [%d] .\n", ch->name, size); @@ -1477,7 +1480,7 @@ static int spcom_handle_send_modified_command(struct spcom_channel *ch, /* user buf */ memcpy(hdr->buf, buf, buf_size); - for (i = 0 ; i < SPCOM_MAX_ION_BUF ; i++) { + for (i = 0 ; i < ARRAY_SIZE(ion_info) ; i++) { if (ion_info[i].fd >= 0) { ret = modify_ion_addr(hdr->buf, buf_size, ion_info[i]); if (ret < 0) { @@ -1532,7 +1535,7 @@ static int spcom_handle_lock_ion_buf_command(struct spcom_channel *ch, pr_debug("ion handle ok.\n"); /* Check if this ION buffer is already locked */ - for (i = 0 ; i < SPCOM_MAX_ION_BUF ; i++) { + for (i = 0 ; i < ARRAY_SIZE(ch->ion_handle_table) ; i++) { if (ch->ion_handle_table[i] == ion_handle) { pr_debug("fd [%d] ion buf is already locked.\n", fd); /* decrement back the ref count */ @@ -1542,7 +1545,7 @@ static int spcom_handle_lock_ion_buf_command(struct spcom_channel *ch, } /* Store the ION handle */ - for (i = 0 ; i < SPCOM_MAX_ION_BUF ; i++) { + for (i = 0 ; i < ARRAY_SIZE(ch->ion_handle_table) ; i++) { if (ch->ion_handle_table[i] == NULL) { ch->ion_handle_table[i] = ion_handle; ch->ion_fd_table[i] = fd; @@ -1576,7 +1579,7 @@ static int spcom_handle_unlock_ion_buf_command(struct spcom_channel *ch, if (fd == (int) SPCOM_ION_FD_UNLOCK_ALL) { /* unlock all ION buf */ - for (i = 0 ; i < SPCOM_MAX_ION_BUF ; i++) { + for (i = 0 ; i < ARRAY_SIZE(ch->ion_handle_table) ; i++) { if (ch->ion_handle_table[i] != NULL) { ion_free(ion_client, ch->ion_handle_table[i]); ch->ion_handle_table[i] = NULL; @@ -1586,7 +1589,7 @@ static int spcom_handle_unlock_ion_buf_command(struct spcom_channel *ch, } } else { /* unlock specific ION buf */ - for (i = 0 ; i < SPCOM_MAX_ION_BUF ; i++) { + for (i = 0 ; i < ARRAY_SIZE(ch->ion_handle_table) ; i++) { if (ch->ion_fd_table[i] == fd) { ion_free(ion_client, ch->ion_handle_table[i]); ch->ion_handle_table[i] = NULL; diff --git a/drivers/staging/android/ion/ion.c b/drivers/staging/android/ion/ion.c index 53a31e79536b..e9237202e79f 100755 --- a/drivers/staging/android/ion/ion.c +++ b/drivers/staging/android/ion/ion.c @@ -1118,6 +1118,8 @@ void ion_pages_sync_for_device(struct device *dev, struct page *page, { struct scatterlist sg; + WARN_ONCE(!dev, "A device is required for dma_sync\n"); + sg_init_table(&sg, 1); sg_set_page(&sg, page, size, 0); /* diff --git a/drivers/staging/android/ion/ion_carveout_heap.c b/drivers/staging/android/ion/ion_carveout_heap.c index e702ce6461fc..eab137a85703 100644 --- a/drivers/staging/android/ion/ion_carveout_heap.c +++ b/drivers/staging/android/ion/ion_carveout_heap.c @@ -111,13 +111,15 @@ static void ion_carveout_heap_free(struct ion_buffer *buffer) struct ion_heap *heap = buffer->heap; struct sg_table *table = buffer->priv_virt; struct page *page = sg_page(table->sgl); + struct device *dev = heap->priv; + ion_phys_addr_t paddr = PFN_PHYS(page_to_pfn(page)); ion_heap_buffer_zero(buffer); if (ion_buffer_cached(buffer)) - dma_sync_sg_for_device(NULL, table->sgl, table->nents, - DMA_BIDIRECTIONAL); + dma_sync_sg_for_device(dev, table->sgl, table->nents, + DMA_BIDIRECTIONAL); ion_carveout_free(heap, paddr, buffer->size); sg_free_table(table); @@ -153,11 +155,12 @@ struct ion_heap *ion_carveout_heap_create(struct ion_platform_heap *heap_data) struct page *page; size_t size; + struct device *dev = heap_data->priv; page = pfn_to_page(PFN_DOWN(heap_data->base)); size = heap_data->size; - ion_pages_sync_for_device(NULL, page, size, DMA_BIDIRECTIONAL); + ion_pages_sync_for_device(dev, page, size, DMA_BIDIRECTIONAL); ret = ion_heap_pages_zero(page, size, pgprot_writecombine(PAGE_KERNEL)); if (ret) diff --git a/drivers/staging/android/ion/ion_chunk_heap.c b/drivers/staging/android/ion/ion_chunk_heap.c index 6b3e18aa1c64..1ad68f47bc01 100644 --- a/drivers/staging/android/ion/ion_chunk_heap.c +++ b/drivers/staging/android/ion/ion_chunk_heap.c @@ -99,14 +99,15 @@ static void ion_chunk_heap_free(struct ion_buffer *buffer) struct scatterlist *sg; int i; unsigned long allocated_size; + struct device *dev = heap->priv; allocated_size = ALIGN(buffer->size, chunk_heap->chunk_size); ion_heap_buffer_zero(buffer); if (ion_buffer_cached(buffer)) - dma_sync_sg_for_device(NULL, table->sgl, table->nents, - DMA_BIDIRECTIONAL); + dma_sync_sg_for_device(dev, table->sgl, table->nents, + DMA_BIDIRECTIONAL); for_each_sg(table->sgl, sg, table->nents, i) { gen_pool_free(chunk_heap->pool, page_to_phys(sg_page(sg)), @@ -144,11 +145,12 @@ struct ion_heap *ion_chunk_heap_create(struct ion_platform_heap *heap_data) int ret; struct page *page; size_t size; + struct device *dev = heap_data->priv; page = pfn_to_page(PFN_DOWN(heap_data->base)); size = heap_data->size; - ion_pages_sync_for_device(NULL, page, size, DMA_BIDIRECTIONAL); + ion_pages_sync_for_device(dev, page, size, DMA_BIDIRECTIONAL); ret = ion_heap_pages_zero(page, size, pgprot_writecombine(PAGE_KERNEL)); if (ret) diff --git a/drivers/staging/android/ion/ion_page_pool.c b/drivers/staging/android/ion/ion_page_pool.c index 8dc72085f7a8..d549d6271d89 100644 --- a/drivers/staging/android/ion/ion_page_pool.c +++ b/drivers/staging/android/ion/ion_page_pool.c @@ -35,7 +35,8 @@ static void *ion_page_pool_alloc_pages(struct ion_page_pool *pool) return NULL; if (pool->gfp_mask & __GFP_ZERO) - if (msm_ion_heap_high_order_page_zero(page, pool->order)) + if (msm_ion_heap_high_order_page_zero(pool->dev, page, + pool->order)) goto error_free_pages; ion_page_pool_alloc_set_cache_policy(pool, page); @@ -222,12 +223,14 @@ int ion_page_pool_shrink(struct ion_page_pool *pool, gfp_t gfp_mask, return ion_page_pool_total(pool, high); } -struct ion_page_pool *ion_page_pool_create(gfp_t gfp_mask, unsigned int order) +struct ion_page_pool *ion_page_pool_create(struct device *dev, gfp_t gfp_mask, + unsigned int order) { struct ion_page_pool *pool = kmalloc(sizeof(struct ion_page_pool), GFP_KERNEL); if (!pool) return NULL; + pool->dev = dev; pool->high_count = 0; pool->low_count = 0; pool->nr_unreserved = 0; diff --git a/drivers/staging/android/ion/ion_priv.h b/drivers/staging/android/ion/ion_priv.h index 65237205f070..4f383661258a 100644 --- a/drivers/staging/android/ion/ion_priv.h +++ b/drivers/staging/android/ion/ion_priv.h @@ -262,9 +262,11 @@ int ion_heap_map_user(struct ion_heap *, struct ion_buffer *, int ion_heap_buffer_zero(struct ion_buffer *buffer); int ion_heap_pages_zero(struct page *page, size_t size, pgprot_t pgprot); -int msm_ion_heap_high_order_page_zero(struct page *page, int order); +int msm_ion_heap_high_order_page_zero(struct device *dev, struct page *page, + int order); struct ion_heap *get_ion_heap(int heap_id); -int msm_ion_heap_sg_table_zero(struct sg_table *, size_t size); +int msm_ion_heap_sg_table_zero(struct device *dev, struct sg_table *, + size_t size); int msm_ion_heap_pages_zero(struct page **pages, int num_pages); int msm_ion_heap_alloc_pages_mem(struct pages_mem *pages_mem); void msm_ion_heap_free_pages_mem(struct pages_mem *pages_mem); @@ -435,12 +437,14 @@ struct ion_page_pool { struct list_head high_items; struct list_head low_items; struct mutex mutex; + struct device *dev; gfp_t gfp_mask; unsigned int order; struct plist_node list; }; -struct ion_page_pool *ion_page_pool_create(gfp_t gfp_mask, unsigned int order); +struct ion_page_pool *ion_page_pool_create(struct device *dev, gfp_t gfp_mask, + unsigned int order); void ion_page_pool_destroy(struct ion_page_pool *); void *ion_page_pool_alloc(struct ion_page_pool *, bool *from_pool); void *ion_page_pool_alloc_pool_only(struct ion_page_pool *); diff --git a/drivers/staging/android/ion/ion_system_heap.c b/drivers/staging/android/ion/ion_system_heap.c index 487dc37cde27..ff75e1690f59 100644 --- a/drivers/staging/android/ion/ion_system_heap.c +++ b/drivers/staging/android/ion/ion_system_heap.c @@ -82,6 +82,7 @@ static struct page *alloc_buffer_page(struct ion_system_heap *heap, struct page *page; struct ion_page_pool *pool; int vmid = get_secure_vmid(buffer->flags); + struct device *dev = heap->heap.priv; if (*from_pool) { if (vmid > 0) @@ -100,6 +101,8 @@ static struct page *alloc_buffer_page(struct ion_system_heap *heap, if (order) gfp_mask = high_order_gfp_flags; page = alloc_pages(gfp_mask, order); + ion_pages_sync_for_device(dev, page, PAGE_SIZE << order, + DMA_BIDIRECTIONAL); } if (!page) return 0; @@ -222,6 +225,7 @@ static int ion_system_heap_allocate(struct ion_heap *heap, struct pages_mem data; unsigned int sz; int vmid = get_secure_vmid(buffer->flags); + struct device *dev = heap->priv; if (align > PAGE_SIZE) return -EINVAL; @@ -311,7 +315,7 @@ static int ion_system_heap_allocate(struct ion_heap *heap, } if (nents_sync) { - dma_sync_sg_for_device(NULL, table_sync.sgl, table_sync.nents, + dma_sync_sg_for_device(dev, table_sync.sgl, table_sync.nents, DMA_BIDIRECTIONAL); if (vmid > 0) { ret = ion_system_secure_heap_assign_sg(&table_sync, @@ -368,11 +372,12 @@ void ion_system_heap_free(struct ion_buffer *buffer) LIST_HEAD(pages); int i; int vmid = get_secure_vmid(buffer->flags); + struct device *dev = heap->priv; if (!(buffer->private_flags & ION_PRIV_FLAG_SHRINKER_FREE) && !(buffer->flags & ION_FLAG_POOL_FORCE_ALLOC)) { if (vmid < 0) - msm_ion_heap_sg_table_zero(table, buffer->size); + msm_ion_heap_sg_table_zero(dev, table, buffer->size); } else if (vmid > 0) { if (ion_system_secure_heap_unassign_sg(table, vmid)) return; @@ -627,7 +632,8 @@ static void ion_system_heap_destroy_pools(struct ion_page_pool **pools) * nothing. If it succeeds you'll eventually need to use * ion_system_heap_destroy_pools to destroy the pools. */ -static int ion_system_heap_create_pools(struct ion_page_pool **pools) +static int ion_system_heap_create_pools(struct device *dev, + struct ion_page_pool **pools) { int i; for (i = 0; i < num_orders; i++) { @@ -636,7 +642,7 @@ static int ion_system_heap_create_pools(struct ion_page_pool **pools) if (orders[i]) gfp_flags = high_order_gfp_flags; - pool = ion_page_pool_create(gfp_flags, orders[i]); + pool = ion_page_pool_create(dev, gfp_flags, orders[i]); if (!pool) goto err_create_pool; pools[i] = pool; @@ -647,11 +653,12 @@ err_create_pool: return 1; } -struct ion_heap *ion_system_heap_create(struct ion_platform_heap *unused) +struct ion_heap *ion_system_heap_create(struct ion_platform_heap *data) { struct ion_system_heap *heap; int i; int pools_size = sizeof(struct ion_page_pool *) * num_orders; + struct device *dev = data->priv; heap = kzalloc(sizeof(struct ion_system_heap), GFP_KERNEL); if (!heap) @@ -673,15 +680,16 @@ struct ion_heap *ion_system_heap_create(struct ion_platform_heap *unused) heap->secure_pools[i] = kzalloc(pools_size, GFP_KERNEL); if (!heap->secure_pools[i]) goto err_create_secure_pools; - if (ion_system_heap_create_pools(heap->secure_pools[i])) + if (ion_system_heap_create_pools( + dev, heap->secure_pools[i])) goto err_create_secure_pools; } } - if (ion_system_heap_create_pools(heap->uncached_pools)) + if (ion_system_heap_create_pools(dev, heap->uncached_pools)) goto err_create_uncached_pools; - if (ion_system_heap_create_pools(heap->cached_pools)) + if (ion_system_heap_create_pools(dev, heap->cached_pools)) goto err_create_cached_pools; heap->heap.debug_show = ion_system_heap_debug_show; @@ -738,6 +746,7 @@ static int ion_system_contig_heap_allocate(struct ion_heap *heap, struct sg_table *table; unsigned long i; int ret; + struct device *dev = heap->priv; if (align > (PAGE_SIZE << order)) return -EINVAL; @@ -766,7 +775,7 @@ static int ion_system_contig_heap_allocate(struct ion_heap *heap, buffer->priv_virt = table; - ion_pages_sync_for_device(NULL, page, len, DMA_BIDIRECTIONAL); + ion_pages_sync_for_device(dev, page, len, DMA_BIDIRECTIONAL); return 0; diff --git a/drivers/staging/android/ion/msm/msm_ion.c b/drivers/staging/android/ion/msm/msm_ion.c index 49b5270e5162..46d7aae9b1c4 100644 --- a/drivers/staging/android/ion/msm/msm_ion.c +++ b/drivers/staging/android/ion/msm/msm_ion.c @@ -859,7 +859,8 @@ void msm_ion_heap_free_pages_mem(struct pages_mem *pages_mem) pages_mem->free_fn(pages_mem->pages); } -int msm_ion_heap_high_order_page_zero(struct page *page, int order) +int msm_ion_heap_high_order_page_zero(struct device *dev, struct page *page, + int order) { int i, ret; struct pages_mem pages_mem; @@ -873,13 +874,14 @@ int msm_ion_heap_high_order_page_zero(struct page *page, int order) pages_mem.pages[i] = page + i; ret = msm_ion_heap_pages_zero(pages_mem.pages, npages); - dma_sync_single_for_device(NULL, page_to_phys(page), pages_mem.size, + dma_sync_single_for_device(dev, page_to_phys(page), pages_mem.size, DMA_BIDIRECTIONAL); msm_ion_heap_free_pages_mem(&pages_mem); return ret; } -int msm_ion_heap_sg_table_zero(struct sg_table *table, size_t size) +int msm_ion_heap_sg_table_zero(struct device *dev, struct sg_table *table, + size_t size) { struct scatterlist *sg; int i, j, ret = 0, npages = 0; @@ -901,7 +903,7 @@ int msm_ion_heap_sg_table_zero(struct sg_table *table, size_t size) } ret = msm_ion_heap_pages_zero(pages_mem.pages, npages); - dma_sync_sg_for_device(NULL, table->sgl, table->nents, + dma_sync_sg_for_device(dev, table->sgl, table->nents, DMA_BIDIRECTIONAL); msm_ion_heap_free_pages_mem(&pages_mem); return ret; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 4438833ca935..5cfed430b8d4 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -511,6 +511,10 @@ struct mmc_host { const struct mmc_bus_ops *bus_ops; /* current bus driver */ unsigned int bus_refs; /* reference counter */ + unsigned int bus_resume_flags; +#define MMC_BUSRESUME_MANUAL_RESUME (1 << 0) +#define MMC_BUSRESUME_NEEDS_RESUME (1 << 1) + unsigned int sdio_irqs; struct task_struct *sdio_irq_thread; bool sdio_irq_pending; @@ -614,6 +618,20 @@ static inline void *mmc_cmdq_private(struct mmc_host *host) #define mmc_dev(x) ((x)->parent) #define mmc_classdev(x) (&(x)->class_dev) #define mmc_hostname(x) (dev_name(&(x)->class_dev)) +#define mmc_bus_needs_resume(host) ((host)->bus_resume_flags & \ + MMC_BUSRESUME_NEEDS_RESUME) +#define mmc_bus_manual_resume(host) ((host)->bus_resume_flags & \ + MMC_BUSRESUME_MANUAL_RESUME) + +static inline void mmc_set_bus_resume_policy(struct mmc_host *host, int manual) +{ + if (manual) + host->bus_resume_flags |= MMC_BUSRESUME_MANUAL_RESUME; + else + host->bus_resume_flags &= ~MMC_BUSRESUME_MANUAL_RESUME; +} + +extern int mmc_resume_bus(struct mmc_host *host); int mmc_power_save_host(struct mmc_host *host); int mmc_power_restore_host(struct mmc_host *host); diff --git a/include/linux/msm_pcie.h b/include/linux/msm_pcie.h index aba7fe20835e..c9c467ff7428 100644 --- a/include/linux/msm_pcie.h +++ b/include/linux/msm_pcie.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014-2016, 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 @@ -59,6 +59,7 @@ struct msm_pcie_register_event { u32 options; }; +#ifdef CONFIG_PCI_MSM /** * msm_pcie_pm_control - control the power state of a PCIe link. * @pm_opt: power management operation @@ -168,4 +169,49 @@ int msm_pcie_debug_info(struct pci_dev *dev, u32 option, u32 base, */ int msm_pcie_configure_sid(struct device *dev, u32 *sid, int *domain); -#endif +#else /* !CONFIG_PCI_MSM */ +static inline int msm_pcie_pm_control(enum msm_pcie_pm_opt pm_opt, u32 busnr, + void *user, void *data, u32 options) +{ + return -ENODEV; +} + +static inline int msm_pcie_register_event(struct msm_pcie_register_event *reg) +{ + return -ENODEV; +} + +static inline int msm_pcie_deregister_event(struct msm_pcie_register_event *reg) +{ + return -ENODEV; +} + +static inline int msm_pcie_recover_config(struct pci_dev *dev) +{ + return -ENODEV; +} + +static inline int msm_pcie_enumerate(u32 rc_idx) +{ + return -ENODEV; +} + +static inline int msm_pcie_shadow_control(struct pci_dev *dev, bool enable) +{ + return -ENODEV; +} + +static inline int msm_pcie_debug_info(struct pci_dev *dev, u32 option, u32 base, + u32 offset, u32 mask, u32 value) +{ + return -ENODEV; +} + +static inline int msm_pcie_configure_sid(struct device *dev, u32 *sid, + int *domain) +{ + return -ENODEV; +} +#endif /* CONFIG_PCI_MSM */ + +#endif /* __MSM_PCIE_H */ diff --git a/include/uapi/media/msm_cam_sensor.h b/include/uapi/media/msm_cam_sensor.h index d83e00876fd5..5d340b9a2523 100644 --- a/include/uapi/media/msm_cam_sensor.h +++ b/include/uapi/media/msm_cam_sensor.h @@ -342,6 +342,13 @@ enum msm_actuator_cfg_type_t { CFG_ACTUATOR_INIT, }; +struct msm_ois_opcode { + uint32_t prog; + uint32_t coeff; + uint32_t pheripheral; + uint32_t memory; +}; + enum msm_ois_cfg_type_t { CFG_OIS_INIT, CFG_OIS_POWERDOWN, @@ -350,6 +357,11 @@ enum msm_ois_cfg_type_t { CFG_OIS_I2C_WRITE_SEQ_TABLE, }; +enum msm_ois_cfg_download_type_t { + CFG_OIS_DOWNLOAD, + CFG_OIS_DATA_CONFIG, +}; + enum msm_ois_i2c_operation { MSM_OIS_WRITE = 0, MSM_OIS_POLL, @@ -448,6 +460,11 @@ enum af_camera_name { ACTUATOR_WEB_CAM_2, }; +struct msm_ois_slave_info { + char ois_name[MAX_OIS_NAME_SIZE]; + uint32_t i2c_addr; + struct msm_ois_opcode opcode; +}; struct msm_ois_cfg_data { int cfgtype; union { @@ -456,6 +473,11 @@ struct msm_ois_cfg_data { } cfg; }; +struct msm_ois_cfg_download_data { + int cfgtype; + struct msm_ois_slave_info slave_info; +}; + struct msm_actuator_set_position_t { uint16_t number_of_steps; uint32_t hw_params; @@ -560,5 +582,8 @@ struct sensor_init_cfg_data { #define VIDIOC_MSM_FLASH_CFG \ _IOWR('V', BASE_VIDIOC_PRIVATE + 13, struct msm_flash_cfg_data_t) +#define VIDIOC_MSM_OIS_CFG_DOWNLOAD \ + _IOWR('V', BASE_VIDIOC_PRIVATE + 14, struct msm_ois_cfg_download_data) + #endif diff --git a/include/uapi/media/msmb_camera.h b/include/uapi/media/msmb_camera.h index f16479c250e2..092551d0cc16 100644 --- a/include/uapi/media/msmb_camera.h +++ b/include/uapi/media/msmb_camera.h @@ -23,6 +23,9 @@ #define MSM_CAM_V4L2_IOCTL_NOTIFY_DEBUG \ _IOW('V', BASE_VIDIOC_PRIVATE + 34, struct msm_v4l2_event_data) +#define MSM_CAM_V4L2_IOCTL_DAEMON_DISABLED \ + _IOW('V', BASE_VIDIOC_PRIVATE + 35, struct msm_v4l2_event_data) + #define QCAMERA_DEVICE_GROUP_ID 1 #define QCAMERA_VNODE_GROUP_ID 2 #define MSM_CAMERA_NAME "msm_camera" diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 6a7751e99e82..81fbed978da3 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -2244,15 +2244,7 @@ static int do_proc_dointvec_conv(bool *negp, unsigned long *lvalp, int write, void *data) { if (write) { - if (*negp) { - if (*lvalp > (unsigned long) INT_MAX + 1) - return -EINVAL; - *valp = -*lvalp; - } else { - if (*lvalp > (unsigned long) INT_MAX) - return -EINVAL; - *valp = *lvalp; - } + *valp = *negp ? -*lvalp : *lvalp; } else { int val = *valp; if (val < 0) { diff --git a/sound/soc/codecs/wcd-mbhc-v2.c b/sound/soc/codecs/wcd-mbhc-v2.c index 85c48b38e3db..36ab897b8dc0 100644 --- a/sound/soc/codecs/wcd-mbhc-v2.c +++ b/sound/soc/codecs/wcd-mbhc-v2.c @@ -2023,6 +2023,12 @@ static void wcd_mbhc_moisture_config(struct wcd_mbhc *mbhc) if (mbhc->mbhc_cfg->moist_cfg.m_vref_ctl == V_OFF) return; + /* Donot enable moisture detection if jack type is NC */ + if (!mbhc->hphl_swh) { + pr_debug("%s: disable moisture detection for NC\n", __func__); + return; + } + WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_MOISTURE_VREF, mbhc->mbhc_cfg->moist_cfg.m_vref_ctl); if (mbhc->mbhc_cb->hph_pull_up_control) diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c index 8c7ab5a8f358..7ba7ec93b779 100755 --- a/sound/soc/codecs/wcd9335.c +++ b/sound/soc/codecs/wcd9335.c @@ -5429,6 +5429,48 @@ out: return ret; } +static u32 tasha_get_dmic_sample_rate(struct snd_soc_codec *codec, + unsigned int dmic, struct wcd9xxx_pdata *pdata) +{ + u8 tx_stream_fs; + u8 adc_mux_index = 0, adc_mux_sel = 0; + bool dec_found = false; + u16 adc_mux_ctl_reg, tx_fs_reg; + u32 dmic_fs; + + while (dec_found == 0 && adc_mux_index < WCD9335_MAX_VALID_ADC_MUX) { + if (adc_mux_index < 4) { + adc_mux_ctl_reg = WCD9335_CDC_TX_INP_MUX_ADC_MUX0_CFG0 + + (adc_mux_index * 2); + adc_mux_sel = ((snd_soc_read(codec, adc_mux_ctl_reg) & + 0x78) >> 3) - 1; + } else if (adc_mux_index < 9) { + adc_mux_ctl_reg = WCD9335_CDC_TX_INP_MUX_ADC_MUX4_CFG0 + + ((adc_mux_index - 4) * 1); + adc_mux_sel = ((snd_soc_read(codec, adc_mux_ctl_reg) & + 0x38) >> 3) - 1; + } else if (adc_mux_index == 9) { + ++adc_mux_index; + continue; + } + if (adc_mux_sel == dmic) + dec_found = true; + else + ++adc_mux_index; + } + + if (dec_found == true && adc_mux_index <= 8) { + tx_fs_reg = WCD9335_CDC_TX0_TX_PATH_CTL + (16 * adc_mux_index); + tx_stream_fs = snd_soc_read(codec, tx_fs_reg) & 0x0F; + dmic_fs = tx_stream_fs <= 4 ? WCD9XXX_DMIC_SAMPLE_RATE_2P4MHZ : + WCD9XXX_DMIC_SAMPLE_RATE_4P8MHZ; + } else { + dmic_fs = pdata->dmic_sample_rate; + } + + return dmic_fs; +} + static u8 tasha_get_dmic_clk_val(struct snd_soc_codec *codec, u32 mclk_rate, u32 dmic_clk_rate) { @@ -5512,6 +5554,7 @@ static int tasha_codec_enable_dmic(struct snd_soc_dapm_widget *w, s32 *dmic_clk_cnt; u8 dmic_rate_val, dmic_rate_shift = 1; unsigned int dmic; + u32 dmic_sample_rate; int ret; char *wname; @@ -5554,10 +5597,12 @@ static int tasha_codec_enable_dmic(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: + dmic_sample_rate = tasha_get_dmic_sample_rate(codec, dmic, + pdata); dmic_rate_val = tasha_get_dmic_clk_val(codec, pdata->mclk_rate, - pdata->dmic_sample_rate); + dmic_sample_rate); (*dmic_clk_cnt)++; if (*dmic_clk_cnt == 1) { diff --git a/sound/soc/msm/qdsp6v2/msm-compress-q6-v2.c b/sound/soc/msm/qdsp6v2/msm-compress-q6-v2.c index a7ed5381e690..00519a454fb4 100755 --- a/sound/soc/msm/qdsp6v2/msm-compress-q6-v2.c +++ b/sound/soc/msm/qdsp6v2/msm-compress-q6-v2.c @@ -1533,8 +1533,15 @@ static int msm_compr_trigger(struct snd_compr_stream *cstream, int cmd) } if (atomic_read(&prtd->eos)) { pr_debug("%s: interrupt eos wait queues", __func__); - prtd->cmd_interrupt = 1; - wake_up(&prtd->eos_wait); + /* + * Gapless playback does not wait for eos, do not set + * cmd_int and do not wake up eos_wait during gapless + * transition + */ + if (!prtd->gapless_state.gapless_transition) { + prtd->cmd_interrupt = 1; + wake_up(&prtd->eos_wait); + } atomic_set(&prtd->eos, 0); } if (atomic_read(&prtd->drain)) { |
