From 1d86f7ae7bc6f368ac18ae0c7e015ebae03723d3 Mon Sep 17 00:00:00 2001 From: TheKit Date: Sun, 20 Mar 2022 00:13:44 +0100 Subject: [PATCH] Import Pro1-X kernel source code --- Makefile | 26 +- arch/Kconfig | 13 +- .../vendor/msm8937_32go-perf_defconfig | 655 ++ .../arm/configs/vendor/msm8937_32go_defconfig | 711 ++ arch/arm/mach-qcom/Kconfig | 58 + arch/arm/mach-qcom/Makefile | 6 + arch/arm/mach-qcom/board-bengal.c | 2 +- arch/arm/mach-qcom/board-msm8937.c | 25 + arch/arm/mach-qcom/board-msm8953.c | 25 + arch/arm/mach-qcom/board-qm215.c | 25 + arch/arm/mach-qcom/board-scuba.c | 1 - arch/arm/mach-qcom/board-sdm429.c | 25 + arch/arm/mach-qcom/board-sdm439.c | 25 + arch/arm/mach-qcom/board-sdm450.c | 25 + arch/arm64/Kconfig | 10 - arch/arm64/Kconfig.platforms | 20 + arch/arm64/Makefile | 15 - arch/arm64/boot/Makefile | 10 +- arch/arm64/boot/dts/Makefile | 1 + .../configs/vendor/bengal-perf_defconfig | 130 +- arch/arm64/configs/vendor/bengal_defconfig | 19 +- arch/arm64/configs/vendor/kona-perf_defconfig | 7 +- arch/arm64/configs/vendor/kona_defconfig | 5 + arch/arm64/include/asm/atomic.h | 24 - arch/arm64/include/asm/atomic_ll_sc.h | 50 - arch/arm64/include/asm/atomic_lse.h | 151 +- arch/arm64/include/asm/brk-imm.h | 2 - arch/arm64/include/asm/debug-monitors.h | 18 +- arch/arm64/kernel/Makefile | 1 - arch/arm64/kernel/debug-monitors.c | 85 +- arch/arm64/kernel/kgdb.c | 22 +- arch/arm64/kernel/probes/uprobes.c | 7 +- arch/arm64/kernel/traps.c | 48 +- arch/arm64/kernel/vmlinux.lds.S | 8 + arch/arm64/lib/atomic_ll_sc.c | 12 - arch/arm64/lib/copy_template.S | 2 - arch/arm64/lib/memcmp.S | 341 +- arch/arm64/lib/memmove.S | 2 - arch/arm64/mm/init.c | 5 +- block/Makefile | 4 +- drivers/base/firmware_loader/main.c | 7 +- drivers/base/power/main.c | 3 + drivers/bluetooth/bluetooth-power.c | 3 +- drivers/bluetooth/btfm_slim_slave.h | 5 +- drivers/bus/mhi/core/mhi_pm.c | 9 + drivers/char/Kconfig | 21 + drivers/char/Makefile | 2 + drivers/char/adsprpc.c | 130 +- drivers/char/diag/diag_masks.c | 16 +- drivers/char/diag/diag_usb.c | 2 + drivers/char/diag/diagfwd_peripheral.c | 23 +- drivers/char/diag/diagfwd_rpmsg.c | 393 +- drivers/char/diag/diagfwd_rpmsg.h | 5 +- drivers/char/diag/diagfwd_socket.c | 23 +- drivers/char/diag/diagfwd_socket.h | 4 +- drivers/char/diag/diagmem.c | 8 +- drivers/char/msm_smd_pkt.c | 977 +++ drivers/char/random.c | 6 +- drivers/clk/qcom/Kconfig | 24 + drivers/clk/qcom/Makefile | 3 + drivers/clk/qcom/apcs-msm8916.c | 10 +- drivers/clk/qcom/clk-cpu-sdm.c | 1009 +++ drivers/clk/qcom/clk-debug.c | 26 +- drivers/clk/qcom/clk-pll.c | 63 +- drivers/clk/qcom/clk-pll.h | 3 +- drivers/clk/qcom/clk-regmap-mux-div.c | 44 +- drivers/clk/qcom/clk-regmap-mux-div.h | 21 +- drivers/clk/qcom/clk-smd-rpm.c | 124 +- drivers/clk/qcom/debugcc-sdm429w.c | 385 ++ drivers/clk/qcom/gcc-sdm429w.c | 4461 +++++++++++++ drivers/clk/qcom/gdsc-debug.h | 11 + drivers/clk/qcom/gdsc-regulator.c | 34 +- drivers/clk/qcom/mdss/Makefile | 4 + .../clk/qcom/mdss/mdss-dsi-pll-12nm-util.c | 979 +++ drivers/clk/qcom/mdss/mdss-dsi-pll-12nm.c | 709 ++ drivers/clk/qcom/mdss/mdss-dsi-pll-12nm.h | 113 + drivers/clk/qcom/mdss/mdss-dsi-pll-28lpm.c | 557 ++ .../clk/qcom/mdss/mdss-dsi-pll-28nm-util.c | 665 ++ drivers/clk/qcom/mdss/mdss-dsi-pll-28nm.h | 73 + drivers/clk/qcom/mdss/mdss-dsi-pll.h | 6 +- drivers/clk/qcom/mdss/mdss-pll.c | 14 +- drivers/clk/qcom/mdss/mdss-pll.h | 6 +- drivers/clk/qcom/vdd-level-cpu.h | 27 + drivers/clk/qcom/vdd-level-sdm429w.h | 31 + drivers/cpufreq/cpu-boost.c | 134 +- drivers/cpufreq/cpufreq.c | 3 - drivers/cpufreq/qcom-cpufreq-hw.c | 1 - drivers/cpuidle/cpuidle.c | 16 +- drivers/cpuidle/lpm-levels.c | 84 + drivers/crypto/msm/ice.c | 37 +- drivers/crypto/msm/qce50.c | 12 +- drivers/devfreq/devfreq.c | 4 +- drivers/dma-buf/sync_file.c | 99 +- drivers/gpio/gpio-pca953x.c | 15 +- drivers/gpu/msm/adreno.c | 189 +- drivers/gpu/msm/adreno.h | 13 +- drivers/gpu/msm/adreno_a3xx.c | 12 +- drivers/gpu/msm/adreno_a5xx.c | 13 +- drivers/gpu/msm/adreno_a5xx.h | 4 +- drivers/gpu/msm/adreno_a5xx_preempt.c | 8 +- drivers/gpu/msm/adreno_a6xx.c | 11 +- drivers/gpu/msm/adreno_a6xx_gmu.c | 6 +- drivers/gpu/msm/adreno_a6xx_preempt.c | 22 +- drivers/gpu/msm/adreno_debugfs.c | 5 + drivers/gpu/msm/adreno_dispatch.c | 6 + drivers/gpu/msm/adreno_ioctl.c | 4 +- drivers/gpu/msm/adreno_pm4types.h | 4 +- drivers/gpu/msm/adreno_ringbuffer.c | 54 +- drivers/gpu/msm/adreno_ringbuffer.h | 5 +- drivers/gpu/msm/adreno_sysfs.c | 2 + drivers/gpu/msm/kgsl.c | 21 +- drivers/gpu/msm/kgsl.h | 17 +- drivers/gpu/msm/kgsl_drawobj.c | 38 +- drivers/gpu/msm/kgsl_drawobj.h | 12 + drivers/gpu/msm/kgsl_gmu.c | 2 +- drivers/gpu/msm/kgsl_ioctl.c | 6 - drivers/gpu/msm/kgsl_iommu.c | 5 +- drivers/gpu/msm/kgsl_pwrctrl.c | 126 +- drivers/gpu/msm/kgsl_pwrctrl.h | 12 +- drivers/gpu/msm/kgsl_pwrscale.c | 231 +- drivers/gpu/msm/kgsl_pwrscale.h | 28 +- drivers/gpu/msm/kgsl_reclaim.c | 18 +- drivers/gpu/msm/kgsl_rgmu.c | 2 +- drivers/gpu/msm/kgsl_sync.c | 50 +- drivers/gpu/msm/kgsl_sync.h | 6 +- drivers/gpu/msm/kgsl_trace.h | 72 + drivers/hwtracing/coresight/coresight-cti.c | 19 +- drivers/hwtracing/coresight/coresight.c | 3 +- drivers/iio/adc/qcom-rradc.c | 134 +- drivers/iio/proximity/Kconfig | 2 + drivers/iio/proximity/Makefile | 2 + drivers/iio/proximity/inv_ch101/Kconfig | 17 + drivers/iio/proximity/inv_ch101/Makefile | 26 + .../iio/proximity/inv_ch101/ch101_client.h | 88 + drivers/iio/proximity/inv_ch101/ch101_core.c | 1198 ++++ drivers/iio/proximity/inv_ch101/ch101_data.h | 45 + drivers/iio/proximity/inv_ch101/ch101_i2c.c | 460 ++ drivers/iio/proximity/inv_ch101/ch101_reg.h | 47 + drivers/iio/proximity/inv_ch101/ch101_sysfs.c | 222 + drivers/iio/proximity/inv_ch101/ch101_sysfs.h | 20 + drivers/iio/proximity/inv_ch101/src/ch101.h | 48 + .../iio/proximity/inv_ch101/src/ch101_gpr.c | 75 + .../iio/proximity/inv_ch101/src/ch101_gpr.h | 71 + .../proximity/inv_ch101/src/ch101_gpr_fw.c | 93 + .../proximity/inv_ch101/src/ch101_gpr_open.c | 90 + .../proximity/inv_ch101/src/ch101_gpr_open.h | 71 + .../inv_ch101/src/ch101_gpr_open_fw.c | 33 + .../inv_ch101/src/ch101_gpr_sr_open.c | 87 + .../inv_ch101/src/ch101_gpr_sr_open.h | 72 + .../inv_ch101/src/ch101_gpr_sr_open_fw.c | 32 + drivers/iio/proximity/inv_ch101/src/ch201.h | 46 + .../iio/proximity/inv_ch101/src/ch201_gprmt.h | 80 + .../proximity/inv_ch101/src/ch201_gprmt_fw.c | 57 + drivers/iio/proximity/inv_ch101/src/ch_api.c | 452 ++ .../iio/proximity/inv_ch101/src/ch_common.c | 797 +++ .../iio/proximity/inv_ch101/src/ch_common.h | 149 + .../iio/proximity/inv_ch101/src/ch_driver.c | 1461 +++++ .../iio/proximity/inv_ch101/src/ch_driver.h | 668 ++ .../iio/proximity/inv_ch101/src/chbsp_dummy.c | 53 + .../iio/proximity/inv_ch101/src/chbsp_init.c | 864 +++ .../iio/proximity/inv_ch101/src/chbsp_init.h | 70 + .../inv_ch101/src/chirp_board_config.h | 34 + .../iio/proximity/inv_ch101/src/chirp_bsp.h | 1022 +++ .../iio/proximity/inv_ch101/src/chirp_hal.c | 272 + .../iio/proximity/inv_ch101/src/chirp_hal.h | 43 + drivers/iio/proximity/inv_ch101/src/i2c_hal.c | 258 + drivers/iio/proximity/inv_ch101/src/i2c_hal.h | 66 + .../iio/proximity/inv_ch101/src/idlprotocol.h | 259 + .../iio/proximity/inv_ch101/src/init_driver.c | 1291 ++++ .../iio/proximity/inv_ch101/src/init_driver.h | 69 + .../iio/proximity/inv_ch101/src/soniclib.h | 1214 ++++ drivers/iio/proximity/inv_ch101/src/system.h | 39 + drivers/iio/temperature/Kconfig | 15 + drivers/iio/temperature/Makefile | 1 + drivers/iio/temperature/tdk_thermistor.c | 438 ++ drivers/input/Kconfig | 2 + drivers/input/Makefile | 1 + drivers/input/fingerprint/Kconfig | 13 + drivers/input/fingerprint/Makefile | 15 + drivers/input/fingerprint/ioctl_cmd.h | 64 + drivers/input/fingerprint/madev.c | 858 +++ drivers/input/fingerprint/madev.h | 228 + drivers/input/fingerprint/qcom-settings.c | 546 ++ drivers/input/fingerprint/qcom-settings.h | 125 + drivers/input/keyboard/Kconfig | 2 + drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/aw9523/Kconfig | 8 + drivers/input/keyboard/aw9523/Makefile | 1 + drivers/input/keyboard/aw9523/aw9523b.c | 2356 +++++++ drivers/input/keyboard/aw9523/aw9523b.h | 69 + drivers/input/keyboard/gpio_keys.c | 63 +- drivers/input/misc/qpnp-power-on.c | 127 - drivers/input/touchscreen/Kconfig | 38 +- drivers/input/touchscreen/Makefile | 5 +- .../focaltech_touch/focaltech_common.h | 11 +- .../touchscreen/gt1x_v1_6_revised/Kconfig | 220 + .../touchscreen/gt1x_v1_6_revised/Makefile | 5 + ...ting_Guide_for_Android_20150710_Rev.02.pdf | Bin 0 -> 355764 bytes .../docs/GT1x-dts-bindings.txt | 74 + ...for-Goodix-TP-in-pinctrl-dts-reference.txt | 129 + .../gt1x_v1_6_revised/docs/RevisionLog.txt | 44 + .../touchscreen/gt1x_v1_6_revised/gt1x.c | 1090 ++++ .../gt1x_v1_6_revised/gt1x_extents.c | 1005 +++ .../gt1x_v1_6_revised/gt1x_generic.c | 2612 ++++++++ .../gt1x_v1_6_revised/gt1x_generic.h | 532 ++ .../gt1x_v1_6_revised/gt1x_tools.c | 468 ++ .../gt1x_v1_6_revised/gt1x_update.c | 1494 +++++ .../input/touchscreen/synaptics_tcm/Kconfig | 2 +- drivers/iommu/arm-smmu.c | 55 +- drivers/iommu/io-pgtable-msm-secure.c | 3 +- drivers/iommu/iommu.c | 23 + drivers/irqchip/Makefile | 2 +- drivers/irqchip/qcom-mpm-msm8937.c | 15 + drivers/irqchip/qcom-mpm-msm8953.c | 16 + drivers/irqchip/qcom-mpm.c | 10 +- drivers/leds/Kconfig | 10 + drivers/leds/Makefile | 1 + drivers/leds/leds-qpnp-vibrator.c | 527 ++ drivers/md/dm-verity-target.c | 11 +- .../msm/dvb/demux/mpq_dmx_plugin_common.c | 26 +- drivers/media/platform/msm/npu/npu_host_ipc.c | 21 +- drivers/media/platform/msm/npu/npu_mgr.c | 3 +- .../msm/sde/rotator/sde_rotator_base.c | 22 +- .../msm/sde/rotator/sde_rotator_base.h | 2 + .../msm/sde/rotator/sde_rotator_hwio.h | 7 +- .../msm/sde/rotator/sde_rotator_smmu.c | 4 +- .../platform/msm/vidc_3x/msm_v4l2_vidc.c | 4 +- .../platform/msm/vidc_3x/msm_vidc_debug.c | 4 +- drivers/media/radio/Kconfig | 21 + drivers/media/radio/Makefile | 2 + drivers/media/radio/radio-iris-transport.c | 262 + drivers/media/radio/radio-iris.c | 5694 ++++++++++++++++ drivers/media/rc/Kconfig | 9 - drivers/media/rc/ir-spi.c | 328 +- drivers/media/rc/lirc_dev.c | 1179 ++-- drivers/media/rc/rc-core-priv.h | 3 - drivers/media/rc/rc-main.c | 3 - drivers/misc/Kconfig | 20 +- drivers/misc/Makefile | 9 +- drivers/misc/aw862xx_haptic/Kconfig | 16 + drivers/misc/aw862xx_haptic/Makefile | 1 + drivers/misc/aw862xx_haptic/aw8622x.c | 3977 ++++++++++++ drivers/misc/aw862xx_haptic/aw8622x.h | 304 + drivers/misc/aw862xx_haptic/aw8622x_reg.h | 856 +++ drivers/misc/aw862xx_haptic/aw8624.c | 4438 +++++++++++++ drivers/misc/aw862xx_haptic/aw8624.h | 345 + drivers/misc/aw862xx_haptic/aw8624_reg.h | 558 ++ drivers/misc/aw862xx_haptic/haptic.c | 537 ++ drivers/misc/aw862xx_haptic/haptic.h | 93 + drivers/misc/fpr_FingerprintCard/Kconfig | 17 +- drivers/misc/fpr_FingerprintCard/Makefile | 3 +- .../fpc1020_platform_tee.c | 442 +- drivers/misc/qrc/Kconfig | 25 + drivers/misc/qrc/Makefile | 9 + drivers/misc/qrc/qrc_core.c | 315 + drivers/misc/qrc/qrc_core.h | 140 + drivers/misc/qrc/qrc_uart.c | 334 + drivers/misc/qseecom.c | 158 +- drivers/misc/wigig_sensing.c | 18 + drivers/mmc/core/core.c | 3 +- drivers/net/ethernet/Kconfig | 1 + drivers/net/ethernet/Makefile | 1 + drivers/net/ethernet/qcom/Kconfig | 19 + drivers/net/ethernet/qcom/Makefile | 5 + drivers/net/ethernet/qcom/msm_rmnet_bam.c | 1002 +++ drivers/net/wireless/cnss2/pci.c | 50 +- .../wireless/cnss_prealloc/cnss_prealloc.c | 16 + drivers/nfc/nq-nci.c | 927 +-- drivers/nfc/nq-nci.h | 2 +- drivers/of/of_batterydata.c | 23 +- drivers/pci/controller/pci-msm.c | 57 +- drivers/perf/qcom_llcc_pmu.c | 2 +- drivers/pinctrl/qcom/Kconfig | 20 + drivers/pinctrl/qcom/Makefile | 2 + drivers/pinctrl/qcom/pinctrl-msm.c | 9 + drivers/pinctrl/qcom/pinctrl-msm8917.c | 1466 +++++ drivers/pinctrl/qcom/pinctrl-msm8937.c | 1471 +++++ drivers/platform/Kconfig | 4 - drivers/platform/msm/Makefile | 1 + drivers/platform/msm/gsi/gsi.c | 52 +- .../platform/msm/ipa/ipa_clients/ipa_wdi3.c | 39 +- drivers/platform/msm/ipa/ipa_common_i.h | 19 +- .../platform/msm/ipa/ipa_v2/ipa_qmi_service.c | 54 +- drivers/platform/msm/ipa/ipa_v3/ipa.c | 9 + drivers/platform/msm/ipa/ipa_v3/ipa_debugfs.c | 8 +- drivers/platform/msm/ipa/ipa_v3/ipa_dp.c | 53 +- .../platform/msm/ipa/ipa_v3/ipa_hw_stats.c | 2 +- drivers/platform/msm/ipa/ipa_v3/ipa_i.h | 2 + drivers/platform/msm/ipa/ipa_v3/ipa_pm.c | 9 +- drivers/platform/msm/ipa/ipa_v3/ipa_utils.c | 30 +- .../msm/ipa/ipa_v3/ipahal/ipahal_hw_stats.c | 2 +- drivers/platform/msm/qcom-geni-se.c | 10 +- drivers/platform/msm/qpnp-revid.c | 8 +- drivers/platform/msm/usb_bam.c | 367 +- drivers/power/reset/msm-poweroff.c | 20 +- drivers/power/supply/power_supply_core.c | 3 +- drivers/power/supply/power_supply_sysfs.c | 3 +- drivers/power/supply/qcom/battery.c | 6 +- drivers/power/supply/qcom/fg-core.h | 3 +- drivers/power/supply/qcom/fg-memif.c | 60 +- drivers/power/supply/qcom/fg-reg.h | 16 +- drivers/power/supply/qcom/pmic-voter.c | 10 +- drivers/power/supply/qcom/qg-core.h | 2 - drivers/power/supply/qcom/qg-soc.c | 3 +- drivers/power/supply/qcom/qg-util.c | 3 +- drivers/power/supply/qcom/qpnp-fg-gen3.c | 109 +- drivers/power/supply/qcom/qpnp-qg.c | 25 +- drivers/power/supply/qcom/qpnp-smb2.c | 7 + drivers/power/supply/qcom/qpnp-smb5.c | 40 +- drivers/power/supply/qcom/smb-lib.c | 74 + drivers/power/supply/qcom/smb-lib.h | 4 + drivers/power/supply/qcom/smb-reg.h | 7 + drivers/power/supply/qcom/smb1355-charger.c | 4 +- drivers/power/supply/qcom/smb5-lib.c | 238 +- drivers/power/supply/qcom/smb5-lib.h | 10 +- drivers/power/supply/qcom/smb5-reg.h | 18 +- drivers/power/supply/qcom/step-chg-jeita.c | 129 +- drivers/regulator/Kconfig | 10 + drivers/regulator/Makefile | 1 + drivers/regulator/cpr-regulator.c | 5756 +++++++++++++++++ drivers/regulator/qpnp-lcdb-regulator.c | 18 +- drivers/rpmsg/qcom_smd.c | 213 +- drivers/rpmsg/rpm-smd.c | 24 +- drivers/rtc/rtc-pm8xxx.c | 13 +- drivers/scsi/ufs/ufshcd.c | 11 +- drivers/soc/qcom/Kconfig | 9 +- drivers/soc/qcom/Makefile | 2 +- drivers/soc/qcom/bam_dmux.c | 200 +- drivers/soc/qcom/bam_dmux_private.h | 11 +- drivers/soc/qcom/icnss2/main.c | 34 +- drivers/soc/qcom/msm_bus/msm_bus_arb_adhoc.c | 9 +- drivers/soc/qcom/msm_bus/msm_bus_arb_rpmh.c | 19 +- drivers/soc/qcom/qdss_bridge.c | 20 +- drivers/soc/qcom/qtee_shmbridge.c | 5 +- drivers/soc/qcom/smcinvoke.c | 5 +- drivers/soc/qcom/smp2p.c | 18 +- drivers/soc/qcom/smsm.c | 13 +- drivers/soc/qcom/socinfo.c | 22 + drivers/soc/qcom/subsys-pil-tz.c | 128 +- drivers/soc/qcom/wcnss/Kconfig | 40 + drivers/soc/qcom/wcnss/Makefile | 6 + drivers/soc/qcom/wcnss/wcnss_vreg.c | 826 +++ drivers/soc/qcom/wcnss/wcnss_wlan.c | 3961 ++++++++++++ drivers/spmi/spmi-pmic-arb-debug.c | 5 +- drivers/thermal/cpu_cooling.c | 81 +- drivers/thermal/thermal_core.c | 286 +- drivers/thermal/thermal_sysfs.c | 2 +- drivers/tty/serial/msm_geni_serial.c | 181 +- drivers/uio/msm_sharedmem/msm_sharedmem.c | 7 +- drivers/usb/core/hub.c | 8 - drivers/usb/dwc3/core.c | 3 +- drivers/usb/dwc3/debug_ipc.c | 38 +- drivers/usb/dwc3/gadget.c | 41 +- drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/ci13xxx_msm.c | 555 ++ drivers/usb/gadget/ci13xxx_udc.c | 4115 ++++++++++++ drivers/usb/gadget/ci13xxx_udc.h | 263 + drivers/usb/gadget/function/f_diag.c | 12 +- drivers/usb/gadget/function/f_mass_storage.c | 5 - drivers/usb/gadget/udc/Kconfig | 15 + drivers/usb/host/Kconfig | 12 + drivers/usb/host/Makefile | 1 + drivers/usb/host/ehci-msm.c | 268 + drivers/usb/misc/ssusb-redriver-nb7vpq904m.c | 30 +- drivers/usb/pd/policy_engine.c | 65 +- drivers/usb/phy/Kconfig | 14 + drivers/usb/phy/Makefile | 1 + drivers/usb/phy/phy-msm-qusb-v2.c | 8 +- drivers/usb/phy/phy-msm-usb.c | 5049 +++++++++++++++ drivers/video/backlight/Makefile | 1 - drivers/video/backlight/backlight.c | 13 +- drivers/video/backlight/qcom-spmi-wled.c | 54 +- drivers/video/fbdev/msm/Makefile | 1 + drivers/video/fbdev/msm/mdss_dsi.c | 26 +- drivers/video/fbdev/msm/mdss_dsi.h | 8 +- drivers/video/fbdev/msm/mdss_dsi_clk.c | 380 +- drivers/video/fbdev/msm/mdss_dsi_clk.h | 48 +- drivers/video/fbdev/msm/mdss_dsi_host.c | 137 +- drivers/video/fbdev/msm/mdss_dsi_panel.c | 29 +- drivers/video/fbdev/msm/mdss_dsi_phy.h | 53 +- drivers/video/fbdev/msm/mdss_dsi_phy_12nm.c | 6 +- drivers/video/fbdev/msm/mdss_dsi_status.c | 6 +- drivers/video/fbdev/msm/mdss_mdp_intf_cmd.c | 11 +- drivers/video/fbdev/msm/mdss_mdp_intf_video.c | 11 +- drivers/video/fbdev/msm/mdss_panel.h | 4 +- drivers/video/fbdev/msm/mdss_rgb.c | 6 +- drivers/video/fbdev/msm/msm_mdss_io_8974.c | 140 +- fs/Kconfig | 1 - fs/Makefile | 1 - fs/crypto/Kconfig | 9 + fs/crypto/keysetup_v1.c | 23 +- fs/namespace.c | 2 +- fs/proc/task_mmu.c | 6 +- fs/pstore/inode.c | 3 +- gen_headers_arm.bp | 28 - gen_headers_arm64.bp | 28 - include/crypto/ice.h | 2 +- include/drm/drm_device.h | 2 - include/dt-bindings/clock/mdss-12nm-pll-clk.h | 44 + include/dt-bindings/clock/qcom,cpu-sdm.h | 13 + include/dt-bindings/clock/qcom,gcc-sdm429w.h | 206 + include/dt-bindings/clock/qcom,rpmcc.h | 136 +- include/dt-bindings/iio/qcom,spmi-vadc.h | 5 +- include/linux/audit.h | 4 - include/linux/backlight.h | 9 - include/linux/blkdev.h | 6 +- include/linux/clk/qcom.h | 8 +- include/linux/coresight.h | 6 +- include/linux/cpu_cooling.h | 2 - include/linux/cpufreq.h | 2 - include/linux/diagchar.h | 6 +- include/linux/init.h | 2 + include/linux/input/qpnp-power-on.h | 18 - include/linux/kernel.h | 4 - include/linux/lsm_audit.h | 8 - include/linux/mm.h | 2 +- include/linux/msm_pcie.h | 1 + include/linux/msm_smd_pkt.h | 24 + .../linux/platform_data/qcom_wcnss_device.h | 12 + include/linux/pm_wakeup.h | 19 - include/linux/power_supply.h | 3 +- include/linux/qpnp/qpnp-revid.h | 3 - include/linux/regulator/cpr-regulator.h | 24 + include/linux/sync_file.h | 9 + include/linux/usb/msm_hsusb.h | 346 + include/linux/usb/msm_hsusb_hw.h | 170 + include/linux/usb/phy.h | 5 + include/linux/usb_bam.h | 30 +- include/linux/wcnss_wlan.h | 196 + include/linux/workqueue.h | 4 +- include/media/radio-iris.h | 307 + include/media/rc-core.h | 15 - include/net/cfg80211.h | 26 + include/net/pkt_sched.h | 8 +- include/soc/qcom/mpm.h | 5 +- include/soc/qcom/msm_tz_smmu.h | 18 +- include/soc/qcom/socinfo.h | 14 +- include/uapi/linux/input-event-codes.h | 2 + init/Kconfig | 2 + kernel/Makefile | 2 +- kernel/power/Kconfig | 5 - kernel/power/process.c | 6 +- kernel/power/wakelock.c | 2 +- kernel/rcu/tree.c | 4 +- kernel/sched/core_ctl.c | 1 - kernel/sched/fair.c | 2 +- kernel/sysctl.c | 14 - kernel/trace/Kconfig | 14 - mm/cma.c | 2 +- net/core/dev.c | 9 +- net/netfilter/xt_qtaguid.c | 11 +- net/wireless/nl80211.c | 62 +- net/wireless/reg.c | 1 + net/wireless/reg.h | 3 - net/wireless/util.c | 8 +- scripts/Makefile.lib | 12 +- scripts/dtc/Makefile | 7 - scripts/dtc/Makefile.dtc | 5 - scripts/dtc/checks.c | 286 +- scripts/dtc/data.c | 21 +- scripts/dtc/dtc-lexer.l | 21 +- scripts/dtc/dtc-parser.y | 85 +- scripts/dtc/dtc.c | 39 +- scripts/dtc/dtc.h | 44 +- scripts/dtc/flattree.c | 25 +- scripts/dtc/fstree.c | 22 +- scripts/dtc/libfdt/Makefile.libfdt | 7 - scripts/dtc/libfdt/fdt.c | 128 +- scripts/dtc/libfdt/fdt.h | 47 +- scripts/dtc/libfdt/fdt_addresses.c | 135 +- scripts/dtc/libfdt/fdt_empty_tree.c | 47 +- scripts/dtc/libfdt/fdt_overlay.c | 63 +- scripts/dtc/libfdt/fdt_ro.c | 296 +- scripts/dtc/libfdt/fdt_rw.c | 97 +- scripts/dtc/libfdt/fdt_strerror.c | 47 +- scripts/dtc/libfdt/fdt_sw.c | 234 +- scripts/dtc/libfdt/fdt_wip.c | 47 +- scripts/dtc/libfdt/libfdt.h | 289 +- scripts/dtc/libfdt/libfdt_env.h | 49 +- scripts/dtc/libfdt/libfdt_internal.h | 52 +- scripts/dtc/livetree.c | 76 +- scripts/dtc/srcpos.c | 169 +- scripts/dtc/srcpos.h | 30 +- scripts/dtc/treesource.c | 287 +- scripts/dtc/update-dtc-source.sh | 2 +- scripts/dtc/util.c | 101 +- scripts/dtc/util.h | 42 +- scripts/dtc/version_gen.h | 2 +- scripts/setlocalversion | 9 + security/Kconfig.hardening | 77 +- security/selinux/Kconfig | 2 +- security/selinux/hooks.c | 2 +- sound/soc/soc-pcm.c | 2 - .../camera/drivers/cam_cdm/cam_cdm_hw_core.c | 5 + .../cam_ope/ope_hw_mgr/cam_ope_hw_mgr.c | 11 +- .../cam_ope/ope_hw_mgr/cam_ope_hw_mgr.h | 1 + .../ope_hw_mgr/ope_hw/bus_rd/ope_bus_rd.c | 2 +- .../drivers/cam_req_mgr/cam_req_mgr_core.c | 77 +- .../drivers/cam_req_mgr/cam_req_mgr_core.h | 6 + .../drivers/cam_req_mgr/cam_req_mgr_dev.c | 2 +- .../cam_flash/cam_flash_core.c | 42 +- .../cam_sensor/cam_sensor_core.c | 8 +- .../cam_sensor/cam_sensor_dev.h | 3 + .../cam_sensor/cam_sensor_soc.c | 8 + .../cam_sensor_utils/cam_sensor_cmn_header.h | 3 + .../cam_sensor_utils/cam_sensor_util.c | 58 +- .../camera/drivers/cam_smmu/cam_smmu_api.c | 5 +- .../drivers/cam_sync/cam_sync_private.h | 2 +- techpack/camera/drivers/cam_utils/Makefile | 5 +- .../camera/drivers/cam_utils/cam_debug_util.h | 82 +- techpack/camera/include/uapi/media/cam_ope.h | 4 +- techpack/display/config/konadisp.conf | 4 +- techpack/display/config/konadispconf.h | 6 +- techpack/display/config/saipdisp.conf | 3 + techpack/display/config/saipdispconf.h | 4 + techpack/display/msm/Makefile | 3 +- techpack/display/msm/dp/dp_debug.c | 4 +- techpack/display/msm/dp/dp_display.c | 13 +- techpack/display/msm/dp/dp_panel.c | 23 +- techpack/display/msm/dp/dp_panel.h | 2 +- techpack/display/msm/dsi/dsi_ctrl.c | 9 +- techpack/display/msm/dsi/dsi_defs.h | 4 - techpack/display/msm/dsi/dsi_display.c | 453 +- techpack/display/msm/dsi/dsi_display.h | 10 + techpack/display/msm/dsi/dsi_drm.c | 57 +- techpack/display/msm/dsi/dsi_panel.c | 190 +- techpack/display/msm/dsi/dsi_panel.h | 19 +- techpack/display/msm/msm_drv.c | 8 +- techpack/display/msm/msm_drv.h | 3 + techpack/display/msm/msm_notifier.c | 4 +- techpack/display/msm/sde/sde_connector.c | 21 +- techpack/display/msm/sde/sde_connector.h | 10 +- techpack/display/msm/sde/sde_crtc.c | 153 +- techpack/display/msm/sde/sde_crtc.h | 28 + techpack/display/msm/sde/sde_encoder.c | 48 +- techpack/display/msm/sde/sde_encoder_phys.h | 2 +- .../display/msm/sde/sde_encoder_phys_cmd.c | 2 +- .../display/msm/sde/sde_encoder_phys_vid.c | 14 +- techpack/display/msm/sde/sde_hw_catalog.c | 2 - techpack/display/msm/sde/sde_hw_catalog.h | 2 - techpack/display/msm/sde/sde_hw_top.c | 9 +- techpack/display/msm/sde/sde_hw_util.c | 3 + techpack/display/msm/sde/sde_kms.c | 106 +- techpack/display/msm/sde/sde_rm.c | 4 +- techpack/display/msm/sde_dbg.c | 94 +- techpack/display/msm/sde_dbg.h | 194 +- techpack/display/msm/sde_dbg_evtlog.c | 48 +- techpack/display/msm/sde_io_util.c | 3 + techpack/video/msm/vidc/hfi_iris2.c | 3 +- techpack/video/msm/vidc/msm_v4l2_vidc.c | 2 +- techpack/video/msm/vidc/msm_venc.c | 20 - techpack/video/msm/vidc/msm_vidc.c | 4 +- .../msm/vidc/msm_vidc_buffer_calculations.c | 94 +- .../msm/vidc/msm_vidc_buffer_calculations.h | 3 +- techpack/video/msm/vidc/msm_vidc_common.c | 19 + techpack/video/msm/vidc/msm_vidc_common.h | 1 + techpack/video/msm/vidc/msm_vidc_debug.c | 4 +- techpack/video/msm/vidc/msm_vidc_internal.h | 4 +- techpack/video/msm/vidc/msm_vidc_platform.c | 2 +- 559 files changed, 90104 insertions(+), 7564 deletions(-) create mode 100644 arch/arm/configs/vendor/msm8937_32go-perf_defconfig create mode 100644 arch/arm/configs/vendor/msm8937_32go_defconfig create mode 100644 arch/arm/mach-qcom/board-msm8937.c create mode 100644 arch/arm/mach-qcom/board-msm8953.c create mode 100644 arch/arm/mach-qcom/board-qm215.c create mode 100644 arch/arm/mach-qcom/board-sdm429.c create mode 100644 arch/arm/mach-qcom/board-sdm439.c create mode 100644 arch/arm/mach-qcom/board-sdm450.c create mode 100644 drivers/char/msm_smd_pkt.c create mode 100644 drivers/clk/qcom/clk-cpu-sdm.c create mode 100644 drivers/clk/qcom/debugcc-sdm429w.c create mode 100644 drivers/clk/qcom/gcc-sdm429w.c create mode 100644 drivers/clk/qcom/gdsc-debug.h create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll-12nm-util.c create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll-12nm.c create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll-12nm.h create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll-28lpm.c create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll-28nm-util.c create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll-28nm.h create mode 100644 drivers/clk/qcom/vdd-level-cpu.h create mode 100644 drivers/clk/qcom/vdd-level-sdm429w.h create mode 100644 drivers/iio/proximity/inv_ch101/Kconfig create mode 100644 drivers/iio/proximity/inv_ch101/Makefile create mode 100644 drivers/iio/proximity/inv_ch101/ch101_client.h create mode 100644 drivers/iio/proximity/inv_ch101/ch101_core.c create mode 100644 drivers/iio/proximity/inv_ch101/ch101_data.h create mode 100644 drivers/iio/proximity/inv_ch101/ch101_i2c.c create mode 100644 drivers/iio/proximity/inv_ch101/ch101_reg.h create mode 100644 drivers/iio/proximity/inv_ch101/ch101_sysfs.c create mode 100644 drivers/iio/proximity/inv_ch101/ch101_sysfs.h create mode 100644 drivers/iio/proximity/inv_ch101/src/ch101.h create mode 100644 drivers/iio/proximity/inv_ch101/src/ch101_gpr.c create mode 100644 drivers/iio/proximity/inv_ch101/src/ch101_gpr.h create mode 100644 drivers/iio/proximity/inv_ch101/src/ch101_gpr_fw.c create mode 100644 drivers/iio/proximity/inv_ch101/src/ch101_gpr_open.c create mode 100644 drivers/iio/proximity/inv_ch101/src/ch101_gpr_open.h create mode 100644 drivers/iio/proximity/inv_ch101/src/ch101_gpr_open_fw.c create mode 100644 drivers/iio/proximity/inv_ch101/src/ch101_gpr_sr_open.c create mode 100644 drivers/iio/proximity/inv_ch101/src/ch101_gpr_sr_open.h create mode 100644 drivers/iio/proximity/inv_ch101/src/ch101_gpr_sr_open_fw.c create mode 100644 drivers/iio/proximity/inv_ch101/src/ch201.h create mode 100644 drivers/iio/proximity/inv_ch101/src/ch201_gprmt.h create mode 100644 drivers/iio/proximity/inv_ch101/src/ch201_gprmt_fw.c create mode 100644 drivers/iio/proximity/inv_ch101/src/ch_api.c create mode 100644 drivers/iio/proximity/inv_ch101/src/ch_common.c create mode 100644 drivers/iio/proximity/inv_ch101/src/ch_common.h create mode 100644 drivers/iio/proximity/inv_ch101/src/ch_driver.c create mode 100644 drivers/iio/proximity/inv_ch101/src/ch_driver.h create mode 100644 drivers/iio/proximity/inv_ch101/src/chbsp_dummy.c create mode 100644 drivers/iio/proximity/inv_ch101/src/chbsp_init.c create mode 100644 drivers/iio/proximity/inv_ch101/src/chbsp_init.h create mode 100644 drivers/iio/proximity/inv_ch101/src/chirp_board_config.h create mode 100644 drivers/iio/proximity/inv_ch101/src/chirp_bsp.h create mode 100644 drivers/iio/proximity/inv_ch101/src/chirp_hal.c create mode 100644 drivers/iio/proximity/inv_ch101/src/chirp_hal.h create mode 100644 drivers/iio/proximity/inv_ch101/src/i2c_hal.c create mode 100644 drivers/iio/proximity/inv_ch101/src/i2c_hal.h create mode 100644 drivers/iio/proximity/inv_ch101/src/idlprotocol.h create mode 100644 drivers/iio/proximity/inv_ch101/src/init_driver.c create mode 100644 drivers/iio/proximity/inv_ch101/src/init_driver.h create mode 100644 drivers/iio/proximity/inv_ch101/src/soniclib.h create mode 100644 drivers/iio/proximity/inv_ch101/src/system.h create mode 100644 drivers/iio/temperature/tdk_thermistor.c create mode 100755 drivers/input/fingerprint/Kconfig create mode 100755 drivers/input/fingerprint/Makefile create mode 100755 drivers/input/fingerprint/ioctl_cmd.h create mode 100755 drivers/input/fingerprint/madev.c create mode 100755 drivers/input/fingerprint/madev.h create mode 100755 drivers/input/fingerprint/qcom-settings.c create mode 100755 drivers/input/fingerprint/qcom-settings.h create mode 100755 drivers/input/keyboard/aw9523/Kconfig create mode 100755 drivers/input/keyboard/aw9523/Makefile create mode 100755 drivers/input/keyboard/aw9523/aw9523b.c create mode 100755 drivers/input/keyboard/aw9523/aw9523b.h create mode 100755 drivers/input/touchscreen/gt1x_v1_6_revised/Kconfig create mode 100755 drivers/input/touchscreen/gt1x_v1_6_revised/Makefile create mode 100755 drivers/input/touchscreen/gt1x_v1_6_revised/docs/GT1X_Driver_Porting_Guide_for_Android_20150710_Rev.02.pdf create mode 100755 drivers/input/touchscreen/gt1x_v1_6_revised/docs/GT1x-dts-bindings.txt create mode 100755 drivers/input/touchscreen/gt1x_v1_6_revised/docs/Pingroup-for-Goodix-TP-in-pinctrl-dts-reference.txt create mode 100755 drivers/input/touchscreen/gt1x_v1_6_revised/docs/RevisionLog.txt create mode 100755 drivers/input/touchscreen/gt1x_v1_6_revised/gt1x.c create mode 100755 drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_extents.c create mode 100755 drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_generic.c create mode 100755 drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_generic.h create mode 100755 drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_tools.c create mode 100755 drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_update.c create mode 100644 drivers/irqchip/qcom-mpm-msm8937.c create mode 100644 drivers/irqchip/qcom-mpm-msm8953.c create mode 100644 drivers/leds/leds-qpnp-vibrator.c create mode 100644 drivers/media/radio/radio-iris-transport.c create mode 100644 drivers/media/radio/radio-iris.c create mode 100644 drivers/misc/aw862xx_haptic/Kconfig create mode 100644 drivers/misc/aw862xx_haptic/Makefile create mode 100644 drivers/misc/aw862xx_haptic/aw8622x.c create mode 100644 drivers/misc/aw862xx_haptic/aw8622x.h create mode 100644 drivers/misc/aw862xx_haptic/aw8622x_reg.h create mode 100644 drivers/misc/aw862xx_haptic/aw8624.c create mode 100644 drivers/misc/aw862xx_haptic/aw8624.h create mode 100644 drivers/misc/aw862xx_haptic/aw8624_reg.h create mode 100644 drivers/misc/aw862xx_haptic/haptic.c create mode 100644 drivers/misc/aw862xx_haptic/haptic.h create mode 100644 drivers/misc/qrc/Kconfig create mode 100644 drivers/misc/qrc/Makefile create mode 100644 drivers/misc/qrc/qrc_core.c create mode 100644 drivers/misc/qrc/qrc_core.h create mode 100644 drivers/misc/qrc/qrc_uart.c create mode 100644 drivers/net/ethernet/qcom/Kconfig create mode 100644 drivers/net/ethernet/qcom/Makefile create mode 100644 drivers/net/ethernet/qcom/msm_rmnet_bam.c create mode 100644 drivers/pinctrl/qcom/pinctrl-msm8917.c create mode 100644 drivers/pinctrl/qcom/pinctrl-msm8937.c create mode 100644 drivers/regulator/cpr-regulator.c create mode 100644 drivers/soc/qcom/wcnss/Kconfig create mode 100644 drivers/soc/qcom/wcnss/Makefile create mode 100644 drivers/soc/qcom/wcnss/wcnss_vreg.c create mode 100644 drivers/soc/qcom/wcnss/wcnss_wlan.c create mode 100644 drivers/usb/gadget/ci13xxx_msm.c create mode 100644 drivers/usb/gadget/ci13xxx_udc.c create mode 100644 drivers/usb/gadget/ci13xxx_udc.h create mode 100644 drivers/usb/host/ehci-msm.c create mode 100644 drivers/usb/phy/phy-msm-usb.c create mode 100644 include/dt-bindings/clock/mdss-12nm-pll-clk.h create mode 100644 include/dt-bindings/clock/qcom,cpu-sdm.h create mode 100644 include/dt-bindings/clock/qcom,gcc-sdm429w.h create mode 100644 include/linux/msm_smd_pkt.h create mode 100644 include/linux/platform_data/qcom_wcnss_device.h create mode 100644 include/linux/regulator/cpr-regulator.h create mode 100644 include/linux/usb/msm_hsusb.h create mode 100644 include/linux/usb/msm_hsusb_hw.h create mode 100644 include/linux/wcnss_wlan.h create mode 100644 include/media/radio-iris.h diff --git a/Makefile b/Makefile index 7552014b6565..e6ffc28de7c4 100644 --- a/Makefile +++ b/Makefile @@ -319,7 +319,7 @@ include scripts/subarch.include # Alternatively CROSS_COMPILE can be set in the environment. # Default value for CROSS_COMPILE is not to prefix executables # Note: Some architectures assign CROSS_COMPILE in their arch/*/Makefile -ARCH := arm64 +ARCH ?= $(SUBARCH) # Architecture as present in compile.h UTS_MACHINE := $(ARCH) @@ -366,7 +366,7 @@ HOSTCC = gcc HOSTCXX = g++ endif KBUILD_HOSTCFLAGS := -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 \ - -fomit-frame-pointer -std=gnu89 -pipe $(HOST_LFS_CFLAGS) \ + -fomit-frame-pointer -std=gnu89 $(HOST_LFS_CFLAGS) \ $(HOSTCFLAGS) KBUILD_HOSTCXXFLAGS := -O2 $(HOST_LFS_CFLAGS) $(HOSTCXXFLAGS) KBUILD_HOSTLDFLAGS := $(HOST_LFS_LDFLAGS) $(HOSTLDFLAGS) @@ -385,7 +385,7 @@ READELF = llvm-readelf OBJSIZE = llvm-size STRIP = llvm-strip else -CC = $(CROSS_COMPILE)gcc +REAL_CC = $(CROSS_COMPILE)gcc LD = $(CROSS_COMPILE)ld AR = $(CROSS_COMPILE)ar NM = $(CROSS_COMPILE)nm @@ -409,7 +409,7 @@ CHECK = sparse # Use the wrapper for the compiler. This wrapper scans for new # warnings and causes the build to stop upon encountering them -# CC = $(PYTHON2) $(srctree)/scripts/gcc-wrapper.py $(REAL_CC) +CC = $(PYTHON) $(srctree)/scripts/gcc-wrapper.py $(REAL_CC) CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \ -Wbitwise -Wno-return-void -Wno-unknown-attribute $(CF) @@ -439,7 +439,7 @@ LINUXINCLUDE := \ $(USERINCLUDE) KBUILD_AFLAGS := -D__ASSEMBLY__ -KBUILD_CFLAGS := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -pipe \ +KBUILD_CFLAGS := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \ -fno-strict-aliasing -fno-common -fshort-wchar \ -Werror-implicit-function-declaration \ -Wno-format-security \ @@ -688,18 +688,11 @@ KBUILD_CFLAGS += $(call cc-disable-warning, format-truncation) KBUILD_CFLAGS += $(call cc-disable-warning, format-overflow) KBUILD_CFLAGS += $(call cc-disable-warning, int-in-bool-context) KBUILD_CFLAGS += $(call cc-disable-warning, address-of-packed-member) -KBUILD_CFLAGS += $(call cc-disable-warning, attribute-alias) -KBUILD_CFLAGS += $(call cc-disable-warning, packed-not-aligned) -KBUILD_CFLAGS += $(call cc-disable-warning, psabi) -KBUILD_CFLAGS += $(call cc-disable-warning, restrict) -KBUILD_CFLAGS += $(call cc-disable-warning, stringop-overflow) -KBUILD_CFLAGS += $(call cc-disable-warning, stringop-truncation) -KBUILD_CFLAGS += $(call cc-disable-warning, zero-length-bounds) ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE KBUILD_CFLAGS += -Os else -KBUILD_CFLAGS += -O3 +KBUILD_CFLAGS += -O2 endif # Tell gcc to never replace conditional load with a non-conditional one @@ -961,6 +954,9 @@ KBUILD_CFLAGS += $(call cc-option,-fmerge-constants) # Make sure -fstack-check isn't enabled (like gentoo apparently did) KBUILD_CFLAGS += $(call cc-option,-fno-stack-check,) +# conserve stack if available +KBUILD_CFLAGS += $(call cc-option,-fconserve-stack) + # disallow errors like 'EXPORT_GPL(foo);' with missing header KBUILD_CFLAGS += $(call cc-option,-Werror=implicit-int) @@ -1349,7 +1345,7 @@ headers_install: __headers $(error Headers not exportable for the $(SRCARCH) architecture)) $(Q)$(MAKE) $(hdr-inst)=include/uapi dst=include $(Q)$(MAKE) $(hdr-inst)=arch/$(SRCARCH)/include/uapi $(hdr-dst) - $(Q)$(MAKE) $(hdr-inst)=techpack/audio/include/uapi dst=techpack/audio/include + $(Q)$(MAKE) $(hdr-inst)=techpack PHONY += headers_check_all headers_check_all: headers_install_all @@ -1359,7 +1355,7 @@ PHONY += headers_check headers_check: headers_install $(Q)$(MAKE) $(hdr-inst)=include/uapi dst=include HDRCHECK=1 $(Q)$(MAKE) $(hdr-inst)=arch/$(SRCARCH)/include/uapi $(hdr-dst) HDRCHECK=1 - $(Q)$(MAKE) $(hdr-inst)=techpack/audio/include/uapi dst=techpack/audio/include HDRCHECK=1 + $(Q)$(MAKE) $(hdr-inst)=techpack HDRCHECK=1 # --------------------------------------------------------------------------- # Kernel selftest diff --git a/arch/Kconfig b/arch/Kconfig index 36a4da64ffac..8a5149f875fe 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -563,7 +563,6 @@ config CFI_CLANG_SHADOW config ARCH_SUPPORTS_SHADOW_CALL_STACK bool - default y help An architecture should select this if it supports Clang's Shadow Call Stack, has asm/scs.h, and implements runtime support for shadow @@ -944,16 +943,6 @@ config STRICT_MODULE_RWX config ARCH_HAS_PHYS_TO_DMA bool -config ARCH_HAS_REFCOUNT_FULL - bool - select ARCH_HAS_REFCOUNT - help - An architecture selects this when the optimized refcount_t - implementation it provides covers all the cases that - CONFIG_REFCOUNT_FULL covers as well, in which case it makes no - sense to even offer CONFIG_REFCOUNT_FULL as a user selectable - option. - config ARCH_HAS_REFCOUNT bool help @@ -967,7 +956,7 @@ config ARCH_HAS_REFCOUNT against bugs in reference counts. config REFCOUNT_FULL - bool "Perform full reference count validation at the expense of speed" if !ARCH_HAS_REFCOUNT_FULL + bool "Perform full reference count validation at the expense of speed" help Enabling this switches the refcounting infrastructure from a fast unchecked atomic_t implementation to a fully state checked diff --git a/arch/arm/configs/vendor/msm8937_32go-perf_defconfig b/arch/arm/configs/vendor/msm8937_32go-perf_defconfig new file mode 100644 index 000000000000..b5070161ecbc --- /dev/null +++ b/arch/arm/configs/vendor/msm8937_32go-perf_defconfig @@ -0,0 +1,655 @@ +CONFIG_LOCALVERSION="-perf" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_AUDIT=y +# CONFIG_AUDITSYSCALL is not set +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_PREEMPT=y +CONFIG_IRQ_TIME_ACCOUNTING=y +CONFIG_SCHED_WALT=y +CONFIG_TASKSTATS=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_PSI=y +CONFIG_RCU_EXPERT=y +CONFIG_RCU_FAST_NO_HZ=y +CONFIG_RCU_NOCB_CPU=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_CPU_MAX_BUF_SHIFT=17 +CONFIG_MEMCG=y +CONFIG_MEMCG_SWAP=y +CONFIG_BLK_CGROUP=y +CONFIG_RT_GROUP_SCHED=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_BPF=y +CONFIG_SCHED_CORE_CTL=y +CONFIG_NAMESPACES=y +# CONFIG_UTS_NS is not set +# CONFIG_PID_NS is not set +CONFIG_SCHED_AUTOGROUP=y +CONFIG_SCHED_TUNE=y +CONFIG_BLK_DEV_INITRD=y +# CONFIG_RD_BZIP2 is not set +# CONFIG_RD_LZMA is not set +# CONFIG_RD_XZ is not set +# CONFIG_RD_LZO is not set +# CONFIG_RD_LZ4 is not set +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +# CONFIG_FHANDLE is not set +# CONFIG_BASE_FULL is not set +CONFIG_KALLSYMS_ALL=y +CONFIG_BPF_SYSCALL=y +CONFIG_EMBEDDED=y +# CONFIG_SLUB_DEBUG is not set +# CONFIG_COMPAT_BRK is not set +CONFIG_PROFILING=y +CONFIG_ARCH_QCOM=y +CONFIG_ARCH_QM215=y +CONFIG_ARCH_MSM8917=y +# CONFIG_VDSO is not set +CONFIG_SMP=y +CONFIG_SCHED_MC=y +CONFIG_NR_CPUS=8 +CONFIG_ARM_PSCI=y +CONFIG_HIGHMEM=y +CONFIG_SECCOMP=y +CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_TIMES=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_BOOST=y +CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y +CONFIG_CPU_FREQ_MSM=y +CONFIG_CPU_IDLE=y +CONFIG_VFP=y +CONFIG_NEON=y +CONFIG_KERNEL_MODE_NEON=y +CONFIG_PM_WAKELOCKS=y +CONFIG_PM_WAKELOCKS_LIMIT=0 +# CONFIG_PM_WAKELOCKS_GC is not set +CONFIG_ENERGY_MODEL=y +CONFIG_MSM_TZ_LOG=y +CONFIG_ARM_CRYPTO=y +CONFIG_CRYPTO_SHA1_ARM_NEON=y +CONFIG_CRYPTO_SHA2_ARM_CE=y +CONFIG_CRYPTO_AES_ARM_BS=y +CONFIG_CRYPTO_AES_ARM_CE=y +CONFIG_CRYPTO_GHASH_ARM_CE=y +CONFIG_ARCH_MMAP_RND_BITS=16 +CONFIG_PANIC_ON_REFCOUNT_ERROR=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SIG=y +CONFIG_MODULE_SIG_FORCE=y +CONFIG_MODULE_SIG_SHA512=y +# CONFIG_BLK_DEV_BSG is not set +CONFIG_BLK_DEV_ZONED=y +CONFIG_BLK_INLINE_ENCRYPTION=y +CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK=y +CONFIG_PARTITION_ADVANCED=y +# CONFIG_IOSCHED_DEADLINE is not set +CONFIG_CFQ_GROUP_IOSCHED=y +CONFIG_IOSCHED_BFQ=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_CLEANCACHE=y +CONFIG_CMA=y +CONFIG_CMA_DEBUGFS=y +CONFIG_ZSMALLOC=y +CONFIG_BALANCE_ANON_FILE_RECLAIM=y +CONFIG_HAVE_USERSPACE_LOW_MEMORY_KILLER=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_XFRM_USER=y +CONFIG_XFRM_INTERFACE=y +CONFIG_XFRM_STATISTICS=y +CONFIG_NET_KEY=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_NET_IPGRE_DEMUX=y +CONFIG_NET_IPVTI=y +CONFIG_INET_AH=y +CONFIG_INET_ESP=y +CONFIG_INET_IPCOMP=y +CONFIG_INET_UDP_DIAG=y +CONFIG_INET_DIAG_DESTROY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_IPV6_OPTIMISTIC_DAD=y +CONFIG_INET6_AH=y +CONFIG_INET6_ESP=y +CONFIG_INET6_IPCOMP=y +CONFIG_IPV6_MIP6=y +CONFIG_IPV6_VTI=y +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_SECMARK=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CONNTRACK_AMANDA=y +CONFIG_NF_CONNTRACK_FTP=y +CONFIG_NF_CONNTRACK_H323=y +CONFIG_NF_CONNTRACK_IRC=y +CONFIG_NF_CONNTRACK_NETBIOS_NS=y +CONFIG_NF_CONNTRACK_PPTP=y +CONFIG_NF_CONNTRACK_SANE=y +CONFIG_NF_CONNTRACK_TFTP=y +CONFIG_NF_CT_NETLINK=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_CONNSECMARK=y +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=y +CONFIG_NETFILTER_XT_TARGET_HARDIDLETIMER=y +CONFIG_NETFILTER_XT_TARGET_LOG=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_TARGET_NOTRACK=y +CONFIG_NETFILTER_XT_TARGET_TEE=y +CONFIG_NETFILTER_XT_TARGET_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_TRACE=y +CONFIG_NETFILTER_XT_TARGET_SECMARK=y +CONFIG_NETFILTER_XT_TARGET_TCPMSS=y +CONFIG_NETFILTER_XT_MATCH_BPF=y +CONFIG_NETFILTER_XT_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_DSCP=y +CONFIG_NETFILTER_XT_MATCH_ESP=y +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y +CONFIG_NETFILTER_XT_MATCH_HELPER=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +# CONFIG_NETFILTER_XT_MATCH_L2TP is not set +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +CONFIG_NETFILTER_XT_MATCH_STRING=y +CONFIG_NETFILTER_XT_MATCH_TIME=y +CONFIG_NETFILTER_XT_MATCH_U32=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_RPFILTER=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_IP_NF_NAT=y +CONFIG_IP_NF_TARGET_MASQUERADE=y +CONFIG_IP_NF_TARGET_NETMAP=y +CONFIG_IP_NF_TARGET_REDIRECT=y +CONFIG_IP_NF_MANGLE=y +CONFIG_IP_NF_RAW=y +CONFIG_IP_NF_SECURITY=y +CONFIG_IP_NF_ARPTABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARP_MANGLE=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_MATCH_RPFILTER=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_TARGET_REJECT=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_IP6_NF_RAW=y +CONFIG_BRIDGE_NF_EBTABLES=y +CONFIG_BRIDGE_EBT_BROUTE=y +CONFIG_L2TP=y +CONFIG_L2TP_DEBUGFS=y +CONFIG_L2TP_V3=y +CONFIG_L2TP_IP=y +CONFIG_L2TP_ETH=y +CONFIG_BRIDGE=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_SCH_INGRESS=y +CONFIG_NET_CLS_FW=y +CONFIG_NET_CLS_U32=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=y +CONFIG_NET_CLS_BPF=y +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_CMP=y +CONFIG_NET_EMATCH_NBYTE=y +CONFIG_NET_EMATCH_U32=y +CONFIG_NET_EMATCH_META=y +CONFIG_NET_EMATCH_TEXT=y +CONFIG_NET_CLS_ACT=y +CONFIG_DNS_RESOLVER=y +CONFIG_QRTR=y +CONFIG_QRTR_SMD=y +CONFIG_BT=y +# CONFIG_BT_BREDR is not set +# CONFIG_BT_LE is not set +CONFIG_MSM_BT_POWER=y +CONFIG_BTFM_SLIM_WCN3990=y +CONFIG_CFG80211=y +CONFIG_CFG80211_INTERNAL_REGDB=y +# CONFIG_CFG80211_CRDA_SUPPORT is not set +CONFIG_RFKILL=y +CONFIG_NFC_NQ=y +CONFIG_FW_LOADER_USER_HELPER=y +CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y +# CONFIG_FW_CACHE is not set +CONFIG_REGMAP_ALLOW_WRITE_DEBUGFS=y +CONFIG_DMA_CMA=y +CONFIG_ZRAM=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_LOOP_MIN_COUNT=16 +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_HDCP_QSEECOM=y +CONFIG_QSEECOM=y +CONFIG_UID_SYS_STATS=y +CONFIG_SCSI=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_SCSI_UFSHCD=y +CONFIG_SCSI_UFSHCD_PLATFORM=y +CONFIG_SCSI_UFS_QCOM=y +CONFIG_SCSI_UFS_CRYPTO=y +CONFIG_SCSI_UFS_CRYPTO_QTI=y +CONFIG_MD=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_CRYPT=y +CONFIG_DM_UEVENT=y +CONFIG_DM_VERITY=y +CONFIG_DM_VERITY_FEC=y +CONFIG_DM_ANDROID_VERITY=y +CONFIG_DM_ANDROID_VERITY_AT_MOST_ONCE_DEFAULT_ENABLED=y +CONFIG_DM_BOW=y +CONFIG_NETDEVICES=y +CONFIG_DUMMY=y +CONFIG_TUN=y +# CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set +CONFIG_PPP=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_DEFLATE=y +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=y +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOE=y +CONFIG_PPTP=y +CONFIG_PPPOL2TP=y +CONFIG_PPP_ASYNC=y +CONFIG_PPP_SYNC_TTY=y +CONFIG_USB_RTL8152=y +CONFIG_USB_USBNET=y +# CONFIG_WLAN_VENDOR_ADMTEK is not set +# CONFIG_WLAN_VENDOR_ATH is not set +# CONFIG_WLAN_VENDOR_ATMEL is not set +# CONFIG_WLAN_VENDOR_BROADCOM is not set +# CONFIG_WLAN_VENDOR_CISCO is not set +# CONFIG_WLAN_VENDOR_INTEL is not set +# CONFIG_WLAN_VENDOR_INTERSIL is not set +# CONFIG_WLAN_VENDOR_MARVELL is not set +# CONFIG_WLAN_VENDOR_MEDIATEK is not set +# CONFIG_WLAN_VENDOR_RALINK is not set +# CONFIG_WLAN_VENDOR_REALTEK is not set +# CONFIG_WLAN_VENDOR_RSI is not set +# CONFIG_WLAN_VENDOR_ST is not set +# CONFIG_WLAN_VENDOR_TI is not set +# CONFIG_WLAN_VENDOR_ZYDAS is not set +CONFIG_WCNSS_MEM_PRE_ALLOC=y +CONFIG_CLD_LL_CORE=y +CONFIG_INPUT_EVDEV=y +CONFIG_KEYBOARD_GPIO=y +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_JOYSTICK=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_HBTP_INPUT=y +CONFIG_INPUT_QPNP_POWER_ON=y +CONFIG_INPUT_UINPUT=y +# CONFIG_SERIO_SERPORT is not set +# CONFIG_VT is not set +# CONFIG_LEGACY_PTYS is not set +# CONFIG_DEVMEM is not set +CONFIG_SERIAL_MSM_HS=y +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_MSM_LEGACY=y +CONFIG_MSM_SMD_PKT=y +CONFIG_DIAG_CHAR=y +CONFIG_MSM_ADSPRPC=y +CONFIG_MSM_RDBG=m +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_MSM_V2=y +CONFIG_SPI=y +CONFIG_SPI_QUP=y +CONFIG_SPI_SPIDEV=y +CONFIG_SPMI=y +CONFIG_PINCTRL_MSM8937=y +CONFIG_PINCTRL_MSM8917=y +CONFIG_PINCTRL_QCOM_SPMI_PMIC=y +CONFIG_GPIOLIB=y +CONFIG_GPIO_SYSFS=y +CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_QCOM=y +CONFIG_POWER_RESET_SYSCON=y +CONFIG_QPNP_SMB5=y +CONFIG_SMB1351_USB_CHARGER=y +CONFIG_SMB1355_SLAVE_CHARGER=y +CONFIG_QPNP_QG=y +CONFIG_THERMAL=y +CONFIG_THERMAL_WRITABLE_TRIPS=y +CONFIG_THERMAL_GOV_USER_SPACE=y +CONFIG_THERMAL_GOV_LOW_LIMITS=y +CONFIG_CPU_THERMAL=y +CONFIG_DEVFREQ_THERMAL=y +CONFIG_THERMAL_TSENS=y +CONFIG_QTI_VIRTUAL_SENSOR=y +CONFIG_QTI_BCL_PMIC5=y +CONFIG_QTI_BCL_SOC_DRIVER=y +CONFIG_QTI_QMI_COOLING_DEVICE=y +CONFIG_REGULATOR_COOLING_DEVICE=y +CONFIG_MFD_I2C_PMIC=y +CONFIG_MFD_SPMI_PMIC=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_PROXY_CONSUMER=y +CONFIG_REGULATOR_QPNP_LABIBB=y +CONFIG_REGULATOR_QPNP_LCDB=y +CONFIG_REGULATOR_MEM_ACC=y +CONFIG_REGULATOR_CPR=y +CONFIG_REGULATOR_RPM_SMD=y +CONFIG_REGULATOR_SPM=y +CONFIG_REGULATOR_STUB=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_MEDIA_CAMERA_SUPPORT=y +CONFIG_MEDIA_CONTROLLER=y +CONFIG_VIDEO_V4L2_SUBDEV_API=y +CONFIG_MEDIA_USB_SUPPORT=y +CONFIG_USB_VIDEO_CLASS=y +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_MSM_VIDC_3X_GOVERNORS=y +CONFIG_MSM_VIDC_3X_V4L2=y +CONFIG_MSM_CAMERA=y +CONFIG_MSMB_CAMERA=y +CONFIG_MSM_CAMERA_SENSOR=y +CONFIG_MSM_CPP=y +CONFIG_MSM_CCI=y +CONFIG_MSM_CSI20_HEADER=y +CONFIG_MSM_CSI22_HEADER=y +CONFIG_MSM_CSI30_HEADER=y +CONFIG_MSM_CSI31_HEADER=y +CONFIG_MSM_CSIPHY=y +CONFIG_MSM_CSID=y +CONFIG_MSM_EEPROM=y +CONFIG_MSM_ISPIF_V2=y +CONFIG_IMX134=y +CONFIG_IMX132=y +CONFIG_OV9724=y +CONFIG_OV5648=y +CONFIG_GC0339=y +CONFIG_OV8825=y +CONFIG_OV8865=y +CONFIG_s5k4e1=y +CONFIG_OV12830=y +CONFIG_MSM_V4L2_VIDEO_OVERLAY_DEVICE=y +CONFIG_MSMB_JPEG=y +CONFIG_MSM_FD=y +CONFIG_FB=y +CONFIG_FB_MSM=y +CONFIG_FB_MSM_MDSS=y +CONFIG_FB_MSM_MDSS_WRITEBACK=y +CONFIG_FB_MSM_MDSS_SPI_PANEL=y +CONFIG_FB_MSM_MDSS_DSI_CTRL_STATUS=y +CONFIG_FB_MSM_MDSS_XLOG_DEBUG=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_BACKLIGHT_CLASS_DEVICE is not set +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_DYNAMIC_MINORS=y +CONFIG_SND_USB_AUDIO=y +CONFIG_SND_SOC=y +CONFIG_UHID=y +CONFIG_HID_APPLE=y +CONFIG_HID_ELECOM=y +CONFIG_HID_MAGICMOUSE=y +CONFIG_HID_MICROSOFT=y +CONFIG_HID_MULTITOUCH=y +CONFIG_HID_SONY=y +CONFIG_USB_HIDDEV=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_MON=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_HCD_PLATFORM=y +CONFIG_USB_EHCI_MSM=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_DATAFAB=y +CONFIG_USB_STORAGE_FREECOM=y +CONFIG_USB_STORAGE_ISD200=y +CONFIG_USB_STORAGE_USBAT=y +CONFIG_USB_STORAGE_SDDR09=y +CONFIG_USB_STORAGE_SDDR55=y +CONFIG_USB_STORAGE_JUMPSHOT=y +CONFIG_USB_STORAGE_ALAUDA=y +CONFIG_USB_STORAGE_ONETOUCH=y +CONFIG_USB_STORAGE_KARMA=y +CONFIG_USB_STORAGE_CYPRESS_ATACB=y +CONFIG_USB_SERIAL=y +CONFIG_USB_EHSET_TEST_FIXTURE=y +CONFIG_NOP_USB_XCEIV=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_DEBUG_FILES=y +CONFIG_USB_GADGET_DEBUG_FS=y +CONFIG_USB_GADGET_VBUS_DRAW=500 +CONFIG_USB_CI13XXX_MSM=y +CONFIG_USB_CONFIGFS=y +CONFIG_USB_CONFIGFS_UEVENT=y +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_CONFIGFS_RMNET_BAM=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_USB_CONFIGFS_F_FS=y +CONFIG_USB_CONFIGFS_F_ACC=y +CONFIG_USB_CONFIGFS_F_AUDIO_SRC=y +CONFIG_USB_CONFIGFS_F_MIDI=y +CONFIG_USB_CONFIGFS_F_HID=y +CONFIG_USB_CONFIGFS_F_DIAG=y +CONFIG_USB_CONFIGFS_F_CDEV=y +CONFIG_USB_CONFIGFS_F_CCID=y +CONFIG_USB_CONFIGFS_F_QDSS=y +CONFIG_USB_CONFIGFS_F_MTP=y +CONFIG_USB_CONFIGFS_F_PTP=y +CONFIG_TYPEC=y +CONFIG_MMC=y +# CONFIG_PWRSEQ_EMMC is not set +# CONFIG_PWRSEQ_SIMPLE is not set +CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_MMC_BLOCK_DEFERRED_RESUME=y +CONFIG_MMC_IPC_LOGGING=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_MSM=y +CONFIG_MMC_CQHCI_CRYPTO=y +CONFIG_MMC_CQHCI_CRYPTO_QTI=y +CONFIG_LEDS_QTI_TRI_LED=y +CONFIG_LEDS_QPNP_FLASH_V2=y +CONFIG_LEDS_QPNP_VIBRATOR_LDO=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_EDAC=y +CONFIG_RTC_CLASS=y +CONFIG_DMADEVICES=y +CONFIG_QCOM_SPS_DMA=y +CONFIG_UIO=y +CONFIG_UIO_MSM_SHAREDMEM=y +CONFIG_STAGING=y +CONFIG_ASHMEM=y +CONFIG_ION=y +CONFIG_ION_POOL_AUTO_REFILL=y +CONFIG_QPNP_REVID=y +CONFIG_SPS=y +CONFIG_SPS_SUPPORT_NDP_BAM=y +CONFIG_IPA=y +CONFIG_RMNET_IPA=y +CONFIG_RNDIS_IPA=y +CONFIG_MDSS_PLL=y +CONFIG_QCOM_CLK_SMD_RPM=y +CONFIG_SDM_GCC_429W=y +CONFIG_SDM_DEBUGCC_429W=y +CONFIG_CLOCK_CPU_SDM=y +CONFIG_HWSPINLOCK=y +CONFIG_HWSPINLOCK_QCOM=y +CONFIG_MAILBOX=y +CONFIG_ARM_SMMU=y +CONFIG_QCOM_LAZY_MAPPING=y +CONFIG_RPMSG_CHAR=y +CONFIG_RPMSG_QCOM_GLINK_RPM=y +CONFIG_RPMSG_QCOM_GLINK_SMEM=y +CONFIG_RPMSG_QCOM_SMD=y +CONFIG_MSM_RPM_SMD=y +CONFIG_QCOM_CPUSS_DUMP=y +CONFIG_QCOM_RUN_QUEUE_STATS=y +CONFIG_QCOM_QMI_HELPERS=y +CONFIG_QCOM_SMEM=y +CONFIG_QCOM_SMD_RPM=y +CONFIG_MSM_SPM=y +CONFIG_MSM_L2_SPM=y +CONFIG_QCOM_EARLY_RANDOM=y +CONFIG_QCOM_MEMORY_DUMP_V2=y +CONFIG_QCOM_SMP2P=y +CONFIG_QCOM_SMSM=y +CONFIG_MSM_PIL_MSS_QDSP6V5=y +CONFIG_QCOM_SECURE_BUFFER=y +CONFIG_MSM_SUBSYSTEM_RESTART=y +CONFIG_MSM_PIL=y +CONFIG_MSM_PIL_SSR_GENERIC=y +CONFIG_MSM_BOOT_STATS=y +CONFIG_QCOM_DCC_V2=y +CONFIG_MSM_CORE_HANG_DETECT=y +CONFIG_QCOM_WATCHDOG_V2=y +CONFIG_QCOM_FORCE_WDOG_BITE_ON_PANIC=y +CONFIG_QCOM_BUS_SCALING=y +CONFIG_QCOM_GLINK=y +CONFIG_MSM_EVENT_TIMER=y +CONFIG_MSM_PM=y +CONFIG_QTI_RPM_STATS_LOG=y +CONFIG_QTEE_SHM_BRIDGE=y +CONFIG_MEM_SHARE_QMI_SERVICE=y +CONFIG_MSM_PERFORMANCE=y +CONFIG_QTI_CRYPTO_COMMON=y +CONFIG_QTI_CRYPTO_TZ=y +CONFIG_WCNSS_CORE=y +CONFIG_WCNSS_CORE_PRONTO=y +CONFIG_WCNSS_REGISTER_DUMP_ON_BITE=y +CONFIG_DEVFREQ_GOV_PASSIVE=y +CONFIG_QCOM_BIMC_BWMON=y +CONFIG_ARM_MEMLAT_MON=y +CONFIG_DEVFREQ_GOV_QCOM_BW_HWMON=y +CONFIG_DEVFREQ_GOV_MEMLAT=y +CONFIG_DEVFREQ_SIMPLE_DEV=y +CONFIG_QCOM_DEVFREQ_DEVBW=y +CONFIG_IIO=y +CONFIG_PWM=y +CONFIG_PWM_QTI_LPG=y +CONFIG_QCOM_SHOW_RESUME_IRQ=y +CONFIG_QCOM_MPM=y +CONFIG_RAS=y +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_BINDERFS=y +CONFIG_QCOM_QFPROM=y +CONFIG_SLIMBUS_MSM_NGD=y +CONFIG_SENSORS_SSC=y +CONFIG_QCOM_KGSL=y +CONFIG_LEGACY_ENERGY_MODEL_DT=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_EXT4_ENCRYPTION=y +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_SECURITY=y +CONFIG_F2FS_FS_ENCRYPTION=y +CONFIG_FS_ENCRYPTION_INLINE_CRYPT=y +CONFIG_FS_VERITY=y +CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y +CONFIG_QUOTA=y +CONFIG_QUOTA_NETLINK_INTERFACE=y +CONFIG_QFMT_V2=y +CONFIG_FUSE_FS=y +CONFIG_OVERLAY_FS=y +CONFIG_INCREMENTAL_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_SDCARD_FS=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_XATTR=y +CONFIG_SQUASHFS_LZ4=y +# CONFIG_NETWORK_FILESYSTEMS is not set +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ISO8859_1=y +CONFIG_SECURITY_PERF_EVENTS_RESTRICT=y +CONFIG_SECURITY=y +CONFIG_LSM_MMAP_MIN_ADDR=4096 +CONFIG_HARDENED_USERCOPY=y +CONFIG_HARDENED_USERCOPY_PAGESPAN=y +CONFIG_FORTIFY_SOURCE=y +CONFIG_STATIC_USERMODEHELPER=y +CONFIG_STATIC_USERMODEHELPER_PATH="" +CONFIG_SECURITY_SELINUX=y +CONFIG_SECURITY_SMACK=y +CONFIG_CRYPTO_GCM=y +CONFIG_CRYPTO_XCBC=y +CONFIG_CRYPTO_MD4=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRYPTO_LZ4=y +CONFIG_CRYPTO_ANSI_CPRNG=y +CONFIG_CRYPTO_DEV_QCE=y +CONFIG_CRYPTO_DEV_QCOM_MSM_QCE=y +CONFIG_CRYPTO_DEV_QCRYPTO=y +CONFIG_CRYPTO_DEV_QCEDEV=y +CONFIG_CRYPTO_DEV_QCOM_ICE=y +CONFIG_PRINTK_TIME=y +CONFIG_DEBUG_INFO=y +CONFIG_FRAME_WARN=2048 +CONFIG_DEBUG_FS=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_WQ_WATCHDOG=y +CONFIG_PANIC_ON_OOPS=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_SCHED_STACK_END_CHECK=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_FAULT_INJECTION=y +CONFIG_FAIL_PAGE_ALLOC=y +CONFIG_IPC_LOGGING=y +# CONFIG_FTRACE is not set +CONFIG_LKDTM=m +CONFIG_BUG_ON_DATA_CORRUPTION=y diff --git a/arch/arm/configs/vendor/msm8937_32go_defconfig b/arch/arm/configs/vendor/msm8937_32go_defconfig new file mode 100644 index 000000000000..433e6853d1be --- /dev/null +++ b/arch/arm/configs/vendor/msm8937_32go_defconfig @@ -0,0 +1,711 @@ +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_AUDIT=y +# CONFIG_AUDITSYSCALL is not set +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_PREEMPT=y +CONFIG_IRQ_TIME_ACCOUNTING=y +CONFIG_SCHED_WALT=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_PSI=y +CONFIG_RCU_EXPERT=y +CONFIG_RCU_FAST_NO_HZ=y +CONFIG_RCU_NOCB_CPU=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_CPU_MAX_BUF_SHIFT=17 +CONFIG_MEMCG=y +CONFIG_MEMCG_SWAP=y +CONFIG_BLK_CGROUP=y +CONFIG_DEBUG_BLK_CGROUP=y +CONFIG_RT_GROUP_SCHED=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_BPF=y +CONFIG_CGROUP_DEBUG=y +CONFIG_SCHED_CORE_CTL=y +CONFIG_NAMESPACES=y +# CONFIG_UTS_NS is not set +# CONFIG_PID_NS is not set +CONFIG_SCHED_AUTOGROUP=y +CONFIG_SCHED_TUNE=y +CONFIG_BLK_DEV_INITRD=y +# CONFIG_RD_BZIP2 is not set +# CONFIG_RD_LZMA is not set +# CONFIG_RD_XZ is not set +# CONFIG_RD_LZO is not set +# CONFIG_RD_LZ4 is not set +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +# CONFIG_FHANDLE is not set +# CONFIG_BASE_FULL is not set +CONFIG_KALLSYMS_ALL=y +CONFIG_BPF_SYSCALL=y +CONFIG_EMBEDDED=y +# CONFIG_COMPAT_BRK is not set +CONFIG_PROFILING=y +CONFIG_ARCH_QCOM=y +CONFIG_ARCH_QM215=y +CONFIG_ARCH_MSM8917=y +# CONFIG_VDSO is not set +CONFIG_SMP=y +CONFIG_SCHED_MC=y +CONFIG_NR_CPUS=8 +CONFIG_ARM_PSCI=y +CONFIG_HIGHMEM=y +CONFIG_SECCOMP=y +CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_TIMES=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_BOOST=y +CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y +CONFIG_CPU_FREQ_MSM=y +CONFIG_CPU_IDLE=y +CONFIG_VFP=y +CONFIG_NEON=y +CONFIG_KERNEL_MODE_NEON=y +CONFIG_PM_WAKELOCKS=y +CONFIG_PM_WAKELOCKS_LIMIT=0 +# CONFIG_PM_WAKELOCKS_GC is not set +CONFIG_PM_DEBUG=y +CONFIG_ENERGY_MODEL=y +CONFIG_MSM_TZ_LOG=y +CONFIG_ARM_CRYPTO=y +CONFIG_CRYPTO_SHA1_ARM_NEON=y +CONFIG_CRYPTO_SHA2_ARM_CE=y +CONFIG_CRYPTO_AES_ARM_BS=y +CONFIG_CRYPTO_AES_ARM_CE=y +CONFIG_CRYPTO_GHASH_ARM_CE=y +CONFIG_OPROFILE=m +CONFIG_KPROBES=y +CONFIG_ARCH_MMAP_RND_BITS=16 +CONFIG_PANIC_ON_REFCOUNT_ERROR=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SIG=y +CONFIG_MODULE_SIG_FORCE=y +CONFIG_MODULE_SIG_SHA512=y +# CONFIG_BLK_DEV_BSG is not set +CONFIG_BLK_DEV_ZONED=y +CONFIG_BLK_INLINE_ENCRYPTION=y +CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK=y +CONFIG_PARTITION_ADVANCED=y +# CONFIG_IOSCHED_DEADLINE is not set +CONFIG_CFQ_GROUP_IOSCHED=y +CONFIG_IOSCHED_BFQ=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_CLEANCACHE=y +CONFIG_CMA=y +CONFIG_CMA_DEBUGFS=y +CONFIG_ZSMALLOC=y +CONFIG_BALANCE_ANON_FILE_RECLAIM=y +CONFIG_HAVE_USERSPACE_LOW_MEMORY_KILLER=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_XFRM_USER=y +CONFIG_XFRM_INTERFACE=y +CONFIG_XFRM_STATISTICS=y +CONFIG_NET_KEY=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_NET_IPGRE_DEMUX=y +CONFIG_NET_IPVTI=y +CONFIG_INET_AH=y +CONFIG_INET_ESP=y +CONFIG_INET_IPCOMP=y +CONFIG_INET_UDP_DIAG=y +CONFIG_INET_DIAG_DESTROY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_IPV6_OPTIMISTIC_DAD=y +CONFIG_INET6_AH=y +CONFIG_INET6_ESP=y +CONFIG_INET6_IPCOMP=y +CONFIG_IPV6_MIP6=y +CONFIG_IPV6_VTI=y +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_SECMARK=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CONNTRACK_AMANDA=y +CONFIG_NF_CONNTRACK_FTP=y +CONFIG_NF_CONNTRACK_H323=y +CONFIG_NF_CONNTRACK_IRC=y +CONFIG_NF_CONNTRACK_NETBIOS_NS=y +CONFIG_NF_CONNTRACK_PPTP=y +CONFIG_NF_CONNTRACK_SANE=y +CONFIG_NF_CONNTRACK_TFTP=y +CONFIG_NF_CT_NETLINK=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_CONNSECMARK=y +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=y +CONFIG_NETFILTER_XT_TARGET_HARDIDLETIMER=y +CONFIG_NETFILTER_XT_TARGET_LOG=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_TARGET_NOTRACK=y +CONFIG_NETFILTER_XT_TARGET_TEE=y +CONFIG_NETFILTER_XT_TARGET_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_TRACE=y +CONFIG_NETFILTER_XT_TARGET_SECMARK=y +CONFIG_NETFILTER_XT_TARGET_TCPMSS=y +CONFIG_NETFILTER_XT_MATCH_BPF=y +CONFIG_NETFILTER_XT_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_DSCP=y +CONFIG_NETFILTER_XT_MATCH_ESP=y +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y +CONFIG_NETFILTER_XT_MATCH_HELPER=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +# CONFIG_NETFILTER_XT_MATCH_L2TP is not set +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2=y +CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +CONFIG_NETFILTER_XT_MATCH_STRING=y +CONFIG_NETFILTER_XT_MATCH_TIME=y +CONFIG_NETFILTER_XT_MATCH_U32=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_RPFILTER=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_IP_NF_NAT=y +CONFIG_IP_NF_TARGET_MASQUERADE=y +CONFIG_IP_NF_TARGET_NETMAP=y +CONFIG_IP_NF_TARGET_REDIRECT=y +CONFIG_IP_NF_MANGLE=y +CONFIG_IP_NF_RAW=y +CONFIG_IP_NF_SECURITY=y +CONFIG_IP_NF_ARPTABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARP_MANGLE=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_MATCH_RPFILTER=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_TARGET_REJECT=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_IP6_NF_RAW=y +CONFIG_BRIDGE_NF_EBTABLES=y +CONFIG_BRIDGE_EBT_BROUTE=y +CONFIG_L2TP=y +CONFIG_L2TP_DEBUGFS=y +CONFIG_L2TP_V3=y +CONFIG_L2TP_IP=y +CONFIG_L2TP_ETH=y +CONFIG_BRIDGE=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_SCH_INGRESS=y +CONFIG_NET_CLS_FW=y +CONFIG_NET_CLS_U32=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=y +CONFIG_NET_CLS_BPF=y +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_CMP=y +CONFIG_NET_EMATCH_NBYTE=y +CONFIG_NET_EMATCH_U32=y +CONFIG_NET_EMATCH_META=y +CONFIG_NET_EMATCH_TEXT=y +CONFIG_NET_CLS_ACT=y +CONFIG_DNS_RESOLVER=y +CONFIG_QRTR=y +CONFIG_QRTR_SMD=y +CONFIG_BT=y +# CONFIG_BT_BREDR is not set +# CONFIG_BT_LE is not set +CONFIG_MSM_BT_POWER=y +CONFIG_BTFM_SLIM_WCN3990=y +CONFIG_CFG80211=y +CONFIG_CFG80211_INTERNAL_REGDB=y +# CONFIG_CFG80211_CRDA_SUPPORT is not set +CONFIG_RFKILL=y +CONFIG_NFC_NQ=y +CONFIG_FW_LOADER_USER_HELPER=y +CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y +# CONFIG_FW_CACHE is not set +CONFIG_REGMAP_ALLOW_WRITE_DEBUGFS=y +CONFIG_DMA_CMA=y +CONFIG_ZRAM=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_LOOP_MIN_COUNT=16 +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_HDCP_QSEECOM=y +CONFIG_QSEECOM=y +CONFIG_UID_SYS_STATS=y +CONFIG_SCSI=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_SCSI_UFSHCD=y +CONFIG_SCSI_UFSHCD_PLATFORM=y +CONFIG_SCSI_UFS_QCOM=y +CONFIG_SCSI_UFSHCD_CMD_LOGGING=y +CONFIG_SCSI_UFS_CRYPTO=y +CONFIG_SCSI_UFS_CRYPTO_QTI=y +CONFIG_MD=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_CRYPT=y +CONFIG_DM_UEVENT=y +CONFIG_DM_VERITY=y +CONFIG_DM_VERITY_FEC=y +CONFIG_DM_ANDROID_VERITY=y +CONFIG_DM_ANDROID_VERITY_AT_MOST_ONCE_DEFAULT_ENABLED=y +CONFIG_DM_BOW=y +CONFIG_NETDEVICES=y +CONFIG_DUMMY=y +CONFIG_TUN=y +# CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set +CONFIG_PPP=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_DEFLATE=y +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=y +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOE=y +CONFIG_PPTP=y +CONFIG_PPPOL2TP=y +CONFIG_PPP_ASYNC=y +CONFIG_PPP_SYNC_TTY=y +CONFIG_USB_RTL8152=y +CONFIG_USB_USBNET=y +# CONFIG_WLAN_VENDOR_ADMTEK is not set +# CONFIG_WLAN_VENDOR_ATH is not set +# CONFIG_WLAN_VENDOR_ATMEL is not set +# CONFIG_WLAN_VENDOR_BROADCOM is not set +# CONFIG_WLAN_VENDOR_CISCO is not set +# CONFIG_WLAN_VENDOR_INTEL is not set +# CONFIG_WLAN_VENDOR_INTERSIL is not set +# CONFIG_WLAN_VENDOR_MARVELL is not set +# CONFIG_WLAN_VENDOR_MEDIATEK is not set +# CONFIG_WLAN_VENDOR_RALINK is not set +# CONFIG_WLAN_VENDOR_REALTEK is not set +# CONFIG_WLAN_VENDOR_RSI is not set +# CONFIG_WLAN_VENDOR_ST is not set +# CONFIG_WLAN_VENDOR_TI is not set +# CONFIG_WLAN_VENDOR_ZYDAS is not set +CONFIG_WCNSS_MEM_PRE_ALLOC=y +CONFIG_CLD_LL_CORE=y +CONFIG_INPUT_EVDEV=y +CONFIG_KEYBOARD_GPIO=y +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_JOYSTICK=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_HBTP_INPUT=y +CONFIG_INPUT_QPNP_POWER_ON=y +CONFIG_INPUT_UINPUT=y +# CONFIG_SERIO_SERPORT is not set +# CONFIG_VT is not set +# CONFIG_LEGACY_PTYS is not set +# CONFIG_DEVMEM is not set +CONFIG_SERIAL_MSM=y +CONFIG_SERIAL_MSM_CONSOLE=y +CONFIG_SERIAL_MSM_HS=y +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_MSM_LEGACY=y +CONFIG_MSM_SMD_PKT=y +CONFIG_DIAG_CHAR=y +CONFIG_MSM_ADSPRPC=y +CONFIG_MSM_RDBG=m +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_MSM_V2=y +CONFIG_SPI=y +CONFIG_SPI_QUP=y +CONFIG_SPI_SPIDEV=y +CONFIG_SPMI=y +CONFIG_PINCTRL_MSM8937=y +CONFIG_PINCTRL_MSM8917=y +CONFIG_PINCTRL_QCOM_SPMI_PMIC=y +CONFIG_GPIOLIB=y +CONFIG_GPIO_SYSFS=y +CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_QCOM=y +CONFIG_QPNP_SMB5=y +CONFIG_SMB1351_USB_CHARGER=y +CONFIG_SMB1355_SLAVE_CHARGER=y +CONFIG_QPNP_QG=y +CONFIG_THERMAL=y +CONFIG_THERMAL_WRITABLE_TRIPS=y +CONFIG_THERMAL_GOV_USER_SPACE=y +CONFIG_THERMAL_GOV_LOW_LIMITS=y +CONFIG_CPU_THERMAL=y +CONFIG_DEVFREQ_THERMAL=y +CONFIG_THERMAL_TSENS=y +CONFIG_QTI_VIRTUAL_SENSOR=y +CONFIG_QTI_BCL_PMIC5=y +CONFIG_QTI_BCL_SOC_DRIVER=y +CONFIG_QTI_QMI_COOLING_DEVICE=y +CONFIG_REGULATOR_COOLING_DEVICE=y +CONFIG_MFD_I2C_PMIC=y +CONFIG_MFD_SPMI_PMIC=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_PROXY_CONSUMER=y +CONFIG_REGULATOR_QPNP_LABIBB=y +CONFIG_REGULATOR_QPNP_LCDB=y +CONFIG_REGULATOR_MEM_ACC=y +CONFIG_REGULATOR_CPR=y +CONFIG_REGULATOR_RPM_SMD=y +CONFIG_REGULATOR_SPM=y +CONFIG_REGULATOR_STUB=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_MEDIA_CAMERA_SUPPORT=y +CONFIG_MEDIA_CONTROLLER=y +CONFIG_VIDEO_V4L2_SUBDEV_API=y +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_MSM_VIDC_3X_GOVERNORS=y +CONFIG_MSM_VIDC_3X_V4L2=y +CONFIG_MSM_CAMERA=y +CONFIG_MSM_CAMERA_DEBUG=y +CONFIG_MSMB_CAMERA=y +CONFIG_MSMB_CAMERA_DEBUG=y +CONFIG_MSM_CAMERA_SENSOR=y +CONFIG_MSM_CPP=y +CONFIG_MSM_CCI=y +CONFIG_MSM_CSI20_HEADER=y +CONFIG_MSM_CSI22_HEADER=y +CONFIG_MSM_CSI30_HEADER=y +CONFIG_MSM_CSI31_HEADER=y +CONFIG_MSM_CSIPHY=y +CONFIG_MSM_CSID=y +CONFIG_MSM_EEPROM=y +CONFIG_MSM_ISPIF_V2=y +CONFIG_IMX134=y +CONFIG_IMX132=y +CONFIG_OV9724=y +CONFIG_OV5648=y +CONFIG_GC0339=y +CONFIG_OV8825=y +CONFIG_OV8865=y +CONFIG_s5k4e1=y +CONFIG_OV12830=y +CONFIG_MSM_V4L2_VIDEO_OVERLAY_DEVICE=y +CONFIG_MSMB_JPEG=y +CONFIG_MSM_FD=y +CONFIG_FB=y +CONFIG_FB_VIRTUAL=y +CONFIG_FB_MSM=y +CONFIG_FB_MSM_MDSS=y +CONFIG_FB_MSM_MDSS_WRITEBACK=y +CONFIG_FB_MSM_MDSS_SPI_PANEL=y +CONFIG_FB_MSM_MDSS_DSI_CTRL_STATUS=y +CONFIG_FB_MSM_MDSS_XLOG_DEBUG=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_BACKLIGHT_CLASS_DEVICE is not set +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_DYNAMIC_MINORS=y +CONFIG_SND_USB_AUDIO=y +CONFIG_SND_SOC=y +CONFIG_UHID=y +CONFIG_HID_APPLE=y +CONFIG_HID_ELECOM=y +CONFIG_HID_MAGICMOUSE=y +CONFIG_HID_MICROSOFT=y +CONFIG_HID_MULTITOUCH=y +CONFIG_HID_SONY=y +CONFIG_USB_HIDDEV=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_MON=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_HCD_PLATFORM=y +CONFIG_USB_EHCI_MSM=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_DATAFAB=y +CONFIG_USB_STORAGE_FREECOM=y +CONFIG_USB_STORAGE_ISD200=y +CONFIG_USB_STORAGE_USBAT=y +CONFIG_USB_STORAGE_SDDR09=y +CONFIG_USB_STORAGE_SDDR55=y +CONFIG_USB_STORAGE_JUMPSHOT=y +CONFIG_USB_STORAGE_ALAUDA=y +CONFIG_USB_STORAGE_ONETOUCH=y +CONFIG_USB_STORAGE_KARMA=y +CONFIG_USB_STORAGE_CYPRESS_ATACB=y +CONFIG_USB_SERIAL=y +CONFIG_USB_EHSET_TEST_FIXTURE=y +CONFIG_NOP_USB_XCEIV=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_DEBUG_FILES=y +CONFIG_USB_GADGET_DEBUG_FS=y +CONFIG_USB_GADGET_VBUS_DRAW=500 +CONFIG_USB_CI13XXX_MSM=y +CONFIG_USB_CONFIGFS=y +CONFIG_USB_CONFIGFS_UEVENT=y +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_CONFIGFS_RMNET_BAM=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_USB_CONFIGFS_F_FS=y +CONFIG_USB_CONFIGFS_F_ACC=y +CONFIG_USB_CONFIGFS_F_AUDIO_SRC=y +CONFIG_USB_CONFIGFS_F_MIDI=y +CONFIG_USB_CONFIGFS_F_HID=y +CONFIG_USB_CONFIGFS_F_DIAG=y +CONFIG_USB_CONFIGFS_F_CDEV=y +CONFIG_USB_CONFIGFS_F_CCID=y +CONFIG_USB_CONFIGFS_F_QDSS=y +CONFIG_USB_CONFIGFS_F_MTP=y +CONFIG_USB_CONFIGFS_F_PTP=y +CONFIG_TYPEC=y +CONFIG_MMC=y +# CONFIG_PWRSEQ_EMMC is not set +# CONFIG_PWRSEQ_SIMPLE is not set +CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_MMC_BLOCK_DEFERRED_RESUME=y +CONFIG_MMC_IPC_LOGGING=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_MSM=y +CONFIG_MMC_CQHCI_CRYPTO=y +CONFIG_MMC_CQHCI_CRYPTO_QTI=y +CONFIG_LEDS_QTI_TRI_LED=y +CONFIG_LEDS_QPNP_FLASH_V2=y +CONFIG_LEDS_QPNP_VIBRATOR_LDO=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_EDAC=y +CONFIG_RTC_CLASS=y +CONFIG_DMADEVICES=y +CONFIG_QCOM_SPS_DMA=y +CONFIG_UIO=y +CONFIG_UIO_MSM_SHAREDMEM=y +CONFIG_STAGING=y +CONFIG_ASHMEM=y +CONFIG_ION=y +CONFIG_ION_POOL_AUTO_REFILL=y +CONFIG_MSM_EXT_DISPLAY=y +CONFIG_QPNP_REVID=y +CONFIG_SPS=y +CONFIG_SPS_SUPPORT_NDP_BAM=y +CONFIG_IPA=y +CONFIG_RMNET_IPA=y +CONFIG_RNDIS_IPA=y +CONFIG_MDSS_PLL=y +CONFIG_QCOM_CLK_SMD_RPM=y +CONFIG_SDM_GCC_429W=y +CONFIG_SDM_DEBUGCC_429W=y +CONFIG_CLOCK_CPU_SDM=y +CONFIG_HWSPINLOCK=y +CONFIG_HWSPINLOCK_QCOM=y +CONFIG_MAILBOX=y +CONFIG_ARM_SMMU=y +CONFIG_QCOM_LAZY_MAPPING=y +CONFIG_IOMMU_DEBUG=y +CONFIG_IOMMU_TESTS=y +CONFIG_RPMSG_CHAR=y +CONFIG_RPMSG_QCOM_GLINK_RPM=y +CONFIG_RPMSG_QCOM_GLINK_SMEM=y +CONFIG_RPMSG_QCOM_SMD=y +CONFIG_MSM_RPM_SMD=y +CONFIG_QCOM_CPUSS_DUMP=y +CONFIG_QCOM_RUN_QUEUE_STATS=y +CONFIG_QCOM_QMI_HELPERS=y +CONFIG_QCOM_SMEM=y +CONFIG_QCOM_SMD_RPM=y +CONFIG_MSM_SPM=y +CONFIG_MSM_L2_SPM=y +CONFIG_QCOM_EARLY_RANDOM=y +CONFIG_QCOM_MEMORY_DUMP_V2=y +CONFIG_QCOM_SMP2P=y +CONFIG_QCOM_SMSM=y +CONFIG_MSM_PIL_MSS_QDSP6V5=y +CONFIG_QCOM_SECURE_BUFFER=y +CONFIG_MSM_SUBSYSTEM_RESTART=y +CONFIG_MSM_PIL=y +CONFIG_MSM_PIL_SSR_GENERIC=y +CONFIG_MSM_BOOT_STATS=y +CONFIG_QCOM_DCC_V2=y +CONFIG_MSM_CORE_HANG_DETECT=y +CONFIG_QCOM_WATCHDOG_V2=y +CONFIG_QCOM_FORCE_WDOG_BITE_ON_PANIC=y +CONFIG_QCOM_BUS_SCALING=y +CONFIG_QCOM_GLINK=y +CONFIG_MSM_EVENT_TIMER=y +CONFIG_MSM_PM=y +CONFIG_QTI_RPM_STATS_LOG=y +CONFIG_QTEE_SHM_BRIDGE=y +CONFIG_MEM_SHARE_QMI_SERVICE=y +CONFIG_MSM_PERFORMANCE=y +CONFIG_QTI_CRYPTO_COMMON=y +CONFIG_QTI_CRYPTO_TZ=y +CONFIG_WCNSS_CORE=y +CONFIG_WCNSS_CORE_PRONTO=y +CONFIG_WCNSS_REGISTER_DUMP_ON_BITE=y +CONFIG_DEVFREQ_GOV_PASSIVE=y +CONFIG_QCOM_BIMC_BWMON=y +CONFIG_ARM_MEMLAT_MON=y +CONFIG_DEVFREQ_GOV_QCOM_BW_HWMON=y +CONFIG_DEVFREQ_GOV_MEMLAT=y +CONFIG_DEVFREQ_SIMPLE_DEV=y +CONFIG_QCOM_DEVFREQ_DEVBW=y +CONFIG_IIO=y +CONFIG_PWM=y +CONFIG_PWM_QTI_LPG=y +CONFIG_QCOM_SHOW_RESUME_IRQ=y +CONFIG_QCOM_MPM=y +CONFIG_RAS=y +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_BINDERFS=y +CONFIG_QCOM_QFPROM=y +CONFIG_STM=y +CONFIG_SLIMBUS_MSM_NGD=y +CONFIG_SENSORS_SSC=y +CONFIG_QCOM_KGSL=y +CONFIG_LEGACY_ENERGY_MODEL_DT=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_EXT4_ENCRYPTION=y +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_SECURITY=y +CONFIG_F2FS_CHECK_FS=y +CONFIG_F2FS_FS_ENCRYPTION=y +CONFIG_FS_ENCRYPTION_INLINE_CRYPT=y +CONFIG_FS_VERITY=y +CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y +CONFIG_QUOTA=y +CONFIG_QUOTA_NETLINK_INTERFACE=y +CONFIG_QFMT_V2=y +CONFIG_FUSE_FS=y +CONFIG_OVERLAY_FS=y +CONFIG_INCREMENTAL_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_SDCARD_FS=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_XATTR=y +CONFIG_SQUASHFS_LZ4=y +# CONFIG_NETWORK_FILESYSTEMS is not set +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ISO8859_1=y +CONFIG_SECURITY_PERF_EVENTS_RESTRICT=y +CONFIG_SECURITY=y +CONFIG_LSM_MMAP_MIN_ADDR=4096 +CONFIG_HARDENED_USERCOPY=y +CONFIG_HARDENED_USERCOPY_PAGESPAN=y +CONFIG_FORTIFY_SOURCE=y +CONFIG_STATIC_USERMODEHELPER=y +CONFIG_STATIC_USERMODEHELPER_PATH="" +CONFIG_SECURITY_SELINUX=y +CONFIG_SECURITY_SMACK=y +CONFIG_CRYPTO_GCM=y +CONFIG_CRYPTO_XCBC=y +CONFIG_CRYPTO_MD4=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRYPTO_LZ4=y +CONFIG_CRYPTO_ANSI_CPRNG=y +CONFIG_CRYPTO_DEV_QCE=y +CONFIG_CRYPTO_DEV_QCOM_MSM_QCE=y +CONFIG_CRYPTO_DEV_QCRYPTO=y +CONFIG_CRYPTO_DEV_QCEDEV=y +CONFIG_CRYPTO_DEV_QCOM_ICE=y +CONFIG_PRINTK_TIME=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_CONSOLE_UNHASHED_POINTERS=y +CONFIG_DEBUG_MODULE_LOAD_INFO=y +CONFIG_DEBUG_INFO=y +CONFIG_FRAME_WARN=2048 +CONFIG_PAGE_OWNER=y +CONFIG_PAGE_OWNER_ENABLE_DEFAULT=y +CONFIG_DEBUG_SECTION_MISMATCH=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_DEBUG_PAGEALLOC=y +CONFIG_SLUB_DEBUG_PANIC_ON=y +CONFIG_DEBUG_PANIC_ON_OOM=y +CONFIG_DEBUG_PAGEALLOC_ENABLE_DEFAULT=y +CONFIG_PAGE_POISONING=y +CONFIG_PAGE_POISONING_ENABLE_DEFAULT=y +CONFIG_DEBUG_OBJECTS=y +CONFIG_DEBUG_OBJECTS_FREE=y +CONFIG_DEBUG_OBJECTS_TIMERS=y +CONFIG_DEBUG_OBJECTS_WORK=y +CONFIG_DEBUG_OBJECTS_RCU_HEAD=y +CONFIG_DEBUG_OBJECTS_PERCPU_COUNTER=y +CONFIG_DEBUG_KMEMLEAK=y +CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE=4000 +CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF=y +CONFIG_DEBUG_STACK_USAGE=y +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_WQ_WATCHDOG=y +CONFIG_PANIC_ON_OOPS=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_PANIC_ON_SCHED_BUG=y +CONFIG_PANIC_ON_RT_THROTTLING=y +CONFIG_SCHED_STACK_END_CHECK=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_DEBUG_SPINLOCK=y +CONFIG_DEBUG_MUTEXES=y +CONFIG_DEBUG_ATOMIC_SLEEP=y +CONFIG_LOCK_TORTURE_TEST=m +CONFIG_DEBUG_SG=y +CONFIG_DEBUG_NOTIFIERS=y +CONFIG_DEBUG_CREDENTIALS=y +CONFIG_FAULT_INJECTION=y +CONFIG_FAIL_PAGE_ALLOC=y +CONFIG_FAULT_INJECTION_DEBUG_FS=y +CONFIG_FAULT_INJECTION_STACKTRACE_FILTER=y +CONFIG_IPC_LOGGING=y +CONFIG_QCOM_RTB=y +CONFIG_QCOM_RTB_SEPARATE_CPUS=y +CONFIG_PREEMPTIRQ_EVENTS=y +CONFIG_IRQSOFF_TRACER=y +CONFIG_PREEMPT_TRACER=y +CONFIG_BLK_DEV_IO_TRACE=y +CONFIG_LKDTM=y +CONFIG_ATOMIC64_SELFTEST=m +CONFIG_MEMTEST=y +CONFIG_BUG_ON_DATA_CORRUPTION=y +CONFIG_PANIC_ON_DATA_CORRUPTION=y +CONFIG_DEBUG_USER=y +CONFIG_FORCE_PAGES=y +CONFIG_PID_IN_CONTEXTIDR=y diff --git a/arch/arm/mach-qcom/Kconfig b/arch/arm/mach-qcom/Kconfig index 78f3125cca6f..6c62f76b01a0 100644 --- a/arch/arm/mach-qcom/Kconfig +++ b/arch/arm/mach-qcom/Kconfig @@ -130,6 +130,42 @@ config ARCH_MSM8917 select COMMON_CLK select COMMON_CLK_QCOM +config ARCH_MSM8937 + bool "Enable support for MSM8937" + select CPU_V7 + select HAVE_ARM_ARCH_TIMER + select PINCTRL + select QCOM_SCM if SMP + select PM_DEVFREQ + select CLKDEV_LOOKUP + select HAVE_CLK + select HAVE_CLK_PREPARE + select COMMON_CLK_QCOM + +config ARCH_SDM439 + bool "Enable support for SDM439" + select CPU_V7 + select HAVE_ARM_ARCH_TIMER + select PINCTRL + select QCOM_SCM if SMP + select PM_DEVFREQ + select CLKDEV_LOOKUP + select HAVE_CLK + select HAVE_CLK_PREPARE + select COMMON_CLK_QCOM + +config ARCH_SDM429 + bool "Enable support for SDM429" + select CPU_V7 + select HAVE_ARM_ARCH_TIMER + select PINCTRL + select QCOM_SCM if SMP + select PM_DEVFREQ + select CLKDEV_LOOKUP + select HAVE_CLK + select HAVE_CLK_PREPARE + select COMMON_CLK_QCOM + config ARCH_SCUBA bool "Enable Support for Qualcomm Technologies, Inc. SCUBA" select COMMON_CLK_QCOM @@ -153,5 +189,27 @@ config ARCH_SCUBA This enables support for the SCUBA chipset. If you do not wish to build a kernel that runs on this chipset, say 'N' here. +config ARCH_MSM8953 + bool "Enable support for MSM8953" + select CPU_V7 + select HAVE_ARM_ARCH_TIMER + select PINCTRL + select QCOM_SCM if SMP + select PM_DEVFREQ + select COMMON_CLK + select COMMON_CLK_QCOM + select QCOM_GDSC + +config ARCH_SDM450 + bool "Enable support for SDM450" + select CPU_V7 + select HAVE_ARM_ARCH_TIMER + select PINCTRL + select QCOM_SCM if SMP + select PM_DEVFREQ + select COMMON_CLK + select COMMON_CLK_QCOM + select QCOM_GDSC + endmenu endif diff --git a/arch/arm/mach-qcom/Makefile b/arch/arm/mach-qcom/Makefile index d234afa48d92..ffa82f6d1767 100644 --- a/arch/arm/mach-qcom/Makefile +++ b/arch/arm/mach-qcom/Makefile @@ -4,3 +4,9 @@ obj-$(CONFIG_ARCH_BENGAL) += board-bengal.o obj-$(CONFIG_ARCH_SCUBA) += board-scuba.o obj-$(CONFIG_ARCH_SDM660) += board-660.o obj-$(CONFIG_ARCH_MSM8917) += board-msm8917.o +obj-$(CONFIG_ARCH_QM215) += board-qm215.o +obj-$(CONFIG_ARCH_MSM8953) += board-msm8953.o +obj-$(CONFIG_ARCH_SDM450) += board-sdm450.o +obj-$(CONFIG_ARCH_MSM8937) += board-msm8937.o +obj-$(CONFIG_ARCH_SDM429) += board-sdm429.o +obj-$(CONFIG_ARCH_SDM439) += board-sdm439.o diff --git a/arch/arm/mach-qcom/board-bengal.c b/arch/arm/mach-qcom/board-bengal.c index 37f7a72d2bc0..fdd00a11c015 100644 --- a/arch/arm/mach-qcom/board-bengal.c +++ b/arch/arm/mach-qcom/board-bengal.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #include @@ -12,6 +11,7 @@ static const char *trinket_dt_match[] __initconst = { "qcom,bengal", "qcom,bengal-iot", + "qcom,bengalp-iot", NULL }; diff --git a/arch/arm/mach-qcom/board-msm8937.c b/arch/arm/mach-qcom/board-msm8937.c new file mode 100644 index 000000000000..bbea40dd74c3 --- /dev/null +++ b/arch/arm/mach-qcom/board-msm8937.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018, 2021, The Linux Foundation. All rights reserved. + */ + +#include +#include "board-dt.h" +#include +#include + +static const char *msm8937_dt_match[] __initconst = { + "qcom,msm8937", + NULL +}; + +static void __init msm8937_init(void) +{ + board_dt_populate(NULL); +} + +DT_MACHINE_START(MSM8937_DT, + "Qualcomm Technologies, Inc. MSM8937 (Flattened Device Tree)") + .init_machine = msm8937_init, + .dt_compat = msm8937_dt_match, +MACHINE_END diff --git a/arch/arm/mach-qcom/board-msm8953.c b/arch/arm/mach-qcom/board-msm8953.c new file mode 100644 index 000000000000..ab2ac57b5fd7 --- /dev/null +++ b/arch/arm/mach-qcom/board-msm8953.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017,2021, The Linux Foundation. All rights reserved. + */ + +#include +#include "board-dt.h" +#include +#include + +static const char *msm8953_dt_match[] __initconst = { + "qcom,msm8953", + NULL +}; + +static void __init msm8953_init(void) +{ + board_dt_populate(NULL); +} + +DT_MACHINE_START(MSM8953_DT, + "Qualcomm Technologies, Inc. MSM8953 (Flattened Device Tree)") + .init_machine = msm8953_init, + .dt_compat = msm8953_dt_match, +MACHINE_END diff --git a/arch/arm/mach-qcom/board-qm215.c b/arch/arm/mach-qcom/board-qm215.c new file mode 100644 index 000000000000..3cdeab98a7fc --- /dev/null +++ b/arch/arm/mach-qcom/board-qm215.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018, 2021, The Linux Foundation. All rights reserved. + */ + +#include +#include "board-dt.h" +#include +#include + +static const char *qm215_dt_match[] __initconst = { + "qcom,qm215", + NULL +}; + +static void __init qm215_init(void) +{ + board_dt_populate(NULL); +} + +DT_MACHINE_START(QM215_DT, + "Qualcomm Technologies, Inc. QM215") + .init_machine = qm215_init, + .dt_compat = qm215_dt_match, +MACHINE_END diff --git a/arch/arm/mach-qcom/board-scuba.c b/arch/arm/mach-qcom/board-scuba.c index 1c4becebc92f..361ad53d7a1d 100644 --- a/arch/arm/mach-qcom/board-scuba.c +++ b/arch/arm/mach-qcom/board-scuba.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2020, The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #include diff --git a/arch/arm/mach-qcom/board-sdm429.c b/arch/arm/mach-qcom/board-sdm429.c new file mode 100644 index 000000000000..c59daffe39b0 --- /dev/null +++ b/arch/arm/mach-qcom/board-sdm429.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018, 2021, The Linux Foundation. All rights reserved. + */ + +#include +#include "board-dt.h" +#include +#include + +static const char *sdm429_dt_match[] __initconst = { + "qcom,sdm429", + NULL +}; + +static void __init sdm429_init(void) +{ + board_dt_populate(NULL); +} + +DT_MACHINE_START(SDM429_DT, + "Qualcomm Technologies, Inc. SDM429 (Flattened Device Tree)") + .init_machine = sdm429_init, + .dt_compat = sdm429_dt_match, +MACHINE_END diff --git a/arch/arm/mach-qcom/board-sdm439.c b/arch/arm/mach-qcom/board-sdm439.c new file mode 100644 index 000000000000..59079a8ec15a --- /dev/null +++ b/arch/arm/mach-qcom/board-sdm439.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018, 2021, The Linux Foundation. All rights reserved. + */ + +#include +#include "board-dt.h" +#include +#include + +static const char *sdm439_dt_match[] __initconst = { + "qcom,sdm439", + NULL +}; + +static void __init sdm439_init(void) +{ + board_dt_populate(NULL); +} + +DT_MACHINE_START(SDM439_DT, + "Qualcomm Technologies, Inc. SDM439 (Flattened Device Tree)") + .init_machine = sdm439_init, + .dt_compat = sdm439_dt_match, +MACHINE_END diff --git a/arch/arm/mach-qcom/board-sdm450.c b/arch/arm/mach-qcom/board-sdm450.c new file mode 100644 index 000000000000..49a34cf1edb7 --- /dev/null +++ b/arch/arm/mach-qcom/board-sdm450.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017,2021, The Linux Foundation. All rights reserved. + */ + +#include +#include "board-dt.h" +#include +#include + +static const char *sdm450_dt_match[] __initconst = { + "qcom,sdm450", + NULL +}; + +static void __init sdm450_init(void) +{ + board_dt_populate(NULL); +} + +DT_MACHINE_START(SDM450_DT, + "Qualcomm Technologies, Inc. SDM450 (Flattened Device Tree)") + .init_machine = sdm450_init, + .dt_compat = sdm450_dt_match, +MACHINE_END diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index f0dfdad63de6..80b0a65347b5 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -20,7 +20,6 @@ config ARM64 select ARCH_HAS_KCOV select ARCH_HAS_MEMBARRIER_SYNC_CORE select ARCH_HAS_PTE_SPECIAL - select ARCH_HAS_REFCOUNT_FULL select ARCH_HAS_SET_MEMORY select ARCH_HAS_SG_CHAIN select ARCH_HAS_STRICT_KERNEL_RWX @@ -505,8 +504,6 @@ config ARM64_ERRATUM_1024718 config ARM64_ERRATUM_1188873 bool "Cortex-A76: MRC read following MRRC read of specific Generic Timer in AArch32 might give incorrect result" default y - select ARM_ARCH_TIMER_OOL_WORKAROUND - depends on ARM_ARCH_TIMER help This option adds work arounds for ARM Cortex-A76 erratum 1188873 @@ -1579,13 +1576,6 @@ config KRYO_PMU_WORKAROUND Enable this flag for effect SoCs. -config BOOT_INFO - bool "Boot information from bootloader" - default y - help - On embedded linux device, we try to collect more information from - bootloader to kernel. eg. powerup reason. - config BUILD_ARM64_DT_OVERLAY bool "enable DT overlay compilation support" depends on OF diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index 4f5b70de7682..a148ceceb363 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -248,6 +248,26 @@ config ARCH_SDM439 This enables support for the sdm439 chipset. If you do not wish to build a kernel that runs on this chipset, say 'N' here. +config ARCH_MSM8953 + bool "Enable Support for Qualcomm Technologies Inc. MSM8953" + depends on ARCH_QCOM + select COMMON_CLK_QCOM + select QCOM_GDSC + select CPU_FREQ_QCOM + help + This enables support for the MSM8953 chipset. If you do not + wish to build a kernel that runs on this chipset, say 'N' here. + +config ARCH_SDM450 + bool "Enable Support for Qualcomm Technologies Inc. SDM450" + depends on ARCH_QCOM + select COMMON_CLK_QCOM + select QCOM_GDSC + select CPU_FREQ_QCOM + help + This enables support for the sdm450 chipset. If you do not + wish to build a kernel that runs on this chipset, say 'N' here. + config ARCH_ROCKCHIP bool "Rockchip Platforms" select ARCH_HAS_RESET_CONTROLLER diff --git a/arch/arm64/Makefile b/arch/arm64/Makefile index e3f78c225450..ddb607140f7c 100644 --- a/arch/arm64/Makefile +++ b/arch/arm64/Makefile @@ -148,10 +148,6 @@ endif KBUILD_DTBS := dtbs -KBUILD_DTBO_IMG := dtbo.img - -KBUILD_DTB_IMG := dtb.img - ifeq ($(CONFIG_BUILD_ARM64_DT_OVERLAY),y) export DTC_FLAGS := -@ endif @@ -184,17 +180,6 @@ Image-dtb: vmlinux scripts dtbs Image.gz-dtb: vmlinux scripts dtbs Image.gz $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@ -ifeq ($(CONFIG_BUILD_ARM64_DT_OVERLAY),y) -$(KBUILD_DTBO_IMG): dtbs - $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@ - -all: $(KBUILD_DTBO_IMG) -$(KBUILD_DTB_IMG): dtbs - $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@ - -all: $(KBUILD_DTB_IMG) -endif - PHONY += vdso_install vdso_install: $(Q)$(MAKE) $(build)=arch/arm64/kernel/vdso $@ diff --git a/arch/arm64/boot/Makefile b/arch/arm64/boot/Makefile index 2469ee1b7a0d..4715d40a6d4b 100644 --- a/arch/arm64/boot/Makefile +++ b/arch/arm64/boot/Makefile @@ -18,7 +18,7 @@ include $(srctree)/arch/arm64/boot/dts/Makefile OBJCOPYFLAGS_Image :=-O binary -R .note -R .note.gnu.build-id -R .comment -S -targets := Image Image.bz2 Image.gz Image.lz4 Image.lzma Image.lzo dtbo.img dtb.img +targets := Image Image.bz2 Image.gz Image.lz4 Image.lzma Image.lzo DTB_NAMES := $(subst $\",,$(CONFIG_BUILD_ARM64_APPENDED_DTB_IMAGE_NAMES)) ifneq ($(DTB_NAMES),) @@ -28,8 +28,6 @@ DTB_LIST := $(dtb-y) endif DTB_OBJS := $(shell find $(obj)/dts/ -name \*.dtb) -DTBO_OBJS := $(shell find $(obj)/dts/ -name \*.dtbo) - # Add RTIC DTB to the DTB list if RTIC MPGen is enabled # Note, we keep this for compatibility with # BUILD_ARM64_APPENDED_DTB_IMAGE targets. @@ -70,12 +68,6 @@ $(obj)/Image.lzo: $(obj)/Image FORCE $(obj)/Image.gz-dtb: $(obj)/Image.gz $(DTB_OBJS) FORCE $(call if_changed,cat) -$(obj)/dtbo.img: $(DTBO_OBJS) FORCE - $(call if_changed,mkdtimg) - -$(obj)/dtb.img: $(DTB_OBJS) FORCE - $(call if_changed,cat) - install: $(CONFIG_SHELL) $(srctree)/$(src)/install.sh $(KERNELRELEASE) \ $(obj)/Image System.map "$(INSTALL_PATH)" diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile index 529a44cbbbcb..254262de63ad 100644 --- a/arch/arm64/boot/dts/Makefile +++ b/arch/arm64/boot/dts/Makefile @@ -16,6 +16,7 @@ subdir-y += lg subdir-y += marvell subdir-y += mediatek subdir-y += nvidia +subdir-y += qcom subdir-y += realtek subdir-y += renesas subdir-y += rockchip diff --git a/arch/arm64/configs/vendor/bengal-perf_defconfig b/arch/arm64/configs/vendor/bengal-perf_defconfig index 822eb718ee4c..17416d519bd6 100644 --- a/arch/arm64/configs/vendor/bengal-perf_defconfig +++ b/arch/arm64/configs/vendor/bengal-perf_defconfig @@ -1,15 +1,13 @@ -CONFIG_LOCALVERSION="-CartelProject" +CONFIG_LOCALVERSION="-perf" # CONFIG_LOCALVERSION_AUTO is not set CONFIG_AUDIT=y # CONFIG_AUDITSYSCALL is not set -CONFIG_BOEFFLA_WL_BLOCKER=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_PREEMPT=y CONFIG_IRQ_TIME_ACCOUNTING=y CONFIG_SCHED_WALT=y CONFIG_TASKSTATS=y -CONFIG_TASK_DELAY_ACCT=y CONFIG_TASK_XACCT=y CONFIG_TASK_IO_ACCOUNTING=y CONFIG_PSI=y @@ -19,7 +17,7 @@ CONFIG_RCU_FAST_NO_HZ=y CONFIG_RCU_NOCB_CPU=y CONFIG_IKCONFIG=y CONFIG_IKCONFIG_PROC=y -CONFIG_LOG_BUF_SHIFT=21 +CONFIG_IKHEADERS=y CONFIG_LOG_CPU_MAX_BUF_SHIFT=17 CONFIG_MEMCG=y CONFIG_MEMCG_SWAP=y @@ -37,11 +35,13 @@ CONFIG_BLK_DEV_INITRD=y # CONFIG_RD_BZIP2 is not set # CONFIG_RD_LZMA is not set # CONFIG_RD_XZ is not set +# CONFIG_RD_LZO is not set # CONFIG_RD_LZ4 is not set # CONFIG_FHANDLE is not set +CONFIG_KALLSYMS_ALL=y CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT_ALWAYS_ON=y CONFIG_EMBEDDED=y -# CONFIG_SLUB_DEBUG is not set # CONFIG_COMPAT_BRK is not set CONFIG_SLAB_FREELIST_RANDOM=y CONFIG_SLAB_FREELIST_HARDENED=y @@ -50,9 +50,9 @@ CONFIG_PROFILING=y CONFIG_HOTPLUG_SIZE_BITS=29 CONFIG_ARCH_QCOM=y CONFIG_ARCH_BENGAL=y +CONFIG_ARCH_SCUBA=y CONFIG_SCHED_MC=y CONFIG_NR_CPUS=8 -CONFIG_HZ_100=y CONFIG_SECCOMP=y # CONFIG_UNMAP_KERNEL_AT_EL0 is not set CONFIG_HARDEN_BRANCH_PREDICTOR=y @@ -63,9 +63,6 @@ CONFIG_SETEND_EMULATION=y CONFIG_ARM64_SW_TTBR0_PAN=y # CONFIG_ARM64_VHE is not set CONFIG_RANDOMIZE_BASE=y -CONFIG_CMDLINE="ramoops_memreserve=4M mitigations=off" -CONFIG_BUILD_ARM64_UNCOMPRESSED_KERNEL=y -CONFIG_BUILD_ARM64_DT_OVERLAY=y CONFIG_COMPAT=y CONFIG_PM_WAKELOCKS=y CONFIG_PM_WAKELOCKS_LIMIT=0 @@ -74,7 +71,6 @@ CONFIG_ENERGY_MODEL=y CONFIG_CPU_IDLE=y CONFIG_ARM_CPUIDLE=y CONFIG_CPU_FREQ=y -CONFIG_CPU_FREQ_STAT=y CONFIG_CPU_FREQ_TIMES=y CONFIG_CPU_FREQ_GOV_POWERSAVE=y CONFIG_CPU_FREQ_GOV_USERSPACE=y @@ -83,6 +79,7 @@ CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y CONFIG_CPU_BOOST=y CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y CONFIG_ARM_QCOM_CPUFREQ_HW=y +CONFIG_MSM_TZ_LOG=y CONFIG_ARM64_CRYPTO=y CONFIG_CRYPTO_SHA1_ARM64_CE=y CONFIG_CRYPTO_SHA2_ARM64_CE=y @@ -90,8 +87,7 @@ CONFIG_CRYPTO_GHASH_ARM64_CE=y CONFIG_CRYPTO_AES_ARM64_CE_CCM=y CONFIG_CRYPTO_AES_ARM64_CE_BLK=y CONFIG_CRYPTO_AES_ARM64_NEON_BLK=y -# CONFIG_STACKPROTECTOR is not set -# CONFIG_VMAP_STACK is not set +CONFIG_PANIC_ON_REFCOUNT_ERROR=y CONFIG_MODULES=y CONFIG_MODULE_UNLOAD=y CONFIG_MODVERSIONS=y @@ -99,7 +95,6 @@ CONFIG_MODULE_SIG=y CONFIG_MODULE_SIG_FORCE=y CONFIG_MODULE_SIG_SHA512=y # CONFIG_BLK_DEV_BSG is not set -CONFIG_BLK_WBT=y CONFIG_BLK_INLINE_ENCRYPTION=y CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK=y CONFIG_PARTITION_ADVANCED=y @@ -237,7 +232,6 @@ CONFIG_L2TP_V3=y CONFIG_L2TP_IP=y CONFIG_L2TP_ETH=y CONFIG_BRIDGE=y -# CONFIG_BRIDGE_IGMP_SNOOPING is not set CONFIG_NET_SCHED=y CONFIG_NET_SCH_HTB=y CONFIG_NET_SCH_PRIO=y @@ -275,6 +269,7 @@ CONFIG_FW_LOADER_USER_HELPER=y CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y # CONFIG_FW_CACHE is not set CONFIG_REGMAP_WCD_IRQ=y +CONFIG_REGMAP_ALLOW_WRITE_DEBUGFS=y CONFIG_DMA_CMA=y CONFIG_ZRAM=y CONFIG_ZRAM_DEDUP=y @@ -284,11 +279,7 @@ CONFIG_BLK_DEV_RAM=y CONFIG_BLK_DEV_RAM_SIZE=8192 CONFIG_QSEECOM=y CONFIG_UID_SYS_STATS=y -CONFIG_SIMTRAY_STATUS=y -CONFIG_DPDT_STATUS=y CONFIG_FPR_FPC=y -CONFIG_MI_FS=y -CONFIG_HQ_SYSFS_SUPPORT=y CONFIG_SCSI=y CONFIG_BLK_DEV_SD=y CONFIG_CHR_DEV_SG=y @@ -346,10 +337,10 @@ CONFIG_INPUT_JOYSTICK=y CONFIG_JOYSTICK_XPAD=y CONFIG_JOYSTICK_XPAD_LEDS=y CONFIG_INPUT_TOUCHSCREEN=y -CONFIG_TOUCHSCREEN_NT36672A=y -CONFIG_VITURALSAR=y -CONFIG_TOUCHSCREEN_FT8719=y -CONFIG_TOUCHSCREEN_XIAOMI_TOUCHFEATURE=y +# CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_REFLASH is not set +# CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_RECOVERY is not set +# CONFIG_TOUCHSCREEN_FTS is not set +# CONFIG_TOUCHSCREEN_NT36XXX is not set CONFIG_INPUT_MISC=y CONFIG_INPUT_QPNP_POWER_ON=y CONFIG_INPUT_UINPUT=y @@ -363,7 +354,7 @@ CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM_MSM_LEGACY=y CONFIG_DIAG_CHAR=y CONFIG_MSM_ADSPRPC=y -CONFIG_MSM_RDBG=y +CONFIG_MSM_RDBG=m CONFIG_I2C_CHARDEV=y CONFIG_I2C_QCOM_GENI=y CONFIG_SPI=y @@ -372,6 +363,7 @@ CONFIG_SPI_SPIDEV=y CONFIG_SPMI=y CONFIG_PINCTRL_QCOM_SPMI_PMIC=y CONFIG_PINCTRL_BENGAL=y +CONFIG_PINCTRL_SCUBA=y CONFIG_GPIO_SYSFS=y CONFIG_POWER_RESET_QCOM=y CONFIG_POWER_RESET_XGENE=y @@ -410,11 +402,6 @@ CONFIG_REGULATOR_QPNP_LCDB=y CONFIG_REGULATOR_RPM_SMD=y CONFIG_REGULATOR_STUB=y CONFIG_REGULATOR_PM8008=y -CONFIG_RC_CORE=y -CONFIG_LIRC=y -CONFIG_RC_DECODERS=y -CONFIG_IR_SPI=y -CONFIG_RC_DEVICES=y CONFIG_MEDIA_SUPPORT=y CONFIG_MEDIA_CAMERA_SUPPORT=y CONFIG_MEDIA_DIGITAL_TV_SUPPORT=y @@ -422,13 +409,12 @@ CONFIG_MEDIA_CONTROLLER=y CONFIG_VIDEO_V4L2_SUBDEV_API=y CONFIG_VIDEO_FIXED_MINOR_RANGES=y CONFIG_V4L_PLATFORM_DRIVERS=y -CONFIG_DVB_MPQ=y -CONFIG_DVB_MPQ_DEMUX=y +CONFIG_DVB_MPQ=m +CONFIG_DVB_MPQ_DEMUX=m CONFIG_DVB_MPQ_SW=y CONFIG_VIDEO_V4L2_VIDEOBUF2_CORE=y CONFIG_DRM=y CONFIG_BACKLIGHT_LCD_SUPPORT=y -CONFIG_LCD_CLASS_DEVICE=y CONFIG_BACKLIGHT_CLASS_DEVICE=y CONFIG_LOGO=y # CONFIG_LOGO_LINUX_MONO is not set @@ -495,7 +481,7 @@ CONFIG_LEDS_QTI_FLASH=y CONFIG_LEDS_PWM=y CONFIG_LEDS_QTI_TRI_LED=y CONFIG_LEDS_QPNP_FLASH_V2=y -CONFIG_LEDS_QPNP_VIBRATOR_LDO=y +# CONFIG_LEDS_QPNP_VIBRATOR_LDO is not set CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_RTC_CLASS=y CONFIG_RTC_DRV_PM8XXX=y @@ -507,7 +493,6 @@ CONFIG_STAGING=y CONFIG_ASHMEM=y CONFIG_ION=y CONFIG_ION_POOL_AUTO_REFILL=y -CONFIG_QCA_CLD_WLAN=y CONFIG_QPNP_REVID=y CONFIG_SPS=y CONFIG_SPS_SUPPORT_NDP_BAM=y @@ -515,14 +500,17 @@ CONFIG_IPA3=y CONFIG_IPA_WDI_UNIFIED_API=y CONFIG_RMNET_IPA3=y CONFIG_RNDIS_IPA=y +CONFIG_IPA_UT=y CONFIG_USB_BAM=y CONFIG_QCOM_GENI_SE=y -CONFIG_MACH_XIAOMI_LIME=y CONFIG_QCOM_CLK_SMD_RPM=y CONFIG_SPMI_PMIC_CLKDIV=y CONFIG_SM_GPUCC_BENGAL=y CONFIG_SM_DISPCC_BENGAL=y CONFIG_SM_DEBUGCC_BENGAL=y +CONFIG_QM_DISPCC_SCUBA=y +CONFIG_QM_GPUCC_SCUBA=y +CONFIG_QM_DEBUGCC_SCUBA=y CONFIG_HWSPINLOCK=y CONFIG_HWSPINLOCK_QCOM=y CONFIG_MAILBOX=y @@ -530,6 +518,8 @@ CONFIG_QCOM_APCS_IPC=y CONFIG_IOMMU_IO_PGTABLE_FAST=y CONFIG_ARM_SMMU=y CONFIG_QCOM_LAZY_MAPPING=y +CONFIG_IOMMU_DEBUG=y +CONFIG_IOMMU_TESTS=y CONFIG_RPMSG_CHAR=y CONFIG_RPMSG_QCOM_GLINK_RPM=y CONFIG_RPMSG_QCOM_GLINK_SMEM=y @@ -571,16 +561,15 @@ CONFIG_QCOM_GLINK_PKT=y CONFIG_QCOM_SMP2P_SLEEPSTATE=y CONFIG_MSM_CDSP_LOADER=y CONFIG_QCOM_SMCINVOKE=y -CONFIG_SERIAL_NUM=y CONFIG_MSM_EVENT_TIMER=y CONFIG_MSM_PM=y CONFIG_QTI_L2_REUSE=y CONFIG_QTI_RPM_STATS_LOG=y CONFIG_QTEE_SHM_BRIDGE=y CONFIG_MEM_SHARE_QMI_SERVICE=y -# CONFIG_MSM_PERFORMANCE is not set +CONFIG_MSM_PERFORMANCE=y CONFIG_QCOM_CDSP_RM=y -# CONFIG_QCOM_QHEE_ENABLE_MEM_PROTECTION is not set +CONFIG_QCOM_QHEE_ENABLE_MEM_PROTECTION=y CONFIG_QCOM_CX_IPEAK=y CONFIG_QTI_CRYPTO_COMMON=y CONFIG_QTI_CRYPTO_TZ=y @@ -610,9 +599,7 @@ CONFIG_ANDROID_BINDERFS=y # CONFIG_NVMEM_SYSFS is not set CONFIG_QCOM_QFPROM=y CONFIG_NVMEM_SPMI_SDAM=y -CONFIG_STM=y CONFIG_SLIMBUS_MSM_NGD=y -CONFIG_SWITCH=y CONFIG_QCOM_KGSL=y CONFIG_EXT4_FS=y CONFIG_EXT4_FS_POSIX_ACL=y @@ -631,19 +618,10 @@ CONFIG_OVERLAY_FS=y CONFIG_INCREMENTAL_FS=y CONFIG_MSDOS_FS=y CONFIG_VFAT_FS=y -CONFIG_EXFAT_FS=y -CONFIG_EXFAT_DEFAULT_CODEPAGE=437 -CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8" -CONFIG_NTFS_FS=y -CONFIG_NTFS_RW=y CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y CONFIG_EFIVAR_FS=y CONFIG_SDCARD_FS=y -CONFIG_PSTORE=y -CONFIG_PSTORE_CONSOLE=y -CONFIG_PSTORE_PMSG=y -CONFIG_PSTORE_RAM=y # CONFIG_NETWORK_FILESYSTEMS is not set CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_ISO8859_1=y @@ -658,37 +636,39 @@ CONFIG_SECURITY_SELINUX=y CONFIG_SECURITY_SMACK=y CONFIG_CRYPTO_GCM=y CONFIG_CRYPTO_XCBC=y -CONFIG_CRYPTO_MD4=y CONFIG_CRYPTO_TWOFISH=y CONFIG_CRYPTO_ANSI_CPRNG=y CONFIG_CRYPTO_DEV_QCE=y CONFIG_CRYPTO_DEV_QCOM_MSM_QCE=y CONFIG_CRYPTO_DEV_QCRYPTO=y CONFIG_CRYPTO_DEV_QCEDEV=y -CONFIG_STRIP_ASM_SYMS=y +CONFIG_STACK_HASH_ORDER_SHIFT=12 +CONFIG_PRINTK_TIME=y +CONFIG_DEBUG_INFO=y +CONFIG_PAGE_OWNER=y +# CONFIG_SECTION_MISMATCH_WARN_ONLY is not set +CONFIG_MAGIC_SYSRQ=y CONFIG_PANIC_TIMEOUT=5 -CONFIG_STACKTRACE=y -# CONFIG_RUNTIME_TESTING_MENU is not set -# CONFIG_ARM64_ERRATUM_826319 is not set -# CONFIG_ARM64_ERRATUM_827319 is not set -# CONFIG_ARM64_ERRATUM_824069 is not set -# CONFIG_ARM64_ERRATUM_819472 is not set -# CONFIG_ARM64_ERRATUM_832075 is not set -# CONFIG_ARM64_ERRATUM_845719 is not set -# CONFIG_ARM64_ERRATUM_843419 is not set -# CONFIG_ARM64_ERRATUM_1024718 is not set -# CONFIG_ARM64_ERRATUM_1463225 is not set -# CONFIG_ARM64_ERRATUM_1542418 is not set -# CONFIG_CAVIUM_ERRATUM_22375 is not set -# CONFIG_CAVIUM_ERRATUM_23154 is not set -# CONFIG_CAVIUM_ERRATUM_27456 is not set -# CONFIG_CAVIUM_ERRATUM_30115 is not set -# CONFIG_QCOM_FALKOR_ERRATUM_1003 is not set -# CONFIG_QCOM_FALKOR_ERRATUM_1009 is not set -# CONFIG_QCOM_QDF2400_ERRATUM_0065 is not set -# CONFIG_SOCIONEXT_SYNQUACER_PREITS is not set -# CONFIG_HISILICON_ERRATUM_161600802 is not set -# CONFIG_QCOM_FALKOR_ERRATUM_E1041 is not set -# CONFIG_FSL_ERRATUM_A008585 is not set -# CONFIG_HISILICON_ERRATUM_161010101 is not set -# CONFIG_ARM64_ERRATUM_858921 is not set +CONFIG_SCHEDSTATS=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_DEBUG_LIST=y +CONFIG_IPC_LOGGING=y +CONFIG_CORESIGHT=y +CONFIG_CORESIGHT_LINK_AND_SINK_TMC=y +CONFIG_CORESIGHT_DYNAMIC_REPLICATOR=y +CONFIG_CORESIGHT_STM=y +CONFIG_CORESIGHT_CTI=y +CONFIG_CORESIGHT_CTI_SAVE_DISABLE=y +CONFIG_CORESIGHT_TPDA=y +CONFIG_CORESIGHT_TPDM=y +CONFIG_CORESIGHT_HWEVENT=y +CONFIG_CORESIGHT_DUMMY=y +CONFIG_CORESIGHT_REMOTE_ETM=y +CONFIG_CORESIGHT_TGU=y +CONFIG_TOUCHSCREEN_GOODIX_GT1X=y +CONFIG_KEYBOARD_AW9523B=y +CONFIG_GPIO_PCA953X=y +CONFIG_LEDS_GPIO=y +# CONFIG_TOUCHSCREEN_ST is not set +CONFIG_AW862XX_HAPTIC=y +CONFIG_MICROARRAY_FINGERPRINT=y diff --git a/arch/arm64/configs/vendor/bengal_defconfig b/arch/arm64/configs/vendor/bengal_defconfig index e66ba00cfe99..3886cd719ae5 100644 --- a/arch/arm64/configs/vendor/bengal_defconfig +++ b/arch/arm64/configs/vendor/bengal_defconfig @@ -54,7 +54,6 @@ CONFIG_ARCH_BENGAL=y CONFIG_ARCH_SCUBA=y CONFIG_SCHED_MC=y CONFIG_NR_CPUS=8 -CONFIG_HZ_100=y CONFIG_SECCOMP=y # CONFIG_UNMAP_KERNEL_AT_EL0 is not set CONFIG_HARDEN_BRANCH_PREDICTOR=y @@ -349,10 +348,10 @@ CONFIG_INPUT_JOYSTICK=y CONFIG_JOYSTICK_XPAD=y CONFIG_JOYSTICK_XPAD_LEDS=y CONFIG_INPUT_TOUCHSCREEN=y -CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_REFLASH=y -CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_RECOVERY=y -CONFIG_TOUCHSCREEN_FTS=y -CONFIG_TOUCHSCREEN_NT36XXX=y +# CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_REFLASH is not set +# CONFIG_TOUCHSCREEN_SYNAPTICS_TCM_RECOVERY is not set +# CONFIG_TOUCHSCREEN_FTS is not set +# CONFIG_TOUCHSCREEN_NT36XXX is not set CONFIG_INPUT_MISC=y CONFIG_INPUT_QPNP_POWER_ON=y CONFIG_INPUT_UINPUT=y @@ -497,7 +496,7 @@ CONFIG_LEDS_QTI_FLASH=y CONFIG_LEDS_PWM=y CONFIG_LEDS_QTI_TRI_LED=y CONFIG_LEDS_QPNP_FLASH_V2=y -CONFIG_LEDS_QPNP_VIBRATOR_LDO=y +# CONFIG_LEDS_QPNP_VIBRATOR_LDO is not set CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_EDAC=y CONFIG_EDAC_CORTEX_ARM64=y @@ -740,3 +739,11 @@ CONFIG_CORESIGHT_HWEVENT=y CONFIG_CORESIGHT_DUMMY=y CONFIG_CORESIGHT_REMOTE_ETM=y CONFIG_CORESIGHT_TGU=y +CONFIG_TOUCHSCREEN_GOODIX_GT1X=y +CONFIG_KEYBOARD_AW9523B=y +CONFIG_GPIO_PCA953X=y +CONFIG_LEDS_GPIO=y +# CONFIG_TOUCHSCREEN_ST is not set +CONFIG_AW862XX_HAPTIC=y +CONFIG_MICROARRAY_FINGERPRINT=y +CONFIG_AW862xx_HAPTIC=y diff --git a/arch/arm64/configs/vendor/kona-perf_defconfig b/arch/arm64/configs/vendor/kona-perf_defconfig index 35cc49ccf2dc..056562f9b6e9 100644 --- a/arch/arm64/configs/vendor/kona-perf_defconfig +++ b/arch/arm64/configs/vendor/kona-perf_defconfig @@ -306,6 +306,7 @@ CONFIG_SCSI_UFS_CRYPTO_QTI=y CONFIG_MD=y CONFIG_BLK_DEV_DM=y CONFIG_DM_CRYPT=y +CONFIG_DM_DEFAULT_KEY=y CONFIG_DM_SNAPSHOT=y CONFIG_DM_UEVENT=y CONFIG_DM_VERITY=y @@ -377,6 +378,7 @@ CONFIG_SPI_QCOM_GENI=y CONFIG_SPI_SPIDEV=y CONFIG_SPMI=y CONFIG_SPMI_MSM_PMIC_ARB_DEBUG=y +CONFIG_PINCTRL_SX150X=y CONFIG_PINCTRL_QCOM_SPMI_PMIC=y CONFIG_PINCTRL_KONA=y CONFIG_GPIO_SYSFS=y @@ -448,6 +450,7 @@ CONFIG_SND_DYNAMIC_MINORS=y CONFIG_SND_USB_AUDIO=y CONFIG_SND_USB_AUDIO_QMI=y CONFIG_SND_SOC=y +CONFIG_HIDRAW=y CONFIG_UHID=y CONFIG_HID_APPLE=y CONFIG_HID_ELECOM=y @@ -530,8 +533,6 @@ CONFIG_STAGING=y CONFIG_ASHMEM=y CONFIG_ION=y CONFIG_ION_POOL_AUTO_REFILL=y -CONFIG_QCA_CLD_WLAN=y -CONFIG_QCA_CLD_WLAN_PROFILE="qca6390" CONFIG_QPNP_REVID=y CONFIG_SPS=y CONFIG_SPS_SUPPORT_NDP_BAM=y @@ -645,6 +646,8 @@ CONFIG_DEVFREQ_GOV_STATICMAP=y CONFIG_EXTCON_USB_GPIO=y CONFIG_IIO=y CONFIG_QCOM_SPMI_ADC5=y +CONFIG_CH101_I2C=y +CONFIG_ADS7052_TDK_THERMISTOR=y CONFIG_PWM=y CONFIG_PWM_QTI_LPG=y CONFIG_QCOM_PDC=y diff --git a/arch/arm64/configs/vendor/kona_defconfig b/arch/arm64/configs/vendor/kona_defconfig index 27da9f7f201b..36ef31e29ee9 100644 --- a/arch/arm64/configs/vendor/kona_defconfig +++ b/arch/arm64/configs/vendor/kona_defconfig @@ -321,6 +321,7 @@ CONFIG_SCSI_UFS_CRYPTO_QTI=y CONFIG_MD=y CONFIG_BLK_DEV_DM=y CONFIG_DM_CRYPT=y +CONFIG_DM_DEFAULT_KEY=y CONFIG_DM_SNAPSHOT=y CONFIG_DM_UEVENT=y CONFIG_DM_VERITY=y @@ -395,6 +396,7 @@ CONFIG_SPI_QCOM_GENI=y CONFIG_SPI_SPIDEV=y CONFIG_SPMI=y CONFIG_SPMI_MSM_PMIC_ARB_DEBUG=y +CONFIG_PINCTRL_SX150X=y CONFIG_PINCTRL_QCOM_SPMI_PMIC=y CONFIG_PINCTRL_KONA=y CONFIG_GPIO_SYSFS=y @@ -467,6 +469,7 @@ CONFIG_SND_DYNAMIC_MINORS=y CONFIG_SND_USB_AUDIO=y CONFIG_SND_USB_AUDIO_QMI=y CONFIG_SND_SOC=y +CONFIG_HIDRAW=y CONFIG_UHID=y CONFIG_HID_APPLE=y CONFIG_HID_ELECOM=y @@ -675,6 +678,8 @@ CONFIG_DEVFREQ_GOV_STATICMAP=y CONFIG_EXTCON_USB_GPIO=y CONFIG_IIO=y CONFIG_QCOM_SPMI_ADC5=y +CONFIG_CH101_I2C=y +CONFIG_ADS7052_TDK_THERMISTOR=y CONFIG_PWM=y CONFIG_PWM_QTI_LPG=y CONFIG_QCOM_PDC=y diff --git a/arch/arm64/include/asm/atomic.h b/arch/arm64/include/asm/atomic.h index fec91ed5f535..9bca54dda75c 100644 --- a/arch/arm64/include/asm/atomic.h +++ b/arch/arm64/include/asm/atomic.h @@ -21,37 +21,13 @@ #define __ASM_ATOMIC_H #include -#include #include #include -#include #include #ifdef __KERNEL__ -/* - * To avoid having to allocate registers that pass the counter address and - * address of the call site to the overflow handler, encode the register and - * call site offset in a dummy cbz instruction that we can decode later. - */ -#define REFCOUNT_CHECK_TAIL \ -" .subsection 1\n" \ -"33: brk " __stringify(REFCOUNT_BRK_IMM) "\n" \ -" cbz %[counter], 22b\n" /* never reached */ \ -" .previous\n" - -#define REFCOUNT_POST_CHECK_NEG \ -"22: b.mi 33f\n" \ - REFCOUNT_CHECK_TAIL - -#define REFCOUNT_POST_CHECK_NEG_OR_ZERO \ -" b.eq 33f\n" \ - REFCOUNT_POST_CHECK_NEG - -#define REFCOUNT_PRE_CHECK_ZERO(reg) "ccmp " #reg ", wzr, #8, pl\n" -#define REFCOUNT_PRE_CHECK_NONE(reg) - #define __ARM64_IN_ATOMIC_IMPL #if defined(CONFIG_ARM64_LSE_ATOMICS) && defined(CONFIG_AS_LSE) diff --git a/arch/arm64/include/asm/atomic_ll_sc.h b/arch/arm64/include/asm/atomic_ll_sc.h index eedf79f425dd..f5a2d09afb38 100644 --- a/arch/arm64/include/asm/atomic_ll_sc.h +++ b/arch/arm64/include/asm/atomic_ll_sc.h @@ -327,54 +327,4 @@ __CMPXCHG_DBL(_mb, dmb ish, l, "memory") #undef __CMPXCHG_DBL -#define REFCOUNT_OP(op, asm_op, pre, post, l) \ -__LL_SC_INLINE int \ -__LL_SC_PREFIX(__refcount_##op(int i, atomic_t *r)) \ -{ \ - unsigned int tmp; \ - int result; \ - \ - asm volatile("// refcount_" #op "\n" \ -" prfm pstl1strm, %[cval]\n" \ -"1: ldxr %w1, %[cval]\n" \ -" " #asm_op " %w[val], %w1, %w[i]\n" \ - REFCOUNT_PRE_CHECK_ ## pre (%w1) \ -" st" #l "xr %w1, %w[val], %[cval]\n" \ -" cbnz %w1, 1b\n" \ - REFCOUNT_POST_CHECK_ ## post \ - : [val] "=&r"(result), "=&r"(tmp), [cval] "+Q"(r->counter) \ - : [counter] "r"(&r->counter), [i] "Ir" (i) \ - : "cc"); \ - \ - return result; \ -} \ -__LL_SC_EXPORT(__refcount_##op); - -REFCOUNT_OP(add_lt, adds, ZERO, NEG_OR_ZERO, ); -REFCOUNT_OP(sub_lt, subs, NONE, NEG, l); -REFCOUNT_OP(sub_le, subs, NONE, NEG_OR_ZERO, l); - -__LL_SC_INLINE int -__LL_SC_PREFIX(__refcount_add_not_zero(int i, atomic_t *r)) -{ - unsigned int tmp; - int result; - - asm volatile("// refcount_add_not_zero\n" -" prfm pstl1strm, %[cval]\n" -"1: ldxr %w[val], %[cval]\n" -" cbz %w[val], 2f\n" -" adds %w[val], %w[val], %w[i]\n" -" stxr %w1, %w[val], %[cval]\n" -" cbnz %w1, 1b\n" - REFCOUNT_POST_CHECK_NEG -"2:" - : [val] "=&r" (result), "=&r" (tmp), [cval] "+Q" (r->counter) - : [counter] "r"(&r->counter), [i] "Ir" (i) - : "cc"); - - return result; -} -__LL_SC_EXPORT(__refcount_add_not_zero); - #endif /* __ASM_ATOMIC_LL_SC_H */ diff --git a/arch/arm64/include/asm/atomic_lse.h b/arch/arm64/include/asm/atomic_lse.h index 6a9b3c317fe4..f9b0b09153e0 100644 --- a/arch/arm64/include/asm/atomic_lse.h +++ b/arch/arm64/include/asm/atomic_lse.h @@ -32,12 +32,7 @@ static inline void atomic_##op(int i, atomic_t *v) \ register int w0 asm ("w0") = i; \ register atomic_t *x1 asm ("x1") = v; \ \ - asm volatile(ARM64_LSE_ATOMIC_INSN( \ - /* LL/SC */ \ - __LL_SC_ATOMIC(op) \ - __nops(1), \ - /* LSE atomics */ \ -" prfm pstl1strm, %[v]\n" \ + asm volatile(ARM64_LSE_ATOMIC_INSN(__LL_SC_ATOMIC(op), \ " " #asm_op " %w[i], %[v]\n") \ : [i] "+r" (w0), [v] "+Q" (v->counter) \ : "r" (x1) \ @@ -59,10 +54,8 @@ static inline int atomic_fetch_##op##name(int i, atomic_t *v) \ \ asm volatile(ARM64_LSE_ATOMIC_INSN( \ /* LL/SC */ \ - __LL_SC_ATOMIC(fetch_##op##name) \ - __nops(1), \ + __LL_SC_ATOMIC(fetch_##op##name), \ /* LSE atomics */ \ -" prfm pstl1strm, %[v]\n" \ " " #asm_op #mb " %w[i], %w[i], %[v]") \ : [i] "+r" (w0), [v] "+Q" (v->counter) \ : "r" (x1) \ @@ -94,9 +87,8 @@ static inline int atomic_add_return##name(int i, atomic_t *v) \ asm volatile(ARM64_LSE_ATOMIC_INSN( \ /* LL/SC */ \ __LL_SC_ATOMIC(add_return##name) \ - __nops(2), \ + __nops(1), \ /* LSE atomics */ \ - " prfm pstl1strm, %[v]\n" \ " ldadd" #mb " %w[i], w30, %[v]\n" \ " add %w[i], %w[i], w30") \ : [i] "+r" (w0), [v] "+Q" (v->counter) \ @@ -121,9 +113,8 @@ static inline void atomic_and(int i, atomic_t *v) asm volatile(ARM64_LSE_ATOMIC_INSN( /* LL/SC */ __LL_SC_ATOMIC(and) - __nops(2), + __nops(1), /* LSE atomics */ - " prfm pstl1strm, %[v]\n" " mvn %w[i], %w[i]\n" " stclr %w[i], %[v]") : [i] "+&r" (w0), [v] "+Q" (v->counter) @@ -140,9 +131,8 @@ static inline int atomic_fetch_and##name(int i, atomic_t *v) \ asm volatile(ARM64_LSE_ATOMIC_INSN( \ /* LL/SC */ \ __LL_SC_ATOMIC(fetch_and##name) \ - __nops(2), \ + __nops(1), \ /* LSE atomics */ \ - " prfm pstl1strm, %[v]\n" \ " mvn %w[i], %w[i]\n" \ " ldclr" #mb " %w[i], %w[i], %[v]") \ : [i] "+&r" (w0), [v] "+Q" (v->counter) \ @@ -167,9 +157,8 @@ static inline void atomic_sub(int i, atomic_t *v) asm volatile(ARM64_LSE_ATOMIC_INSN( /* LL/SC */ __LL_SC_ATOMIC(sub) - __nops(2), + __nops(1), /* LSE atomics */ - " prfm pstl1strm, %[v]\n" " neg %w[i], %w[i]\n" " stadd %w[i], %[v]") : [i] "+&r" (w0), [v] "+Q" (v->counter) @@ -186,9 +175,8 @@ static inline int atomic_sub_return##name(int i, atomic_t *v) \ asm volatile(ARM64_LSE_ATOMIC_INSN( \ /* LL/SC */ \ __LL_SC_ATOMIC(sub_return##name) \ - __nops(3), \ + __nops(2), \ /* LSE atomics */ \ - " prfm pstl1strm, %[v]\n" \ " neg %w[i], %w[i]\n" \ " ldadd" #mb " %w[i], w30, %[v]\n" \ " add %w[i], %w[i], w30") \ @@ -215,9 +203,8 @@ static inline int atomic_fetch_sub##name(int i, atomic_t *v) \ asm volatile(ARM64_LSE_ATOMIC_INSN( \ /* LL/SC */ \ __LL_SC_ATOMIC(fetch_sub##name) \ - __nops(2), \ + __nops(1), \ /* LSE atomics */ \ - " prfm pstl1strm, %[v]\n" \ " neg %w[i], %w[i]\n" \ " ldadd" #mb " %w[i], %w[i], %[v]") \ : [i] "+&r" (w0), [v] "+Q" (v->counter) \ @@ -242,12 +229,7 @@ static inline void atomic64_##op(long i, atomic64_t *v) \ register long x0 asm ("x0") = i; \ register atomic64_t *x1 asm ("x1") = v; \ \ - asm volatile(ARM64_LSE_ATOMIC_INSN( \ - /* LL/SC */ \ - __LL_SC_ATOMIC64(op) \ - __nops(1), \ - /* LSE atomics */ \ -" prfm pstl1strm, %[v]\n" \ + asm volatile(ARM64_LSE_ATOMIC_INSN(__LL_SC_ATOMIC64(op), \ " " #asm_op " %[i], %[v]\n") \ : [i] "+r" (x0), [v] "+Q" (v->counter) \ : "r" (x1) \ @@ -269,10 +251,8 @@ static inline long atomic64_fetch_##op##name(long i, atomic64_t *v) \ \ asm volatile(ARM64_LSE_ATOMIC_INSN( \ /* LL/SC */ \ - __LL_SC_ATOMIC64(fetch_##op##name) \ - __nops(1), \ + __LL_SC_ATOMIC64(fetch_##op##name), \ /* LSE atomics */ \ -" prfm pstl1strm, %[v]\n" \ " " #asm_op #mb " %[i], %[i], %[v]") \ : [i] "+r" (x0), [v] "+Q" (v->counter) \ : "r" (x1) \ @@ -304,9 +284,8 @@ static inline long atomic64_add_return##name(long i, atomic64_t *v) \ asm volatile(ARM64_LSE_ATOMIC_INSN( \ /* LL/SC */ \ __LL_SC_ATOMIC64(add_return##name) \ - __nops(2), \ + __nops(1), \ /* LSE atomics */ \ - " prfm pstl1strm, %[v]\n" \ " ldadd" #mb " %[i], x30, %[v]\n" \ " add %[i], %[i], x30") \ : [i] "+r" (x0), [v] "+Q" (v->counter) \ @@ -331,9 +310,8 @@ static inline void atomic64_and(long i, atomic64_t *v) asm volatile(ARM64_LSE_ATOMIC_INSN( /* LL/SC */ __LL_SC_ATOMIC64(and) - __nops(2), + __nops(1), /* LSE atomics */ - " prfm pstl1strm, %[v]\n" " mvn %[i], %[i]\n" " stclr %[i], %[v]") : [i] "+&r" (x0), [v] "+Q" (v->counter) @@ -350,9 +328,8 @@ static inline long atomic64_fetch_and##name(long i, atomic64_t *v) \ asm volatile(ARM64_LSE_ATOMIC_INSN( \ /* LL/SC */ \ __LL_SC_ATOMIC64(fetch_and##name) \ - __nops(2), \ + __nops(1), \ /* LSE atomics */ \ - " prfm pstl1strm, %[v]\n" \ " mvn %[i], %[i]\n" \ " ldclr" #mb " %[i], %[i], %[v]") \ : [i] "+&r" (x0), [v] "+Q" (v->counter) \ @@ -377,9 +354,8 @@ static inline void atomic64_sub(long i, atomic64_t *v) asm volatile(ARM64_LSE_ATOMIC_INSN( /* LL/SC */ __LL_SC_ATOMIC64(sub) - __nops(2), + __nops(1), /* LSE atomics */ - " prfm pstl1strm, %[v]\n" " neg %[i], %[i]\n" " stadd %[i], %[v]") : [i] "+&r" (x0), [v] "+Q" (v->counter) @@ -396,9 +372,8 @@ static inline long atomic64_sub_return##name(long i, atomic64_t *v) \ asm volatile(ARM64_LSE_ATOMIC_INSN( \ /* LL/SC */ \ __LL_SC_ATOMIC64(sub_return##name) \ - __nops(3), \ + __nops(2), \ /* LSE atomics */ \ - " prfm pstl1strm, %[v]\n" \ " neg %[i], %[i]\n" \ " ldadd" #mb " %[i], x30, %[v]\n" \ " add %[i], %[i], x30") \ @@ -425,9 +400,8 @@ static inline long atomic64_fetch_sub##name(long i, atomic64_t *v) \ asm volatile(ARM64_LSE_ATOMIC_INSN( \ /* LL/SC */ \ __LL_SC_ATOMIC64(fetch_sub##name) \ - __nops(2), \ + __nops(1), \ /* LSE atomics */ \ - " prfm pstl1strm, %[v]\n" \ " neg %[i], %[i]\n" \ " ldadd" #mb " %[i], %[i], %[v]") \ : [i] "+&r" (x0), [v] "+Q" (v->counter) \ @@ -451,9 +425,8 @@ static inline long atomic64_dec_if_positive(atomic64_t *v) asm volatile(ARM64_LSE_ATOMIC_INSN( /* LL/SC */ __LL_SC_ATOMIC64(dec_if_positive) - __nops(7), + __nops(6), /* LSE atomics */ - " prfm pstl1strm, %[v]\n" "1: ldr x30, %[v]\n" " subs %[ret], x30, #1\n" " b.lt 2f\n" @@ -485,9 +458,8 @@ static inline unsigned long __cmpxchg_case_##name(volatile void *ptr, \ asm volatile(ARM64_LSE_ATOMIC_INSN( \ /* LL/SC */ \ __LL_SC_CMPXCHG(name) \ - __nops(3), \ + __nops(2), \ /* LSE atomics */ \ - " prfm pstl1strm, %[v]\n" \ " mov " #w "30, %" #w "[old]\n" \ " cas" #mb #sz "\t" #w "30, %" #w "[new], %[v]\n" \ " mov %" #w "[ret], " #w "30") \ @@ -538,9 +510,8 @@ static inline long __cmpxchg_double##name(unsigned long old1, \ asm volatile(ARM64_LSE_ATOMIC_INSN( \ /* LL/SC */ \ __LL_SC_CMPXCHG_DBL(name) \ - __nops(4), \ + __nops(3), \ /* LSE atomics */ \ - " prfm pstl1strm, %[v]\n" \ " casp" #mb "\t%[old1], %[old2], %[new1], %[new2], %[v]\n"\ " eor %[old1], %[old1], %[oldval1]\n" \ " eor %[old2], %[old2], %[oldval2]\n" \ @@ -560,88 +531,4 @@ __CMPXCHG_DBL(_mb, al, "memory") #undef __LL_SC_CMPXCHG_DBL #undef __CMPXCHG_DBL -#define REFCOUNT_ADD_OP(op, pre, post) \ -static inline int __refcount_##op(int i, atomic_t *r) \ -{ \ - register int w0 asm ("w0") = i; \ - register atomic_t *x1 asm ("x1") = r; \ - \ - asm volatile(ARM64_LSE_ATOMIC_INSN( \ - /* LL/SC */ \ - __LL_SC_CALL(__refcount_##op) \ - " cmp %w0, wzr\n" \ - __nops(2), \ - /* LSE atomics */ \ - " prfm pstl1strm, %[cval]\n" \ - " ldadd %w[i], w30, %[cval]\n" \ - " adds %w[i], %w[i], w30\n" \ - REFCOUNT_PRE_CHECK_ ## pre (w30)) \ - REFCOUNT_POST_CHECK_ ## post \ - : [i] "+r" (w0), [cval] "+Q" (r->counter) \ - : [counter] "r"(&r->counter), "r" (x1) \ - : __LL_SC_CLOBBERS, "cc"); \ - \ - return w0; \ -} - -REFCOUNT_ADD_OP(add_lt, ZERO, NEG_OR_ZERO); - -#define REFCOUNT_SUB_OP(op, post) \ -static inline int __refcount_##op(int i, atomic_t *r) \ -{ \ - register int w0 asm ("w0") = i; \ - register atomic_t *x1 asm ("x1") = r; \ - \ - asm volatile(ARM64_LSE_ATOMIC_INSN( \ - /* LL/SC */ \ - __LL_SC_CALL(__refcount_##op) \ - " cmp %w0, wzr\n" \ - __nops(2), \ - /* LSE atomics */ \ - " prfm pstl1strm, %[cval]\n" \ - " neg %w[i], %w[i]\n" \ - " ldaddl %w[i], w30, %[cval]\n" \ - " adds %w[i], %w[i], w30\n") \ - REFCOUNT_POST_CHECK_ ## post \ - : [i] "+r" (w0), [cval] "+Q" (r->counter) \ - : [counter] "r" (&r->counter), "r" (x1) \ - : __LL_SC_CLOBBERS, "cc"); \ - \ - return w0; \ -} - -REFCOUNT_SUB_OP(sub_lt, NEG); -REFCOUNT_SUB_OP(sub_le, NEG_OR_ZERO); - -static inline int __refcount_add_not_zero(int i, atomic_t *r) -{ - register int result asm ("w0"); - register atomic_t *x1 asm ("x1") = r; - - asm volatile(ARM64_LSE_ATOMIC_INSN( - /* LL/SC */ - " mov %w0, %w[i]\n" - __LL_SC_CALL(__refcount_add_not_zero) - " cmp %w0, wzr\n" - __nops(7), - /* LSE atomics */ - " prfm pstl1strm, %[cval]\n" - " ldr %w0, %[cval]\n" - "1: cmp %w0, wzr\n" - " b.eq 2f\n" - " add w30, %w0, %w[i]\n" - " cas %w0, w30, %[cval]\n" - " sub w30, w30, %w[i]\n" - " cmp %w0, w30\n" - " b.ne 1b\n" - " adds %w0, w30, %w[i]\n" - "2:\n") - REFCOUNT_POST_CHECK_NEG - : "=&r" (result), [cval] "+Q" (r->counter) - : [counter] "r" (&r->counter), [i] "Ir" (i), "r" (x1) - : __LL_SC_CLOBBERS, "cc"); - - return result; -} - #endif /* __ASM_ATOMIC_LSE_H */ diff --git a/arch/arm64/include/asm/brk-imm.h b/arch/arm64/include/asm/brk-imm.h index f3a372e3636e..2945fe6cd863 100644 --- a/arch/arm64/include/asm/brk-imm.h +++ b/arch/arm64/include/asm/brk-imm.h @@ -19,11 +19,9 @@ * 0x9xx: tag-based KASAN trap (allowed values 0x900 - 0x9ff) */ #define FAULT_BRK_IMM 0x100 -#define REFCOUNT_BRK_IMM 0x101 #define KGDB_DYN_DBG_BRK_IMM 0x400 #define KGDB_COMPILED_DBG_BRK_IMM 0x401 #define BUG_BRK_IMM 0x800 #define KASAN_BRK_IMM 0x900 -#define KASAN_BRK_MASK 0x0ff #endif diff --git a/arch/arm64/include/asm/debug-monitors.h b/arch/arm64/include/asm/debug-monitors.h index 4fcafbe4e041..6bd2ab9746f8 100644 --- a/arch/arm64/include/asm/debug-monitors.h +++ b/arch/arm64/include/asm/debug-monitors.h @@ -95,24 +95,18 @@ struct step_hook { int (*fn)(struct pt_regs *regs, unsigned int esr); }; -void register_user_step_hook(struct step_hook *hook); -void unregister_user_step_hook(struct step_hook *hook); - -void register_kernel_step_hook(struct step_hook *hook); -void unregister_kernel_step_hook(struct step_hook *hook); +void register_step_hook(struct step_hook *hook); +void unregister_step_hook(struct step_hook *hook); struct break_hook { struct list_head node; + u32 esr_val; + u32 esr_mask; int (*fn)(struct pt_regs *regs, unsigned int esr); - u16 imm; - u16 mask; /* These bits are ignored when comparing with imm */ }; -void register_user_break_hook(struct break_hook *hook); -void unregister_user_break_hook(struct break_hook *hook); - -void register_kernel_break_hook(struct break_hook *hook); -void unregister_kernel_break_hook(struct break_hook *hook); +void register_break_hook(struct break_hook *hook); +void unregister_break_hook(struct break_hook *hook); u8 debug_monitors_arch(void); diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index 614c456e4d96..f0d7f22fae63 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -44,7 +44,6 @@ arm64-obj-$(CONFIG_CPU_PM) += sleep.o suspend.o arm64-obj-$(CONFIG_CPU_IDLE) += cpuidle.o arm64-obj-$(CONFIG_JUMP_LABEL) += jump_label.o arm64-obj-$(CONFIG_KGDB) += kgdb.o -arm64-obj-$(CONFIG_BOOT_INFO) += bootinfo.o arm64-obj-$(CONFIG_EFI) += efi.o efi-entry.stub.o \ efi-rt-wrapper.o arm64-obj-$(CONFIG_PCI) += pci.o diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c index 041181d20df1..501e835c6500 100644 --- a/arch/arm64/kernel/debug-monitors.c +++ b/arch/arm64/kernel/debug-monitors.c @@ -167,46 +167,25 @@ NOKPROBE_SYMBOL(clear_user_regs_spsr_ss); #define set_regs_spsr_ss(r) set_user_regs_spsr_ss(&(r)->user_regs) #define clear_regs_spsr_ss(r) clear_user_regs_spsr_ss(&(r)->user_regs) -static DEFINE_SPINLOCK(debug_hook_lock); -static LIST_HEAD(user_step_hook); -static LIST_HEAD(kernel_step_hook); +/* EL1 Single Step Handler hooks */ +static LIST_HEAD(step_hook); +static DEFINE_SPINLOCK(step_hook_lock); -static void register_debug_hook(struct list_head *node, struct list_head *list) +void register_step_hook(struct step_hook *hook) { - spin_lock(&debug_hook_lock); - list_add_rcu(node, list); - spin_unlock(&debug_hook_lock); - + spin_lock(&step_hook_lock); + list_add_rcu(&hook->node, &step_hook); + spin_unlock(&step_hook_lock); } -static void unregister_debug_hook(struct list_head *node) +void unregister_step_hook(struct step_hook *hook) { - spin_lock(&debug_hook_lock); - list_del_rcu(node); - spin_unlock(&debug_hook_lock); + spin_lock(&step_hook_lock); + list_del_rcu(&hook->node); + spin_unlock(&step_hook_lock); synchronize_rcu(); } -void register_user_step_hook(struct step_hook *hook) -{ - register_debug_hook(&hook->node, &user_step_hook); -} - -void unregister_user_step_hook(struct step_hook *hook) -{ - unregister_debug_hook(&hook->node); -} - -void register_kernel_step_hook(struct step_hook *hook) -{ - register_debug_hook(&hook->node, &kernel_step_hook); -} - -void unregister_kernel_step_hook(struct step_hook *hook) -{ - unregister_debug_hook(&hook->node); -} - /* * Call registered single step handlers * There is no Syndrome info to check for determining the handler. @@ -216,14 +195,11 @@ void unregister_kernel_step_hook(struct step_hook *hook) static int call_step_hook(struct pt_regs *regs, unsigned int esr) { struct step_hook *hook; - struct list_head *list; int retval = DBG_HOOK_ERROR; - list = user_mode(regs) ? &user_step_hook : &kernel_step_hook; - rcu_read_lock(); - list_for_each_entry_rcu(hook, list, node) { + list_for_each_entry_rcu(hook, &step_hook, node) { retval = hook->fn(regs, esr); if (retval == DBG_HOOK_HANDLED) break; @@ -302,44 +278,33 @@ NOKPROBE_SYMBOL(single_step_handler); * hit within breakpoint handler, especically in kprobes. * Use reader/writer locks instead of plain spinlock. */ -static LIST_HEAD(user_break_hook); -static LIST_HEAD(kernel_break_hook); +static LIST_HEAD(break_hook); +static DEFINE_SPINLOCK(break_hook_lock); -void register_user_break_hook(struct break_hook *hook) +void register_break_hook(struct break_hook *hook) { - register_debug_hook(&hook->node, &user_break_hook); + spin_lock(&break_hook_lock); + list_add_rcu(&hook->node, &break_hook); + spin_unlock(&break_hook_lock); } -void unregister_user_break_hook(struct break_hook *hook) +void unregister_break_hook(struct break_hook *hook) { - unregister_debug_hook(&hook->node); -} - -void register_kernel_break_hook(struct break_hook *hook) -{ - register_debug_hook(&hook->node, &kernel_break_hook); -} - -void unregister_kernel_break_hook(struct break_hook *hook) -{ - unregister_debug_hook(&hook->node); + spin_lock(&break_hook_lock); + list_del_rcu(&hook->node); + spin_unlock(&break_hook_lock); + synchronize_rcu(); } static int call_break_hook(struct pt_regs *regs, unsigned int esr) { struct break_hook *hook; - struct list_head *list; int (*fn)(struct pt_regs *regs, unsigned int esr) = NULL; - list = user_mode(regs) ? &user_break_hook : &kernel_break_hook; - rcu_read_lock(); - list_for_each_entry_rcu(hook, list, node) { - unsigned int comment = esr & BRK64_ESR_MASK; - - if ((comment & ~hook->mask) == hook->imm) + list_for_each_entry_rcu(hook, &break_hook, node) + if ((esr & hook->esr_mask) == hook->esr_val) fn = hook->fn; - } rcu_read_unlock(); return fn ? fn(regs, esr) : DBG_HOOK_ERROR; diff --git a/arch/arm64/kernel/kgdb.c b/arch/arm64/kernel/kgdb.c index b3a124bfe72d..8815b5457dd0 100644 --- a/arch/arm64/kernel/kgdb.c +++ b/arch/arm64/kernel/kgdb.c @@ -275,13 +275,15 @@ static int kgdb_step_brk_fn(struct pt_regs *regs, unsigned int esr) NOKPROBE_SYMBOL(kgdb_step_brk_fn); static struct break_hook kgdb_brkpt_hook = { - .fn = kgdb_brk_fn, - .imm = KGDB_DYN_DBG_BRK_IMM, + .esr_mask = 0xffffffff, + .esr_val = (u32)ESR_ELx_VAL_BRK64(KGDB_DYN_DBG_BRK_IMM), + .fn = kgdb_brk_fn }; static struct break_hook kgdb_compiled_brkpt_hook = { - .fn = kgdb_compiled_brk_fn, - .imm = KGDB_COMPILED_DBG_BRK_IMM, + .esr_mask = 0xffffffff, + .esr_val = (u32)ESR_ELx_VAL_BRK64(KGDB_COMPILED_DBG_BRK_IMM), + .fn = kgdb_compiled_brk_fn }; static struct step_hook kgdb_step_hook = { @@ -342,9 +344,9 @@ int kgdb_arch_init(void) if (ret != 0) return ret; - register_kernel_break_hook(&kgdb_brkpt_hook); - register_kernel_break_hook(&kgdb_compiled_brkpt_hook); - register_kernel_step_hook(&kgdb_step_hook); + register_break_hook(&kgdb_brkpt_hook); + register_break_hook(&kgdb_compiled_brkpt_hook); + register_step_hook(&kgdb_step_hook); return 0; } @@ -355,9 +357,9 @@ int kgdb_arch_init(void) */ void kgdb_arch_exit(void) { - unregister_kernel_break_hook(&kgdb_brkpt_hook); - unregister_kernel_break_hook(&kgdb_compiled_brkpt_hook); - unregister_kernel_step_hook(&kgdb_step_hook); + unregister_break_hook(&kgdb_brkpt_hook); + unregister_break_hook(&kgdb_compiled_brkpt_hook); + unregister_step_hook(&kgdb_step_hook); unregister_die_notifier(&kgdb_notifier); } diff --git a/arch/arm64/kernel/probes/uprobes.c b/arch/arm64/kernel/probes/uprobes.c index 7d6ea88796a6..636ca0119c0e 100644 --- a/arch/arm64/kernel/probes/uprobes.c +++ b/arch/arm64/kernel/probes/uprobes.c @@ -195,7 +195,8 @@ static int uprobe_single_step_handler(struct pt_regs *regs, /* uprobe breakpoint handler hook */ static struct break_hook uprobes_break_hook = { - .imm = BRK64_ESR_UPROBES, + .esr_mask = BRK64_ESR_MASK, + .esr_val = BRK64_ESR_UPROBES, .fn = uprobe_breakpoint_handler, }; @@ -206,8 +207,8 @@ static struct step_hook uprobes_step_hook = { static int __init arch_init_uprobes(void) { - register_user_break_hook(&uprobes_break_hook); - register_user_step_hook(&uprobes_step_hook); + register_break_hook(&uprobes_break_hook); + register_step_hook(&uprobes_step_hook); return 0; } diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c index 982c477883d0..2923254e27ca 100644 --- a/arch/arm64/kernel/traps.c +++ b/arch/arm64/kernel/traps.c @@ -975,8 +975,9 @@ static int bug_handler(struct pt_regs *regs, unsigned int esr) } static struct break_hook bug_break_hook = { + .esr_val = 0xf2000000 | BUG_BRK_IMM, + .esr_mask = 0xffffffff, .fn = bug_handler, - .imm = BUG_BRK_IMM, }; #ifdef CONFIG_KASAN_SW_TAGS @@ -1025,9 +1026,9 @@ static int kasan_handler(struct pt_regs *regs, unsigned int esr) #define KASAN_ESR_MASK 0xffffff00 static struct break_hook kasan_break_hook = { + .esr_val = KASAN_ESR_VAL, + .esr_mask = KASAN_ESR_MASK, .fn = kasan_handler, - .imm = KASAN_BRK_IMM, - .mask = KASAN_BRK_MASK, }; #endif @@ -1045,48 +1046,11 @@ int __init early_brk64(unsigned long addr, unsigned int esr, return bug_handler(regs, esr) != DBG_HOOK_HANDLED; } -static int refcount_overflow_handler(struct pt_regs *regs, unsigned int esr) -{ - u32 dummy_cbz = le32_to_cpup((__le32 *)(regs->pc + 4)); - bool zero = regs->pstate & PSR_Z_BIT; - u32 rt; - - /* - * Find the register that holds the counter address from the - * dummy 'cbz' instruction that follows the 'brk' instruction - * that sent us here. - */ - rt = aarch64_insn_decode_register(AARCH64_INSN_REGTYPE_RT, dummy_cbz); - - /* First unconditionally saturate the refcount. */ - *(int *)regs->regs[rt] = INT_MIN / 2; - - /* - * This function has been called because either a negative refcount - * value was seen by any of the refcount functions, or a zero - * refcount value was seen by refcount_{add,dec}(). - */ - - /* point pc to the branch instruction that detected the overflow */ - regs->pc += 4 + aarch64_get_branch_offset(dummy_cbz); - refcount_error_report(regs, zero ? "hit zero" : "overflow"); - - /* advance pc and proceed */ - regs->pc += 4; - return DBG_HOOK_HANDLED; -} - -static struct break_hook refcount_break_hook = { - .fn = refcount_overflow_handler, - .imm = REFCOUNT_BRK_IMM, -}; - /* This registration must happen early, before debug_traps_init(). */ void __init trap_init(void) { - register_kernel_break_hook(&bug_break_hook); + register_break_hook(&bug_break_hook); #ifdef CONFIG_KASAN_SW_TAGS - register_kernel_break_hook(&kasan_break_hook); + register_break_hook(&kasan_break_hook); #endif - register_kernel_break_hook(&refcount_break_hook); } diff --git a/arch/arm64/kernel/vmlinux.lds.S b/arch/arm64/kernel/vmlinux.lds.S index 05df6fffedea..86a57e2fa9af 100644 --- a/arch/arm64/kernel/vmlinux.lds.S +++ b/arch/arm64/kernel/vmlinux.lds.S @@ -76,6 +76,10 @@ jiffies = jiffies_64; #define TRAMP_TEXT #endif +#define RTIC_BSS \ + . = ALIGN(PAGE_SIZE); \ + KEEP(*(.bss.rtic)); \ + . = ALIGN(PAGE_SIZE); \ /* * The size of the PE/COFF section that covers the kernel image, which * runs from stext to _edata, must be a round multiple of the PE/COFF @@ -256,6 +260,10 @@ SECTIONS STABS_DEBUG HEAD_SYMBOLS + + .bss : { /* bss segment */ + RTIC_BSS + } } /* diff --git a/arch/arm64/lib/atomic_ll_sc.c b/arch/arm64/lib/atomic_ll_sc.c index 8a335cd9f0e2..b0c538b0da28 100644 --- a/arch/arm64/lib/atomic_ll_sc.c +++ b/arch/arm64/lib/atomic_ll_sc.c @@ -1,15 +1,3 @@ #include #define __ARM64_IN_ATOMIC_IMPL - -/* - * Disarm the refcount checks in the out-of-line LL/SC routines. These are - * redundant, given that the LSE callers already perform the same checks. - * We do have to make sure that we exit with a zero value if the pre-check - * detected a zero value. - */ -#undef REFCOUNT_POST_CHECK_NEG -#undef REFCOUNT_POST_CHECK_NEG_OR_ZERO -#define REFCOUNT_POST_CHECK_NEG -#define REFCOUNT_POST_CHECK_NEG_OR_ZERO "csel %w[val], wzr, %w[val], eq\n" - #include diff --git a/arch/arm64/lib/copy_template.S b/arch/arm64/lib/copy_template.S index 7d2e550d4282..f5b9210f1c83 100644 --- a/arch/arm64/lib/copy_template.S +++ b/arch/arm64/lib/copy_template.S @@ -51,7 +51,6 @@ C_h .req x12 D_l .req x13 D_h .req x14 - prfm pldl1strm, [src, #(1*L1_CACHE_BYTES)] mov dst, dstin cmp count, #16 /*When memory length is less than 16, the accessed are not aligned.*/ @@ -182,7 +181,6 @@ D_h .req x14 ldp1 C_l, C_h, src, #16 stp1 D_l, D_h, dst, #16 ldp1 D_l, D_h, src, #16 - prfm pldl1strm, [src, #(4*L1_CACHE_BYTES)] subs count, count, #64 b.ge 1b stp1 A_l, A_h, dst, #16 diff --git a/arch/arm64/lib/memcmp.S b/arch/arm64/lib/memcmp.S index f365a5055c30..fb295f52e9f8 100644 --- a/arch/arm64/lib/memcmp.S +++ b/arch/arm64/lib/memcmp.S @@ -1,131 +1,258 @@ /* - * Copyright (c) 2017 ARM Ltd - * All rights reserved. + * Copyright (C) 2013 ARM Ltd. + * Copyright (C) 2013 Linaro. * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The name of the company may not be used to endorse or promote - * products derived from this software without specific prior written - * permission. + * This code is based on glibc cortex strings work originally authored by Linaro + * and re-licensed under GPLv2 for the Linux kernel. The original code can + * be found @ * - * THIS SOFTWARE IS PROVIDED BY ARM LTD ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL ARM LTD BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * http://bazaar.launchpad.net/~linaro-toolchain-dev/cortex-strings/trunk/ + * files/head:/src/aarch64/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ -/* Assumptions: - * - * ARMv8-a, AArch64, unaligned accesses. - */ - -/* includes here */ #include #include +/* +* compare memory areas(when two memory areas' offset are different, +* alignment handled by the hardware) +* +* Parameters: +* x0 - const memory area 1 pointer +* x1 - const memory area 2 pointer +* x2 - the maximal compare byte length +* Returns: +* x0 - a compare result, maybe less than, equal to, or greater than ZERO +*/ + /* Parameters and result. */ -#define src1 x0 -#define src2 x1 -#define limit x2 -#define result w0 +src1 .req x0 +src2 .req x1 +limit .req x2 +result .req x0 /* Internal variables. */ -#define data1 x3 -#define data1w w3 -#define data2 x4 -#define data2w w4 -#define tmp1 x5 +data1 .req x3 +data1w .req w3 +data2 .req x4 +data2w .req w4 +has_nul .req x5 +diff .req x6 +endloop .req x7 +tmp1 .req x8 +tmp2 .req x9 +tmp3 .req x10 +pos .req x11 +limit_wd .req x12 +mask .req x13 -/* Small inputs of less than 8 bytes are handled separately. This allows the - main code to be sped up using unaligned loads since there are now at least - 8 bytes to be compared. If the first 8 bytes are equal, align src1. - This ensures each iteration does at most one unaligned access even if both - src1 and src2 are unaligned, and mutually aligned inputs behave as if - aligned. After the main loop, process the last 8 bytes using unaligned - accesses. */ - -.p2align 6 WEAK(memcmp) - subs limit, limit, 8 - b.lo .Lless8 + cbz limit, .Lret0 + eor tmp1, src1, src2 + tst tmp1, #7 + b.ne .Lmisaligned8 + ands tmp1, src1, #7 + b.ne .Lmutual_align + sub limit_wd, limit, #1 /* limit != 0, so no underflow. */ + lsr limit_wd, limit_wd, #3 /* Convert to Dwords. */ + /* + * The input source addresses are at alignment boundary. + * Directly compare eight bytes each time. + */ +.Lloop_aligned: + ldr data1, [src1], #8 + ldr data2, [src2], #8 +.Lstart_realigned: + subs limit_wd, limit_wd, #1 + eor diff, data1, data2 /* Non-zero if differences found. */ + csinv endloop, diff, xzr, cs /* Last Dword or differences. */ + cbz endloop, .Lloop_aligned - /* Limit >= 8, so check first 8 bytes using unaligned loads. */ - ldr data1, [src1], 8 - ldr data2, [src2], 8 - and tmp1, src1, 7 - add limit, limit, tmp1 - cmp data1, data2 - bne .Lreturn + /* Not reached the limit, must have found a diff. */ + tbz limit_wd, #63, .Lnot_limit - /* Align src1 and adjust src2 with bytes not yet done. */ - sub src1, src1, tmp1 - sub src2, src2, tmp1 + /* Limit % 8 == 0 => the diff is in the last 8 bytes. */ + ands limit, limit, #7 + b.eq .Lnot_limit + /* + * The remained bytes less than 8. It is needed to extract valid data + * from last eight bytes of the intended memory range. + */ + lsl limit, limit, #3 /* bytes-> bits. */ + mov mask, #~0 +CPU_BE( lsr mask, mask, limit ) +CPU_LE( lsl mask, mask, limit ) + bic data1, data1, mask + bic data2, data2, mask - subs limit, limit, 8 - b.ls .Llast_bytes + orr diff, diff, mask + b .Lnot_limit - /* Loop performing 8 bytes per iteration using aligned src1. - Limit is pre-decremented by 8 and must be larger than zero. - Exit if <= 8 bytes left to do or if the data is not equal. */ - .p2align 4 -.Lloop8: - ldr data1, [src1], 8 - ldr data2, [src2], 8 - subs limit, limit, 8 - ccmp data1, data2, 0, hi /* NZCV = 0b0000. */ - b.eq .Lloop8 +.Lmutual_align: + /* + * Sources are mutually aligned, but are not currently at an + * alignment boundary. Round down the addresses and then mask off + * the bytes that precede the start point. + */ + bic src1, src1, #7 + bic src2, src2, #7 + ldr data1, [src1], #8 + ldr data2, [src2], #8 + /* + * We can not add limit with alignment offset(tmp1) here. Since the + * addition probably make the limit overflown. + */ + sub limit_wd, limit, #1/*limit != 0, so no underflow.*/ + and tmp3, limit_wd, #7 + lsr limit_wd, limit_wd, #3 + add tmp3, tmp3, tmp1 + add limit_wd, limit_wd, tmp3, lsr #3 + add limit, limit, tmp1/* Adjust the limit for the extra. */ - cmp data1, data2 - bne .Lreturn + lsl tmp1, tmp1, #3/* Bytes beyond alignment -> bits.*/ + neg tmp1, tmp1/* Bits to alignment -64. */ + mov tmp2, #~0 + /*mask off the non-intended bytes before the start address.*/ +CPU_BE( lsl tmp2, tmp2, tmp1 )/*Big-endian.Early bytes are at MSB*/ + /* Little-endian. Early bytes are at LSB. */ +CPU_LE( lsr tmp2, tmp2, tmp1 ) - /* Compare last 1-8 bytes using unaligned access. */ -.Llast_bytes: - ldr data1, [src1, limit] - ldr data2, [src2, limit] + orr data1, data1, tmp2 + orr data2, data2, tmp2 + b .Lstart_realigned - /* Compare data bytes and set return value to 0, -1 or 1. */ -.Lreturn: -#ifndef __AARCH64EB__ - rev data1, data1 - rev data2, data2 -#endif - cmp data1, data2 -.Lret_eq: - cset result, ne - cneg result, result, lo - ret + /*src1 and src2 have different alignment offset.*/ +.Lmisaligned8: + cmp limit, #8 + b.lo .Ltiny8proc /*limit < 8: compare byte by byte*/ - .p2align 4 - /* Compare up to 8 bytes. Limit is [-8..-1]. */ -.Lless8: - adds limit, limit, 4 - b.lo .Lless4 - ldr data1w, [src1], 4 - ldr data2w, [src2], 4 + and tmp1, src1, #7 + neg tmp1, tmp1 + add tmp1, tmp1, #8/*valid length in the first 8 bytes of src1*/ + and tmp2, src2, #7 + neg tmp2, tmp2 + add tmp2, tmp2, #8/*valid length in the first 8 bytes of src2*/ + subs tmp3, tmp1, tmp2 + csel pos, tmp1, tmp2, hi /*Choose the maximum.*/ + + sub limit, limit, pos + /*compare the proceeding bytes in the first 8 byte segment.*/ +.Ltinycmp: + ldrb data1w, [src1], #1 + ldrb data2w, [src2], #1 + subs pos, pos, #1 + ccmp data1w, data2w, #0, ne /* NZCV = 0b0000. */ + b.eq .Ltinycmp + cbnz pos, 1f /*diff occurred before the last byte.*/ cmp data1w, data2w - b.ne .Lreturn - sub limit, limit, 4 -.Lless4: - adds limit, limit, 4 - beq .Lret_eq -.Lbyte_loop: - ldrb data1w, [src1], 1 - ldrb data2w, [src2], 1 - subs limit, limit, 1 - ccmp data1w, data2w, 0, ne /* NZCV = 0b0000. */ - b.eq .Lbyte_loop - sub result, data1w, data2w + b.eq .Lstart_align +1: + sub result, data1, data2 + ret + +.Lstart_align: + lsr limit_wd, limit, #3 + cbz limit_wd, .Lremain8 + + ands xzr, src1, #7 + b.eq .Lrecal_offset + /*process more leading bytes to make src1 aligned...*/ + add src1, src1, tmp3 /*backwards src1 to alignment boundary*/ + add src2, src2, tmp3 + sub limit, limit, tmp3 + lsr limit_wd, limit, #3 + cbz limit_wd, .Lremain8 + /*load 8 bytes from aligned SRC1..*/ + ldr data1, [src1], #8 + ldr data2, [src2], #8 + + subs limit_wd, limit_wd, #1 + eor diff, data1, data2 /*Non-zero if differences found.*/ + csinv endloop, diff, xzr, ne + cbnz endloop, .Lunequal_proc + /*How far is the current SRC2 from the alignment boundary...*/ + and tmp3, tmp3, #7 + +.Lrecal_offset:/*src1 is aligned now..*/ + neg pos, tmp3 +.Lloopcmp_proc: + /* + * Divide the eight bytes into two parts. First,backwards the src2 + * to an alignment boundary,load eight bytes and compare from + * the SRC2 alignment boundary. If all 8 bytes are equal,then start + * the second part's comparison. Otherwise finish the comparison. + * This special handle can garantee all the accesses are in the + * thread/task space in avoid to overrange access. + */ + ldr data1, [src1,pos] + ldr data2, [src2,pos] + eor diff, data1, data2 /* Non-zero if differences found. */ + cbnz diff, .Lnot_limit + + /*The second part process*/ + ldr data1, [src1], #8 + ldr data2, [src2], #8 + eor diff, data1, data2 /* Non-zero if differences found. */ + subs limit_wd, limit_wd, #1 + csinv endloop, diff, xzr, ne/*if limit_wd is 0,will finish the cmp*/ + cbz endloop, .Lloopcmp_proc +.Lunequal_proc: + cbz diff, .Lremain8 + +/* There is difference occurred in the latest comparison. */ +.Lnot_limit: +/* +* For little endian,reverse the low significant equal bits into MSB,then +* following CLZ can find how many equal bits exist. +*/ +CPU_LE( rev diff, diff ) +CPU_LE( rev data1, data1 ) +CPU_LE( rev data2, data2 ) + + /* + * The MS-non-zero bit of DIFF marks either the first bit + * that is different, or the end of the significant data. + * Shifting left now will bring the critical information into the + * top bits. + */ + clz pos, diff + lsl data1, data1, pos + lsl data2, data2, pos + /* + * We need to zero-extend (char is unsigned) the value and then + * perform a signed subtraction. + */ + lsr data1, data1, #56 + sub result, data1, data2, lsr #56 + ret + +.Lremain8: + /* Limit % 8 == 0 =>. all data are equal.*/ + ands limit, limit, #7 + b.eq .Lret0 + +.Ltiny8proc: + ldrb data1w, [src1], #1 + ldrb data2w, [src2], #1 + subs limit, limit, #1 + + ccmp data1w, data2w, #0, ne /* NZCV = 0b0000. */ + b.eq .Ltiny8proc + sub result, data1, data2 + ret +.Lret0: + mov result, #0 ret ENDPIPROC(memcmp) diff --git a/arch/arm64/lib/memmove.S b/arch/arm64/lib/memmove.S index f5ac945d2a65..a5a4459013b1 100644 --- a/arch/arm64/lib/memmove.S +++ b/arch/arm64/lib/memmove.S @@ -60,7 +60,6 @@ D_h .req x14 .weak memmove ENTRY(__memmove) ENTRY(memmove) - prfm pldl1strm, [src, #L1_CACHE_BYTES] cmp dstin, src b.lo __memcpy add tmp1, src, count @@ -187,7 +186,6 @@ ENTRY(memmove) ldp C_l, C_h, [src, #-48] stp D_l, D_h, [dst, #-64]! ldp D_l, D_h, [src, #-64]! - prfm pldl1strm, [src, #(4*L1_CACHE_BYTES)] subs count, count, #64 b.ge 1b stp A_l, A_h, [dst, #-16] diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c index 671c44ed7fce..a7cf7af73400 100644 --- a/arch/arm64/mm/init.c +++ b/arch/arm64/mm/init.c @@ -508,10 +508,9 @@ void __init arm64_memblock_init(void) * Save bootloader imposed memory limit before we overwirte * memblock. */ - if (memory_limit == PHYS_ADDR_MAX) + bootloader_memory_limit = memblock_max_addr(memory_limit); + if (bootloader_memory_limit > memblock_end_of_DRAM()) bootloader_memory_limit = memblock_end_of_DRAM(); - else - bootloader_memory_limit = memblock_max_addr(memory_limit); update_memory_limit(); diff --git a/block/Makefile b/block/Makefile index 02d832d0342c..a2e05332682f 100644 --- a/block/Makefile +++ b/block/Makefile @@ -11,8 +11,6 @@ obj-$(CONFIG_BLOCK) := bio.o elevator.o blk-core.o blk-tag.o blk-sysfs.o \ genhd.o partition-generic.o ioprio.o \ badblocks.o partitions/ blk-rq-qos.o -CFLAGS_blk-mq.o := $(call cc-disable-warning, align-mismatch) - obj-$(CONFIG_BOUNCE) += bounce.o obj-$(CONFIG_BLK_SCSI_REQUEST) += scsi_ioctl.o obj-$(CONFIG_BLK_DEV_BSG) += bsg.o @@ -41,4 +39,4 @@ obj-$(CONFIG_BLK_DEBUG_FS_ZONED)+= blk-mq-debugfs-zoned.o obj-$(CONFIG_BLK_SED_OPAL) += sed-opal.o obj-$(CONFIG_BLK_INLINE_ENCRYPTION) += keyslot-manager.o bio-crypt-ctx.o \ blk-crypto.o -obj-$(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK) += blk-crypto-fallback.o +obj-$(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK) += blk-crypto-fallback.o \ No newline at end of file diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index e196656959d2..c138fc92c561 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -3,7 +3,6 @@ * main.c - Multi purpose firmware loading support * * Copyright (c) 2003 Manuel Estrada Sainz - * Copyright (C) 2020 XiaoMi, Inc. * * Please see Documentation/firmware_class/ for more information. * @@ -284,12 +283,12 @@ static void free_fw_priv(struct fw_priv *fw_priv) static char fw_path_para[256]; static const char * const fw_path[] = { fw_path_para, + "/system/vendor/firmware", + "/system/etc/firmware", "/lib/firmware/updates/" UTS_RELEASE, "/lib/firmware/updates", "/lib/firmware/" UTS_RELEASE, - "/lib/firmware", - "/system/vendor/firmware", - "/vendor/firmware" + "/lib/firmware" }; /* diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index d12121483af8..6492da4f9543 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -797,6 +798,7 @@ void dpm_noirq_end(void) { resume_device_irqs(); device_wakeup_disarm_wake_irqs(); + cpuidle_resume(); } /** @@ -1418,6 +1420,7 @@ static int device_suspend_noirq(struct device *dev) void dpm_noirq_begin(void) { + cpuidle_pause(); device_wakeup_arm_wake_irqs(); suspend_device_irqs(); } diff --git a/drivers/bluetooth/bluetooth-power.c b/drivers/bluetooth/bluetooth-power.c index cee4fe2b2bb5..f404f2a4f11b 100644 --- a/drivers/bluetooth/bluetooth-power.c +++ b/drivers/bluetooth/bluetooth-power.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ /* @@ -388,6 +388,7 @@ static int bt_configure_gpios(int on) if (rc) { BT_PWR_ERR("%s:bt_enable_bt_reset_gpios_safely failed", __func__); + return rc; } msleep(50); diff --git a/drivers/bluetooth/btfm_slim_slave.h b/drivers/bluetooth/btfm_slim_slave.h index 48b9e84c9f53..26fc8abae8a3 100644 --- a/drivers/bluetooth/btfm_slim_slave.h +++ b/drivers/bluetooth/btfm_slim_slave.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ #ifndef BTFM_SLIM_SLAVE_H @@ -97,6 +97,9 @@ enum { QCA_COMANCHE_SOC_ID_0101 = 0x40070101, QCA_COMANCHE_SOC_ID_0110 = 0x40070110, QCA_COMANCHE_SOC_ID_0120 = 0x40070120, + QCA_COMANCHE_SOC_ID_0130 = 0x40070130, + QCA_COMANCHE_SOC_ID_5120 = 0x40075120, + QCA_COMANCHE_SOC_ID_5130 = 0x40075130, }; enum { diff --git a/drivers/bus/mhi/core/mhi_pm.c b/drivers/bus/mhi/core/mhi_pm.c index e61523d27d7b..0b4e3025e953 100644 --- a/drivers/bus/mhi/core/mhi_pm.c +++ b/drivers/bus/mhi/core/mhi_pm.c @@ -1511,6 +1511,15 @@ int mhi_pm_fast_resume(struct mhi_controller *mhi_cntrl, bool notify_client) } if (mhi_cntrl->rddm_supported) { + + /* check EP is in proper state */ + if (mhi_cntrl->link_status(mhi_cntrl, mhi_cntrl->priv_data)) { + MHI_ERR("Unable to access EP Config space\n"); + write_unlock_irq(&mhi_cntrl->pm_lock); + tasklet_enable(&mhi_cntrl->mhi_event->task); + return -ETIMEDOUT; + } + if (mhi_get_exec_env(mhi_cntrl) == MHI_EE_RDDM && !mhi_cntrl->power_down) { mhi_cntrl->ee = MHI_EE_RDDM; diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 883da9e0908e..f9ad19501bbb 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -538,6 +538,27 @@ config DEVPORT source "drivers/s390/char/Kconfig" +config MSM_SMD_PKT + bool "Enable device interface for some SMD packet ports" + default n + depends on RPMSG_QCOM_SMD + help + smd_pkt driver provides the interface for the userspace clients + to communicate over smd via device nodes. This enable the + usersapce clients to read and write to some smd packets channel + for MSM chipset. + +config TILE_SROM + tristate "Character-device access via hypervisor to the Tilera SPI ROM" + depends on TILE + default y + help + This device provides character-level read-write access + to the SROM, typically via the "0", "1", and "2" devices + in /dev/srom/. The Tilera hypervisor makes the flash + device appear much like a simple EEPROM, and knows + how to partition a single ROM for multiple purposes. + source "drivers/char/xillybus/Kconfig" source "drivers/char/diag/Kconfig" diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 0bc77edd8aca..28c14cc2ae8e 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -14,6 +14,8 @@ obj-$(CONFIG_MSPEC) += mspec.o obj-$(CONFIG_UV_MMTIMER) += uv_mmtimer.o obj-$(CONFIG_IBM_BSR) += bsr.o obj-$(CONFIG_SGI_MBCS) += mbcs.o +obj-$(CONFIG_BFIN_OTP) += bfin-otp.o +obj-$(CONFIG_MSM_SMD_PKT) += msm_smd_pkt.o obj-$(CONFIG_PRINTER) += lp.o diff --git a/drivers/char/adsprpc.c b/drivers/char/adsprpc.c index 387c870faaeb..cc265cd8f882 100644 --- a/drivers/char/adsprpc.c +++ b/drivers/char/adsprpc.c @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -23,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -1698,7 +1696,7 @@ static int get_args(uint32_t kernel, struct smq_invoke_ctx *ctx) uintptr_t args; size_t rlen = 0, copylen = 0, metalen = 0, lrpralen = 0; int i, oix; - int err = 0; + int err = 0, j = 0; int mflags = 0; uint64_t *fdlist; uint32_t *crclist; @@ -1743,6 +1741,8 @@ static int get_args(uint32_t kernel, struct smq_invoke_ctx *ctx) FASTRPC_ATTR_NOVA, 0, 0, dmaflags, &ctx->maps[i]); if (err) { + for (j = bufs; j < i; j++) + fastrpc_mmap_free(ctx->maps[j], 0); mutex_unlock(&ctx->fl->map_mutex); goto bail; } @@ -2780,19 +2780,6 @@ static int fastrpc_init_process(struct fastrpc_file *fl, return err; } -static int fastrpc_kstat(const char *filename, struct kstat *stat) -{ - int result; - mm_segment_t fs_old; - - fs_old = get_fs(); - set_fs(KERNEL_DS); - result = vfs_stat((const char __user *)filename, stat); - set_fs(fs_old); - - return result; -} - static int fastrpc_send_cpuinfo_to_dsp(struct fastrpc_file *fl) { int err = 0; @@ -2835,33 +2822,29 @@ static int fastrpc_get_info_from_dsp(struct fastrpc_file *fl, int err = 0, dsp_support = 0; struct fastrpc_ioctl_invoke_crc ioctl; remote_arg_t ra[2]; - struct kstat sb; + struct fastrpc_apps *me = &gfa; // Querying device about DSP support switch (domain) { case ADSP_DOMAIN_ID: - if (!fastrpc_kstat("/dev/subsys_adsp", &sb)) + case SDSP_DOMAIN_ID: + case CDSP_DOMAIN_ID: + if (me->channel[domain].issubsystemup) dsp_support = 1; break; case MDSP_DOMAIN_ID: //Modem not supported for fastRPC break; - case SDSP_DOMAIN_ID: - if (!fastrpc_kstat("/dev/subsys_slpi", &sb)) - dsp_support = 1; - break; - case CDSP_DOMAIN_ID: - if (!fastrpc_kstat("/dev/subsys_cdsp", &sb)) - dsp_support = 1; - break; default: dsp_support = 0; break; } dsp_attr_buf[0] = dsp_support; - if (dsp_support == 0) + if (dsp_support == 0) { + err = -ENOTCONN; goto bail; + } err = fastrpc_channel_open(fl); if (err) @@ -3315,8 +3298,13 @@ static int fastrpc_internal_munmap(struct fastrpc_file *fl, mutex_unlock(&fl->map_mutex); if (err) goto bail; + VERIFY(err, map != NULL); + if (err) { + err = -EINVAL; + goto bail; + } VERIFY(err, !fastrpc_munmap_on_dsp(fl, map->raddr, - map->phys, map->size, map->flags)); + map->phys, map->size, map->flags)); if (err) goto bail; mutex_lock(&fl->map_mutex); @@ -4737,6 +4725,8 @@ static const struct of_device_id fastrpc_match_table[] = { { .compatible = "qcom,msm-fastrpc-adsp", }, { .compatible = "qcom,msm-fastrpc-compute", }, { .compatible = "qcom,msm-fastrpc-compute-cb", }, + { .compatible = "qcom,msm-fastrpc-legacy-compute", }, + { .compatible = "qcom,msm-fastrpc-legacy-compute-cb", }, { .compatible = "qcom,msm-adsprpc-mem-region", }, {} }; @@ -4834,6 +4824,85 @@ static int fastrpc_cb_probe(struct device *dev) return err; } + +static int fastrpc_cb_legacy_probe(struct device *dev) +{ + struct fastrpc_channel_ctx *chan; + struct fastrpc_session_ctx *first_sess = NULL, *sess = NULL; + struct fastrpc_apps *me = &gfa; + const char *name; + unsigned int *sids = NULL, sids_size = 0; + int err = 0, ret = 0, i; + uint32_t dma_addr_pool[2] = {0, 0}; + + + VERIFY(err, NULL != (name = of_get_property(dev->of_node, + "label", NULL))); + if (err) + goto bail; + + for (i = 0; i < NUM_CHANNELS; i++) { + if (!gcinfo[i].name) + continue; + if (!strcmp(name, gcinfo[i].name)) + break; + } + VERIFY(err, i < NUM_CHANNELS); + if (err) + goto bail; + + chan = &gcinfo[i]; + VERIFY(err, chan->sesscount < NUM_SESSIONS); + if (err) + goto bail; + + first_sess = &chan->session[chan->sesscount]; + + VERIFY(err, NULL != of_get_property(dev->of_node, + "sids", &sids_size)); + if (err) + goto bail; + + VERIFY(err, NULL != (sids = kzalloc(sids_size, GFP_KERNEL))); + if (err) + goto bail; + ret = of_property_read_u32_array(dev->of_node, "sids", sids, + sids_size/sizeof(unsigned int)); + if (ret) + goto bail; + + if (err) + goto bail; + + for (i = 0; i < sids_size/sizeof(unsigned int); i++) { + VERIFY(err, chan->sesscount < NUM_SESSIONS); + if (err) + goto bail; + sess = &chan->session[chan->sesscount]; + sess->smmu.cb = sids[i]; + sess->smmu.dev = dev; + sess->smmu.dev_name = dev_name(dev); + sess->smmu.enabled = 1; + sess->used = 0; + sess->smmu.coherent = false; + sess->smmu.secure = false; + chan->sesscount++; + if (!sess->smmu.dev->dma_parms) + sess->smmu.dev->dma_parms = devm_kzalloc(sess->smmu.dev, + sizeof(*sess->smmu.dev->dma_parms), GFP_KERNEL); + dma_set_max_seg_size(sess->smmu.dev, DMA_BIT_MASK(32)); + dma_set_seg_boundary(sess->smmu.dev, + (unsigned long)DMA_BIT_MASK(64)); + } + of_property_read_u32_array(dev->of_node, "qcom,iommu-dma-addr-pool", + dma_addr_pool, 2); + me->max_size_limit = (dma_addr_pool[1] == 0 ? 0x78000000 : + dma_addr_pool[1]); +bail: + kfree(sids); + return err; +} + static void init_secure_vmid_list(struct device *dev, char *prop_name, struct secure_vm *destvm) { @@ -4964,6 +5033,9 @@ static int fastrpc_probe(struct platform_device *pdev) if (of_device_is_compatible(dev->of_node, "qcom,msm-fastrpc-compute-cb")) return fastrpc_cb_probe(dev); + if (of_device_is_compatible(dev->of_node, + "qcom,msm-fastrpc-legacy-compute-cb")) + return fastrpc_cb_legacy_probe(dev); if (of_device_is_compatible(dev->of_node, "qcom,msm-adsprpc-mem-region")) { @@ -5112,7 +5184,7 @@ static struct platform_driver fastrpc_driver = { static const struct rpmsg_device_id fastrpc_rpmsg_match[] = { { FASTRPC_GLINK_GUID }, - { }, + { FASTRPC_SMD_GUID }, }; static const struct of_device_id fastrpc_rpmsg_of_match[] = { diff --git a/drivers/char/diag/diag_masks.c b/drivers/char/diag/diag_masks.c index d40f2f21b73c..2ee3a46da4ef 100644 --- a/drivers/char/diag/diag_masks.c +++ b/drivers/char/diag/diag_masks.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2008-2020, The Linux Foundation. All rights reserved. +/* Copyright (c) 2008-2021, The Linux Foundation. All rights reserved. */ #include @@ -3120,8 +3120,13 @@ int diag_copy_to_user_msg_mask(char __user *buf, size_t count, return -EINVAL; } - err = copy_to_user(buf, mask_info->update_buf_client, + if ((count - (sizeof(int))) >= + mask_info->update_buf_client_len) { + err = copy_to_user(buf, mask_info->update_buf_client, mask_info->update_buf_client_len); + } else { + err = -EINVAL; + } if (err) { pr_err("diag: In %s Unable to send msg masks to user space clients, err: %d\n", __func__, err); @@ -3147,8 +3152,13 @@ int diag_copy_to_user_log_mask(char __user *buf, size_t count, return -EINVAL; } - err = copy_to_user(buf, mask_info->update_buf_client, + if ((count - (sizeof(int))) >= + mask_info->update_buf_client_len) { + err = copy_to_user(buf, mask_info->update_buf_client, mask_info->update_buf_client_len); + } else { + err = -EINVAL; + } if (err) { pr_err("diag: In %s Unable to send msg masks to user space clients, err: %d\n", __func__, err); diff --git a/drivers/char/diag/diag_usb.c b/drivers/char/diag/diag_usb.c index a1ae3ab953d1..048619d40c74 100644 --- a/drivers/char/diag/diag_usb.c +++ b/drivers/char/diag/diag_usb.c @@ -312,7 +312,9 @@ static void usb_read_work_fn(struct work_struct *work) atomic_set(&ch->read_pending, 1); req->buf = ch->read_buf; req->length = USB_MAX_OUT_BUF; + spin_unlock_irqrestore(&ch->lock, flags); err = usb_diag_read(ch->hdl, req); + spin_lock_irqsave(&ch->lock, flags); if (err) { pr_debug("diag: In %s, error in reading from USB %s, err: %d\n", __func__, ch->name, err); diff --git a/drivers/char/diag/diagfwd_peripheral.c b/drivers/char/diag/diagfwd_peripheral.c index ca0ccf55f097..4bc7a57affb3 100644 --- a/drivers/char/diag/diagfwd_peripheral.c +++ b/drivers/char/diag/diagfwd_peripheral.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2015-2019, The Linux Foundation. All rights reserved. +/* Copyright (c) 2015-2019, 2021, The Linux Foundation. All rights reserved. */ #include #include @@ -1345,11 +1345,26 @@ int diagfwd_channel_open(struct diagfwd_info *fwd_info) int diagfwd_channel_close(struct diagfwd_info *fwd_info) { + struct diag_rpmsg_info *rpmsg_info = NULL; + struct diag_socket_info *socket_info = NULL; + if (!fwd_info) return -EIO; mutex_lock(&driver->diagfwd_channel_mutex[fwd_info->peripheral]); fwd_info->ch_open = 0; + rpmsg_info = diag_get_rpmsg_info_ptr(fwd_info->type, + fwd_info->peripheral); + socket_info = diag_get_socket_info_ptr(fwd_info->type, + fwd_info->peripheral); + + if (rpmsg_info && socket_info && rpmsg_info->probed + && socket_info->reset_flag) { + mutex_unlock( + &driver->diagfwd_channel_mutex[fwd_info->peripheral]); + return 0; + } + if (fwd_info && fwd_info->c_ops && fwd_info->c_ops->close) fwd_info->c_ops->close(fwd_info); @@ -1440,6 +1455,7 @@ void diagfwd_write_done(uint8_t peripheral, uint8_t type, int buf_num) DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "Buffer 1 for core PD is marked free, p: %d, t: %d, buf_num: %d\n", fwd_info->peripheral, fwd_info->type, buf_num); + rpmsg_mark_buffers_free(peripheral, type, buf_num); } } else if (buf_num == 2 && fwd_info->buf_2) { /* @@ -1466,6 +1482,7 @@ void diagfwd_write_done(uint8_t peripheral, uint8_t type, int buf_num) DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "Buffer 2 for core PD is marked free, p: %d, t: %d, buf_num: %d\n", fwd_info->peripheral, fwd_info->type, buf_num); + rpmsg_mark_buffers_free(peripheral, type, buf_num); } } else if (buf_num >= 3 && (buf_num % 2)) { /* @@ -1501,6 +1518,7 @@ void diagfwd_write_done(uint8_t peripheral, uint8_t type, int buf_num) DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "Buffer 1 for core PD is marked free, p: %d, t: %d, buf_num: %d\n", fwd_info->peripheral, fwd_info->type, buf_num); + rpmsg_mark_buffers_free(peripheral, type, 1); } } else if (buf_num >= 4 && !(buf_num % 2)) { /* @@ -1536,7 +1554,8 @@ void diagfwd_write_done(uint8_t peripheral, uint8_t type, int buf_num) DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "Buffer 2 for core PD is marked free, p: %d, t: %d, buf_num: %d\n", fwd_info->peripheral, fwd_info->type, buf_num); - } + rpmsg_mark_buffers_free(peripheral, type, 2); + } } else pr_err("diag: In %s, invalid buf_num %d\n", __func__, buf_num); diff --git a/drivers/char/diag/diagfwd_rpmsg.c b/drivers/char/diag/diagfwd_rpmsg.c index c1262c1e2d0e..c194e831551d 100644 --- a/drivers/char/diag/diagfwd_rpmsg.c +++ b/drivers/char/diag/diagfwd_rpmsg.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. +/* Copyright (c) 2017-2019, 2021, The Linux Foundation. All rights reserved. */ #include @@ -24,11 +24,22 @@ #define PERI_RPMSG rpmsg_info->peripheral struct diag_rpmsg_read_work { - struct diag_rpmsg_info *rpmsg_info; - const void *ptr_read_done; - const void *ptr_rx_done; - size_t ptr_read_size; struct work_struct work; + struct list_head rx_list_head; + spinlock_t rx_lock; +}; + +static struct diag_rpmsg_read_work *read_work_struct; + +/** + ** struct rx_buff_list - holds rx rpmsg data, before it will be consumed + ** by diagfwd_channel_read_done worker, item per rx packet + **/ +struct rx_buff_list { + struct list_head list; + void *rpmsg_rx_buf; + int rx_buf_size; + struct diag_rpmsg_info *rpmsg_info; }; struct diag_rpmsg_info rpmsg_data[NUM_PERIPHERALS] = { @@ -36,7 +47,7 @@ struct diag_rpmsg_info rpmsg_data[NUM_PERIPHERALS] = { .peripheral = PERIPHERAL_MODEM, .type = TYPE_DATA, .edge = "mpss", - .name = "DIAG_DATA", + .name = "DIAG", .buf1 = NULL, .buf2 = NULL, .hdl = NULL @@ -45,7 +56,7 @@ struct diag_rpmsg_info rpmsg_data[NUM_PERIPHERALS] = { .peripheral = PERIPHERAL_LPASS, .type = TYPE_DATA, .edge = "lpass", - .name = "DIAG_DATA", + .name = "DIAG", .buf1 = NULL, .buf2 = NULL, .hdl = NULL @@ -54,7 +65,7 @@ struct diag_rpmsg_info rpmsg_data[NUM_PERIPHERALS] = { .peripheral = PERIPHERAL_WCNSS, .type = TYPE_DATA, .edge = "wcnss", - .name = "DIAG_DATA", + .name = "APPS_RIVA_DATA", .buf1 = NULL, .buf2 = NULL, .hdl = NULL @@ -102,7 +113,7 @@ struct diag_rpmsg_info rpmsg_cntl[NUM_PERIPHERALS] = { .peripheral = PERIPHERAL_MODEM, .type = TYPE_CNTL, .edge = "mpss", - .name = "DIAG_CTRL", + .name = "DIAG_CNTL", .buf1 = NULL, .buf2 = NULL, .hdl = NULL @@ -111,7 +122,7 @@ struct diag_rpmsg_info rpmsg_cntl[NUM_PERIPHERALS] = { .peripheral = PERIPHERAL_LPASS, .type = TYPE_CNTL, .edge = "lpass", - .name = "DIAG_CTRL", + .name = "DIAG_CNTL", .buf1 = NULL, .buf2 = NULL, .hdl = NULL @@ -120,7 +131,7 @@ struct diag_rpmsg_info rpmsg_cntl[NUM_PERIPHERALS] = { .peripheral = PERIPHERAL_WCNSS, .type = TYPE_CNTL, .edge = "wcnss", - .name = "DIAG_CTRL", + .name = "APPS_RIVA_CTRL", .buf1 = NULL, .buf2 = NULL, .hdl = NULL @@ -168,7 +179,7 @@ struct diag_rpmsg_info rpmsg_dci[NUM_PERIPHERALS] = { .peripheral = PERIPHERAL_MODEM, .type = TYPE_DCI, .edge = "mpss", - .name = "DIAG_DCI_DATA", + .name = "DIAG_2", .buf1 = NULL, .buf2 = NULL, .hdl = NULL @@ -300,7 +311,7 @@ struct diag_rpmsg_info rpmsg_dci_cmd[NUM_PERIPHERALS] = { .peripheral = PERIPHERAL_MODEM, .type = TYPE_DCI_CMD, .edge = "mpss", - .name = "DIAG_DCI_CMD", + .name = "DIAG_2_CMD", .buf1 = NULL, .buf2 = NULL, .hdl = NULL @@ -463,7 +474,7 @@ static int diag_rpmsg_read(void *ctxt, unsigned char *buf, int buf_len) rpmsg_info->buf2 = buf; } mutex_unlock(&driver->diagfwd_channel_mutex[rpmsg_info->peripheral]); - + queue_work(rpmsg_info->wq, &read_work_struct->work); return ret_val; } @@ -488,14 +499,13 @@ static void diag_rpmsg_read_work_fn(struct work_struct *work) return; } mutex_unlock(&driver->rpmsginfo_mutex[PERI_RPMSG]); - diagfwd_channel_read(rpmsg_info->fwd_ctxt); } static int diag_rpmsg_write(void *ctxt, unsigned char *buf, int len) { - struct diag_rpmsg_info *rpmsg_info = NULL; int err = 0; + struct diag_rpmsg_info *rpmsg_info = NULL; struct rpmsg_device *rpdev = NULL; if (!ctxt || !buf) @@ -518,6 +528,7 @@ static int diag_rpmsg_write(void *ctxt, unsigned char *buf, int len) } rpdev = (struct rpmsg_device *)rpmsg_info->hdl; + err = rpmsg_send(rpdev->ept, buf, len); if (!err) { DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "%s wrote to rpmsg, len: %d\n", @@ -599,85 +610,178 @@ static int diag_rpmsg_notify_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src) { struct diag_rpmsg_info *rpmsg_info = NULL; - struct diagfwd_info *fwd_info = NULL; - struct diag_rpmsg_read_work *read_work = NULL; - void *buf = NULL; + struct rx_buff_list *rx_item; + unsigned long flags; + + if (!rpdev || !data) + return -EINVAL; rpmsg_info = dev_get_drvdata(&rpdev->dev); + if (!rpmsg_info || !rpmsg_info->fwd_ctxt) { DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "diag: Invalid rpmsg info\n"); - return 0; + return -EINVAL; } - if (!rpmsg_info->buf1 && !rpmsg_info->buf2) { - DIAG_LOG(DIAG_DEBUG_PERIPHERALS, - "dropping data for %s len %d\n", - rpmsg_info->name, len); - return 0; + rx_item = kzalloc(sizeof(*rx_item), GFP_ATOMIC); + if (!rx_item) + return -ENOMEM; + + rx_item->rpmsg_rx_buf = kmemdup(data, len, GFP_ATOMIC); + if (!rx_item->rpmsg_rx_buf) { + kfree(rx_item); + return -ENOMEM; } - fwd_info = rpmsg_info->fwd_ctxt; + rx_item->rx_buf_size = len; + rx_item->rpmsg_info = rpmsg_info; - DIAG_LOG(DIAG_DEBUG_PERIPHERALS, - "diag: received data of length: %d for p:%d, t:%d\n", - len, rpmsg_info->peripheral, rpmsg_info->type); + spin_lock_irqsave(&read_work_struct->rx_lock, flags); + list_add(&rx_item->list, &read_work_struct->rx_list_head); + spin_unlock_irqrestore(&read_work_struct->rx_lock, flags); - if (rpmsg_info->buf1 && !fwd_info->buffer_status[BUF_1_INDEX] && - atomic_read(&fwd_info->buf_1->in_busy)) { - buf = rpmsg_info->buf1; - fwd_info->buffer_status[BUF_1_INDEX] = 1; - } else if (rpmsg_info->buf2 && !fwd_info->buffer_status[BUF_2_INDEX] && - atomic_read(&fwd_info->buf_2->in_busy) && - (fwd_info->type == TYPE_DATA)) { - buf = rpmsg_info->buf2; - fwd_info->buffer_status[BUF_2_INDEX] = 1; - } else { - buf = NULL; - } - - if (!buf) - return 0; - - memcpy(buf, data, len); - - read_work = kmalloc(sizeof(*read_work), GFP_ATOMIC); - if (!read_work) { - DIAG_LOG(DIAG_DEBUG_PERIPHERALS, - "diag: Could not allocate read_work\n"); - return 0; - } - read_work->rpmsg_info = rpmsg_info; - read_work->ptr_read_done = buf; - read_work->ptr_read_size = len; - INIT_WORK(&read_work->work, diag_rpmsg_notify_rx_work_fn); - queue_work(rpmsg_info->wq, &read_work->work); + queue_work(rpmsg_info->wq, &read_work_struct->work); return 0; } static void diag_rpmsg_notify_rx_work_fn(struct work_struct *work) { - struct diag_rpmsg_read_work *read_work = container_of(work, - struct diag_rpmsg_read_work, work); - struct diag_rpmsg_info *rpmsg_info = read_work->rpmsg_info; + struct diag_rpmsg_info *rpmsg_info; + struct rx_buff_list *rx_item; + struct diagfwd_info *fwd_info; + void *buf = NULL; + unsigned long flags; + int err_flag = 0; - if (!rpmsg_info || !rpmsg_info->hdl) { - kfree(read_work); - read_work = NULL; + spin_lock_irqsave(&read_work_struct->rx_lock, flags); + if (!list_empty(&read_work_struct->rx_list_head)) { + /* detach last entry */ + rx_item = list_last_entry(&read_work_struct->rx_list_head, + struct rx_buff_list, list); + + if (!rx_item) { + err_flag = 1; + goto err_handling; + } + + rpmsg_info = rx_item->rpmsg_info; + if (!rpmsg_info) { + err_flag = 1; + goto err_handling; + } + + fwd_info = rpmsg_info->fwd_ctxt; + if (!fwd_info) { + err_flag = 1; + goto err_handling; + } + + if (!rpmsg_info->buf1 && !rpmsg_info->buf2) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "retry data send for %s len %d\n", + rpmsg_info->name, rx_item->rx_buf_size); + err_flag = 1; + goto err_handling; + } + + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: received data of length: %d, p: %d, t: %d\n", + rx_item->rx_buf_size, rpmsg_info->peripheral, + rpmsg_info->type); + + if (rpmsg_info->buf1 && !fwd_info->buffer_status[BUF_1_INDEX] && + atomic_read(&(fwd_info->buf_1->in_busy))) { + buf = rpmsg_info->buf1; + fwd_info->buffer_status[BUF_1_INDEX] = 1; + } else if (rpmsg_info->buf2 && + !fwd_info->buffer_status[BUF_2_INDEX] && + atomic_read(&fwd_info->buf_2->in_busy) && + (fwd_info->type == TYPE_DATA)) { + buf = rpmsg_info->buf2; + fwd_info->buffer_status[BUF_2_INDEX] = 1; + } else { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "Both the rpmsg buffers are busy\n"); + buf = NULL; + } + if (!buf) { + err_flag = 1; + goto err_handling; + } + +err_handling: + if (!err_flag) { + memcpy(buf, rx_item->rpmsg_rx_buf, + rx_item->rx_buf_size); + list_del(&rx_item->list); + spin_unlock_irqrestore(&read_work_struct->rx_lock, + flags); + } else { + spin_unlock_irqrestore(&read_work_struct->rx_lock, + flags); + goto end; + } + + mutex_lock(&driver->diagfwd_channel_mutex[PERI_RPMSG]); + + diagfwd_channel_read_done(rpmsg_info->fwd_ctxt, + (unsigned char *)(buf), rx_item->rx_buf_size); + + mutex_unlock(&driver->diagfwd_channel_mutex[PERI_RPMSG]); + kfree(rx_item->rpmsg_rx_buf); + kfree(rx_item); + + } else { + spin_unlock_irqrestore(&read_work_struct->rx_lock, flags); + } +end: + return; +} + +struct diag_rpmsg_info *diag_get_rpmsg_info_ptr(int type, int peripheral) +{ + if (type == TYPE_CMD) + return &rpmsg_cmd[peripheral]; + else if (type == TYPE_CNTL) + return &rpmsg_cntl[peripheral]; + else if (type == TYPE_DATA) + return &rpmsg_data[peripheral]; + else if (type == TYPE_DCI_CMD) + return &rpmsg_dci_cmd[peripheral]; + else if (type == TYPE_DCI) + return &rpmsg_dci[peripheral]; + else + return NULL; +} + +void rpmsg_mark_buffers_free(uint8_t peripheral, uint8_t type, int buf_num) +{ + struct diag_rpmsg_info *rpmsg_info; + + switch (peripheral) { + case PERIPHERAL_WDSP: + break; + case PERIPHERAL_WCNSS: + break; + case PERIPHERAL_MODEM: + break; + case PERIPHERAL_LPASS: + break; + default: return; } - mutex_lock(&driver->diagfwd_channel_mutex[rpmsg_info->peripheral]); - diagfwd_channel_read_done(rpmsg_info->fwd_ctxt, - (unsigned char *)(read_work->ptr_read_done), - read_work->ptr_read_size); + rpmsg_info = diag_get_rpmsg_info_ptr(type, peripheral); + if (!rpmsg_info) + return; - if (read_work->ptr_read_done == rpmsg_info->buf1) + if (buf_num == 1) { rpmsg_info->buf1 = NULL; - else if (read_work->ptr_read_done == rpmsg_info->buf2) + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "marked buf1 NULL"); + } else if (buf_num == 2) { rpmsg_info->buf2 = NULL; - kfree(read_work); - read_work = NULL; - mutex_unlock(&driver->diagfwd_channel_mutex[rpmsg_info->peripheral]); + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "marked buf2 NULL"); + } } static void rpmsg_late_init(struct diag_rpmsg_info *rpmsg_info) @@ -745,6 +849,7 @@ static void __diag_rpmsg_init(struct diag_rpmsg_info *rpmsg_info) mutex_lock(&driver->rpmsginfo_mutex[PERI_RPMSG]); rpmsg_info->hdl = NULL; rpmsg_info->fwd_ctxt = NULL; + rpmsg_info->probed = 0; atomic_set(&rpmsg_info->opened, 0); atomic_set(&rpmsg_info->diag_state, 0); DIAG_LOG(DIAG_DEBUG_PERIPHERALS, @@ -771,8 +876,19 @@ int diag_rpmsg_init(void) struct diag_rpmsg_info *rpmsg_info = NULL; for (peripheral = 0; peripheral < NUM_PERIPHERALS; peripheral++) { - if (peripheral != PERIPHERAL_WDSP) + switch (peripheral) { + case PERIPHERAL_WDSP: + break; + case PERIPHERAL_WCNSS: + break; + case PERIPHERAL_MODEM: + break; + case PERIPHERAL_LPASS: + break; + default: continue; + } + rpmsg_info = &rpmsg_cntl[peripheral]; __diag_rpmsg_init(rpmsg_info); diagfwd_cntl_register(TRANSPORT_RPMSG, rpmsg_info->peripheral, @@ -788,6 +904,18 @@ int diag_rpmsg_init(void) __diag_rpmsg_init(&rpmsg_dci[peripheral]); __diag_rpmsg_init(&rpmsg_dci_cmd[peripheral]); } + read_work_struct = kmalloc(sizeof(*read_work_struct), GFP_ATOMIC); + if (!read_work_struct) { + DIAG_LOG(DIAG_DEBUG_PERIPHERALS, + "diag: Could not allocate read_work\n"); + return 0; + } + kmemleak_not_leak(read_work_struct); + + INIT_WORK(&read_work_struct->work, diag_rpmsg_notify_rx_work_fn); + INIT_LIST_HEAD(&read_work_struct->rx_list_head); + spin_lock_init(&read_work_struct->rx_lock); + return 0; } @@ -815,8 +943,19 @@ void diag_rpmsg_early_exit(void) int peripheral = 0; for (peripheral = 0; peripheral < NUM_PERIPHERALS; peripheral++) { - if (peripheral != PERIPHERAL_WDSP) + switch (peripheral) { + case PERIPHERAL_WDSP: + break; + case PERIPHERAL_WCNSS: + break; + case PERIPHERAL_MODEM: + break; + case PERIPHERAL_LPASS: + break; + default: continue; + } + mutex_lock(&driver->rpmsginfo_mutex[peripheral]); __diag_rpmsg_exit(&rpmsg_cntl[peripheral]); mutex_unlock(&driver->rpmsginfo_mutex[peripheral]); @@ -837,72 +976,124 @@ void diag_rpmsg_exit(void) } } -static struct diag_rpmsg_info *diag_get_rpmsg_ptr(char *name) +static struct diag_rpmsg_info *diag_get_rpmsg_ptr(char *name, int pid) { - if (!name) return NULL; - if (!strcmp(name, "DIAG_CMD")) - return &rpmsg_cmd[PERIPHERAL_WDSP]; - else if (!strcmp(name, "DIAG_CTRL")) - return &rpmsg_cntl[PERIPHERAL_WDSP]; - else if (!strcmp(name, "DIAG_DATA")) - return &rpmsg_data[PERIPHERAL_WDSP]; - else if (!strcmp(name, "DIAG_DCI_CMD")) - return &rpmsg_dci_cmd[PERIPHERAL_WDSP]; - else if (!strcmp(name, "DIAG_DCI_DATA")) - return &rpmsg_dci[PERIPHERAL_WDSP]; - else - return NULL; + if (pid == PERIPHERAL_WDSP) { + if (!strcmp(name, "DIAG_CMD")) + return &rpmsg_cmd[PERIPHERAL_WDSP]; + else if (!strcmp(name, "DIAG_CTRL")) + return &rpmsg_cntl[PERIPHERAL_WDSP]; + else if (!strcmp(name, "DIAG_DATA")) + return &rpmsg_data[PERIPHERAL_WDSP]; + else if (!strcmp(name, "DIAG_DCI_CMD")) + return &rpmsg_dci_cmd[PERIPHERAL_WDSP]; + else if (!strcmp(name, "DIAG_DCI_DATA")) + return &rpmsg_dci[PERIPHERAL_WDSP]; + else + return NULL; + } else if (pid == PERIPHERAL_WCNSS) { + if (!strcmp(name, "APPS_RIVA_DATA")) + return &rpmsg_data[PERIPHERAL_WCNSS]; + else if (!strcmp(name, "APPS_RIVA_CTRL")) + return &rpmsg_cntl[PERIPHERAL_WCNSS]; + else + return NULL; + } else if (pid == PERIPHERAL_MODEM) { + if (!strcmp(name, "DIAG_CMD")) + return &rpmsg_cmd[PERIPHERAL_MODEM]; + else if (!strcmp(name, "DIAG_CNTL")) + return &rpmsg_cntl[PERIPHERAL_MODEM]; + else if (!strcmp(name, "DIAG")) + return &rpmsg_data[PERIPHERAL_MODEM]; + else if (!strcmp(name, "DIAG_2_CMD")) + return &rpmsg_dci_cmd[PERIPHERAL_MODEM]; + else if (!strcmp(name, "DIAG_2")) + return &rpmsg_dci[PERIPHERAL_MODEM]; + else + return NULL; + } else if (pid == PERIPHERAL_LPASS) { + if (!strcmp(name, "DIAG")) + return &rpmsg_data[PERIPHERAL_LPASS]; + else if (!strcmp(name, "DIAG_CNTL")) + return &rpmsg_cntl[PERIPHERAL_LPASS]; + else + return NULL; + } + return NULL; } static int diag_rpmsg_probe(struct rpmsg_device *rpdev) { struct diag_rpmsg_info *rpmsg_info = NULL; + int peripheral = -1; if (!rpdev) return 0; - if (strcmp(rpdev->dev.parent->of_node->name, "wdsp")) - return 0; - rpmsg_info = diag_get_rpmsg_ptr(rpdev->id.name); + if (!strcmp(rpdev->dev.parent->of_node->name, "wdsp")) + peripheral = PERIPHERAL_WDSP; + else if (!strcmp(rpdev->dev.parent->of_node->name, "wcnss")) + peripheral = PERIPHERAL_WCNSS; + else if (!strcmp(rpdev->dev.parent->of_node->name, "modem")) + peripheral = PERIPHERAL_MODEM; + else if (!strcmp(rpdev->dev.parent->of_node->name, "adsp")) + peripheral = PERIPHERAL_LPASS; + + rpmsg_info = diag_get_rpmsg_ptr(rpdev->id.name, peripheral); if (rpmsg_info) { - mutex_lock(&driver->rpmsginfo_mutex[PERI_RPMSG]); rpmsg_info->hdl = rpdev; atomic_set(&rpmsg_info->opened, 1); mutex_unlock(&driver->rpmsginfo_mutex[PERI_RPMSG]); - + rpmsg_info->probed = 1; dev_set_drvdata(&rpdev->dev, rpmsg_info); diagfwd_channel_read(rpmsg_info->fwd_ctxt); queue_work(rpmsg_info->wq, &rpmsg_info->open_work); } - return 0; } static void diag_rpmsg_remove(struct rpmsg_device *rpdev) { struct diag_rpmsg_info *rpmsg_info = NULL; + int peripheral = -1; if (!rpdev) return; - rpmsg_info = diag_get_rpmsg_ptr(rpdev->id.name); + if (!strcmp(rpdev->dev.parent->of_node->name, "wdsp")) + peripheral = PERIPHERAL_WDSP; + else if (!strcmp(rpdev->dev.parent->of_node->name, "wcnss")) + peripheral = PERIPHERAL_WCNSS; + else if (!strcmp(rpdev->dev.parent->of_node->name, "modem")) + peripheral = PERIPHERAL_MODEM; + else if (!strcmp(rpdev->dev.parent->of_node->name, "adsp")) + peripheral = PERIPHERAL_LPASS; + + rpmsg_info = diag_get_rpmsg_ptr(rpdev->id.name, peripheral); if (rpmsg_info) { mutex_lock(&driver->rpmsginfo_mutex[PERI_RPMSG]); atomic_set(&rpmsg_info->opened, 0); mutex_unlock(&driver->rpmsginfo_mutex[PERI_RPMSG]); + rpmsg_info->probed = 0; queue_work(rpmsg_info->wq, &rpmsg_info->close_work); } } static struct rpmsg_device_id rpmsg_diag_table[] = { - { .name = "DIAG_CMD" }, - { .name = "DIAG_CTRL" }, - { .name = "DIAG_DATA" }, - { .name = "DIAG_DCI_CMD" }, - { .name = "DIAG_DCI_DATA" }, + { .name = "APPS_RIVA_DATA" }, + { .name = "APPS_RIVA_CTRL" }, + { .name = "DIAG" }, + { .name = "DIAG_CNTL" }, + { .name = "DIAG_2" }, + { .name = "DIAG_2_CMD" }, + { .name = "DIAG_CMD" }, + { .name = "DIAG_CTRL" }, + { .name = "DIAG_DATA" }, + { .name = "DIAG_DCI_CMD" }, + { .name = "DIAG_DCI_DATA" }, { }, }; MODULE_DEVICE_TABLE(rpmsg, rpmsg_diag_table); diff --git a/drivers/char/diag/diagfwd_rpmsg.h b/drivers/char/diag/diagfwd_rpmsg.h index 7c2641de5702..6a973bac5c07 100644 --- a/drivers/char/diag/diagfwd_rpmsg.h +++ b/drivers/char/diag/diagfwd_rpmsg.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0 */ -/* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. +/* Copyright (c) 2017-2018, 2021, The Linux Foundation. All rights reserved. */ #ifndef DIAGFWD_RPMSG_H @@ -12,6 +12,7 @@ struct diag_rpmsg_info { uint8_t peripheral; uint8_t type; uint8_t inited; + uint8_t probed; atomic_t opened; atomic_t diag_state; uint32_t fifo_size; @@ -43,5 +44,7 @@ int diag_rpmsg_init(void); void diag_rpmsg_early_exit(void); void diag_rpmsg_invalidate(void *ctxt, struct diagfwd_info *fwd_ctxt); int diag_rpmsg_check_state(void *ctxt); +void rpmsg_mark_buffers_free(uint8_t peripheral, uint8_t type, int buf_num); +struct diag_rpmsg_info *diag_get_rpmsg_info_ptr(int type, int peripheral); #endif diff --git a/drivers/char/diag/diagfwd_socket.c b/drivers/char/diag/diagfwd_socket.c index ebfa7a69b81b..be3b34bbad16 100644 --- a/drivers/char/diag/diagfwd_socket.c +++ b/drivers/char/diag/diagfwd_socket.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. +/* Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. */ #include @@ -244,6 +244,22 @@ struct diag_socket_info socket_dci_cmd[NUM_PERIPHERALS] = { } }; +struct diag_socket_info *diag_get_socket_info_ptr(int type, int peripheral) +{ + if (type == TYPE_CMD) + return &socket_cmd[peripheral]; + else if (type == TYPE_CNTL) + return &socket_cntl[peripheral]; + else if (type == TYPE_DATA) + return &socket_data[peripheral]; + else if (type == TYPE_DCI_CMD) + return &socket_dci_cmd[peripheral]; + else if (type == TYPE_DCI) + return &socket_dci[peripheral]; + else + return NULL; +} + struct restart_notifier_block { unsigned int processor; char *name; @@ -611,7 +627,9 @@ static void socket_read_work_fn(struct work_struct *work) err = sock_error(info->hdl->sk); mutex_unlock(&info->socket_info_mutex); if (unlikely(err == -ENETRESET)) { + info->reset_flag = 1; socket_close_channel(info); + info->reset_flag = 0; if (info->port_type == PORT_TYPE_SERVER) socket_init_work_fn(&info->init_work); diag_ws_release(); @@ -831,7 +849,9 @@ static int diag_socket_read(void *ctxt, unsigned char *buf, int buf_len) mutex_lock(channel_mutex); diagfwd_channel_read_done(info->fwd_ctxt, buf, 0); mutex_unlock(channel_mutex); + info->reset_flag = 1; socket_close_channel(info); + info->reset_flag = 0; if (info->port_type == PORT_TYPE_SERVER) socket_init_work_fn(&info->init_work); return read_len; @@ -980,6 +1000,7 @@ static void __diag_socket_init(struct diag_socket_info *info) info->hdl = NULL; info->fwd_ctxt = NULL; info->data_ready = 0; + info->reset_flag = 0; atomic_set(&info->flow_cnt, 0); spin_lock_init(&info->lock); strlcpy(wq_name, info->name, sizeof(wq_name)); diff --git a/drivers/char/diag/diagfwd_socket.h b/drivers/char/diag/diagfwd_socket.h index eec1bfcdc41f..d113ed543cf0 100644 --- a/drivers/char/diag/diagfwd_socket.h +++ b/drivers/char/diag/diagfwd_socket.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. +/* Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. */ #ifndef DIAGFWD_SOCKET_H @@ -33,6 +33,7 @@ struct diag_socket_info { uint8_t type; uint8_t port_type; uint8_t inited; + uint8_t reset_flag; atomic_t opened; atomic_t diag_state; uint32_t pkt_len; @@ -68,4 +69,5 @@ int diag_socket_check_state(void *ctxt); int diag_socket_init(void); void diag_socket_exit(void); int diag_socket_init_peripheral(uint8_t peripheral); +struct diag_socket_info *diag_get_socket_info_ptr(int type, int peripheral); #endif diff --git a/drivers/char/diag/diagmem.c b/drivers/char/diag/diagmem.c index 0094a8485b84..3cec3053070c 100644 --- a/drivers/char/diag/diagmem.c +++ b/drivers/char/diag/diagmem.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2008-2014, 2016-2019 The Linux Foundation. All rights reserved. +/* Copyright (c) 2008-2014, 2016-2019, 2021 The Linux Foundation. All rights reserved. */ #include @@ -143,6 +143,9 @@ void diagmem_setsize(int pool_idx, int itemsize, int poolsize) } diag_mempools[pool_idx].itemsize = itemsize; + if (diag_mempools[pool_idx].pool) + diag_mempools[pool_idx].pool->pool_data = + (void *)(uintptr_t)itemsize; diag_mempools[pool_idx].poolsize = poolsize; pr_debug("diag: Mempool %s sizes: itemsize %d poolsize %d\n", diag_mempools[pool_idx].name, diag_mempools[pool_idx].itemsize, @@ -168,7 +171,8 @@ void *diagmem_alloc(struct diagchar_dev *driver, int size, int pool_type) mempool->name); break; } - if (size == 0 || size > mempool->itemsize) { + if (size == 0 || size > mempool->itemsize || + size > (int)mempool->pool->pool_data) { pr_err_ratelimited("diag: cannot alloc from mempool %s, invalid size: %d\n", mempool->name, size); break; diff --git a/drivers/char/msm_smd_pkt.c b/drivers/char/msm_smd_pkt.c new file mode 100644 index 000000000000..c9514dff0d6d --- /dev/null +++ b/drivers/char/msm_smd_pkt.c @@ -0,0 +1,977 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2021, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "msm_smdpkt" +#define DEVICE_NAME "smdpkt" +#define SMD_PKT_IPC_LOG_PAGE_CNT 2 + +/** + * struct smd_pkt - driver context, relates rpdev to cdev + * @dev: smd pkt device + * @cdev: cdev for the smd pkt device + * @drv: rpmsg driver for registering to rpmsg bus + * @lock: synchronization of @rpdev and @open_tout modifications + * @ch_open: wait object for opening the smd channel + * @refcount: count how many userspace clients have handles + * @rpdev: underlaying rpmsg device + * @queue_lock: synchronization of @queue operations + * @queue: incoming message queue + * @readq: wait object for incoming queue + * @sig_change: flag to indicate serial signal change + * @notify_state_update: notify channel state + * @fragmented_read: set from dt node for partial read + * @dev_name: /dev/@dev_name for smd_pkt device + * @ch_name: smd channel to match to + * @edge: smd edge to match to + * @open_tout: timeout for open syscall, configurable in sysfs + * @rskb: current skb being read + * @rdata: data pointer in current skb + * @rdata_len: remaining data to be read from skb + */ +struct smd_pkt_dev { + + struct device dev; + struct cdev cdev; + struct rpmsg_driver drv; + struct mutex lock; + struct completion ch_open; + refcount_t refcount; + struct rpmsg_device *rpdev; + + spinlock_t queue_lock; + struct sk_buff_head queue; + wait_queue_head_t readq; + int sig_change; + bool notify_state_update; + bool fragmented_read; + const char *dev_name; + const char *ch_name; + const char *edge; + int open_tout; + struct sk_buff *rskb; + unsigned char *rdata; + size_t rdata_len; +}; + +#define dev_to_smd_pkt_devp(_dev) container_of(_dev, struct smd_pkt_dev, dev) +#define cdev_to_smd_pkt_devp(_cdev) container_of(_cdev, \ + struct smd_pkt_dev, cdev) +#define drv_to_rpdrv(_drv) container_of(_drv, struct rpmsg_driver, drv) +#define rpdrv_to_smd_pkt_devp(_rdrv) container_of(_rdrv, \ + struct smd_pkt_dev, drv) + +static void *smd_pkt_ilctxt; + +static int smd_pkt_debug_mask; + +module_param_named(debug_mask, smd_pkt_debug_mask, int, 0664); + +enum { + SMD_PKT_INFO = 1U << 0, +}; + +#define SMD_PKT_INFO(x, ...) \ +do { \ + if (smd_pkt_debug_mask & SMD_PKT_INFO) { \ + ipc_log_string(smd_pkt_ilctxt, \ + "[%s]: "x, __func__, ##__VA_ARGS__); \ + } \ +} while (0) + +#define SMD_PKT_ERR(x, ...) \ +do { \ + pr_err_ratelimited("[%s]: "x, __func__, ##__VA_ARGS__); \ + ipc_log_string(smd_pkt_ilctxt, "[%s]: "x, __func__, ##__VA_ARGS__); \ +} while (0) + +#define SMD_PKT_IOCTL_QUEUE_RX_INTENT \ + _IOW(SMD_PKT_IOCTL_MAGIC, 0, unsigned int) + +static dev_t smd_pkt_major; +static struct class *smd_pkt_class; +static int num_smd_pkt_devs; + +static DEFINE_IDA(smd_pkt_minor_ida); +static ssize_t open_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t n) +{ + struct smd_pkt_dev *smd_pkt_devp = dev_to_smd_pkt_devp(dev); + long tmp; + + mutex_lock(&smd_pkt_devp->lock); + if (kstrtol(buf, 0, &tmp)) { + mutex_unlock(&smd_pkt_devp->lock); + SMD_PKT_ERR("unable to convert:%s to an int for /dev/%s\n", + buf, smd_pkt_devp->dev_name); + return -EINVAL; + } + smd_pkt_devp->open_tout = tmp; + mutex_unlock(&smd_pkt_devp->lock); + + return n; +} + +static ssize_t open_timeout_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct smd_pkt_dev *smd_pkt_devp = dev_to_smd_pkt_devp(dev); + ssize_t ret; + + mutex_lock(&smd_pkt_devp->lock); + ret = scnprintf(buf, PAGE_SIZE, "%d\n", smd_pkt_devp->open_tout); + mutex_unlock(&smd_pkt_devp->lock); + + return ret; +} + +static DEVICE_ATTR(open_timeout, 0664, open_timeout_show, open_timeout_store); + +static int smd_pkt_rpdev_probe(struct rpmsg_device *rpdev) +{ + struct device_driver *drv = rpdev->dev.driver; + struct rpmsg_driver *rpdrv = drv_to_rpdrv(drv); + struct smd_pkt_dev *smd_pkt_devp = rpdrv_to_smd_pkt_devp(rpdrv); + + mutex_lock(&smd_pkt_devp->lock); + smd_pkt_devp->rpdev = rpdev; + smd_pkt_devp->notify_state_update = true; + mutex_unlock(&smd_pkt_devp->lock); + dev_set_drvdata(&rpdev->dev, smd_pkt_devp); + complete_all(&smd_pkt_devp->ch_open); + return 0; +} + +static int smd_pkt_rpdev_cb(struct rpmsg_device *rpdev, void *buf, int len, + void *priv, u32 addr) +{ + struct smd_pkt_dev *smd_pkt_devp = dev_get_drvdata(&rpdev->dev); + unsigned long flags; + struct sk_buff *skb; + + if (!smd_pkt_devp) + return -EINVAL; + + skb = alloc_skb(len, GFP_ATOMIC); + + if (!skb) + return -ENOMEM; + + skb_put_data(skb, buf, len); + + spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags); + + skb_queue_tail(&smd_pkt_devp->queue, skb); + spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags); + + /* wake up any blocking processes, waiting for new data */ + wake_up_interruptible(&smd_pkt_devp->readq); + + return 0; +} + +static int smd_pkt_rpdev_sigs(struct rpmsg_device *rpdev, u32 old, u32 new) +{ + struct device_driver *drv = rpdev->dev.driver; + struct rpmsg_driver *rpdrv = drv_to_rpdrv(drv); + struct smd_pkt_dev *smd_pkt_devp = rpdrv_to_smd_pkt_devp(rpdrv); + unsigned long flags; + + spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags); + smd_pkt_devp->sig_change = true; + spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags); + + /* wake up any blocking processes, waiting for new data */ + wake_up_interruptible(&smd_pkt_devp->readq); + + return 0; +} + +/** + * smd_pkt_tiocmset() - set the signals for smd_pkt device + * smd_pkt_devp: Pointer to the smd_pkt device structure. + * cmd: IOCTL command. + * arg: Arguments to the ioctl call. + * + * This function is used to set the signals on the smd pkt device + * when userspace client do a ioctl() system call with TIOCMBIS, + * TIOCMBIC and TICOMSET. + */ +static int smd_pkt_tiocmset(struct smd_pkt_dev *smd_pkt_devp, unsigned int cmd, + unsigned long arg) +{ + u32 lsigs, rsigs, val; + int ret; + + ret = get_user(val, (u32 *)arg); + if (ret) + return ret; + + ret = rpmsg_get_sigs(smd_pkt_devp->rpdev->ept, &lsigs, &rsigs); + if (ret < 0) { + SMD_PKT_ERR("Get signals failed[%d]\n", ret); + return ret; + } + + switch (cmd) { + case TIOCMBIS: + lsigs |= val; + break; + case TIOCMBIC: + lsigs &= ~val; + break; + case TIOCMSET: + lsigs = val; + break; + } + + return rpmsg_set_sigs(smd_pkt_devp->rpdev->ept, lsigs); + SMD_PKT_INFO("sigs[0x%x] ret[%d]\n", lsigs, ret); + return ret; +} + +/** + * smd_pkt_ioctl() - ioctl() syscall for the smd_pkt device + * file: Pointer to the file structure. + * cmd: IOCTL command. + * arg: Arguments to the ioctl call. + * + * This function is used to ioctl on the smd pkt device when + * userspace client do a ioctl() system call. All input arguments are + * validated by the virtual file system before calling this function. + */ +static long smd_pkt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct smd_pkt_dev *smd_pkt_devp; + unsigned long flags; + u32 lsigs, rsigs, resetsigs; + int ret; + + smd_pkt_devp = file->private_data; + + if (!smd_pkt_devp || refcount_read(&smd_pkt_devp->refcount) == 1) { + SMD_PKT_ERR("invalid device handle\n"); + return -EINVAL; + } + + if (mutex_lock_interruptible(&smd_pkt_devp->lock)) + return -ERESTARTSYS; + + if (!completion_done(&smd_pkt_devp->ch_open)) { + SMD_PKT_ERR("%s channel in reset\n", smd_pkt_devp->ch_name); + if ((cmd == TIOCMGET) && (smd_pkt_devp->notify_state_update)) { + resetsigs = TIOCM_OUT1 | TIOCM_OUT2; + smd_pkt_devp->notify_state_update = false; + mutex_unlock(&smd_pkt_devp->lock); + + SMD_PKT_ERR("%s: reset notified resetsigs=%d\n", + smd_pkt_devp->ch_name, resetsigs); + ret = put_user(resetsigs, (uint32_t __user *)arg); + return ret; + } + mutex_unlock(&smd_pkt_devp->lock); + return -ENETRESET; + } + + switch (cmd) { + case TIOCMGET: + resetsigs = 0; + spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags); + smd_pkt_devp->sig_change = false; + if (smd_pkt_devp->notify_state_update) { + resetsigs = TIOCM_OUT2; + smd_pkt_devp->notify_state_update = false; + SMD_PKT_ERR("%s: reset notified resetsigs=%d\n", + smd_pkt_devp->ch_name, resetsigs); + } + spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags); + + ret = rpmsg_get_sigs(smd_pkt_devp->rpdev->ept, &lsigs, &rsigs); + if (!ret) + ret = put_user(rsigs | resetsigs, + (uint32_t __user *)arg); + break; + case TIOCMSET: + case TIOCMBIS: + case TIOCMBIC: + ret = smd_pkt_tiocmset(smd_pkt_devp, cmd, arg); + break; + case SMD_PKT_IOCTL_QUEUE_RX_INTENT: + /* Return success to not break userspace client logic */ + ret = 0; + break; + default: + SMD_PKT_ERR("unrecognized ioctl command 0x%x\n", cmd); + ret = -ENOIOCTLCMD; + break; + } + + mutex_unlock(&smd_pkt_devp->lock); + + return ret; +} + +/** + * smd_pkt_read() - read() syscall for the smd_pkt device + * file: Pointer to the file structure. + * buf: Pointer to the userspace buffer. + * count: Number bytes to read from the file. + * ppos: Pointer to the position into the file. + * + * This function is used to Read the data from smd pkt device when + * userspace client do a read() system call. All input arguments are + * validated by the virtual file system before calling this function. + */ +ssize_t smd_pkt_read(struct file *file, + char __user *buf, + size_t count, + loff_t *ppos) +{ + + struct smd_pkt_dev *smd_pkt_devp = file->private_data; + unsigned long flags; + int use; + + if (!smd_pkt_devp || + refcount_read(&smd_pkt_devp->refcount) == 1) { + SMD_PKT_ERR("invalid device handle\n"); + return -EINVAL; + } + + if (!completion_done(&smd_pkt_devp->ch_open)) { + SMD_PKT_ERR("%s channel in reset\n", smd_pkt_devp->ch_name); + return -ENETRESET; + } + + SMD_PKT_INFO( + "begin for %s by %s:%d ref_cnt[%d], remaining[%d], count[%d]\n", + smd_pkt_devp->ch_name, current->comm, + task_pid_nr(current), + refcount_read(&smd_pkt_devp->refcount), + smd_pkt_devp->rdata_len, count); + + spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags); + + /* Wait for data in the queue */ + if (skb_queue_empty(&smd_pkt_devp->queue) && !smd_pkt_devp->rskb) { + spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + /* Wait until we get data or the endpoint goes away */ + if (wait_event_interruptible(smd_pkt_devp->readq, + !skb_queue_empty(&smd_pkt_devp->queue) || + !completion_done(&smd_pkt_devp->ch_open))) + return -ERESTARTSYS; + + spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags); + } + + if (!smd_pkt_devp->rskb) { + smd_pkt_devp->rskb = skb_dequeue(&smd_pkt_devp->queue); + if (!smd_pkt_devp->rskb) { + spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, + flags); + return -EFAULT; + } + smd_pkt_devp->rdata = smd_pkt_devp->rskb->data; + smd_pkt_devp->rdata_len = smd_pkt_devp->rskb->len; + } + spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags); + + use = min_t(size_t, count, smd_pkt_devp->rdata_len); + + if (copy_to_user(buf, smd_pkt_devp->rdata, use)) + use = -EFAULT; + + if (!smd_pkt_devp->fragmented_read && smd_pkt_devp->rdata_len == use) { + struct sk_buff *skb = smd_pkt_devp->rskb; + + spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags); + smd_pkt_devp->rskb = NULL; + smd_pkt_devp->rdata = NULL; + smd_pkt_devp->rdata_len = 0; + spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags); + + kfree_skb(skb); + } else { + struct sk_buff *skb = NULL; + + spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags); + smd_pkt_devp->rdata += use; + smd_pkt_devp->rdata_len -= use; + if (smd_pkt_devp->rdata_len == 0) { + skb = smd_pkt_devp->rskb; + smd_pkt_devp->rskb = NULL; + smd_pkt_devp->rdata = NULL; + smd_pkt_devp->rdata_len = 0; + } + spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags); + if (skb) + kfree_skb(skb); + } + + SMD_PKT_INFO("end for %s by %s:%d ret[%d], remaining[%d]\n", + smd_pkt_devp->ch_name, current->comm, + task_pid_nr(current), use, smd_pkt_devp->rdata_len); + + return use; +} + +/** + * smd_pkt_write() - write() syscall for the smd_pkt device + * file: Pointer to the file structure. + * buf: Pointer to the userspace buffer. + * count: Number bytes to read from the file. + * ppos: Pointer to the position into the file. + * + * This function is used to write the data to smd pkt device when + * userspace client do a write() system call. All input arguments are + * validated by the virtual file system before calling this function. + */ +ssize_t smd_pkt_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + struct smd_pkt_dev *smd_pkt_devp = file->private_data; + void *kbuf; + int ret; + + smd_pkt_devp = file->private_data; + + if (!smd_pkt_devp || refcount_read(&smd_pkt_devp->refcount) == 1) { + SMD_PKT_ERR("invalid device handle\n"); + return -EINVAL; + } + + SMD_PKT_INFO("begin to %s buffer_size %zu\n", + smd_pkt_devp->ch_name, count); + kbuf = memdup_user(buf, count); + if (IS_ERR(kbuf)) + return PTR_ERR(kbuf); + + if (mutex_lock_interruptible(&smd_pkt_devp->lock)) { + ret = -ERESTARTSYS; + goto free_kbuf; + } + + if (!completion_done(&smd_pkt_devp->ch_open) || + !smd_pkt_devp->rpdev) { + SMD_PKT_ERR("%s channel in reset\n", smd_pkt_devp->ch_name); + ret = -ENETRESET; + goto unlock_ch; + } + + if (file->f_flags & O_NONBLOCK) + ret = rpmsg_trysend(smd_pkt_devp->rpdev->ept, kbuf, count); + else + ret = rpmsg_send(smd_pkt_devp->rpdev->ept, kbuf, count); + +unlock_ch: + mutex_unlock(&smd_pkt_devp->lock); + +free_kbuf: + kfree(kbuf); + SMD_PKT_INFO("finish to %s ret %d\n", smd_pkt_devp->ch_name, ret); + return ret < 0 ? ret : count; +} + +/** + * smd_pkt_poll() - poll() syscall for the smd_pkt device + * file: Pointer to the file structure. + * wait: pointer to Poll table. + * + * This function is used to poll on the smd pkt device when + * userspace client do a poll() system call. All input arguments are + * validated by the virtual file system before calling this function. + */ +static unsigned int smd_pkt_poll(struct file *file, poll_table *wait) + +{ + struct smd_pkt_dev *smd_pkt_devp = file->private_data; + unsigned int mask = 0; + unsigned long flags; + + smd_pkt_devp = file->private_data; + + if (!smd_pkt_devp || refcount_read(&smd_pkt_devp->refcount) == 1) { + SMD_PKT_ERR("invalid device handle\n"); + return POLLERR; + } + + if (!completion_done(&smd_pkt_devp->ch_open)) { + SMD_PKT_ERR("%s channel in reset\n", smd_pkt_devp->ch_name); + return POLLHUP; + } + + poll_wait(file, &smd_pkt_devp->readq, wait); + + mutex_lock(&smd_pkt_devp->lock); + + if (!completion_done(&smd_pkt_devp->ch_open) || + !smd_pkt_devp->rpdev) { + SMD_PKT_ERR("%s channel reset after wait\n", + smd_pkt_devp->ch_name); + mutex_unlock(&smd_pkt_devp->lock); + return POLLHUP; + } + + spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags); + if (!skb_queue_empty(&smd_pkt_devp->queue) || smd_pkt_devp->rskb) + mask |= POLLIN | POLLRDNORM; + + if (smd_pkt_devp->sig_change) + mask |= POLLPRI; + spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags); + + mask |= rpmsg_poll(smd_pkt_devp->rpdev->ept, file, wait); + + mutex_unlock(&smd_pkt_devp->lock); + + return mask; +} + +static void smd_pkt_rpdev_remove(struct rpmsg_device *rpdev) +{ + struct device_driver *drv = rpdev->dev.driver; + struct rpmsg_driver *rpdrv = drv_to_rpdrv(drv); + struct smd_pkt_dev *smd_pkt_devp = rpdrv_to_smd_pkt_devp(rpdrv); + + mutex_lock(&smd_pkt_devp->lock); + smd_pkt_devp->rpdev = NULL; + smd_pkt_devp->notify_state_update = true; + mutex_unlock(&smd_pkt_devp->lock); + + dev_set_drvdata(&rpdev->dev, NULL); + + /* wake up any blocked readers */ + reinit_completion(&smd_pkt_devp->ch_open); + wake_up_interruptible(&smd_pkt_devp->readq); +} + +/** + * smd_pkt_open() - open() syscall for the smd_pkt device + * inode: Pointer to the inode structure. + * file: Pointer to the file structure. + * + * This function is used to open the smd pkt device when + * userspace client do a open() system call. All input arguments are + * validated by the virtual file system before calling this function. + */ +int smd_pkt_open(struct inode *inode, struct file *file) +{ + struct smd_pkt_dev *smd_pkt_devp = cdev_to_smd_pkt_devp(inode->i_cdev); + int tout = msecs_to_jiffies(smd_pkt_devp->open_tout * 1000); + struct device *dev = &smd_pkt_devp->dev; + int ret; + + refcount_inc(&smd_pkt_devp->refcount); + get_device(dev); + + SMD_PKT_INFO("begin for %s by %s:%d ref_cnt[%d]\n", + smd_pkt_devp->ch_name, current->comm, + task_pid_nr(current), + refcount_read(&smd_pkt_devp->refcount)); + + ret = wait_for_completion_interruptible_timeout(&smd_pkt_devp->ch_open, + tout); + if (ret <= 0) { + refcount_dec(&smd_pkt_devp->refcount); + put_device(dev); + SMD_PKT_INFO("timeout for %s by %s:%d\n", smd_pkt_devp->ch_name, + current->comm, task_pid_nr(current)); + return -ETIMEDOUT; + } + file->private_data = smd_pkt_devp; + + SMD_PKT_INFO("end for %s by %s:%d ref_cnt[%d]\n", + smd_pkt_devp->ch_name, current->comm, + task_pid_nr(current), + refcount_read(&smd_pkt_devp->refcount)); + return 0; +} + +/** + * smd_pkt_release() - release operation on smd_pkt device + * inode: Pointer to the inode structure. + * file: Pointer to the file structure. + * + * This function is used to release the smd pkt device when + * userspace client do a close() system call. All input arguments are + * validated by the virtual file system before calling this function. + */ +int smd_pkt_release(struct inode *inode, struct file *file) +{ + struct smd_pkt_dev *smd_pkt_devp = cdev_to_smd_pkt_devp(inode->i_cdev); + struct device *dev = &smd_pkt_devp->dev; + struct sk_buff *skb; + unsigned long flags; + + SMD_PKT_INFO("for %s by %s:%d ref_cnt[%d]\n", + smd_pkt_devp->ch_name, current->comm, + task_pid_nr(current), + refcount_read(&smd_pkt_devp->refcount)); + + refcount_dec(&smd_pkt_devp->refcount); + if (refcount_read(&smd_pkt_devp->refcount) == 1) { + spin_lock_irqsave(&smd_pkt_devp->queue_lock, flags); + + /* Discard all SKBs */ + if (smd_pkt_devp->rskb) { + kfree_skb(smd_pkt_devp->rskb); + smd_pkt_devp->rskb = NULL; + smd_pkt_devp->rdata = NULL; + smd_pkt_devp->rdata_len = 0; + } + + while (!skb_queue_empty(&smd_pkt_devp->queue)) { + skb = skb_dequeue(&smd_pkt_devp->queue); + kfree_skb(skb); + } + wake_up_interruptible(&smd_pkt_devp->readq); + smd_pkt_devp->sig_change = false; + spin_unlock_irqrestore(&smd_pkt_devp->queue_lock, flags); + } + + put_device(dev); + + return 0; +} + +static const struct file_operations smd_pkt_fops = { + .owner = THIS_MODULE, + .open = smd_pkt_open, + .release = smd_pkt_release, + .read = smd_pkt_read, + .write = smd_pkt_write, + .poll = smd_pkt_poll, + .unlocked_ioctl = smd_pkt_ioctl, + .compat_ioctl = smd_pkt_ioctl, +}; + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct smd_pkt_dev *smd_pkt_devp = dev_to_smd_pkt_devp(dev); + + return scnprintf(buf, RPMSG_NAME_SIZE, "%s\n", smd_pkt_devp->ch_name); +} +static DEVICE_ATTR_RO(name); + +static struct attribute *smd_pkt_device_attrs[] = { + &dev_attr_name.attr, + NULL +}; +ATTRIBUTE_GROUPS(smd_pkt_device); + +/** + * parse_smdpkt_devicetree() - parse device tree binding for a subnode + * + * np: pointer to a device tree node + * smd_pkt_devp: pointer to SMD PACKET device + * + * Return: 0 on success, standard Linux error codes on error. + */ +static int smd_pkt_parse_devicetree(struct device_node *np, + struct smd_pkt_dev *smd_pkt_devp) +{ + char *key; + int ret; + + key = "qcom,smdpkt-edge"; + + ret = of_property_read_string(np, key, &smd_pkt_devp->edge); + if (ret < 0) + goto error; + + key = "qcom,smdpkt-ch-name"; + + ret = of_property_read_string(np, key, &smd_pkt_devp->ch_name); + if (ret < 0) + goto error; + + key = "qcom,smdpkt-dev-name"; + + ret = of_property_read_string(np, key, &smd_pkt_devp->dev_name); + if (ret < 0) + goto error; + + key = "qcom,smdpkt-fragmented-read"; + + smd_pkt_devp->fragmented_read = of_property_read_bool(np, key); + + SMD_PKT_INFO("Parsed %s:%s /dev/%s\n", smd_pkt_devp->edge, + smd_pkt_devp->ch_name, + smd_pkt_devp->dev_name, + smd_pkt_devp->fragmented_read); + return 0; + +error: + SMD_PKT_ERR("missing key: %s\n", key); + return ret; +} + +static void smd_pkt_release_device(struct device *dev) +{ + struct smd_pkt_dev *smd_pkt_devp = dev_to_smd_pkt_devp(dev); + + ida_simple_remove(&smd_pkt_minor_ida, MINOR(smd_pkt_devp->dev.devt)); + cdev_del(&smd_pkt_devp->cdev); +} + +static int smd_pkt_init_rpmsg(struct smd_pkt_dev *smd_pkt_devp) +{ + struct rpmsg_driver *rpdrv = &smd_pkt_devp->drv; + struct device *dev = &smd_pkt_devp->dev; + struct rpmsg_device_id *match; + char *drv_name; + + /* zalloc array of two to NULL terminate the match list */ + match = devm_kzalloc(dev, 2 * sizeof(*match), GFP_KERNEL); + if (!match) + return -ENOMEM; + + snprintf(match->name, RPMSG_NAME_SIZE, "%s", smd_pkt_devp->ch_name); + + drv_name = devm_kasprintf(dev, GFP_KERNEL, + "%s_%s", "msm_smd_pkt", smd_pkt_devp->dev_name); + if (!drv_name) + return -ENOMEM; + + rpdrv->probe = smd_pkt_rpdev_probe; + rpdrv->remove = smd_pkt_rpdev_remove; + rpdrv->callback = smd_pkt_rpdev_cb; + rpdrv->signals = smd_pkt_rpdev_sigs; + rpdrv->id_table = match; + rpdrv->drv.name = drv_name; + + register_rpmsg_driver(rpdrv); + + return 0; +} + +/** + * smdpkt - Create smd packet device and add cdev + * parent: pointer to the parent device of this smd packet device + * np: pointer to device node this smd packet device represents + * + * return: 0 for success, Standard Linux errors + */ +static int smd_pkt_create_device(struct device *parent, + struct device_node *np) +{ + struct smd_pkt_dev *smd_pkt_devp; + struct device *dev; + int ret; + + smd_pkt_devp = devm_kzalloc(parent, sizeof(*smd_pkt_devp), GFP_KERNEL); + if (!smd_pkt_devp) + return -ENOMEM; + + ret = smd_pkt_parse_devicetree(np, smd_pkt_devp); + if (ret < 0) { + SMD_PKT_ERR("failed to parse dt ret:%d\n", ret); + goto free_smd_pkt_devp; + } + + dev = &smd_pkt_devp->dev; + mutex_init(&smd_pkt_devp->lock); + refcount_set(&smd_pkt_devp->refcount, 1); + init_completion(&smd_pkt_devp->ch_open); + + /* Default open timeout for open is 120 sec */ + smd_pkt_devp->open_tout = 120; + smd_pkt_devp->sig_change = false; + + spin_lock_init(&smd_pkt_devp->queue_lock); + + smd_pkt_devp->rskb = NULL; + smd_pkt_devp->rdata = NULL; + smd_pkt_devp->rdata_len = 0; + + skb_queue_head_init(&smd_pkt_devp->queue); + init_waitqueue_head(&smd_pkt_devp->readq); + + device_initialize(dev); + dev->class = smd_pkt_class; + dev->parent = parent; + dev->groups = smd_pkt_device_groups; + dev_set_drvdata(dev, smd_pkt_devp); + + cdev_init(&smd_pkt_devp->cdev, &smd_pkt_fops); + smd_pkt_devp->cdev.owner = THIS_MODULE; + ret = ida_simple_get(&smd_pkt_minor_ida, 0, num_smd_pkt_devs, + GFP_KERNEL); + if (ret < 0) + goto free_dev; + + dev->devt = MKDEV(MAJOR(smd_pkt_major), ret); + dev_set_name(dev, smd_pkt_devp->dev_name, ret); + + ret = cdev_add(&smd_pkt_devp->cdev, dev->devt, 1); + if (ret) { + SMD_PKT_ERR("cdev_add failed for %s ret:%d\n", + smd_pkt_devp->dev_name, ret); + goto free_minor_ida; + } + + dev->release = smd_pkt_release_device; + ret = device_add(dev); + if (ret) { + SMD_PKT_ERR("device_create failed for %s ret:%d\n", + smd_pkt_devp->dev_name, ret); + goto free_minor_ida; + } + + if (device_create_file(dev, &dev_attr_open_timeout)) + SMD_PKT_ERR("device_create_file failed for %s\n", + smd_pkt_devp->dev_name); + + if (smd_pkt_init_rpmsg(smd_pkt_devp)) + goto free_minor_ida; + + return 0; + +free_minor_ida: + ida_simple_remove(&smd_pkt_minor_ida, MINOR(dev->devt)); +free_dev: + put_device(dev); + +free_smd_pkt_devp: + return ret; +} + +/** + * smd_pkt_deinit() - De-initialize this module + * + * This function frees all the memory and unregisters the char device region. + */ +static void smd_pkt_deinit(void) +{ + class_destroy(smd_pkt_class); + unregister_chrdev_region(MAJOR(smd_pkt_major), num_smd_pkt_devs); +} + +/** + * smd_pkt_probe() - Probe a SMD packet device + * + * pdev: Pointer to platform device. + * + * return: 0 on success, standard Linux error codes on error. + * + * This function is called when the underlying device tree driver registers + * a platform device, mapped to a SMD packet device. + */ +static int msm_smd_pkt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *cn; + int ret; + + num_smd_pkt_devs = of_get_child_count(dev->of_node); + ret = alloc_chrdev_region(&smd_pkt_major, 0, num_smd_pkt_devs, + "smdpkt"); + + if (ret < 0) { + SMD_PKT_ERR("alloc_chrdev_region failed ret:%d\n", ret); + return ret; + } + + smd_pkt_class = class_create(THIS_MODULE, "smdpkt"); + if (IS_ERR(smd_pkt_class)) { + SMD_PKT_ERR("class_create failed ret:%ld\n", + PTR_ERR(smd_pkt_class)); + goto error_deinit; + } + + for_each_child_of_node(dev->of_node, cn) + smd_pkt_create_device(dev, cn); + + SMD_PKT_INFO("smd Packet Port Driver Initialized\n"); + return 0; + +error_deinit: + smd_pkt_deinit(); + return ret; +} + +static const struct of_device_id msm_smd_pkt_match_table[] = { + { .compatible = "qcom,smdpkt" }, + {}, +}; + +static struct platform_driver msm_smd_pkt_driver = { + .probe = msm_smd_pkt_probe, + .driver = { + .name = MODULE_NAME, + .of_match_table = msm_smd_pkt_match_table, + }, +}; + +/** + * smd_pkt_init() - Initialization function for this module + * + * returns: 0 on success, standard Linux error code otherwise. + */ +static int __init smd_pkt_init(void) +{ + int rc; + + rc = platform_driver_register(&msm_smd_pkt_driver); + if (rc) { + SMD_PKT_ERR("msm_smd_pkt driver register failed %d\n", rc); + return rc; + } + + smd_pkt_ilctxt = ipc_log_context_create(SMD_PKT_IPC_LOG_PAGE_CNT, + "smd_pkt", 0); + return 0; +} +module_init(smd_pkt_init); + +/** + * smd_pkt_exit() - Exit function for this module + * + * This function is used to cleanup the module during the exit. + */ +static void __exit smd_pkt_exit(void) +{ + smd_pkt_deinit(); +} +module_exit(smd_pkt_exit); + +MODULE_DESCRIPTION("MSM Shared Memory Packet Port"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/char/random.c b/drivers/char/random.c index a8b7141a664a..bef8c75f0429 100644 --- a/drivers/char/random.c +++ b/drivers/char/random.c @@ -1828,7 +1828,7 @@ urandom_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) return urandom_read_nowarn(file, buf, nbytes, ppos); } -static ssize_t __maybe_unused +static ssize_t random_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) { int ret; @@ -1958,7 +1958,7 @@ static int random_fasync(int fd, struct file *filp, int on) } const struct file_operations random_fops = { - .read = urandom_read, + .read = random_read, .write = random_write, .poll = random_poll, .unlocked_ioctl = random_ioctl, @@ -2296,4 +2296,4 @@ void add_bootloader_randomness(const void *buf, unsigned int size) else add_device_randomness(buf, size); } -EXPORT_SYMBOL_GPL(add_bootloader_randomness); +EXPORT_SYMBOL_GPL(add_bootloader_randomness); \ No newline at end of file diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 3e68d37d21f8..6c43d83c29ea 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -584,3 +584,27 @@ config CLOCK_CPU_OSM_660 frequency and voltage requests for multiple clusters via the existence of multiple OSM domains. Say Y if you want to support OSM clocks. + +config SDM_GCC_429W + tristate "SDM429w Global Clock Controller" + depends on COMMON_CLK_QCOM + help + Support for the global clock controller on SDM429w/QM215 devices. + Say Y if you want to use peripheral devices such as UART, SPI, + I2C, USB, UFS, SDCC, Display, Camera, Video etc. + +config SDM_DEBUGCC_429W + tristate "SDM429W Debug Clock Controller" + depends on SDM_GCC_429W + help + Support for the debug clock controller on Qualcomm Technologies, Inc + SDM429W/QM215 devices. + Say Y if you want to support the clock measurement functionality. + +config CLOCK_CPU_SDM + bool "CPU SDM Clock Controller" + depends on COMMON_CLK_QCOM + help + Support for the cpu clock controller on SDM based devices(e.g. QM215/SDM429). + Say Y if you want to support CPU clock scaling using + CPUfreq drivers for dynamic power management. diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 0588a458ab4e..fa1f143e1d24 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -20,6 +20,7 @@ clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.o obj-$(CONFIG_APQ_MMCC_8084) += mmcc-apq8084.o obj-$(CONFIG_CLOCK_CPU_OSM_660) += clk-cpu-osm-660.o +obj-$(CONFIG_CLOCK_CPU_SDM) += clk-cpu-sdm.o obj-$(CONFIG_IPQ_GCC_4019) += gcc-ipq4019.o obj-$(CONFIG_IPQ_GCC_806X) += gcc-ipq806x.o obj-$(CONFIG_IPQ_GCC_8074) += gcc-ipq8074.o @@ -55,9 +56,11 @@ obj-$(CONFIG_QM_GCC_SCUBA) += gcc-scuba.o obj-$(CONFIG_QM_GPUCC_SCUBA) += gpucc-scuba.o obj-$(CONFIG_QM_DEBUGCC_SCUBA) += debugcc-scuba.o obj-$(CONFIG_SDM_CAMCC_LAGOON) += camcc-lagoon.o +obj-$(CONFIG_SDM_DEBUGCC_429W) += debugcc-sdm429w.o obj-$(CONFIG_SDM_DEBUGCC_LAGOON) += debugcc-lagoon.o obj-$(CONFIG_SDM_DISPCC_845) += dispcc-sdm845.o obj-$(CONFIG_SDM_DISPCC_LAGOON) += dispcc-lagoon.o +obj-$(CONFIG_SDM_GCC_429W) += gcc-sdm429w.o obj-$(CONFIG_SDM_GCC_660) += gcc-sdm660.o obj-$(CONFIG_SDM_GCC_845) += gcc-sdm845.o obj-$(CONFIG_SDM_GCC_LAGOON) += gcc-lagoon.o diff --git a/drivers/clk/qcom/apcs-msm8916.c b/drivers/clk/qcom/apcs-msm8916.c index b1cc8dbcd327..a5fbb4b895d4 100644 --- a/drivers/clk/qcom/apcs-msm8916.c +++ b/drivers/clk/qcom/apcs-msm8916.c @@ -17,7 +17,15 @@ #include "clk-regmap.h" #include "clk-regmap-mux-div.h" -static const u32 gpll0_a53cc_map[] = { 4, 5 }; +enum apcs_mux_clk_parent { + P_GPLL0, + P_APCS_CPU_PLL, +}; + +static const struct parent_map gpll0_a53cc_map[] = { + { P_GPLL0, 4 }, + { P_APCS_CPU_PLL, 5 }, +}; static const char * const gpll0_a53cc[] = { "gpll0_vote", diff --git a/drivers/clk/qcom/clk-cpu-sdm.c b/drivers/clk/qcom/clk-cpu-sdm.c new file mode 100644 index 000000000000..ebad9f0b0fe5 --- /dev/null +++ b/drivers/clk/qcom/clk-cpu-sdm.c @@ -0,0 +1,1009 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clk-pll.h" +#include "clk-debug.h" +#include "clk-rcg.h" +#include "clk-regmap-mux-div.h" +#include "common.h" +#include "vdd-level-cpu.h" + +#define to_clk_regmap_mux_div(_hw) \ + container_of(to_clk_regmap(_hw), struct clk_regmap_mux_div, clkr) + +static DEFINE_VDD_REGULATORS(vdd_hf_pll, VDD_HF_PLL_NUM, 2, vdd_hf_levels); +static DEFINE_VDD_REGS_INIT(vdd_cpu_c1, 1); +static DEFINE_VDD_REGS_INIT(vdd_cpu_cci, 1); + +enum apcs_mux_clk_parent { + P_BI_TCXO_AO, + P_GPLL0_AO_OUT_MAIN, + P_APCS_CPU_PLL, +}; + +struct pll_spm_ctrl { + u32 offset; + u32 force_event_offset; + u32 event_bit; + void __iomem *spm_base; +}; + +static struct pll_spm_ctrl apcs_pll_spm = { + .offset = 0x50, + .force_event_offset = 0x4, + .event_bit = 0x4, +}; + +static const struct parent_map apcs_mux_clk_parent_map0[] = { + { P_BI_TCXO_AO, 0 }, + { P_GPLL0_AO_OUT_MAIN, 4 }, + { P_APCS_CPU_PLL, 5 }, +}; + +static const char *const apcs_mux_clk_parent_name0[] = { + "bi_tcxo_ao", + "gpll0_ao_out_main", + "apcs_cpu_pll", +}; + +static const struct parent_map apcs_mux_clk_parent_map1[] = { + { P_BI_TCXO_AO, 0 }, + { P_GPLL0_AO_OUT_MAIN, 4 }, +}; + +static const char *const apcs_mux_clk_parent_name1[] = { + "bi_tcxo_ao", + "gpll0_ao_out_main", +}; + +static unsigned long +calc_rate(unsigned long rate, u32 m, u32 n, u32 mode, u32 hid_div) +{ + u64 tmp = rate; + + if (hid_div) { + tmp *= 2; + do_div(tmp, hid_div + 1); + } + + if (mode) { + tmp *= m; + do_div(tmp, n); + } + + return tmp; +} + +static int cpucc_clk_set_rate_and_parent(struct clk_hw *hw, unsigned long rate, + unsigned long prate, u8 index) +{ + struct clk_regmap_mux_div *cpuclk = to_clk_regmap_mux_div(hw); + + return mux_div_set_src_div(cpuclk, cpuclk->parent_map[index].cfg, + cpuclk->div); +} + +static int cpucc_clk_set_parent(struct clk_hw *hw, u8 index) +{ + return 0; +} + +static int cpucc_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long prate) +{ + struct clk_regmap_mux_div *cpuclk = to_clk_regmap_mux_div(hw); + + return mux_div_set_src_div(cpuclk, cpuclk->src, cpuclk->div); +} + +static int cpucc_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_hw *xo, *apcs_gpll0_hw, *apcs_cpu_pll_hw; + struct clk_rate_request parent_req = { }; + struct clk_regmap_mux_div *cpuclk = to_clk_regmap_mux_div(hw); + unsigned long apcs_gpll0_rate, apcs_gpll0_rrate, rate = req->rate; + unsigned long mask = BIT(cpuclk->hid_width) - 1; + u32 div = 1; + int ret; + + xo = clk_hw_get_parent_by_index(hw, P_BI_TCXO_AO); + if (rate == clk_hw_get_rate(xo)) { + req->best_parent_hw = xo; + req->best_parent_rate = rate; + cpuclk->div = div; + cpuclk->src = cpuclk->parent_map[P_BI_TCXO_AO].cfg; + return 0; + } + + apcs_gpll0_hw = clk_hw_get_parent_by_index(hw, P_GPLL0_AO_OUT_MAIN); + apcs_cpu_pll_hw = clk_hw_get_parent_by_index(hw, P_APCS_CPU_PLL); + + apcs_gpll0_rate = clk_hw_get_rate(apcs_gpll0_hw); + apcs_gpll0_rrate = DIV_ROUND_UP(apcs_gpll0_rate, 1000000) * 1000000; + + if (rate <= apcs_gpll0_rrate) { + req->best_parent_hw = apcs_gpll0_hw; + req->best_parent_rate = apcs_gpll0_rrate; + div = DIV_ROUND_CLOSEST(2 * apcs_gpll0_rrate, rate) - 1; + div = min_t(unsigned long, div, mask); + req->rate = calc_rate(req->best_parent_rate, 0, + 0, 0, div); + cpuclk->src = cpuclk->parent_map[P_GPLL0_AO_OUT_MAIN].cfg; + } else { + parent_req.rate = rate; + parent_req.best_parent_hw = apcs_cpu_pll_hw; + + req->best_parent_hw = apcs_cpu_pll_hw; + ret = __clk_determine_rate(req->best_parent_hw, &parent_req); + if (ret) + return ret; + + req->best_parent_rate = parent_req.rate; + cpuclk->src = cpuclk->parent_map[P_APCS_CPU_PLL].cfg; + } + + cpuclk->div = div; + + return 0; +} + +static void cpucc_clk_list_registers(struct seq_file *f, struct clk_hw *hw) +{ + struct clk_regmap_mux_div *cpuclk = to_clk_regmap_mux_div(hw); + int i = 0, size = 0, val; + + static struct clk_register_data data[] = { + {"CMD_RCGR", 0x0}, + {"CFG_RCGR", 0x4}, + }; + + size = ARRAY_SIZE(data); + for (i = 0; i < size; i++) { + regmap_read(cpuclk->clkr.regmap, + cpuclk->reg_offset + data[i].offset, &val); + clock_debug_output(f, false, + "%20s: 0x%.8x\n", data[i].name, val); + } +} + +static unsigned long cpucc_clk_recalc_rate(struct clk_hw *hw, + unsigned long prate) +{ + struct clk_regmap_mux_div *cpuclk = to_clk_regmap_mux_div(hw); + struct clk_hw *parent; + const char *name = clk_hw_get_name(hw); + unsigned long parent_rate; + u32 i, div, src = 0; + u32 num_parents = clk_hw_get_num_parents(hw); + int ret; + + ret = mux_div_get_src_div(cpuclk, &src, &div); + if (ret) + return ret; + + cpuclk->src = src; + cpuclk->div = div; + + for (i = 0; i < num_parents; i++) { + if (src == cpuclk->parent_map[i].cfg) { + parent = clk_hw_get_parent_by_index(hw, i); + parent_rate = clk_hw_get_rate(parent); + return calc_rate(parent_rate, 0, 0, 0, div); + } + } + pr_err("%s: Can't find parent %d\n", name, src); + + return ret; +} + +static int cpucc_clk_enable(struct clk_hw *hw) +{ + return clk_regmap_mux_div_ops.enable(hw); +} + +static void cpucc_clk_disable(struct clk_hw *hw) +{ + clk_regmap_mux_div_ops.disable(hw); +} + +static u8 cpucc_clk_get_parent(struct clk_hw *hw) +{ + return clk_regmap_mux_div_ops.get_parent(hw); +} + +static void spm_event(struct pll_spm_ctrl *apcs_pll_spm, bool enable) +{ + void __iomem *base = apcs_pll_spm->spm_base; + u32 offset, force_event_offset, bit, val; + + if (!apcs_pll_spm || !base) + return; + + offset = apcs_pll_spm->offset; + force_event_offset = apcs_pll_spm->force_event_offset; + bit = apcs_pll_spm->event_bit; + + if (enable) { + /* L2_SPM_FORCE_EVENT_EN */ + val = readl_relaxed(base + offset); + val |= BIT(bit); + writel_relaxed(val, (base + offset)); + /* Ensure that the write above goes through. */ + mb(); + + /* L2_SPM_FORCE_EVENT */ + val = readl_relaxed(base + offset + force_event_offset); + val |= BIT(bit); + writel_relaxed(val, (base + offset + force_event_offset)); + /* Ensure that the write above goes through. */ + mb(); + } else { + /* L2_SPM_FORCE_EVENT */ + val = readl_relaxed(base + offset + force_event_offset); + val &= ~BIT(bit); + writel_relaxed(val, (base + offset + force_event_offset)); + /* Ensure that the write above goes through. */ + mb(); + + /* L2_SPM_FORCE_EVENT_EN */ + val = readl_relaxed(base + offset); + val &= ~BIT(bit); + writel_relaxed(val, (base + offset)); + /* Ensure that the write above goes through. */ + mb(); + } +} + +/* + * We use the notifier function for switching to a temporary safe configuration + * (mux and divider), while the APSS pll is reconfigured. + */ +static int cpucc_notifier_cb(struct notifier_block *nb, unsigned long event, + void *data) +{ + struct clk_regmap_mux_div *cpuclk = container_of(nb, + struct clk_regmap_mux_div, clk_nb); + int ret = 0, safe_src = cpuclk->safe_src; + + switch (event) { + case PRE_RATE_CHANGE: + /* set the mux to safe source gpll0_ao_out & div */ + ret = mux_div_set_src_div(cpuclk, safe_src, 1); + spm_event(&apcs_pll_spm, true); + break; + case POST_RATE_CHANGE: + if (cpuclk->src != safe_src) + spm_event(&apcs_pll_spm, false); + break; + case ABORT_RATE_CHANGE: + pr_err("Error in configuring PLL - stay at safe src only\n"); + } + + return notifier_from_errno(ret); +} + +static const struct clk_ops cpucc_clk_ops = { + .enable = cpucc_clk_enable, + .disable = cpucc_clk_disable, + .get_parent = cpucc_clk_get_parent, + .set_rate = cpucc_clk_set_rate, + .set_parent = cpucc_clk_set_parent, + .set_rate_and_parent = cpucc_clk_set_rate_and_parent, + .determine_rate = cpucc_clk_determine_rate, + .recalc_rate = cpucc_clk_recalc_rate, + .debug_init = clk_debug_measure_add, + .list_registers = cpucc_clk_list_registers, +}; + +/* Initial configuration for 1305.6MHz */ +static const struct pll_config apcs_cpu_pll_config = { + .l = 0x44, + .m = 0, + .n = 1, + .pre_div_val = 0x0, + .pre_div_mask = 0x7 << 12, + .post_div_val = 0x0, + .post_div_mask = 0x3 << 8, + .main_output_mask = BIT(0), + .aux_output_mask = BIT(1), +}; + +static struct clk_pll apcs_cpu_pll = { + .mode_reg = 0x0, + .l_reg = 0x4, + .m_reg = 0x8, + .n_reg = 0xc, + .config_reg = 0x10, + .status_reg = 0x1c, + .status_bit = 16, + .clkr.hw.init = &(struct clk_init_data){ + .name = "apcs_cpu_pll", + .parent_names = (const char *[]){ "bi_tcxo_ao" }, + .num_parents = 1, + .ops = &clk_pll_hf_ops, + .vdd_class = &vdd_hf_pll, + .rate_max = (unsigned long[VDD_HF_PLL_NUM]) { + [VDD_HF_PLL_SVS] = 1000000000, + [VDD_HF_PLL_NOM] = 2020000000, + }, + .num_rate_max = VDD_HF_PLL_NUM, + }, +}; + +static struct clk_regmap_mux_div apcs_mux_c1_clk = { + .reg_offset = 0x0, + .hid_width = 5, + .hid_shift = 0, + .src_width = 3, + .src_shift = 8, + .safe_src = 4, + .safe_div = 1, + .parent_map = apcs_mux_clk_parent_map0, + .clk_nb.notifier_call = cpucc_notifier_cb, + .clkr.hw.init = &(struct clk_init_data) { + .name = "apcs_mux_c1_clk", + .parent_names = apcs_mux_clk_parent_name0, + .num_parents = 3, + .vdd_class = &vdd_cpu_c1, + .flags = CLK_SET_RATE_PARENT, + .ops = &cpucc_clk_ops, + }, +}; + +static struct clk_regmap_mux_div apcs_mux_cci_clk = { + .reg_offset = 0x0, + .hid_width = 5, + .hid_shift = 0, + .src_width = 3, + .src_shift = 8, + .safe_src = 4, + .safe_div = 1, + .parent_map = apcs_mux_clk_parent_map1, + .clkr.hw.init = &(struct clk_init_data) { + .name = "apcs_mux_cci_clk", + .parent_names = apcs_mux_clk_parent_name1, + .num_parents = 2, + .vdd_class = &vdd_cpu_cci, + .flags = CLK_SET_RATE_PARENT, + .ops = &cpucc_clk_ops, + }, +}; + +static const struct of_device_id match_table[] = { + { .compatible = "qcom,cpu-clock-sdm429" }, + { .compatible = "qcom,cpu-clock-qm215" }, + {} +}; + +static struct regmap_config cpu_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x34, + .fast_io = true, +}; + +static struct clk_hw *cpu_clks_hws_qm215[] = { + [APCS_CPU_PLL] = &apcs_cpu_pll.clkr.hw, + [APCS_MUX_C1_CLK] = &apcs_mux_c1_clk.clkr.hw, +}; + +static struct clk_hw *cpu_clks_hws_sdm429[] = { + [APCS_CPU_PLL] = &apcs_cpu_pll.clkr.hw, + [APCS_MUX_C1_CLK] = &apcs_mux_c1_clk.clkr.hw, + [APCS_MUX_CCI_CLK] = &apcs_mux_cci_clk.clkr.hw, +}; +static void cpucc_clk_get_speed_bin(struct platform_device *pdev, int *bin, + int *version) +{ + struct resource *res; + void __iomem *base; + u32 pte_efuse; + + *bin = 0; + *version = 0; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "efuse"); + if (!res) { + dev_info(&pdev->dev, + "No speed/PVS binning available. Defaulting to 0!\n"); + return; + } + + base = ioremap(res->start, resource_size(res)); + if (!base) { + dev_info(&pdev->dev, + "Unable to read efuse data. Defaulting to 0!\n"); + return; + } + + pte_efuse = readl_relaxed(base); + iounmap(base); + + *bin = (pte_efuse >> 2) & 0x7; + + dev_info(&pdev->dev, "PVS version: %d speed bin: %d\n", *version, *bin); +} + +static int cpucc_clk_get_fmax_vdd_class(struct platform_device *pdev, + struct clk_init_data *clk_intd, char *prop_name) +{ + struct device_node *of = pdev->dev.of_node; + struct clk_vdd_class *vdd = clk_intd->vdd_class; + u32 *array; + int prop_len, i, j, ret; + int num = vdd->num_regulators + 1; + + if (!of_find_property(of, prop_name, &prop_len)) { + dev_err(&pdev->dev, "missing %s\n", prop_name); + return -EINVAL; + } + + prop_len /= sizeof(u32); + if (prop_len % num) { + dev_err(&pdev->dev, "bad length %d\n", prop_len); + return -EINVAL; + } + + prop_len /= num; + vdd->level_votes = devm_kzalloc(&pdev->dev, prop_len * sizeof(int), + GFP_KERNEL); + if (!vdd->level_votes) + return -ENOMEM; + + vdd->vdd_uv = devm_kzalloc(&pdev->dev, + prop_len * sizeof(int) * (num - 1), GFP_KERNEL); + if (!vdd->vdd_uv) + return -ENOMEM; + + clk_intd->rate_max = devm_kzalloc(&pdev->dev, + prop_len * sizeof(unsigned long), GFP_KERNEL); + if (!clk_intd->rate_max) + return -ENOMEM; + + array = kzalloc(prop_len * sizeof(u32) * num, GFP_KERNEL); + if (!array) + return -ENOMEM; + + ret = of_property_read_u32_array(of, prop_name, array, prop_len * num); + if (ret) + return -ENOMEM; + + for (i = 0; i < prop_len; i++) { + clk_intd->rate_max[i] = array[num * i]; + for (j = 1; j < num; j++) { + vdd->vdd_uv[(num - 1) * i + (j - 1)] = + array[num * i + j]; + } + } + + kfree(array); + vdd->num_levels = prop_len; + vdd->cur_level = prop_len; + clk_intd->num_rate_max = prop_len; + + return 0; +} + +static int find_vdd_level(struct clk_init_data *clk_intd, unsigned long rate) +{ + int level; + + for (level = 0; level < clk_intd->num_rate_max; level++) + if (rate <= clk_intd->rate_max[level]) + break; + + if (level == clk_intd->num_rate_max) { + pr_err("Rate %lu for %s is greater than highest Fmax\n", rate, + clk_intd->name); + return -EINVAL; + } + + return level; +} + +static int +cpucc_clk_add_opp(struct clk_hw *hw, struct device *dev, unsigned long max_rate) +{ + struct clk_init_data *clk_intd = (struct clk_init_data *)hw->init; + struct clk_vdd_class *vdd = clk_intd->vdd_class; + int level, uv, j = 1; + unsigned long rate = 0; + long ret; + + if (IS_ERR_OR_NULL(dev)) { + pr_err("%s: Invalid parameters\n", __func__); + return -EINVAL; + } + + while (1) { + rate = clk_intd->rate_max[j++]; + level = find_vdd_level(clk_intd, rate); + if (level <= 0) { + pr_warn("clock-cpu: no corner for %lu.\n", rate); + return -EINVAL; + } + + uv = vdd->vdd_uv[level]; + if (uv < 0) { + pr_warn("clock-cpu: no uv for %lu.\n", rate); + return -EINVAL; + } + + ret = dev_pm_opp_add(dev, rate, uv); + if (ret) { + pr_warn("clock-cpu: failed to add OPP for %lu\n", rate); + return rate; + } + + if (rate >= max_rate) + break; + } + + return 0; +} + +static void cpucc_clk_print_opp_table(int cpu) +{ + struct dev_pm_opp *oppfmax, *oppfmin; + unsigned long apc_c1_fmax, apc_c1_fmin; + u32 max_index = apcs_mux_c1_clk.clkr.hw.init->num_rate_max; + + apc_c1_fmax = apcs_mux_c1_clk.clkr.hw.init->rate_max[max_index - 1]; + apc_c1_fmin = apcs_mux_c1_clk.clkr.hw.init->rate_max[1]; + + oppfmax = dev_pm_opp_find_freq_exact(get_cpu_device(cpu), + apc_c1_fmax, true); + oppfmin = dev_pm_opp_find_freq_exact(get_cpu_device(cpu), + apc_c1_fmin, true); + pr_info("Clock_cpu:(cpu %d) OPP voltage for %lu: %ld\n", cpu, + apc_c1_fmin, dev_pm_opp_get_voltage(oppfmin)); + pr_info("Clock_cpu:(cpu %d) OPP voltage for %lu: %ld\n", cpu, + apc_c1_fmax, dev_pm_opp_get_voltage(oppfmax)); + +} + +static void cpucc_clk_populate_opp_table(struct platform_device *pdev) +{ + unsigned long apc_c1_fmax; + u32 max_index = apcs_mux_c1_clk.clkr.hw.init->num_rate_max; + int cpu, sdm_cpu = 0; + + apc_c1_fmax = apcs_mux_c1_clk.clkr.hw.init->rate_max[max_index - 1]; + + for_each_possible_cpu(cpu) { + sdm_cpu = cpu; + WARN(cpucc_clk_add_opp(&apcs_mux_c1_clk.clkr.hw, + get_cpu_device(cpu), apc_c1_fmax), + "Failed to add OPP levels for apcs_mux_c1_clk\n"); + } + cpucc_clk_print_opp_table(sdm_cpu); +} + +static int clock_sdm429_pm_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + switch (event) { + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + clk_unprepare(apcs_mux_c1_clk.clkr.hw.clk); + clk_unprepare(apcs_mux_cci_clk.clkr.hw.clk); + break; + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + clk_prepare(apcs_mux_c1_clk.clkr.hw.clk); + clk_prepare(apcs_mux_cci_clk.clkr.hw.clk); + break; + default: + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block clock_sdm429_pm_notifier = { + .notifier_call = clock_sdm429_pm_event, +}; + +static int clock_qm215_pm_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + switch (event) { + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + clk_unprepare(apcs_mux_c1_clk.clkr.hw.clk); + break; + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + clk_prepare(apcs_mux_c1_clk.clkr.hw.clk); + break; + default: + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block clock_qm215_pm_notifier = { + .notifier_call = clock_qm215_pm_event, +}; + +static int cpucc_driver_probe(struct platform_device *pdev) +{ + struct resource *res; + struct clk_hw_onecell_data *data; + struct device *dev = &pdev->dev; + struct clk *clk; + int i, ret, speed_bin, version, cpu; + char prop_name[] = "qcom,speedX-bin-vX-XXX"; + void __iomem *base; + bool is_sdm429, is_qm215; + + is_sdm429 = of_device_is_compatible(pdev->dev.of_node, + "qcom,cpu-clock-sdm429"); + + is_qm215 = of_device_is_compatible(pdev->dev.of_node, + "qcom,cpu-clock-qm215"); + + clk = clk_get(dev, "xo_ao"); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) != -EPROBE_DEFER) + dev_err(dev, "Unable to get xo clock\n"); + return PTR_ERR(clk); + } + clk_put(clk); + + clk = clk_get(dev, "gpll0_ao"); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) != -EPROBE_DEFER) + dev_err(dev, "Unable to get GPLL0 clock\n"); + return PTR_ERR(clk); + } + clk_put(clk); + + /* Rail Regulator for apcs_pll */ + vdd_hf_pll.regulator[0] = devm_regulator_get(&pdev->dev, "vdd_hf_pll"); + if (IS_ERR(vdd_hf_pll.regulator[0])) { + if (!(PTR_ERR(vdd_hf_pll.regulator[0]) == -EPROBE_DEFER)) + dev_err(&pdev->dev, + "Unable to get vdd_hf_pll regulator\n"); + return PTR_ERR(vdd_hf_pll.regulator[0]); + } + + vdd_hf_pll.regulator[1] = devm_regulator_get(&pdev->dev, "vdd_dig_ao"); + if (IS_ERR(vdd_hf_pll.regulator[1])) { + if (!(PTR_ERR(vdd_hf_pll.regulator[1]) == -EPROBE_DEFER)) + dev_err(&pdev->dev, + "Unable to get vdd_dig_ao regulator\n"); + return PTR_ERR(vdd_hf_pll.regulator[1]); + } + + /* Rail Regulator for APCS C1 mux */ + vdd_cpu_c1.regulator[0] = devm_regulator_get(&pdev->dev, "cpu-vdd"); + if (IS_ERR(vdd_cpu_c1.regulator[0])) { + if (!(PTR_ERR(vdd_cpu_c1.regulator[0]) == -EPROBE_DEFER)) + dev_err(&pdev->dev, + "Unable to get cpu-vdd regulator\n"); + return PTR_ERR(vdd_cpu_c1.regulator[0]); + } + + /* Rail Regulator for APCS CCI mux */ + if (is_sdm429) { + vdd_cpu_cci.regulator[0] = + devm_regulator_get(&pdev->dev, "cpu-vdd"); + if (IS_ERR(vdd_cpu_cci.regulator[0])) { + if (!(PTR_ERR(vdd_cpu_cci.regulator[0]) == + -EPROBE_DEFER)) + dev_err(&pdev->dev, + "Unable to get cpu-vdd regulator\n"); + return PTR_ERR(vdd_cpu_cci.regulator[0]); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "apcs_pll"); + if (res == NULL) { + dev_err(&pdev->dev, "Failed to get apcs_pll resources\n"); + return -EINVAL; + } + + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) { + dev_err(&pdev->dev, "Failed map apcs_cpu_pll register base\n"); + return PTR_ERR(base); + } + + cpu_regmap_config.name = "apcs_pll"; + apcs_cpu_pll.clkr.regmap = devm_regmap_init_mmio(dev, base, + &cpu_regmap_config); + if (IS_ERR(apcs_cpu_pll.clkr.regmap)) { + dev_err(&pdev->dev, "Couldn't get regmap for apcs_cpu_pll\n"); + return PTR_ERR(apcs_cpu_pll.clkr.regmap); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "apcs-c1-rcg-base"); + if (res == NULL) { + dev_err(&pdev->dev, "Failed to get apcs-c1 resources\n"); + return -EINVAL; + } + + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) { + dev_err(&pdev->dev, "Failed map apcs-c1-rcg register base\n"); + return PTR_ERR(base); + } + + cpu_regmap_config.name = "apcs-c1-rcg-base"; + apcs_mux_c1_clk.clkr.regmap = devm_regmap_init_mmio(dev, base, + &cpu_regmap_config); + if (IS_ERR(apcs_mux_c1_clk.clkr.regmap)) { + dev_err(&pdev->dev, "Couldn't get regmap for apcs-c1-rcg\n"); + return PTR_ERR(apcs_mux_c1_clk.clkr.regmap); + } + + if (is_sdm429) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "apcs-cci-rcg-base"); + if (res == NULL) { + dev_err(&pdev->dev, "Failed to get apcs-cci resources\n"); + return -EINVAL; + } + + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) { + dev_err(&pdev->dev, "Failed map apcs-cci-rcg register base\n"); + return PTR_ERR(base); + } + + cpu_regmap_config.name = "apcs-cci-rcg-base"; + apcs_mux_cci_clk.clkr.regmap = devm_regmap_init_mmio(dev, base, + &cpu_regmap_config); + if (IS_ERR(apcs_mux_cci_clk.clkr.regmap)) { + dev_err(&pdev->dev, "Couldn't get regmap for apcs-cci-rcg\n"); + return PTR_ERR(apcs_mux_cci_clk.clkr.regmap); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "spm_c1_base"); + if (res == NULL) { + dev_err(&pdev->dev, "Failed to get spm-c1 resources\n"); + return -EINVAL; + } + + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) { + dev_err(&pdev->dev, "Failed to ioremap c1 spm registers\n"); + return -ENOMEM; + } + + apcs_pll_spm.spm_base = base; + } + + /* Get speed bin information */ + cpucc_clk_get_speed_bin(pdev, &speed_bin, &version); + + snprintf(prop_name, ARRAY_SIZE(prop_name), + "qcom,speed%d-bin-v%d-%s", speed_bin, version, "c1"); + + ret = cpucc_clk_get_fmax_vdd_class(pdev, + (struct clk_init_data *)apcs_mux_c1_clk.clkr.hw.init, + prop_name); + if (ret) { + dev_err(&pdev->dev, "Didn't get c1 speed bin\n"); + ret = cpucc_clk_get_fmax_vdd_class(pdev, + (struct clk_init_data *) + apcs_mux_c1_clk.clkr.hw.init, + prop_name); + if (ret) { + dev_err(&pdev->dev, "Unable to get vdd class for c1\n"); + return ret; + } + } + + if (is_sdm429) { + snprintf(prop_name, ARRAY_SIZE(prop_name), + "qcom,speed%d-bin-v%d-%s", speed_bin, version, "cci"); + + ret = cpucc_clk_get_fmax_vdd_class(pdev, + (struct clk_init_data *)apcs_mux_cci_clk.clkr.hw.init, + prop_name); + if (ret) { + dev_err(&pdev->dev, "Didn't get cci speed bin\n"); + ret = cpucc_clk_get_fmax_vdd_class(pdev, + (struct clk_init_data *) + apcs_mux_cci_clk.clkr.hw.init, + prop_name); + if (ret) { + dev_err(&pdev->dev, "Unable get vdd class for cci\n"); + return ret; + } + } + } + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (is_sdm429) { + data->num = ARRAY_SIZE(cpu_clks_hws_sdm429); + + for (i = 0; i < ARRAY_SIZE(cpu_clks_hws_sdm429); i++) { + ret = devm_clk_hw_register(dev, + cpu_clks_hws_sdm429[i]); + if (ret) { + dev_err(&pdev->dev, + "Failed to register clock\n"); + return ret; + } + data->hws[i] = cpu_clks_hws_sdm429[i]; + } + } else if (is_qm215) { + data->num = ARRAY_SIZE(cpu_clks_hws_qm215); + + for (i = 0; i < ARRAY_SIZE(cpu_clks_hws_qm215); i++) { + ret = devm_clk_hw_register(dev, + cpu_clks_hws_qm215[i]); + if (ret) { + dev_err(&pdev->dev, + "Failed to register clock\n"); + return ret; + } + data->hws[i] = cpu_clks_hws_qm215[i]; + } + } + + ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, data); + if (ret) { + dev_err(&pdev->dev, "CPU clock driver registration failed\n"); + return ret; + } + + /* For safe freq switching during rate change */ + ret = clk_notifier_register(apcs_mux_c1_clk.clkr.hw.clk, + &apcs_mux_c1_clk.clk_nb); + if (ret) { + dev_err(dev, "failed to register clock notifier: %d\n", ret); + return ret; + } + + /* + * To increase the enable count for the clocks so + * that they dont get disabled during late init. + */ + get_online_cpus(); + for_each_online_cpu(cpu) { + WARN(clk_prepare_enable(apcs_mux_c1_clk.clkr.hw.clk), + "Unable to turn on CPU clock\n"); + if (is_sdm429) + clk_prepare_enable(apcs_mux_cci_clk.clkr.hw.clk); + } + put_online_cpus(); + + if (is_sdm429) + register_pm_notifier(&clock_sdm429_pm_notifier); + else if (is_qm215) + register_pm_notifier(&clock_qm215_pm_notifier); + + cpucc_clk_populate_opp_table(pdev); + dev_info(dev, "CPU clock Driver probed successfully\n"); + + return ret; +} + +static struct platform_driver cpu_clk_driver = { + .probe = cpucc_driver_probe, + .driver = { + .name = "qcom-cpu-sdm", + .of_match_table = match_table, + }, +}; + +static int __init cpu_clk_init(void) +{ + return platform_driver_register(&cpu_clk_driver); +} +subsys_initcall(cpu_clk_init); + +static void __exit cpu_clk_exit(void) +{ + platform_driver_unregister(&cpu_clk_driver); +} +module_exit(cpu_clk_exit); + +#define REG_OFFSET 0x4 +#define APCS_PLL 0x0b016000 +#define A53SS_MUX_C1 0x0b011050 + +static void config_enable_hf_pll(void __iomem *base) +{ + /* Configure USER_CTL value */ + writel_relaxed(0xf, base + apcs_cpu_pll.config_reg); + + /* Enable the pll */ + writel_relaxed(0x2, base + apcs_cpu_pll.mode_reg); + udelay(2); + writel_relaxed(0x6, base + apcs_cpu_pll.mode_reg); + udelay(50); + writel_relaxed(0x7, base + apcs_cpu_pll.mode_reg); + /* Ensure that the writes go through before enabling PLL */ + mb(); +} + +static int __init cpu_clock_init(void) +{ + struct device_node *dev; + void __iomem *base; + int count, regval = 0; + unsigned long enable_mask = GENMASK(2, 0); + + dev = of_find_compatible_node(NULL, NULL, "qcom,cpu-clock-sdm429"); + + if (!dev) + dev = of_find_compatible_node(NULL, NULL, + "qcom,cpu-clock-qm215"); + if (!dev) { + pr_err("device node not initialized\n"); + return -ENOMEM; + } + + base = ioremap_nocache(APCS_PLL, SZ_64); + if (!base) + return -ENOMEM; + + regval = readl_relaxed(base); + if (!((regval & enable_mask) == enable_mask)) + config_enable_hf_pll(base); + + iounmap(base); + + base = ioremap_nocache(A53SS_MUX_C1, SZ_8); + if (!base) + return -ENOMEM; + + writel_relaxed(0x501, base + REG_OFFSET); + + /* Update bit */ + regval = readl_relaxed(base); + regval |= BIT(0); + writel_relaxed(regval, base); + + /* Wait for update to take effect */ + for (count = 500; count > 0; count--) { + if ((!(readl_relaxed(base))) & BIT(0)) + break; + udelay(1); + } + + /* Update bit */ + regval = readl_relaxed(base); + regval |= BIT(0); + writel_relaxed(regval, base); + + /* Wait for update to take effect */ + for (count = 500; count > 0; count--) { + if ((!(readl_relaxed(base))) & BIT(0)) + break; + udelay(1); + } + + return 0; +} +early_initcall(cpu_clock_init); + +MODULE_ALIAS("platform:cpu"); +MODULE_DESCRIPTION("SDM CPU clock Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clk/qcom/clk-debug.c b/drivers/clk/qcom/clk-debug.c index 889f124a2f11..aa7855596430 100644 --- a/drivers/clk/qcom/clk-debug.c +++ b/drivers/clk/qcom/clk-debug.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2016, 2019-2020 The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2016, 2019-2021 The Linux Foundation. All rights reserved. */ #include #include @@ -15,6 +15,7 @@ #include "clk-regmap.h" #include "clk-debug.h" #include "common.h" +#include "gdsc-debug.h" static struct clk_hw *measure; @@ -413,16 +414,22 @@ EXPORT_SYMBOL(map_debug_bases); /** * qcom_clk_dump - dump the HW specific registers associated with this clock + * and regulator * @clk: clock source + * @regulator: regulator * @calltrace: indicates whether calltrace is required * * This function attempts to print all the registers associated with the - * clock and it's parents. + * clock, it's parents and regulator. */ -void qcom_clk_dump(struct clk *clk, bool calltrace) +void qcom_clk_dump(struct clk *clk, struct regulator *regulator, + bool calltrace) { struct clk_hw *hw; + if (!IS_ERR_OR_NULL(regulator)) + gdsc_debug_print_regs(regulator); + if (IS_ERR_OR_NULL(clk)) return; @@ -437,22 +444,27 @@ EXPORT_SYMBOL(qcom_clk_dump); /** * qcom_clk_bulk_dump - dump the HW specific registers associated with clocks - * @clks: the clk_bulk_data table of consumer + * and regulator * @num_clks: the number of clk_bulk_data + * @clks: the clk_bulk_data table of consumer + * @regulator: regulator source * @calltrace: indicates whether calltrace is required * * This function attempts to print all the registers associated with the - * clock and it's parents for all the clocks in the list. + * clocks in the list and regulator. */ void qcom_clk_bulk_dump(int num_clks, struct clk_bulk_data *clks, - bool calltrace) + struct regulator *regulator, bool calltrace) { int i; + if (!IS_ERR_OR_NULL(regulator)) + gdsc_debug_print_regs(regulator); + if (IS_ERR_OR_NULL(clks)) return; for (i = 0; i < num_clks; i++) - qcom_clk_dump(clks[i].clk, calltrace); + qcom_clk_dump(clks[i].clk, NULL, calltrace); } EXPORT_SYMBOL(qcom_clk_bulk_dump); diff --git a/drivers/clk/qcom/clk-pll.c b/drivers/clk/qcom/clk-pll.c index cb6cb8710daf..1f4298eb7034 100644 --- a/drivers/clk/qcom/clk-pll.c +++ b/drivers/clk/qcom/clk-pll.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Copyright (c) 2013, 2021, The Linux Foundation. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -342,3 +342,64 @@ const struct clk_ops clk_pll_sr2_ops = { .determine_rate = clk_pll_determine_rate, }; EXPORT_SYMBOL_GPL(clk_pll_sr2_ops); + +static int +clk_pll_hf_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long prate) +{ + struct clk_pll *pll = to_clk_pll(hw); + bool enabled; + u32 mode, l_val; + u32 enable_mask = PLL_OUTCTRL | PLL_BYPASSNL | PLL_RESET_N; + + regmap_read(pll->clkr.regmap, pll->mode_reg, &mode); + enabled = (mode & enable_mask) == enable_mask; + + if (enabled) + clk_pll_disable(hw); + + l_val = rate / prate; + + regmap_update_bits(pll->clkr.regmap, pll->l_reg, 0x3ff, l_val); + regmap_update_bits(pll->clkr.regmap, pll->m_reg, 0x7ffff, 0); + regmap_update_bits(pll->clkr.regmap, pll->n_reg, 0x7ffff, 1); + + if (enabled) + clk_pll_sr2_enable(hw); + + return 0; +} + +static void clk_pll_hf_list_registers(struct seq_file *f, struct clk_hw *hw) +{ + struct clk_pll *pll = to_clk_pll(hw); + int size, i, val; + + static struct clk_register_data data[] = { + {"PLL_MODE", 0x0}, + {"PLL_L_VAL", 0x4}, + {"PLL_M_VAL", 0x8}, + {"PLL_N_VAL", 0xC}, + {"PLL_USER_CTL", 0x10}, + {"PLL_CONFIG_CTL", 0x14}, + {"PLL_STATUS_CTL", 0x1C}, + }; + + size = ARRAY_SIZE(data); + + for (i = 0; i < size; i++) { + regmap_read(pll->clkr.regmap, pll->mode_reg + data[i].offset, + &val); + clock_debug_output(f, false, + "%20s: 0x%.8x\n", data[i].name, val); + } +} + +const struct clk_ops clk_pll_hf_ops = { + .enable = clk_pll_sr2_enable, + .disable = clk_pll_disable, + .set_rate = clk_pll_hf_set_rate, + .recalc_rate = clk_pll_recalc_rate, + .determine_rate = clk_pll_determine_rate, + .list_registers = clk_pll_hf_list_registers, +}; +EXPORT_SYMBOL(clk_pll_hf_ops); diff --git a/drivers/clk/qcom/clk-pll.h b/drivers/clk/qcom/clk-pll.h index ffd0c63bddbc..31fe26003037 100644 --- a/drivers/clk/qcom/clk-pll.h +++ b/drivers/clk/qcom/clk-pll.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Copyright (c) 2013, 2021, The Linux Foundation. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -63,6 +63,7 @@ struct clk_pll { extern const struct clk_ops clk_pll_ops; extern const struct clk_ops clk_pll_vote_ops; extern const struct clk_ops clk_pll_sr2_ops; +extern const struct clk_ops clk_pll_hf_ops; #define to_clk_pll(_hw) container_of(to_clk_regmap(_hw), struct clk_pll, clkr) diff --git a/drivers/clk/qcom/clk-regmap-mux-div.c b/drivers/clk/qcom/clk-regmap-mux-div.c index 6044839da85a..0ba00428d42e 100644 --- a/drivers/clk/qcom/clk-regmap-mux-div.c +++ b/drivers/clk/qcom/clk-regmap-mux-div.c @@ -56,20 +56,26 @@ int mux_div_set_src_div(struct clk_regmap_mux_div *md, u32 src, u32 div) } EXPORT_SYMBOL_GPL(mux_div_set_src_div); -static void mux_div_get_src_div(struct clk_regmap_mux_div *md, u32 *src, - u32 *div) +int mux_div_get_src_div(struct clk_regmap_mux_div *md, u32 *src, + u32 *div) { + int ret = 0; u32 val, d, s; const char *name = clk_hw_get_name(&md->clkr.hw); - regmap_read(md->clkr.regmap, CMD_RCGR + md->reg_offset, &val); + ret = regmap_read(md->clkr.regmap, CMD_RCGR + md->reg_offset, &val); + if (ret) + return ret; if (val & CMD_RCGR_DIRTY_CFG) { pr_err("%s: RCG configuration is pending\n", name); - return; + return -EBUSY; } - regmap_read(md->clkr.regmap, CFG_RCGR + md->reg_offset, &val); + ret = regmap_read(md->clkr.regmap, CFG_RCGR + md->reg_offset, &val); + if (ret) + return ret; + s = (val >> md->src_shift); s &= BIT(md->src_width) - 1; *src = s; @@ -77,6 +83,8 @@ static void mux_div_get_src_div(struct clk_regmap_mux_div *md, u32 *src, d = (val >> md->hid_shift); d &= BIT(md->hid_width) - 1; *div = d; + + return ret; } static inline bool is_better_rate(unsigned long req, unsigned long best, @@ -142,7 +150,7 @@ static int __mux_div_set_rate_and_parent(struct clk_hw *hw, unsigned long rate, if (is_better_rate(rate, best_rate, actual_rate)) { best_rate = actual_rate; - best_src = md->parent_map[i]; + best_src = md->parent_map[i].cfg; best_div = div - 1; } @@ -169,7 +177,7 @@ static u8 mux_div_get_parent(struct clk_hw *hw) mux_div_get_src_div(md, &src, &div); for (i = 0; i < clk_hw_get_num_parents(hw); i++) - if (src == md->parent_map[i]) + if (src == md->parent_map[i].cfg) return i; pr_err("%s: Can't find parent with src %d\n", name, src); @@ -180,7 +188,7 @@ static int mux_div_set_parent(struct clk_hw *hw, u8 index) { struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); - return mux_div_set_src_div(md, md->parent_map[index], md->div); + return mux_div_set_src_div(md, md->parent_map[index].cfg, md->div); } static int mux_div_set_rate(struct clk_hw *hw, @@ -197,7 +205,7 @@ static int mux_div_set_rate_and_parent(struct clk_hw *hw, unsigned long rate, struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); return __mux_div_set_rate_and_parent(hw, rate, prate, - md->parent_map[index]); + md->parent_map[index].cfg); } static unsigned long mux_div_recalc_rate(struct clk_hw *hw, unsigned long prate) @@ -209,7 +217,7 @@ static unsigned long mux_div_recalc_rate(struct clk_hw *hw, unsigned long prate) mux_div_get_src_div(md, &src, &div); for (i = 0; i < num_parents; i++) - if (src == md->parent_map[i]) { + if (src == md->parent_map[i].cfg) { struct clk_hw *p = clk_hw_get_parent_by_index(hw, i); unsigned long parent_rate = clk_hw_get_rate(p); @@ -220,7 +228,23 @@ static unsigned long mux_div_recalc_rate(struct clk_hw *hw, unsigned long prate) return 0; } +static int mux_div_enable(struct clk_hw *hw) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + + return mux_div_set_src_div(md, md->src, md->div); +} + +static void mux_div_disable(struct clk_hw *hw) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + + mux_div_set_src_div(md, md->safe_src, md->safe_div); +} + const struct clk_ops clk_regmap_mux_div_ops = { + .enable = mux_div_enable, + .disable = mux_div_disable, .get_parent = mux_div_get_parent, .set_parent = mux_div_set_parent, .set_rate = mux_div_set_rate, diff --git a/drivers/clk/qcom/clk-regmap-mux-div.h b/drivers/clk/qcom/clk-regmap-mux-div.h index 6cd6261be7ac..33dd3d06deb7 100644 --- a/drivers/clk/qcom/clk-regmap-mux-div.h +++ b/drivers/clk/qcom/clk-regmap-mux-div.h @@ -8,6 +8,7 @@ #define __QCOM_CLK_REGMAP_MUX_DIV_H__ #include +#include "common.h" #include "clk-regmap.h" /** @@ -19,7 +20,19 @@ * @src_shift: lowest bit of source select field * @div: the divider raw configuration value * @src: the mux index which will be used if the clock is enabled - * @parent_map: map from parent_names index to src_sel field + * @safe_src: the safe source mux value we switch to, while the main PLL is + * reconfigured + * @safe_div: the safe divider value that we set, while the main PLL is + * reconfigured + * @safe_freq: When switching rates from A to B, the mux div clock will + * instead switch from A -> safe_freq -> B. This allows the + * mux_div clock to change rates while enabled, even if this + * behavior is not supported by the parent clocks. + * If changing the rate of parent A also causes the rate of + * parent B to change, then safe_freq must be defined. + * safe_freq is expected to have a source clock which is always + * on and runs at only one rate. + * @parent_map: pointer to parent_map struct * @clkr: handle between common and hardware-specific interfaces * @pclk: the input PLL clock * @clk_nb: clock notifier for rate changes of the input PLL @@ -32,7 +45,10 @@ struct clk_regmap_mux_div { u32 src_shift; u32 div; u32 src; - const u32 *parent_map; + u32 safe_src; + u32 safe_div; + unsigned long safe_freq; + const struct parent_map *parent_map; struct clk_regmap clkr; struct clk *pclk; struct notifier_block clk_nb; @@ -40,5 +56,6 @@ struct clk_regmap_mux_div { extern const struct clk_ops clk_regmap_mux_div_ops; extern int mux_div_set_src_div(struct clk_regmap_mux_div *md, u32 src, u32 div); +int mux_div_get_src_div(struct clk_regmap_mux_div *md, u32 *src, u32 *div); #endif diff --git a/drivers/clk/qcom/clk-smd-rpm.c b/drivers/clk/qcom/clk-smd-rpm.c index 1f7faa192601..78dbdb012f40 100644 --- a/drivers/clk/qcom/clk-smd-rpm.c +++ b/drivers/clk/qcom/clk-smd-rpm.c @@ -1080,6 +1080,108 @@ static const struct rpm_smd_clk_desc rpm_clk_sdm660 = { .num_clks = ARRAY_SIZE(sdm660_clks), }; +/* sdm429w SMD clocks */ +DEFINE_CLK_SMD_RPM_BRANCH(sdm429w, bi_tcxo, bi_tcxo_ao, + QCOM_SMD_RPM_MISC_CLK, 0, 19200000); +DEFINE_CLK_SMD_RPM(sdm429w, pnoc_clk, pnoc_a_clk, QCOM_SMD_RPM_BUS_CLK, 0); +DEFINE_CLK_SMD_RPM(sdm429w, snoc_clk, snoc_a_clk, QCOM_SMD_RPM_BUS_CLK, 1); + +DEFINE_CLK_SMD_RPM(sdm429w, bimc_clk, bimc_a_clk, QCOM_SMD_RPM_MEM_CLK, 0); + +DEFINE_CLK_SMD_RPM(sdm429w, sysmmnoc_clk, sysmmnoc_a_clk, QCOM_SMD_RPM_BUS_CLK, + 2); + +DEFINE_CLK_SMD_RPM_QDSS(sdm429w, qdss_clk, qdss_a_clk, QCOM_SMD_RPM_MISC_CLK, + 1); + +DEFINE_CLK_SMD_RPM_XO_BUFFER(sdm429w, bb_clk1, bb_clk1_a, 1); +DEFINE_CLK_SMD_RPM_XO_BUFFER(sdm429w, bb_clk2, bb_clk2_a, 2); +DEFINE_CLK_SMD_RPM_XO_BUFFER(sdm429w, rf_clk2, rf_clk2_a, 5); +DEFINE_CLK_SMD_RPM_XO_BUFFER(sdm429w, div_clk2, div_clk2_a, 0xc); + +DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(sdm429w, bb_clk1_pin, bb_clk1_a_pin, 1); +DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(sdm429w, bb_clk2_pin, bb_clk2_a_pin, 2); + +/* Voter clocks */ +static DEFINE_CLK_VOTER(pnoc_msmbus_clk, pnoc_clk, LONG_MAX); +static DEFINE_CLK_VOTER(pnoc_msmbus_a_clk, pnoc_a_clk, LONG_MAX); +static DEFINE_CLK_VOTER(pnoc_keepalive_a_clk, pnoc_a_clk, LONG_MAX); + +static DEFINE_CLK_VOTER(sysmmnoc_msmbus_clk, sysmmnoc_clk, LONG_MAX); +static DEFINE_CLK_VOTER(sysmmnoc_msmbus_a_clk, sysmmnoc_a_clk, LONG_MAX); + +static DEFINE_CLK_VOTER(pnoc_usb_clk, pnoc_clk, LONG_MAX); +static DEFINE_CLK_VOTER(snoc_usb_clk, snoc_clk, LONG_MAX); +static DEFINE_CLK_VOTER(bimc_usb_clk, bimc_clk, LONG_MAX); + +static DEFINE_CLK_VOTER(pnoc_usb_a_clk, pnoc_a_clk, LONG_MAX); +static DEFINE_CLK_VOTER(snoc_usb_a_clk, snoc_a_clk, LONG_MAX); +static DEFINE_CLK_VOTER(bimc_usb_a_clk, bimc_a_clk, LONG_MAX); + +static DEFINE_CLK_VOTER(snoc_wcnss_a_clk, snoc_a_clk, LONG_MAX); +static DEFINE_CLK_VOTER(bimc_wcnss_a_clk, bimc_a_clk, LONG_MAX); + +/* Branch Voter clocks */ +static DEFINE_CLK_BRANCH_VOTER(bi_tcxo_lpm_clk, bi_tcxo); + +static struct clk_hw *qm215_clks[] = { + [RPM_SMD_XO_CLK_SRC] = &sdm429w_bi_tcxo.hw, + [RPM_SMD_XO_A_CLK_SRC] = &sdm429w_bi_tcxo_ao.hw, + [RPM_SMD_QDSS_CLK] = &sdm429w_qdss_clk.hw, + [RPM_SMD_QDSS_A_CLK] = &sdm429w_qdss_a_clk.hw, + [RPM_SMD_PNOC_CLK] = &sdm429w_pnoc_clk.hw, + [RPM_SMD_PNOC_A_CLK] = &sdm429w_pnoc_a_clk.hw, + [RPM_SMD_SNOC_CLK] = &sdm429w_snoc_clk.hw, + [RPM_SMD_SNOC_A_CLK] = &sdm429w_snoc_a_clk.hw, + [RPM_SMD_BIMC_CLK] = &sdm429w_bimc_clk.hw, + [RPM_SMD_BIMC_A_CLK] = &sdm429w_bimc_a_clk.hw, + [RPM_SMD_BIMC_GPU_CLK] = &scuba_bimc_gpu_clk.hw, + [RPM_SMD_BIMC_GPU_A_CLK] = &scuba_bimc_gpu_a_clk.hw, + [RPM_SMD_SYSMMNOC_CLK] = &sdm429w_sysmmnoc_clk.hw, + [RPM_SMD_SYSMMNOC_A_CLK] = &sdm429w_sysmmnoc_a_clk.hw, + [RPM_SMD_BB_CLK1] = &sdm429w_bb_clk1.hw, + [RPM_SMD_BB_CLK1_A] = &sdm429w_bb_clk1_a.hw, + [RPM_SMD_BB_CLK2] = &sdm429w_bb_clk2.hw, + [RPM_SMD_BB_CLK2_A] = &sdm429w_bb_clk2_a.hw, + [RPM_SMD_BB_CLK1_PIN] = &sdm429w_bb_clk1_pin.hw, + [RPM_SMD_BB_CLK1_A_PIN] = &sdm429w_bb_clk1_a_pin.hw, + [RPM_SMD_BB_CLK2_PIN] = &sdm429w_bb_clk2_pin.hw, + [RPM_SMD_BB_CLK2_A_PIN] = &sdm429w_bb_clk2_a_pin.hw, + [RPM_SMD_RF_CLK2] = &sdm429w_rf_clk2.hw, + [RPM_SMD_RF_CLK2_A] = &sdm429w_rf_clk2_a.hw, + [RPM_SMD_DIV_CLK2] = &sdm429w_div_clk2.hw, + [RPM_SMD_DIV_A_CLK2] = &sdm429w_div_clk2_a.hw, + [PNOC_MSMBUS_CLK] = &pnoc_msmbus_clk.hw, + [PNOC_MSMBUS_A_CLK] = &pnoc_msmbus_a_clk.hw, + [PNOC_KEEPALIVE_A_CLK] = &pnoc_keepalive_a_clk.hw, + [SNOC_MSMBUS_CLK] = &snoc_msmbus_clk.hw, + [SNOC_MSMBUS_A_CLK] = &snoc_msmbus_a_clk.hw, + [BIMC_MSMBUS_CLK] = &bimc_msmbus_clk.hw, + [BIMC_MSMBUS_A_CLK] = &bimc_msmbus_a_clk.hw, + [PNOC_USB_CLK] = &pnoc_usb_clk.hw, + [PNOC_USB_A_CLK] = &pnoc_usb_a_clk.hw, + [SNOC_USB_CLK] = &snoc_usb_clk.hw, + [SNOC_USB_A_CLK] = &snoc_usb_a_clk.hw, + [BIMC_USB_CLK] = &bimc_usb_clk.hw, + [BIMC_USB_A_CLK] = &bimc_usb_a_clk.hw, + [SNOC_WCNSS_A_CLK] = &snoc_wcnss_a_clk.hw, + [BIMC_WCNSS_A_CLK] = &bimc_wcnss_a_clk.hw, + [SYSMMNOC_MSMBUS_CLK] = &sysmmnoc_msmbus_clk.hw, + [SYSMMNOC_MSMBUS_A_CLK] = &sysmmnoc_msmbus_a_clk.hw, + [CXO_SMD_OTG_CLK] = &bi_tcxo_otg_clk.hw, + [CXO_SMD_LPM_CLK] = &bi_tcxo_lpm_clk.hw, + [CXO_SMD_PIL_PRONTO_CLK] = &bi_tcxo_pil_pronto_clk.hw, + [CXO_SMD_PIL_MSS_CLK] = &bi_tcxo_pil_mss_clk.hw, + [CXO_SMD_WLAN_CLK] = &bi_tcxo_wlan_clk.hw, + [CXO_SMD_PIL_LPASS_CLK] = &bi_tcxo_pil_lpass_clk.hw, +}; + +static const struct rpm_smd_clk_desc rpm_clk_qm215 = { + .clks = qm215_clks, + .num_rpm_clks = RPM_SMD_SYSMMNOC_A_CLK, + .num_clks = ARRAY_SIZE(qm215_clks), +}; + static const struct of_device_id rpm_smd_clk_match_table[] = { { .compatible = "qcom,rpmcc-msm8916", .data = &rpm_clk_msm8916 }, { .compatible = "qcom,rpmcc-msm8974", .data = &rpm_clk_msm8974 }, @@ -1087,6 +1189,7 @@ static const struct of_device_id rpm_smd_clk_match_table[] = { { .compatible = "qcom,rpmcc-bengal", .data = &rpm_clk_bengal}, { .compatible = "qcom,rpmcc-scuba", .data = &rpm_clk_scuba}, { .compatible = "qcom,rpmcc-sdm660", .data = &rpm_clk_sdm660 }, + { .compatible = "qcom,rpmcc-qm215", .data = &rpm_clk_qm215 }, { } }; MODULE_DEVICE_TABLE(of, rpm_smd_clk_match_table); @@ -1097,7 +1200,7 @@ static int rpm_smd_clk_probe(struct platform_device *pdev) struct clk *clk; struct rpm_cc *rcc; struct clk_onecell_data *data; - int ret, is_bengal, is_scuba, is_sdm660; + int ret, is_bengal, is_scuba, is_sdm660, is_qm215; size_t num_clks, i; struct clk_hw **hw_clks; const struct rpm_smd_clk_desc *desc; @@ -1115,12 +1218,22 @@ static int rpm_smd_clk_probe(struct platform_device *pdev) is_sdm660 = of_device_is_compatible(pdev->dev.of_node, "qcom,rpmcc-sdm660"); + + is_qm215 = of_device_is_compatible(pdev->dev.of_node, + "qcom,rpmcc-qm215"); + if (is_sdm660) { ret = clk_vote_bimc(&sdm660_bimc_clk.hw, INT_MAX); if (ret < 0) return ret; } + if (is_qm215) { + ret = clk_vote_bimc(&sdm429w_bimc_clk.hw, INT_MAX); + if (ret < 0) + return ret; + } + desc = of_device_get_match_data(&pdev->dev); if (!desc) return -EINVAL; @@ -1204,6 +1317,15 @@ static int rpm_smd_clk_probe(struct platform_device *pdev) /* Hold an active set vote for the cnoc_periph resource */ clk_set_rate(cnoc_periph_keepalive_a_clk.hw.clk, 19200000); clk_prepare_enable(cnoc_periph_keepalive_a_clk.hw.clk); + } else if (is_qm215) { + clk_prepare_enable(sdm429w_bi_tcxo_ao.hw.clk); + + /* + * Hold an active set vote for the pnoc_periph PCNOC AHB + * resource. Sleep set vote is 0 + */ + clk_set_rate(pnoc_keepalive_a_clk.hw.clk, 19200000); + clk_prepare_enable(pnoc_keepalive_a_clk.hw.clk); } dev_info(&pdev->dev, "Registered RPM clocks\n"); diff --git a/drivers/clk/qcom/debugcc-sdm429w.c b/drivers/clk/qcom/debugcc-sdm429w.c new file mode 100644 index 000000000000..d14919a3a168 --- /dev/null +++ b/drivers/clk/qcom/debugcc-sdm429w.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "clk: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clk-debug.h" +#include "common.h" + +static struct measure_clk_data debug_mux_priv = { + .ctl_reg = 0x74004, + .status_reg = 0x74008, + .xo_div4_cbcr = 0x30034, +}; + +static const char *const gcc_debug_mux_parent_names[] = { + "gcc_ahb_clk", + "gcc_apss_ahb_clk", + "gcc_apss_axi_clk", + "gcc_bimc_gfx_clk", + "gcc_bimc_gpu_clk", + "gcc_blsp1_ahb_clk", + "gcc_blsp1_qup2_i2c_apps_clk", + "gcc_blsp1_qup2_spi_apps_clk", + "gcc_blsp1_qup3_i2c_apps_clk", + "gcc_blsp1_qup3_spi_apps_clk", + "gcc_blsp1_qup4_i2c_apps_clk", + "gcc_blsp1_qup4_spi_apps_clk", + "gcc_blsp1_sleep_clk", + "gcc_blsp1_uart1_apps_clk", + "gcc_blsp1_uart1_sim_clk", + "gcc_blsp1_uart2_apps_clk", + "gcc_blsp1_uart2_sim_clk", + "gcc_blsp2_ahb_clk", + "gcc_blsp2_qup1_i2c_apps_clk", + "gcc_blsp2_qup1_spi_apps_clk", + "gcc_blsp2_qup2_i2c_apps_clk", + "gcc_blsp2_qup2_spi_apps_clk", + "gcc_blsp2_qup3_i2c_apps_clk", + "gcc_blsp2_qup3_spi_apps_clk", + "gcc_blsp2_sleep_clk", + "gcc_blsp2_uart1_apps_clk", + "gcc_blsp2_uart1_sim_clk", + "gcc_blsp2_uart2_apps_clk", + "gcc_blsp2_uart2_sim_clk", + "gcc_boot_rom_ahb_clk", + "gcc_camss_ahb_clk", + "gcc_camss_cci_ahb_clk", + "gcc_camss_cci_clk", + "gcc_camss_cpp_ahb_clk", + "gcc_camss_cpp_axi_clk", + "gcc_camss_cpp_clk", + "gcc_camss_csi0_ahb_clk", + "gcc_camss_csi0_clk", + "gcc_camss_csi0phy_clk", + "gcc_camss_csi0phytimer_clk", + "gcc_camss_csi0pix_clk", + "gcc_camss_csi0rdi_clk", + "gcc_camss_csi1_ahb_clk", + "gcc_camss_csi1_clk", + "gcc_camss_csi1phy_clk", + "gcc_camss_csi1phytimer_clk", + "gcc_camss_csi1pix_clk", + "gcc_camss_csi1rdi_clk", + "gcc_camss_csi2_ahb_clk", + "gcc_camss_csi2_clk", + "gcc_camss_csi2phy_clk", + "gcc_camss_csi2pix_clk", + "gcc_camss_csi2rdi_clk", + "gcc_camss_csi_vfe0_clk", + "gcc_camss_csi_vfe1_clk", + "gcc_camss_gp0_clk", + "gcc_camss_gp1_clk", + "gcc_camss_ispif_ahb_clk", + "gcc_camss_jpeg0_clk", + "gcc_camss_jpeg_ahb_clk", + "gcc_camss_jpeg_axi_clk", + "gcc_camss_mclk0_clk", + "gcc_camss_mclk1_clk", + "gcc_camss_mclk2_clk", + "gcc_camss_micro_ahb_clk", + "gcc_camss_top_ahb_clk", + "gcc_camss_vfe0_clk", + "gcc_camss_vfe1_ahb_clk", + "gcc_camss_vfe1_axi_clk", + "gcc_camss_vfe1_clk", + "gcc_camss_vfe_ahb_clk", + "gcc_camss_vfe_axi_clk", + "gcc_crypto_ahb_clk", + "gcc_crypto_axi_clk", + "gcc_crypto_clk", + "gcc_gp1_clk", + "gcc_gp2_clk", + "gcc_gp3_clk", + "gcc_im_sleep_clk", + "gcc_lpass_mport_axi_clk", + "gcc_lpass_q6_axi_clk", + "gcc_lpass_sway_clk", + "gcc_mdss_ahb_clk", + "gcc_mdss_axi_clk", + "gcc_mdss_byte0_clk", + "gcc_mdss_esc0_clk", + "gcc_mdss_mdp_clk", + "gcc_mdss_pclk0_clk", + "gcc_mdss_vsync_clk", + "gcc_mpm_ahb_clk", + "gcc_msg_ram_ahb_clk", + "gcc_oxili_ahb_clk", + "gcc_oxili_aon_clk", + "gcc_oxili_gfx3d_clk", + "gcc_pcnoc_mpu_cfg_ahb_clk", + "gcc_pdm2_clk", + "gcc_pdm_ahb_clk", + "gcc_pdm_xo4_clk", + "gcc_prng_ahb_clk", + "gcc_q6_mpu_cfg_ahb_clk", + "gcc_rpm_cfg_xpu_clk", + "gcc_sdcc1_ahb_clk", + "gcc_sdcc1_apps_clk", + "gcc_sdcc1_ice_core_clk", + "gcc_sdcc2_ahb_clk", + "gcc_sdcc2_apps_clk", + "gcc_sec_ctrl_acc_clk", + "gcc_sec_ctrl_ahb_clk", + "gcc_sec_ctrl_boot_rom_patch_clk", + "gcc_sec_ctrl_clk", + "gcc_sec_ctrl_sense_clk", + "gcc_tcsr_ahb_clk", + "gcc_tlmm_ahb_clk", + "gcc_tlmm_clk", + "gcc_usb2a_phy_sleep_clk", + "gcc_usb_hs_ahb_clk", + "gcc_usb_hs_inactivity_timers_clk", + "gcc_usb_hs_phy_cfg_ahb_clk", + "gcc_usb_hs_system_clk", + "gcc_venus0_ahb_clk", + "gcc_venus0_axi_clk", + "gcc_venus0_core0_vcodec0_clk", + "gcc_venus0_vcodec0_clk", + "gcc_xo_clk", + "gcc_xo_div4_clk", + "gcc_gfx_tbu_clk", + "gcc_gfx_tcu_clk", + "gcc_gtcu_ahb_clk", + "gcc_bimc_clk", + "gcc_smmu_cfg_clk", +}; + +static int gcc_debug_mux_sels[] = { + 0x148, /* gcc_ahb_clk */ + 0x168, /* gcc_apss_ahb_clk */ + 0x169, /* gcc_apss_axi_clk */ + 0x2D, /* gcc_bimc_gfx_clk */ + 0x157, /* gcc_bimc_gpu_clk */ + 0x88, /* gcc_blsp1_ahb_clk */ + 0x90, /* gcc_blsp1_qup2_i2c_apps_clk */ + 0x8E, /* gcc_blsp1_qup2_spi_apps_clk */ + 0x94, /* gcc_blsp1_qup3_i2c_apps_clk */ + 0x93, /* gcc_blsp1_qup3_spi_apps_clk */ + 0x96, /* gcc_blsp1_qup4_i2c_apps_clk */ + 0x95, /* gcc_blsp1_qup4_spi_apps_clk */ + 0x89, /* gcc_blsp1_sleep_clk */ + 0x8C, /* gcc_blsp1_uart1_apps_clk */ + 0x8D, /* gcc_blsp1_uart1_sim_clk */ + 0x91, /* gcc_blsp1_uart2_apps_clk */ + 0x92, /* gcc_blsp1_uart2_sim_clk */ + 0x98, /* gcc_blsp2_ahb_clk */ + 0x9B, /* gcc_blsp2_qup1_i2c_apps_clk */ + 0x9A, /* gcc_blsp2_qup1_spi_apps_clk */ + 0xA0, /* gcc_blsp2_qup2_i2c_apps_clk */ + 0x9E, /* gcc_blsp2_qup2_spi_apps_clk */ + 0xA4, /* gcc_blsp2_qup3_i2c_apps_clk */ + 0xA3, /* gcc_blsp2_qup3_spi_apps_clk */ + 0x99, /* gcc_blsp2_sleep_clk */ + 0x9C, /* gcc_blsp2_uart1_apps_clk */ + 0x9D, /* gcc_blsp2_uart1_sim_clk */ + 0x9A, /* gcc_blsp2_uart2_apps_clk */ + 0xA2, /* gcc_blsp2_uart2_sim_clk */ + 0xF8, /* gcc_boot_rom_ahb_clk */ + 0xA8, /* gcc_camss_ahb_clk */ + 0xB0, /* gcc_camss_cci_ahb_clk */ + 0xAF, /* gcc_camss_cci_clk */ + 0xBA, /* gcc_camss_cpp_ahb_clk */ + 0x1A3, /* gcc_camss_cpp_axi_clk */ + 0xB9, /* gcc_camss_cpp_clk */ + 0xC1, /* gcc_camss_csi0_ahb_clk */ + 0xC0, /* gcc_camss_csi0_clk */ + 0xC2, /* gcc_camss_csi0phy_clk */ + 0xB1, /* gcc_camss_csi0phytimer_clk */ + 0xC4, /* gcc_camss_csi0pix_clk */ + 0xC3, /* gcc_camss_csi0rdi_clk */ + 0xC6, /* gcc_camss_csi1_ahb_clk */ + 0xC5, /* gcc_camss_csi1_clk */ + 0xC7, /* gcc_camss_csi1phy_clk */ + 0xB2, /* gcc_camss_csi1phytimer_clk */ + 0xE1, /* gcc_camss_csi1pix_clk */ + 0xE0, /* gcc_camss_csi1rdi_clk */ + 0xE4, /* gcc_camss_csi2_ahb_clk */ + 0xE3, /* gcc_camss_csi2_clk */ + 0xE5, /* gcc_camss_csi2phy_clk */ + 0xE7, /* gcc_camss_csi2pix_clk */ + 0xE6, /* gcc_camss_csi2rdi_clk */ + 0xBF, /* gcc_camss_csi_vfe0_clk */ + 0x1A0, /* gcc_camss_csi_vfe1_clk */ + 0xAB, /* gcc_camss_gp0_clk */ + 0xAC, /* gcc_camss_gp1_clk */ + 0xE2, /* gcc_camss_ispif_ahb_clk */ + 0xB3, /* gcc_camss_jpeg0_clk */ + 0xB4, /* gcc_camss_jpeg_ahb_clk */ + 0xB5, /* gcc_camss_jpeg_axi_clk */ + 0xAD, /* gcc_camss_mclk0_clk */ + 0xAE, /* gcc_camss_mclk1_clk */ + 0x1BD, /* gcc_camss_mclk2_clk */ + 0xAA, /* gcc_camss_micro_ahb_clk */ + 0xA9, /* gcc_camss_top_ahb_clk */ + 0xB8, /* gcc_camss_vfe0_clk */ + 0x1A2, /* gcc_camss_vfe1_ahb_clk */ + 0x1A4, /* gcc_camss_vfe1_axi_clk */ + 0x1A1, /* gcc_camss_vfe1_clk */ + 0xBB, /* gcc_camss_vfe_ahb_clk */ + 0xBC, /* gcc_camss_vfe_axi_clk */ + 0x13A, /* gcc_crypto_ahb_clk */ + 0x139, /* gcc_crypto_axi_clk */ + 0x138, /* gcc_crypto_clk */ + 0x10, /* gcc_gp1_clk */ + 0x11, /* gcc_gp2_clk */ + 0x12, /* gcc_gp3_clk */ + 0x14B, /* gcc_im_sleep_clk */ + 0x162, /* gcc_lpass_mport_axi_clk */ + 0x160, /* gcc_lpass_q6_axi_clk */ + 0x163, /* gcc_lpass_sway_clk */ + 0x1F6, /* gcc_mdss_ahb_clk */ + 0x1F7, /* gcc_mdss_axi_clk */ + 0x1FC, /* gcc_mdss_byte0_clk */ + 0x1FD, /* gcc_mdss_esc0_clk */ + 0x1F9, /* gcc_mdss_mdp_clk */ + 0x1F8, /* gcc_mdss_pclk0_clk */ + 0x1FB, /* gcc_mdss_vsync_clk */ + 0x110, /* gcc_mpm_ahb_clk */ + 0x100, /* gcc_msg_ram_ahb_clk */ + 0x1EB, /* gcc_oxili_ahb_clk */ + 0xEE, /* gcc_oxili_aon_clk */ + 0x1EA, /* gcc_oxili_gfx3d_clk */ + 0xC9, /* gcc_pcnoc_mpu_cfg_ahb_clk */ + 0xD2, /* gcc_pdm2_clk */ + 0xD0, /* gcc_pdm_ahb_clk */ + 0xD1, /* gcc_pdm_xo4_clk */ + 0xD8, /* gcc_prng_ahb_clk */ + 0xC8, /* gcc_q6_mpu_cfg_ahb_clk */ + 0x38, /* gcc_rpm_cfg_xpu_clk */ + 0x69, /* gcc_sdcc1_ahb_clk */ + 0x68, /* gcc_sdcc1_apps_clk */ + 0x6A, /* gcc_sdcc1_ice_core_clk */ + 0x71, /* gcc_sdcc2_ahb_clk */ + 0x70, /* gcc_sdcc2_apps_clk */ + 0x120, /* gcc_sec_ctrl_acc_clk */ + 0x121, /* gcc_sec_ctrl_ahb_clk */ + 0x124, /* gcc_sec_ctrl_boot_rom_patch_clk */ + 0x122, /* gcc_sec_ctrl_clk */ + 0x123, /* gcc_sec_ctrl_sense_clk */ + 0xE8, /* gcc_tcsr_ahb_clk */ + 0x108, /* gcc_tlmm_ahb_clk */ + 0x109, /* gcc_tlmm_clk */ + 0x63, /* gcc_usb2a_phy_sleep_clk */ + 0x61, /* gcc_usb_hs_ahb_clk */ + 0x62, /* gcc_usb_hs_inactivity_timers_clk */ + 0x64, /* gcc_usb_hs_phy_cfg_ahb_clk */ + 0x60, /* gcc_usb_hs_system_clk */ + 0x1F3, /* gcc_venus0_ahb_clk */ + 0x1F2, /* gcc_venus0_axi_clk */ + 0x1B8, /* gcc_venus0_core0_vcodec0_clk */ + 0x1F1, /* gcc_venus0_vcodec0_clk */ + 0x149, /* gcc_xo_clk */ + 0x14A, /* gcc_xo_div4_clk */ + 0x52, /* gcc_gfx_tbu_clk */ + 0x53, /* gcc_gfx_tcu_clk */ + 0x58, /* gcc_gtcu_ahb_clk */ + 0x15A, /* gcc_bimc_clk */ + 0x5B, /* gcc_smmu_cfg_clk */ +}; + +static struct clk_debug_mux gcc_debug_mux = { + .priv = &debug_mux_priv, + .en_mask = BIT(16), + .debug_offset = 0x74000, + .post_div_offset = 0x74000, + .cbcr_offset = 0x74000, + .src_sel_mask = 0x1FF, + .src_sel_shift = 0, + .post_div_mask = 0xF000, + .post_div_shift = 12, + .post_div_val = 1, + .mux_sels = gcc_debug_mux_sels, + .hw.init = &(struct clk_init_data){ + .name = "gcc_debug_mux", + .ops = &clk_debug_mux_ops, + .parent_names = gcc_debug_mux_parent_names, + .num_parents = ARRAY_SIZE(gcc_debug_mux_parent_names), + .flags = CLK_IS_MEASURE, + }, +}; + +static struct mux_regmap_names mux_list[] = { + { .mux = &gcc_debug_mux, .regmap_name = "qcom,gcc" }, +}; + +static const struct of_device_id clk_debug_match_table[] = { + { .compatible = "qcom,sdm429w-debugcc" }, + { .compatible = "qcom,qm215-debugcc" }, + { } +}; + +static int clk_debug_sdm429w_probe(struct platform_device *pdev) +{ + struct clk *clk; + int ret, i; + + BUILD_BUG_ON(ARRAY_SIZE(gcc_debug_mux_parent_names) != + ARRAY_SIZE(gcc_debug_mux_sels)); + + clk = devm_clk_get(&pdev->dev, "xo_clk_src"); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) != -EPROBE_DEFER) + dev_err(&pdev->dev, "Unable to get xo clock\n"); + return PTR_ERR(clk); + } + + debug_mux_priv.cxo = clk; + + for (i = 0; i < ARRAY_SIZE(mux_list); i++) { + ret = map_debug_bases(pdev, mux_list[i].regmap_name, + mux_list[i].mux); + if (ret == -EBADR) + continue; + else if (ret) + return ret; + + clk = devm_clk_register(&pdev->dev, &mux_list[i].mux->hw); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Unable to register %s, err:(%d)\n", + clk_hw_get_name(&mux_list[i].mux->hw), + PTR_ERR(clk)); + return PTR_ERR(clk); + } + } + + ret = clk_debug_measure_register(&gcc_debug_mux.hw); + if (ret) { + dev_err(&pdev->dev, "Could not register Measure clocks\n"); + return ret; + } + + dev_info(&pdev->dev, "Registered debug measure clocks\n"); + + return ret; +} + +static struct platform_driver clk_debug_driver = { + .probe = clk_debug_sdm429w_probe, + .driver = { + .name = "sdm429w-debugcc", + .of_match_table = clk_debug_match_table, + }, +}; + +static int __init clk_debug_sdm429w_init(void) +{ + return platform_driver_register(&clk_debug_driver); +} +fs_initcall(clk_debug_sdm429w_init); + +MODULE_DESCRIPTION("QTI DEBUG CC SDM429W Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clk/qcom/gcc-sdm429w.c b/drivers/clk/qcom/gcc-sdm429w.c new file mode 100644 index 000000000000..96b99c080a59 --- /dev/null +++ b/drivers/clk/qcom/gcc-sdm429w.c @@ -0,0 +1,4461 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-voter.h" +#include "common.h" +#include "reset.h" +#include "vdd-level-sdm429w.h" + +#define F_SLEW(f, s, h, m, n, sf) { (f), (s), (2 * (h) - 1), (m), (n), (sf) } + +static DEFINE_VDD_REGULATORS(vdd_cx, VDD_NUM, 1, vdd_corner); + +enum { + P_BI_TCXO, + P_CORE_BI_PLL_TEST_SE, + P_DSI0_PHY_PLL_OUT_BYTECLK, + P_DSI0_PHY_PLL_OUT_DSICLK, + P_DSI1_PHY_PLL_OUT_BYTECLK, + P_DSI1_PHY_PLL_OUT_DSICLK, + P_GPLL0_OUT_AUX, + P_GPLL0_OUT_MAIN, + P_GPLL3_OUT_MAIN, + P_GPLL4_OUT_AUX, + P_GPLL4_OUT_MAIN, + P_GPLL6_OUT_AUX, + P_GPLL6_OUT_MAIN, + P_SLEEP_CLK, +}; + +static const struct parent_map gcc_parent_map_0[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 1 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_0[] = { + "bi_tcxo", + "gpll0_out_main", + "core_bi_pll_test_se", +}; + +static const char * const gcc_parent_names_ao_0[] = { + "bi_tcxo_ao", + "gpll0_ao_out_main", + "core_bi_pll_test_se", +}; + + +static const struct parent_map gcc_parent_map_1[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 1 }, + { P_GPLL6_OUT_AUX, 2 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_1[] = { + "bi_tcxo", + "gpll0_out_main", + "gpll6_out_aux", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_2[] = { + { P_BI_TCXO, 0 }, +}; + +static const char * const gcc_parent_names_2[] = { + "bi_tcxo", +}; + +static const struct parent_map gcc_parent_map_3[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 1 }, + { P_GPLL6_OUT_AUX, 2 }, + { P_SLEEP_CLK, 6 }, +}; + +static const char * const gcc_parent_names_3[] = { + "bi_tcxo", + "gpll0_out_main", + "gpll6_out_aux", + "sleep_clk", +}; + +static const struct parent_map gcc_parent_map_4[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 1 }, + { P_GPLL6_OUT_MAIN, 2 }, + { P_SLEEP_CLK, 6 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_4[] = { + "bi_tcxo", + "gpll0_out_main", + "gpll6_out_main", + "sleep_clk", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_5[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 1 }, + { P_GPLL6_OUT_MAIN, 2 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_5[] = { + "bi_tcxo", + "gpll0_out_main", + "gpll6_out_main", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_6[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_AUX, 2 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_6[] = { + "bi_tcxo", + "gpll0_out_aux", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_7[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 1 }, + { P_GPLL6_OUT_MAIN, 2 }, + { P_GPLL4_OUT_MAIN, 3 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_7[] = { + "bi_tcxo", + "gpll0_out_main", + "gpll6_out_main", + "gpll4_out_main", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_8[] = { + { P_BI_TCXO, 0 }, + { P_DSI0_PHY_PLL_OUT_BYTECLK, 1 }, + { P_GPLL0_OUT_AUX, 2 }, + { P_DSI1_PHY_PLL_OUT_BYTECLK, 3 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_8[] = { + "bi_tcxo", + "dsi0_phy_pll_out_byteclk", + "gpll0_out_aux", + "dsi1_phy_pll_out_byteclk", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_9[] = { + { P_BI_TCXO, 0 }, + { P_DSI1_PHY_PLL_OUT_BYTECLK, 1 }, + { P_GPLL0_OUT_AUX, 2 }, + { P_DSI0_PHY_PLL_OUT_BYTECLK, 3 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_9[] = { + "bi_tcxo", + "dsi1_phy_pll_out_byteclk", + "gpll0_out_aux", + "dsi0_phy_pll_out_byteclk", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_10[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 1 }, + { P_GPLL6_OUT_MAIN, 2 }, + { P_GPLL4_OUT_AUX, 3 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_10[] = { + "bi_tcxo", + "gpll0_out_main", + "gpll6_out_main", + "gpll4_out_aux", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_11[] = { + { P_BI_TCXO, 0 }, + { P_DSI0_PHY_PLL_OUT_BYTECLK, 2 }, + { P_GPLL0_OUT_AUX, 3 }, + { P_DSI1_PHY_PLL_OUT_BYTECLK, 4 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_11[] = { + "bi_tcxo", + "dsi0_phy_pll_out_byteclk", + "gpll0_out_aux", + "dsi1_phy_pll_out_byteclk", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_12[] = { + { P_BI_TCXO, 0 }, + { P_DSI1_PHY_PLL_OUT_BYTECLK, 2 }, + { P_GPLL0_OUT_AUX, 3 }, + { P_DSI0_PHY_PLL_OUT_BYTECLK, 4 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_12[] = { + "bi_tcxo", + "dsi1_phy_pll_out_byteclk", + "gpll0_out_aux", + "dsi0_phy_pll_out_byteclk", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_14[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 1 }, + { P_GPLL3_OUT_MAIN, 2 }, + { P_GPLL6_OUT_AUX, 3 }, + { P_GPLL4_OUT_AUX, 4 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const struct parent_map gcc_parent_map_14_gfx3d[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 5 }, + { P_GPLL3_OUT_MAIN, 2 }, + { P_GPLL6_OUT_AUX, 6 }, + { P_GPLL4_OUT_AUX, 4 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_14[] = { + "bi_tcxo", + "gpll0_out_main", + "gpll3_out_main", + "gpll6_out_aux", + "gpll4_out_aux", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_15[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 1 }, + { P_DSI0_PHY_PLL_OUT_DSICLK, 2 }, + { P_GPLL6_OUT_AUX, 3 }, + { P_DSI1_PHY_PLL_OUT_DSICLK, 4 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_15[] = { + "bi_tcxo", + "gpll0_out_main", + "dsi0_phy_pll_out_dsiclk", + "gpll6_out_aux", + "dsi1_phy_pll_out_dsiclk", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_16[] = { + { P_BI_TCXO, 0 }, + { P_DSI0_PHY_PLL_OUT_DSICLK, 1 }, + { P_GPLL0_OUT_AUX, 2 }, + { P_DSI1_PHY_PLL_OUT_DSICLK, 3 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_16[] = { + "bi_tcxo", + "dsi0_phy_pll_out_dsiclk", + "gpll0_out_aux", + "dsi1_phy_pll_out_dsiclk", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_17[] = { + { P_BI_TCXO, 0 }, + { P_DSI1_PHY_PLL_OUT_DSICLK, 1 }, + { P_GPLL0_OUT_AUX, 2 }, + { P_DSI0_PHY_PLL_OUT_DSICLK, 3 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_17[] = { + "bi_tcxo", + "dsi1_phy_pll_out_dsiclk", + "gpll0_out_aux", + "dsi0_phy_pll_out_dsiclk", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_18[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 1 }, + { P_GPLL4_OUT_MAIN, 2 }, + { P_GPLL6_OUT_AUX, 3 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_18[] = { + "bi_tcxo", + "gpll0_out_main", + "gpll4_out_main", + "gpll6_out_aux", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_19[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 1 }, + { P_GPLL4_OUT_AUX, 2 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_19[] = { + "bi_tcxo", + "gpll0_out_main", + "gpll4_out_aux", + "core_bi_pll_test_se", +}; + +static const struct parent_map gcc_parent_map_20[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 1 }, + { P_GPLL6_OUT_AUX, 2 }, + { P_SLEEP_CLK, 6 }, + { P_CORE_BI_PLL_TEST_SE, 7 }, +}; + +static const char * const gcc_parent_names_20[] = { + "bi_tcxo", + "gpll0_out_main", + "gpll6_out_aux", + "sleep_clk", + "core_bi_pll_test_se", +}; + +static struct clk_alpha_pll gpll0_sleep_clk_src = { + .offset = 0x21000, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT], + .clkr = { + .enable_reg = 0x45008, + .enable_mask = BIT(23), + .enable_is_inverted = true, + .hw.init = &(struct clk_init_data){ + .name = "gpll0_sleep_clk_src", + .parent_names = (const char *[]){ "bi_tcxo" }, + .num_parents = 1, + .ops = &clk_alpha_pll_ops, + }, + }, +}; + +static unsigned int soft_vote_gpll0; + +static struct clk_alpha_pll gpll0_out_main = { + .offset = 0x21000, + .soft_vote = &soft_vote_gpll0, + .soft_vote_mask = PLL_SOFT_VOTE_PRIMARY, + .flags = SUPPORTS_FSM_MODE, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT], + .clkr = { + .enable_reg = 0x45000, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gpll0_out_main", + .parent_names = (const char *[]) + { "bi_tcxo" }, + .num_parents = 1, + .ops = &clk_alpha_pll_ops, + }, + }, +}; + +static struct clk_fixed_factor gpll0_out_aux = { + .mult = 1, + .div = 1, + .hw.init = &(struct clk_init_data){ + .name = "gpll0_out_aux", + .parent_names = (const char *[]){ "gpll0_out_main" }, + .num_parents = 1, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_alpha_pll gpll0_ao_out_main = { + .offset = 0x21000, + .soft_vote = &soft_vote_gpll0, + .soft_vote_mask = PLL_SOFT_VOTE_CPU, + .flags = SUPPORTS_FSM_MODE, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT], + .clkr = { + .enable_reg = 0x45000, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gpll0_ao_out_main", + .parent_names = (const char *[]){ "bi_tcxo_ao" }, + .num_parents = 1, + .ops = &clk_alpha_pll_ops, + }, + }, +}; + +/* 750MHz configuration */ +static struct alpha_pll_config gpll3_config = { + .l = 0x27, + .alpha = 0x0, + .alpha_hi = 0x10, + .alpha_en_mask = BIT(24), + .post_div_mask = 0xf << 8, + .post_div_val = 0x1 << 8, + .vco_mask = 0x3 << 20, + .main_output_mask = 0x1, + .config_ctl_val = 0x4001055b, + .test_ctl_hi_val = 0x40000600, + .test_ctl_hi_mask = 0xffffffff, +}; + +static struct pll_vco gpll3_vco[] = { + { 700000000, 1400000000, 0 }, +}; + +static struct clk_alpha_pll gpll3_out_main = { + .offset = 0x22000, + .flags = SUPPORTS_SLEW, + .vco_table = gpll3_vco, + .num_vco = ARRAY_SIZE(gpll3_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT], + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "gpll3_out_main", + .parent_names = (const char *[]){ "bi_tcxo" }, + .num_parents = 1, + .ops = &clk_alpha_pll_slew_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW_L1] = 800000000, + [VDD_NOMINAL] = 1400000000}, + }, + }, +}; + +static struct clk_alpha_pll gpll4_out_main = { + .offset = 0x24000, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT], + .clkr = { + .enable_reg = 0x45000, + .enable_mask = BIT(5), + .hw.init = &(struct clk_init_data){ + .name = "gpll4_out_main", + .parent_names = (const char *[]){ "bi_tcxo" }, + .num_parents = 1, + .ops = &clk_alpha_pll_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_NOMINAL] = 1400000000}, + }, + }, +}; + +static struct clk_pll gpll6 = { + .l_reg = 0x37004, + .m_reg = 0x37008, + .n_reg = 0x3700C, + .config_reg = 0x37014, + .mode_reg = 0x37000, + .status_reg = 0x3701C, + .status_bit = 17, + .clkr.hw.init = &(struct clk_init_data){ + .name = "gpll6", + .parent_names = (const char *[]){ "bi_tcxo" }, + .num_parents = 1, + .ops = &clk_pll_ops, + }, +}; + +static struct clk_regmap gpll6_out_main = { + .enable_reg = 0x45000, + .enable_mask = BIT(7), + .hw.init = &(struct clk_init_data){ + .name = "gpll6_out_main", + .parent_names = (const char *[]){ "gpll6" }, + .num_parents = 1, + .ops = &clk_pll_vote_ops, + }, +}; + +static struct clk_regmap gpll6_out_aux = { + .enable_reg = 0x45000, + .enable_mask = BIT(7), + .hw.init = &(struct clk_init_data){ + .name = "gpll6_out_aux", + .parent_names = (const char *[]){ "gpll6" }, + .num_parents = 1, + .ops = &clk_pll_vote_ops, + }, +}; + +static const struct freq_tbl ftbl_apss_ahb_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(50000000, P_GPLL0_OUT_MAIN, 16, 0, 0), + F(100000000, P_GPLL0_OUT_MAIN, 8, 0, 0), + F(133333333, P_GPLL0_OUT_MAIN, 6, 0, 0), + { } +}; + +static struct clk_rcg2 apss_ahb_clk_src = { + .cmd_rcgr = 0x46000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_apss_ahb_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "apss_ahb_clk_src", + .parent_names = gcc_parent_names_ao_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + }, +}; + +static const struct freq_tbl ftbl_blsp1_qup1_i2c_apps_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(50000000, P_GPLL0_OUT_MAIN, 16, 0, 0), + { } +}; + +static struct clk_rcg2 blsp1_qup1_i2c_apps_clk_src = { + .cmd_rcgr = 0x200c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_i2c_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp1_qup1_i2c_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 50000000}, + }, +}; + +static const struct freq_tbl ftbl_blsp1_qup1_spi_apps_clk_src[] = { + F(960000, P_BI_TCXO, 10, 1, 2), + F(4800000, P_BI_TCXO, 4, 0, 0), + F(9600000, P_BI_TCXO, 2, 0, 0), + F(16000000, P_GPLL0_OUT_MAIN, 10, 1, 5), + F(19200000, P_BI_TCXO, 1, 0, 0), + F(25000000, P_GPLL0_OUT_MAIN, 16, 1, 2), + F(50000000, P_GPLL0_OUT_MAIN, 16, 0, 0), + { } +}; + +static struct clk_rcg2 blsp1_qup1_spi_apps_clk_src = { + .cmd_rcgr = 0x2024, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_spi_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp1_qup1_spi_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 25000000, + [VDD_NOMINAL] = 50000000}, + }, +}; + +static struct clk_rcg2 blsp1_qup2_i2c_apps_clk_src = { + .cmd_rcgr = 0x3000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_i2c_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp1_qup2_i2c_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 50000000}, + }, +}; + +static struct clk_rcg2 blsp1_qup2_spi_apps_clk_src = { + .cmd_rcgr = 0x3014, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_spi_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp1_qup2_spi_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 25000000, + [VDD_NOMINAL] = 50000000}, + }, +}; + +static struct clk_rcg2 blsp1_qup3_i2c_apps_clk_src = { + .cmd_rcgr = 0x4000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_i2c_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp1_qup3_i2c_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 50000000}, + }, +}; + +static struct clk_rcg2 blsp1_qup3_spi_apps_clk_src = { + .cmd_rcgr = 0x4024, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_spi_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp1_qup3_spi_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 25000000, + [VDD_NOMINAL] = 50000000}, + }, +}; + +static struct clk_rcg2 blsp1_qup4_i2c_apps_clk_src = { + .cmd_rcgr = 0x5000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_i2c_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp1_qup4_i2c_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 50000000}, + }, +}; + +static struct clk_rcg2 blsp1_qup4_spi_apps_clk_src = { + .cmd_rcgr = 0x5024, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_spi_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp1_qup4_spi_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 25000000, + [VDD_NOMINAL] = 50000000}, + }, +}; + +static const struct freq_tbl ftbl_blsp1_uart1_apps_clk_src[] = { + F(3686400, P_GPLL0_OUT_MAIN, 1, 72, 15625), + F(7372800, P_GPLL0_OUT_MAIN, 1, 144, 15625), + F(14745600, P_GPLL0_OUT_MAIN, 1, 288, 15625), + F(16000000, P_GPLL0_OUT_MAIN, 10, 1, 5), + F(19200000, P_BI_TCXO, 1, 0, 0), + F(24000000, P_GPLL0_OUT_MAIN, 1, 3, 100), + F(25000000, P_GPLL0_OUT_MAIN, 16, 1, 2), + F(32000000, P_GPLL0_OUT_MAIN, 1, 1, 25), + F(40000000, P_GPLL0_OUT_MAIN, 1, 1, 20), + F(46400000, P_GPLL0_OUT_MAIN, 1, 29, 500), + F(48000000, P_GPLL0_OUT_MAIN, 1, 3, 50), + F(51200000, P_GPLL0_OUT_MAIN, 1, 8, 125), + F(56000000, P_GPLL0_OUT_MAIN, 1, 7, 100), + F(58982400, P_GPLL0_OUT_MAIN, 1, 1152, 15625), + F(60000000, P_GPLL0_OUT_MAIN, 1, 3, 40), + { } +}; + +static struct clk_rcg2 blsp1_uart1_apps_clk_src = { + .cmd_rcgr = 0x2044, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_uart1_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp1_uart1_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 32000000, + [VDD_NOMINAL] = 64000000}, + }, +}; + +static struct clk_rcg2 blsp1_uart2_apps_clk_src = { + .cmd_rcgr = 0x3034, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_uart1_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp1_uart2_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 32000000, + [VDD_NOMINAL] = 64000000}, + }, +}; + +static struct clk_rcg2 blsp2_qup1_i2c_apps_clk_src = { + .cmd_rcgr = 0xc00c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_i2c_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp2_qup1_i2c_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 50000000}, + }, +}; + +static struct clk_rcg2 blsp2_qup1_spi_apps_clk_src = { + .cmd_rcgr = 0xc024, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_spi_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp2_qup1_spi_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 25000000, + [VDD_NOMINAL] = 50000000}, + }, +}; + +static struct clk_rcg2 blsp2_qup2_i2c_apps_clk_src = { + .cmd_rcgr = 0xd000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_i2c_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp2_qup2_i2c_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 50000000}, + }, +}; + +static struct clk_rcg2 blsp2_qup2_spi_apps_clk_src = { + .cmd_rcgr = 0xd014, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_spi_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp2_qup2_spi_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 25000000, + [VDD_NOMINAL] = 50000000}, + }, +}; + +static struct clk_rcg2 blsp2_qup3_i2c_apps_clk_src = { + .cmd_rcgr = 0xf000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_i2c_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp2_qup3_i2c_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 50000000}, + }, +}; + +static struct clk_rcg2 blsp2_qup3_spi_apps_clk_src = { + .cmd_rcgr = 0xf024, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_spi_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp2_qup3_spi_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 25000000, + [VDD_NOMINAL] = 50000000}, + }, +}; + +static struct clk_rcg2 blsp2_qup4_i2c_apps_clk_src = { + .cmd_rcgr = 0x18000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_i2c_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp2_qup4_i2c_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 50000000}, + }, +}; + +static struct clk_rcg2 blsp2_qup4_spi_apps_clk_src = { + .cmd_rcgr = 0x18024, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_qup1_spi_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp2_qup4_spi_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 25000000, + [VDD_NOMINAL] = 50000000}, + }, +}; + +static struct clk_rcg2 blsp2_uart1_apps_clk_src = { + .cmd_rcgr = 0xc044, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_uart1_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp2_uart1_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 32000000, + [VDD_NOMINAL] = 64000000}, + }, +}; + +static struct clk_rcg2 blsp2_uart2_apps_clk_src = { + .cmd_rcgr = 0xd034, + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_blsp1_uart1_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "blsp2_uart2_apps_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 32000000, + [VDD_NOMINAL] = 64000000}, + }, +}; + +static const struct freq_tbl ftbl_cpp_clk_src[] = { + F(133333333, P_GPLL0_OUT_MAIN, 6, 0, 0), + F(200000000, P_GPLL0_OUT_MAIN, 4, 0, 0), + F(266666667, P_GPLL0_OUT_MAIN, 3, 0, 0), + F(308571428, P_GPLL6_OUT_MAIN, 3.5, 0, 0), + F(320000000, P_GPLL0_OUT_MAIN, 2.5, 0, 0), + F(360000000, P_GPLL6_OUT_MAIN, 3, 0, 0), + { } +}; + +static const struct freq_tbl ftbl_cpp_clk_src_qm215[] = { + F( 133330000, P_GPLL0_OUT_MAIN, 6, 0, 0), + F( 160000000, P_GPLL0_OUT_MAIN, 5, 0, 0), + F( 266670000, P_GPLL0_OUT_MAIN, 3, 0, 0), + F( 308570000, P_GPLL6_OUT_MAIN, 3.5, 0, 0), + F( 320000000, P_GPLL0_OUT_MAIN, 2.5, 0, 0), + F( 360000000, P_GPLL6_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 cpp_clk_src = { + .cmd_rcgr = 0x58018, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_10, + .freq_tbl = ftbl_cpp_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "cpp_clk_src", + .parent_names = gcc_parent_names_10, + .num_parents = 5, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 160000000, + [VDD_LOW_L1] = 266666667, + [VDD_NOMINAL] = 320000000, + [VDD_NOMINAL_L1] = 342857143, + [VDD_HIGH] = 360000000}, + }, +}; + +static const struct freq_tbl ftbl_crypto_clk_src[] = { + F(50000000, P_GPLL0_OUT_MAIN, 16, 0, 0), + F(80000000, P_GPLL0_OUT_MAIN, 10, 0, 0), + F(100000000, P_GPLL0_OUT_MAIN, 8, 0, 0), + F(160000000, P_GPLL0_OUT_MAIN, 5, 0, 0), + { } +}; + +static struct clk_rcg2 crypto_clk_src = { + .cmd_rcgr = 0x16004, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_crypto_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "crypto_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 80000000, + [VDD_NOMINAL] = 160000000}, + }, +}; + +static const struct freq_tbl ftbl_esc0_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + { } +}; + +static struct clk_rcg2 gp1_clk_src = { + .cmd_rcgr = 0x8004, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_3, + .freq_tbl = ftbl_esc0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "gp1_clk_src", + .parent_names = gcc_parent_names_3, + .num_parents = 4, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 100000000, + [VDD_NOMINAL] = 200000000}, + }, +}; + +static struct clk_rcg2 gp2_clk_src = { + .cmd_rcgr = 0x9004, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_3, + .freq_tbl = ftbl_esc0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "gp2_clk_src", + .parent_names = gcc_parent_names_3, + .num_parents = 4, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 100000000, + [VDD_NOMINAL] = 200000000}, + }, +}; + +static struct clk_rcg2 gp3_clk_src = { + .cmd_rcgr = 0xa004, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_3, + .freq_tbl = ftbl_esc0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "gp3_clk_src", + .parent_names = gcc_parent_names_3, + .num_parents = 4, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 100000000, + [VDD_NOMINAL] = 200000000}, + }, +}; + +static const struct freq_tbl ftbl_jpeg0_clk_src[] = { + F(133333333, P_GPLL0_OUT_MAIN, 6, 0, 0), + F(200000000, P_GPLL0_OUT_MAIN, 4, 0, 0), + F(266666667, P_GPLL0_OUT_MAIN, 3, 0, 0), + F(308571428, P_GPLL6_OUT_AUX, 3.5, 0, 0), + F(320000000, P_GPLL0_OUT_MAIN, 2.5, 0, 0), + { } +}; + +static struct clk_rcg2 jpeg0_clk_src = { + .cmd_rcgr = 0x57000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_jpeg0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "jpeg0_clk_src", + .parent_names = gcc_parent_names_1, + .num_parents = 4, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 133333333, + [VDD_LOW_L1] = 200000000, + [VDD_NOMINAL] = 266666667, + [VDD_NOMINAL_L1] = 308571429, + [VDD_HIGH] = 320000000}, + }, +}; + +static const struct freq_tbl ftbl_pdm2_clk_src[] = { + F(64000000, P_GPLL0_OUT_MAIN, 12.5, 0, 0), + { } +}; + +static struct clk_rcg2 pdm2_clk_src = { + .cmd_rcgr = 0x44010, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_pdm2_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "pdm2_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 64000000}, + }, +}; + +static const struct freq_tbl ftbl_sdcc1_apps_clk_src[] = { + F(144000, P_BI_TCXO, 16, 3, 25), + F(400000, P_BI_TCXO, 12, 1, 4), + F(20000000, P_GPLL0_OUT_MAIN, 10, 1, 4), + F(25000000, P_GPLL0_OUT_MAIN, 16, 1, 2), + F(50000000, P_GPLL0_OUT_MAIN, 16, 0, 0), + F(100000000, P_GPLL0_OUT_MAIN, 8, 0, 0), + F(177777778, P_GPLL0_OUT_MAIN, 4.5, 0, 0), + F(200000000, P_GPLL0_OUT_MAIN, 4, 0, 0), + F(384000000, P_GPLL4_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 sdcc1_apps_clk_src = { + .cmd_rcgr = 0x42004, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_18, + .freq_tbl = ftbl_sdcc1_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "sdcc1_apps_clk_src", + .parent_names = gcc_parent_names_18, + .num_parents = 5, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 100000000, + [VDD_NOMINAL] = 400000000}, + }, +}; + +static const struct freq_tbl ftbl_sdcc1_ice_core_clk_src[] = { + F(100000000, P_GPLL0_OUT_MAIN, 8, 0, 0), + F(200000000, P_GPLL0_OUT_MAIN, 4, 0, 0), + { } +}; + +static struct clk_rcg2 sdcc1_ice_core_clk_src = { + .cmd_rcgr = 0x5d000, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_sdcc1_ice_core_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "sdcc1_ice_core_clk_src", + .parent_names = gcc_parent_names_1, + .num_parents = 4, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 100000000, + [VDD_NOMINAL] = 200000000}, + }, +}; + +static const struct freq_tbl ftbl_sdcc2_apps_clk_src[] = { + F(144000, P_BI_TCXO, 16, 3, 25), + F(400000, P_BI_TCXO, 12, 1, 4), + F(20000000, P_GPLL0_OUT_MAIN, 10, 1, 4), + F(25000000, P_GPLL0_OUT_MAIN, 16, 1, 2), + F(50000000, P_GPLL0_OUT_MAIN, 16, 0, 0), + F(100000000, P_GPLL0_OUT_MAIN, 8, 0, 0), + F(177777778, P_GPLL0_OUT_MAIN, 4.5, 0, 0), + F(200000000, P_GPLL0_OUT_MAIN, 4, 0, 0), + { } +}; + +static struct clk_rcg2 sdcc2_apps_clk_src = { + .cmd_rcgr = 0x43004, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_19, + .freq_tbl = ftbl_sdcc2_apps_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "sdcc2_apps_clk_src", + .parent_names = gcc_parent_names_19, + .num_parents = 4, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 100000000, + [VDD_NOMINAL] = 200000000}, + }, +}; + +static const struct freq_tbl ftbl_usb_hs_system_clk_src[] = { + F(57140000, P_GPLL0_OUT_MAIN, 14, 0, 0), + F(100000000, P_GPLL0_OUT_MAIN, 8, 0, 0), + F(133333333, P_GPLL0_OUT_MAIN, 6, 0, 0), + F(177780000, P_GPLL0_OUT_MAIN, 4.5, 0, 0), + { } +}; + +static const struct freq_tbl ftbl_usb_hs_system_clk_src_qm215[] = { + F( 80000000, P_GPLL0_OUT_MAIN, 10, 0, 0), + F( 100000000, P_GPLL0_OUT_MAIN, 8, 0, 0), + F( 133330000, P_GPLL0_OUT_MAIN, 6, 0, 0), + F( 177780000, P_GPLL0_OUT_MAIN, 4.5, 0, 0), + { } +}; + +static struct clk_rcg2 usb_hs_system_clk_src = { + .cmd_rcgr = 0x41010, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_1, + .freq_tbl = ftbl_usb_hs_system_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "usb_hs_system_clk_src", + .parent_names = gcc_parent_names_1, + .num_parents = 4, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 57142857, + [VDD_NOMINAL] = 133333333, + [VDD_HIGH] = 177777778}, + }, +}; + +static const struct freq_tbl ftbl_vfe0_clk_src[] = { + F(50000000, P_GPLL0_OUT_MAIN, 16, 0, 0), + F(80000000, P_GPLL0_OUT_MAIN, 10, 0, 0), + F(100000000, P_GPLL0_OUT_MAIN, 8, 0, 0), + F(133333333, P_GPLL0_OUT_MAIN, 6, 0, 0), + F(160000000, P_GPLL0_OUT_MAIN, 5, 0, 0), + F(177777778, P_GPLL0_OUT_MAIN, 4.5, 0, 0), + F(200000000, P_GPLL0_OUT_MAIN, 4, 0, 0), + F(266666667, P_GPLL0_OUT_MAIN, 3, 0, 0), + F(308571428, P_GPLL6_OUT_MAIN, 3.5, 0, 0), + F(320000000, P_GPLL0_OUT_MAIN, 2.5, 0, 0), + F(360000000, P_GPLL6_OUT_MAIN, 3, 0, 0), + F(400000000, P_GPLL0_OUT_MAIN, 2, 0, 0), + F(432000000, P_GPLL6_OUT_MAIN, 2.5, 0, 0), + { } +}; + +static const struct freq_tbl ftbl_vfe0_clk_src_qm215[] = { + F( 50000000, P_GPLL0_OUT_MAIN, 16, 0, 0), + F( 80000000, P_GPLL0_OUT_MAIN, 10, 0, 0), + F( 100000000, P_GPLL0_OUT_MAIN, 8, 0, 0), + F( 133333333, P_GPLL0_OUT_MAIN, 6, 0, 0), + F( 160000000, P_GPLL0_OUT_MAIN, 5, 0, 0), + F( 177780000, P_GPLL0_OUT_MAIN, 4.5, 0, 0), + F( 200000000, P_GPLL0_OUT_MAIN, 4, 0, 0), + F( 266670000, P_GPLL0_OUT_MAIN, 3, 0, 0), + F( 308570000, P_GPLL6_OUT_MAIN, 3.5, 0, 0), + F( 320000000, P_GPLL0_OUT_MAIN, 2.5, 0, 0), + F( 329140000, P_GPLL4_OUT_MAIN, 3.5, 0, 0), + F( 360000000, P_GPLL6_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 vfe0_clk_src = { + .cmd_rcgr = 0x58000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_7, + .freq_tbl = ftbl_vfe0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "vfe0_clk_src", + .parent_names = gcc_parent_names_7, + .num_parents = 5, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 160000000, + [VDD_LOW_L1] = 308571429, + [VDD_NOMINAL] = 400000000, + [VDD_NOMINAL_L1] = 432000000}, + }, +}; + +static struct clk_rcg2 vfe1_clk_src = { + .cmd_rcgr = 0x58054, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_7, + .freq_tbl = ftbl_vfe0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "vfe1_clk_src", + .parent_names = gcc_parent_names_7, + .num_parents = 5, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 160000000, + [VDD_LOW_L1] = 308571429, + [VDD_NOMINAL] = 400000000, + [VDD_NOMINAL_L1] = 432000000}, + }, +}; + +static struct clk_rcg2 byte0_clk_src = { + .cmd_rcgr = 0x4d044, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_8, + .clkr.hw.init = &(struct clk_init_data){ + .name = "byte0_clk_src", + .parent_names = gcc_parent_names_8, + .num_parents = 5, + .flags = CLK_SET_RATE_PARENT | CLK_GET_RATE_NOCACHE, + .ops = &clk_byte2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 125000000, + [VDD_NOMINAL] = 187500000}, + }, +}; + +static struct clk_rcg2 byte1_clk_src = { + .cmd_rcgr = 0x4d0b0, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_9, + .clkr.hw.init = &(struct clk_init_data){ + .name = "byte1_clk_src", + .parent_names = gcc_parent_names_9, + .num_parents = 5, + .flags = CLK_SET_RATE_PARENT | CLK_GET_RATE_NOCACHE, + .ops = &clk_byte2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 125000000, + [VDD_NOMINAL] = 187500000}, + }, +}; + +static const struct freq_tbl ftbl_camss_gp0_clk_src[] = { + F(100000000, P_GPLL0_OUT_MAIN, 8, 0, 0), + F(160000000, P_GPLL0_OUT_MAIN, 5, 0, 0), + F(200000000, P_GPLL0_OUT_MAIN, 4, 0, 0), + { } +}; + +static struct clk_rcg2 camss_gp0_clk_src = { + .cmd_rcgr = 0x54000, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_20, + .freq_tbl = ftbl_camss_gp0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "camss_gp0_clk_src", + .parent_names = gcc_parent_names_20, + .num_parents = 5, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 100000000, + [VDD_LOW_L1] = 160000000, + [VDD_NOMINAL] = 200000000}, + }, +}; + +static struct clk_rcg2 camss_gp1_clk_src = { + .cmd_rcgr = 0x55000, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_20, + .freq_tbl = ftbl_camss_gp0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "camss_gp1_clk_src", + .parent_names = gcc_parent_names_20, + .num_parents = 5, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 100000000, + [VDD_LOW_L1] = 160000000, + [VDD_NOMINAL] = 200000000}, + }, +}; + +static const struct freq_tbl ftbl_camss_top_ahb_clk_src[] = { + F(40000000, P_GPLL0_OUT_MAIN, 10, 1, 2), + F(61538461, P_GPLL0_OUT_MAIN, 13, 0, 0), + F(80000000, P_GPLL0_OUT_MAIN, 10, 0, 0), + { } +}; + +static struct clk_rcg2 camss_top_ahb_clk_src = { + .cmd_rcgr = 0x5a000, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_camss_top_ahb_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "camss_top_ahb_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 40000000, + [VDD_LOW_L1] = 61538462, + [VDD_NOMINAL] = 80000000}, + }, +}; + +static const struct freq_tbl ftbl_cci_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + F(37500000, P_GPLL0_OUT_AUX, 1, 3, 64), + { } +}; + +static struct clk_rcg2 cci_clk_src = { + .cmd_rcgr = 0x51000, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_6, + .freq_tbl = ftbl_cci_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "cci_clk_src", + .parent_names = gcc_parent_names_6, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 37500000}, + }, +}; + +static const struct freq_tbl ftbl_csi0_clk_src[] = { + F(100000000, P_GPLL0_OUT_MAIN, 8, 0, 0), + F(160000000, P_GPLL0_OUT_MAIN, 5, 0, 0), + F(200000000, P_GPLL0_OUT_MAIN, 4, 0, 0), + F(266670000, P_GPLL0_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 csi0_clk_src = { + .cmd_rcgr = 0x4e020, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_csi0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "csi0_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 100000000, + [VDD_LOW_L1] = 200000000, + [VDD_NOMINAL] = 266666667}, + }, +}; + +static const struct freq_tbl ftbl_csi0phytimer_clk_src[] = { + F(100000000, P_GPLL0_OUT_MAIN, 8, 0, 0), + F(160000000, P_GPLL0_OUT_MAIN, 5, 0, 0), + F(200000000, P_GPLL0_OUT_MAIN, 4, 0, 0), + { } +}; + +static const struct freq_tbl ftbl_csi0phytimer_clk_src_qm215[] = { + F( 100000000, P_GPLL0_OUT_MAIN, 8, 0, 0), + F( 160000000, P_GPLL0_OUT_MAIN, 5, 0, 0), + F( 200000000, P_GPLL0_OUT_MAIN, 4, 0, 0), + F( 266670000, P_GPLL0_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 csi0phytimer_clk_src = { + .cmd_rcgr = 0x4e000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_csi0phytimer_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "csi0phytimer_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 100000000, + [VDD_LOW_L1] = 200000000}, + }, +}; + +static struct clk_rcg2 csi1_clk_src = { + .cmd_rcgr = 0x4f020, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_csi0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "csi1_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 100000000, + [VDD_LOW_L1] = 200000000, + [VDD_NOMINAL] = 266666667}, + }, +}; + +static struct clk_rcg2 csi1phytimer_clk_src = { + .cmd_rcgr = 0x4f000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_csi0phytimer_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "csi1phytimer_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 100000000, + [VDD_LOW_L1] = 200000000}, + }, +}; + +static struct clk_rcg2 csi2_clk_src = { + .cmd_rcgr = 0x3c020, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_0, + .freq_tbl = ftbl_csi0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "csi2_clk_src", + .parent_names = gcc_parent_names_0, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 100000000, + [VDD_LOW_L1] = 200000000, + [VDD_NOMINAL] = 266666667}, + }, +}; + +static struct clk_rcg2 esc0_clk_src = { + .cmd_rcgr = 0x4d05c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_11, + .freq_tbl = ftbl_esc0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "esc0_clk_src", + .parent_names = gcc_parent_names_11, + .num_parents = 5, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 19200000}, + }, +}; + +static struct clk_rcg2 esc1_clk_src = { + .cmd_rcgr = 0x4d0a8, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_12, + .freq_tbl = ftbl_esc0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "esc1_clk_src", + .parent_names = gcc_parent_names_12, + .num_parents = 5, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 19200000}, + }, +}; + +static struct clk_rcg2 gcc_xo_clk_src = { + .cmd_rcgr = 0x30018, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_2, + .freq_tbl = ftbl_esc0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "gcc_xo_clk_src", + .parent_names = gcc_parent_names_2, + .num_parents = 1, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 19200000}, + }, +}; + +static const struct freq_tbl ftbl_gfx3d_clk_src[] = { + F_SLEW(19200000, P_BI_TCXO, 1, 0, 0, FIXED_FREQ_SRC), + F_SLEW(50000000, P_GPLL0_OUT_MAIN, 16, 0, 0, FIXED_FREQ_SRC), + F_SLEW(80000000, P_GPLL0_OUT_MAIN, 10, 0, 0, FIXED_FREQ_SRC), + F_SLEW(100000000, P_GPLL0_OUT_MAIN, 8, 0, 0, FIXED_FREQ_SRC), + F_SLEW(160000000, P_GPLL0_OUT_MAIN, 5, 0, 0, FIXED_FREQ_SRC), + F_SLEW(200000000, P_GPLL0_OUT_MAIN, 4, 0, 0, FIXED_FREQ_SRC), + F_SLEW(216000000, P_GPLL6_OUT_AUX, 5, 0, 0, FIXED_FREQ_SRC), + F_SLEW(228571429, P_GPLL0_OUT_MAIN, 3.5, 0, 0, FIXED_FREQ_SRC), + F_SLEW(240000000, P_GPLL6_OUT_AUX, 4.5, 0, 0, FIXED_FREQ_SRC), + F_SLEW(266666667, P_GPLL0_OUT_MAIN, 3, 0, 0, FIXED_FREQ_SRC), + F_SLEW(320000000, P_GPLL0_OUT_MAIN, 2.5, 0, 0, FIXED_FREQ_SRC), + F_SLEW(355200000, P_GPLL3_OUT_MAIN, 1, 0, 0, 710400000), + F_SLEW(375000000, P_GPLL3_OUT_MAIN, 1, 0, 0, 750000000), + F_SLEW(400000000, P_GPLL0_OUT_MAIN, 2, 0, 0, FIXED_FREQ_SRC), + F_SLEW(450000000, P_GPLL3_OUT_MAIN, 1, 0, 0, 900000000), + F_SLEW(510000000, P_GPLL3_OUT_MAIN, 1, 0, 0, 1020000000), + F_SLEW(560000000, P_GPLL3_OUT_MAIN, 1, 0, 0, 1120000000), + F_SLEW(650000000, P_GPLL3_OUT_MAIN, 1, 0, 0, 1300000000), + { } +}; + +static struct freq_tbl ftbl_oxili_gfx3d_clk_src_qm215[] = { + F_SLEW( 19200000, P_BI_TCXO, 1, 0, 0, FIXED_FREQ_SRC), + F_SLEW( 50000000, P_GPLL0_OUT_MAIN, 16, 0, 0, FIXED_FREQ_SRC), + F_SLEW( 80000000, P_GPLL0_OUT_MAIN, 10, 0, 0, FIXED_FREQ_SRC), + F_SLEW( 100000000, P_GPLL0_OUT_MAIN, 8, 0, 0, FIXED_FREQ_SRC), + F_SLEW( 160000000, P_GPLL0_OUT_MAIN, 5, 0, 0, FIXED_FREQ_SRC), + F_SLEW( 200000000, P_GPLL0_OUT_MAIN, 4, 0, 0, FIXED_FREQ_SRC), + F_SLEW( 228570000, P_GPLL0_OUT_MAIN, 3.5, 0, 0, FIXED_FREQ_SRC), + F_SLEW( 240000000, P_GPLL6_OUT_AUX, 4.5, 0, 0, FIXED_FREQ_SRC), + F_SLEW( 266670000, P_GPLL0_OUT_MAIN, 3, 0, 0, FIXED_FREQ_SRC), + F_SLEW( 270000000, P_GPLL6_OUT_AUX, 4, 0, 0, FIXED_FREQ_SRC), + F_SLEW( 320000000, P_GPLL0_OUT_MAIN, 2.5, 0, 0, FIXED_FREQ_SRC), + F_SLEW( 400000000, P_GPLL0_OUT_MAIN, 2, 0, 0, FIXED_FREQ_SRC), + { } +}; + +static struct clk_rcg2 gfx3d_clk_src = { + .cmd_rcgr = 0x59000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_14, + .freq_tbl = ftbl_gfx3d_clk_src, + .flags = FORCE_ENABLE_RCG, + .clkr.hw.init = &(struct clk_init_data){ + .name = "gfx3d_clk_src", + .parent_names = gcc_parent_names_14, + .num_parents = 6, + .ops = &clk_rcg2_ops, + }, +}; + +static const struct freq_tbl ftbl_mclk0_clk_src[] = { + F(24000000, P_GPLL6_OUT_MAIN, 1, 1, 45), + F(66666667, P_GPLL0_OUT_MAIN, 12, 0, 0), + { } +}; + +static struct clk_rcg2 mclk0_clk_src = { + .cmd_rcgr = 0x52000, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_4, + .freq_tbl = ftbl_mclk0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "mclk0_clk_src", + .parent_names = gcc_parent_names_4, + .num_parents = 5, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 66666667}, + }, +}; + +static struct clk_rcg2 mclk1_clk_src = { + .cmd_rcgr = 0x53000, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_4, + .freq_tbl = ftbl_mclk0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "mclk1_clk_src", + .parent_names = gcc_parent_names_4, + .num_parents = 5, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 66666667}, + }, +}; + +static struct clk_rcg2 mclk2_clk_src = { + .cmd_rcgr = 0x5c000, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_4, + .freq_tbl = ftbl_mclk0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "mclk2_clk_src", + .parent_names = gcc_parent_names_4, + .num_parents = 5, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 66666667}, + }, +}; + +static const struct freq_tbl ftbl_mdp_clk_src[] = { + F(50000000, P_GPLL0_OUT_MAIN, 16, 0, 0), + F(80000000, P_GPLL0_OUT_MAIN, 10, 0, 0), + F(100000000, P_GPLL0_OUT_MAIN, 8, 0, 0), + F(145454545, P_GPLL0_OUT_MAIN, 5.5, 0, 0), + F(160000000, P_GPLL0_OUT_MAIN, 5, 0, 0), + F(177777778, P_GPLL0_OUT_MAIN, 4.5, 0, 0), + F(200000000, P_GPLL0_OUT_MAIN, 4, 0, 0), + F(266666667, P_GPLL0_OUT_MAIN, 3, 0, 0), + F(320000000, P_GPLL0_OUT_MAIN, 2.5, 0, 0), + { } +}; + +static struct clk_rcg2 mdp_clk_src = { + .cmd_rcgr = 0x4d014, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_15, + .freq_tbl = ftbl_mdp_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "mdp_clk_src", + .parent_names = gcc_parent_names_15, + .num_parents = 6, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 160000000, + [VDD_NOMINAL] = 266666667, + [VDD_HIGH] = 320000000}, + }, +}; + +static struct clk_rcg2 pclk0_clk_src = { + .cmd_rcgr = 0x4d000, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_16, + .clkr.hw.init = &(struct clk_init_data){ + .name = "pclk0_clk_src", + .parent_names = gcc_parent_names_16, + .num_parents = 5, + .flags = CLK_SET_RATE_PARENT | CLK_GET_RATE_NOCACHE, + .ops = &clk_pixel_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 166666667, + [VDD_NOMINAL] = 250000000}, + }, +}; + +static struct clk_rcg2 pclk1_clk_src = { + .cmd_rcgr = 0x4d0b8, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_17, + .clkr.hw.init = &(struct clk_init_data){ + .name = "pclk1_clk_src", + .parent_names = gcc_parent_names_17, + .num_parents = 5, + .flags = CLK_SET_RATE_PARENT | CLK_GET_RATE_NOCACHE, + .ops = &clk_pixel_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 166666667, + [VDD_NOMINAL] = 250000000}, + }, +}; + +static const struct freq_tbl ftbl_vcodec0_clk_src[] = { + F(166150000, P_GPLL6_OUT_MAIN, 6.5, 0, 0), + F(240000000, P_GPLL6_OUT_MAIN, 4.5, 0, 0), + F(308571428, P_GPLL6_OUT_MAIN, 3.5, 0, 0), + F(320000000, P_GPLL0_OUT_MAIN, 2.5, 0, 0), + F(360000000, P_GPLL6_OUT_MAIN, 3, 0, 0), + { } +}; + +static const struct freq_tbl ftbl_vcodec0_clk_src_qm215[] = { + F( 160000000, P_GPLL0_OUT_MAIN, 5, 0, 0), + F( 200000000, P_GPLL0_OUT_MAIN, 4, 0, 0), + F( 270000000, P_GPLL6_OUT_MAIN, 4, 0, 0), + F( 308570000, P_GPLL6_OUT_MAIN, 3.5, 0, 0), + F( 329140000, P_GPLL4_OUT_MAIN, 3.5, 0, 0), + F( 360000000, P_GPLL6_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 vcodec0_clk_src = { + .cmd_rcgr = 0x4c000, + .mnd_width = 8, + .hid_width = 5, + .parent_map = gcc_parent_map_5, + .freq_tbl = ftbl_vcodec0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "vcodec0_clk_src", + .parent_names = gcc_parent_names_5, + .num_parents = 4, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 166153846, + [VDD_LOW_L1] = 240000000, + [VDD_NOMINAL] = 308571429, + [VDD_NOMINAL_L1] = 320000000, + [VDD_HIGH] = 360000000}, + }, +}; + +static struct clk_rcg2 vsync_clk_src = { + .cmd_rcgr = 0x4d02c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gcc_parent_map_6, + .freq_tbl = ftbl_esc0_clk_src, + .enable_safe_config = true, + .clkr.hw.init = &(struct clk_init_data){ + .name = "vsync_clk_src", + .parent_names = gcc_parent_names_6, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 19200000}, + }, +}; + +static struct clk_branch gcc_bimc_gfx_clk = { + .halt_reg = 0x59034, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x59034, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_bimc_gfx_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gfx_tbu_clk = { + .halt_reg = 0x12010, + .halt_check = BRANCH_VOTED, + .clkr = { + .enable_reg = 0x3600C, + .enable_mask = BIT(3), + .hw.init = &(struct clk_init_data){ + .name = "gcc_gfx_tbu_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gfx_tcu_clk = { + .halt_reg = 0x12020, + .halt_check = BRANCH_VOTED, + .clkr = { + .enable_reg = 0x3600C, + .enable_mask = BIT(2), + .hw.init = &(struct clk_init_data){ + .name = "gcc_gfx_tcu_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gtcu_ahb_clk = { + .halt_reg = 0x12044, + .halt_check = BRANCH_VOTED, + .clkr = { + .enable_reg = 0x3600C, + .enable_mask = BIT(13), + .hw.init = &(struct clk_init_data){ + .name = "gcc_gtcu_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_oxili_gmem_clk = { + .halt_reg = 0x59024, + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x59024, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_oxili_gmem_clk", + .parent_names = (const char *[]){ + "gfx3d_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_bimc_gpu_clk = { + .halt_reg = 0x59030, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x59030, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_bimc_gpu_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_dcc_clk = { + .halt_reg = 0x77004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x77004, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_dcc_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_mss_cfg_ahb_clk = { + .halt_reg = 0x49000, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x49000, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mss_cfg_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_mss_q6_bimc_axi_clk = { + .halt_reg = 0x49004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x49004, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mss_q6_bimc_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp1_ahb_clk = { + .halt_reg = 0x1008, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x45004, + .enable_mask = BIT(10), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp1_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp1_qup1_i2c_apps_clk = { + .halt_reg = 0x2008, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x2008, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp1_qup1_i2c_apps_clk", + .parent_names = (const char *[]){ + "blsp1_qup1_i2c_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp1_qup1_spi_apps_clk = { + .halt_reg = 0x2004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x2004, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp1_qup1_spi_apps_clk", + .parent_names = (const char *[]){ + "blsp1_qup1_spi_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp1_qup2_i2c_apps_clk = { + .halt_reg = 0x3010, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x3010, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp1_qup2_i2c_apps_clk", + .parent_names = (const char *[]){ + "blsp1_qup2_i2c_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp1_qup2_spi_apps_clk = { + .halt_reg = 0x300c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x300c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp1_qup2_spi_apps_clk", + .parent_names = (const char *[]){ + "blsp1_qup2_spi_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp1_qup3_i2c_apps_clk = { + .halt_reg = 0x4020, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4020, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp1_qup3_i2c_apps_clk", + .parent_names = (const char *[]){ + "blsp1_qup3_i2c_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp1_qup3_spi_apps_clk = { + .halt_reg = 0x401c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x401c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp1_qup3_spi_apps_clk", + .parent_names = (const char *[]){ + "blsp1_qup3_spi_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp1_qup4_i2c_apps_clk = { + .halt_reg = 0x5020, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5020, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp1_qup4_i2c_apps_clk", + .parent_names = (const char *[]){ + "blsp1_qup4_i2c_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp1_qup4_spi_apps_clk = { + .halt_reg = 0x501c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x501c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp1_qup4_spi_apps_clk", + .parent_names = (const char *[]){ + "blsp1_qup4_spi_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp1_uart1_apps_clk = { + .halt_reg = 0x203c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x203c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp1_uart1_apps_clk", + .parent_names = (const char *[]){ + "blsp1_uart1_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp1_uart2_apps_clk = { + .halt_reg = 0x302c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x302c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp1_uart2_apps_clk", + .parent_names = (const char *[]){ + "blsp1_uart2_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp2_qup1_i2c_apps_clk = { + .halt_reg = 0xc008, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xc008, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp2_qup1_i2c_apps_clk", + .parent_names = (const char *[]){ + "blsp2_qup1_i2c_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp2_qup1_spi_apps_clk = { + .halt_reg = 0xc004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xc004, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp2_qup1_spi_apps_clk", + .parent_names = (const char *[]){ + "blsp2_qup1_spi_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp2_qup2_i2c_apps_clk = { + .halt_reg = 0xd010, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xd010, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp2_qup2_i2c_apps_clk", + .parent_names = (const char *[]){ + "blsp2_qup2_i2c_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp2_qup2_spi_apps_clk = { + .halt_reg = 0xd00c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xd00c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp2_qup2_spi_apps_clk", + .parent_names = (const char *[]){ + "blsp2_qup2_spi_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp2_ahb_clk = { + .halt_reg = 0xb008, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x45004, + .enable_mask = BIT(20), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp2_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp2_qup3_i2c_apps_clk = { + .halt_reg = 0xf020, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xf020, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp2_qup3_i2c_apps_clk", + .parent_names = (const char *[]){ + "blsp2_qup3_i2c_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp2_qup3_spi_apps_clk = { + .halt_reg = 0xf01c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xf01c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp2_qup3_spi_apps_clk", + .parent_names = (const char *[]){ + "blsp2_qup3_spi_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp2_qup4_i2c_apps_clk = { + .halt_reg = 0x18020, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x18020, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp2_qup4_i2c_apps_clk", + .parent_names = (const char *[]){ + "blsp2_qup4_i2c_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp2_qup4_spi_apps_clk = { + .halt_reg = 0x1801c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1801c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp2_qup4_spi_apps_clk", + .parent_names = (const char *[]){ + "blsp2_qup4_spi_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp2_uart1_apps_clk = { + .halt_reg = 0xc03c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xc03c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp2_uart1_apps_clk", + .parent_names = (const char *[]){ + "blsp2_uart1_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_blsp2_uart2_apps_clk = { + .halt_reg = 0xd02c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xd02c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp2_uart2_apps_clk", + .parent_names = (const char *[]){ + "blsp2_uart2_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_boot_rom_ahb_clk = { + .halt_reg = 0x1300c, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x45004, + .enable_mask = BIT(7), + .hw.init = &(struct clk_init_data){ + .name = "gcc_boot_rom_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_crypto_ahb_clk = { + .halt_reg = 0x16024, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x45004, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_crypto_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_crypto_axi_clk = { + .halt_reg = 0x16020, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x45004, + .enable_mask = BIT(1), + .hw.init = &(struct clk_init_data){ + .name = "gcc_crypto_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_crypto_clk = { + .halt_reg = 0x1601c, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x45004, + .enable_mask = BIT(2), + .hw.init = &(struct clk_init_data){ + .name = "gcc_crypto_clk", + .parent_names = (const char *[]){ + "crypto_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gp1_clk = { + .halt_reg = 0x8000, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8000, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_gp1_clk", + .parent_names = (const char *[]){ + "gp1_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gp2_clk = { + .halt_reg = 0x9000, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9000, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_gp2_clk", + .parent_names = (const char *[]){ + "gp2_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gp3_clk = { + .halt_reg = 0xa000, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xa000, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_gp3_clk", + .parent_names = (const char *[]){ + "gp3_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pdm2_clk = { + .halt_reg = 0x4400c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4400c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_pdm2_clk", + .parent_names = (const char *[]){ + "pdm2_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_pdm_ahb_clk = { + .halt_reg = 0x44004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x44004, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_pdm_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_prng_ahb_clk = { + .halt_reg = 0x13004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x45004, + .enable_mask = BIT(8), + .hw.init = &(struct clk_init_data){ + .name = "gcc_prng_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_sdcc1_ahb_clk = { + .halt_reg = 0x4201c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4201c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_sdcc1_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_sdcc1_apps_clk = { + .halt_reg = 0x42018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x42018, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_sdcc1_apps_clk", + .parent_names = (const char *[]){ + "sdcc1_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_sdcc1_ice_core_clk = { + .halt_reg = 0x5d014, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5d014, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_sdcc1_ice_core_clk", + .parent_names = (const char *[]){ + "sdcc1_ice_core_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_sdcc2_ahb_clk = { + .halt_reg = 0x4301c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4301c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_sdcc2_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_sdcc2_apps_clk = { + .halt_reg = 0x43018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x43018, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_sdcc2_apps_clk", + .parent_names = (const char *[]){ + "sdcc2_apps_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_usb2a_phy_sleep_clk = { + .halt_reg = 0x4102c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4102c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_usb2a_phy_sleep_clk", + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_usb_hs_ahb_clk = { + .halt_reg = 0x41008, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x41008, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_usb_hs_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_usb_hs_phy_cfg_ahb_clk = { + .halt_reg = 0x41030, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x41030, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_usb_hs_phy_cfg_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_usb_hs_system_clk = { + .halt_reg = 0x41004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x41004, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_usb_hs_system_clk", + .parent_names = (const char *[]){ + "usb_hs_system_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_ahb_clk = { + .halt_reg = 0x56004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x56004, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_cci_ahb_clk = { + .halt_reg = 0x5101c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5101c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_cci_ahb_clk", + .parent_names = (const char *[]){ + "camss_top_ahb_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_cci_clk = { + .halt_reg = 0x51018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x51018, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_cci_clk", + .parent_names = (const char *[]){ + "cci_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_cpp_ahb_clk = { + .halt_reg = 0x58040, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x58040, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_cpp_ahb_clk", + .parent_names = (const char *[]){ + "camss_top_ahb_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_cpp_axi_clk = { + .halt_reg = 0x58064, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x58064, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_cpp_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_cpp_clk = { + .halt_reg = 0x5803c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5803c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_cpp_clk", + .parent_names = (const char *[]){ + "cpp_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi0_ahb_clk = { + .halt_reg = 0x4e040, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4e040, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi0_ahb_clk", + .parent_names = (const char *[]){ + "camss_top_ahb_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi0_clk = { + .halt_reg = 0x4e03c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4e03c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi0_clk", + .parent_names = (const char *[]){ + "csi0_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi0phy_clk = { + .halt_reg = 0x4e048, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4e048, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi0phy_clk", + .parent_names = (const char *[]){ + "csi0_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi0phytimer_clk = { + .halt_reg = 0x4e01c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4e01c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi0phytimer_clk", + .parent_names = (const char *[]){ + "csi0phytimer_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi1phytimer_clk = { + .halt_reg = 0x4f01c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4f01c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi1phytimer_clk", + .parent_names = (const char *[]){ + "csi1phytimer_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi0pix_clk = { + .halt_reg = 0x4e058, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4e058, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi0pix_clk", + .parent_names = (const char *[]){ + "csi0_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi0rdi_clk = { + .halt_reg = 0x4e050, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4e050, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi0rdi_clk", + .parent_names = (const char *[]){ + "csi0_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi1_ahb_clk = { + .halt_reg = 0x4f040, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4f040, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi1_ahb_clk", + .parent_names = (const char *[]){ + "camss_top_ahb_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi1_clk = { + .halt_reg = 0x4f03c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4f03c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi1_clk", + .parent_names = (const char *[]){ + "csi1_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi1phy_clk = { + .halt_reg = 0x4f048, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4f048, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi1phy_clk", + .parent_names = (const char *[]){ + "csi1_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi1pix_clk = { + .halt_reg = 0x4f058, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4f058, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi1pix_clk", + .parent_names = (const char *[]){ + "csi1_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi1rdi_clk = { + .halt_reg = 0x4f050, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4f050, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi1rdi_clk", + .parent_names = (const char *[]){ + "csi1_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi2_ahb_clk = { + .halt_reg = 0x3c040, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x3c040, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi2_ahb_clk", + .parent_names = (const char *[]){ + "camss_top_ahb_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi2_clk = { + .halt_reg = 0x3c03c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x3c03c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi2_clk", + .parent_names = (const char *[]){ + "csi2_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi2phy_clk = { + .halt_reg = 0x3c048, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x3c048, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi2phy_clk", + .parent_names = (const char *[]){ + "csi2_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi2pix_clk = { + .halt_reg = 0x3c058, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x3c058, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi2pix_clk", + .parent_names = (const char *[]){ + "csi2_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi2rdi_clk = { + .halt_reg = 0x3c050, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x3c050, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi2rdi_clk", + .parent_names = (const char *[]){ + "csi2_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi_vfe0_clk = { + .halt_reg = 0x58050, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x58050, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi_vfe0_clk", + .parent_names = (const char *[]){ + "vfe0_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_csi_vfe1_clk = { + .halt_reg = 0x58074, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x58074, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_csi_vfe1_clk", + .parent_names = (const char *[]){ + "vfe1_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_gp0_clk = { + .halt_reg = 0x54018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x54018, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_gp0_clk", + .parent_names = (const char *[]){ + "camss_gp0_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_gp1_clk = { + .halt_reg = 0x55018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x55018, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_gp1_clk", + .parent_names = (const char *[]){ + "camss_gp1_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_ispif_ahb_clk = { + .halt_reg = 0x50004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x50004, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_ispif_ahb_clk", + .parent_names = (const char *[]){ + "camss_top_ahb_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_jpeg0_clk = { + .halt_reg = 0x57020, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x57020, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_jpeg0_clk", + .parent_names = (const char *[]){ + "jpeg0_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_jpeg_ahb_clk = { + .halt_reg = 0x57024, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x57024, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_jpeg_ahb_clk", + .parent_names = (const char *[]){ + "camss_top_ahb_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_jpeg_axi_clk = { + .halt_reg = 0x57028, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x57028, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_jpeg_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_mclk0_clk = { + .halt_reg = 0x52018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x52018, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_mclk0_clk", + .parent_names = (const char *[]){ + "mclk0_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_mclk1_clk = { + .halt_reg = 0x53018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x53018, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_mclk1_clk", + .parent_names = (const char *[]){ + "mclk1_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_mclk2_clk = { + .halt_reg = 0x5c018, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5c018, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_mclk2_clk", + .parent_names = (const char *[]){ + "mclk2_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_micro_ahb_clk = { + .halt_reg = 0x5600c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5600c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_micro_ahb_clk", + .parent_names = (const char *[]){ + "camss_top_ahb_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_top_ahb_clk = { + .halt_reg = 0x5a014, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5a014, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_top_ahb_clk", + .parent_names = (const char *[]){ + "camss_top_ahb_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_vfe0_clk = { + .halt_reg = 0x58038, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x58038, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_vfe0_clk", + .parent_names = (const char *[]){ + "vfe0_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_vfe1_ahb_clk = { + .halt_reg = 0x58060, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x58060, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_vfe1_ahb_clk", + .parent_names = (const char *[]){ + "camss_top_ahb_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_vfe1_axi_clk = { + .halt_reg = 0x58068, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x58068, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_vfe1_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_vfe1_clk = { + .halt_reg = 0x5805c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5805c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_vfe1_clk", + .parent_names = (const char *[]){ + "vfe1_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_vfe_ahb_clk = { + .halt_reg = 0x58044, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x58044, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_vfe_ahb_clk", + .parent_names = (const char *[]){ + "camss_top_ahb_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_camss_vfe_axi_clk = { + .halt_reg = 0x58048, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x58048, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_camss_vfe_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_mdss_ahb_clk = { + .halt_reg = 0x4d07c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4d07c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mdss_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_mdss_axi_clk = { + .halt_reg = 0x4d080, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4d080, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mdss_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_mdss_byte0_clk = { + .halt_reg = 0x4d094, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4d094, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mdss_byte0_clk", + .parent_names = (const char *[]){ + "byte0_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_mdss_byte1_clk = { + .halt_reg = 0x4d0a0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4d0a0, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mdss_byte1_clk", + .parent_names = (const char *[]){ + "byte1_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_mdss_esc0_clk = { + .halt_reg = 0x4d098, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4d098, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mdss_esc0_clk", + .parent_names = (const char *[]){ + "esc0_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_mdss_esc1_clk = { + .halt_reg = 0x4d09c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4d09c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mdss_esc1_clk", + .parent_names = (const char *[]){ + "esc1_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_mdss_mdp_clk = { + .halt_reg = 0x4d088, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4d088, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mdss_mdp_clk", + .parent_names = (const char *[]){ + "mdp_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static DEFINE_CLK_VOTER(mdss_mdp_vote_clk, gcc_mdss_mdp_clk, 0); +static DEFINE_CLK_VOTER(mdss_rotator_vote_clk, gcc_mdss_mdp_clk, 0); + +static struct clk_branch gcc_mdss_pclk0_clk = { + .halt_reg = 0x4d084, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4d084, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mdss_pclk0_clk", + .parent_names = (const char *[]){ + "pclk0_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_mdss_pclk1_clk = { + .halt_reg = 0x4d0a4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4d0a4, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mdss_pclk1_clk", + .parent_names = (const char *[]){ + "pclk1_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_mdss_vsync_clk = { + .halt_reg = 0x4d090, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4d090, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mdss_vsync_clk", + .parent_names = (const char *[]){ + "vsync_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_oxili_ahb_clk = { + .halt_reg = 0x59028, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x59028, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_oxili_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_oxili_aon_clk = { + .halt_reg = 0x5904c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x5904c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_oxili_aon_clk", + .parent_names = (const char *[]){ + "gfx3d_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 320000000, + [VDD_LOW_L1] = 400000000, + [VDD_NOMINAL] = 510000000, + [VDD_NOMINAL_L1] = 560000000, + [VDD_HIGH] = 650000000}, + }, + }, +}; + +static struct clk_branch gcc_oxili_gfx3d_clk = { + .halt_reg = 0x59020, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x59020, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_oxili_gfx3d_clk", + .parent_names = (const char *[]){ + "gfx3d_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 320000000, + [VDD_LOW_L1] = 400000000, + [VDD_NOMINAL] = 510000000, + [VDD_NOMINAL_L1] = 560000000, + [VDD_HIGH] = 650000000}, + }, + }, +}; + +static struct clk_branch gcc_oxili_timer_clk = { + .halt_reg = 0x59040, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x59040, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_oxili_timer_clk", + .parent_names = (const char *[]){ + "gcc_xo_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_venus0_ahb_clk = { + .halt_reg = 0x4c020, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4c020, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_venus0_ahb_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_venus0_axi_clk = { + .halt_reg = 0x4c024, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4c024, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_venus0_axi_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_venus0_core0_vcodec0_clk = { + .halt_reg = 0x4c02c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4c02c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_venus0_core0_vcodec0_clk", + .parent_names = (const char *[]){ + "vcodec0_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_venus0_vcodec0_clk = { + .halt_reg = 0x4c01c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x4c01c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_venus0_vcodec0_clk", + .parent_names = (const char *[]){ + "vcodec0_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_apss_tcu_clk = { + .halt_reg = 0x12018, + .halt_check = BRANCH_VOTED, + .clkr = { + .enable_reg = 0x4500c, + .enable_mask = BIT(1), + .hw.init = &(struct clk_init_data){ + .name = "gcc_apss_tcu_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_cpp_tbu_clk = { + .halt_reg = 0x12040, + .halt_check = BRANCH_VOTED, + .clkr = { + .enable_reg = 0x3600C, + .enable_mask = BIT(14), + .hw.init = &(struct clk_init_data){ + .name = "gcc_cpp_tbu_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_jpeg_tbu_clk = { + .halt_reg = 0x12034, + .halt_check = BRANCH_VOTED, + .clkr = { + .enable_reg = 0x3600C, + .enable_mask = BIT(10), + .hw.init = &(struct clk_init_data){ + .name = "gcc_jpeg_tbu_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_mdp_tbu_clk = { + .halt_reg = 0x1201c, + .halt_check = BRANCH_VOTED, + .clkr = { + .enable_reg = 0x4500c, + .enable_mask = BIT(4), + .hw.init = &(struct clk_init_data){ + .name = "gcc_mdp_tbu_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_smmu_cfg_clk = { + .halt_reg = 0x12038, + .halt_check = BRANCH_VOTED, + .clkr = { + .enable_reg = 0x3600C, + .enable_mask = BIT(12), + .hw.init = &(struct clk_init_data){ + .name = "gcc_smmu_cfg_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_venus_tbu_clk = { + .halt_reg = 0x12014, + .halt_check = BRANCH_VOTED, + .clkr = { + .enable_reg = 0x3600C, + .enable_mask = BIT(5), + .hw.init = &(struct clk_init_data){ + .name = "gcc_venus_tbu_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_vfe_tbu_clk = { + .halt_reg = 0x1203C, + .halt_check = BRANCH_VOTED, + .clkr = { + .enable_reg = 0x3600C, + .enable_mask = BIT(9), + .hw.init = &(struct clk_init_data){ + .name = "gcc_vfe_tbu_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_vfe1_tbu_clk = { + .halt_reg = 0x12090, + .halt_check = BRANCH_VOTED, + .clkr = { + .enable_reg = 0x3600C, + .enable_mask = BIT(17), + .hw.init = &(struct clk_init_data){ + .name = "gcc_vfe1_tbu_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_qdss_dap_clk = { + .halt_reg = 0x29084, + .halt_check = BRANCH_VOTED, + .clkr = { + .enable_reg = 0x45004, + .enable_mask = BIT(21), + .hw.init = &(struct clk_init_data){ + .name = "gcc_qdss_dap_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_dummy wcnss_m_clk = { + .rrate = 0, + .hw.init = &(struct clk_init_data){ + .name = "wcnss_m_clk", + .ops = &clk_dummy_ops, + }, +}; + +struct clk_hw *gcc_sdm429w_hws[] = { + [GPLL0_OUT_AUX] = &gpll0_out_aux.hw, +}; + +static struct clk_regmap *gcc_sdm429w_clocks[] = { + [APSS_AHB_CLK_SRC] = &apss_ahb_clk_src.clkr, + [BLSP1_QUP1_I2C_APPS_CLK_SRC] = &blsp1_qup1_i2c_apps_clk_src.clkr, + [BLSP1_QUP1_SPI_APPS_CLK_SRC] = &blsp1_qup1_spi_apps_clk_src.clkr, + [BLSP1_QUP2_I2C_APPS_CLK_SRC] = &blsp1_qup2_i2c_apps_clk_src.clkr, + [BLSP1_QUP2_SPI_APPS_CLK_SRC] = &blsp1_qup2_spi_apps_clk_src.clkr, + [BLSP1_QUP3_I2C_APPS_CLK_SRC] = &blsp1_qup3_i2c_apps_clk_src.clkr, + [BLSP1_QUP3_SPI_APPS_CLK_SRC] = &blsp1_qup3_spi_apps_clk_src.clkr, + [BLSP1_QUP4_I2C_APPS_CLK_SRC] = &blsp1_qup4_i2c_apps_clk_src.clkr, + [BLSP1_QUP4_SPI_APPS_CLK_SRC] = &blsp1_qup4_spi_apps_clk_src.clkr, + [BLSP1_UART1_APPS_CLK_SRC] = &blsp1_uart1_apps_clk_src.clkr, + [BLSP1_UART2_APPS_CLK_SRC] = &blsp1_uart2_apps_clk_src.clkr, + [GCC_BLSP2_AHB_CLK] = &gcc_blsp2_ahb_clk.clkr, + [BLSP2_QUP1_I2C_APPS_CLK_SRC] = &blsp2_qup1_i2c_apps_clk_src.clkr, + [BLSP2_QUP1_SPI_APPS_CLK_SRC] = &blsp2_qup1_spi_apps_clk_src.clkr, + [BLSP2_QUP2_I2C_APPS_CLK_SRC] = &blsp2_qup2_i2c_apps_clk_src.clkr, + [BLSP2_QUP2_SPI_APPS_CLK_SRC] = &blsp2_qup2_spi_apps_clk_src.clkr, + [BLSP2_QUP3_I2C_APPS_CLK_SRC] = &blsp2_qup3_i2c_apps_clk_src.clkr, + [BLSP2_QUP3_SPI_APPS_CLK_SRC] = &blsp2_qup3_spi_apps_clk_src.clkr, + [BLSP2_QUP4_I2C_APPS_CLK_SRC] = &blsp2_qup4_i2c_apps_clk_src.clkr, + [BLSP2_QUP4_SPI_APPS_CLK_SRC] = &blsp2_qup4_spi_apps_clk_src.clkr, + [BLSP2_UART1_APPS_CLK_SRC] = &blsp2_uart1_apps_clk_src.clkr, + [BLSP2_UART2_APPS_CLK_SRC] = &blsp2_uart2_apps_clk_src.clkr, + [CPP_CLK_SRC] = &cpp_clk_src.clkr, + [CRYPTO_CLK_SRC] = &crypto_clk_src.clkr, + [GCC_BIMC_GFX_CLK] = &gcc_bimc_gfx_clk.clkr, + [GCC_GFX_TCU_CLK] = &gcc_gfx_tcu_clk.clkr, + [GCC_GFX_TBU_CLK] = &gcc_gfx_tbu_clk.clkr, + [GCC_GTCU_AHB_CLK] = &gcc_gtcu_ahb_clk.clkr, + [GCC_OXILI_GMEM_CLK] = &gcc_oxili_gmem_clk.clkr, + [GCC_BLSP1_AHB_CLK] = &gcc_blsp1_ahb_clk.clkr, + [GCC_BLSP1_QUP1_I2C_APPS_CLK] = &gcc_blsp1_qup1_i2c_apps_clk.clkr, + [GCC_BLSP1_QUP1_SPI_APPS_CLK] = &gcc_blsp1_qup1_spi_apps_clk.clkr, + [GCC_BLSP1_QUP2_I2C_APPS_CLK] = &gcc_blsp1_qup2_i2c_apps_clk.clkr, + [GCC_BLSP1_QUP2_SPI_APPS_CLK] = &gcc_blsp1_qup2_spi_apps_clk.clkr, + [GCC_BLSP1_QUP3_I2C_APPS_CLK] = &gcc_blsp1_qup3_i2c_apps_clk.clkr, + [GCC_BLSP1_QUP3_SPI_APPS_CLK] = &gcc_blsp1_qup3_spi_apps_clk.clkr, + [GCC_BLSP1_QUP4_I2C_APPS_CLK] = &gcc_blsp1_qup4_i2c_apps_clk.clkr, + [GCC_BLSP1_QUP4_SPI_APPS_CLK] = &gcc_blsp1_qup4_spi_apps_clk.clkr, + [GCC_BLSP1_UART1_APPS_CLK] = &gcc_blsp1_uart1_apps_clk.clkr, + [GCC_BLSP1_UART2_APPS_CLK] = &gcc_blsp1_uart2_apps_clk.clkr, + [GCC_BLSP2_QUP1_I2C_APPS_CLK] = &gcc_blsp2_qup1_i2c_apps_clk.clkr, + [GCC_BLSP2_QUP1_SPI_APPS_CLK] = &gcc_blsp2_qup1_spi_apps_clk.clkr, + [GCC_BLSP2_QUP2_I2C_APPS_CLK] = &gcc_blsp2_qup2_i2c_apps_clk.clkr, + [GCC_BLSP2_QUP2_SPI_APPS_CLK] = &gcc_blsp2_qup2_spi_apps_clk.clkr, + [GCC_BLSP2_QUP3_I2C_APPS_CLK] = &gcc_blsp2_qup3_i2c_apps_clk.clkr, + [GCC_BLSP2_QUP3_SPI_APPS_CLK] = &gcc_blsp2_qup3_spi_apps_clk.clkr, + [GCC_BLSP2_QUP4_I2C_APPS_CLK] = &gcc_blsp2_qup4_i2c_apps_clk.clkr, + [GCC_BLSP2_QUP4_SPI_APPS_CLK] = &gcc_blsp2_qup4_spi_apps_clk.clkr, + [GCC_BLSP2_UART1_APPS_CLK] = &gcc_blsp2_uart1_apps_clk.clkr, + [GCC_BLSP2_UART2_APPS_CLK] = &gcc_blsp2_uart2_apps_clk.clkr, + [GCC_BOOT_ROM_AHB_CLK] = &gcc_boot_rom_ahb_clk.clkr, + [GCC_CRYPTO_AHB_CLK] = &gcc_crypto_ahb_clk.clkr, + [GCC_CRYPTO_AXI_CLK] = &gcc_crypto_axi_clk.clkr, + [GCC_CRYPTO_CLK] = &gcc_crypto_clk.clkr, + [GCC_GP1_CLK] = &gcc_gp1_clk.clkr, + [GCC_GP2_CLK] = &gcc_gp2_clk.clkr, + [GCC_GP3_CLK] = &gcc_gp3_clk.clkr, + [GCC_PDM2_CLK] = &gcc_pdm2_clk.clkr, + [GCC_PDM_AHB_CLK] = &gcc_pdm_ahb_clk.clkr, + [GCC_PRNG_AHB_CLK] = &gcc_prng_ahb_clk.clkr, + [GCC_SDCC1_AHB_CLK] = &gcc_sdcc1_ahb_clk.clkr, + [GCC_SDCC1_APPS_CLK] = &gcc_sdcc1_apps_clk.clkr, + [GCC_SDCC1_ICE_CORE_CLK] = &gcc_sdcc1_ice_core_clk.clkr, + [GCC_SDCC2_AHB_CLK] = &gcc_sdcc2_ahb_clk.clkr, + [GCC_SDCC2_APPS_CLK] = &gcc_sdcc2_apps_clk.clkr, + [GCC_USB2A_PHY_SLEEP_CLK] = &gcc_usb2a_phy_sleep_clk.clkr, + [GCC_USB_HS_AHB_CLK] = &gcc_usb_hs_ahb_clk.clkr, + [GCC_USB_HS_PHY_CFG_AHB_CLK] = &gcc_usb_hs_phy_cfg_ahb_clk.clkr, + [GCC_USB_HS_SYSTEM_CLK] = &gcc_usb_hs_system_clk.clkr, + [GP1_CLK_SRC] = &gp1_clk_src.clkr, + [GP2_CLK_SRC] = &gp2_clk_src.clkr, + [GP3_CLK_SRC] = &gp3_clk_src.clkr, + [GPLL0_OUT_MAIN] = &gpll0_out_main.clkr, + [GPLL0_AO_OUT_MAIN] = &gpll0_ao_out_main.clkr, + [GPLL0_SLEEP_CLK_SRC] = &gpll0_sleep_clk_src.clkr, + [GPLL3_OUT_MAIN] = &gpll3_out_main.clkr, + [GPLL4_OUT_MAIN] = &gpll4_out_main.clkr, + [GPLL6_OUT_MAIN] = &gpll6_out_main, + [GPLL6] = &gpll6.clkr, + [GPLL6_OUT_AUX] = &gpll6_out_aux, + [JPEG0_CLK_SRC] = &jpeg0_clk_src.clkr, + [PDM2_CLK_SRC] = &pdm2_clk_src.clkr, + [SDCC1_APPS_CLK_SRC] = &sdcc1_apps_clk_src.clkr, + [SDCC1_ICE_CORE_CLK_SRC] = &sdcc1_ice_core_clk_src.clkr, + [SDCC2_APPS_CLK_SRC] = &sdcc2_apps_clk_src.clkr, + [USB_HS_SYSTEM_CLK_SRC] = &usb_hs_system_clk_src.clkr, + [VFE0_CLK_SRC] = &vfe0_clk_src.clkr, + [VFE1_CLK_SRC] = &vfe1_clk_src.clkr, + [GCC_BIMC_GPU_CLK] = &gcc_bimc_gpu_clk.clkr, + [GCC_DCC_CLK] = &gcc_dcc_clk.clkr, + [GCC_MSS_CFG_AHB_CLK] = &gcc_mss_cfg_ahb_clk.clkr, + [GCC_MSS_Q6_BIMC_AXI_CLK] = &gcc_mss_q6_bimc_axi_clk.clkr, + [CAMSS_TOP_AHB_CLK_SRC] = &camss_top_ahb_clk_src.clkr, + [CCI_CLK_SRC] = &cci_clk_src.clkr, + [CSI0_CLK_SRC] = &csi0_clk_src.clkr, + [CSI0PHYTIMER_CLK_SRC] = &csi0phytimer_clk_src.clkr, + [CSI1_CLK_SRC] = &csi1_clk_src.clkr, + [CSI1PHYTIMER_CLK_SRC] = &csi1phytimer_clk_src.clkr, + [CSI2_CLK_SRC] = &csi2_clk_src.clkr, + [ESC0_CLK_SRC] = &esc0_clk_src.clkr, + [ESC1_CLK_SRC] = &esc1_clk_src.clkr, + [GCC_CAMSS_AHB_CLK] = &gcc_camss_ahb_clk.clkr, + [GCC_CAMSS_CCI_AHB_CLK] = &gcc_camss_cci_ahb_clk.clkr, + [GCC_CAMSS_CCI_CLK] = &gcc_camss_cci_clk.clkr, + [GCC_CAMSS_CPP_AHB_CLK] = &gcc_camss_cpp_ahb_clk.clkr, + [GCC_CAMSS_CPP_AXI_CLK] = &gcc_camss_cpp_axi_clk.clkr, + [GCC_CAMSS_CPP_CLK] = &gcc_camss_cpp_clk.clkr, + [GCC_CAMSS_CSI0_AHB_CLK] = &gcc_camss_csi0_ahb_clk.clkr, + [GCC_CAMSS_CSI0_CLK] = &gcc_camss_csi0_clk.clkr, + [GCC_CAMSS_CSI0PHY_CLK] = &gcc_camss_csi0phy_clk.clkr, + [GCC_CAMSS_CSI0PIX_CLK] = &gcc_camss_csi0pix_clk.clkr, + [GCC_CAMSS_CSI0RDI_CLK] = &gcc_camss_csi0rdi_clk.clkr, + [GCC_CAMSS_CSI1_AHB_CLK] = &gcc_camss_csi1_ahb_clk.clkr, + [GCC_CAMSS_CSI1_CLK] = &gcc_camss_csi1_clk.clkr, + [GCC_CAMSS_CSI1PHY_CLK] = &gcc_camss_csi1phy_clk.clkr, + [GCC_CAMSS_CSI1PIX_CLK] = &gcc_camss_csi1pix_clk.clkr, + [GCC_CAMSS_CSI1RDI_CLK] = &gcc_camss_csi1rdi_clk.clkr, + [GCC_CAMSS_CSI2_AHB_CLK] = &gcc_camss_csi2_ahb_clk.clkr, + [GCC_CAMSS_CSI2_CLK] = &gcc_camss_csi2_clk.clkr, + [GCC_CAMSS_CSI2PHY_CLK] = &gcc_camss_csi2phy_clk.clkr, + [GCC_CAMSS_CSI2PIX_CLK] = &gcc_camss_csi2pix_clk.clkr, + [GCC_CAMSS_CSI2RDI_CLK] = &gcc_camss_csi2rdi_clk.clkr, + [GCC_CAMSS_CSI_VFE0_CLK] = &gcc_camss_csi_vfe0_clk.clkr, + [GCC_CAMSS_CSI_VFE1_CLK] = &gcc_camss_csi_vfe1_clk.clkr, + [GCC_CAMSS_GP0_CLK] = &gcc_camss_gp0_clk.clkr, + [GCC_CAMSS_GP0_CLK_SRC] = &camss_gp0_clk_src.clkr, + [GCC_CAMSS_GP1_CLK] = &gcc_camss_gp1_clk.clkr, + [GCC_CAMSS_GP1_CLK_SRC] = &camss_gp1_clk_src.clkr, + [GCC_CAMSS_ISPIF_AHB_CLK] = &gcc_camss_ispif_ahb_clk.clkr, + [GCC_CAMSS_JPEG0_CLK] = &gcc_camss_jpeg0_clk.clkr, + [GCC_CAMSS_JPEG_AHB_CLK] = &gcc_camss_jpeg_ahb_clk.clkr, + [GCC_CAMSS_JPEG_AXI_CLK] = &gcc_camss_jpeg_axi_clk.clkr, + [GCC_CAMSS_MCLK0_CLK] = &gcc_camss_mclk0_clk.clkr, + [GCC_CAMSS_MCLK1_CLK] = &gcc_camss_mclk1_clk.clkr, + [GCC_CAMSS_MCLK2_CLK] = &gcc_camss_mclk2_clk.clkr, + [GCC_CAMSS_MICRO_AHB_CLK] = &gcc_camss_micro_ahb_clk.clkr, + [GCC_CAMSS_TOP_AHB_CLK] = &gcc_camss_top_ahb_clk.clkr, + [GCC_CAMSS_VFE0_CLK] = &gcc_camss_vfe0_clk.clkr, + [GCC_CAMSS_VFE1_AHB_CLK] = &gcc_camss_vfe1_ahb_clk.clkr, + [GCC_CAMSS_VFE1_AXI_CLK] = &gcc_camss_vfe1_axi_clk.clkr, + [GCC_CAMSS_VFE1_CLK] = &gcc_camss_vfe1_clk.clkr, + [GCC_CAMSS_VFE_AHB_CLK] = &gcc_camss_vfe_ahb_clk.clkr, + [GCC_CAMSS_VFE_AXI_CLK] = &gcc_camss_vfe_axi_clk.clkr, + [GCC_CAMSS_CSI0PHYTIMER_CLK] = &gcc_camss_csi0phytimer_clk.clkr, + [GCC_CAMSS_CSI1PHYTIMER_CLK] = &gcc_camss_csi1phytimer_clk.clkr, + [GCC_MDSS_AHB_CLK] = &gcc_mdss_ahb_clk.clkr, + [GCC_MDSS_AXI_CLK] = &gcc_mdss_axi_clk.clkr, + [GCC_MDSS_ESC0_CLK] = &gcc_mdss_esc0_clk.clkr, + [GCC_MDSS_ESC1_CLK] = &gcc_mdss_esc1_clk.clkr, + [GCC_MDSS_MDP_CLK] = &gcc_mdss_mdp_clk.clkr, + [GCC_MDSS_VSYNC_CLK] = &gcc_mdss_vsync_clk.clkr, + [GCC_OXILI_AHB_CLK] = &gcc_oxili_ahb_clk.clkr, + [GCC_OXILI_AON_CLK] = &gcc_oxili_aon_clk.clkr, + [GCC_OXILI_GFX3D_CLK] = &gcc_oxili_gfx3d_clk.clkr, + [GCC_OXILI_TIMER_CLK] = &gcc_oxili_timer_clk.clkr, + [GCC_VENUS0_AHB_CLK] = &gcc_venus0_ahb_clk.clkr, + [GCC_VENUS0_AXI_CLK] = &gcc_venus0_axi_clk.clkr, + [GCC_VENUS0_CORE0_VCODEC0_CLK] = &gcc_venus0_core0_vcodec0_clk.clkr, + [GCC_VENUS0_VCODEC0_CLK] = &gcc_venus0_vcodec0_clk.clkr, + [GCC_XO_CLK_SRC] = &gcc_xo_clk_src.clkr, + [GFX3D_CLK_SRC] = &gfx3d_clk_src.clkr, + [MCLK0_CLK_SRC] = &mclk0_clk_src.clkr, + [MCLK1_CLK_SRC] = &mclk1_clk_src.clkr, + [MCLK2_CLK_SRC] = &mclk2_clk_src.clkr, + [MDP_CLK_SRC] = &mdp_clk_src.clkr, + [VCODEC0_CLK_SRC] = &vcodec0_clk_src.clkr, + [VSYNC_CLK_SRC] = &vsync_clk_src.clkr, + [GCC_APSS_TCU_CLK] = &gcc_apss_tcu_clk.clkr, + [GCC_CPP_TBU_CLK] = &gcc_cpp_tbu_clk.clkr, + [GCC_JPEG_TBU_CLK] = &gcc_jpeg_tbu_clk.clkr, + [GCC_MDP_TBU_CLK] = &gcc_mdp_tbu_clk.clkr, + [GCC_SMMU_CFG_CLK] = &gcc_smmu_cfg_clk.clkr, + [GCC_VENUS_TBU_CLK] = &gcc_venus_tbu_clk.clkr, + [GCC_VFE_TBU_CLK] = &gcc_vfe_tbu_clk.clkr, + [GCC_VFE1_TBU_CLK] = &gcc_vfe1_tbu_clk.clkr, + [GCC_QDSS_DAP_CLK] = &gcc_qdss_dap_clk.clkr, +}; + +static const struct qcom_reset_map gcc_sdm429w_resets[] = { + [GCC_CAMSS_MICRO_BCR] = {0x56008}, + [GCC_USB_FS_BCR] = {0x3F000}, + [GCC_USB_HS_BCR] = {0x41000}, + [GCC_USB2_HS_PHY_ONLY_BCR] = {0x41034}, + [GCC_QUSB2_PHY_BCR] = {0x4103C}, +}; + +static const struct regmap_config gcc_sdm429w_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x7f000, + .fast_io = true, +}; + +static const struct qcom_cc_desc gcc_sdm429w_desc = { + .config = &gcc_sdm429w_regmap_config, + .clks = gcc_sdm429w_clocks, + .num_clks = ARRAY_SIZE(gcc_sdm429w_clocks), + .hwclks = gcc_sdm429w_hws, + .num_hwclks = ARRAY_SIZE(gcc_sdm429w_hws), + .resets = gcc_sdm429w_resets, + .num_resets = ARRAY_SIZE(gcc_sdm429w_resets), +}; + +static void get_speed_bin(struct platform_device *pdev, int *bin) +{ + struct resource *res; + void __iomem *base; + u32 config_efuse; + + *bin = 0; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gpu-bin"); + if (!res) { + dev_info(&pdev->dev, + "No GPU speed binning available. Defaulting to 0.\n"); + return; + } + + base = ioremap(res->start, resource_size(res)); + if (!base) { + dev_warn(&pdev->dev, + "Unable to ioremap efuse reg address. Defaulting to 0.\n"); + return; + } + + config_efuse = readl_relaxed(base); + iounmap(base); + *bin = (config_efuse >> 31) & 0x1; + + dev_info(&pdev->dev, "GPU speed bin: %d\n", *bin); +} + +static struct clk_init_data vcodec0_clk_src_init = { + .name = "vcodec0_clk_src", + .parent_names = gcc_parent_names_7, + .num_parents = 5, + .ops = &clk_rcg2_ops, + .vdd_class = &vdd_cx, + .num_rate_max = VDD_NUM, + .rate_max = (unsigned long[VDD_NUM]) { + [VDD_LOW] = 200000000, + [VDD_LOW_L1] = 270000000, + [VDD_NOMINAL] = 308570000, + [VDD_NOMINAL_L1] = 329140000, + [VDD_HIGH] = 360000000}, +}; + +static void fixup_for_qm215(struct platform_device *pdev, + struct regmap *regmap, int speed_bin) +{ + gpll3_config.l = 0x30; + gpll3_config.alpha_hi = 0x70; + + vfe0_clk_src.clkr.hw.init->rate_max[VDD_LOW] = 160000000; + vfe0_clk_src.clkr.hw.init->rate_max[VDD_LOW_L1] = 266670000; + vfe0_clk_src.clkr.hw.init->rate_max[VDD_NOMINAL] = 320000000; + vfe0_clk_src.clkr.hw.init->rate_max[VDD_NOMINAL_L1] = 329140000; + vfe0_clk_src.clkr.hw.init->rate_max[VDD_HIGH] = 360000000; + vfe0_clk_src.freq_tbl = ftbl_vfe0_clk_src_qm215; + + vfe1_clk_src.clkr.hw.init->rate_max[VDD_LOW] = 160000000; + vfe1_clk_src.clkr.hw.init->rate_max[VDD_LOW_L1] = 266670000; + vfe1_clk_src.clkr.hw.init->rate_max[VDD_NOMINAL] = 320000000; + vfe1_clk_src.clkr.hw.init->rate_max[VDD_NOMINAL_L1] = 329140000; + vfe1_clk_src.clkr.hw.init->rate_max[VDD_HIGH] = 360000000; + vfe1_clk_src.freq_tbl = ftbl_vfe0_clk_src_qm215; + + cpp_clk_src.clkr.hw.init->rate_max[VDD_LOW] = 160000000; + cpp_clk_src.clkr.hw.init->rate_max[VDD_LOW_L1] = 266670000; + cpp_clk_src.clkr.hw.init->rate_max[VDD_NOMINAL] = 320000000; + cpp_clk_src.clkr.hw.init->rate_max[VDD_NOMINAL_L1] = 360000000; + cpp_clk_src.clkr.hw.init->rate_max[VDD_HIGH] = 0; + cpp_clk_src.freq_tbl = ftbl_cpp_clk_src_qm215; + + vcodec0_clk_src.freq_tbl = ftbl_vcodec0_clk_src_qm215; + vcodec0_clk_src.parent_map = gcc_parent_map_7; + vcodec0_clk_src.clkr.hw.init = &vcodec0_clk_src_init; + + gfx3d_clk_src.parent_map = gcc_parent_map_14_gfx3d; + gfx3d_clk_src.freq_tbl = ftbl_oxili_gfx3d_clk_src_qm215; + + if (speed_bin) { + gcc_oxili_gfx3d_clk.clkr.hw.init->rate_max[VDD_LOW] = + 270000000; + gcc_oxili_gfx3d_clk.clkr.hw.init->rate_max[VDD_LOW_L1] = + 400000000; + gcc_oxili_gfx3d_clk.clkr.hw.init->rate_max[VDD_NOMINAL] = + 484800000; + gcc_oxili_gfx3d_clk.clkr.hw.init->rate_max[VDD_NOMINAL_L1] = + 523200000; + gcc_oxili_gfx3d_clk.clkr.hw.init->rate_max[VDD_HIGH] = + 650000000; + } else { + gcc_oxili_gfx3d_clk.clkr.hw.init->rate_max[VDD_LOW] = + 270000000; + gcc_oxili_gfx3d_clk.clkr.hw.init->rate_max[VDD_LOW_L1] = + 400000000; + gcc_oxili_gfx3d_clk.clkr.hw.init->rate_max[VDD_NOMINAL] = + 484800000; + gcc_oxili_gfx3d_clk.clkr.hw.init->rate_max[VDD_NOMINAL_L1] = + 523200000; + gcc_oxili_gfx3d_clk.clkr.hw.init->rate_max[VDD_HIGH] = + 598000000; + } + + gpll3_out_main.clkr.hw.init->rate_max[VDD_LOW_L1] = 0; + gpll3_out_main.clkr.hw.init->rate_max[VDD_NOMINAL] = 1400000000; + + csi0phytimer_clk_src.clkr.hw.init->rate_max[VDD_LOW] = 100000000; + csi0phytimer_clk_src.clkr.hw.init->rate_max[VDD_LOW_L1] = 200000000; + csi0phytimer_clk_src.clkr.hw.init->rate_max[VDD_NOMINAL] = 266670000; + csi0phytimer_clk_src.freq_tbl = ftbl_csi0phytimer_clk_src_qm215; + + csi1phytimer_clk_src.clkr.hw.init->rate_max[VDD_LOW] = 100000000; + csi1phytimer_clk_src.clkr.hw.init->rate_max[VDD_LOW_L1] = 200000000; + csi1phytimer_clk_src.clkr.hw.init->rate_max[VDD_NOMINAL] = 266670000; + csi1phytimer_clk_src.freq_tbl = ftbl_csi0phytimer_clk_src_qm215; + + sdcc1_apps_clk_src.clkr.hw.init->rate_max[VDD_LOW] = 200000000; + sdcc1_apps_clk_src.clkr.hw.init->rate_max[VDD_NOMINAL] = 400000000; + + usb_hs_system_clk_src.clkr.hw.init->rate_max[VDD_LOW] = 800000000; + usb_hs_system_clk_src.clkr.hw.init->rate_max[VDD_NOMINAL] = 133333000; + usb_hs_system_clk_src.clkr.hw.init->rate_max[VDD_HIGH] = 177780000; + usb_hs_system_clk_src.freq_tbl = ftbl_usb_hs_system_clk_src_qm215; + + /* + * Below clocks are not available on QM215, thus mark them NULL. + */ + gcc_sdm429w_desc.clks[BLSP1_QUP1_I2C_APPS_CLK_SRC] = NULL; + gcc_sdm429w_desc.clks[BLSP1_QUP1_SPI_APPS_CLK_SRC] = NULL; + gcc_sdm429w_desc.clks[BLSP2_QUP4_I2C_APPS_CLK_SRC] = NULL; + gcc_sdm429w_desc.clks[BLSP2_QUP4_SPI_APPS_CLK_SRC] = NULL; + gcc_sdm429w_desc.clks[GCC_BLSP1_QUP1_I2C_APPS_CLK] = NULL; + gcc_sdm429w_desc.clks[GCC_BLSP1_QUP1_SPI_APPS_CLK] = NULL; + gcc_sdm429w_desc.clks[GCC_BLSP2_QUP4_I2C_APPS_CLK] = NULL; + gcc_sdm429w_desc.clks[GCC_BLSP2_QUP4_SPI_APPS_CLK] = NULL; + gcc_sdm429w_desc.clks[GCC_OXILI_AON_CLK] = NULL; + gcc_sdm429w_desc.clks[GCC_OXILI_TIMER_CLK] = NULL; + gcc_sdm429w_desc.clks[ESC1_CLK_SRC] = NULL; + gcc_sdm429w_desc.clks[GCC_MDSS_ESC1_CLK] = NULL; +} + +static const struct of_device_id gcc_sdm429w_match_table[] = { + { .compatible = "qcom,gcc-sdm429w" }, + { .compatible = "qcom,gcc-qm215" }, + { } +}; +MODULE_DEVICE_TABLE(of, gcc_sdm429w_match_table); + +static int gcc_sdm429w_probe(struct platform_device *pdev) +{ + struct regmap *regmap; + struct clk *clk; + int ret, speed_bin; + bool qm215; + + qm215 = of_device_is_compatible(pdev->dev.of_node, + "qcom,gcc-qm215"); + + clk = clk_get(&pdev->dev, "bi_tcxo"); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) != -EPROBE_DEFER) + dev_err(&pdev->dev, "Unable to get bi_tcxo clock\n"); + return PTR_ERR(clk); + } + clk_put(clk); + + vdd_cx.regulator[0] = devm_regulator_get(&pdev->dev, "vdd_cx"); + if (IS_ERR(vdd_cx.regulator[0])) { + if (PTR_ERR(vdd_cx.regulator[0]) != -EPROBE_DEFER) + dev_err(&pdev->dev, "Unable to get vdd_cx regulator\n"); + return PTR_ERR(vdd_cx.regulator[0]); + } + + regmap = qcom_cc_map(pdev, &gcc_sdm429w_desc); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + if (qm215) { + speed_bin = 0; + get_speed_bin(pdev, &speed_bin); + fixup_for_qm215(pdev, regmap, speed_bin); + + /* Configure Sleep and Wakeup cycles for GMEM clock */ + regmap_update_bits(regmap, gcc_oxili_gmem_clk.clkr.enable_reg, + 0xff0, 0xff0); + } + + clk_alpha_pll_configure(&gpll3_out_main, regmap, &gpll3_config); + + clk = devm_clk_register(&pdev->dev, &wcnss_m_clk.hw); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Unable to register wcnss_m_clk\n"); + return PTR_ERR(clk); + } + + ret = qcom_cc_really_probe(pdev, &gcc_sdm429w_desc, regmap); + if (ret) { + dev_err(&pdev->dev, "Failed to register GCC clocks\n"); + return ret; + } + + clk_set_rate(apss_ahb_clk_src.clkr.hw.clk, 19200000); + clk_prepare_enable(apss_ahb_clk_src.clkr.hw.clk); + clk_prepare_enable(gpll0_ao_out_main.clkr.hw.clk); + + dev_info(&pdev->dev, "Registered GCC clocks\n"); + + return 0; +} + +static struct platform_driver gcc_sdm429w_driver = { + .probe = gcc_sdm429w_probe, + .driver = { + .name = "gcc-sdm429w", + .of_match_table = gcc_sdm429w_match_table, + }, +}; + +static int __init gcc_sdm429w_init(void) +{ + return platform_driver_register(&gcc_sdm429w_driver); +} +subsys_initcall(gcc_sdm429w_init); + +static void __exit gcc_sdm429w_exit(void) +{ + platform_driver_unregister(&gcc_sdm429w_driver); +} +module_exit(gcc_sdm429w_exit); + +struct clk_hw *mdss_sdm429w_hws[] = { + [MDSS_MDP_VOTE_CLK] = &mdss_mdp_vote_clk.hw, + [MDSS_ROTATOR_VOTE_CLK] = &mdss_rotator_vote_clk.hw, +}; + +static struct clk_regmap *mdss_sdm429w_clocks[] = { + [GCC_MDSS_BYTE0_CLK] = &gcc_mdss_byte0_clk.clkr, + [GCC_MDSS_BYTE1_CLK] = &gcc_mdss_byte1_clk.clkr, + [GCC_MDSS_PCLK0_CLK] = &gcc_mdss_pclk0_clk.clkr, + [GCC_MDSS_PCLK1_CLK] = &gcc_mdss_pclk1_clk.clkr, + [BYTE0_CLK_SRC] = &byte0_clk_src.clkr, + [BYTE1_CLK_SRC] = &byte1_clk_src.clkr, + [PCLK0_CLK_SRC] = &pclk0_clk_src.clkr, + [PCLK1_CLK_SRC] = &pclk1_clk_src.clkr, +}; + +static const struct qcom_cc_desc mdss_sdm429w_desc = { + .config = &gcc_sdm429w_regmap_config, + .clks = mdss_sdm429w_clocks, + .num_clks = ARRAY_SIZE(mdss_sdm429w_clocks), + .hwclks = mdss_sdm429w_hws, + .num_hwclks = ARRAY_SIZE(mdss_sdm429w_hws), +}; + +static const struct of_device_id mdss_sdm429w_match_table[] = { + { .compatible = "qcom,gcc-mdss-sdm429w" }, + { .compatible = "qcom,gcc-mdss-8917" }, + {} +}; +MODULE_DEVICE_TABLE(of, mdss_sdm429w_match_table); + +static int mdss_sdm429w_probe(struct platform_device *pdev) +{ + struct clk *clk; + struct regmap *regmap; + struct resource *res; + void __iomem *base; + int ret; + + clk = clk_get(&pdev->dev, "pclk0_src"); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) != -EPROBE_DEFER) + dev_err(&pdev->dev, "Unable to get pclk0_src clock\n"); + return PTR_ERR(clk); + } + clk_put(clk); + + clk = clk_get(&pdev->dev, "byte0_src"); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) != -EPROBE_DEFER) + dev_err(&pdev->dev, "Unable to get byte0_src clock\n"); + return PTR_ERR(clk); + } + clk_put(clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "Failed to get resources\n"); + return -EINVAL; + } + + base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(&pdev->dev, base, + mdss_sdm429w_desc.config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = qcom_cc_really_probe(pdev, &mdss_sdm429w_desc, regmap); + if (ret) { + dev_err(&pdev->dev, "Failed to register MDSS clocks\n"); + return ret; + } + + dev_info(&pdev->dev, "Registered GCC MDSS Clocks\n"); + + return ret; +} + +static struct platform_driver mdss_sdm429w_driver = { + .probe = mdss_sdm429w_probe, + .driver = { + .name = "gcc-mdss-sdm429w", + .of_match_table = mdss_sdm429w_match_table, + }, +}; + +static int __init mdss_sdm429w_init(void) +{ + return platform_driver_register(&mdss_sdm429w_driver); +} +subsys_initcall(mdss_sdm429w_init); + +static void __exit mdss_sdm429w_exit(void) +{ + platform_driver_unregister(&mdss_sdm429w_driver); +} +module_exit(mdss_sdm429w_exit); + +MODULE_DESCRIPTION("QTI GCC sdm429w Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clk/qcom/gdsc-debug.h b/drivers/clk/qcom/gdsc-debug.h new file mode 100644 index 000000000000..5f1798ef2078 --- /dev/null +++ b/drivers/clk/qcom/gdsc-debug.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __QCOM_GDSC_DEBUG_H__ +#define __QCOM_GDSC_DEBUG_H__ + +void gdsc_debug_print_regs(struct regulator *regulator); + +#endif /* __QCOM_GDSC_DEBUG_H__ */ diff --git a/drivers/clk/qcom/gdsc-regulator.c b/drivers/clk/qcom/gdsc-regulator.c index 402fac560963..ee8b6a2cde89 100644 --- a/drivers/clk/qcom/gdsc-regulator.c +++ b/drivers/clk/qcom/gdsc-regulator.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. */ #include @@ -22,6 +22,8 @@ #include #include +#include "../../regulator/internal.h" +#include "gdsc-debug.h" /* GDSCR */ #define PWR_ON_MASK BIT(31) @@ -615,13 +617,38 @@ static struct regulator_ops gdsc_ops = { .get_mode = gdsc_get_mode, }; -static const struct regmap_config gdsc_regmap_config = { +static struct regmap_config gdsc_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, + .max_register = 0x8, .fast_io = true, }; +void gdsc_debug_print_regs(struct regulator *regulator) +{ + struct gdsc *sc = rdev_get_drvdata(regulator->rdev); + uint32_t regvals[3] = {0}; + int ret; + + if (!sc) { + pr_err("Failed to get GDSC Handle\n"); + return; + } + + ret = regmap_bulk_read(sc->regmap, REG_OFFSET, regvals, + gdsc_regmap_config.max_register ? 3 : 1); + if (ret) { + pr_err("Failed to read %s registers\n", sc->rdesc.name); + return; + } + + pr_info("Dumping %s Registers:\n", sc->rdesc.name); + pr_info("GDSCR: 0x%.8x CFG: 0x%.8x CFG2: 0x%.8x\n", + regvals[0], regvals[1], regvals[2]); +} +EXPORT_SYMBOL(gdsc_debug_print_regs); + static int gdsc_parse_dt_data(struct gdsc *sc, struct device *dev, struct regulator_init_data **init_data) { @@ -732,6 +759,9 @@ static int gdsc_get_resources(struct gdsc *sc, struct platform_device *pdev) if (sc->gdscr == NULL) return -ENOMEM; + if (of_property_read_bool(dev->of_node, "qcom,no-config-gdscr")) + gdsc_regmap_config.max_register = 0; + sc->regmap = devm_regmap_init_mmio(dev, sc->gdscr, &gdsc_regmap_config); if (!sc->regmap) { dev_err(dev, "Couldn't get regmap\n"); diff --git a/drivers/clk/qcom/mdss/Makefile b/drivers/clk/qcom/mdss/Makefile index 895c356a03b4..9b0cb01bff9d 100644 --- a/drivers/clk/qcom/mdss/Makefile +++ b/drivers/clk/qcom/mdss/Makefile @@ -4,3 +4,7 @@ obj-$(CONFIG_MDSS_PLL) += mdss-pll.o obj-$(CONFIG_MDSS_PLL) += mdss-dsi-pll-14nm.o obj-$(CONFIG_MDSS_PLL) += mdss-dsi-pll-14nm-util.o obj-$(CONFIG_MDSS_PLL) += mdss-dp-pll-14nm.o +obj-$(CONFIG_MDSS_PLL) += mdss-dsi-pll-28lpm.o +obj-$(CONFIG_MDSS_PLL) += mdss-dsi-pll-28nm-util.o +obj-$(CONFIG_MDSS_PLL) += mdss-dsi-pll-12nm.o +obj-$(CONFIG_MDSS_PLL) += mdss-dsi-pll-12nm-util.o diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-12nm-util.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-12nm-util.c new file mode 100644 index 000000000000..e26698eb0d0e --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-12nm-util.c @@ -0,0 +1,979 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" +#include "mdss-dsi-pll-12nm.h" + +#define DSI_PLL_POLL_MAX_READS 15 +#define DSI_PLL_POLL_TIMEOUT_US 1000 + +int pixel_div_set_div(void *context, unsigned int reg, + unsigned int div) +{ + struct mdss_pll_resources *pll = context; + struct dsi_pll_db *pdb; + + pdb = (struct dsi_pll_db *)pll->priv; + + /* Programming during vco_prepare. Keep this value */ + pdb->param.pixel_divhf = (div - 1); + + pr_debug("ndx=%d div=%d divhf=%d\n", + pll->index, div, pdb->param.pixel_divhf); + + return 0; +} + +int pixel_div_get_div(void *context, unsigned int reg, + unsigned int *div) +{ + int rc; + struct mdss_pll_resources *pll = context; + + if (is_gdsc_disabled(pll)) + return 0; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + *div = (MDSS_PLL_REG_R(pll->pll_base, DSIPHY_SSC9) & 0x7F); + pr_debug("pixel_div = %d\n", (*div+1)); + + mdss_pll_resource_enable(pll, false); + + return 0; +} + +int set_post_div_mux_sel(void *context, unsigned int reg, + unsigned int sel) +{ + struct mdss_pll_resources *pll = context; + struct dsi_pll_db *pdb; + + pdb = (struct dsi_pll_db *)pll->priv; + + /* Programming during vco_prepare. Keep this value */ + pdb->param.post_div_mux = sel; + + pr_debug("ndx=%d post_div_mux_sel=%d p_div=%d\n", + pll->index, sel, (u32) BIT(sel)); + + return 0; +} + +int get_post_div_mux_sel(void *context, unsigned int reg, + unsigned int *sel) +{ + u32 vco_cntrl = 0, cpbias_cntrl = 0; + int rc; + struct mdss_pll_resources *pll = context; + + if (is_gdsc_disabled(pll)) + return 0; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + vco_cntrl = MDSS_PLL_REG_R(pll->pll_base, DSIPHY_PLL_VCO_CTRL); + vco_cntrl &= 0x30; + + cpbias_cntrl = MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_PLL_CHAR_PUMP_BIAS_CTRL); + cpbias_cntrl = ((cpbias_cntrl >> 6) & 0x1); + + if (cpbias_cntrl == 0) { + if (vco_cntrl == 0x00) + *sel = 0; + else if (vco_cntrl == 0x10) + *sel = 2; + else if (vco_cntrl == 0x20) + *sel = 3; + else if (vco_cntrl == 0x30) + *sel = 4; + } else if (cpbias_cntrl == 1) { + if (vco_cntrl == 0x30) + *sel = 2; + else if (vco_cntrl == 0x00) + *sel = 5; + } + + mdss_pll_resource_enable(pll, false); + + return 0; +} + +int set_gp_mux_sel(void *context, unsigned int reg, + unsigned int sel) +{ + struct mdss_pll_resources *pll = context; + struct dsi_pll_db *pdb; + + pdb = (struct dsi_pll_db *)pll->priv; + + /* Programming during vco_prepare. Keep this value */ + pdb->param.gp_div_mux = sel; + + pr_debug("ndx=%d gp_div_mux_sel=%d gp_cntrl=%d\n", + pll->index, sel, (u32) BIT(sel)); + + return 0; +} + +int get_gp_mux_sel(void *context, unsigned int reg, + unsigned int *sel) +{ + int rc; + struct mdss_pll_resources *pll = context; + u32 reg_val; + + if (is_gdsc_disabled(pll)) + return 0; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + reg_val = MDSS_PLL_REG_R(pll->pll_base, DSIPHY_PLL_CTRL); + *sel = (reg_val >> 5) & 0x7; + pr_debug("gp_cntrl = %d\n", *sel); + + mdss_pll_resource_enable(pll, false); + + return 0; +} + +static bool pll_is_pll_locked_12nm(struct mdss_pll_resources *pll, + bool is_handoff) +{ + u32 status; + bool pll_locked; + + /* poll for PLL ready status */ + if (readl_poll_timeout_atomic((pll->pll_base + + DSIPHY_STAT0), + status, + ((status & BIT(1)) > 0), + DSI_PLL_POLL_MAX_READS, + DSI_PLL_POLL_TIMEOUT_US)) { + if (!is_handoff) + pr_err("DSI PLL ndx=%d status=%x failed to Lock\n", + pll->index, status); + pll_locked = false; + } else { + pll_locked = true; + } + + return pll_locked; +} + +int dsi_pll_enable_seq_12nm(struct mdss_pll_resources *pll) +{ + int rc = 0; + struct dsi_pll_db *pdb; + void __iomem *pll_base; + + if (!pll) { + pr_err("Invalid PLL resources\n"); + return -EINVAL; + } + + pdb = (struct dsi_pll_db *)pll->priv; + if (!pdb) { + pr_err("No priv found\n"); + return -EINVAL; + } + + pll_base = pll->pll_base; + + MDSS_PLL_REG_W(pll_base, DSIPHY_SYS_CTRL, 0x49); + wmb(); /* make sure register committed before enabling branch clocks */ + udelay(5); /* h/w recommended delay */ + MDSS_PLL_REG_W(pll_base, DSIPHY_SYS_CTRL, 0xc9); + wmb(); /* make sure register committed before enabling branch clocks */ + udelay(50); /* h/w recommended delay */ + + if (!pll_is_pll_locked_12nm(pll, false)) { + pr_err("DSI PLL ndx=%d lock failed!\n", + pll->index); + rc = -EINVAL; + goto init_lock_err; + } + + pr_debug("DSI PLL ndx:%d Locked!\n", pll->index); + +init_lock_err: + return rc; +} + +static int dsi_pll_enable(struct clk_hw *hw) +{ + int i, rc = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *pll = vco->priv; + + /* Try all enable sequences until one succeeds */ + for (i = 0; i < vco->pll_en_seq_cnt; i++) { + rc = vco->pll_enable_seqs[i](pll); + pr_debug("DSI PLL %s after sequence #%d\n", + rc ? "unlocked" : "locked", i + 1); + if (!rc) + break; + } + + if (rc) + pr_err("ndx=%d DSI PLL failed to lock\n", pll->index); + else + pll->pll_on = true; + + return rc; +} + +static int dsi_pll_relock(struct mdss_pll_resources *pll) +{ + void __iomem *pll_base = pll->pll_base; + u32 data = 0; + int rc = 0; + + data = MDSS_PLL_REG_R(pll_base, DSIPHY_PLL_POWERUP_CTRL); + data &= ~BIT(1); /* remove ONPLL_OVR_EN bit */ + data |= 0x1; /* set ONPLL_OVN to 0x1 */ + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_POWERUP_CTRL, data); + ndelay(500); /* h/w recommended delay */ + MDSS_PLL_REG_W(pll_base, DSIPHY_SYS_CTRL, 0x49); + wmb(); /* make sure register committed before enabling branch clocks */ + udelay(5); /* h/w recommended delay */ + MDSS_PLL_REG_W(pll_base, DSIPHY_SYS_CTRL, 0xc9); + wmb(); /* make sure register committed before enabling branch clocks */ + udelay(50); /* h/w recommended delay */ + + if (!pll_is_pll_locked_12nm(pll, false)) { + pr_err("DSI PLL ndx=%d lock failed!\n", + pll->index); + rc = -EINVAL; + goto relock_err; + } + ndelay(50); /* h/w recommended delay */ + + data = MDSS_PLL_REG_R(pll_base, DSIPHY_PLL_CTRL); + data |= 0x01; /* set CLK_SEL bits to 0x1 */ + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_CTRL, data); + ndelay(500); /* h/w recommended delay */ + wmb(); /* make sure register committed before enabling branch clocks */ + pll->pll_on = true; +relock_err: + return rc; +} + +static void dsi_pll_disable(struct clk_hw *hw) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *pll = vco->priv; + void __iomem *pll_base = pll->pll_base; + u32 data = 0; + + if (!pll->pll_on && + mdss_pll_resource_enable(pll, true)) { + pr_err("Failed to enable mdss dsi pll=%d\n", pll->index); + return; + } + + data = MDSS_PLL_REG_R(pll_base, DSIPHY_SSC0); + data &= ~BIT(6); /* disable GP_CLK_EN */ + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC0, data); + ndelay(500); /* h/w recommended delay */ + + data = MDSS_PLL_REG_R(pll_base, DSIPHY_PLL_CTRL); + data &= ~0x03; /* remove CLK_SEL bits */ + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_CTRL, data); + ndelay(500); /* h/w recommended delay */ + + data = MDSS_PLL_REG_R(pll_base, DSIPHY_PLL_POWERUP_CTRL); + data &= ~0x1; /* remove ONPLL_OVR bit */ + data |= BIT(1); /* set ONPLL_OVR_EN to 0x1 */ + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_POWERUP_CTRL, data); + ndelay(500); /* h/w recommended delay */ + wmb(); /* make sure register committed before disabling branch clocks */ + pll->handoff_resources = false; + + mdss_pll_resource_enable(pll, false); + + pll->pll_on = false; + + pr_debug("DSI PLL ndx=%d Disabled\n", pll->index); +} + +static u32 __mdss_dsi_get_hsfreqrange(u64 target_freq) +{ + u64 bitclk_rate_mhz = div_u64((target_freq * 2), 1000000); + + if (bitclk_rate_mhz >= 80 && bitclk_rate_mhz < 90) + return 0x00; + else if (bitclk_rate_mhz >= 90 && bitclk_rate_mhz < 100) + return 0x10; + else if (bitclk_rate_mhz >= 100 && bitclk_rate_mhz < 110) + return 0x20; + else if (bitclk_rate_mhz >= 110 && bitclk_rate_mhz < 120) + return 0x30; + else if (bitclk_rate_mhz >= 120 && bitclk_rate_mhz < 130) + return 0x01; + else if (bitclk_rate_mhz >= 130 && bitclk_rate_mhz < 140) + return 0x11; + else if (bitclk_rate_mhz >= 140 && bitclk_rate_mhz < 150) + return 0x21; + else if (bitclk_rate_mhz >= 150 && bitclk_rate_mhz < 160) + return 0x31; + else if (bitclk_rate_mhz >= 160 && bitclk_rate_mhz < 170) + return 0x02; + else if (bitclk_rate_mhz >= 170 && bitclk_rate_mhz < 180) + return 0x12; + else if (bitclk_rate_mhz >= 180 && bitclk_rate_mhz < 190) + return 0x22; + else if (bitclk_rate_mhz >= 190 && bitclk_rate_mhz < 205) + return 0x32; + else if (bitclk_rate_mhz >= 205 && bitclk_rate_mhz < 220) + return 0x03; + else if (bitclk_rate_mhz >= 220 && bitclk_rate_mhz < 235) + return 0x13; + else if (bitclk_rate_mhz >= 235 && bitclk_rate_mhz < 250) + return 0x23; + else if (bitclk_rate_mhz >= 250 && bitclk_rate_mhz < 275) + return 0x33; + else if (bitclk_rate_mhz >= 275 && bitclk_rate_mhz < 300) + return 0x04; + else if (bitclk_rate_mhz >= 300 && bitclk_rate_mhz < 325) + return 0x14; + else if (bitclk_rate_mhz >= 325 && bitclk_rate_mhz < 350) + return 0x25; + else if (bitclk_rate_mhz >= 350 && bitclk_rate_mhz < 400) + return 0x35; + else if (bitclk_rate_mhz >= 400 && bitclk_rate_mhz < 450) + return 0x05; + else if (bitclk_rate_mhz >= 450 && bitclk_rate_mhz < 500) + return 0x16; + else if (bitclk_rate_mhz >= 500 && bitclk_rate_mhz < 550) + return 0x26; + else if (bitclk_rate_mhz >= 550 && bitclk_rate_mhz < 600) + return 0x37; + else if (bitclk_rate_mhz >= 600 && bitclk_rate_mhz < 650) + return 0x07; + else if (bitclk_rate_mhz >= 650 && bitclk_rate_mhz < 700) + return 0x18; + else if (bitclk_rate_mhz >= 700 && bitclk_rate_mhz < 750) + return 0x28; + else if (bitclk_rate_mhz >= 750 && bitclk_rate_mhz < 800) + return 0x39; + else if (bitclk_rate_mhz >= 800 && bitclk_rate_mhz < 850) + return 0x09; + else if (bitclk_rate_mhz >= 850 && bitclk_rate_mhz < 900) + return 0x19; + else if (bitclk_rate_mhz >= 900 && bitclk_rate_mhz < 950) + return 0x29; + else if (bitclk_rate_mhz >= 950 && bitclk_rate_mhz < 1000) + return 0x3a; + else if (bitclk_rate_mhz >= 1000 && bitclk_rate_mhz < 1050) + return 0x0a; + else if (bitclk_rate_mhz >= 1050 && bitclk_rate_mhz < 1100) + return 0x1a; + else if (bitclk_rate_mhz >= 1100 && bitclk_rate_mhz < 1150) + return 0x2a; + else if (bitclk_rate_mhz >= 1150 && bitclk_rate_mhz < 1200) + return 0x3b; + else if (bitclk_rate_mhz >= 1200 && bitclk_rate_mhz < 1250) + return 0x0b; + else if (bitclk_rate_mhz >= 1250 && bitclk_rate_mhz < 1300) + return 0x1b; + else if (bitclk_rate_mhz >= 1300 && bitclk_rate_mhz < 1350) + return 0x2b; + else if (bitclk_rate_mhz >= 1350 && bitclk_rate_mhz < 1400) + return 0x3c; + else if (bitclk_rate_mhz >= 1400 && bitclk_rate_mhz < 1450) + return 0x0c; + else if (bitclk_rate_mhz >= 1450 && bitclk_rate_mhz < 1500) + return 0x1c; + else if (bitclk_rate_mhz >= 1500 && bitclk_rate_mhz < 1550) + return 0x2c; + else if (bitclk_rate_mhz >= 1550 && bitclk_rate_mhz < 1600) + return 0x3d; + else if (bitclk_rate_mhz >= 1600 && bitclk_rate_mhz < 1650) + return 0x0d; + else if (bitclk_rate_mhz >= 1650 && bitclk_rate_mhz < 1700) + return 0x1d; + else if (bitclk_rate_mhz >= 1700 && bitclk_rate_mhz < 1750) + return 0x2e; + else if (bitclk_rate_mhz >= 1750 && bitclk_rate_mhz < 1800) + return 0x3e; + else if (bitclk_rate_mhz >= 1800 && bitclk_rate_mhz < 1850) + return 0x0e; + else if (bitclk_rate_mhz >= 1850 && bitclk_rate_mhz < 1900) + return 0x1e; + else if (bitclk_rate_mhz >= 1900 && bitclk_rate_mhz < 1950) + return 0x2f; + else if (bitclk_rate_mhz >= 1950 && bitclk_rate_mhz < 2000) + return 0x3f; + else if (bitclk_rate_mhz >= 2000 && bitclk_rate_mhz < 2050) + return 0x0f; + else if (bitclk_rate_mhz >= 2050 && bitclk_rate_mhz < 2100) + return 0x40; + else if (bitclk_rate_mhz >= 2100 && bitclk_rate_mhz < 2150) + return 0x41; + else if (bitclk_rate_mhz >= 2150 && bitclk_rate_mhz < 2200) + return 0x42; + else if (bitclk_rate_mhz >= 2200 && bitclk_rate_mhz <= 2249) + return 0x43; + else if (bitclk_rate_mhz > 2249 && bitclk_rate_mhz < 2300) + return 0x44; + else if (bitclk_rate_mhz >= 2300 && bitclk_rate_mhz < 2350) + return 0x45; + else if (bitclk_rate_mhz >= 2350 && bitclk_rate_mhz < 2400) + return 0x46; + else if (bitclk_rate_mhz >= 2400 && bitclk_rate_mhz < 2450) + return 0x47; + else if (bitclk_rate_mhz >= 2450 && bitclk_rate_mhz < 2500) + return 0x48; + else + return 0x49; +} + +static void __mdss_dsi_get_pll_vco_cntrl(u64 target_freq, u32 post_div_mux, + u32 *vco_cntrl, u32 *cpbias_cntrl) +{ + u64 target_freq_mhz = div_u64(target_freq, 1000000); + u32 p_div = BIT(post_div_mux); + + if (p_div == 1) { + *vco_cntrl = 0x00; + *cpbias_cntrl = 0; + } else if (p_div == 2) { + *vco_cntrl = 0x30; + *cpbias_cntrl = 1; + } else if (p_div == 4) { + *vco_cntrl = 0x10; + *cpbias_cntrl = 0; + } else if (p_div == 8) { + *vco_cntrl = 0x20; + *cpbias_cntrl = 0; + } else if (p_div == 16) { + *vco_cntrl = 0x30; + *cpbias_cntrl = 0; + } else { + *vco_cntrl = 0x00; + *cpbias_cntrl = 1; + } + + if (target_freq_mhz <= 1250 && target_freq_mhz >= 1092) + *vco_cntrl = *vco_cntrl | 2; + else if (target_freq_mhz < 1092 && target_freq_mhz >= 950) + *vco_cntrl = *vco_cntrl | 3; + else if (target_freq_mhz < 950 && target_freq_mhz >= 712) + *vco_cntrl = *vco_cntrl | 1; + else if (target_freq_mhz < 712 && target_freq_mhz >= 546) + *vco_cntrl = *vco_cntrl | 2; + else if (target_freq_mhz < 546 && target_freq_mhz >= 475) + *vco_cntrl = *vco_cntrl | 3; + else if (target_freq_mhz < 475 && target_freq_mhz >= 356) + *vco_cntrl = *vco_cntrl | 1; + else if (target_freq_mhz < 356 && target_freq_mhz >= 273) + *vco_cntrl = *vco_cntrl | 2; + else if (target_freq_mhz < 273 && target_freq_mhz >= 237) + *vco_cntrl = *vco_cntrl | 3; + else if (target_freq_mhz < 237 && target_freq_mhz >= 178) + *vco_cntrl = *vco_cntrl | 1; + else if (target_freq_mhz < 178 && target_freq_mhz >= 136) + *vco_cntrl = *vco_cntrl | 2; + else if (target_freq_mhz < 136 && target_freq_mhz >= 118) + *vco_cntrl = *vco_cntrl | 3; + else if (target_freq_mhz < 118 && target_freq_mhz >= 89) + *vco_cntrl = *vco_cntrl | 1; + else if (target_freq_mhz < 89 && target_freq_mhz >= 68) + *vco_cntrl = *vco_cntrl | 2; + else if (target_freq_mhz < 68 && target_freq_mhz >= 57) + *vco_cntrl = *vco_cntrl | 3; + else if (target_freq_mhz < 57 && target_freq_mhz >= 44) + *vco_cntrl = *vco_cntrl | 1; + else + *vco_cntrl = *vco_cntrl | 2; +} + +static u32 __mdss_dsi_get_osc_freq_target(u64 target_freq) +{ + u64 target_freq_mhz = div_u64(target_freq, 1000000); + + if (target_freq_mhz <= 1000) + return 1315; + else if (target_freq_mhz > 1000 && target_freq_mhz <= 1500) + return 1839; + else + return 0; +} + +static u64 __mdss_dsi_pll_get_m_div(u64 vco_rate) +{ + return div_u64((vco_rate * 4), 19200000); +} + +static u32 __mdss_dsi_get_fsm_ovr_ctrl(u64 target_freq) +{ + u64 bitclk_rate_mhz = div_u64((target_freq * 2), 1000000); + + if (bitclk_rate_mhz > 1500 && bitclk_rate_mhz <= 2500) + return 0; + else + return BIT(6); +} + +static void mdss_dsi_pll_12nm_calc_reg(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + struct dsi_pll_param *param = &pdb->param; + u64 target_freq = 0; + + target_freq = div_u64(pll->vco_current_rate, + BIT(pdb->param.post_div_mux)); + + param->hsfreqrange = __mdss_dsi_get_hsfreqrange(target_freq); + __mdss_dsi_get_pll_vco_cntrl(target_freq, param->post_div_mux, + ¶m->vco_cntrl, ¶m->cpbias_cntrl); + param->osc_freq_target = __mdss_dsi_get_osc_freq_target(target_freq); + param->m_div = (u32) __mdss_dsi_pll_get_m_div(pll->vco_current_rate); + param->fsm_ovr_ctrl = __mdss_dsi_get_fsm_ovr_ctrl(target_freq); + param->prop_cntrl = 0x05; + param->int_cntrl = 0x00; + param->gmp_cntrl = 0x1; +} + +static u32 __mdss_dsi_get_multi_intX100(u64 vco_rate, u32 *rem) +{ + u32 reminder = 0; + u64 temp = 0; + const u32 ref_clk_rate = 19200000, quarterX100 = 25; + + temp = div_u64_rem(vco_rate, ref_clk_rate, &reminder); + temp *= 100; + + /* + * Multiplication integer needs to be floored in steps of 0.25 + * Hence multi_intX100 needs to be rounded off in steps of 25 + */ + if (reminder < (ref_clk_rate / 4)) { + *rem = reminder; + return temp; + } else if ((reminder >= (ref_clk_rate / 4)) && + reminder < (ref_clk_rate / 2)) { + *rem = (reminder - (ref_clk_rate / 4)); + return (temp + quarterX100); + } else if ((reminder >= (ref_clk_rate / 2)) && + (reminder < ((3 * ref_clk_rate) / 4))) { + *rem = (reminder - (ref_clk_rate / 2)); + return (temp + (quarterX100 * 2)); + } + + *rem = (reminder - ((3 * ref_clk_rate) / 4)); + return (temp + (quarterX100 * 3)); +} + +static u32 __calc_gcd(u32 num1, u32 num2) +{ + if (num2 != 0) + return __calc_gcd(num2, (num1 % num2)); + else + return num1; +} + +static void mdss_dsi_pll_12nm_calc_ssc(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + struct dsi_pll_param *param = &pdb->param; + u64 multi_intX100 = 0, temp = 0; + u32 temp_rem1 = 0, temp_rem2 = 0; + const u64 power_2_17 = 131072, power_2_10 = 1024; + const u32 ref_clk_rate = 19200000; + + multi_intX100 = __mdss_dsi_get_multi_intX100(pll->vco_current_rate, + &temp_rem1); + + /* Calculation for mpll_ssc_peak_i */ + temp = (multi_intX100 * pll->ssc_ppm * power_2_17); + temp = div_u64(temp, 100); /* 100 div for multi_intX100 */ + param->mpll_ssc_peak_i = + (u32) div_u64(temp, 1000000); /*10^6 for SSC PPM */ + + /* Calculation for mpll_stepsize_i */ + param->mpll_stepsize_i = (u32) div_u64((param->mpll_ssc_peak_i * + pll->ssc_freq * power_2_10), ref_clk_rate); + + /* Calculation for mpll_mint_i */ + param->mpll_mint_i = (u32) (div_u64((multi_intX100 * 4), 100) - 32); + + /* Calculation for mpll_frac_den */ + param->mpll_frac_den = (u32) div_u64(ref_clk_rate, + __calc_gcd((u32)pll->vco_current_rate, ref_clk_rate)); + + /* Calculation for mpll_frac_quot_i */ + temp = (temp_rem1 * power_2_17); + param->mpll_frac_quot_i = + (u32) div_u64_rem(temp, ref_clk_rate, &temp_rem2); + + /* Calculation for mpll_frac_rem */ + param->mpll_frac_rem = (u32) div_u64(((u64)temp_rem2 * + param->mpll_frac_den), ref_clk_rate); + + pr_debug("mpll_ssc_peak_i=%d mpll_stepsize_i=%d mpll_mint_i=%d\n", + param->mpll_ssc_peak_i, param->mpll_stepsize_i, + param->mpll_mint_i); + pr_debug("mpll_frac_den=%d mpll_frac_quot_i=%d mpll_frac_rem=%d\n", + param->mpll_frac_den, param->mpll_frac_quot_i, + param->mpll_frac_rem); +} + +static void pll_db_commit_12nm_ssc(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + void __iomem *pll_base = pll->pll_base; + struct dsi_pll_param *param = &pdb->param; + char data = 0; + + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC0, 0x27); + + data = (param->mpll_mint_i & 0xff); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC7, data); + + data = ((param->mpll_mint_i & 0xff00) >> 8); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC8, data); + + data = (param->mpll_ssc_peak_i & 0xff); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC1, data); + + data = ((param->mpll_ssc_peak_i & 0xff00) >> 8); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC2, data); + + data = ((param->mpll_ssc_peak_i & 0xf0000) >> 16); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC3, data); + + data = (param->mpll_stepsize_i & 0xff); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC4, data); + + data = ((param->mpll_stepsize_i & 0xff00) >> 8); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC5, data); + + data = ((param->mpll_stepsize_i & 0x1f0000) >> 16); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC6, data); + + data = (param->mpll_frac_quot_i & 0xff); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC10, data); + + data = ((param->mpll_frac_quot_i & 0xff00) >> 8); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC11, data); + + data = (param->mpll_frac_rem & 0xff); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC12, data); + + data = ((param->mpll_frac_rem & 0xff00) >> 8); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC13, data); + + data = (param->mpll_frac_den & 0xff); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC14, data); + + data = ((param->mpll_frac_den & 0xff00) >> 8); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC15, data); +} + +static void pll_db_commit_12nm(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + void __iomem *pll_base = pll->pll_base; + struct dsi_pll_param *param = &pdb->param; + char data = 0; + + MDSS_PLL_REG_W(pll_base, DSIPHY_CTRL0, 0x01); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_CTRL, 0x05); + MDSS_PLL_REG_W(pll_base, DSIPHY_SLEWRATE_DDL_LOOP_CTRL, 0x01); + + data = ((param->hsfreqrange & 0x7f) | BIT(7)); + MDSS_PLL_REG_W(pll_base, DSIPHY_HS_FREQ_RAN_SEL, data); + + data = ((param->vco_cntrl & 0x3f) | BIT(6)); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_VCO_CTRL, data); + + data = (param->osc_freq_target & 0x7f); + MDSS_PLL_REG_W(pll_base, DSIPHY_SLEWRATE_DDL_CYC_FRQ_ADJ_0, data); + + data = ((param->osc_freq_target & 0xf80) >> 7); + MDSS_PLL_REG_W(pll_base, DSIPHY_SLEWRATE_DDL_CYC_FRQ_ADJ_1, data); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_INPUT_LOOP_DIV_RAT_CTRL, 0x30); + + data = (param->m_div & 0x3f); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_LOOP_DIV_RATIO_0, data); + + data = ((param->m_div & 0xfc0) >> 6); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_LOOP_DIV_RATIO_1, data); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_INPUT_DIV_PLL_OVR, 0x60); + + data = (param->prop_cntrl & 0x3f); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PROP_CHRG_PUMP_CTRL, data); + + data = (param->int_cntrl & 0x3f); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_INTEG_CHRG_PUMP_CTRL, data); + + data = ((param->gmp_cntrl & 0x3) << 4); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_GMP_CTRL_DIG_TST, data); + + data = ((param->cpbias_cntrl & 0x1) << 6) | BIT(4); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_CHAR_PUMP_BIAS_CTRL, data); + + data = ((param->gp_div_mux & 0x7) << 5) | 0x5; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_CTRL, data); + + data = (param->pixel_divhf & 0x7f); + MDSS_PLL_REG_W(pll_base, DSIPHY_SSC9, data); + + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_ANA_PROG_CTRL, 0x03); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_ANA_TST_LOCK_ST_OVR_CTRL, 0x50); + MDSS_PLL_REG_W(pll_base, + DSIPHY_SLEWRATE_FSM_OVR_CTRL, param->fsm_ovr_ctrl); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PHA_ERR_CTRL_0, 0x01); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PHA_ERR_CTRL_1, 0x00); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_LOCK_FILTER, 0xff); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_UNLOCK_FILTER, 0x03); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PRO_DLY_RELOCK, 0x0c); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_LOCK_DET_MODE_SEL, 0x02); + + if (pll->ssc_en) + pll_db_commit_12nm_ssc(pll, pdb); + + pr_debug("pll:%d\n", pll->index); + wmb(); /* make sure register committed before preparing the clocks */ +} + +int pll_vco_set_rate_12nm(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + int rc = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *pll = vco->priv; + struct dsi_pll_db *pdb; + + pdb = (struct dsi_pll_db *)pll->priv; + if (!pdb) { + pr_err("pll pdb not found\n"); + rc = -EINVAL; + goto error; + } + + pr_debug("%s: ndx=%d rate=%lu\n", __func__, pll->index, rate); + + pll->vco_current_rate = rate; + pll->vco_ref_clk_rate = vco->ref_clk_rate; +error: + return rc; +} + +static unsigned long pll_vco_get_rate_12nm(struct clk_hw *hw) +{ + u64 vco_rate = 0; + u32 m_div_5_0 = 0, m_div_11_6 = 0, m_div = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + u64 ref_clk = vco->ref_clk_rate; + int rc; + struct mdss_pll_resources *pll = vco->priv; + + if (is_gdsc_disabled(pll)) + return 0; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll=%d\n", pll->index); + return rc; + } + + m_div_5_0 = MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_PLL_LOOP_DIV_RATIO_0); + m_div_5_0 &= 0x3f; + pr_debug("m_div_5_0 = 0x%x\n", m_div_5_0); + + m_div_11_6 = MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_PLL_LOOP_DIV_RATIO_1); + m_div_11_6 &= 0x3f; + pr_debug("m_div_11_6 = 0x%x\n", m_div_11_6); + + m_div = ((m_div_11_6 << 6) | (m_div_5_0)); + + vco_rate = div_u64((ref_clk * m_div), 4); + + pr_debug("returning vco rate = %lu\n", (unsigned long)vco_rate); + + mdss_pll_resource_enable(pll, false); + + return (unsigned long)vco_rate; +} + +long pll_vco_round_rate_12nm(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned long rrate = rate; + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + + if (rate < vco->min_rate) + rrate = vco->min_rate; + if (rate > vco->max_rate) + rrate = vco->max_rate; + + *parent_rate = rrate; + + return rrate; +} + +unsigned long vco_12nm_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *pll = vco->priv; + unsigned long rate = 0; + int rc; + + if (!pll && is_gdsc_disabled(pll)) { + pr_err("gdsc disabled\n"); + return 0; + } + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll=%d\n", pll->index); + return 0; + } + + if (pll_is_pll_locked_12nm(pll, true)) { + pll->handoff_resources = true; + pll->pll_on = true; + rate = pll_vco_get_rate_12nm(hw); + } else { + mdss_pll_resource_enable(pll, false); + } + + return rate; +} + +int pll_vco_prepare_12nm(struct clk_hw *hw) +{ + int rc = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *pll = vco->priv; + struct dsi_pll_db *pdb; + u32 data = 0; + + if (!pll) { + pr_err("Dsi pll resources are not available\n"); + return -EINVAL; + } + + pdb = (struct dsi_pll_db *)pll->priv; + if (!pdb) { + pr_err("No prov found\n"); + return -EINVAL; + } + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("ndx=%d Failed to enable mdss dsi pll resources\n", + pll->index); + return rc; + } + + if ((pll->vco_cached_rate != 0) + && (pll->vco_cached_rate == clk_hw_get_rate(hw))) { + rc = hw->init->ops->set_rate(hw, pll->vco_cached_rate, + pll->vco_cached_rate); + if (rc) { + pr_err("index=%d vco_set_rate failed. rc=%d\n", + pll->index, rc); + goto error; + } + } + + /* + * For cases where DSI PHY is already enabled like: + * 1.) LP-11 during static screen + * 2.) ULPS during static screen + * 3.) Boot up with cont splash enabled where PHY is programmed in LK + * Execute the Re-lock sequence to enable the DSI PLL. + */ + data = MDSS_PLL_REG_R(pll->pll_base, DSIPHY_SYS_CTRL); + if (data & BIT(7)) { + rc = dsi_pll_relock(pll); + if (rc) + goto error; + else + goto end; + } + + mdss_dsi_pll_12nm_calc_reg(pll, pdb); + if (pll->ssc_en) + mdss_dsi_pll_12nm_calc_ssc(pll, pdb); + + /* commit DSI vco */ + pll_db_commit_12nm(pll, pdb); + + rc = dsi_pll_enable(hw); + +error: + if (rc) { + mdss_pll_resource_enable(pll, false); + pr_err("ndx=%d failed to enable dsi pll\n", pll->index); + } + +end: + return rc; +} + +void pll_vco_unprepare_12nm(struct clk_hw *hw) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *pll = vco->priv; + + if (!pll) { + pr_err("Dsi pll resources are not available\n"); + return; + } + + pll->vco_cached_rate = clk_hw_get_rate(hw); + dsi_pll_disable(hw); +} + +int pll_vco_enable_12nm(struct clk_hw *hw) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *pll = vco->priv; + u32 data = 0; + + if (!pll) { + pr_err("Dsi pll resources are not available\n"); + return -EINVAL; + } + + if (!pll->pll_on) { + pr_err("DSI PLL not enabled, return\n"); + return -EINVAL; + } + + data = MDSS_PLL_REG_R(pll->pll_base, DSIPHY_SSC0); + data |= BIT(6); /* enable GP_CLK_EN */ + MDSS_PLL_REG_W(pll->pll_base, DSIPHY_SSC0, data); + wmb(); /* make sure register committed before enabling branch clocks */ + + return 0; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-12nm.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-12nm.c new file mode 100644 index 000000000000..6b63ed28d569 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-12nm.c @@ -0,0 +1,709 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include "mdss-dsi-pll.h" +#include "mdss-pll.h" +#include +#include "mdss-dsi-pll-12nm.h" + +#define VCO_DELAY_USEC 1 + +static struct regmap_config dsi_pll_12nm_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x800, +}; + +static const struct clk_ops clk_ops_vco_12nm = { + .recalc_rate = vco_12nm_recalc_rate, + .set_rate = pll_vco_set_rate_12nm, + .round_rate = pll_vco_round_rate_12nm, + .prepare = pll_vco_prepare_12nm, + .unprepare = pll_vco_unprepare_12nm, +}; + +static struct regmap_bus pclk_div_regmap_bus = { + .reg_write = pixel_div_set_div, + .reg_read = pixel_div_get_div, +}; + +static struct regmap_bus post_div_mux_regmap_bus = { + .reg_write = set_post_div_mux_sel, + .reg_read = get_post_div_mux_sel, +}; + +static struct regmap_bus gp_div_mux_regmap_bus = { + .reg_write = set_gp_mux_sel, + .reg_read = get_gp_mux_sel, +}; + +/* + * Clock tree model for generating DSI byte clock and pclk for 12nm DSI PLL + * + * + * +---------------+ + * +----------| vco_clk |----------+ + * | +---------------+ | + * | | + * | | + * | | + * +---------+---------+----+----+---------+---------+ | + * | | | | | | | + * | | | | | | | + * | | | | | | | + * +---v---+ +---v---+ +---v---+ +---v---+ +---v---+ +---v---+ | + * | DIV(1)| | DIV(2)| | DIV(4)| | DIV(8)| |DIV(16)| |DIV(32)| | + * +---+---+ +---+---+ +---+---+ +---+---+ +---+---+ +---+---+ | + * | | | | | | | + * | | +---+ +---+ | | | + * | +-----------+ | | +-----------+ | | + * +-------------------+ | | | | +-------------------+ | + * | | | | | | | + * +--v-v-v-v-v-v---+ | + * \ post_div_mux / | + * \ / | + * +-----+----+ +---------------------+ + * | | + * +------------------------+ | + * | | + * +----v----+ +---------+---------+----+----+---------+---------+ + * | DIV-4 | | | | | | | + * +----+----+ | | | | | | + * | +---v---+ +---v---+ +---v---+ +---v---+ +---v---+ +---v---+ + * | | DIV(1)| | DIV(2)| | DIV(4)| | DIV(8)| |DIV(16)| |DIV(32)| + * | +---+---+ +---+---+ +---+---+ +---+---+ +---+---+ +---+---+ + * | | | | | | | + * v | | +---+ +---+ | | + * byte_clk_src | +-----------+ | | +-----------+ | + * +-------------------+ | | | | +-------------------+ + * | | | | | | + * +--v-v-v-v-v-v---+ + * \ gp_cntrl_mux / + * \ / + * +-----+----+ + * | + * | + * +-------v-------+ + * | (DIV + 1) | + * | DIV = 0...127 | + * +-------+-------+ + * | + * | + * v + * dsi_pclk input to Clock Controller MND + */ + +static struct dsi_pll_db pll_db[DSI_PLL_MAX]; + +static struct dsi_pll_vco_clk dsi0pll_vco_clk = { + .ref_clk_rate = 19200000UL, + .min_rate = 1000000000UL, + .max_rate = 2000000000UL, + .pll_en_seq_cnt = 1, + .pll_enable_seqs[0] = dsi_pll_enable_seq_12nm, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_vco_clk", + .parent_names = (const char *[]){"bi_tcxo"}, + .num_parents = 1, + .ops = &clk_ops_vco_12nm, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct dsi_pll_vco_clk dsi1pll_vco_clk = { + .ref_clk_rate = 19200000UL, + .min_rate = 1000000000UL, + .max_rate = 2000000000UL, + .pll_en_seq_cnt = 1, + .pll_enable_seqs[0] = dsi_pll_enable_seq_12nm, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_vco_clk", + .parent_names = (const char *[]){"bi_tcxo"}, + .num_parents = 1, + .ops = &clk_ops_vco_12nm, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_fixed_factor dsi0pll_post_div1 = { + .div = 1, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_post_div1", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi0pll_post_div2 = { + .div = 2, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_post_div2", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi0pll_post_div4 = { + .div = 4, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_post_div4", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi0pll_post_div8 = { + .div = 8, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_post_div8", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi0pll_post_div16 = { + .div = 16, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_post_div16", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi0pll_post_div32 = { + .div = 32, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_post_div32", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_regmap_mux dsi0pll_post_div_mux = { + .reg = DSIPHY_PLL_VCO_CTRL, + .shift = 4, + .width = 2, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_post_div_mux", + .parent_names = (const char *[]){"dsi0pll_post_div1", + "dsi0pll_post_div2", + "dsi0pll_post_div4", + "dsi0pll_post_div8", + "dsi0pll_post_div16", + "dsi0pll_post_div32"}, + .num_parents = 6, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_fixed_factor dsi1pll_post_div1 = { + .div = 1, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_post_div1", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_post_div2 = { + .div = 2, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_post_div2", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_post_div4 = { + .div = 4, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_post_div4", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_post_div8 = { + .div = 8, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_post_div8", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_post_div16 = { + .div = 16, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_post_div16", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_post_div32 = { + .div = 32, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_post_div32", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_regmap_mux dsi1pll_post_div_mux = { + .reg = DSIPHY_PLL_VCO_CTRL, + .shift = 4, + .width = 2, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_post_div_mux", + .parent_names = (const char *[]){"dsi1pll_post_div1", + "dsi1pll_post_div2", + "dsi1pll_post_div4", + "dsi1pll_post_div8", + "dsi1pll_post_div16", + "dsi1pll_post_div32"}, + .num_parents = 6, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_fixed_factor dsi0pll_gp_div1 = { + .div = 1, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_gp_div1", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi0pll_gp_div2 = { + .div = 2, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_gp_div2", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi0pll_gp_div4 = { + .div = 4, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_gp_div4", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi0pll_gp_div8 = { + .div = 8, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_gp_div8", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi0pll_gp_div16 = { + .div = 16, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_gp_div16", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi0pll_gp_div32 = { + .div = 32, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_gp_div32", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_regmap_mux dsi0pll_gp_div_mux = { + .reg = DSIPHY_PLL_CTRL, + .shift = 5, + .width = 3, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_gp_div_mux", + .parent_names = (const char *[]){"dsi0pll_gp_div1", + "dsi0pll_gp_div2", + "dsi0pll_gp_div4", + "dsi0pll_gp_div8", + "dsi0pll_gp_div16", + "dsi0pll_gp_div32"}, + .num_parents = 6, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_fixed_factor dsi1pll_gp_div1 = { + .div = 1, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_gp_div1", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_gp_div2 = { + .div = 2, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_gp_div2", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_gp_div4 = { + .div = 4, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_gp_div4", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_gp_div8 = { + .div = 8, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_gp_div8", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_gp_div16 = { + .div = 16, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_gp_div16", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_gp_div32 = { + .div = 32, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_gp_div32", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_regmap_mux dsi1pll_gp_div_mux = { + .reg = DSIPHY_PLL_CTRL, + .shift = 5, + .width = 3, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_gp_div_mux", + .parent_names = (const char *[]){"dsi1pll_gp_div1", + "dsi1pll_gp_div2", + "dsi1pll_gp_div4", + "dsi1pll_gp_div8", + "dsi1pll_gp_div16", + "dsi1pll_gp_div32"}, + .num_parents = 6, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_regmap_div dsi0pll_pclk_src = { + .reg = DSIPHY_SSC9, + .shift = 0, + .width = 6, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_pclk_src", + .parent_names = (const char *[]){ + "dsi0pll_gp_div_mux"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_regmap_div dsi1pll_pclk_src = { + .reg = DSIPHY_SSC9, + .shift = 0, + .width = 6, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_pclk_src", + .parent_names = (const char *[]){ + "dsi1pll_gp_div_mux"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_fixed_factor dsi0pll_byte_clk_src = { + .div = 4, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_byte_clk_src", + .parent_names = (const char *[]){"dsi0pll_post_div_mux"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_byte_clk_src = { + .div = 4, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_byte_clk_src", + .parent_names = (const char *[]){"dsi1pll_post_div_mux"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + + +static struct clk_hw *mdss_dsi_pllcc_12nm[] = { + [VCO_CLK_0] = &dsi0pll_vco_clk.hw, + [POST_DIV1_0_CLK] = &dsi0pll_post_div1.hw, + [POST_DIV2_0_CLK] = &dsi0pll_post_div2.hw, + [POST_DIV4_0_CLK] = &dsi0pll_post_div4.hw, + [POST_DIV8_0_CLK] = &dsi0pll_post_div8.hw, + [POST_DIV16_0_CLK] = &dsi0pll_post_div16.hw, + [POST_DIV32_0_CLK] = &dsi0pll_post_div32.hw, + [POST_DIV_MUX_0_CLK] = &dsi0pll_post_div_mux.clkr.hw, + [GP_DIV1_0_CLK] = &dsi0pll_gp_div1.hw, + [GP_DIV2_0_CLK] = &dsi0pll_gp_div2.hw, + [GP_DIV4_0_CLK] = &dsi0pll_gp_div4.hw, + [GP_DIV8_0_CLK] = &dsi0pll_gp_div8.hw, + [GP_DIV16_0_CLK] = &dsi0pll_gp_div16.hw, + [GP_DIV32_0_CLK] = &dsi0pll_gp_div32.hw, + [GP_DIV_MUX_0_CLK] = &dsi0pll_gp_div_mux.clkr.hw, + [PCLK_SRC_MUX_0_CLK] = &dsi0pll_pclk_src.clkr.hw, + [BYTE_CLK_SRC_0_CLK] = &dsi0pll_byte_clk_src.hw, + [VCO_CLK_1] = &dsi1pll_vco_clk.hw, + [POST_DIV1_1_CLK] = &dsi1pll_post_div1.hw, + [POST_DIV2_1_CLK] = &dsi1pll_post_div2.hw, + [POST_DIV4_1_CLK] = &dsi1pll_post_div4.hw, + [POST_DIV8_1_CLK] = &dsi1pll_post_div8.hw, + [POST_DIV16_1_CLK] = &dsi1pll_post_div16.hw, + [POST_DIV32_1_CLK] = &dsi1pll_post_div32.hw, + [POST_DIV_MUX_1_CLK] = &dsi1pll_post_div_mux.clkr.hw, + [GP_DIV1_1_CLK] = &dsi1pll_gp_div1.hw, + [GP_DIV2_1_CLK] = &dsi1pll_gp_div2.hw, + [GP_DIV4_1_CLK] = &dsi1pll_gp_div4.hw, + [GP_DIV8_1_CLK] = &dsi1pll_gp_div8.hw, + [GP_DIV16_1_CLK] = &dsi1pll_gp_div16.hw, + [GP_DIV32_1_CLK] = &dsi1pll_gp_div32.hw, + [GP_DIV_MUX_1_CLK] = &dsi1pll_gp_div_mux.clkr.hw, + [PCLK_SRC_MUX_1_CLK] = &dsi1pll_pclk_src.clkr.hw, + [BYTE_CLK_SRC_1_CLK] = &dsi1pll_byte_clk_src.hw, +}; + +int dsi_pll_clock_register_12nm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0, ndx, i; + struct clk *clk; + struct clk_onecell_data *clk_data; + int num_clks = ARRAY_SIZE(mdss_dsi_pllcc_12nm); + struct regmap *rmap; + struct dsi_pll_db *pdb; + + if (!pdev || !pdev->dev.of_node || + !pll_res || !pll_res->pll_base || !pll_res->phy_base) { + pr_err("Invalid params\n"); + return -EINVAL; + } + + ndx = pll_res->index; + + if (ndx >= DSI_PLL_MAX) { + pr_err("pll index(%d) NOT supported\n", ndx); + return -EINVAL; + } + + pdb = &pll_db[ndx]; + pll_res->priv = pdb; + pll_res->vco_delay = VCO_DELAY_USEC; + pdb->pll = pll_res; + ndx++; + ndx %= DSI_PLL_MAX; + pdb->next = &pll_db[ndx]; + + clk_data = devm_kzalloc(&pdev->dev, sizeof(struct clk_onecell_data), + GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->clks = devm_kzalloc(&pdev->dev, (num_clks * + sizeof(struct clk *)), GFP_KERNEL); + if (!clk_data->clks) + return -ENOMEM; + clk_data->clk_num = num_clks; + + /* Establish client data */ + if (ndx == 0) { + rmap = devm_regmap_init(&pdev->dev, &post_div_mux_regmap_bus, + pll_res, &dsi_pll_12nm_config); + dsi0pll_post_div_mux.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &gp_div_mux_regmap_bus, + pll_res, &dsi_pll_12nm_config); + dsi0pll_gp_div_mux.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &pclk_div_regmap_bus, + pll_res, &dsi_pll_12nm_config); + dsi0pll_pclk_src.clkr.regmap = rmap; + + dsi0pll_vco_clk.priv = pll_res; + for (i = VCO_CLK_0; i <= BYTE_CLK_SRC_0_CLK; i++) { + clk = devm_clk_register(&pdev->dev, + mdss_dsi_pllcc_12nm[i]); + if (IS_ERR(clk)) { + pr_err("clk registration failed for DSI clock:%d\n", + pll_res->index); + rc = -EINVAL; + goto clk_register_fail; + } + clk_data->clks[i] = clk; + + } + + rc = of_clk_add_provider(pdev->dev.of_node, + of_clk_src_onecell_get, clk_data); + + + } else { + rmap = devm_regmap_init(&pdev->dev, &post_div_mux_regmap_bus, + pll_res, &dsi_pll_12nm_config); + dsi1pll_post_div_mux.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &gp_div_mux_regmap_bus, + pll_res, &dsi_pll_12nm_config); + dsi1pll_gp_div_mux.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &pclk_div_regmap_bus, + pll_res, &dsi_pll_12nm_config); + dsi1pll_pclk_src.clkr.regmap = rmap; + + dsi1pll_vco_clk.priv = pll_res; + + for (i = VCO_CLK_1; i <= BYTE_CLK_SRC_1_CLK; i++) { + clk = devm_clk_register(&pdev->dev, + mdss_dsi_pllcc_12nm[i]); + if (IS_ERR(clk)) { + pr_err("clk registration failed for DSI clock:%d\n", + pll_res->index); + rc = -EINVAL; + goto clk_register_fail; + } + clk_data->clks[i] = clk; + + } + + rc = of_clk_add_provider(pdev->dev.of_node, + of_clk_src_onecell_get, clk_data); + } + if (!rc) { + pr_info("Registered DSI PLL ndx=%d,clocks successfully\n", ndx); + + return rc; + } +clk_register_fail: + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-12nm.h b/drivers/clk/qcom/mdss/mdss-dsi-pll-12nm.h new file mode 100644 index 000000000000..5e53ab213e49 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-12nm.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. */ + +#ifndef MDSS_DSI_PLL_12NM_H +#define MDSS_DSI_PLL_12NM_H + +#define DSIPHY_PLL_POWERUP_CTRL 0x034 +#define DSIPHY_PLL_PROP_CHRG_PUMP_CTRL 0x038 +#define DSIPHY_PLL_INTEG_CHRG_PUMP_CTRL 0x03c +#define DSIPHY_PLL_ANA_TST_LOCK_ST_OVR_CTRL 0x044 +#define DSIPHY_PLL_VCO_CTRL 0x048 +#define DSIPHY_PLL_GMP_CTRL_DIG_TST 0x04c +#define DSIPHY_PLL_PHA_ERR_CTRL_0 0x050 +#define DSIPHY_PLL_LOCK_FILTER 0x054 +#define DSIPHY_PLL_UNLOCK_FILTER 0x058 +#define DSIPHY_PLL_INPUT_DIV_PLL_OVR 0x05c +#define DSIPHY_PLL_LOOP_DIV_RATIO_0 0x060 +#define DSIPHY_PLL_INPUT_LOOP_DIV_RAT_CTRL 0x064 +#define DSIPHY_PLL_PRO_DLY_RELOCK 0x06c +#define DSIPHY_PLL_CHAR_PUMP_BIAS_CTRL 0x070 +#define DSIPHY_PLL_LOCK_DET_MODE_SEL 0x074 +#define DSIPHY_PLL_ANA_PROG_CTRL 0x07c +#define DSIPHY_HS_FREQ_RAN_SEL 0x110 +#define DSIPHY_SLEWRATE_FSM_OVR_CTRL 0x280 +#define DSIPHY_SLEWRATE_DDL_LOOP_CTRL 0x28c +#define DSIPHY_SLEWRATE_DDL_CYC_FRQ_ADJ_0 0x290 +#define DSIPHY_PLL_PHA_ERR_CTRL_1 0x2e4 +#define DSIPHY_PLL_LOOP_DIV_RATIO_1 0x2e8 +#define DSIPHY_SLEWRATE_DDL_CYC_FRQ_ADJ_1 0x328 +#define DSIPHY_SSC0 0x394 +#define DSIPHY_SSC7 0x3b0 +#define DSIPHY_SSC8 0x3b4 +#define DSIPHY_SSC1 0x398 +#define DSIPHY_SSC2 0x39c +#define DSIPHY_SSC3 0x3a0 +#define DSIPHY_SSC4 0x3a4 +#define DSIPHY_SSC5 0x3a8 +#define DSIPHY_SSC6 0x3ac +#define DSIPHY_SSC10 0x360 +#define DSIPHY_SSC11 0x364 +#define DSIPHY_SSC12 0x368 +#define DSIPHY_SSC13 0x36c +#define DSIPHY_SSC14 0x370 +#define DSIPHY_SSC15 0x374 +#define DSIPHY_SSC7 0x3b0 +#define DSIPHY_SSC8 0x3b4 +#define DSIPHY_SSC9 0x3b8 +#define DSIPHY_STAT0 0x3e0 +#define DSIPHY_CTRL0 0x3e8 +#define DSIPHY_SYS_CTRL 0x3f0 +#define DSIPHY_PLL_CTRL 0x3f8 + +struct dsi_pll_param { + u32 hsfreqrange; + u32 vco_cntrl; + u32 osc_freq_target; + u32 m_div; + u32 prop_cntrl; + u32 int_cntrl; + u32 gmp_cntrl; + u32 cpbias_cntrl; + + /* mux and dividers */ + u32 gp_div_mux; + u32 post_div_mux; + u32 pixel_divhf; + u32 fsm_ovr_ctrl; + + /* ssc_params */ + u32 mpll_ssc_peak_i; + u32 mpll_stepsize_i; + u32 mpll_mint_i; + u32 mpll_frac_den; + u32 mpll_frac_quot_i; + u32 mpll_frac_rem; +}; + +enum { + DSI_PLL_0, + DSI_PLL_1, + DSI_PLL_MAX +}; + +struct dsi_pll_db { + struct dsi_pll_db *next; + struct mdss_pll_resources *pll; + struct dsi_pll_param param; +}; + +int pll_vco_set_rate_12nm(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate); +long pll_vco_round_rate_12nm(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate); +unsigned long vco_12nm_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate); +int pll_vco_prepare_12nm(struct clk_hw *hw); +void pll_vco_unprepare_12nm(struct clk_hw *hw); +int pll_vco_enable_12nm(struct clk_hw *hw); +int pixel_div_set_div(void *context, unsigned int reg, + unsigned int div); +int pixel_div_get_div(void *context, unsigned int reg, + unsigned int *div); +int set_post_div_mux_sel(void *context, unsigned int reg, + unsigned int sel); +int get_post_div_mux_sel(void *context, unsigned int reg, + unsigned int *sel); +int set_gp_mux_sel(void *context, unsigned int reg, + unsigned int sel); +int get_gp_mux_sel(void *context, unsigned int reg, + unsigned int *sel); +int dsi_pll_enable_seq_12nm(struct mdss_pll_resources *pll); + +#endif /* MDSS_DSI_PLL_12NM_H */ diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-28lpm.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-28lpm.c new file mode 100644 index 000000000000..0da6b4a85c68 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-28lpm.c @@ -0,0 +1,557 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2012-2018, 2021, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" +#include "mdss-dsi-pll-28nm.h" + +#define VCO_DELAY_USEC 1000 + +enum { + DSI_PLL_0, + DSI_PLL_1, + DSI_PLL_MAX +}; + +static struct lpfr_cfg lpfr_lut_struct[] = { + {479500000, 8}, + {480000000, 11}, + {575500000, 8}, + {576000000, 12}, + {610500000, 8}, + {659500000, 9}, + {671500000, 10}, + {672000000, 14}, + {708500000, 10}, + {750000000, 11}, +}; + +static void dsi_pll_sw_reset(struct mdss_pll_resources *rsc) +{ + /* + * DSI PLL software reset. Add HW recommended delays after toggling + * the software reset bit off and back on. + */ + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x01); + ndelay(500); + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x00); +} + +static void dsi_pll_toggle_lock_detect( + struct mdss_pll_resources *rsc) +{ + /* DSI PLL toggle lock detect setting */ + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x04); + ndelay(500); + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x05); + udelay(512); +} + +static int dsi_pll_check_lock_status( + struct mdss_pll_resources *rsc) +{ + int rc = 0; + + rc = dsi_pll_lock_status(rsc); + if (rc) + pr_debug("PLL Locked\n"); + else + pr_err("PLL failed to lock\n"); + + return rc; +} + + +static int dsi_pll_enable_seq_gf2(struct mdss_pll_resources *rsc) +{ + int pll_locked = 0; + + dsi_pll_sw_reset(rsc); + + /* + * GF PART 2 PLL power up sequence. + * Add necessary delays recommended by hardware. + */ + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG1, 0x04); + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05); + udelay(3); + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f); + udelay(500); + + dsi_pll_toggle_lock_detect(rsc); + + pll_locked = dsi_pll_check_lock_status(rsc); + return pll_locked ? 0 : -EINVAL; +} + +static int dsi_pll_enable_seq_gf1(struct mdss_pll_resources *rsc) +{ + int pll_locked = 0; + + dsi_pll_sw_reset(rsc); + /* + * GF PART 1 PLL power up sequence. + * Add necessary delays recommended by hardware. + */ + + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG1, 0x14); + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05); + udelay(3); + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f); + udelay(500); + + dsi_pll_toggle_lock_detect(rsc); + + pll_locked = dsi_pll_check_lock_status(rsc); + return pll_locked ? 0 : -EINVAL; +} + +static int dsi_pll_enable_seq_tsmc(struct mdss_pll_resources *rsc) +{ + int pll_locked = 0; + + dsi_pll_sw_reset(rsc); + /* + * TSMC PLL power up sequence. + * Add necessary delays recommended by hardware. + */ + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG1, 0x34); + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05); + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f); + udelay(500); + + dsi_pll_toggle_lock_detect(rsc); + + pll_locked = dsi_pll_check_lock_status(rsc); + return pll_locked ? 0 : -EINVAL; +} + +static struct regmap_config dsi_pll_28lpm_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xF4, +}; + +static struct regmap_bus analog_postdiv_regmap_bus = { + .reg_write = analog_postdiv_reg_write, + .reg_read = analog_postdiv_reg_read, +}; + +static struct regmap_bus byteclk_src_mux_regmap_bus = { + .reg_write = byteclk_mux_write_sel, + .reg_read = byteclk_mux_read_sel, +}; + +static struct regmap_bus pclk_src_regmap_bus = { + .reg_write = pixel_clk_set_div, + .reg_read = pixel_clk_get_div, +}; + +static const struct clk_ops clk_ops_vco_28lpm = { + .recalc_rate = vco_28nm_recalc_rate, + .set_rate = vco_28nm_set_rate, + .round_rate = vco_28nm_round_rate, + .prepare = vco_28nm_prepare, + .unprepare = vco_28nm_unprepare, +}; + +static struct dsi_pll_vco_clk dsi0pll_vco_clk = { + .ref_clk_rate = 19200000UL, + .min_rate = 350000000UL, + .max_rate = 750000000UL, + .pll_en_seq_cnt = 9, + .pll_enable_seqs[0] = dsi_pll_enable_seq_tsmc, + .pll_enable_seqs[1] = dsi_pll_enable_seq_tsmc, + .pll_enable_seqs[2] = dsi_pll_enable_seq_tsmc, + .pll_enable_seqs[3] = dsi_pll_enable_seq_gf1, + .pll_enable_seqs[4] = dsi_pll_enable_seq_gf1, + .pll_enable_seqs[5] = dsi_pll_enable_seq_gf1, + .pll_enable_seqs[6] = dsi_pll_enable_seq_gf2, + .pll_enable_seqs[7] = dsi_pll_enable_seq_gf2, + .pll_enable_seqs[8] = dsi_pll_enable_seq_gf2, + .lpfr_lut_size = 10, + .lpfr_lut = lpfr_lut_struct, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_vco_clk", + .parent_names = (const char *[]){"bi_tcxo"}, + .num_parents = 1, + .ops = &clk_ops_vco_28lpm, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct dsi_pll_vco_clk dsi1pll_vco_clk = { + .ref_clk_rate = 19200000UL, + .min_rate = 350000000UL, + .max_rate = 750000000UL, + .pll_en_seq_cnt = 9, + .pll_enable_seqs[0] = dsi_pll_enable_seq_tsmc, + .pll_enable_seqs[1] = dsi_pll_enable_seq_tsmc, + .pll_enable_seqs[2] = dsi_pll_enable_seq_tsmc, + .pll_enable_seqs[3] = dsi_pll_enable_seq_gf1, + .pll_enable_seqs[4] = dsi_pll_enable_seq_gf1, + .pll_enable_seqs[5] = dsi_pll_enable_seq_gf1, + .pll_enable_seqs[6] = dsi_pll_enable_seq_gf2, + .pll_enable_seqs[7] = dsi_pll_enable_seq_gf2, + .pll_enable_seqs[8] = dsi_pll_enable_seq_gf2, + .lpfr_lut_size = 10, + .lpfr_lut = lpfr_lut_struct, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_vco_clk", + .parent_names = (const char *[]){"bi_tcxo"}, + .num_parents = 1, + .ops = &clk_ops_vco_28lpm, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_regmap_div dsi0pll_analog_postdiv = { + .reg = DSI_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG, + .shift = 0, + .width = 4, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_analog_postdiv", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_regmap_div dsi1pll_analog_postdiv = { + .reg = DSI_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG, + .shift = 0, + .width = 4, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_analog_postdiv", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_fixed_factor dsi0pll_indirect_path_src = { + .div = 2, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_indirect_path_src", + .parent_names = (const char *[]){"dsi0pll_analog_postdiv"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_indirect_path_src = { + .div = 2, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_indirect_path_src", + .parent_names = (const char *[]){"dsi1pll_analog_postdiv"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_regmap_mux dsi0pll_byteclk_src_mux = { + .reg = DSI_PHY_PLL_UNIPHY_PLL_VREG_CFG, + .shift = 1, + .width = 1, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_byteclk_src_mux", + .parent_names = (const char *[]){ + "dsi0pll_vco_clk", + "dsi0pll_indirect_path_src"}, + .num_parents = 2, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_regmap_mux dsi1pll_byteclk_src_mux = { + .reg = DSI_PHY_PLL_UNIPHY_PLL_VREG_CFG, + .shift = 1, + .width = 1, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_byteclk_src_mux", + .parent_names = (const char *[]){ + "dsi1pll_vco_clk", + "dsi1pll_indirect_path_src"}, + .num_parents = 2, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_fixed_factor dsi0pll_byteclk_src = { + .div = 4, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0_phy_pll_out_byteclk", + .parent_names = (const char *[]){ + "dsi0pll_byteclk_src_mux"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_byteclk_src = { + .div = 4, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1_phy_pll_out_byteclk", + .parent_names = (const char *[]){ + "dsi1pll_byteclk_src_mux"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_regmap_div dsi0pll_pclk_src = { + .reg = DSI_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG, + .shift = 0, + .width = 8, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0_phy_pll_out_dsiclk", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_regmap_div dsi1pll_pclk_src = { + .reg = DSI_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG, + .shift = 0, + .width = 8, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1_phy_pll_out_dsiclk", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_hw *mdss_dsi_pllcc_28lpm[] = { + [VCO_CLK_0] = &dsi0pll_vco_clk.hw, + [ANALOG_POSTDIV_0_CLK] = &dsi0pll_analog_postdiv.clkr.hw, + [INDIRECT_PATH_SRC_0_CLK] = &dsi0pll_indirect_path_src.hw, + [BYTECLK_SRC_MUX_0_CLK] = &dsi0pll_byteclk_src_mux.clkr.hw, + [BYTECLK_SRC_0_CLK] = &dsi0pll_byteclk_src.hw, + [PCLK_SRC_0_CLK] = &dsi0pll_pclk_src.clkr.hw, + [VCO_CLK_1] = &dsi1pll_vco_clk.hw, + [ANALOG_POSTDIV_1_CLK] = &dsi1pll_analog_postdiv.clkr.hw, + [INDIRECT_PATH_SRC_1_CLK] = &dsi1pll_indirect_path_src.hw, + [BYTECLK_SRC_MUX_1_CLK] = &dsi1pll_byteclk_src_mux.clkr.hw, + [BYTECLK_SRC_1_CLK] = &dsi1pll_byteclk_src.hw, + [PCLK_SRC_1_CLK] = &dsi1pll_pclk_src.clkr.hw, +}; + +int dsi_pll_clock_register_28lpm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0, ndx, i; + struct clk *clk; + struct clk_onecell_data *clk_data; + int num_clks = ARRAY_SIZE(mdss_dsi_pllcc_28lpm); + struct regmap *rmap; + + int const ssc_freq_min = 30000; /* min. recommended freq. value */ + int const ssc_freq_max = 33000; /* max. recommended freq. value */ + int const ssc_ppm_max = 5000; /* max. recommended ppm */ + + if (!pdev || !pdev->dev.of_node || + !pll_res || !pll_res->pll_base) { + pr_err("Invalid params\n"); + return -EINVAL; + } + + ndx = pll_res->index; + + if (ndx >= DSI_PLL_MAX) { + pr_err("pll index(%d) NOT supported\n", ndx); + return -EINVAL; + } + + pll_res->vco_delay = VCO_DELAY_USEC; + + if (pll_res->ssc_en) { + if (!pll_res->ssc_freq || (pll_res->ssc_freq < ssc_freq_min) || + (pll_res->ssc_freq > ssc_freq_max)) { + pll_res->ssc_freq = ssc_freq_min; + pr_debug("SSC frequency out of recommended range. Set to default=%d\n", + pll_res->ssc_freq); + } + + if (!pll_res->ssc_ppm || (pll_res->ssc_ppm > ssc_ppm_max)) { + pll_res->ssc_ppm = ssc_ppm_max; + pr_debug("SSC PPM out of recommended range. Set to default=%d\n", + pll_res->ssc_ppm); + } + } + + clk_data = devm_kzalloc(&pdev->dev, sizeof(struct clk_onecell_data), + GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->clks = devm_kzalloc(&pdev->dev, (num_clks * + sizeof(struct clk *)), GFP_KERNEL); + if (!clk_data->clks) + return -ENOMEM; + clk_data->clk_num = num_clks; + + /* Establish client data */ + if (ndx == 0) { + rmap = devm_regmap_init(&pdev->dev, &byteclk_src_mux_regmap_bus, + pll_res, &dsi_pll_28lpm_config); + if (IS_ERR(rmap)) { + pr_err("regmap init failed for DSI clock:%d\n", + pll_res->index); + return -EINVAL; + } + dsi0pll_byteclk_src_mux.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &analog_postdiv_regmap_bus, + pll_res, &dsi_pll_28lpm_config); + if (IS_ERR(rmap)) { + pr_err("regmap init failed for DSI clock:%d\n", + pll_res->index); + return -EINVAL; + } + dsi0pll_analog_postdiv.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &pclk_src_regmap_bus, + pll_res, &dsi_pll_28lpm_config); + if (IS_ERR(rmap)) { + pr_err("regmap init failed for DSI clock:%d\n", + pll_res->index); + return -EINVAL; + } + dsi0pll_pclk_src.clkr.regmap = rmap; + + dsi0pll_vco_clk.priv = pll_res; + for (i = VCO_CLK_0; i <= PCLK_SRC_0_CLK; i++) { + clk = devm_clk_register(&pdev->dev, + mdss_dsi_pllcc_28lpm[i]); + if (IS_ERR(clk)) { + pr_err("clk registration failed for DSI clock:%d\n", + pll_res->index); + rc = -EINVAL; + goto clk_register_fail; + } + clk_data->clks[i] = clk; + + } + + rc = of_clk_add_provider(pdev->dev.of_node, + of_clk_src_onecell_get, clk_data); + + } else { + rmap = devm_regmap_init(&pdev->dev, &byteclk_src_mux_regmap_bus, + pll_res, &dsi_pll_28lpm_config); + if (IS_ERR(rmap)) { + pr_err("regmap init failed for DSI clock:%d\n", + pll_res->index); + return -EINVAL; + } + dsi1pll_byteclk_src_mux.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &analog_postdiv_regmap_bus, + pll_res, &dsi_pll_28lpm_config); + if (IS_ERR(rmap)) { + pr_err("regmap init failed for DSI clock:%d\n", + pll_res->index); + return -EINVAL; + } + dsi1pll_analog_postdiv.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &pclk_src_regmap_bus, + pll_res, &dsi_pll_28lpm_config); + if (IS_ERR(rmap)) { + pr_err("regmap init failed for DSI clock:%d\n", + pll_res->index); + return -EINVAL; + } + dsi1pll_pclk_src.clkr.regmap = rmap; + + dsi1pll_vco_clk.priv = pll_res; + for (i = VCO_CLK_1; i <= PCLK_SRC_1_CLK; i++) { + clk = devm_clk_register(&pdev->dev, + mdss_dsi_pllcc_28lpm[i]); + if (IS_ERR(clk)) { + pr_err("clk registration failed for DSI clock:%d\n", + pll_res->index); + rc = -EINVAL; + goto clk_register_fail; + } + clk_data->clks[i] = clk; + + } + + rc = of_clk_add_provider(pdev->dev.of_node, + of_clk_src_onecell_get, clk_data); + } + if (!rc) { + pr_info("Registered DSI PLL ndx=%d,clocks successfully\n", ndx); + return rc; + } + +clk_register_fail: + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-28nm-util.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-28nm-util.c new file mode 100644 index 000000000000..b7071ddd2fb8 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-28nm-util.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2012-2018, 2021, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" +#include "mdss-dsi-pll-28nm.h" + +#define DSI_PHY_PLL_UNIPHY_PLL_REFCLK_CFG (0x0) +#define DSI_PHY_PLL_UNIPHY_PLL_CHGPUMP_CFG (0x0008) +#define DSI_PHY_PLL_UNIPHY_PLL_VCOLPF_CFG (0x000C) +#define DSI_PHY_PLL_UNIPHY_PLL_PWRGEN_CFG (0x0014) +#define DSI_PHY_PLL_UNIPHY_PLL_POSTDIV2_CFG (0x0024) +#define DSI_PHY_PLL_UNIPHY_PLL_LPFR_CFG (0x002C) +#define DSI_PHY_PLL_UNIPHY_PLL_LPFC1_CFG (0x0030) +#define DSI_PHY_PLL_UNIPHY_PLL_LPFC2_CFG (0x0034) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG0 (0x0038) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG1 (0x003C) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG2 (0x0040) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG3 (0x0044) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG4 (0x0048) +#define DSI_PHY_PLL_UNIPHY_PLL_SSC_CFG0 (0x004C) +#define DSI_PHY_PLL_UNIPHY_PLL_SSC_CFG1 (0x0050) +#define DSI_PHY_PLL_UNIPHY_PLL_SSC_CFG2 (0x0054) +#define DSI_PHY_PLL_UNIPHY_PLL_SSC_CFG3 (0x0058) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG0 (0x006C) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG2 (0x0074) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG3 (0x0078) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG4 (0x007C) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG5 (0x0080) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG6 (0x0084) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG7 (0x0088) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG8 (0x008C) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG9 (0x0090) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG10 (0x0094) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG11 (0x0098) +#define DSI_PHY_PLL_UNIPHY_PLL_EFUSE_CFG (0x009C) +#define DSI_PHY_PLL_UNIPHY_PLL_STATUS (0x00C0) + +#define DSI_PLL_POLL_DELAY_US 50 +#define DSI_PLL_POLL_TIMEOUT_US 500 + +int analog_postdiv_reg_read(void *context, unsigned int reg, + unsigned int *div) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + *div = MDSS_PLL_REG_R(rsc->pll_base, reg); + + pr_debug("analog_postdiv div = %d\n", *div); + + (void)mdss_pll_resource_enable(rsc, false); + return rc; +} + +int analog_postdiv_reg_write(void *context, unsigned int reg, + unsigned int div) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + pr_debug("analog_postdiv div = %d\n", div); + + MDSS_PLL_REG_W(rsc->pll_base, reg, div); + + (void)mdss_pll_resource_enable(rsc, false); + return rc; +} + +int byteclk_mux_read_sel(void *context, unsigned int reg, + unsigned int *val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + *val = (MDSS_PLL_REG_R(rsc->pll_base, reg) & BIT(1)); + pr_debug("byteclk mux mode = %s\n", *val ? "indirect" : "direct"); + + (void)mdss_pll_resource_enable(rsc, false); + return rc; +} + +int byteclk_mux_write_sel(void *context, unsigned int reg, + unsigned int val) +{ + int rc = 0; + u32 reg_val = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + pr_debug("byteclk mux set to %s mode\n", val ? "indirect" : "direct"); + + reg_val = MDSS_PLL_REG_R(rsc->pll_base, reg); + reg_val &= ~0x02; + reg_val |= val; + + MDSS_PLL_REG_W(rsc->pll_base, reg, reg_val); + + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +int pixel_clk_get_div(void *context, unsigned int reg, + unsigned int *div) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + *div = MDSS_PLL_REG_R(rsc->pll_base, reg); + + pr_debug("pclk_src div = %d\n", *div); + + (void)mdss_pll_resource_enable(rsc, false); + return rc; +} + +int pixel_clk_set_div(void *context, unsigned int reg, + unsigned int div) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + pr_debug("pclk_src div = %d\n", div); + + MDSS_PLL_REG_W(rsc->pll_base, reg, div); + + (void)mdss_pll_resource_enable(rsc, false); + return rc; +} + +int dsi_pll_lock_status(struct mdss_pll_resources *rsc) +{ + u32 status; + int pll_locked; + + /* poll for PLL ready status */ + if (readl_poll_timeout_atomic((rsc->pll_base + + DSI_PHY_PLL_UNIPHY_PLL_STATUS), + status, + ((status & BIT(0)) == 1), + DSI_PLL_POLL_DELAY_US, + DSI_PLL_POLL_TIMEOUT_US)) { + pr_debug("DSI PLL status=%x failed to Lock\n", status); + pll_locked = 0; + } else { + pll_locked = 1; + } + + return pll_locked; +} + +static int pll_28nm_vco_rate_calc(struct dsi_pll_vco_clk *vco, + struct mdss_dsi_vco_calc *vco_calc, unsigned long vco_clk_rate) +{ + s32 rem; + s64 frac_n_mode, ref_doubler_en_b; + s64 ref_clk_to_pll, div_fb, frac_n_value; + int i; + + /* Configure the Loop filter resistance */ + for (i = 0; i < vco->lpfr_lut_size; i++) + if (vco_clk_rate <= vco->lpfr_lut[i].vco_rate) + break; + if (i == vco->lpfr_lut_size) { + pr_err("unable to get loop filter resistance. vco=%ld\n", + vco_clk_rate); + return -EINVAL; + } + vco_calc->lpfr_lut_res = vco->lpfr_lut[i].r; + + div_s64_rem(vco_clk_rate, vco->ref_clk_rate, &rem); + if (rem) { + vco_calc->refclk_cfg = 0x1; + frac_n_mode = 1; + ref_doubler_en_b = 0; + } else { + vco_calc->refclk_cfg = 0x0; + frac_n_mode = 0; + ref_doubler_en_b = 1; + } + + pr_debug("refclk_cfg = %lld\n", vco_calc->refclk_cfg); + + ref_clk_to_pll = ((vco->ref_clk_rate * 2 * (vco_calc->refclk_cfg)) + + (ref_doubler_en_b * vco->ref_clk_rate)); + + div_fb = div_s64_rem(vco_clk_rate, ref_clk_to_pll, &rem); + frac_n_value = div_s64(((s64)rem * (1 << 16)), ref_clk_to_pll); + vco_calc->gen_vco_clk = vco_clk_rate; + + pr_debug("ref_clk_to_pll = %lld\n", ref_clk_to_pll); + pr_debug("div_fb = %lld\n", div_fb); + pr_debug("frac_n_value = %lld\n", frac_n_value); + + pr_debug("Generated VCO Clock: %lld\n", vco_calc->gen_vco_clk); + rem = 0; + if (frac_n_mode) { + vco_calc->sdm_cfg0 = 0; + vco_calc->sdm_cfg1 = (div_fb & 0x3f) - 1; + vco_calc->sdm_cfg3 = div_s64_rem(frac_n_value, 256, &rem); + vco_calc->sdm_cfg2 = rem; + } else { + vco_calc->sdm_cfg0 = (0x1 << 5); + vco_calc->sdm_cfg0 |= (div_fb & 0x3f) - 1; + vco_calc->sdm_cfg1 = 0; + vco_calc->sdm_cfg2 = 0; + vco_calc->sdm_cfg3 = 0; + } + + pr_debug("sdm_cfg0=%lld\n", vco_calc->sdm_cfg0); + pr_debug("sdm_cfg1=%lld\n", vco_calc->sdm_cfg1); + pr_debug("sdm_cfg2=%lld\n", vco_calc->sdm_cfg2); + pr_debug("sdm_cfg3=%lld\n", vco_calc->sdm_cfg3); + + vco_calc->cal_cfg11 = div_s64_rem(vco_calc->gen_vco_clk, + 256 * 1000000, &rem); + vco_calc->cal_cfg10 = rem / 1000000; + pr_debug("cal_cfg10=%lld, cal_cfg11=%lld\n", + vco_calc->cal_cfg10, vco_calc->cal_cfg11); + + return 0; +} + +static void pll_28nm_ssc_param_calc(struct dsi_pll_vco_clk *vco, + struct mdss_dsi_vco_calc *vco_calc) +{ + struct mdss_pll_resources *rsc = vco->priv; + s64 ppm_freq, incr, spread_freq, div_rf, frac_n_value; + s32 rem; + + if (!rsc->ssc_en) { + pr_debug("DSI PLL SSC not enabled\n"); + return; + } + + vco_calc->ssc.kdiv = DIV_ROUND_CLOSEST(vco->ref_clk_rate, + 1000000) - 1; + vco_calc->ssc.triang_steps = DIV_ROUND_CLOSEST(vco->ref_clk_rate, + rsc->ssc_freq * (vco_calc->ssc.kdiv + 1)); + ppm_freq = div_s64(vco_calc->gen_vco_clk * rsc->ssc_ppm, + 1000000); + incr = div64_s64(ppm_freq * 65536, vco->ref_clk_rate * 2 * + vco_calc->ssc.triang_steps); + + vco_calc->ssc.triang_inc_7_0 = incr & 0xff; + vco_calc->ssc.triang_inc_9_8 = (incr >> 8) & 0x3; + + if (!rsc->ssc_center) + spread_freq = vco_calc->gen_vco_clk - ppm_freq; + else + spread_freq = vco_calc->gen_vco_clk - (ppm_freq / 2); + + div_rf = div_s64(spread_freq, 2 * vco->ref_clk_rate); + vco_calc->ssc.dc_offset = (div_rf - 1); + + div_s64_rem(spread_freq, 2 * vco->ref_clk_rate, &rem); + frac_n_value = div_s64((s64)rem * 65536, 2 * vco->ref_clk_rate); + + vco_calc->ssc.freq_seed_7_0 = frac_n_value & 0xff; + vco_calc->ssc.freq_seed_15_8 = (frac_n_value >> 8) & 0xff; +} + +static void pll_28nm_vco_config(struct dsi_pll_vco_clk *vco, + struct mdss_dsi_vco_calc *vco_calc) +{ + struct mdss_pll_resources *rsc = vco->priv; + void __iomem *pll_base = rsc->pll_base; + u32 vco_delay_us = rsc->vco_delay; + bool ssc_en = rsc->ssc_en; + + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_LPFR_CFG, + vco_calc->lpfr_lut_res); + + /* Loop filter capacitance values : c1 and c2 */ + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_LPFC1_CFG, 0x70); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_LPFC2_CFG, 0x15); + + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_CHGPUMP_CFG, 0x02); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG3, 0x2b); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG4, 0x66); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x0d); + + if (!ssc_en) { + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG1, + (u32)(vco_calc->sdm_cfg1 & 0xff)); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG2, + (u32)(vco_calc->sdm_cfg2 & 0xff)); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG3, + (u32)(vco_calc->sdm_cfg3 & 0xff)); + } else { + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG1, + (u32)vco_calc->ssc.dc_offset); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG2, + (u32)vco_calc->ssc.freq_seed_7_0); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG3, + (u32)vco_calc->ssc.freq_seed_15_8); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_SSC_CFG0, + (u32)vco_calc->ssc.kdiv); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_SSC_CFG1, + (u32)vco_calc->ssc.triang_inc_7_0); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_SSC_CFG2, + (u32)vco_calc->ssc.triang_inc_9_8); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_SSC_CFG3, + (u32)vco_calc->ssc.triang_steps); + } + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG4, 0x00); + + /* Add hardware recommended delay for correct PLL configuration */ + if (vco_delay_us) + udelay(vco_delay_us); + + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_REFCLK_CFG, + (u32)vco_calc->refclk_cfg); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_PWRGEN_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_VCOLPF_CFG, 0x71); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG0, + (u32)vco_calc->sdm_cfg0); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG0, 0x12); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG6, 0x30); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG7, 0x00); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG10, + (u32)(vco_calc->cal_cfg10 & 0xff)); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG11, + (u32)(vco_calc->cal_cfg11 & 0xff)); + MDSS_PLL_REG_W(pll_base, DSI_PHY_PLL_UNIPHY_PLL_EFUSE_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV2_CFG, 0x3); /* Fixed div-4 */ +} + +static int vco_set_rate(struct dsi_pll_vco_clk *vco, unsigned long rate) +{ + struct mdss_dsi_vco_calc vco_calc = {0}; + int rc = 0; + + rc = pll_28nm_vco_rate_calc(vco, &vco_calc, rate); + if (rc) { + pr_err("vco rate calculation failed\n"); + return rc; + } + + pll_28nm_ssc_param_calc(vco, &vco_calc); + pll_28nm_vco_config(vco, &vco_calc); + + return 0; +} + +static unsigned long vco_get_rate(struct dsi_pll_vco_clk *vco) +{ + struct mdss_pll_resources *rsc = vco->priv; + int rc; + u32 sdm0, doubler, sdm_byp_div; + u64 vco_rate; + u32 sdm_dc_off, sdm_freq_seed, sdm2, sdm3; + u64 ref_clk = vco->ref_clk_rate; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + /* Check to see if the ref clk doubler is enabled */ + doubler = MDSS_PLL_REG_R(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_REFCLK_CFG) & BIT(0); + ref_clk += (doubler * vco->ref_clk_rate); + + /* see if it is integer mode or sdm mode */ + sdm0 = MDSS_PLL_REG_R(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG0); + if (sdm0 & BIT(6)) { + /* integer mode */ + sdm_byp_div = (MDSS_PLL_REG_R(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG0) & 0x3f) + 1; + vco_rate = ref_clk * sdm_byp_div; + } else { + /* sdm mode */ + sdm_dc_off = MDSS_PLL_REG_R(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG1) & 0xFF; + pr_debug("sdm_dc_off = %d\n", sdm_dc_off); + sdm2 = MDSS_PLL_REG_R(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG2) & 0xFF; + sdm3 = MDSS_PLL_REG_R(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG3) & 0xFF; + sdm_freq_seed = (sdm3 << 8) | sdm2; + pr_debug("sdm_freq_seed = %d\n", sdm_freq_seed); + + vco_rate = (ref_clk * (sdm_dc_off + 1)) + + mult_frac(ref_clk, sdm_freq_seed, BIT(16)); + pr_debug("vco rate = %lld\n", vco_rate); + } + + pr_debug("returning vco rate = %lu\n", (unsigned long)vco_rate); + + mdss_pll_resource_enable(rsc, false); + + return (unsigned long)vco_rate; +} + +static int dsi_pll_enable(struct dsi_pll_vco_clk *vco) +{ + int i, rc; + struct mdss_pll_resources *rsc = vco->priv; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("failed to enable dsi pll(%d) resources\n", + rsc->index); + return rc; + } + + /* Try all enable sequences until one succeeds */ + for (i = 0; i < vco->pll_en_seq_cnt; i++) { + rc = vco->pll_enable_seqs[i](rsc); + pr_debug("DSI PLL %s after sequence #%d\n", + rc ? "unlocked" : "locked", i + 1); + if (!rc) + break; + } + + if (rc) { + mdss_pll_resource_enable(rsc, false); + pr_err("DSI PLL failed to lock\n"); + } + rsc->pll_on = true; + + return rc; +} + +static void dsi_pll_disable(struct dsi_pll_vco_clk *vco) +{ + struct mdss_pll_resources *rsc = vco->priv; + + if (!rsc->pll_on && + mdss_pll_resource_enable(rsc, true)) { + pr_err("failed to enable dsi pll(%d) resources\n", + rsc->index); + return; + } + + rsc->handoff_resources = false; + + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x00); + + mdss_pll_resource_enable(rsc, false); + rsc->pll_on = false; + + pr_debug("DSI PLL Disabled\n"); +} + +int vco_28nm_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *rsc = vco->priv; + int rc; + + if (!rsc) { + pr_err("pll resource not found\n"); + return -EINVAL; + } + + if (rsc->pll_on) + return 0; + + pr_debug("ndx=%d, rate=%lu\n", rsc->index, rate); + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("failed to enable mdss dsi pll(%d), rc=%d\n", + rsc->index, rc); + return rc; + } + + /* + * DSI PLL software reset. Add HW recommended delays after toggling + * the software reset bit off and back on. + */ + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x01); + udelay(1000); + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x00); + udelay(1000); + + rc = vco_set_rate(vco, rate); + rsc->vco_current_rate = rate; + + mdss_pll_resource_enable(rsc, false); + + return 0; +} + +long vco_28nm_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned long rrate = rate; + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + + if (rate < vco->min_rate) + rrate = vco->min_rate; + if (rate > vco->max_rate) + rrate = vco->max_rate; + + *parent_rate = rrate; + + return rrate; +} + +unsigned long vco_28nm_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *rsc = vco->priv; + int rc; + u64 vco_rate = 0; + + if (!rsc) { + pr_err("dsi pll resources not available\n"); + return 0; + } + + if (rsc->vco_current_rate) + return (unsigned long)rsc->vco_current_rate; + + if (is_gdsc_disabled(rsc)) + return 0; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("failed to enable dsi pll(%d) resources\n", + rsc->index); + return 0; + } + + if (dsi_pll_lock_status(rsc)) { + rsc->handoff_resources = true; + rsc->cont_splash_enabled = true; + rsc->pll_on = true; + vco_rate = vco_get_rate(vco); + } else { + mdss_pll_resource_enable(rsc, false); + } + + return (unsigned long)vco_rate; +} + +int vco_28nm_prepare(struct clk_hw *hw) +{ + int rc = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *rsc = vco->priv; + + if (!rsc) { + pr_err("dsi pll resources not available\n"); + return -EINVAL; + } + + if ((rsc->vco_cached_rate != 0) + && (rsc->vco_cached_rate == clk_hw_get_rate(hw))) { + rc = hw->init->ops->set_rate(hw, rsc->vco_cached_rate, + rsc->vco_cached_rate); + if (rc) { + pr_err("pll(%d ) set_rate failed. rc=%d\n", + rsc->index, rc); + goto error; + } + + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG, + rsc->cached_postdiv1); + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG, + rsc->cached_postdiv3); + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_VREG_CFG, + rsc->cached_vreg_cfg); + } else if (!rsc->handoff_resources && rsc->cont_splash_enabled) { + MDSS_PLL_REG_W(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_VREG_CFG, + rsc->cached_vreg_cfg); + } + + rc = dsi_pll_enable(vco); + +error: + return rc; +} + +void vco_28nm_unprepare(struct clk_hw *hw) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *rsc = vco->priv; + + if (!rsc) { + pr_err("dsi pll resources not available\n"); + return; + } + + rsc->cached_postdiv1 = MDSS_PLL_REG_R(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG); + rsc->cached_postdiv3 = MDSS_PLL_REG_R(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG); + rsc->cached_vreg_cfg = MDSS_PLL_REG_R(rsc->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_VREG_CFG); + + rsc->vco_cached_rate = clk_hw_get_rate(hw); + + dsi_pll_disable(vco); +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-28nm.h b/drivers/clk/qcom/mdss/mdss-dsi-pll-28nm.h new file mode 100644 index 000000000000..1924c38e84ab --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-28nm.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2012-2018, 2021, 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. + * + */ + +#ifndef __MDSS_DSI_PLL_28NM_H +#define __MDSS_DSI_PLL_28NM_H + +#define DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG (0x0020) +#define DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2 (0x0064) +#define DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG (0x0068) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG1 (0x0070) + +#define DSI_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG (0x0004) +#define DSI_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG (0x0028) +#define DSI_PHY_PLL_UNIPHY_PLL_VREG_CFG (0x0010) + +struct ssc_params { + s32 kdiv; + s64 triang_inc_7_0; + s64 triang_inc_9_8; + s64 triang_steps; + s64 dc_offset; + s64 freq_seed_7_0; + s64 freq_seed_15_8; +}; + +struct mdss_dsi_vco_calc { + s64 sdm_cfg0; + s64 sdm_cfg1; + s64 sdm_cfg2; + s64 sdm_cfg3; + s64 cal_cfg10; + s64 cal_cfg11; + s64 refclk_cfg; + s64 gen_vco_clk; + u32 lpfr_lut_res; + struct ssc_params ssc; +}; + +unsigned long vco_28nm_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate); +int vco_28nm_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate); +long vco_28nm_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate); +int vco_28nm_prepare(struct clk_hw *hw); +void vco_28nm_unprepare(struct clk_hw *hw); + +int analog_postdiv_reg_write(void *context, + unsigned int reg, unsigned int div); +int analog_postdiv_reg_read(void *context, + unsigned int reg, unsigned int *div); +int byteclk_mux_write_sel(void *context, + unsigned int reg, unsigned int val); +int byteclk_mux_read_sel(void *context, + unsigned int reg, unsigned int *val); +int pixel_clk_set_div(void *context, + unsigned int reg, unsigned int div); +int pixel_clk_get_div(void *context, + unsigned int reg, unsigned int *div); + +int dsi_pll_lock_status(struct mdss_pll_resources *rsc); +#endif /* __MDSS_DSI_PLL_28NM_H */ diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll.h b/drivers/clk/qcom/mdss/mdss-dsi-pll.h index 151f3f2c75ba..80d9c2431ecf 100644 --- a/drivers/clk/qcom/mdss/mdss-dsi-pll.h +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (c) 2012-2018, 2020 The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2012-2018, 2020-2021, The Linux Foundation. All rights reserved. */ #ifndef __MDSS_DSI_PLL_H #define __MDSS_DSI_PLL_H @@ -39,4 +39,8 @@ static inline struct dsi_pll_vco_clk *to_vco_clk_hw(struct clk_hw *hw) int dsi_pll_clock_register_14nm(struct platform_device *pdev, struct mdss_pll_resources *pll_res); +int dsi_pll_clock_register_28lpm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); +int dsi_pll_clock_register_12nm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); #endif diff --git a/drivers/clk/qcom/mdss/mdss-pll.c b/drivers/clk/qcom/mdss/mdss-pll.c index c7766f1a96cd..ab9589f686ba 100644 --- a/drivers/clk/qcom/mdss/mdss-pll.c +++ b/drivers/clk/qcom/mdss/mdss-pll.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2013-2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "%s: " fmt, __func__ @@ -128,7 +128,9 @@ static int mdss_pll_resource_parse(struct platform_device *pdev, pll_res->pll_interface_type = MDSS_DP_PLL_14NM; pll_res->target_id = MDSS_PLL_TARGET_SDM660; pll_res->revision = 2; - } else + } else if (!strcmp(compatible_stream, "qcom,mdss_dsi_pll_28lpm")) + pll_res->pll_interface_type = MDSS_DSI_PLL_28LPM; + else goto err; return rc; @@ -156,6 +158,12 @@ static int mdss_pll_clock_register(struct platform_device *pdev, case MDSS_DP_PLL_14NM: rc = dp_pll_clock_register_14nm(pdev, pll_res); break; + case MDSS_DSI_PLL_28LPM: + rc = dsi_pll_clock_register_28lpm(pdev, pll_res); + break; + case MDSS_DSI_PLL_12NM: + rc = dsi_pll_clock_register_12nm(pdev, pll_res); + break; case MDSS_UNKNOWN_PLL: default: rc = -EINVAL; @@ -386,6 +394,8 @@ static const struct of_device_id mdss_pll_dt_match[] = { {.compatible = "qcom,mdss_dp_pll_14nm"}, {.compatible = "qcom,mdss_dsi_pll_sdm660"}, {.compatible = "qcom,mdss_dp_pll_sdm660"}, + {.compatible = "qcom,mdss_dsi_pll_12nm"}, + {.compatible = "qcom,mdss_dsi_pll_28lpm"}, {} }; diff --git a/drivers/clk/qcom/mdss/mdss-pll.h b/drivers/clk/qcom/mdss/mdss-pll.h index 91897139f4dd..2a54194d50a6 100644 --- a/drivers/clk/qcom/mdss/mdss-pll.h +++ b/drivers/clk/qcom/mdss/mdss-pll.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2013-2021, The Linux Foundation. All rights reserved. */ #ifndef __MDSS_PLL_H #define __MDSS_PLL_H @@ -146,6 +146,7 @@ struct mdss_pll_resources { * feature is disabled. */ bool handoff_resources; + bool cont_splash_enabled; /* * caching the pll trim codes in the case of dynamic refresh @@ -218,7 +219,8 @@ static inline bool is_gdsc_disabled(struct mdss_pll_resources *pll_res) WARN(1, "gdsc_base register is not defined\n"); return true; } - if (pll_res->target_id == MDSS_PLL_TARGET_SDM660) + if ((pll_res->target_id == MDSS_PLL_TARGET_SDM660) || + (pll_res->pll_interface_type == MDSS_DSI_PLL_28LPM)) ret = ((readl_relaxed(pll_res->gdsc_base + 0x4) & BIT(31)) && (!(readl_relaxed(pll_res->gdsc_base) & BIT(0)))) ? false : true; else diff --git a/drivers/clk/qcom/vdd-level-cpu.h b/drivers/clk/qcom/vdd-level-cpu.h new file mode 100644 index 000000000000..f01b8c8c7016 --- /dev/null +++ b/drivers/clk/qcom/vdd-level-cpu.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __DRIVERS_CLK_QCOM_VDD_LEVEL_CPU_H +#define __DRIVERS_CLK_QCOM_VDD_LEVEL_CPU_H + +#include +#include + +enum vdd_hf_pll_levels { + VDD_HF_PLL_OFF, + VDD_HF_PLL_SVS, + VDD_HF_PLL_NOM, + VDD_HF_PLL_TUR, + VDD_HF_PLL_NUM, +}; + +static int vdd_hf_levels[] = { + 0, RPM_REGULATOR_LEVEL_NONE, /* VDD_HF_PLL_OFF */ + 1800000, RPM_REGULATOR_LEVEL_SVS, /* VDD_HF_PLL_SVS */ + 1800000, RPM_REGULATOR_LEVEL_NOM, /* VDD_HF_PLL_NOM */ + 1800000, RPM_REGULATOR_LEVEL_TURBO, /* VDD_HF_PLL_TUR */ +}; + +#endif diff --git a/drivers/clk/qcom/vdd-level-sdm429w.h b/drivers/clk/qcom/vdd-level-sdm429w.h new file mode 100644 index 000000000000..038ee657b6f4 --- /dev/null +++ b/drivers/clk/qcom/vdd-level-sdm429w.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __DRIVERS_CLK_QCOM_VDD_LEVEL_SDM429W_H +#define __DRIVERS_CLK_QCOM_VDD_LEVEL_SDM429W_H + +#include +#include + +enum vdd_levels { + VDD_NONE, + VDD_LOW, + VDD_LOW_L1, + VDD_NOMINAL, + VDD_NOMINAL_L1, + VDD_HIGH, + VDD_NUM +}; + +static int vdd_corner[] = { + RPM_REGULATOR_LEVEL_NONE, /* VDD_NONE */ + RPM_REGULATOR_LEVEL_SVS, /* VDD_SVS */ + RPM_REGULATOR_LEVEL_SVS_PLUS, /* VDD_SVS_PLUS */ + RPM_REGULATOR_LEVEL_NOM, /* VDD_NOM */ + RPM_REGULATOR_LEVEL_NOM_PLUS, /* VDD_NOM_PLUS */ + RPM_REGULATOR_LEVEL_TURBO, /* VDD_TURBO */ +}; + +#endif diff --git a/drivers/cpufreq/cpu-boost.c b/drivers/cpufreq/cpu-boost.c index 6ffac4fd031a..cb07beebd3b8 100644 --- a/drivers/cpufreq/cpu-boost.c +++ b/drivers/cpufreq/cpu-boost.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2013-2015,2017,2019, The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #define pr_fmt(fmt) "cpu-boost: " fmt @@ -41,16 +40,13 @@ struct cpu_sync { int cpu; unsigned int input_boost_min; unsigned int input_boost_freq; - unsigned int powerkey_input_boost_freq; }; - static DEFINE_PER_CPU(struct cpu_sync, sync_info); static struct workqueue_struct *cpu_boost_wq; static struct work_struct input_boost_work; -static struct work_struct powerkey_input_boost_work; static bool input_boost_enabled; static unsigned int input_boost_ms = 40; @@ -58,22 +54,11 @@ show_one(input_boost_ms); store_one(input_boost_ms); cpu_boost_attr_rw(input_boost_ms); -static unsigned int powerkey_input_boost_ms = 400; -show_one(powerkey_input_boost_ms); -store_one(powerkey_input_boost_ms); -cpu_boost_attr_rw(powerkey_input_boost_ms); - static unsigned int sched_boost_on_input; show_one(sched_boost_on_input); store_one(sched_boost_on_input); cpu_boost_attr_rw(sched_boost_on_input); - -static bool sched_boost_on_powerkey_input = true; -show_one(sched_boost_on_powerkey_input); -store_one(sched_boost_on_powerkey_input); -cpu_boost_attr_rw(sched_boost_on_powerkey_input); - static bool sched_boost_active; static struct delayed_work input_boost_rem; @@ -145,72 +130,6 @@ static ssize_t show_input_boost_freq(struct kobject *kobj, } cpu_boost_attr_rw(input_boost_freq); -static ssize_t store_powerkey_input_boost_freq(struct kobject *kobj, - struct kobj_attribute *attr, - const char *buf, size_t count) -{ - int i, ntokens = 0; - unsigned int val, cpu; - const char *cp = buf; - bool enabled = false; - - while ((cp = strpbrk(cp + 1, " :"))) - ntokens++; - - /* single number: apply to all CPUs */ - if (!ntokens) { - if (sscanf(buf, "%u\n", &val) != 1) - return -EINVAL; - for_each_possible_cpu(i) - per_cpu(sync_info, i).powerkey_input_boost_freq = val; - goto check_enable; - } - - /* CPU:value pair */ - if (!(ntokens % 2)) - return -EINVAL; - - cp = buf; - for (i = 0; i < ntokens; i += 2) { - if (sscanf(cp, "%u:%u", &cpu, &val) != 2) - return -EINVAL; - if (cpu >= num_possible_cpus()) - return -EINVAL; - per_cpu(sync_info, cpu).powerkey_input_boost_freq = val; - cp = strnchr(cp, PAGE_SIZE - (cp - buf), ' '); - cp++; - } - -check_enable: - for_each_possible_cpu(i) { - if (per_cpu(sync_info, i).powerkey_input_boost_freq) { - enabled = true; - break; - } - } - input_boost_enabled = enabled; - - return count; -} - -static ssize_t show_powerkey_input_boost_freq(struct kobject *kobj, - struct kobj_attribute *attr, char *buf) -{ - int cnt = 0, cpu; - struct cpu_sync *s; - unsigned int boost_freq = 0; - for_each_possible_cpu(cpu) { - s = &per_cpu(sync_info, cpu); - boost_freq = s->powerkey_input_boost_freq; - cnt += snprintf(buf + cnt, PAGE_SIZE - cnt, - "%d:%u ", cpu, boost_freq); - } - cnt += snprintf(buf + cnt, PAGE_SIZE - cnt, "\n"); - return cnt; -} - - -cpu_boost_attr_rw(powerkey_input_boost_freq); /* * The CPUFREQ_ADJUST notifier is used to override the current policy min to @@ -318,40 +237,6 @@ static void do_input_boost(struct work_struct *work) msecs_to_jiffies(input_boost_ms)); } -static void do_powerkey_input_boost(struct work_struct *work) -{ - - unsigned int i, ret; - struct cpu_sync *i_sync_info; - cancel_delayed_work_sync(&input_boost_rem); - if (sched_boost_active) { - sched_set_boost(0); - sched_boost_active = false; - } - - /* Set the powerkey_input_boost_min for all CPUs in the system */ - pr_debug("Setting powerkey input boost min for all CPUs\n"); - for_each_possible_cpu(i) { - i_sync_info = &per_cpu(sync_info, i); - i_sync_info->input_boost_min = i_sync_info->powerkey_input_boost_freq; - } - - /* Update policies for all online CPUs */ - update_policy_online(); - - /* Enable scheduler boost to migrate tasks to big cluster */ - if (sched_boost_on_powerkey_input) { - ret = sched_set_boost(1); - if (ret) - pr_err("cpu-boost: HMP boost enable failed\n"); - else - sched_boost_active = true; - } - - queue_delayed_work(cpu_boost_wq, &input_boost_rem, - msecs_to_jiffies(powerkey_input_boost_ms)); -} - static void cpuboost_input_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { @@ -367,11 +252,7 @@ static void cpuboost_input_event(struct input_handle *handle, if (work_pending(&input_boost_work)) return; - if (type == EV_KEY && code == KEY_POWER) { - queue_work(cpu_boost_wq, &powerkey_input_boost_work); - } else { - queue_work(cpu_boost_wq, &input_boost_work); - } + queue_work(cpu_boost_wq, &input_boost_work); last_input_time = ktime_to_us(ktime_get()); } @@ -457,7 +338,6 @@ static int cpu_boost_init(void) return -EFAULT; INIT_WORK(&input_boost_work, do_input_boost); - INIT_WORK(&powerkey_input_boost_work, do_powerkey_input_boost); INIT_DELAYED_WORK(&input_boost_rem, do_input_boost_rem); for_each_possible_cpu(cpu) { @@ -475,27 +355,15 @@ static int cpu_boost_init(void) if (ret) pr_err("Failed to create input_boost_ms node: %d\n", ret); - ret = sysfs_create_file(cpu_boost_kobj, &powerkey_input_boost_ms_attr.attr); - if (ret) - pr_err("Failed to create powerkey_input_boost_ms node: %d\n", ret); - ret = sysfs_create_file(cpu_boost_kobj, &input_boost_freq_attr.attr); if (ret) pr_err("Failed to create input_boost_freq node: %d\n", ret); - ret = sysfs_create_file(cpu_boost_kobj, &powerkey_input_boost_freq_attr.attr); - if (ret) - pr_err("Failed to create powerkey_input_boost_freq node: %d\n", ret); ret = sysfs_create_file(cpu_boost_kobj, &sched_boost_on_input_attr.attr); if (ret) pr_err("Failed to create sched_boost_on_input node: %d\n", ret); - ret = sysfs_create_file(cpu_boost_kobj, - &sched_boost_on_powerkey_input_attr.attr); - if (ret) - pr_err("Failed to create sched_boost_on_powerkey_input node: %d\n", ret); - ret = input_register_handler(&cpuboost_input_handler); return 0; } diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 7f7387213a0e..b050316480fc 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -2270,9 +2270,6 @@ static int cpufreq_set_policy(struct cpufreq_policy *policy, blocking_notifier_call_chain(&cpufreq_policy_notifier_list, CPUFREQ_INCOMPATIBLE, new_policy); - blocking_notifier_call_chain(&cpufreq_policy_notifier_list, - CPUFREQ_THERMAL, new_policy); - /* * verify the cpu speed can be set within this limit, which might be * different to the first one diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c index 841e1be624bf..e1f1331fb763 100644 --- a/drivers/cpufreq/qcom-cpufreq-hw.c +++ b/drivers/cpufreq/qcom-cpufreq-hw.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #include diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c index 0884994f772b..219704cd87eb 100644 --- a/drivers/cpuidle/cpuidle.c +++ b/drivers/cpuidle/cpuidle.c @@ -250,17 +250,17 @@ int cpuidle_enter_state(struct cpuidle_device *dev, struct cpuidle_driver *drv, if (!cpuidle_state_is_coupled(drv, index)) local_irq_enable(); + diff = ktime_us_delta(time_end, time_start); + if (diff > INT_MAX) + diff = INT_MAX; + + dev->last_residency = (int) diff; + if (entered_state >= 0) { - /* - * Update cpuidle counters - * This can be moved to within driver enter routine, + /* Update cpuidle counters */ + /* This can be moved to within driver enter routine * but that results in multiple copies of same code. */ - diff = ktime_us_delta(time_end, time_start); - if (diff > INT_MAX) - diff = INT_MAX; - - dev->last_residency = (int)diff; dev->states_usage[entered_state].time += dev->last_residency; dev->states_usage[entered_state].usage++; } else { diff --git a/drivers/cpuidle/lpm-levels.c b/drivers/cpuidle/lpm-levels.c index 2809051feec9..a87df3c9faf1 100644 --- a/drivers/cpuidle/lpm-levels.c +++ b/drivers/cpuidle/lpm-levels.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,30 @@ #define PSCI_POWER_STATE(reset) (reset << 30) #define PSCI_AFFINITY_LEVEL(lvl) ((lvl & 0x3) << 24) +enum { + MSM_LPM_LVL_DBG_SUSPEND_LIMITS = BIT(0), + MSM_LPM_LVL_DBG_IDLE_LIMITS = BIT(1), +}; + +enum debug_event { + CPU_ENTER, + CPU_EXIT, + CLUSTER_ENTER, + CLUSTER_EXIT, + CPU_HP_STARTING, + CPU_HP_DYING, +}; + +struct lpm_debug { + u64 time; + enum debug_event evt; + int cpu; + uint32_t arg1; + uint32_t arg2; + uint32_t arg3; + uint32_t arg4; +}; + static struct system_pm_ops *sys_pm_ops; @@ -83,6 +108,9 @@ static bool suspend_in_progress; static struct hrtimer lpm_hrtimer; static DEFINE_PER_CPU(struct hrtimer, histtimer); static DEFINE_PER_CPU(struct hrtimer, biastimer); +static struct lpm_debug *lpm_debug; +static phys_addr_t lpm_debug_phys; +static const int num_dbg_elements = 0x100; static void cluster_unprepare(struct lpm_cluster *cluster, const struct cpumask *cpu, int child_idx, bool from_idle, @@ -254,10 +282,38 @@ int lpm_get_latency(struct latency_level *level, uint32_t *latency) } EXPORT_SYMBOL(lpm_get_latency); +static void update_debug_pc_event(enum debug_event event, uint32_t arg1, + uint32_t arg2, uint32_t arg3, uint32_t arg4) +{ + struct lpm_debug *dbg; + int idx; + static DEFINE_SPINLOCK(debug_lock); + static int pc_event_index; + + if (!lpm_debug) + return; + + spin_lock(&debug_lock); + idx = pc_event_index++; + dbg = &lpm_debug[idx & (num_dbg_elements - 1)]; + + dbg->evt = event; + dbg->time = arch_counter_get_cntvct(); + dbg->cpu = raw_smp_processor_id(); + dbg->arg1 = arg1; + dbg->arg2 = arg2; + dbg->arg3 = arg3; + dbg->arg4 = arg4; + spin_unlock(&debug_lock); +} + static int lpm_dying_cpu(unsigned int cpu) { struct lpm_cluster *cluster = per_cpu(cpu_lpm, cpu)->parent; + update_debug_pc_event(CPU_HP_DYING, cpu, + cluster->num_children_in_sync.bits[0], + cluster->child_cpus.bits[0], false); cluster_prepare(cluster, get_cpu_mask(cpu), NR_LPM_LEVELS, false, 0); return 0; } @@ -266,6 +322,9 @@ static int lpm_starting_cpu(unsigned int cpu) { struct lpm_cluster *cluster = per_cpu(cpu_lpm, cpu)->parent; + update_debug_pc_event(CPU_HP_STARTING, cpu, + cluster->num_children_in_sync.bits[0], + cluster->child_cpus.bits[0], false); cluster_unprepare(cluster, get_cpu_mask(cpu), NR_LPM_LEVELS, false, 0, true); return 0; @@ -1048,6 +1107,9 @@ static int cluster_configure(struct lpm_cluster *cluster, int idx, return -EPERM; if (idx != cluster->default_level) { + update_debug_pc_event(CLUSTER_ENTER, idx, + cluster->num_children_in_sync.bits[0], + cluster->child_cpus.bits[0], from_idle); trace_cluster_enter(cluster->cluster_name, idx, cluster->num_children_in_sync.bits[0], cluster->child_cpus.bits[0], from_idle); @@ -1201,6 +1263,9 @@ static void cluster_unprepare(struct lpm_cluster *cluster, if (sys_pm_ops && sys_pm_ops->exit) sys_pm_ops->exit(success); + update_debug_pc_event(CLUSTER_EXIT, cluster->last_level, + cluster->num_children_in_sync.bits[0], + cluster->child_cpus.bits[0], from_idle); trace_cluster_exit(cluster->cluster_name, cluster->last_level, cluster->num_children_in_sync.bits[0], cluster->child_cpus.bits[0], from_idle); @@ -1312,11 +1377,15 @@ static bool psci_enter_sleep(struct lpm_cpu *cpu, int idx, bool from_idle) affinity_level = PSCI_AFFINITY_LEVEL(affinity_level); state_id += power_state + affinity_level + cpu->levels[idx].psci_id; + update_debug_pc_event(CPU_ENTER, state_id, + 0xdeaffeed, 0xdeaffeed, from_idle); stop_critical_timings(); success = !arm_cpuidle_suspend(state_id); start_critical_timings(); + update_debug_pc_event(CPU_EXIT, state_id, + success, 0xdeaffeed, from_idle); if (from_idle && cpu->levels[idx].use_bc_timer) tick_broadcast_exit(); @@ -1704,9 +1773,11 @@ static const struct platform_s2idle_ops lpm_s2idle_ops = { static int lpm_probe(struct platform_device *pdev) { int ret; + int size; unsigned int cpu; struct hrtimer *cpu_histtimer; struct kobject *module_kobj = NULL; + struct md_region md_entry; get_online_cpus(); lpm_root_node = lpm_of_parse_cluster(pdev); @@ -1738,6 +1809,10 @@ static int lpm_probe(struct platform_device *pdev) cluster_timer_init(lpm_root_node); + size = num_dbg_elements * sizeof(struct lpm_debug); + lpm_debug = dma_alloc_coherent(&pdev->dev, size, + &lpm_debug_phys, GFP_KERNEL); + register_cluster_lpm_stats(lpm_root_node, NULL); ret = cluster_cpuidle_register(lpm_root_node); @@ -1768,6 +1843,15 @@ static int lpm_probe(struct platform_device *pdev) set_update_ipi_history_callback(update_ipi_history); + /* Add lpm_debug to Minidump*/ + strlcpy(md_entry.name, "KLPMDEBUG", sizeof(md_entry.name)); + md_entry.virt_addr = (uintptr_t)lpm_debug; + md_entry.phys_addr = lpm_debug_phys; + md_entry.size = size; + md_entry.id = MINIDUMP_DEFAULT_ID; + if (msm_minidump_add_region(&md_entry) < 0) + pr_info("Failed to add lpm_debug in Minidump\n"); + return 0; failed: free_cluster_node(lpm_root_node); diff --git a/drivers/crypto/msm/ice.c b/drivers/crypto/msm/ice.c index 42aadb83b5a5..138671c2dbd2 100644 --- a/drivers/crypto/msm/ice.c +++ b/drivers/crypto/msm/ice.c @@ -58,7 +58,6 @@ #define ICE_CRYPTO_CXT_FDE 1 #define ICE_CRYPTO_CXT_FBE 2 -#define ICE_INSTANCE_TYPE_LENGTH 12 static int ice_fde_flag; @@ -584,33 +583,37 @@ static int register_ice_device(struct ice_device *ice_dev) unsigned int baseminor = 0; unsigned int count = 1; struct device *class_dev; - char ice_type[ICE_INSTANCE_TYPE_LENGTH]; - - if (!strcmp(ice_dev->ice_instance_type, "sdcc")) - strlcpy(ice_type, QCOM_SDCC_ICE_DEV, sizeof(ice_type)); - else if (!strcmp(ice_dev->ice_instance_type, "ufscard")) - strlcpy(ice_type, QCOM_UFS_CARD_ICE_DEV, sizeof(ice_type)); - else - strlcpy(ice_type, QCOM_UFS_ICE_DEV, sizeof(ice_type)); + int is_sdcc_ice = !strcmp(ice_dev->ice_instance_type, "sdcc"); + int is_ufscard_ice = !strcmp(ice_dev->ice_instance_type, "ufscard"); rc = alloc_chrdev_region(&ice_dev->device_no, baseminor, count, - ice_type); + is_sdcc_ice ? QCOM_SDCC_ICE_DEV : is_ufscard_ice ? + QCOM_UFS_CARD_ICE_DEV : QCOM_UFS_ICE_DEV); if (rc < 0) { pr_err("alloc_chrdev_region failed %d for %s\n", rc, - ice_type); + is_sdcc_ice ? QCOM_SDCC_ICE_DEV : is_ufscard_ice ? + QCOM_UFS_CARD_ICE_DEV : QCOM_UFS_ICE_DEV); return rc; } - ice_dev->driver_class = class_create(THIS_MODULE, ice_type); + ice_dev->driver_class = class_create(THIS_MODULE, + is_sdcc_ice ? QCOM_SDCC_ICE_DEV : is_ufscard_ice ? + QCOM_UFS_CARD_ICE_DEV : QCOM_UFS_ICE_DEV); if (IS_ERR(ice_dev->driver_class)) { rc = -ENOMEM; - pr_err("class_create failed %d for %s\n", rc, ice_type); + pr_err("class_create failed %d for %s\n", rc, + is_sdcc_ice ? QCOM_SDCC_ICE_DEV : is_ufscard_ice ? + QCOM_UFS_CARD_ICE_DEV : QCOM_UFS_ICE_DEV); goto exit_unreg_chrdev_region; } class_dev = device_create(ice_dev->driver_class, NULL, - ice_dev->device_no, NULL, ice_type); + ice_dev->device_no, NULL, + is_sdcc_ice ? QCOM_SDCC_ICE_DEV : is_ufscard_ice ? + QCOM_UFS_CARD_ICE_DEV : QCOM_UFS_ICE_DEV); if (!class_dev) { - pr_err("class_device_create failed %d for %s\n", rc, ice_type); + pr_err("class_device_create failed %d for %s\n", rc, + is_sdcc_ice ? QCOM_SDCC_ICE_DEV : is_ufscard_ice ? + QCOM_UFS_CARD_ICE_DEV : QCOM_UFS_ICE_DEV); rc = -ENOMEM; goto exit_destroy_class; } @@ -620,7 +623,9 @@ static int register_ice_device(struct ice_device *ice_dev) rc = cdev_add(&ice_dev->cdev, MKDEV(MAJOR(ice_dev->device_no), 0), 1); if (rc < 0) { - pr_err("cdev_add failed %d for %s\n", rc, ice_type); + pr_err("cdev_add failed %d for %s\n", rc, + is_sdcc_ice ? QCOM_SDCC_ICE_DEV : is_ufscard_ice ? + QCOM_UFS_CARD_ICE_DEV : QCOM_UFS_ICE_DEV); goto exit_destroy_device; } return 0; diff --git a/drivers/crypto/msm/qce50.c b/drivers/crypto/msm/qce50.c index 2c84c5fd05bd..feae567497c4 100644 --- a/drivers/crypto/msm/qce50.c +++ b/drivers/crypto/msm/qce50.c @@ -2,7 +2,7 @@ /* * QTI Crypto Engine driver. * - * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "QCE50: %s: " fmt, __func__ @@ -922,6 +922,11 @@ static int _ce_setup_cipher(struct qce_device *pce_dev, struct qce_req *creq, break; case CIPHER_ALG_3DES: if (creq->mode != QCE_MODE_ECB) { + if (ivsize > MAX_IV_LENGTH) { + pr_err("%s: error: Invalid length parameter\n", + __func__); + return -EINVAL; + } _byte_stream_to_net_words(enciv32, creq->iv, ivsize); pce = cmdlistinfo->encr_cntr_iv; pce->data = enciv32[0]; @@ -970,6 +975,11 @@ static int _ce_setup_cipher(struct qce_device *pce_dev, struct qce_req *creq, } } if (creq->mode != QCE_MODE_ECB) { + if (ivsize > MAX_IV_LENGTH) { + pr_err("%s: error: Invalid length parameter\n", + __func__); + return -EINVAL; + } if (creq->mode == QCE_MODE_XTS) _byte_stream_swap_to_net_words(enciv32, creq->iv, ivsize); diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 37d60599b2b8..1b76beecf0e2 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -1400,9 +1400,7 @@ static int __init devfreq_init(void) return PTR_ERR(devfreq_class); } - devfreq_wq = alloc_workqueue("devfreq_wq", - WQ_HIGHPRI | WQ_UNBOUND | WQ_FREEZABLE | - WQ_MEM_RECLAIM, 0); + devfreq_wq = create_freezable_workqueue("devfreq_wq"); if (!devfreq_wq) { class_destroy(devfreq_class); pr_err("%s: couldn't create workqueue\n", __FILE__); diff --git a/drivers/dma-buf/sync_file.c b/drivers/dma-buf/sync_file.c index 026e52995c0d..91d620994123 100644 --- a/drivers/dma-buf/sync_file.c +++ b/drivers/dma-buf/sync_file.c @@ -124,6 +124,36 @@ struct dma_fence *sync_file_get_fence(int fd) } EXPORT_SYMBOL(sync_file_get_fence); +/** + * sync_file_get_name - get the name of the sync_file + * @sync_file: sync_file to get the fence from + * @buf: destination buffer to copy sync_file name into + * @len: available size of destination buffer. + * + * Each sync_file may have a name assigned either by the user (when merging + * sync_files together) or created from the fence it contains. In the latter + * case construction of the name is deferred until use, and so requires + * sync_file_get_name(). + * + * Returns: a string representing the name. + */ +char *sync_file_get_name(struct sync_file *sync_file, char *buf, int len) +{ + if (sync_file->user_name[0]) { + strlcpy(buf, sync_file->user_name, len); + } else { + struct dma_fence *fence = sync_file->fence; + + snprintf(buf, len, "%s-%s%llu-%d", + fence->ops->get_driver_name(fence), + fence->ops->get_timeline_name(fence), + fence->context, + fence->seqno); + } + + return buf; +} + static int sync_file_set_fence(struct sync_file *sync_file, struct dma_fence **fences, int num_fences) { @@ -186,7 +216,7 @@ static void add_fence(struct dma_fence **fences, * @a and @b. @a and @b remain valid, independent sync_file. Returns the * new merged sync_file or NULL in case of error. */ -static struct sync_file *sync_file_merge(struct sync_file *a, +static struct sync_file *sync_file_merge(const char *name, struct sync_file *a, struct sync_file *b) { struct sync_file *sync_file; @@ -261,6 +291,7 @@ static struct sync_file *sync_file_merge(struct sync_file *a, goto err; } + strlcpy(sync_file->user_name, name, sizeof(sync_file->user_name)); return sync_file; err: @@ -304,14 +335,11 @@ static long sync_file_ioctl_merge(struct sync_file *sync_file, int err; struct sync_file *fence2, *fence3; struct sync_merge_data data; - size_t len; if (fd < 0) return fd; - arg += offsetof(typeof(data), fd2); - len = sizeof(data) - offsetof(typeof(data), fd2); - if (copy_from_user(&data.fd2, (void __user *)arg, len)) { + if (copy_from_user(&data, (void __user *)arg, sizeof(data))) { err = -EFAULT; goto err_put_fd; } @@ -327,14 +355,15 @@ static long sync_file_ioctl_merge(struct sync_file *sync_file, goto err_put_fd; } - fence3 = sync_file_merge(sync_file, fence2); + data.name[sizeof(data.name) - 1] = '\0'; + fence3 = sync_file_merge(data.name, sync_file, fence2); if (!fence3) { err = -ENOMEM; goto err_put_fence2; } data.fence = fd; - if (copy_to_user((void __user *)arg, &data.fd2, len)) { + if (copy_to_user((void __user *)arg, &data, sizeof(data))) { err = -EFAULT; goto err_put_fence3; } @@ -357,6 +386,11 @@ static long sync_file_ioctl_merge(struct sync_file *sync_file, static int sync_fill_fence_info(struct dma_fence *fence, struct sync_fence_info *info) { + strlcpy(info->obj_name, fence->ops->get_timeline_name(fence), + sizeof(info->obj_name)); + strlcpy(info->driver_name, fence->ops->get_driver_name(fence), + sizeof(info->driver_name)); + info->status = dma_fence_get_status(fence); while (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags) && !test_bit(DMA_FENCE_FLAG_TIMESTAMP_BIT, &fence->flags)) @@ -373,13 +407,12 @@ static long sync_file_ioctl_fence_info(struct sync_file *sync_file, unsigned long arg) { struct sync_file_info info; + struct sync_fence_info *fence_info = NULL; struct dma_fence **fences; - size_t len, offset; - int num_fences, i; + __u32 size; + int num_fences, ret, i; - arg += offsetof(typeof(info), status); - len = sizeof(info) - offsetof(typeof(info), status); - if (copy_from_user(&info.status, (void __user *)arg, len)) + if (copy_from_user(&info, (void __user *)arg, sizeof(info))) return -EFAULT; if (info.flags || info.pad) @@ -403,31 +436,35 @@ static long sync_file_ioctl_fence_info(struct sync_file *sync_file, if (info.num_fences < num_fences) return -EINVAL; - offset = offsetof(struct sync_fence_info, status); - for (i = 0; i < num_fences; i++) { - struct { - __s32 status; - __u32 flags; - __u64 timestamp_ns; - } fence_info; - struct sync_fence_info *finfo = (void *)&fence_info - offset; - int status = sync_fill_fence_info(fences[i], finfo); - u64 dest; + size = num_fences * sizeof(*fence_info); + fence_info = kzalloc(size, GFP_KERNEL); + if (!fence_info) + return -ENOMEM; - /* Don't leak kernel memory to userspace via finfo->flags */ - finfo->flags = 0; + for (i = 0; i < num_fences; i++) { + int status = sync_fill_fence_info(fences[i], &fence_info[i]); info.status = info.status <= 0 ? info.status : status; - dest = info.sync_fence_info + i * sizeof(*finfo) + offset; - if (copy_to_user(u64_to_user_ptr(dest), &fence_info, - sizeof(fence_info))) - return -EFAULT; + } + + if (copy_to_user(u64_to_user_ptr(info.sync_fence_info), fence_info, + size)) { + ret = -EFAULT; + goto out; } no_fences: + sync_file_get_name(sync_file, info.name, sizeof(info.name)); info.num_fences = num_fences; - if (copy_to_user((void __user *)arg, &info.status, len)) - return -EFAULT; - return 0; + + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + ret = -EFAULT; + else + ret = 0; + +out: + kfree(fence_info); + + return ret; } static long sync_file_ioctl(struct file *file, unsigned int cmd, diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index 0232c25a1586..c7ab294f18fd 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -739,6 +739,7 @@ static int device_pca953x_init(struct pca953x_chip *chip, u32 invert) memset(val, 0, NBANK(chip)); ret = pca953x_write_regs(chip, PCA953X_INVERT, val); + pr_err("device_pca953x_init %d\n",ret); out: return ret; } @@ -771,7 +772,10 @@ static int device_pca957x_init(struct pca953x_chip *chip, u32 invert) memset(val, 0x02, NBANK(chip)); ret = pca953x_write_regs(chip, PCA957X_BKEN, val); if (ret) + { goto out; + } + pr_err("device_pca957x_init %d\n",ret); return 0; out: @@ -788,7 +792,8 @@ static int pca953x_probe(struct i2c_client *client, int irq_base = 0; int ret; u32 invert = 0; - struct regulator *reg; + //struct regulator *reg; + dev_err(&client->dev,"pca953x_probe\n"); chip = devm_kzalloc(&client->dev, sizeof(struct pca953x_chip), GFP_KERNEL); @@ -822,7 +827,7 @@ static int pca953x_probe(struct i2c_client *client, chip->client = client; - reg = devm_regulator_get(&client->dev, "vcc"); + /*reg = devm_regulator_get(&client->dev, "vcc"); if (IS_ERR(reg)) { ret = PTR_ERR(reg); if (ret != -EPROBE_DEFER) @@ -834,7 +839,7 @@ static int pca953x_probe(struct i2c_client *client, dev_err(&client->dev, "reg en err: %d\n", ret); return ret; } - chip->regulator = reg; + chip->regulator = reg;*/ if (i2c_id) { chip->driver_data = i2c_id->driver_data; @@ -919,7 +924,7 @@ static int pca953x_probe(struct i2c_client *client, return 0; err_exit: - regulator_disable(chip->regulator); + //regulator_disable(chip->regulator); return ret; } @@ -939,7 +944,7 @@ static int pca953x_remove(struct i2c_client *client) ret = 0; } - regulator_disable(chip->regulator); + //regulator_disable(chip->regulator); return ret; } diff --git a/drivers/gpu/msm/adreno.c b/drivers/gpu/msm/adreno.c index c7089bf15d51..957abd446ec7 100644 --- a/drivers/gpu/msm/adreno.c +++ b/drivers/gpu/msm/adreno.c @@ -3,6 +3,7 @@ * Copyright (c) 2002,2007-2020, The Linux Foundation. All rights reserved. */ #include +#include #include #include #include @@ -27,7 +28,7 @@ /* Include the master list of GPU cores that are supported */ #include "adreno-gpulist.h" -static void adreno_pwr_on_work(struct work_struct *work); +static void adreno_input_work(struct work_struct *work); static unsigned int counter_delta(struct kgsl_device *device, unsigned int reg, unsigned int *counter); @@ -56,6 +57,8 @@ static struct adreno_device device_3d0 = { .ft_policy = KGSL_FT_DEFAULT_POLICY, .ft_pf_policy = KGSL_FT_PAGEFAULT_DEFAULT_POLICY, .long_ib_detect = 1, + .input_work = __WORK_INITIALIZER(device_3d0.input_work, + adreno_input_work), .pwrctrl_flag = BIT(ADRENO_THROTTLING_CTRL) | BIT(ADRENO_HWCG_CTRL), .profile.enabled = false, .active_list = LIST_HEAD_INIT(device_3d0.active_list), @@ -67,8 +70,6 @@ static struct adreno_device device_3d0 = { .skipsaverestore = 1, .usesgmem = 1, }, - .pwr_on_work = __WORK_INITIALIZER(device_3d0.pwr_on_work, - adreno_pwr_on_work), }; /* Ptr to array for the current set of fault detect registers */ @@ -90,6 +91,9 @@ static unsigned int adreno_ft_regs_default[] = { /* Nice level for the higher priority GPU start thread */ int adreno_wake_nice = -7; +/* Number of milliseconds to stay active active after a wake on touch */ +unsigned int adreno_wake_timeout = 100; + void adreno_reglist_write(struct adreno_device *adreno_dev, const struct adreno_reglist *list, u32 count) { @@ -353,17 +357,152 @@ void adreno_gmu_send_nmi(struct adreno_device *adreno_dev) wmb(); } -static void adreno_pwr_on_work(struct work_struct *work) +/* + * A workqueue callback responsible for actually turning on the GPU after a + * touch event. kgsl_pwrctrl_change_state(ACTIVE) is used without any + * active_count protection to avoid the need to maintain state. Either + * somebody will start using the GPU or the idle timer will fire and put the + * GPU back into slumber. + */ +static void adreno_input_work(struct work_struct *work) { - struct adreno_device *adreno_dev = - container_of(work, typeof(*adreno_dev), pwr_on_work); + struct adreno_device *adreno_dev = container_of(work, + struct adreno_device, input_work); struct kgsl_device *device = KGSL_DEVICE(adreno_dev); mutex_lock(&device->mutex); + + device->flags |= KGSL_FLAG_WAKE_ON_TOUCH; + + /* + * Don't schedule adreno_start in a high priority workqueue, we are + * already in a workqueue which should be sufficient + */ kgsl_pwrctrl_change_state(device, KGSL_STATE_ACTIVE); + + /* + * When waking up from a touch event we want to stay active long enough + * for the user to send a draw command. The default idle timer timeout + * is shorter than we want so go ahead and push the idle timer out + * further for this special case + */ + mod_timer(&device->idle_timer, + jiffies + msecs_to_jiffies(adreno_wake_timeout)); mutex_unlock(&device->mutex); } +/* + * Process input events and schedule work if needed. At this point we are only + * interested in groking EV_ABS touchscreen events + */ +static void adreno_input_event(struct input_handle *handle, unsigned int type, + unsigned int code, int value) +{ + struct kgsl_device *device = handle->handler->private; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + /* Only consider EV_ABS (touch) events */ + if (type != EV_ABS) + return; + + /* + * Don't do anything if anything hasn't been rendered since we've been + * here before + */ + + if (device->flags & KGSL_FLAG_WAKE_ON_TOUCH) + return; + + /* + * If the device is in nap, kick the idle timer to make sure that we + * don't go into slumber before the first render. If the device is + * already in slumber schedule the wake. + */ + + if (device->state == KGSL_STATE_NAP) { + /* + * Set the wake on touch bit to keep from coming back here and + * keeping the device in nap without rendering + */ + + device->flags |= KGSL_FLAG_WAKE_ON_TOUCH; + + mod_timer(&device->idle_timer, + jiffies + device->pwrctrl.interval_timeout); + } else if (device->state == KGSL_STATE_SLUMBER) { + schedule_work(&adreno_dev->input_work); + } +} + +#ifdef CONFIG_INPUT +static int adreno_input_connect(struct input_handler *handler, + struct input_dev *dev, const struct input_device_id *id) +{ + struct input_handle *handle; + int ret; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (handle == NULL) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = handler->name; + + ret = input_register_handle(handle); + if (ret) { + kfree(handle); + return ret; + } + + ret = input_open_device(handle); + if (ret) { + input_unregister_handle(handle); + kfree(handle); + } + + return ret; +} + +static void adreno_input_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} +#else +static int adreno_input_connect(struct input_handler *handler, + struct input_dev *dev, const struct input_device_id *id) +{ + return 0; +} +static void adreno_input_disconnect(struct input_handle *handle) {} +#endif + +/* + * We are only interested in EV_ABS events so only register handlers for those + * input devices that have EV_ABS events + */ +static const struct input_device_id adreno_input_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_ABS) }, + /* assumption: MT_.._X & MT_.._Y are in the same long */ + .absbit = { [BIT_WORD(ABS_MT_POSITION_X)] = + BIT_MASK(ABS_MT_POSITION_X) | + BIT_MASK(ABS_MT_POSITION_Y) }, + }, + { }, +}; + +static struct input_handler adreno_input_handler = { + .event = adreno_input_event, + .connect = adreno_input_connect, + .disconnect = adreno_input_disconnect, + .name = "kgsl", + .id_table = adreno_input_ids, +}; + /* * _soft_reset() - Soft reset GPU * @adreno_dev: Pointer to adreno device @@ -887,6 +1026,7 @@ static void adreno_of_get_initial_pwrlevel(struct adreno_device *adreno_dev, init_level = 1; pwr->active_pwrlevel = init_level; + pwr->default_pwrlevel = init_level; } static void adreno_of_get_limits(struct adreno_device *adreno_dev, @@ -1070,13 +1210,16 @@ static int adreno_of_get_power(struct adreno_device *adreno_dev, device->pwrctrl.pm_qos_wakeup_latency = 101; if (of_property_read_u32(node, "qcom,idle-timeout", &timeout)) - timeout = 58; + timeout = 80; device->pwrctrl.interval_timeout = msecs_to_jiffies(timeout); device->pwrctrl.bus_control = of_property_read_bool(node, "qcom,bus-control"); + device->pwrctrl.input_disable = of_property_read_bool(node, + "qcom,disable-wake-on-touch"); + return 0; } @@ -1407,6 +1550,21 @@ static int adreno_probe(struct platform_device *pdev) dev_warn(device->dev, "Failed to get gpuhtw LLC slice descriptor %ld\n", PTR_ERR(adreno_dev->gpuhtw_llc_slice)); + +#ifdef CONFIG_INPUT + if (!device->pwrctrl.input_disable) { + adreno_input_handler.private = device; + /* + * It isn't fatal if we cannot register the input handler. Sad, + * perhaps, but not fatal + */ + if (input_register_handler(&adreno_input_handler)) { + adreno_input_handler.private = NULL; + dev_err(device->dev, + "Unable to register the input handler\n"); + } + } +#endif out: if (status) { adreno_ringbuffer_close(adreno_dev); @@ -1462,6 +1620,10 @@ static int adreno_remove(struct platform_device *pdev) /* The memory is fading */ _adreno_free_memories(adreno_dev); +#ifdef CONFIG_INPUT + if (adreno_input_handler.private) + input_unregister_handler(&adreno_input_handler); +#endif adreno_sysfs_close(adreno_dev); adreno_coresight_remove(adreno_dev); @@ -3871,6 +4033,19 @@ static bool adreno_is_hwcg_on(struct kgsl_device *device) return test_bit(ADRENO_HWCG_CTRL, &adreno_dev->pwrctrl_flag); } +u32 adreno_get_ucode_version(const u32 *data) +{ + u32 version; + + version = data[1]; + + if ((version & 0xf) != 0xa) + return version; + + version &= ~0xfff; + return version | ((data[3] & 0xfff000) >> 12); +} + static const struct kgsl_functable adreno_functable = { /* Mandatory functions */ .regread = adreno_regread, diff --git a/drivers/gpu/msm/adreno.h b/drivers/gpu/msm/adreno.h index 5beb3fc7a57c..1423135b1089 100644 --- a/drivers/gpu/msm/adreno.h +++ b/drivers/gpu/msm/adreno.h @@ -279,8 +279,8 @@ enum adreno_preempt_states { /** * struct adreno_preemption * @state: The current state of preemption - * @counters: Memory descriptor for the memory where the GPU writes the - * preemption counters on switch + * @scratch: Memory descriptor for the memory where the GPU writes the + * current ctxt record address and preemption counters on switch * @timer: A timer to make sure preemption doesn't stall * @work: A work struct for the preemption worker (for 5XX) * preempt_level: The level of preemption (for 6XX) @@ -290,7 +290,7 @@ enum adreno_preempt_states { */ struct adreno_preemption { atomic_t state; - struct kgsl_memdesc counters; + struct kgsl_memdesc scratch; struct timer_list timer; struct work_struct work; unsigned int preempt_level; @@ -428,7 +428,7 @@ enum gpu_coresight_sources { * @dispatcher: Container for adreno GPU dispatcher * @pwron_fixup: Command buffer to run a post-power collapse shader workaround * @pwron_fixup_dwords: Number of dwords in the command buffer - * @pwr_on_work: Work struct for turning on the GPU + * @input_work: Work struct for turning on the GPU after a touch event * @busy_data: Struct holding GPU VBIF busy stats * @ram_cycles_lo: Number of DDR clock cycles for the monitor session (Only * DDR channel 0 read cycles in case of GBIF) @@ -508,7 +508,7 @@ struct adreno_device { struct adreno_dispatcher dispatcher; struct kgsl_memdesc pwron_fixup; unsigned int pwron_fixup_dwords; - struct work_struct pwr_on_work; + struct work_struct input_work; struct adreno_busy_data busy_data; unsigned int ram_cycles_lo; unsigned int ram_cycles_lo_ch1_read; @@ -896,6 +896,7 @@ struct adreno_gpudev { struct adreno_irq *irq; int num_prio_levels; + int cp_rb_cntl; unsigned int vbif_xin_halt_ctrl0_mask; unsigned int gbif_client_halt_mask; unsigned int gbif_arb_halt_mask; @@ -1042,6 +1043,7 @@ extern struct adreno_gpudev adreno_a5xx_gpudev; extern struct adreno_gpudev adreno_a6xx_gpudev; extern int adreno_wake_nice; +extern unsigned int adreno_wake_timeout; int adreno_start(struct kgsl_device *device, int priority); int adreno_soft_reset(struct kgsl_device *device); @@ -1123,6 +1125,7 @@ void adreno_rscc_regread(struct adreno_device *adreno_dev, unsigned int offsetwords, unsigned int *value); void adreno_isense_regread(struct adreno_device *adreno_dev, unsigned int offsetwords, unsigned int *value); +u32 adreno_get_ucode_version(const u32 *data); #define ADRENO_TARGET(_name, _id) \ diff --git a/drivers/gpu/msm/adreno_a3xx.c b/drivers/gpu/msm/adreno_a3xx.c index 0f8f83415e03..7567e854d81b 100644 --- a/drivers/gpu/msm/adreno_a3xx.c +++ b/drivers/gpu/msm/adreno_a3xx.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2019,2021, The Linux Foundation. All rights reserved. */ #include @@ -1094,8 +1094,14 @@ struct { { A3XX_CP_PROTECT_REG_0 + 13, 0x0cc0, 0 }, /* VBIF */ { A3XX_CP_PROTECT_REG_0 + 14, 0x3000, 6 }, - /* SMMU */ - { A3XX_CP_PROTECT_REG_0 + 15, 0xa000, 12 }, + /* + * SMMU + * For A3xx, base offset for smmu region is 0xa000 and length is + * 0x1000 bytes. Offset must be in dword and length of the block + * must be ilog2(dword length). + * 0xa000 >> 2 = 0x2800, ilog2(0x1000 >> 2) = 10. + */ + { A3XX_CP_PROTECT_REG_0 + 15, 0x2800, 10 }, /* There are no remaining protected mode registers for a3xx */ }; diff --git a/drivers/gpu/msm/adreno_a5xx.c b/drivers/gpu/msm/adreno_a5xx.c index 4f0db5241077..7ac31898f326 100644 --- a/drivers/gpu/msm/adreno_a5xx.c +++ b/drivers/gpu/msm/adreno_a5xx.c @@ -1724,12 +1724,15 @@ static int a5xx_post_start(struct adreno_device *adreno_dev) *cmds++ = 0xF; } - if (adreno_is_preemption_enabled(adreno_dev)) + if (adreno_is_preemption_enabled(adreno_dev)) { cmds += _preemption_init(adreno_dev, rb, cmds, NULL); + rb->_wptr = rb->_wptr - (42 - (cmds - start)); + ret = adreno_ringbuffer_submit_spin_nosync(rb, NULL, 2000); + } else { + rb->_wptr = rb->_wptr - (42 - (cmds - start)); + ret = adreno_ringbuffer_submit_spin(rb, NULL, 2000); + } - rb->_wptr = rb->_wptr - (42 - (cmds - start)); - - ret = adreno_ringbuffer_submit_spin(rb, NULL, 2000); if (ret) adreno_spin_idle_debug(adreno_dev, "hw initialization failed to idle\n"); @@ -2038,7 +2041,7 @@ static int _load_firmware(struct kgsl_device *device, const char *fwfile, memcpy(firmware->memdesc.hostptr, &fw->data[4], fw->size - 4); firmware->size = (fw->size - 4) / sizeof(uint32_t); - firmware->version = *(unsigned int *)&fw->data[4]; + firmware->version = adreno_get_ucode_version((u32 *)fw->data); done: release_firmware(fw); diff --git a/drivers/gpu/msm/adreno_a5xx.h b/drivers/gpu/msm/adreno_a5xx.h index ddfc74acc08a..7ef90529a483 100644 --- a/drivers/gpu/msm/adreno_a5xx.h +++ b/drivers/gpu/msm/adreno_a5xx.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2015-2017,2019 The Linux Foundation. All rights reserved. + * Copyright (c) 2015-2017,2019-2020 The Linux Foundation. All rights reserved. */ #ifndef _ADRENO_A5XX_H_ @@ -134,7 +134,7 @@ void a5xx_crashdump_init(struct adreno_device *adreno_dev); void a5xx_hwcg_set(struct adreno_device *adreno_dev, bool on); -#define A5XX_CP_RB_CNTL_DEFAULT (((ilog2(4) << 8) & 0x1F00) | \ +#define A5XX_CP_RB_CNTL_DEFAULT ((1 << 27) | ((ilog2(4) << 8) & 0x1F00) | \ (ilog2(KGSL_RB_DWORDS >> 1) & 0x3F)) /* GPMU interrupt multiplexor */ #define FW_INTR_INFO (0) diff --git a/drivers/gpu/msm/adreno_a5xx_preempt.c b/drivers/gpu/msm/adreno_a5xx_preempt.c index 46ab52b57fd7..57e4df86bbdd 100644 --- a/drivers/gpu/msm/adreno_a5xx_preempt.c +++ b/drivers/gpu/msm/adreno_a5xx_preempt.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2014-2017,2019 The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2017,2019-2020 The Linux Foundation. All rights reserved. */ #include "adreno.h" @@ -570,7 +570,7 @@ static void _preemption_close(struct adreno_device *adreno_dev) unsigned int i; del_timer(&preempt->timer); - kgsl_free_global(device, &preempt->counters); + kgsl_free_global(device, &preempt->scratch); a5xx_preemption_iommu_close(adreno_dev); FOR_EACH_RINGBUFFER(adreno_dev, rb, i) { @@ -604,14 +604,14 @@ int a5xx_preemption_init(struct adreno_device *adreno_dev) timer_setup(&preempt->timer, _a5xx_preemption_timer, 0); /* Allocate mem for storing preemption counters */ - ret = kgsl_allocate_global(device, &preempt->counters, + ret = kgsl_allocate_global(device, &preempt->scratch, adreno_dev->num_ringbuffers * A5XX_CP_CTXRECORD_PREEMPTION_COUNTER_SIZE, 0, 0, "preemption_counters"); if (ret) goto err; - addr = preempt->counters.gpuaddr; + addr = preempt->scratch.gpuaddr; /* Allocate mem for storing preemption switch record */ FOR_EACH_RINGBUFFER(adreno_dev, rb, i) { diff --git a/drivers/gpu/msm/adreno_a6xx.c b/drivers/gpu/msm/adreno_a6xx.c index 7073e3912822..6d18580125a8 100644 --- a/drivers/gpu/msm/adreno_a6xx.c +++ b/drivers/gpu/msm/adreno_a6xx.c @@ -831,7 +831,7 @@ static int a6xx_post_start(struct adreno_device *adreno_dev) rb->_wptr = rb->_wptr - (42 - (cmds - start)); - ret = adreno_ringbuffer_submit_spin(rb, NULL, 2000); + ret = adreno_ringbuffer_submit_spin_nosync(rb, NULL, 2000); if (ret) adreno_spin_idle_debug(adreno_dev, "hw preemption initialization failed to idle\n"); @@ -859,6 +859,7 @@ static int a6xx_post_start(struct adreno_device *adreno_dev) */ static int a6xx_rb_start(struct adreno_device *adreno_dev) { + struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev); struct adreno_ringbuffer *rb = ADRENO_CURRENT_RINGBUFFER(adreno_dev); struct kgsl_device *device = &adreno_dev->dev; struct adreno_firmware *fw = ADRENO_FW(adreno_dev, ADRENO_FW_SQE); @@ -875,7 +876,7 @@ static int a6xx_rb_start(struct adreno_device *adreno_dev) * representation of the size in quadwords (sizedwords / 2). */ adreno_writereg(adreno_dev, ADRENO_REG_CP_RB_CNTL, - A6XX_CP_RB_CNTL_DEFAULT); + gpudev->cp_rb_cntl); adreno_writereg64(adreno_dev, ADRENO_REG_CP_RB_BASE, ADRENO_REG_CP_RB_BASE_HI, rb->buffer_desc.gpuaddr); @@ -994,7 +995,7 @@ static int _load_firmware(struct kgsl_device *device, const char *fwfile, if (!ret) { memcpy(firmware->memdesc.hostptr, &fw->data[4], fw->size - 4); firmware->size = (fw->size - 4) / sizeof(uint32_t); - firmware->version = *(unsigned int *)&fw->data[4]; + firmware->version = adreno_get_ucode_version((u32 *)fw->data); } release_firmware(fw); @@ -2414,6 +2415,9 @@ static void a6xx_platform_setup(struct adreno_device *adreno_dev) if (ADRENO_FEATURE(adreno_dev, ADRENO_SPTP_PC)) set_bit(ADRENO_SPTP_PC_CTRL, &adreno_dev->pwrctrl_flag); + if (!ADRENO_FEATURE(adreno_dev, ADRENO_APRIV)) + gpudev->cp_rb_cntl |= (1 << 27); + /* Check efuse bits for various capabilties */ a6xx_check_features(adreno_dev); } @@ -2707,6 +2711,7 @@ struct adreno_gpudev adreno_a6xx_gpudev = { .irq = &a6xx_irq, .irq_trace = trace_kgsl_a5xx_irq_status, .num_prio_levels = KGSL_PRIORITY_MAX_RB_LEVELS, + .cp_rb_cntl = A6XX_CP_RB_CNTL_DEFAULT, .platform_setup = a6xx_platform_setup, .init = a6xx_init, .rb_start = a6xx_rb_start, diff --git a/drivers/gpu/msm/adreno_a6xx_gmu.c b/drivers/gpu/msm/adreno_a6xx_gmu.c index 781aebaae9d8..157a0682c7b8 100644 --- a/drivers/gpu/msm/adreno_a6xx_gmu.c +++ b/drivers/gpu/msm/adreno_a6xx_gmu.c @@ -876,7 +876,7 @@ static int a6xx_gmu_gfx_rail_on(struct kgsl_device *device) { struct kgsl_pwrctrl *pwr = &device->pwrctrl; struct gmu_device *gmu = KGSL_GMU_DEVICE(device); - unsigned int perf_idx = pwr->num_pwrlevels - 1; + unsigned int perf_idx = pwr->num_pwrlevels - pwr->default_pwrlevel - 1; uint32_t default_opp = gmu->rpmh_votes.gx_votes[perf_idx]; gmu_core_regwrite(device, A6XX_GMU_BOOT_SLUMBER_OPTION, @@ -1297,8 +1297,8 @@ static int a6xx_gmu_notify_slumber(struct kgsl_device *device) struct adreno_device *adreno_dev = ADRENO_DEVICE(device); struct kgsl_pwrctrl *pwr = &device->pwrctrl; struct gmu_device *gmu = KGSL_GMU_DEVICE(device); - int bus_level = pwr->pwrlevels[pwr->num_pwrlevels - 1].bus_freq; - int perf_idx = gmu->num_gpupwrlevels - 1; + int bus_level = pwr->pwrlevels[pwr->default_pwrlevel].bus_freq; + int perf_idx = gmu->num_gpupwrlevels - pwr->default_pwrlevel - 1; int ret, state; /* Disable the power counter so that the GMU is not busy */ diff --git a/drivers/gpu/msm/adreno_a6xx_preempt.c b/drivers/gpu/msm/adreno_a6xx_preempt.c index 02c41e4a2280..1d53237fda4c 100644 --- a/drivers/gpu/msm/adreno_a6xx_preempt.c +++ b/drivers/gpu/msm/adreno_a6xx_preempt.c @@ -316,8 +316,8 @@ void a6xx_preemption_trigger(struct adreno_device *adreno_dev) kgsl_sharedmem_writel(device, &iommu->smmu_info, PREEMPT_SMMU_RECORD(context_idr), contextidr); - kgsl_sharedmem_readq(&device->scratch, &gpuaddr, - SCRATCH_PREEMPTION_CTXT_RESTORE_ADDR_OFFSET(next->id)); + kgsl_sharedmem_readq(&preempt->scratch, &gpuaddr, + next->id * sizeof(u64)); /* * Set a keepalive bit before the first preemption register write. @@ -543,12 +543,10 @@ unsigned int a6xx_preemption_pre_ibsubmit( rb->perfcounter_save_restore_desc.gpuaddr); if (context) { - struct kgsl_device *device = KGSL_DEVICE(adreno_dev); struct adreno_context *drawctxt = ADRENO_CONTEXT(context); struct adreno_ringbuffer *rb = drawctxt->rb; - uint64_t dest = - SCRATCH_PREEMPTION_CTXT_RESTORE_GPU_ADDR(device, - rb->id); + uint64_t dest = adreno_dev->preempt.scratch.gpuaddr + + sizeof(u64) * rb->id; *cmds++ = cp_mem_packet(adreno_dev, CP_MEM_WRITE, 2, 2); cmds += cp_gpuaddr(adreno_dev, cmds, dest); @@ -566,9 +564,8 @@ unsigned int a6xx_preemption_post_ibsubmit(struct adreno_device *adreno_dev, struct adreno_ringbuffer *rb = adreno_dev->cur_rb; if (rb) { - struct kgsl_device *device = KGSL_DEVICE(adreno_dev); - uint64_t dest = SCRATCH_PREEMPTION_CTXT_RESTORE_GPU_ADDR(device, - rb->id); + uint64_t dest = adreno_dev->preempt.scratch.gpuaddr + + sizeof(u64) * rb->id; *cmds++ = cp_mem_packet(adreno_dev, CP_MEM_WRITE, 2, 2); cmds += cp_gpuaddr(adreno_dev, cmds, dest); @@ -633,6 +630,7 @@ void a6xx_preemption_start(struct adreno_device *adreno_dev) static int a6xx_preemption_ringbuffer_init(struct adreno_device *adreno_dev, struct adreno_ringbuffer *rb) { + struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev); struct kgsl_device *device = KGSL_DEVICE(adreno_dev); int ret; @@ -675,7 +673,7 @@ static int a6xx_preemption_ringbuffer_init(struct adreno_device *adreno_dev, kgsl_sharedmem_writel(device, &rb->preemption_desc, PREEMPT_RECORD(data), 0); kgsl_sharedmem_writel(device, &rb->preemption_desc, - PREEMPT_RECORD(cntl), A6XX_CP_RB_CNTL_DEFAULT); + PREEMPT_RECORD(cntl), gpudev->cp_rb_cntl); kgsl_sharedmem_writel(device, &rb->preemption_desc, PREEMPT_RECORD(rptr), 0); kgsl_sharedmem_writel(device, &rb->preemption_desc, @@ -729,6 +727,7 @@ static void _preemption_close(struct adreno_device *adreno_dev) unsigned int i; del_timer(&preempt->timer); + kgsl_free_global(device, &preempt->scratch); a6xx_preemption_iommu_close(adreno_dev); FOR_EACH_RINGBUFFER(adreno_dev, rb, i) { @@ -764,6 +763,9 @@ int a6xx_preemption_init(struct adreno_device *adreno_dev) timer_setup(&preempt->timer, _a6xx_preemption_timer, 0); + ret = kgsl_allocate_global(device, &preempt->scratch, PAGE_SIZE, 0, 0, + "preemption_scratch"); + /* Allocate mem for storing preemption switch record */ FOR_EACH_RINGBUFFER(adreno_dev, rb, i) { ret = a6xx_preemption_ringbuffer_init(adreno_dev, rb); diff --git a/drivers/gpu/msm/adreno_debugfs.c b/drivers/gpu/msm/adreno_debugfs.c index f9e4a0b8a691..d67945724643 100644 --- a/drivers/gpu/msm/adreno_debugfs.c +++ b/drivers/gpu/msm/adreno_debugfs.c @@ -147,6 +147,11 @@ static void sync_event_print(struct seq_file *s, break; } case KGSL_CMD_SYNCPOINT_TYPE_FENCE: { + int i; + + for (i = 0; i < sync_event->info.num_fences; i++) + seq_printf(s, "sync: %s", + sync_event->info.fences[i].name); break; } default: diff --git a/drivers/gpu/msm/adreno_dispatch.c b/drivers/gpu/msm/adreno_dispatch.c index f3a978268507..055afd6d6816 100644 --- a/drivers/gpu/msm/adreno_dispatch.c +++ b/drivers/gpu/msm/adreno_dispatch.c @@ -1151,6 +1151,12 @@ static inline int _verify_cmdobj(struct kgsl_device_private *dev_priv, if (!_verify_ib(dev_priv, &ADRENO_CONTEXT(context)->base, ib)) return -EINVAL; + /* + * Clear the wake on touch bit to indicate an IB has + * been submitted since the last time we set it. + * But only clear it when we have rendering commands. + */ + device->flags &= ~KGSL_FLAG_WAKE_ON_TOUCH; } /* A3XX does not have support for drawobj profiling */ diff --git a/drivers/gpu/msm/adreno_ioctl.c b/drivers/gpu/msm/adreno_ioctl.c index 126424485454..4d29c0ff0328 100644 --- a/drivers/gpu/msm/adreno_ioctl.c +++ b/drivers/gpu/msm/adreno_ioctl.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2002,2007-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2002,2007-2020, The Linux Foundation. All rights reserved. */ #include @@ -160,7 +160,7 @@ static long adreno_ioctl_preemption_counters_query( levels_to_copy = gpudev->num_prio_levels; if (copy_to_user((void __user *) (uintptr_t) read->counters, - adreno_dev->preempt.counters.hostptr, + adreno_dev->preempt.scratch.hostptr, levels_to_copy * size_level)) return -EFAULT; diff --git a/drivers/gpu/msm/adreno_pm4types.h b/drivers/gpu/msm/adreno_pm4types.h index 7521647936db..756112c168ef 100644 --- a/drivers/gpu/msm/adreno_pm4types.h +++ b/drivers/gpu/msm/adreno_pm4types.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2002,2007-2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2002,2007-2018,2020 The Linux Foundation. All rights reserved. */ #ifndef __ADRENO_PM4TYPES_H #define __ADRENO_PM4TYPES_H @@ -95,6 +95,8 @@ /* A5XX Enable yield in RB only */ #define CP_YIELD_ENABLE 0x1C +#define CP_WHERE_AM_I 0x62 + /* Enable/Disable/Defer A5x global preemption model */ #define CP_PREEMPT_ENABLE_GLOBAL 0x69 diff --git a/drivers/gpu/msm/adreno_ringbuffer.c b/drivers/gpu/msm/adreno_ringbuffer.c index bd0c65dae478..fb026a15314d 100644 --- a/drivers/gpu/msm/adreno_ringbuffer.c +++ b/drivers/gpu/msm/adreno_ringbuffer.c @@ -195,7 +195,7 @@ void adreno_ringbuffer_submit(struct adreno_ringbuffer *rb, adreno_ringbuffer_wptr(adreno_dev, rb); } -int adreno_ringbuffer_submit_spin(struct adreno_ringbuffer *rb, +int adreno_ringbuffer_submit_spin_nosync(struct adreno_ringbuffer *rb, struct adreno_submit_time *time, unsigned int timeout) { struct adreno_device *adreno_dev = ADRENO_RB_DEVICE(rb); @@ -204,6 +204,40 @@ int adreno_ringbuffer_submit_spin(struct adreno_ringbuffer *rb, return adreno_spin_idle(adreno_dev, timeout); } +/* + * adreno_ringbuffer_submit_spin() - Submit the cmds and wait until GPU is idle + * @rb: Pointer to ringbuffer + * @time: Pointer to adreno_submit_time + * @timeout: timeout value in ms + * + * Add commands to the ringbuffer and wait until GPU goes to idle. This routine + * inserts a WHERE_AM_I packet to trigger a shadow rptr update. So, use + * adreno_ringbuffer_submit_spin_nosync() if the previous cmd in the RB is a + * CSY packet because CSY followed by WHERE_AM_I is not legal. + */ +int adreno_ringbuffer_submit_spin(struct adreno_ringbuffer *rb, + struct adreno_submit_time *time, unsigned int timeout) +{ + struct adreno_device *adreno_dev = ADRENO_RB_DEVICE(rb); + struct kgsl_device *device = KGSL_DEVICE(adreno_dev); + unsigned int *cmds; + + /* GPUs which support APRIV feature doesn't require a WHERE_AM_I */ + if (ADRENO_FEATURE(adreno_dev, ADRENO_APRIV) || + adreno_is_a3xx(adreno_dev)) + return adreno_ringbuffer_submit_spin_nosync(rb, time, timeout); + + cmds = adreno_ringbuffer_allocspace(rb, 3); + if (IS_ERR(cmds)) + return PTR_ERR(cmds); + + *cmds++ = cp_packet(adreno_dev, CP_WHERE_AM_I, 2); + cmds += cp_gpuaddr(adreno_dev, cmds, + SCRATCH_RPTR_GPU_ADDR(device, rb->id)); + + return adreno_ringbuffer_submit_spin_nosync(rb, time, timeout); +} + unsigned int *adreno_ringbuffer_allocspace(struct adreno_ringbuffer *rb, unsigned int dwords) { @@ -332,14 +366,11 @@ int adreno_ringbuffer_probe(struct adreno_device *adreno_dev) int status = -ENOMEM; if (!adreno_is_a3xx(adreno_dev)) { - unsigned int priv = KGSL_MEMDESC_RANDOM; - - /* For targets that support it, make the scratch privileged */ - if (ADRENO_FEATURE(adreno_dev, ADRENO_APRIV)) - priv |= KGSL_MEMDESC_PRIVILEGED; + unsigned int priv = + KGSL_MEMDESC_RANDOM | KGSL_MEMDESC_PRIVILEGED; status = kgsl_allocate_global(device, &device->scratch, - PAGE_SIZE, 0, KGSL_MEMDESC_RANDOM, "scratch"); + PAGE_SIZE, 0, priv, "scratch"); if (status != 0) return status; } @@ -539,6 +570,9 @@ adreno_ringbuffer_addcmds(struct adreno_ringbuffer *rb, if (gpudev->preemption_post_ibsubmit && adreno_is_preemption_enabled(adreno_dev)) total_sizedwords += 10; + else if (!adreno_is_a3xx(adreno_dev) && + !ADRENO_FEATURE(adreno_dev, ADRENO_APRIV)) + total_sizedwords += 3; /* * a5xx uses 64 bit memory address. pm4 commands that involve read/write @@ -745,6 +779,12 @@ adreno_ringbuffer_addcmds(struct adreno_ringbuffer *rb, adreno_is_preemption_enabled(adreno_dev)) ringcmds += gpudev->preemption_post_ibsubmit(adreno_dev, ringcmds); + else if (!adreno_is_a3xx(adreno_dev) && + !ADRENO_FEATURE(adreno_dev, ADRENO_APRIV)) { + *ringcmds++ = cp_packet(adreno_dev, CP_WHERE_AM_I, 2); + ringcmds += cp_gpuaddr(adreno_dev, ringcmds, + SCRATCH_RPTR_GPU_ADDR(device, rb->id)); + } /* * If we have more ringbuffer commands than space reserved diff --git a/drivers/gpu/msm/adreno_ringbuffer.h b/drivers/gpu/msm/adreno_ringbuffer.h index 2729e29e34db..8daf0e6a89ed 100644 --- a/drivers/gpu/msm/adreno_ringbuffer.h +++ b/drivers/gpu/msm/adreno_ringbuffer.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2002,2007-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2002,2007-2020, The Linux Foundation. All rights reserved. */ #ifndef __ADRENO_RINGBUFFER_H #define __ADRENO_RINGBUFFER_H @@ -165,6 +165,9 @@ int adreno_ringbuffer_issue_internal_cmds(struct adreno_ringbuffer *rb, void adreno_ringbuffer_submit(struct adreno_ringbuffer *rb, struct adreno_submit_time *time); +int adreno_ringbuffer_submit_spin_nosync(struct adreno_ringbuffer *rb, + struct adreno_submit_time *time, unsigned int timeout); + int adreno_ringbuffer_submit_spin(struct adreno_ringbuffer *rb, struct adreno_submit_time *time, unsigned int timeout); diff --git a/drivers/gpu/msm/adreno_sysfs.c b/drivers/gpu/msm/adreno_sysfs.c index 3fe8c547becf..0e6f1f88b09c 100644 --- a/drivers/gpu/msm/adreno_sysfs.c +++ b/drivers/gpu/msm/adreno_sysfs.c @@ -406,6 +406,7 @@ static ADRENO_SYSFS_BOOL(gpu_llc_slice_enable); static ADRENO_SYSFS_BOOL(gpuhtw_llc_slice_enable); static DEVICE_INT_ATTR(wake_nice, 0644, adreno_wake_nice); +static DEVICE_INT_ATTR(wake_timeout, 0644, adreno_wake_timeout); static ADRENO_SYSFS_BOOL(sptp_pc); static ADRENO_SYSFS_BOOL(lm); @@ -423,6 +424,7 @@ static const struct attribute *_attr_list[] = { &adreno_attr_ft_long_ib_detect.attr.attr, &adreno_attr_ft_hang_intr_status.attr.attr, &dev_attr_wake_nice.attr.attr, + &dev_attr_wake_timeout.attr.attr, &adreno_attr_sptp_pc.attr.attr, &adreno_attr_lm.attr.attr, &adreno_attr_preemption.attr.attr, diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c index 75213d51fd66..c365b3e49e98 100644 --- a/drivers/gpu/msm/kgsl.c +++ b/drivers/gpu/msm/kgsl.c @@ -81,7 +81,7 @@ static inline struct kgsl_pagetable *_get_memdesc_pagetable( static void kgsl_mem_entry_detach_process(struct kgsl_mem_entry *entry); -static const struct file_operations kgsl_fops; +static const struct vm_operations_struct kgsl_gpumem_vm_ops; /* * The memfree list contains the last N blocks of memory that have been freed. @@ -233,9 +233,9 @@ static struct kgsl_mem_entry *kgsl_mem_entry_create(void) kref_init(&entry->refcount); /* put this ref in userspace memory alloc and map ioctls */ kref_get(&entry->refcount); + atomic_set(&entry->map_count, 0); } - atomic_set(&entry->map_count, 0); return entry; } @@ -2340,7 +2340,7 @@ static long gpuobj_free_on_fence(struct kgsl_device_private *dev_priv, } handle = kgsl_sync_fence_async_wait(event.fd, - gpuobj_free_fence_func, entry); + gpuobj_free_fence_func, entry, NULL); if (IS_ERR(handle)) { kgsl_mem_entry_unset_pend(entry); @@ -2437,7 +2437,7 @@ static int check_vma(unsigned long hostptr, u64 size) return false; /* Don't remap memory that we already own */ - if (vma->vm_file && vma->vm_file->f_op == &kgsl_fops) + if (vma->vm_file && vma->vm_ops == &kgsl_gpumem_vm_ops) return false; cur = vma->vm_end; @@ -2594,7 +2594,7 @@ static int kgsl_setup_dmabuf_useraddr(struct kgsl_device *device, * Check to see that this isn't our own memory that we have * already mapped */ - if (vma->vm_file->f_op == &kgsl_fops) { + if (vma->vm_ops == &kgsl_gpumem_vm_ops) { up_read(¤t->mm->mmap_sem); return -EFAULT; } @@ -4973,9 +4973,10 @@ static int kgsl_mmap(struct file *file, struct vm_area_struct *vma) } } - vma->vm_file = file; - - entry->memdesc.vma = vma; + if (entry->memdesc.shmem_filp) { + fput(vma->vm_file); + vma->vm_file = get_file(entry->memdesc.shmem_filp); + } /* * kgsl gets the entry id or the gpu address through vm_pgoff. @@ -5345,7 +5346,7 @@ static void kgsl_core_exit(void) static int __init kgsl_core_init(void) { int result = 0; - struct sched_param param = { .sched_priority = 6 }; + struct sched_param param = { .sched_priority = 2 }; /* alloc major and minor device numbers */ result = alloc_chrdev_region(&kgsl_driver.major, 0, @@ -5408,7 +5409,7 @@ static int __init kgsl_core_init(void) INIT_LIST_HEAD(&kgsl_driver.pagetable_list); kgsl_driver.workqueue = alloc_workqueue("kgsl-workqueue", - WQ_HIGHPRI | WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS, 0); + WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS, 0); kgsl_driver.mem_workqueue = alloc_workqueue("kgsl-mementry", WQ_UNBOUND | WQ_MEM_RECLAIM, 0); diff --git a/drivers/gpu/msm/kgsl.h b/drivers/gpu/msm/kgsl.h index 60323e552f0e..03183cc9208f 100644 --- a/drivers/gpu/msm/kgsl.h +++ b/drivers/gpu/msm/kgsl.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2008-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2008-2021, The Linux Foundation. All rights reserved. */ #ifndef __KGSL_H #define __KGSL_H @@ -57,13 +57,11 @@ /* * SCRATCH MEMORY: The scratch memory is one page worth of data that * is mapped into the GPU. This allows for some 'shared' data between - * the GPU and CPU. For example, it will be used by the GPU to write - * each updated RPTR for each RB. + * the GPU and CPU. * * Used Data: * Offset: Length(bytes): What * 0x0: 4 * KGSL_PRIORITY_MAX_RB_LEVELS: RB0 RPTR - * 0x10: 8 * KGSL_PRIORITY_MAX_RB_LEVELS: RB0 CTXT RESTORE ADDR */ /* Shadow global helpers */ @@ -71,13 +69,6 @@ #define SCRATCH_RPTR_GPU_ADDR(dev, id) \ ((dev)->scratch.gpuaddr + SCRATCH_RPTR_OFFSET(id)) -#define SCRATCH_PREEMPTION_CTXT_RESTORE_ADDR_OFFSET(id) \ - (SCRATCH_RPTR_OFFSET(KGSL_PRIORITY_MAX_RB_LEVELS) + \ - ((id) * sizeof(uint64_t))) -#define SCRATCH_PREEMPTION_CTXT_RESTORE_GPU_ADDR(dev, id) \ - ((dev)->scratch.gpuaddr + \ - SCRATCH_PREEMPTION_CTXT_RESTORE_ADDR_OFFSET(id)) - /* Timestamp window used to detect rollovers (half of integer range) */ #define KGSL_TIMESTAMP_WINDOW 0x80000000 @@ -236,10 +227,6 @@ struct kgsl_memdesc { unsigned int page_count; unsigned int cur_bindings; struct file *shmem_filp; - /** - * @vma: Pointer to the vm_area_struct this memdesc is mapped to - */ - struct vm_area_struct *vma; /** * @lock: Spinlock to protect the pages array */ diff --git a/drivers/gpu/msm/kgsl_drawobj.c b/drivers/gpu/msm/kgsl_drawobj.c index 6254e49e8eb7..ce3fd4b0972f 100644 --- a/drivers/gpu/msm/kgsl_drawobj.c +++ b/drivers/gpu/msm/kgsl_drawobj.c @@ -34,6 +34,18 @@ static struct kmem_cache *memobjs_cache; static struct kmem_cache *sparseobjs_cache; +static void free_fence_names(struct kgsl_drawobj_sync *syncobj) +{ + unsigned int i; + + for (i = 0; i < syncobj->numsyncs; i++) { + struct kgsl_drawobj_sync_event *event = &syncobj->synclist[i]; + + if (event->type == KGSL_CMD_SYNCPOINT_TYPE_FENCE) + kfree(event->info.fences); + } +} + void kgsl_drawobj_destroy_object(struct kref *kref) { struct kgsl_drawobj *drawobj = container_of(kref, @@ -45,6 +57,7 @@ void kgsl_drawobj_destroy_object(struct kref *kref) switch (drawobj->type) { case SYNCOBJ_TYPE: syncobj = SYNCOBJ(drawobj); + free_fence_names(syncobj); kfree(syncobj->synclist); kfree(syncobj); break; @@ -85,6 +98,12 @@ void kgsl_dump_syncpoints(struct kgsl_device *device, break; } case KGSL_CMD_SYNCPOINT_TYPE_FENCE: { + int j; + struct event_fence_info *info = &event->info; + + for (j = 0; j < info->num_fences; j++) + dev_err(device->dev, "[%d] fence: %s\n", + i, info->fences[j].name); break; } } @@ -136,6 +155,12 @@ static void syncobj_timer(struct timer_list *t) i, event->context->id, event->timestamp); break; case KGSL_CMD_SYNCPOINT_TYPE_FENCE: { + int j; + struct event_fence_info *info = &event->info; + + for (j = 0; j < info->num_fences; j++) + dev_err(device->dev, " [%u] FENCE %s\n", + i, info->fences[j].name); break; } } @@ -324,6 +349,11 @@ EXPORT_SYMBOL(kgsl_drawobj_destroy); static bool drawobj_sync_fence_func(void *priv) { struct kgsl_drawobj_sync_event *event = priv; + int i; + + for (i = 0; i < event->info.num_fences; i++) + trace_syncpoint_fence_expire(event->syncobj, + event->info.fences[i].name); /* * Only call kgsl_drawobj_put() if it's not marked for cancellation @@ -349,7 +379,7 @@ static int drawobj_add_sync_fence(struct kgsl_device *device, struct kgsl_cmd_syncpoint_fence *sync = priv; struct kgsl_drawobj *drawobj = DRAWOBJ(syncobj); struct kgsl_drawobj_sync_event *event; - unsigned int id; + unsigned int id, i; kref_get(&drawobj->refcount); @@ -366,7 +396,8 @@ static int drawobj_add_sync_fence(struct kgsl_device *device, set_bit(event->id, &syncobj->pending); event->handle = kgsl_sync_fence_async_wait(sync->fd, - drawobj_sync_fence_func, event); + drawobj_sync_fence_func, event, + &event->info); if (IS_ERR_OR_NULL(event->handle)) { int ret = PTR_ERR(event->handle); @@ -386,6 +417,9 @@ static int drawobj_add_sync_fence(struct kgsl_device *device, return ret; } + for (i = 0; i < event->info.num_fences; i++) + trace_syncpoint_fence(syncobj, event->info.fences[i].name); + return 0; } diff --git a/drivers/gpu/msm/kgsl_drawobj.h b/drivers/gpu/msm/kgsl_drawobj.h index c18d593ba34e..7f3dd2624a5f 100644 --- a/drivers/gpu/msm/kgsl_drawobj.h +++ b/drivers/gpu/msm/kgsl_drawobj.h @@ -100,6 +100,17 @@ struct kgsl_drawobj_sync { unsigned long timeout_jiffies; }; +#define KGSL_FENCE_NAME_LEN 74 + +struct fence_info { + char name[KGSL_FENCE_NAME_LEN]; +}; + +struct event_fence_info { + struct fence_info *fences; + int num_fences; +}; + /** * struct kgsl_drawobj_sync_event * @id: identifer (positiion within the pending bitmap) @@ -120,6 +131,7 @@ struct kgsl_drawobj_sync_event { unsigned int timestamp; struct kgsl_sync_fence_cb *handle; struct kgsl_device *device; + struct event_fence_info info; }; /** diff --git a/drivers/gpu/msm/kgsl_gmu.c b/drivers/gpu/msm/kgsl_gmu.c index 22207f326284..a9fe84909352 100644 --- a/drivers/gpu/msm/kgsl_gmu.c +++ b/drivers/gpu/msm/kgsl_gmu.c @@ -1612,7 +1612,7 @@ static int gmu_start(struct kgsl_device *device) /* Vote for minimal DDR BW for GMU to init */ ret = msm_bus_scale_client_update_request(gmu->pcl, - pwr->pwrlevels[pwr->num_pwrlevels - 1].bus_min); + pwr->pwrlevels[pwr->default_pwrlevel].bus_min); if (ret) dev_err(&gmu->pdev->dev, "Failed to allocate gmu b/w: %d\n", ret); diff --git a/drivers/gpu/msm/kgsl_ioctl.c b/drivers/gpu/msm/kgsl_ioctl.c index 9fae56d65bd3..8df9166bec50 100644 --- a/drivers/gpu/msm/kgsl_ioctl.c +++ b/drivers/gpu/msm/kgsl_ioctl.c @@ -5,7 +5,6 @@ #include "kgsl_device.h" #include "kgsl_sync.h" -#include "adreno.h" static const struct kgsl_ioctl kgsl_ioctl_funcs[] = { KGSL_IOCTL_FUNC(IOCTL_KGSL_DEVICE_GETPROPERTY, @@ -153,13 +152,8 @@ long kgsl_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) { struct kgsl_device_private *dev_priv = filep->private_data; struct kgsl_device *device = dev_priv->device; - struct adreno_device *adreno_dev = ADRENO_DEVICE(device); long ret; - if (cmd == IOCTL_KGSL_GPU_COMMAND && - READ_ONCE(device->state) != KGSL_STATE_ACTIVE) - kgsl_schedule_work(&adreno_dev->pwr_on_work); - ret = kgsl_ioctl_helper(filep, cmd, arg, kgsl_ioctl_funcs, ARRAY_SIZE(kgsl_ioctl_funcs)); diff --git a/drivers/gpu/msm/kgsl_iommu.c b/drivers/gpu/msm/kgsl_iommu.c index d8469bbec464..fd56442ca58b 100644 --- a/drivers/gpu/msm/kgsl_iommu.c +++ b/drivers/gpu/msm/kgsl_iommu.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2011-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2011-2021, The Linux Foundation. All rights reserved. */ #include @@ -210,8 +210,9 @@ static void kgsl_iommu_remove_global(struct kgsl_mmu *mmu, static void kgsl_iommu_add_global(struct kgsl_mmu *mmu, struct kgsl_memdesc *memdesc, const char *name) { - u32 bit, start = 0; + u32 bit; u64 size = kgsl_memdesc_footprint(memdesc); + int start = 0; if (memdesc->gpuaddr != 0) return; diff --git a/drivers/gpu/msm/kgsl_pwrctrl.c b/drivers/gpu/msm/kgsl_pwrctrl.c index 99eac8aeb5ba..0832f1993047 100644 --- a/drivers/gpu/msm/kgsl_pwrctrl.c +++ b/drivers/gpu/msm/kgsl_pwrctrl.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2010-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2010-2021, The Linux Foundation. All rights reserved. */ #include @@ -166,7 +166,8 @@ static void _ab_buslevel_update(struct kgsl_pwrctrl *pwr, * constraint if one exists. */ static unsigned int _adjust_pwrlevel(struct kgsl_pwrctrl *pwr, int level, - struct kgsl_pwr_constraint *pwrc) + struct kgsl_pwr_constraint *pwrc, + int popp) { unsigned int max_pwrlevel = max_t(unsigned int, pwr->thermal_pwrlevel, pwr->max_pwrlevel); @@ -194,6 +195,9 @@ static unsigned int _adjust_pwrlevel(struct kgsl_pwrctrl *pwr, int level, break; } + if (popp && (max_pwrlevel < pwr->active_pwrlevel)) + max_pwrlevel = pwr->active_pwrlevel; + if (level < max_pwrlevel) return max_pwrlevel; if (level > min_pwrlevel) @@ -574,7 +578,8 @@ unsigned int kgsl_pwrctrl_adjust_pwrlevel(struct kgsl_device *device, * Adjust the power level if required by thermal, max/min, * constraints, etc */ - return _adjust_pwrlevel(pwr, new_level, &pwr->constraint); + return _adjust_pwrlevel(pwr, new_level, &pwr->constraint, + device->pwrscale.popp_level); } /** @@ -717,7 +722,7 @@ void kgsl_pwrctrl_set_constraint(struct kgsl_device *device, if (device == NULL || pwrc == NULL) return; constraint = _adjust_pwrlevel(&device->pwrctrl, - device->pwrctrl.active_pwrlevel, pwrc); + device->pwrctrl.active_pwrlevel, pwrc, 0); pwrc_old = &device->pwrctrl.constraint; /* @@ -1054,8 +1059,6 @@ static ssize_t __timer_store(struct device *dev, struct device_attribute *attr, struct kgsl_device *device = dev_get_drvdata(dev); int ret; - return count; - ret = kgsl_sysfs_store(buf, &val); if (ret) return ret; @@ -1323,6 +1326,75 @@ static ssize_t bus_split_store(struct device *dev, return count; } +static ssize_t default_pwrlevel_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kgsl_device *device = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", + device->pwrctrl.default_pwrlevel); +} + +static ssize_t default_pwrlevel_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kgsl_device *device = dev_get_drvdata(dev); + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + struct kgsl_pwrscale *pwrscale = &device->pwrscale; + int ret; + unsigned int level = 0; + + ret = kgsl_sysfs_store(buf, &level); + if (ret) + return ret; + + if (level > pwr->num_pwrlevels - 2) + goto done; + + mutex_lock(&device->mutex); + pwr->default_pwrlevel = level; + pwrscale->gpu_profile.profile.initial_freq + = pwr->pwrlevels[level].gpu_freq; + + mutex_unlock(&device->mutex); +done: + return count; +} + + +static ssize_t popp_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int val = 0; + struct kgsl_device *device = dev_get_drvdata(dev); + int ret; + + ret = kgsl_sysfs_store(buf, &val); + if (ret) + return ret; + + mutex_lock(&device->mutex); + if (val) + set_bit(POPP_ON, &device->pwrscale.popp_state); + else + clear_bit(POPP_ON, &device->pwrscale.popp_state); + mutex_unlock(&device->mutex); + + return count; +} + +static ssize_t popp_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct kgsl_device *device = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", + test_bit(POPP_ON, &device->pwrscale.popp_state)); +} + static ssize_t gpu_model_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1452,23 +1524,29 @@ static ssize_t temp_show(struct device *dev, char *buf) { struct kgsl_device *device = dev_get_drvdata(dev); - struct kgsl_pwrctrl *pwr = &device->pwrctrl; + struct device *_dev; struct thermal_zone_device *thermal_dev; - int ret, temperature = 0; + int temperature = INT_MIN, max_temp = INT_MIN; + const char *name; + struct property *prop; - if (!pwr->tzone_name) - return 0; + _dev = &device->pdev->dev; - thermal_dev = thermal_zone_get_zone_by_name((char *)pwr->tzone_name); - if (thermal_dev == NULL) - return 0; + of_property_for_each_string(_dev->of_node, + "qcom,tzone-names", prop, name) { + thermal_dev = thermal_zone_get_zone_by_name(name); - ret = thermal_zone_get_temp(thermal_dev, &temperature); - if (ret) - return 0; + if (IS_ERR(thermal_dev)) + continue; + + if (thermal_zone_get_temp(thermal_dev, &temperature)) + continue; + + max_temp = max(temperature, max_temp); + } return scnprintf(buf, PAGE_SIZE, "%d\n", - temperature); + max_temp); } static ssize_t pwrscale_store(struct device *dev, @@ -1521,6 +1599,8 @@ static DEVICE_ATTR_RW(force_clk_on); static DEVICE_ATTR_RW(force_bus_on); static DEVICE_ATTR_RW(force_rail_on); static DEVICE_ATTR_RW(bus_split); +static DEVICE_ATTR_RW(default_pwrlevel); +static DEVICE_ATTR_RW(popp); static DEVICE_ATTR_RW(force_no_nap); static DEVICE_ATTR_RO(gpu_model); static DEVICE_ATTR_RO(gpu_busy_percentage); @@ -1548,6 +1628,8 @@ static const struct attribute *pwrctrl_attr_list[] = { &dev_attr_force_rail_on.attr, &dev_attr_force_no_nap.attr, &dev_attr_bus_split.attr, + &dev_attr_default_pwrlevel.attr, + &dev_attr_popp.attr, &dev_attr_gpu_model.attr, &dev_attr_gpu_busy_percentage.attr, &dev_attr_min_clock_mhz.attr, @@ -2351,10 +2433,6 @@ int kgsl_pwrctrl_init(struct kgsl_device *device) kgsl_pwrctrl_vbif_init(device); - /* temperature sensor name */ - of_property_read_string(pdev->dev.of_node, "qcom,tzone-name", - &pwr->tzone_name); - return result; error_cleanup_bus_ib: @@ -2527,8 +2605,10 @@ static int kgsl_pwrctrl_enable(struct kgsl_device *device) if (pwr->wakeup_maxpwrlevel) { level = pwr->max_pwrlevel; pwr->wakeup_maxpwrlevel = 0; + } else if (kgsl_popp_check(device)) { + level = pwr->active_pwrlevel; } else { - level = pwr->num_pwrlevels - 1; + level = pwr->default_pwrlevel; } kgsl_pwrctrl_pwrlevel_change(device, level); @@ -3328,7 +3408,7 @@ EXPORT_SYMBOL(kgsl_pwr_limits_get_freq); int kgsl_pwrctrl_set_default_gpu_pwrlevel(struct kgsl_device *device) { struct kgsl_pwrctrl *pwr = &device->pwrctrl; - unsigned int new_level = pwr->num_pwrlevels - 1; + unsigned int new_level = pwr->default_pwrlevel; unsigned int old_level = pwr->active_pwrlevel; /* diff --git a/drivers/gpu/msm/kgsl_pwrctrl.h b/drivers/gpu/msm/kgsl_pwrctrl.h index 2e520ce23a07..72577d32246c 100644 --- a/drivers/gpu/msm/kgsl_pwrctrl.h +++ b/drivers/gpu/msm/kgsl_pwrctrl.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2010-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2010-2021, The Linux Foundation. All rights reserved. */ #ifndef __KGSL_PWRCTRL_H #define __KGSL_PWRCTRL_H @@ -51,9 +51,9 @@ /* * The effective duration of qos request in usecs at queue time. * After timeout, qos request is cancelled automatically. - * Kept 58ms default, inline with default GPU idle time. + * Kept 80ms default, inline with default GPU idle time. */ -#define KGSL_L2PC_QUEUE_TIMEOUT (58 * 1000) +#define KGSL_L2PC_QUEUE_TIMEOUT (80 * 1000) /* * The effective duration of qos request in usecs at wakeup time. @@ -139,6 +139,7 @@ struct gpu_cx_ipeak_client { * @previous_pwrlevel - The power level before transition * @thermal_pwrlevel - maximum powerlevel constraint from thermal * @thermal_pwrlevel_floor - minimum powerlevel constraint from thermal + * @default_pwrlevel - device wake up power level * @max_pwrlevel - maximum allowable powerlevel per the user * @min_pwrlevel - minimum allowable powerlevel per the user * @num_pwrlevels - number of available power levels @@ -156,6 +157,7 @@ struct gpu_cx_ipeak_client { * @pm_qos_req_dma - the power management quality of service structure * @pm_qos_active_latency - allowed CPU latency in microseconds when active * @pm_qos_cpu_mask_latency - allowed CPU mask latency in microseconds + * @input_disable - To disable GPU wakeup on touch input event * @pm_qos_wakeup_latency - allowed CPU latency in microseconds during wakeup * @bus_control - true if the bus calculation is independent * @bus_mod - modifier from the current power level for the bus vote @@ -178,7 +180,6 @@ struct gpu_cx_ipeak_client { * @cx_ipeak_pwr_limit - pointer to the cx_ipeak limits node * isense_clk_indx - index of isense clock, 0 if no isense * isense_clk_on_level - isense clock rate is XO rate below this level. - * tzone_name - pointer to thermal zone name of GPU temperature sensor * gpu_cx_ipeak_client - CX Ipeak clients used by GPU */ @@ -196,6 +197,7 @@ struct kgsl_pwrctrl { unsigned int previous_pwrlevel; unsigned int thermal_pwrlevel; unsigned int thermal_pwrlevel_floor; + unsigned int default_pwrlevel; unsigned int wakeup_maxpwrlevel; unsigned int max_pwrlevel; unsigned int min_pwrlevel; @@ -215,6 +217,7 @@ struct kgsl_pwrctrl { unsigned int pm_qos_active_latency; unsigned int pm_qos_cpu_mask_latency; unsigned int pm_qos_wakeup_latency; + bool input_disable; bool bus_control; int bus_mod; unsigned int bus_percent_ab; @@ -236,7 +239,6 @@ struct kgsl_pwrctrl { struct kgsl_pwr_limit *cx_ipeak_pwr_limit; unsigned int gpu_bimc_int_clk_freq; bool gpu_bimc_interface_enabled; - const char *tzone_name; struct gpu_cx_ipeak_client gpu_ipeak_client[2]; }; diff --git a/drivers/gpu/msm/kgsl_pwrscale.c b/drivers/gpu/msm/kgsl_pwrscale.c index 20ab59e696e2..4fdb5e2edaff 100644 --- a/drivers/gpu/msm/kgsl_pwrscale.c +++ b/drivers/gpu/msm/kgsl_pwrscale.c @@ -10,6 +10,24 @@ #include "kgsl_pwrscale.h" #include "kgsl_trace.h" +/* + * "SLEEP" is generic counting both NAP & SLUMBER + * PERIODS generally won't exceed 9 for the relavent 150msec + * window, but can be significantly smaller and still POPP + * pushable in cases where SLUMBER is involved. Hence the + * additional reliance on PERCENT to make sure a reasonable + * amount of down-time actually exists. + */ +#define MIN_SLEEP_PERIODS 3 +#define MIN_SLEEP_PERCENT 5 + +static struct kgsl_popp popp_param[POPP_MAX] = { + {0, 0}, + {-5, 20}, + {-5, 0}, + {0, 0}, +}; + /** * struct kgsl_midframe_info - midframe power stats sampling info * @timer - midframe sampling timer @@ -41,10 +59,15 @@ static struct devfreq_dev_status last_status = { .private_data = &last_xstats }; */ void kgsl_pwrscale_sleep(struct kgsl_device *device) { + struct kgsl_pwrscale *psc = &device->pwrscale; + if (!device->pwrscale.enabled) return; device->pwrscale.on_time = 0; + psc->popp_level = 0; + clear_bit(POPP_PUSH, &device->pwrscale.popp_state); + /* to call devfreq_suspend_device() from a kernel thread */ queue_work(device->pwrscale.devfreq_wq, &device->pwrscale.devfreq_suspend_ws); @@ -118,6 +141,18 @@ void kgsl_pwrscale_update_stats(struct kgsl_device *device) struct kgsl_power_stats stats; device->ftbl->power_stats(device, &stats); + if (psc->popp_level) { + u64 x = stats.busy_time; + u64 y = stats.ram_time; + + do_div(x, 100); + do_div(y, 100); + x *= popp_param[psc->popp_level].gpu_x; + y *= popp_param[psc->popp_level].ddr_y; + trace_kgsl_popp_mod(device, x, y); + stats.busy_time += x; + stats.ram_time += y; + } device->pwrscale.accum_stats.busy_time += stats.busy_time; device->pwrscale.accum_stats.ram_time += stats.ram_time; device->pwrscale.accum_stats.ram_wait += stats.ram_wait; @@ -251,7 +286,7 @@ void kgsl_pwrscale_enable(struct kgsl_device *device) * run at default level; */ kgsl_pwrctrl_pwrlevel_change(device, - device->pwrctrl.num_pwrlevels - 1); + device->pwrctrl.default_pwrlevel); device->pwrscale.enabled = false; } } @@ -272,6 +307,194 @@ static int _thermal_adjust(struct kgsl_pwrctrl *pwr, int level) return level; } +/* + * Use various metrics including level stability, NAP intervals, and + * overall GPU freq / DDR freq combination to decide if POPP should + * be activated. + */ +static bool popp_stable(struct kgsl_device *device) +{ + s64 t; + s64 nap_time = 0; + s64 go_time = 0; + int i, index; + int nap = 0; + s64 percent_nap = 0; + struct kgsl_pwr_event *e; + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + struct kgsl_pwrscale *psc = &device->pwrscale; + + if (!test_bit(POPP_ON, &psc->popp_state)) + return false; + + /* If already pushed or running naturally at min don't push further */ + if (test_bit(POPP_PUSH, &psc->popp_state)) + return false; + if (!psc->popp_level && + (pwr->active_pwrlevel == pwr->min_pwrlevel)) + return false; + if (psc->history[KGSL_PWREVENT_STATE].events == NULL) + return false; + + t = ktime_to_ms(ktime_get()); + /* Check for recent NAP statistics: NAPping regularly and well? */ + if (pwr->active_pwrlevel == 0) { + index = psc->history[KGSL_PWREVENT_STATE].index; + i = index > 0 ? (index - 1) : + (psc->history[KGSL_PWREVENT_STATE].size - 1); + while (i != index) { + e = &psc->history[KGSL_PWREVENT_STATE].events[i]; + if (e->data == KGSL_STATE_NAP || + e->data == KGSL_STATE_SLUMBER) { + if (ktime_to_ms(e->start) + STABLE_TIME > t) { + nap++; + nap_time += e->duration; + } + } else if (e->data == KGSL_STATE_ACTIVE) { + if (ktime_to_ms(e->start) + STABLE_TIME > t) + go_time += e->duration; + } + if (i == 0) + i = psc->history[KGSL_PWREVENT_STATE].size - 1; + else + i--; + } + if (nap_time && go_time) { + percent_nap = 100 * nap_time; + div64_s64(percent_nap, nap_time + go_time); + } + trace_kgsl_popp_nap(device, (int)nap_time / 1000, nap, + percent_nap); + /* If running high at turbo, don't push */ + if (nap < MIN_SLEEP_PERIODS || percent_nap < MIN_SLEEP_PERCENT) + return false; + } + + /* Finally check that there hasn't been a recent change */ + if ((device->pwrscale.freq_change_time + STABLE_TIME) < t) { + device->pwrscale.freq_change_time = t; + return true; + } + return false; +} + +bool kgsl_popp_check(struct kgsl_device *device) +{ + int i; + unsigned int index; + struct kgsl_pwrscale *psc = &device->pwrscale; + struct kgsl_pwr_event *e; + + if (!test_bit(POPP_ON, &psc->popp_state)) + return false; + if (!test_bit(POPP_PUSH, &psc->popp_state)) + return false; + if (psc->history[KGSL_PWREVENT_STATE].events == NULL) { + clear_bit(POPP_PUSH, &psc->popp_state); + return false; + } + index = psc->history[KGSL_PWREVENT_STATE].index; + + e = &psc->history[KGSL_PWREVENT_STATE].events[index]; + if (e->data == KGSL_STATE_SLUMBER) + e->duration = ktime_us_delta(ktime_get(), e->start); + + /* If there's been a long SLUMBER in recent history, clear the _PUSH */ + for (i = 0; i < psc->history[KGSL_PWREVENT_STATE].size; i++) { + e = &psc->history[KGSL_PWREVENT_STATE].events[i]; + if ((e->data == KGSL_STATE_SLUMBER) && + (e->duration > POPP_RESET_TIME)) { + clear_bit(POPP_PUSH, &psc->popp_state); + return false; + } + } + return true; +} + +/* + * The GPU has been running at the current frequency for a while. Attempt + * to lower the frequency for boarderline cases. + */ +static void popp_trans1(struct kgsl_device *device) +{ + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + struct kgsl_pwrlevel *pl = &pwr->pwrlevels[pwr->active_pwrlevel]; + struct kgsl_pwrscale *psc = &device->pwrscale; + int old_level = psc->popp_level; + + switch (old_level) { + case 0: + psc->popp_level = 2; + /* If the current level has a high default bus don't push it */ + if (pl->bus_freq == pl->bus_max) + pwr->bus_mod = 1; + kgsl_pwrctrl_pwrlevel_change(device, pwr->active_pwrlevel + 1); + break; + case 1: + case 2: + psc->popp_level++; + break; + case 3: + set_bit(POPP_PUSH, &psc->popp_state); + psc->popp_level = 0; + break; + case POPP_MAX: + default: + psc->popp_level = 0; + break; + } + + trace_kgsl_popp_level(device, old_level, psc->popp_level); +} + +/* + * The GPU DCVS algorithm recommends a level change. Apply any + * POPP restrictions and update the level accordingly + */ +static int popp_trans2(struct kgsl_device *device, int level) +{ + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + struct kgsl_pwrscale *psc = &device->pwrscale; + int old_level = psc->popp_level; + + if (!test_bit(POPP_ON, &psc->popp_state)) + return level; + + clear_bit(POPP_PUSH, &psc->popp_state); + /* If the governor recommends going down, do it! */ + if (pwr->active_pwrlevel < level) { + psc->popp_level = 0; + trace_kgsl_popp_level(device, old_level, psc->popp_level); + return level; + } + + switch (psc->popp_level) { + case 0: + /* If the feature isn't engaged, go up immediately */ + break; + case 1: + /* Turn off mitigation, and go up a level */ + psc->popp_level = 0; + break; + case 2: + case 3: + /* Try a more aggressive mitigation */ + psc->popp_level--; + level++; + /* Update the stable timestamp */ + device->pwrscale.freq_change_time = ktime_to_ms(ktime_get()); + break; + case POPP_MAX: + default: + psc->popp_level = 0; + break; + } + + trace_kgsl_popp_level(device, old_level, psc->popp_level); + + return level; +} + #ifdef DEVFREQ_FLAG_WAKEUP_MAXFREQ static inline bool _check_maxfreq(u32 flags) { @@ -338,11 +561,13 @@ int kgsl_devfreq_target(struct device *dev, unsigned long *freq, u32 flags) if (pwr->thermal_cycle == CYCLE_ACTIVE) level = _thermal_adjust(pwr, i); else - level = i; + level = popp_trans2(device, i); break; } if (level != pwr->active_pwrlevel) kgsl_pwrctrl_pwrlevel_change(device, level); + } else if (popp_stable(device)) { + popp_trans1(device); } *freq = kgsl_pwrctrl_active_freq(pwr); @@ -722,7 +947,7 @@ int kgsl_pwrscale_init(struct device *dev, const char *governor) srcu_init_notifier_head(&pwrscale->nh); profile->initial_freq = - pwr->pwrlevels[pwr->num_pwrlevels - 1].gpu_freq; + pwr->pwrlevels[pwr->default_pwrlevel].gpu_freq; /* Let's start with 10 ms and tune in later */ profile->polling_ms = 10; diff --git a/drivers/gpu/msm/kgsl_pwrscale.h b/drivers/gpu/msm/kgsl_pwrscale.h index 8072f6c0abd4..af07d5b85a08 100644 --- a/drivers/gpu/msm/kgsl_pwrscale.h +++ b/drivers/gpu/msm/kgsl_pwrscale.h @@ -16,7 +16,8 @@ #define KGSL_PWREVENT_STATE 0 #define KGSL_PWREVENT_GPU_FREQ 1 #define KGSL_PWREVENT_BUS_FREQ 2 -#define KGSL_PWREVENT_MAX 3 +#define KGSL_PWREVENT_POPP 3 +#define KGSL_PWREVENT_MAX 4 /** * Amount of time running at a level to be considered @@ -24,6 +25,21 @@ */ #define STABLE_TIME 150 +/* Amount of idle time needed to re-set stability in usec */ +#define POPP_RESET_TIME 1000000 + +/* Number of POPP levels */ +#define POPP_MAX 4 + +/* POPP state bits */ +#define POPP_ON BIT(0) +#define POPP_PUSH BIT(1) + +struct kgsl_popp { + int gpu_x; + int ddr_y; +}; + struct kgsl_power_stats { u64 busy_time; u64 ram_time; @@ -54,7 +70,7 @@ struct kgsl_pwr_history { * @enabled - Whether or not power scaling is enabled * @time - Last submitted sample timestamp * @on_time - Timestamp when gpu busy begins - * @freq_change_time - Timestamp of last freq change + * @freq_change_time - Timestamp of last freq change or popp update * @nh - Notifier for the partner devfreq bus device * @devfreq_wq - Main devfreq workqueue * @devfreq_suspend_ws - Pass device suspension to devfreq @@ -63,6 +79,8 @@ struct kgsl_pwr_history { * @next_governor_call - Timestamp after which the governor may be notified of * a new sample * @history - History of power events with timestamps and durations + * @popp_level - Current level of POPP mitigation + * @popp_state - Control state for POPP, on/off, recently pushed, etc * @cooling_dev - Thermal cooling device handle * @ctxt_aware_enable - Whether or not ctxt aware DCVS feature is enabled * @ctxt_aware_busy_penalty - The time in microseconds required to trigger @@ -88,6 +106,8 @@ struct kgsl_pwrscale { struct work_struct devfreq_notify_ws; ktime_t next_governor_call; struct kgsl_pwr_history history[KGSL_PWREVENT_MAX]; + int popp_level; + unsigned long popp_state; struct thermal_cooling_device *cooling_dev; bool ctxt_aware_enable; unsigned int ctxt_aware_target_pwrlevel; @@ -119,6 +139,9 @@ int kgsl_busmon_get_dev_status(struct device *dev, struct devfreq_dev_status *stat); int kgsl_busmon_get_cur_freq(struct device *dev, unsigned long *freq); +bool kgsl_popp_check(struct kgsl_device *device); + + #define KGSL_PWRSCALE_INIT(_priv_data) { \ .enabled = true, \ .gpu_profile = { \ @@ -138,5 +161,6 @@ int kgsl_busmon_get_cur_freq(struct device *dev, unsigned long *freq); .history[KGSL_PWREVENT_STATE].size = 20, \ .history[KGSL_PWREVENT_GPU_FREQ].size = 3, \ .history[KGSL_PWREVENT_BUS_FREQ].size = 5, \ + .history[KGSL_PWREVENT_POPP].size = 5, \ } #endif diff --git a/drivers/gpu/msm/kgsl_reclaim.c b/drivers/gpu/msm/kgsl_reclaim.c index 84ca92d30668..30d9bfbdb0ba 100644 --- a/drivers/gpu/msm/kgsl_reclaim.c +++ b/drivers/gpu/msm/kgsl_reclaim.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. */ #include @@ -196,12 +196,21 @@ ssize_t kgsl_proc_max_reclaim_limit_show(struct device *dev, static int kgsl_reclaim_callback(struct notifier_block *nb, unsigned long pid, void *data) { - struct kgsl_process_private *process; + struct kgsl_process_private *p, *process = NULL; struct kgsl_mem_entry *entry; struct kgsl_memdesc *memdesc; int valid_entry, next = 0, ret; - process = kgsl_process_private_find(pid); + spin_lock(&kgsl_driver.proclist_lock); + list_for_each_entry(p, &kgsl_driver.process_list, list) { + if ((unsigned long)p->pid == pid) { + if (kgsl_process_private_get(p)) + process = p; + break; + } + } + spin_unlock(&kgsl_driver.proclist_lock); + if (!process) return NOTIFY_OK; @@ -269,8 +278,7 @@ static int kgsl_reclaim_callback(struct notifier_block *nb, memdesc->priv |= KGSL_MEMDESC_RECLAIMED; ret = reclaim_address_space - (memdesc->shmem_filp->f_mapping, - data, memdesc->vma); + (memdesc->shmem_filp->f_mapping, data); memdesc->reclaimed_page_count += memdesc->page_count; atomic_add(memdesc->page_count, diff --git a/drivers/gpu/msm/kgsl_rgmu.c b/drivers/gpu/msm/kgsl_rgmu.c index d449dfa6f392..ff89170831be 100644 --- a/drivers/gpu/msm/kgsl_rgmu.c +++ b/drivers/gpu/msm/kgsl_rgmu.c @@ -174,7 +174,7 @@ static int rgmu_enable_clks(struct kgsl_device *device) /* Let us set gpu clk to default power level */ ret = rgmu_clk_set_rate(rgmu->gpu_clk, - rgmu->gpu_freqs[pwr->num_pwrlevels - 1]); + rgmu->gpu_freqs[pwr->default_pwrlevel]); if (ret) return ret; diff --git a/drivers/gpu/msm/kgsl_sync.c b/drivers/gpu/msm/kgsl_sync.c index 5e3d7e450aaa..a90b9e359571 100644 --- a/drivers/gpu/msm/kgsl_sync.c +++ b/drivers/gpu/msm/kgsl_sync.c @@ -404,8 +404,54 @@ static void kgsl_sync_fence_callback(struct dma_fence *fence, } } +static void kgsl_get_fence_names(struct dma_fence *fence, + struct event_fence_info *info_ptr) +{ + unsigned int num_fences; + struct dma_fence **fences; + struct dma_fence_array *array; + int i; + + if (!info_ptr) + return; + + array = to_dma_fence_array(fence); + + if (array != NULL) { + num_fences = array->num_fences; + fences = array->fences; + } else { + num_fences = 1; + fences = &fence; + } + + info_ptr->fences = kcalloc(num_fences, sizeof(struct fence_info), + GFP_ATOMIC); + if (info_ptr->fences == NULL) + return; + + info_ptr->num_fences = num_fences; + + for (i = 0; i < num_fences; i++) { + struct dma_fence *f = fences[i]; + struct fence_info *fi = &info_ptr->fences[i]; + int len; + + len = scnprintf(fi->name, sizeof(fi->name), "%s %s", + f->ops->get_driver_name(f), + f->ops->get_timeline_name(f)); + + if (f->ops->fence_value_str) { + len += scnprintf(fi->name + len, sizeof(fi->name) - len, + ": "); + f->ops->fence_value_str(f, fi->name + len, + sizeof(fi->name) - len); + } + } +} + struct kgsl_sync_fence_cb *kgsl_sync_fence_async_wait(int fd, - bool (*func)(void *priv), void *priv) + bool (*func)(void *priv), void *priv, struct event_fence_info *info_ptr) { struct kgsl_sync_fence_cb *kcb; struct dma_fence *fence; @@ -426,6 +472,8 @@ struct kgsl_sync_fence_cb *kgsl_sync_fence_async_wait(int fd, kcb->priv = priv; kcb->func = func; + kgsl_get_fence_names(fence, info_ptr); + /* if status then error or signaled */ status = dma_fence_add_callback(fence, &kcb->fence_cb, kgsl_sync_fence_callback); diff --git a/drivers/gpu/msm/kgsl_sync.h b/drivers/gpu/msm/kgsl_sync.h index c5a6cccbe3af..43209b1532cb 100644 --- a/drivers/gpu/msm/kgsl_sync.h +++ b/drivers/gpu/msm/kgsl_sync.h @@ -85,7 +85,8 @@ void kgsl_sync_timeline_destroy(struct kgsl_context *context); void kgsl_sync_timeline_put(struct kgsl_sync_timeline *ktimeline); struct kgsl_sync_fence_cb *kgsl_sync_fence_async_wait(int fd, - bool (*func)(void *priv), void *priv); + bool (*func)(void *priv), void *priv, + struct event_fence_info *info_ptr); void kgsl_sync_fence_async_cancel(struct kgsl_sync_fence_cb *kcb); @@ -127,7 +128,8 @@ static inline void kgsl_sync_timeline_put(struct kgsl_sync_timeline *ktimeline) static inline struct kgsl_sync_fence_cb *kgsl_sync_fence_async_wait(int fd, - bool (*func)(void *priv), void *priv) + bool (*func)(void *priv), void *priv, + struct event_fence_info *info_ptr) { return NULL; } diff --git a/drivers/gpu/msm/kgsl_trace.h b/drivers/gpu/msm/kgsl_trace.h index 566d299823de..501d97eb0e3a 100644 --- a/drivers/gpu/msm/kgsl_trace.h +++ b/drivers/gpu/msm/kgsl_trace.h @@ -866,6 +866,78 @@ TRACE_EVENT(kgsl_regwrite, ) ); +TRACE_EVENT(kgsl_popp_level, + + TP_PROTO(struct kgsl_device *device, int level1, int level2), + + TP_ARGS(device, level1, level2), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(int, level1) + __field(int, level2) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->level1 = level1; + __entry->level2 = level2; + ), + + TP_printk( + "d_name=%s old level=%d new level=%d", + __get_str(device_name), __entry->level1, __entry->level2) +); + +TRACE_EVENT(kgsl_popp_mod, + + TP_PROTO(struct kgsl_device *device, int x, int y), + + TP_ARGS(device, x, y), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(int, x) + __field(int, y) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->x = x; + __entry->y = y; + ), + + TP_printk( + "d_name=%s GPU busy mod=%d bus busy mod=%d", + __get_str(device_name), __entry->x, __entry->y) +); + +TRACE_EVENT(kgsl_popp_nap, + + TP_PROTO(struct kgsl_device *device, int t, int nap, int percent), + + TP_ARGS(device, t, nap, percent), + + TP_STRUCT__entry( + __string(device_name, device->name) + __field(int, t) + __field(int, nap) + __field(int, percent) + ), + + TP_fast_assign( + __assign_str(device_name, device->name); + __entry->t = t; + __entry->nap = nap; + __entry->percent = percent; + ), + + TP_printk( + "d_name=%s nap time=%d number of naps=%d percentage=%d", + __get_str(device_name), __entry->t, __entry->nap, + __entry->percent) +); + TRACE_EVENT(kgsl_register_event, TP_PROTO(unsigned int id, unsigned int timestamp, void *func), TP_ARGS(id, timestamp, func), diff --git a/drivers/hwtracing/coresight/coresight-cti.c b/drivers/hwtracing/coresight/coresight-cti.c index e901137c2fb1..6b42201c37f1 100644 --- a/drivers/hwtracing/coresight/coresight-cti.c +++ b/drivers/hwtracing/coresight/coresight-cti.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2013-2021, The Linux Foundation. All rights reserved. */ #include @@ -87,8 +87,6 @@ struct cti_drvdata { struct coresight_cti cti; int refcnt; int cpu; - unsigned int trig_num_max; - unsigned int ch_num_max; bool cti_save; bool cti_hwclk; bool l2_off; @@ -1361,9 +1359,18 @@ static ssize_t show_info_show(struct device *dev, struct device_attribute *attr, { struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); ssize_t size = 0; + unsigned int ctidevid, trig_num_max, ch_num_max; + + pm_runtime_get_sync(drvdata->dev); + + ctidevid = cti_readl(drvdata, DEVID); + trig_num_max = (ctidevid & GENMASK(15, 8)) >> 8; + ch_num_max = (ctidevid & GENMASK(21, 16)) >> 16; + + pm_runtime_put(drvdata->dev); size = scnprintf(&buf[size], PAGE_SIZE, "%d %d\n", - drvdata->trig_num_max, drvdata->ch_num_max); + trig_num_max, ch_num_max); return size; } @@ -1485,7 +1492,6 @@ static int cti_init_save(struct cti_drvdata *drvdata, static int cti_probe(struct amba_device *adev, const struct amba_id *id) { int ret; - unsigned int ctidevid; struct device *dev = &adev->dev; struct coresight_platform_data *pdata; struct cti_drvdata *drvdata; @@ -1556,9 +1562,6 @@ static int cti_probe(struct amba_device *adev, const struct amba_id *id) cpu_pm_register_notifier(&cti_cpu_pm_notifier); registered++; } - ctidevid = cti_readl(drvdata, DEVID); - drvdata->trig_num_max = (ctidevid & GENMASK(15, 8)) >> 8; - drvdata->ch_num_max = (ctidevid & GENMASK(21, 16)) >> 16; pm_runtime_put(&adev->dev); dev_dbg(dev, "CTI initialized\n"); return 0; diff --git a/drivers/hwtracing/coresight/coresight.c b/drivers/hwtracing/coresight/coresight.c index 5b083e5f1b6e..67ab20d17872 100644 --- a/drivers/hwtracing/coresight/coresight.c +++ b/drivers/hwtracing/coresight/coresight.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2012, 2017-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2012, 2017-2021, The Linux Foundation. All rights reserved. */ #include @@ -1016,6 +1016,7 @@ static void coresight_enable_source_link(struct list_head *path) return; err: + coresight_disable_path_from(path, nd); coresight_release_path(csdev, path); } diff --git a/drivers/iio/adc/qcom-rradc.c b/drivers/iio/adc/qcom-rradc.c index 81a61c855adc..99b24f27f3df 100644 --- a/drivers/iio/adc/qcom-rradc.c +++ b/drivers/iio/adc/qcom-rradc.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2017, 2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2017, 2020-2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "RRADC: %s: " fmt, __func__ @@ -186,6 +186,7 @@ #define FG_RR_ADC_STS_CHANNEL_STS 0x2 #define FG_RR_CONV_CONTINUOUS_TIME_MIN_MS 50 +#define FG_RR_CONV_CONT_CBK_TIME_MIN_MS 10 #define FG_RR_CONV_MAX_RETRY_CNT 50 #define FG_RR_TP_REV_VERSION1 21 #define FG_RR_TP_REV_VERSION2 29 @@ -228,6 +229,12 @@ struct rradc_chip { struct pmic_revid_data *pmic_fab_id; int volt; struct power_supply *usb_trig; + struct power_supply *batt_psy; + struct power_supply *bms_psy; + struct notifier_block nb; + bool conv_cbk; + bool rradc_fg_reset_wa; + struct work_struct psy_notify_work; }; struct rradc_channels { @@ -672,6 +679,28 @@ static const struct rradc_channels rradc_chans[] = { FG_ADC_RR_AUX_THERM_STS) }; +static bool rradc_is_batt_psy_available(struct rradc_chip *chip) +{ + if (!chip->batt_psy) + chip->batt_psy = power_supply_get_by_name("battery"); + + if (!chip->batt_psy) + return false; + + return true; +} + +static bool rradc_is_bms_psy_available(struct rradc_chip *chip) +{ + if (!chip->bms_psy) + chip->bms_psy = power_supply_get_by_name("bms"); + + if (!chip->bms_psy) + return false; + + return true; +} + static int rradc_enable_continuous_mode(struct rradc_chip *chip) { int rc = 0; @@ -741,6 +770,7 @@ static int rradc_check_status_ready_with_retry(struct rradc_chip *chip, struct rradc_chan_prop *prop, u8 *buf, u16 status) { int rc = 0, retry_cnt = 0, mask = 0; + union power_supply_propval pval = {0, }; switch (prop->channel) { case RR_ADC_BATT_ID: @@ -766,7 +796,11 @@ static int rradc_check_status_ready_with_retry(struct rradc_chip *chip, break; } - msleep(FG_RR_CONV_CONTINUOUS_TIME_MIN_MS); + if ((chip->conv_cbk) && (prop->channel == RR_ADC_USBIN_V)) + msleep(FG_RR_CONV_CONT_CBK_TIME_MIN_MS); + else + msleep(FG_RR_CONV_CONTINUOUS_TIME_MIN_MS); + retry_cnt++; rc = rradc_read(chip, status, buf, 1); if (rc < 0) { @@ -775,8 +809,27 @@ static int rradc_check_status_ready_with_retry(struct rradc_chip *chip, } } - if (retry_cnt >= FG_RR_CONV_MAX_RETRY_CNT) - rc = -ENODATA; + if ((retry_cnt >= FG_RR_CONV_MAX_RETRY_CNT) && + ((prop->channel != RR_ADC_DCIN_V) || + (prop->channel != RR_ADC_DCIN_I)) && + chip->rradc_fg_reset_wa) { + pr_err("rradc is hung, Proceed to recovery\n"); + if (rradc_is_bms_psy_available(chip)) { + rc = power_supply_set_property(chip->bms_psy, + POWER_SUPPLY_PROP_FG_RESET_CLOCK, + &pval); + if (rc < 0) { + pr_err("Couldn't reset FG clock rc=%d\n", rc); + return rc; + } + } else { + pr_err("Error obtaining bms power supply\n"); + rc = -EINVAL; + } + } else { + if (retry_cnt >= FG_RR_CONV_MAX_RETRY_CNT) + rc = -ENODATA; + } return rc; } @@ -1088,6 +1141,67 @@ static int rradc_read_raw(struct iio_dev *indio_dev, return rc; } +static void psy_notify_work(struct work_struct *work) +{ + struct rradc_chip *chip = container_of(work, + struct rradc_chip, psy_notify_work); + + struct rradc_chan_prop *prop; + union power_supply_propval pval = {0, }; + u16 adc_code; + int rc = 0; + + if (rradc_is_batt_psy_available(chip)) { + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_STATUS, &pval); + if (rc < 0) + pr_err("Error obtaining battery status, rc=%d\n", rc); + + if (pval.intval == POWER_SUPPLY_STATUS_CHARGING) { + chip->conv_cbk = true; + prop = &chip->chan_props[RR_ADC_USBIN_V]; + rc = rradc_do_conversion(chip, prop, &adc_code); + if (rc == -ENODATA) { + pr_err("rradc is hung, Proceed to recovery\n"); + if (rradc_is_bms_psy_available(chip)) { + rc = power_supply_set_property + (chip->bms_psy, + POWER_SUPPLY_PROP_FG_RESET_CLOCK, + &pval); + if (rc < 0) + pr_err("Couldn't reset FG clock rc=%d\n", + rc); + prop = &chip->chan_props[RR_ADC_BATT_ID]; + rc = rradc_do_conversion(chip, prop, + &adc_code); + if (rc == -ENODATA) + pr_err("RRADC read failed after reset\n"); + } else { + pr_err("Error obtaining bms power supply\n"); + } + } + } + } else { + pr_err("Error obtaining battery power supply\n"); + } + chip->conv_cbk = false; + pm_relax(chip->dev); +} + +static int rradc_psy_notifier_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct power_supply *psy = data; + struct rradc_chip *chip = container_of(nb, struct rradc_chip, nb); + + if (strcmp(psy->desc->name, "battery") == 0) { + pm_stay_awake(chip->dev); + schedule_work(&chip->psy_notify_work); + } + + return NOTIFY_OK; +} + static const struct iio_info rradc_info = { .read_raw = &rradc_read_raw, }; @@ -1140,6 +1254,9 @@ static int rradc_get_dt_data(struct rradc_chip *chip, struct device_node *node) } } + chip->rradc_fg_reset_wa = + of_property_read_bool(node, "qcom,rradc-fg-reset-wa"); + iio_chan = chip->iio_chans; for (i = 0; i < RR_ADC_MAX; i++) { @@ -1201,6 +1318,15 @@ static int rradc_probe(struct platform_device *pdev) if (!chip->usb_trig) pr_debug("Error obtaining usb power supply\n"); + if (chip->rradc_fg_reset_wa) { + chip->nb.notifier_call = rradc_psy_notifier_cb; + rc = power_supply_reg_notifier(&chip->nb); + if (rc < 0) + pr_err("Error registering psy notifier rc = %d\n", rc); + + INIT_WORK(&chip->psy_notify_work, psy_notify_work); + } + return devm_iio_device_register(dev, indio_dev); } diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig index 388ef70c11d2..02514381577a 100644 --- a/drivers/iio/proximity/Kconfig +++ b/drivers/iio/proximity/Kconfig @@ -20,6 +20,8 @@ endmenu menu "Proximity and distance sensors" +source "drivers/iio/proximity/inv_ch101/Kconfig" + config ISL29501 tristate "Intersil ISL29501 Time Of Flight sensor" depends on I2C diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile index cac3d7d3325e..f608cd8f8b7d 100644 --- a/drivers/iio/proximity/Makefile +++ b/drivers/iio/proximity/Makefile @@ -11,3 +11,5 @@ obj-$(CONFIG_RFD77402) += rfd77402.o obj-$(CONFIG_SRF04) += srf04.o obj-$(CONFIG_SRF08) += srf08.o obj-$(CONFIG_SX9500) += sx9500.o + +obj-y += inv_ch101/ \ No newline at end of file diff --git a/drivers/iio/proximity/inv_ch101/Kconfig b/drivers/iio/proximity/inv_ch101/Kconfig new file mode 100644 index 000000000000..87c481e0cdb2 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config CH101 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config CH101_I2C + tristate "Invensense CH-101 devices (I2C)" + depends on I2C_MUX + select CH101 + select REGMAP_I2C + help + This driver supports the Invensense CH-101 ultra-low power + ultrasonic Time-of-Flight (ToF) range sensor over I2C. + This driver can be built as a module. The module will be called + ch101-i2c diff --git a/drivers/iio/proximity/inv_ch101/Makefile b/drivers/iio/proximity/inv_ch101/Makefile new file mode 100644 index 000000000000..990d0b82ee79 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/Makefile @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_CH101) += ch101-core.o +ch101-core-objs += ch101_core.o +ch101-core-objs += ch101_sysfs.o +ch101-core-objs += src/init_driver.o +ch101-core-objs += src/chbsp_dummy.o +ch101-core-objs += src/chbsp_init.o +ch101-core-objs += src/chirp_hal.o +ch101-core-objs += src/i2c_hal.o +ch101-core-objs += src/ch101_gpr.o +ch101-core-objs += src/ch101_gpr_fw.o +ch101-core-objs += src/ch101_gpr_open.o +ch101-core-objs += src/ch101_gpr_open_fw.o +ch101-core-objs += src/ch101_gpr_sr_open.o +ch101-core-objs += src/ch101_gpr_sr_open_fw.o +ch101-core-objs += src/ch_api.o +ch101-core-objs += src/ch_common.o +ch101-core-objs += src/ch_driver.o +ch101-core-objs += src/ch201_gprmt_fw.o + +obj-$(CONFIG_CH101_I2C) += ch101-i2c.o +ch101-i2c-objs += ch101_i2c.o + +# disable some warning flags +ccflags-y += -Wno-format diff --git a/drivers/iio/proximity/inv_ch101/ch101_client.h b/drivers/iio/proximity/inv_ch101/ch101_client.h new file mode 100644 index 000000000000..b757e27d8436 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/ch101_client.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef DRIVERS_IIO_PROXIMITY_INV_CH101_CH101_CLIENT_H_ +#define DRIVERS_IIO_PROXIMITY_INV_CH101_CH101_CLIENT_H_ + +#include +#include "src/chbsp_init.h" + + +#define TAG "ch101: " + +#define MAX_SAMPLES 450 +#define MAX_DEVICES 3 +#define MAX_BUSES 2 +#define MAX_DEV_BUSES (MAX_DEVICES * MAX_BUSES) + +typedef u32 ioport_pin_t; + +enum ioport_direction { + IOPORT_DIR_INPUT, /*!< IOPORT input direction */ + IOPORT_DIR_OUTPUT, /*!< IOPORT output direction */ +}; + +enum ioport_value { + IOPORT_PIN_LEVEL_LOW, /*!< IOPORT pin value low */ + IOPORT_PIN_LEVEL_HIGH, /*!< IOPORT pin value high */ +}; + +struct ch101_iq_data { + s16 I; + s16 Q; +}; + +struct ch101_buffer { + u16 distance[MAX_DEV_BUSES]; + u16 amplitude[MAX_DEV_BUSES]; + u16 nb_samples[MAX_DEV_BUSES]; + struct ch101_iq_data iq_data[MAX_DEV_BUSES][MAX_SAMPLES]; + u8 mode[MAX_DEV_BUSES]; +}; + +struct ch101_client; +struct ch101_callbacks { + int (*write_reg)(void *cl, u16 i2c_addr, u8 reg, u16 length, u8 *data); + int (*read_reg)(void *cl, u16 i2c_addr, u8 reg, u16 length, u8 *data); + int (*write_sync)(void *cl, u16 i2c_addr, u16 length, u8 *data); + int (*read_sync)(void *cl, u16 i2c_addr, u16 length, u8 *data); + void (*set_pin_level)(ioport_pin_t pin, int value); + void (*set_pin_dir)(ioport_pin_t pin, enum ioport_direction dir); + void (*data_complete)(struct ch101_client *data); + int (*setup_int_gpio)(struct ch101_client *data, uint32_t pin); + int (*free_int_gpio)(struct ch101_client *data, uint32_t pin); +}; + +struct ch101_i2c_bus { + struct i2c_client *i2c_client; + struct gpio_desc *gpiod_int[MAX_DEVICES]; + u32 gpio_exp_prog_pin[MAX_DEVICES]; +}; + +struct ch101_gpios { + struct gpio_desc *gpiod_rst; + struct gpio_desc *gpiod_rst_pulse; +}; + +struct ch101_client { + struct ch101_i2c_bus bus[MAX_BUSES]; + struct ch101_gpios gpios; + struct ch101_callbacks *cbk; +}; + +int ch101_core_probe(struct i2c_client *client, struct regmap *regmap, + struct ch101_callbacks *cbk, const char *name); +int ch101_core_remove(struct i2c_client *client); + +#endif /* DRIVERS_IIO_PROXIMITY_INV_CH101_CH101_CLIENT_H_ */ diff --git a/drivers/iio/proximity/inv_ch101/ch101_core.c b/drivers/iio/proximity/inv_ch101/ch101_core.c new file mode 100644 index 000000000000..92f7d3388389 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/ch101_core.c @@ -0,0 +1,1198 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include /* for struct device */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ch101_client.h" +#include "ch101_data.h" +#include "ch101_reg.h" +#include "ch101_sysfs.h" + +#include "src/init_driver.h" + + +#define CH101_GPIO_RESET "rst" +#define CH101_GPIO_PROG "prg" +#define CH101_GPIO_INT "int" +#define CH101_GPIO_RST_PULSE "rtc_rst" +#define CH101_IRQ_NAME "ch101_event" + +#define CH101_MIN_FREQ_HZ 1 +#define CH101_MAX_FREQ_HZ 10 +#define CH101_DEFAULT_FREQ 5 + +#define CH101_IQ_PACK 7 // Max 8 samples (256 bits) +#define CH101_IQ_PACK_BYTES (4 * CH101_IQ_PACK) // Max 32 bytes (256 bits) +#define TIMER_COUNTER 50 +#define TIMEOUT_US (200 * 1000) // 200 ms +#define TEST_RUN_TIME 0 // seconds + + +static bool start; +static bool stop; +static bool int_cal_request; +static bool prog_cal_request; +static int cal_count = TIMER_COUNTER; +static int ss_count = TIMER_COUNTER; +static bool current_state; + +static struct ch101_i2c_bus ch101_store; + +static void init_fw(struct ch101_data *data); +static void test_gpios(struct ch101_data *data); +static int test_rst_gpio(struct ch101_data *data, struct gpio_desc *rst); + +static const struct iio_event_spec ch101_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + + +#define CH101_CHANNEL_POSITION(idx) { \ + .type = IIO_POSITIONRELATIVE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .indexed = 1, \ + .scan_index = idx, \ + .channel = idx, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 8, \ + .storagebits = 8, \ + .shift = 0, \ + }, \ +} + +#define CH101_CHANNEL_IQ(idx) { \ + .type = IIO_PROXIMITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .indexed = 1, \ + .scan_index = idx, \ + .channel = idx, \ + .event_spec = ch101_events, \ + .num_event_specs = ARRAY_SIZE(ch101_events), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 8 * CH101_IQ_PACK_BYTES, \ + .storagebits = 8 * CH101_IQ_PACK_BYTES, \ + .shift = 0, \ + }, \ +} + +#define CH101_CHANNEL_DISTANCE(idx) { \ + .type = IIO_DISTANCE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \ + .indexed = 1, \ + .scan_index = idx, \ + .channel = idx, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .shift = 0, \ + }, \ +} + +#define CH101_CHANNEL_INTENSITY(idx) { \ + .type = IIO_INTENSITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .indexed = 1, \ + .scan_index = idx, \ + .channel = idx, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .shift = 0, \ + }, \ +} + +enum ch101_chan { + IQ_0, + IQ_1, + IQ_2, + IQ_3, + IQ_4, + IQ_5, + DISTANCE_0, + DISTANCE_1, + DISTANCE_2, + DISTANCE_3, + DISTANCE_4, + DISTANCE_5, + INTENSITY_0, + INTENSITY_1, + INTENSITY_2, + INTENSITY_3, + INTENSITY_4, + INTENSITY_5, + POSITION_0, + POSITION_1, + POSITION_2, + POSITION_3, + POSITION_4, + POSITION_5, + TIME +}; + +static const struct iio_chan_spec ch101_channels[] = { + CH101_CHANNEL_IQ(IQ_0), + CH101_CHANNEL_IQ(IQ_1), + CH101_CHANNEL_IQ(IQ_2), + CH101_CHANNEL_IQ(IQ_3), + CH101_CHANNEL_IQ(IQ_4), + CH101_CHANNEL_IQ(IQ_5), + CH101_CHANNEL_DISTANCE(DISTANCE_0), + CH101_CHANNEL_DISTANCE(DISTANCE_1), + CH101_CHANNEL_DISTANCE(DISTANCE_2), + CH101_CHANNEL_DISTANCE(DISTANCE_3), + CH101_CHANNEL_DISTANCE(DISTANCE_4), + CH101_CHANNEL_DISTANCE(DISTANCE_5), + CH101_CHANNEL_INTENSITY(INTENSITY_0), + CH101_CHANNEL_INTENSITY(INTENSITY_1), + CH101_CHANNEL_INTENSITY(INTENSITY_2), + CH101_CHANNEL_INTENSITY(INTENSITY_3), + CH101_CHANNEL_INTENSITY(INTENSITY_4), + CH101_CHANNEL_INTENSITY(INTENSITY_5), + CH101_CHANNEL_POSITION(POSITION_0), + CH101_CHANNEL_POSITION(POSITION_1), + CH101_CHANNEL_POSITION(POSITION_2), + CH101_CHANNEL_POSITION(POSITION_3), + CH101_CHANNEL_POSITION(POSITION_4), + CH101_CHANNEL_POSITION(POSITION_5), + IIO_CHAN_SOFT_TIMESTAMP(TIME), +}; + +static void ch101_read_range_data(struct ch101_data *data, + int ind, int *range) +{ + *range = data->buffer.distance[ind]; + pr_info(TAG "%s: %d: range: %d\n", __func__, ind, *range); +} + +static void ch101_read_amplitude_data(struct ch101_data *data, + int ind, int *amplitude) +{ + *amplitude = data->buffer.amplitude[ind]; + pr_info(TAG "%s: %d: amplitude: %d\n", __func__, ind, *amplitude); +} + +static void ch101_set_freq(struct ch101_data *data, int freq) +{ + if (freq < CH101_MIN_FREQ_HZ) + freq = CH101_MIN_FREQ_HZ; + else if (freq > CH101_MAX_FREQ_HZ) + freq = CH101_MAX_FREQ_HZ; + + data->scan_rate = freq; + data->period = ns_to_ktime((NSEC_PER_SEC / freq)); +} + +extern int is_sensor_connected(int ind); + +static int ch101_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct ch101_data *data = iio_priv(indio_dev); + int ret; + int ind; + int range; + int amplitude; + + init_fw(data); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *val = data->scan_rate; + *val2 = 0; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val = (0x01 * int_cal_request) & (0x02 * prog_cal_request); + *val2 = 0; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + *val = cal_count; + *val2 = 0; + return IIO_VAL_INT; + default: + break; + } + + switch (chan->type) { + case IIO_INTENSITY: + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ind = chan->channel - INTENSITY_0; + ch101_read_amplitude_data(data, ind, &litude); + *val = amplitude; + *val2 = 0; + + iio_device_release_direct_mode(indio_dev); + return IIO_VAL_INT; + default: + return -EINVAL; + } + + case IIO_DISTANCE: + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ind = chan->channel - DISTANCE_0; + ch101_read_range_data(data, ind, &range); + *val = range; + *val2 = 0; + + iio_device_release_direct_mode(indio_dev); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 32; + *val2 = 0; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + case IIO_PROXIMITY: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ind = chan->channel - IQ_0; + *val = data->buffer.nb_samples[ind]; + *val2 = 0; + + iio_device_release_direct_mode(indio_dev); + + return IIO_VAL_INT; + + case IIO_POSITIONRELATIVE: + ind = chan->channel - POSITION_0; + *val = is_sensor_connected(ind); + *val2 = 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +void set_output_samples(int ind, int val); + +static int ch101_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int val, int val2, long mask) +{ + struct ch101_data *data = iio_priv(indio_dev); + int ind; + + pr_info(TAG "%s: type: %d, mask: %lu val: %d val2: %d\n", __func__, + chan->type, mask, val, val2); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ch101_set_freq(data, val); + return 0; + case IIO_CHAN_INFO_CALIBSCALE: + int_cal_request = val & 0x01; + prog_cal_request = val & 0x02; + return 0; + case IIO_CHAN_INFO_CALIBBIAS: + cal_count = val; + ss_count = val; + data->counter = ss_count; + return 0; + default: + break; + } + switch (chan->type) { + case IIO_POSITIONRELATIVE: + ind = chan->channel - POSITION_0; + set_output_samples(ind, val); + return 0; + default: + return -EINVAL; + } +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL +("1 2 5 10"); + +static struct attribute *ch101_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ch101_attribute_group = { + .name = "ch101_attr", + .attrs = ch101_attributes +}; + +static const struct iio_info ch101_info = { + .attrs = &ch101_attribute_group, + .read_raw = &ch101_read_raw, + .write_raw = &ch101_write_raw, +}; + +static int ch101_trig_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct ch101_data *data = iio_priv(indio_dev); + struct device *dev = data->dev; + + dev_info(dev, "%s: state: %d\n", __func__, state); + + init_fw(data); + + current_state = state; + if (state) + hrtimer_start(&data->timer, data->period, HRTIMER_MODE_REL); + else + hrtimer_cancel(&data->timer); + + return 0; +} + +static const struct iio_trigger_ops ch101_trigger_ops = { + .set_trigger_state = ch101_trig_set_state, +}; + +static irqreturn_t ch101_store_time(int irq, void *p) +{ + struct iio_poll_func *pf = p; + + pf->timestamp = ktime_get_boot_ns(); + pr_info(TAG "%s: t: %llu\n", __func__, pf->timestamp); + + return IRQ_WAKE_THREAD; +} + +static char ch101_iio_buffer[256]; + +static irqreturn_t ch101_trigger_handler(int irq, void *handle) +{ + struct iio_poll_func *pf = handle; + struct iio_dev *indio_dev = pf->indio_dev; + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static s64 starting_ts; + +static int ch101_push_to_buffer(void *input_data) +{ + struct ch101_data *data = (struct ch101_data *)input_data; + struct device *dev = data->dev; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ch101_buffer *buffer = &(data->buffer); + u8 *buf; + int bit, ret; + int i, ind, len; + u8 mode; + int cur_max_samples; + + pr_info(TAG "%s: mask: %02x\n", __func__, + *indio_dev->active_scan_mask); + + mutex_lock(&data->lock); + + mode = 0; + cur_max_samples = 0; + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + if (bit >= POSITION_0 && bit < TIME) { + ind = bit - POSITION_0; + mode |= buffer->mode[ind]; + if (cur_max_samples < buffer->nb_samples[ind]) + cur_max_samples = buffer->nb_samples[ind]; + } + } + if (mode == 0) + goto out; + + buf = ch101_iio_buffer; + pr_info(TAG "scan bytes: %d, ts=%lld\n", indio_dev->scan_bytes, + starting_ts); + + for (i = 0; i < cur_max_samples; i += CH101_IQ_PACK) { + u8 *pbuf = buf; + + memset(buf, 0, indio_dev->scan_bytes); + len = CH101_IQ_PACK_BYTES; + if (len > (cur_max_samples - i) * 4) + len = (cur_max_samples - i) * 4; + + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + if (bit >= IQ_0 && bit < DISTANCE_0) { + ind = bit - IQ_0; + memcpy(pbuf, &(buffer->iq_data[ind][i]), len); + pbuf += CH101_IQ_PACK_BYTES; + //pr_info("iq_data: %d %d\n", + //buffer->iq_data[ind][i].I, + //buffer->iq_data[ind][i].Q); + } else if (bit >= DISTANCE_0 && bit < INTENSITY_0) { + ind = bit - DISTANCE_0; + memcpy(pbuf, &(buffer->distance[ind]), 2); + pbuf += sizeof(u16); + } else if (bit >= INTENSITY_0 && bit < POSITION_0) { + ind = bit - INTENSITY_0; + memcpy(pbuf, &(buffer->amplitude[ind]), 2); + pbuf += sizeof(u16); + } else if (bit >= POSITION_0 && bit < TIME) { + ind = bit - POSITION_0; + mode = buffer->mode[ind]; + memcpy(pbuf, &mode, 1); + pbuf += sizeof(u8); + //pr_info("mode copy=%d, %d\n", ind, mode); + } + } + pbuf += sizeof(u64); + ret = iio_push_to_buffers_with_timestamp(indio_dev, buf, + starting_ts); + + pr_info(TAG "push tobuffer=%d, size=%d\n", i, + (int)(pbuf-buf)); + } +out: + mutex_unlock(&data->lock); + + return 0; +} + +static int control_thread_fn(void *input_data) +{ + struct ch101_data *data = (struct ch101_data *)input_data; + struct device *dev = data->dev; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + start = true; + stop = false; + + dev_info(dev, "%s: Start", __func__); + + while (start) { + wait_for_completion(&data->data_completion); + + dev_info(dev, "%s: , mode: %02x", + __func__, indio_dev->currentmode); + + starting_ts = iio_get_time_ns(indio_dev); + single_shot_driver(); + if (current_state) + ch101_push_to_buffer(input_data); + //iio_trigger_poll(indio_dev->trig); + if (int_cal_request || prog_cal_request) { + dev_info(dev, "%s: cal_request", __func__); + test_gpios(data); + int_cal_request = false; + prog_cal_request = false; + } + } + + stop = true; + + return 0; +} + +static enum hrtimer_restart ch101_hrtimer_handler(struct hrtimer *t) +{ + struct ch101_data *data = container_of(t, struct ch101_data, timer); + + if (!data) + return HRTIMER_NORESTART; + + pr_info(TAG "%s: %d\n", __func__, data->counter); + if (data->counter-- <= 0) { + data->counter = ss_count; + pr_info(TAG "%s: Stop\n", __func__); + return HRTIMER_NORESTART; + } + + pr_info(TAG "%s: t: %lld, counter: %d\n", + __func__, ktime_get_boot_ns(), data->counter); + + complete(&data->data_completion); + + hrtimer_forward_now(t, data->period); + + return HRTIMER_RESTART; +} + +static void set_gpios_bus(struct device *dev, struct ch101_i2c_bus *bus) +{ + int i; + + for (i = 0; i < MAX_DEVICES; i++) { + of_property_read_u32_index(dev->of_node, "prg-gpios", i, + &(bus->gpio_exp_prog_pin[i])); + + dev_info(dev, "gpio exp prog pin %d is %d\n", + i, bus->gpio_exp_prog_pin[i]); + + + if (bus->gpiod_int[i] == NULL) { + bus->gpiod_int[i] = devm_gpiod_get_index(dev, + CH101_GPIO_INT, i, GPIOD_OUT_LOW); + if (IS_ERR(bus->gpiod_int[i])) { + dev_warn(dev, "gpio get INT pin failed\n"); + bus->gpiod_int[i] = NULL; + } + } + + dev_info(dev, "%s: %08p %d\n", __func__, + bus->gpiod_int[i], + bus->gpio_exp_prog_pin[i]); + } +} + +static void set_gpios(int bus_index, struct ch101_data *data, int rst_pin) +{ + struct device *dev = data->dev; + struct ch101_gpios *gpios = &data->client.gpios; + struct ch101_i2c_bus *bus = &data->client.bus[bus_index]; + int offset = bus_index * MAX_DEVICES; + int i; + + if (gpios->gpiod_rst == NULL) { + gpios->gpiod_rst = devm_gpiod_get_index(dev, + CH101_GPIO_RESET, rst_pin, GPIOD_OUT_HIGH); + if (IS_ERR(gpios->gpiod_rst) || test_rst_gpio(data, + gpios->gpiod_rst)) { + dev_warn(dev, "gpio get reset pin %d failed\n", + rst_pin); + gpios->gpiod_rst = NULL; + } else { + gpiod_set_value(gpios->gpiod_rst, 1); + dev_info(dev, "%s: Reset found and disabled.\n", + __func__); + } + } else { + dev_info(dev, "%s: Reset pin already found.\n", __func__); + } + + /* Get timer pulse reset control */ + if (gpios->gpiod_rst_pulse == NULL) { + gpios->gpiod_rst_pulse = devm_gpiod_get_index(dev, + CH101_GPIO_RST_PULSE, 0, GPIOD_OUT_HIGH); + if (IS_ERR(gpios->gpiod_rst_pulse)) { + dev_warn(dev, "gpio get timer reset pin failed\n"); + gpios->gpiod_rst_pulse = NULL; + } else { + gpiod_set_value(gpios->gpiod_rst_pulse, 1); + dev_info(dev, "%s: Timer Reset found and disabled.\n", + __func__); + } + } else { + dev_info(dev, "%s: Timer Reset pin already found.\n", __func__); + } + + dev_info(dev, "%s: Reset: %08p Pulse: %08p\n", __func__, + gpios->gpiod_rst, gpios->gpiod_rst_pulse); + + for (i = 0; i < MAX_DEVICES; i++) { + int index = i + offset; + + of_property_read_u32_index(dev->of_node, "prg-gpios", index, + &(bus->gpio_exp_prog_pin[i])); + + dev_info(dev, "gpio exp prog pin %d is %d\n", + index, bus->gpio_exp_prog_pin[i]); + + + if (bus->gpiod_int[i] == NULL) { + bus->gpiod_int[i] = devm_gpiod_get_index(dev, + CH101_GPIO_INT, index, GPIOD_OUT_LOW); + if (IS_ERR(bus->gpiod_int[i])) { + dev_warn(dev, "gpio get INT pin failed\n"); + bus->gpiod_int[i] = NULL; + } + } + + dev_info(dev, "%s: %08p %d\n", __func__, + bus->gpiod_int[i], bus->gpio_exp_prog_pin[i]); + } +} + +static irqreturn_t ch101_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ch101_data *data = iio_priv(indio_dev); + struct device *dev = data->dev; + int i = 0; + + dev_info(dev, "%s: irq: %d\n", __func__, irq); + + for (i = 0; i < CHBSP_MAX_DEVICES; i++) { + if (data->irq[i] == irq) + break; + } + + if (i < CHBSP_MAX_DEVICES) { +// dev_info(dev, "%s: irq: %d i: %d\n", __func__, irq, i); + ext_ChirpINT0_handler(i); + } + + return IRQ_HANDLED; +} + +static irqreturn_t ch101_event_handler1(int irq, void *private) +{ + return ch101_event_handler(irq, private); +} + +static irqreturn_t ch101_event_handler2(int irq, void *private) +{ + return ch101_event_handler(irq, private); +} + +static irqreturn_t ch101_event_handler3(int irq, void *private) +{ + return ch101_event_handler(irq, private); +} + +static irqreturn_t ch101_event_handler4(int irq, void *private) +{ + return ch101_event_handler(irq, private); +} + +static irqreturn_t ch101_event_handler5(int irq, void *private) +{ + return ch101_event_handler(irq, private); +} + +static irqreturn_t ch101_event_handler6(int irq, void *private) +{ + return ch101_event_handler(irq, private); +} +typedef irqreturn_t (*ch101_handler)(int irq, void *private); + +static const ch101_handler int_table[] = { + ch101_event_handler1, + ch101_event_handler2, + ch101_event_handler3, + ch101_event_handler4, + ch101_event_handler5, + ch101_event_handler6 +}; + +static void complete_done(struct ch101_client *client_drv) +{ + struct ch101_data *data; + + data = container_of(client_drv, struct ch101_data, client); + complete(&data->ss_completion); +} + +static int setup_int_gpio(struct ch101_client *client_drv, uint32_t pin) +{ + struct ch101_data *data; + struct device *dev; + struct ch101_i2c_bus *bus; + struct iio_dev *indio_dev; + int index = pin - CHIRP0_INT_0; + int bus_index = index / MAX_DEVICES; + int dev_index = index % MAX_DEVICES; + char devname[32]; + int ret = 0; + + if (index < 0 || index >= CHBSP_MAX_DEVICES) + return -EPERM; + + data = container_of(client_drv, struct ch101_data, client); + dev = data->dev; + indio_dev = dev_get_drvdata(dev); + bus = &data->client.bus[bus_index]; + + if (data->irq_set[index] == 1) + return 0; + + if (data->irq[index] == 0) + return -EPERM; + + snprintf(devname, sizeof(devname), "%s%d", CH101_IRQ_NAME, index); + dev_info(dev, "%s: IRQ: %d %s\n", __func__, data->irq[index], devname); + + ret = gpiod_direction_input(bus->gpiod_int[dev_index]); + if (ret < 0) + dev_warn(dev, "gpiod_direction_input INT pin failed\n"); + + // Enable IRQ handler + ret = devm_request_threaded_irq(dev, data->irq[index], + NULL, int_table[index], + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + devname, indio_dev); + if (ret < 0) + dev_err(dev, "devm_request_threaded_irq %d failed\n", + data->irq[index]); + + data->irq_set[index] = (ret == 0); + + return ret; +} + +static int free_int_gpio(struct ch101_client *client_drv, uint32_t pin) +{ + struct ch101_data *data; + struct device *dev; + struct ch101_i2c_bus *bus; + struct iio_dev *indio_dev; + int index = pin - CHIRP0_INT_0; + int bus_index = index / MAX_DEVICES; + int dev_index = index % MAX_DEVICES; + int ret = 0; + + if (index < 0 || index >= CHBSP_MAX_DEVICES) + return -EPERM; + + data = container_of(client_drv, struct ch101_data, client); + dev = data->dev; + indio_dev = dev_get_drvdata(dev); + bus = &data->client.bus[bus_index]; + +// dev_info(dev, "%s: IRQ: %d, set: %d\n", +// __func__, data->irq[index], data->irq_set[index]); + + if (data->irq_set[index] == 0) + return 0; + + if (data->irq[index] == 0) + return -EPERM; + + dev_info(dev, "%s: IRQ: %d\n", __func__, data->irq[index]); + + free_irq(data->irq[index], indio_dev); + + ret = gpiod_direction_output(bus->gpiod_int[dev_index], 0); + if (ret < 0) { + dev_warn(dev, "gpiod_direction_input for IRQ %d failed\n", + data->irq[index]); + } + + data->irq_set[index] = 0; + + return ret; +} + +static void test_gpios(struct ch101_data *data) +{ + int cycle_cnt = 0; + int index; + int bus_index; + int dev_index; + struct ch101_i2c_bus *bus; + struct gpio_desc *gpio_int; + struct ch101_callbacks *cbk = data->client.cbk; + + for (index = 0; index < CHBSP_MAX_DEVICES; index++) { + bus_index = index / MAX_DEVICES; + dev_index = index % MAX_DEVICES; + bus = &data->client.bus[bus_index]; + if (bus->gpiod_int[dev_index] == NULL) { + dev_err(data->dev, "%s: No valid INT pin\n", __func__); + return; + } + if (bus->gpio_exp_prog_pin[dev_index] > CHBSP_MAX_DEVICES) { + dev_err(data->dev, "%s: No valid PROG pin\n", __func__); + return; + } + } + + for (index = 0; index < CHBSP_MAX_DEVICES; index++) + free_int_gpio(&data->client, data->irq_map[index]); + + while (cycle_cnt++ < 2 * cal_count && + (int_cal_request || prog_cal_request)) { + for (index = 0; index < CHBSP_MAX_DEVICES; index++) { + int set_value; + int raw_value; + int value; + int dir; + int is_active_low; + + bus = &data->client.bus[bus_index]; + gpio_int = bus->gpiod_int[dev_index]; + + bus_index = index / MAX_DEVICES; + dev_index = index % MAX_DEVICES; + + if (int_cal_request) { + + dev_info(data->dev, "%s: %d %d\n", + __func__, index, data->irq_map[index]); + + set_value = (cycle_cnt % 2); + + dev_info(data->dev, "%s: INT set to %d\n", + __func__, set_value); + gpiod_set_value(gpio_int, set_value); + + msleep(100); + + raw_value = gpiod_get_raw_value(gpio_int); + value = gpiod_get_value(gpio_int); + dir = gpiod_get_direction(gpio_int); + is_active_low = gpiod_is_active_low(gpio_int); + + if (set_value == raw_value && + set_value == value) { + dev_info(data->dev, + "%s: Current INT pin state: rv: %i v: %i d: %i al: %i\n", + __func__, raw_value, value, dir, is_active_low); + } else { + dev_err(data->dev, + "%s: Error INT pin state: rv: %i v: %i d: %i al: %i\n", + __func__, raw_value, value, dir, is_active_low); + } + } + + if (prog_cal_request) { + int pin = bus->gpio_exp_prog_pin[dev_index]; + + dev_info(data->dev, "%s: %d pin: %d\n", + __func__, index, pin); + + set_value = (cycle_cnt % 2); + + dev_info(data->dev, "%s: PROG set to %d\n", + __func__, set_value); + cbk->set_pin_level(pin, set_value); + + msleep(100); + } + } + } + + msleep(500); +} + +static int test_rst_gpio(struct ch101_data *data, struct gpio_desc *rst) +{ + int error = 0; + int i; + + for (i = 0; i < 2; i++) { + int set_value; + int raw_value; + int value; + int dir; + int is_active_low; + + set_value = i % 2; + + dev_info(data->dev, "%s: RST set to %d\n", __func__, set_value); + gpiod_set_value(rst, set_value); + + msleep(100); + + raw_value = gpiod_get_raw_value(rst); + value = gpiod_get_value(rst); + dir = gpiod_get_direction(rst); + is_active_low = gpiod_is_active_low(rst); + + if (set_value == raw_value && + set_value == value) { + dev_info(data->dev, + "%s: Current RST pin state: rv: %i v: %i d: %i al: %i\n", + __func__, raw_value, value, dir, is_active_low); + } else { + error = 1; + dev_err(data->dev, + "%s: Error RST pin state: rv: %i v: %i d: %i al: %i\n", + __func__, raw_value, value, dir, is_active_low); + } + } + return error; +} + +static void init_fw(struct ch101_data *data) +{ + if (data && !data->fw_initialized) { + init_driver(); + config_driver(); + data->fw_initialized = 1; + + if (TEST_RUN_TIME) { + start_driver(100, TEST_RUN_TIME * 1000); + stop_driver(); + } + } +} + +int ch101_core_probe(struct i2c_client *client, struct regmap *regmap, + struct ch101_callbacks *cbk, const char *name) +{ + struct ch101_data *data; + struct ch101_i2c_bus *bus; + struct device *dev; + struct iio_dev *indio_dev; + int ret = 0; + int i = 0; + + dev = &client->dev; + + dev_info(dev, "%s: Start v.1.60 s: %d", __func__, sizeof(*data)); + + if (ch101_store.i2c_client == NULL) { + ch101_store.i2c_client = client; + set_gpios_bus(dev, &ch101_store); + return 0; + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + dev_info(dev, "%s: indio_dev: %p, client: %p", + __func__, indio_dev, client); + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + + data->dev = dev; + data->regmap = regmap; + data->client.bus[0].i2c_client = client; + data->client.cbk = cbk; + data->client.cbk->data_complete = complete_done; + data->client.cbk->setup_int_gpio = setup_int_gpio; + data->client.cbk->free_int_gpio = free_int_gpio; + + if (ch101_store.i2c_client) { + bus = &data->client.bus[1]; + bus->i2c_client = ch101_store.i2c_client; + for (i = 0; i < MAX_DEVICES; i++) { + bus->gpiod_int[i] = + ch101_store.gpiod_int[i]; + bus->gpio_exp_prog_pin[i] = + ch101_store.gpio_exp_prog_pin[i]; + dev_info(dev, "%s: %08p %d\n", __func__, + bus->gpiod_int[i], bus->gpio_exp_prog_pin[i]); + } + } + + set_chirp_data(&data->client); + set_chirp_buffer(&data->buffer); + + set_gpios(0, data, 0); + + ret = find_sensors(); + if (ret < 0) { + dev_err(dev, "find_sensors: %d\n", ret); + data->client.gpios.gpiod_rst = NULL; + } + + if (ret < 0) { + dev_err(dev, "No RST pin on sensors found\n"); + goto error_find_sensors; + } + + msleep(50); + + indio_dev->dev.parent = dev; + indio_dev->name = name; + indio_dev->info = &ch101_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ch101_channels; + indio_dev->num_channels = ARRAY_SIZE(ch101_channels); + + mutex_init(&data->lock); + init_completion(&data->ss_completion); + init_completion(&data->data_completion); + + for (i = 0; i < CHBSP_MAX_DEVICES; i++) { + int bus_index = i / MAX_DEVICES; + int dev_index = i % MAX_DEVICES; + struct gpio_desc *gpiod_int; + + gpiod_int = data->client.bus[bus_index].gpiod_int[dev_index]; + + if (gpiod_int != NULL) { + data->irq[i] = gpiod_to_irq(gpiod_int); + dev_info(dev, "%s: IRQ: %d\n", __func__, data->irq[i]); + } else { + data->irq[i] = 0; + } + + data->irq_map[i] = i + CHIRP0_INT_0; + + // Enable IRQ handler + ret = setup_int_gpio(&data->client, data->irq_map[i]); + if (ret < 0) { + dev_err(dev, "devm_request_threaded_irq failed\n", + data->irq[i]); + break; + } + } + + if (ret < 0) + goto error_request_threaded_irq; + + ret = iio_triggered_buffer_setup(indio_dev, ch101_store_time, + ch101_trigger_handler, NULL); + if (ret) { + dev_err(&client->dev, "iio triggered buffer error %d\n", ret); + goto error_triggered_buffer_setup; + } + + data->trig = iio_trigger_alloc("%s-hrtimer%d", indio_dev->name, + indio_dev->id); + if (data->trig == NULL) { + ret = -ENOMEM; + dev_err(&client->dev, "iio trigger alloc error\n"); + goto error_trigger_alloc; + } + data->trig->dev.parent = &client->dev; + data->trig->ops = &ch101_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + + ret = iio_trigger_register(data->trig); + if (ret) { + dev_err(&client->dev, "iio trigger register error %d\n", ret); + goto error_trigger_register; + } + iio_trigger_get(data->trig); + indio_dev->trig = data->trig; + + for (i = 0; i < CHBSP_MAX_DEVICES; i++) + free_int_gpio(&data->client, data->irq_map[i]); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(dev, "iio_device_register failed: %d\n", ret); + goto error_device_register; + } + + ret = ch101_create_dmp_sysfs(indio_dev); + if (ret) { + dev_err(dev, "create dmp sysfs failed\n"); + goto error_dmp_sysfs; + } + + data->fw_initialized = 0; + if (!CH101_EXTERNAL_FW) + init_fw(data); + + // Set timer count + data->counter = ss_count; + + // Init the hrtimer after driver init --yd + hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ch101_set_freq(data, CH101_DEFAULT_FREQ); + data->timer.function = ch101_hrtimer_handler; + + dev_info(dev, "%s: data->period: %d\n", __func__, data->period); + + { + static struct task_struct *thread_st; + + thread_st = kthread_run(control_thread_fn, data, + "CH101 Control Thread"); + } + + dev_info(dev, "%s: End\n", __func__); + + return ret; + +error_device_register: +error_dmp_sysfs: + iio_trigger_unregister(data->trig); + +error_trigger_register: + iio_trigger_free(data->trig); + +error_trigger_alloc: +error_triggered_buffer_setup: +error_request_threaded_irq: +error_find_sensors: + devm_iio_device_free(&client->dev, indio_dev); + dev_err(dev, "%s: Error %d:\n", __func__, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(ch101_core_probe); + +int ch101_core_remove(struct i2c_client *client) +{ + int ret = 0; + + struct device *dev = &client->dev; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ch101_data *data = iio_priv(indio_dev); + struct ch101_callbacks *cbk = data->client.cbk; + + dev_info(dev, "%s: Start: %p\n", __func__, data); + + start = false; + while (!stop) + msleep(20); + + { + // Enable IRQ handler + int i = 0; + + for (i = 0; i < CHBSP_MAX_DEVICES; i++) + setup_int_gpio(&data->client, data->irq_map[i]); + } + + dev_info(dev, "%s: trig: %p\n", __func__, data->trig); + iio_trigger_unregister(data->trig); + iio_trigger_free(data->trig); + + iio_device_unregister(indio_dev); + devm_iio_device_free(dev, indio_dev); + devm_kfree(dev, cbk); + + dev_info(dev, "%s: End\n", __func__); + + return ret; +} +EXPORT_SYMBOL_GPL(ch101_core_remove); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense CH101 core device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/proximity/inv_ch101/ch101_data.h b/drivers/iio/proximity/inv_ch101/ch101_data.h new file mode 100644 index 000000000000..9785230e487e --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/ch101_data.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef DRIVERS_IIO_PROXIMITY_CH101_DATA_H_ +#define DRIVERS_IIO_PROXIMITY_CH101_DATA_H_ + +#include +#include +#include + +#include + +#include "ch101_client.h" + +struct ch101_data { + struct device *dev; + struct ch101_client client; + struct ch101_buffer buffer; + struct regmap *regmap; + struct mutex lock; + struct completion ss_completion; + struct completion data_completion; + struct iio_trigger *trig; + struct hrtimer timer; + int irq[CHBSP_MAX_DEVICES]; + int irq_set[CHBSP_MAX_DEVICES]; + int irq_map[CHBSP_MAX_DEVICES]; + int scan_rate; + ktime_t period; + int counter; + int fw_initialized; +}; + +#endif /* DRIVERS_IIO_PROXIMITY_CH101_DATA_H_ */ diff --git a/drivers/iio/proximity/inv_ch101/ch101_i2c.c b/drivers/iio/proximity/inv_ch101/ch101_i2c.c new file mode 100644 index 000000000000..88141d9a2ec5 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/ch101_i2c.c @@ -0,0 +1,460 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ch101_data.h" +#include "ch101_client.h" +#include "ch101_reg.h" + +#define SX1508_DEVICE_ID "sx1508q" + +// Device tree RP4 +// +// ch101@45 { +// compatible = "invensense,ch101"; +// reg = <0x45>; +// pinctrl-0 = <&chirp_int_pin>; +// rst-gpios = <&gpio 22 GPIO_ACTIVE_HIGH>; +// prg-gpios = <&gpio 27 GPIO_ACTIVE_HIGH>; +// int-gpios = <&gpio 23 GPIO_ACTIVE_HIGH>; +// interrupts = <23 1>; +// interrupt-parent = <&gpio>; +// }; +// +// Device tree RB5 +//&qupv3_se1_i2c { +// status = "ok"; +// qcom,clk-freq-out = <400000>; +// #address-cells = <1>; +// #size-cells = <0>; +// +// /* TDK Chirp IO Expander */ +// ch_io_expander@22 { +// #gpio-cells = <2>; +// #interrupt-cells = <2>; +// compatible = "semtech,sx1508q"; +// reg = <0x22>; +// gpio-controller; +// }; +//}; +// +//&qupv3_se4_i2c { +// #address-cells = <1>; +// #size-cells = <0>; +// status = "ok"; +// /* TDK Chirp 0, 1, and 2 are connected to QUP4 */ +// qcom,clk-freq-out = <400000>; +// ch101_0: ch101_0@45 { +// compatible = "invensense,ch101"; +// reg = <0x45>; +// prg-gpios = <0 1 2>; +// rst-gpios = <&tlmm 140 GPIO_ACTIVE_HIGH>; +// rtc_rst-gpios = <&tlmm 0 GPIO_ACTIVE_HIGH>; +// int-gpios = <&tlmm 129 GPIO_ACTIVE_HIGH>, +// <&tlmm 138 GPIO_ACTIVE_HIGH>, +// <&tlmm 113 GPIO_ACTIVE_HIGH>; +// interrupt-parent = <&tlmm>; +// interrupts = <129 1>, +// <141 1>, +// <113 1>; +// interrupt-names = "ch101_0_irq"; +// pinctrl-names = "default"; +// pinctrl-0 = <&ch101_rst &ch101_tmr_rst &ch101_0_irq>; +// }; +//}; +// +//&qupv3_se15_i2c { +// #address-cells = <1>; +// #size-cells = <0>; +// status = "ok"; +// /* TDK Chirp 3, 4, and 5 are connected to QUP15 */ +// qcom,clk-freq-out = <400000>; +// ch101_1: ch101_1@45 { +// compatible = "invensense,ch101"; +// reg = <0x45>; +// prg-gpios = <3 4 5>; +// rst-gpios = <&tlmm 140 GPIO_ACTIVE_HIGH>; +// rtc_rst-gpios = <&tlmm 0 GPIO_ACTIVE_HIGH>; +// int-gpios = <&tlmm 122 GPIO_ACTIVE_HIGH>, +// <&tlmm 123 GPIO_ACTIVE_HIGH>, +// <&tlmm 66 GPIO_ACTIVE_HIGH>; +// interrupt-parent = <&tlmm>; +// interrupts = <122 1>, +// <123 1>, +// <66 1>; +// interrupt-names = "ch101_1_irq"; +// pinctrl-names = "default"; +// pinctrl-0 = <&ch101_rst &ch101_tmr_rst &ch101_1_irq>; +// }; + + +static const struct regmap_config ch101_i2c_regmap_config = { + .reg_bits = 8, .val_bits = 8, +}; + +static int read_reg(void *client, u16 i2c_addr, u8 reg, u16 length, u8 *data) +{ + int res; + struct i2c_msg msg[] = { + { .addr = i2c_addr, .flags = 0, .len = 1, .buf = ® }, + { .addr = i2c_addr, .flags = I2C_M_RD, + .len = length, .buf = (__u8 *)data } + }; + + struct i2c_client *i2c_client = (struct i2c_client *)client; + struct i2c_adapter *i2c_adapter = i2c_client->adapter; + +// pr_info(TAG "%s: name: %s, addr: %02x, flags: %x\n", __func__, +// i2c_client->name, i2c_client->addr, i2c_client->flags); +// pr_info(TAG "%s: addr: %02x, reg: %02x, len: %d\n", __func__, +// i2c_addr, reg, length); + + res = i2c_transfer(i2c_adapter, msg, 2); + +// pr_info(TAG "%s: RES: %d\n", __func__, res); + +// { +// int i; +// pr_info(TAG "Read Values: "); +// for (i = 0; i < (length < 3 ? length : 3); i++) +// pr_info(TAG " %02x ", *(u8 *)(data + i)); +// pr_info(TAG "\n"); +// } + + if (res <= 0) { + if (res == 0) + res = -EIO; + return res; + } else { + return 0; + } +} + +static int write_reg(void *client, u16 i2c_addr, u8 reg, u16 length, u8 *data) +{ + int res; + u8 buf[4]; + struct i2c_msg msg[] = { + { .addr = i2c_addr, .flags = I2C_M_STOP, + .len = length + 1, .buf = (__u8 *)buf } + }; + + struct i2c_client *i2c_client = (struct i2c_client *)client; + struct i2c_adapter *i2c_adapter = i2c_client->adapter; + +// pr_info(TAG "%s: name: %s, addr: %02x, flags: %x\n", __func__, +// i2c_client->name, i2c_client->addr, i2c_client->flags); +// pr_info(TAG "%s: addr: %02x, reg: %02x, len: %d\n", __func__, +// i2c_addr, reg, length); + + if (length > sizeof(buf)) + return -EIO; + + // prepend the 'register address' to the buffer + buf[0] = reg; + memcpy(&(buf[1]), data, length); + +// { +// int i; +// pr_info(TAG "Write Values: "); +// for (i = 0; i < sizeof(buf); i++) +// pr_info(TAG " %02x ", *(u8 *)(buf + i)); +// pr_info(TAG "\n"); +// } + + res = i2c_transfer(i2c_adapter, msg, 1); + +// pr_info(TAG "%s: RES: %d\n", __func__, res); + + if (res <= 0) { + if (res == 0) + res = -EIO; + return res; + } else { + return 0; + } +} + +static int read_sync(void *client, u16 i2c_addr, u16 length, u8 *data) +{ + int res; + struct i2c_msg msg[] = { + { .addr = i2c_addr, .flags = I2C_M_STOP | I2C_M_RD, + .len = length, .buf = (__u8 *)data } + }; + + struct i2c_client *i2c_client = (struct i2c_client *)client; + struct i2c_adapter *i2c_adapter = i2c_client->adapter; + +// pr_info(TAG "%s: name: %s, addr: %02x, flags: %x\n", __func__, +// i2c_client->name, i2c_client->addr, i2c_client->flags); +// pr_info(TAG "%s: addr: %02x, len: %d\n", __func__, +// i2c_addr, length); + + res = i2c_transfer(i2c_adapter, msg, 1); + +// pr_info(TAG "%s: RES: %d\n", __func__, res); + + if (res <= 0) { + if (res == 0) + res = -EIO; + return res; + } else { + return 0; + } +} + +static int write_sync(void *client, u16 i2c_addr, u16 length, u8 *data) +{ + int res; + struct i2c_msg msg[] = { + { .addr = i2c_addr, .flags = I2C_M_STOP, + .len = length, .buf = (__u8 *)data } + }; + + struct i2c_client *i2c_client = (struct i2c_client *)client; + struct i2c_adapter *i2c_adapter = i2c_client->adapter; + +// pr_info(TAG "%s: name: %s, addr: %02x, flags: %x\n", __func__, +// i2c_client->name, i2c_client->addr, i2c_client->flags); +// pr_info(TAG "%s: addr: %02x, len: %d\n", __func__, +// i2c_addr, length); + + res = i2c_transfer(i2c_adapter, msg, 1); + +// pr_info(TAG "%s: RES: %d\n", __func__, res); + + if (res <= 0) { + if (res == 0) + res = -EIO; + return res; + } else { + return 0; + } +} + +static int match_gpio_chip_by_label(struct gpio_chip *chip, void *data) +{ + return !strcmp(chip->label, data); +} + +static struct gpio_chip *get_gpio_exp(void) +{ + struct gpio_chip *chip; + + chip = gpiochip_find(SX1508_DEVICE_ID, + match_gpio_chip_by_label); + +// pr_info(TAG "%s: name: %s gpio: %p\n", __func__,SX1508_DEVICE_ID,chip); + + return chip; +} + +static void set_pin_level(ioport_pin_t pin, int value) +{ + struct gpio_chip *gpio = get_gpio_exp(); + + if (!gpio) + return; + +// pr_info(TAG "%s: pin: %d, value: %d\n", __func__, pin, value); + + gpio->set(gpio, pin, value); +} + +static void set_pin_dir(ioport_pin_t pin, enum ioport_direction dir) +{ + struct gpio_chip *gpio = get_gpio_exp(); + + if (!gpio) + return; + +// pr_info(TAG "%s: pin: %d, dir: %d\n", __func__, pin, dir); + + if (dir == IOPORT_DIR_INPUT) + gpio->direction_input(gpio, pin); + else + gpio->direction_output(gpio, pin, 0); +} + +static int ch101_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + struct device *dev; + struct gpio_chip *gpio; + int irq; + struct irq_desc *desc; + struct ch101_callbacks *cbk; + int ret = 0; + + if (!client) + return -ENOMEM; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK)) { + return -EPERM; + } + + dev = &client->dev; + + cbk = devm_kmalloc(dev, sizeof(struct ch101_callbacks), GFP_KERNEL); + if (!cbk) + return -ENOMEM; + + irq = client->irq; + desc = irq_to_desc(irq); + + dev_info(dev, "%s: Start: %p, %s\n", __func__, + dev, dev ? dev->init_name : ""); + + dev_info(dev, "%s: IRQ: %d\n", __func__, irq); + + gpio = get_gpio_exp(); + if (!gpio) { + dev_err(dev, "Error initializing expander: %s\n", + SX1508_DEVICE_ID); + return -ENODEV; + } + + cbk->read_reg = read_reg; + cbk->write_reg = write_reg; + cbk->read_sync = read_sync; + cbk->write_sync = write_sync; + cbk->set_pin_level = set_pin_level; + cbk->set_pin_dir = set_pin_dir; + + regmap = devm_regmap_init_i2c(client, &ch101_i2c_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "Error initializing i2c regmap: %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + pr_info(TAG "%s: client: %p\n", __func__, client); + pr_info(TAG "%s: adapter: %p\n", __func__, client->adapter); + + + ret = ch101_core_probe(client, regmap, cbk, id ? id->name : NULL); + + dev_info(dev, "%s: End\n", __func__); + + return ret; +} + +static int ch101_i2c_remove(struct i2c_client *client) +{ + struct device *dev; + int irq; + struct irq_desc *desc; + int ret = 0; + + dev = &client->dev; + irq = client->irq; + desc = irq_to_desc(irq); + + dev_info(dev, "%s: Start: %p, %s\n", __func__, + dev, dev ? dev->init_name : ""); + + dev_info(dev, "%s: IRQ: %d, %s\n", __func__, + irq, ((desc && desc->action) ? desc->action->name : "")); + + ret = ch101_core_remove(client); + + dev_info(dev, "%s: End: %p, %s\n", __func__, + dev, dev ? dev->init_name : ""); + + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static int ch101_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ch101_data *data = iio_priv(indio_dev); + int ret; + + dev_info(dev, "%s: Start\n", __func__); + + mutex_lock(&data->lock); + ret = regmap_write(data->regmap, CH_PROG_REG_CPU, 0x11); + mutex_unlock(&data->lock); + + dev_info(dev, "%s: End\n", __func__); + + return ret; +} + +static int ch101_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ch101_data *data = iio_priv(indio_dev); + int ret; + + dev_info(dev, "%s: Start\n", __func__); + + mutex_lock(&data->lock); + ret = regmap_write(data->regmap, CH_PROG_REG_CPU, 0x02); + mutex_unlock(&data->lock); + + dev_info(dev, "%s: End\n", __func__); + + return ret; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops ch101_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ch101_suspend, ch101_resume) +}; + +static const struct i2c_device_id ch101_i2c_id[] = { +{ "ch101", 0 }, +{ }, +}; + +MODULE_DEVICE_TABLE(i2c, ch101_i2c_id); + +static const struct of_device_id ch101_of_match[] = { +{ .compatible = "invensense,ch101" }, +{ }, +}; + +MODULE_DEVICE_TABLE(of, ch101_of_match); + +static struct i2c_driver ch101_i2c_driver = { + .driver = { + .name = "ch101_i2c", + .of_match_table = ch101_of_match, + .pm = &ch101_pm_ops, + }, + .probe = ch101_i2c_probe, + .remove = ch101_i2c_remove, + .id_table = ch101_i2c_id, +}; + +module_i2c_driver(ch101_i2c_driver); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense CH101 I2C device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/proximity/inv_ch101/ch101_reg.h b/drivers/iio/proximity/inv_ch101/ch101_reg.h new file mode 100644 index 000000000000..432eacd1e0ec --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/ch101_reg.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CH101REG_H_ +#define _CH101REG_H_ + +/* CH-101 common definitions */ + +#define CH101_COMMON_REG_OPMODE 0x01 +#define CH101_COMMON_REG_TICK_INTERVAL 0x02 +#define CH101_COMMON_REG_PERIOD 0x05 +#define CH101_COMMON_REG_MAX_RANGE 0x07 +#define CH101_COMMON_REG_TIME_PLAN 0x09 +#define CH101_COMMON_REG_STAT_RANGE 0x12 +#define CH101_COMMON_REG_STAT_COEFF 0x13 +#define CH101_COMMON_REG_READY 0x14 +#define CH101_COMMON_REG_TOF_SF 0x16 +#define CH101_COMMON_REG_TOF 0x18 +#define CH101_COMMON_REG_AMPLITUDE 0x1A +#define CH101_COMMON_REG_CAL_TRIG 0x06 +#define CH101_COMMON_REG_CAL_RESULT 0x0A +#define CH101_COMMON_REG_DATA 0x1C + +/* Programming interface register addresses */ +#define CH_PROG_REG_PING 0x00 /*!< Read-only register used for ping device.*/ +#define CH_PROG_REG_CPU 0x42 /*!< Processor control register address. */ +#define CH_PROG_REG_STAT 0x43 /*!< Processor status register address. */ +#define CH_PROG_REG_CTL 0x44 /*!< Data transfer control register address. */ +#define CH_PROG_REG_ADDR 0x05 /*!< Data transfer starting register address.*/ +#define CH_PROG_REG_CNT 0x07 /*!< Data transfer size register address. */ +#define CH_PROG_REG_DATA 0x06 /*!< Data transfer value register address. */ + +#define CH101_SIG_BYTE_0 (0x0a) /* Signature bytes in sensor */ +#define CH101_SIG_BYTE_1 (0x02) + +#endif /* _CH101REG_H_ */ diff --git a/drivers/iio/proximity/inv_ch101/ch101_sysfs.c b/drivers/iio/proximity/inv_ch101/ch101_sysfs.c new file mode 100644 index 000000000000..025de8225aab --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/ch101_sysfs.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include "ch101_sysfs.h" +#include "ch101_data.h" +#include "src/ch101_gpr.h" +#include "src/ch201_gprmt.h" + +static int current_part; + +static ssize_t ch101_fw_read(char *buf, loff_t off, size_t count, + u8 *addr, u8 *end_addr) +{ + int read_count = 0; + int i = 0; + + while (count > 0 && addr < end_addr) { + if (count > MAX_DMP_READ_SIZE) + read_count = MAX_DMP_READ_SIZE; + else + read_count = count; + + if (read_count > end_addr - addr) + read_count = end_addr - addr; + + pr_info(TAG "%s: %d, %p, %p, %d", __func__, + i, &buf[i], addr, read_count); + memcpy((void *)&buf[i], (const void *)addr, read_count); + + addr += read_count; + i += read_count; + count -= read_count; + } + pr_info(TAG "%s: %d", __func__, read_count); + + return read_count; +} + +/* + * ch101_firmware_write() - calling this function will load the firmware. + */ +static ssize_t ch101_firmware_write(struct file *fp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t pos, size_t size) +{ + struct iio_dev *indio_dev = + dev_get_drvdata(container_of(kobj, struct device, kobj)); + struct ch101_data *data = iio_priv(indio_dev); + ssize_t len; + u16 ram_addr; + u8 ram_size; + + if (current_part == CH101_PART_NUMBER) + len = sizeof(ch101_gpr_fw); + else + len = sizeof(ch201_gprmt_fw); + + data->fw_initialized = 0; + if (size > len - pos) { + pr_err("firmware load failed\n"); + return -EFBIG; + } + + pr_info(TAG "%s: %d, %d, %d", __func__, + pos, size, len); + + if (current_part == CH101_PART_NUMBER) { + + pr_info(TAG "write CH101"); + memcpy(ch101_gpr_fw + pos, buf, len - pos); + + if ((len - pos) == size) { + ram_size = (u8)ch101_gpr_fw[CH101_FW_SIZE]; + if (ram_size > 29) + ram_size = 29; + + ram_addr = (u8)ch101_gpr_fw[CH101_FW_SIZE + 2]; + ram_addr <<= 8; + ram_addr += (u8)ch101_gpr_fw[CH101_FW_SIZE + 1]; + + set_ch101_gpr_fw_ram_init_addr(ram_addr); + set_ch101_gpr_fw_ram_write_size(ram_size); + + memcpy(get_ram_ch101_gpr_init_ptr(), + &ch101_gpr_fw[CH101_FW_SIZE + 3], ram_size); + } + } else { + pr_info(TAG "write CH201"); + memcpy(ch201_gprmt_fw + pos, buf, len - pos); + + if ((len - pos) == size) { + ram_size = (u8)ch201_gprmt_fw[CH101_FW_SIZE]; + if (ram_size > 29) + ram_size = 29; + + ram_addr = (u8)ch201_gprmt_fw[CH101_FW_SIZE + 2]; + ram_addr <<= 8; + ram_addr += (u8)ch201_gprmt_fw[CH101_FW_SIZE + 1]; + + set_ch201_gpr_fw_ram_init_addr(ram_addr); + set_ch201_gpr_fw_ram_write_size(ram_size); + + memcpy(get_ram_ch201_gprmt_init_ptr(), + &ch201_gprmt_fw[CH201_FW_SIZE + 3], ram_size); + } + } + return len; +} + +static ssize_t ch101_firmware_read(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + u8 *addr = (u8 *)ch101_gpr_fw + off; + u8 *end_addr = (u8 *)ch101_gpr_fw + CH101_FW_SIZE; + + + return ch101_fw_read(buf, off, count, addr, end_addr); +} + +/* + * ch101_firmware_write_vers() - calling this function will load the firmware. + */ +static ssize_t ch101_firmware_write_vers(struct file *fp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t pos, size_t size) +{ + ssize_t len = sizeof(ch101_gpr_version) - 2; + char ch201_fw_name[] = "ch201"; + char input_file[] = "ch201"; + int res; + + if (size > len - pos) { + pr_err("firmware version load failed\n"); + return -EFBIG; + } + + pr_info(TAG "%s: %d, %d, %d", __func__, + pos, size, len); + + memcpy(input_file, buf, sizeof(input_file)); + pr_info("size=%d, size2=%d, %s\n", + sizeof(input_file), sizeof(ch201_fw_name), input_file); + res = memcmp(buf, ch201_fw_name, sizeof(ch201_fw_name)-1); + + pr_info(TAG "%d, %s\n", res, buf); + + if (res) { + memcpy(ch101_gpr_version + pos, buf, len - pos); + current_part = CH101_PART_NUMBER; + } else { + memcpy(ch201_gprmt_version + pos, buf, len - pos); + current_part = CH201_PART_NUMBER; + } + return len; +} + +static ssize_t ch101_firmware_read_vers(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + u8 *addr = (u8 *)ch101_gpr_version + off; + u8 *end_addr = (u8 *)ch101_gpr_version + CH101_FW_VERS_SIZE; + + ch101_fw_read(buf, off, count, addr, end_addr); + + return strlen(buf); +} + +static struct bin_attribute dmp_firmware = { + .attr = { + .name = "misc_bin_dmp_firmware", + .mode = 0666}, + .read = ch101_firmware_read, + .write = ch101_firmware_write, +}; + +static struct bin_attribute dmp_firmware_vers = { + .attr = { + .name = "misc_bin_dmp_firmware_vers", + .mode = 0666}, + .read = ch101_firmware_read_vers, + .write = ch101_firmware_write_vers, +}; + +int ch101_create_dmp_sysfs(struct iio_dev *indio_dev) +{ + int result = 0; + struct ch101_data *data = iio_priv(indio_dev); + struct device *dev = data->dev; + + dev_info(dev, "%s:\n", __func__); + + dmp_firmware.size = CH101_FW_SIZE+32; + result = sysfs_create_bin_file(&indio_dev->dev.kobj, &dmp_firmware); + if (result) + return result; + + dmp_firmware_vers.size = CH101_FW_VERS_SIZE; + result = sysfs_create_bin_file(&indio_dev->dev.kobj, + &dmp_firmware_vers); + if (result) + return result; + + return result; +} diff --git a/drivers/iio/proximity/inv_ch101/ch101_sysfs.h b/drivers/iio/proximity/inv_ch101/ch101_sysfs.h new file mode 100644 index 000000000000..90cd04105c81 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/ch101_sysfs.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include + +#define MAX_DMP_READ_SIZE 16 + +int ch101_create_dmp_sysfs(struct iio_dev *ind); diff --git a/drivers/iio/proximity/inv_ch101/src/ch101.h b/drivers/iio/proximity/inv_ch101/src/ch101.h new file mode 100644 index 000000000000..b40fe4290519 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch101.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*! \file ch101.h + * + * \brief Internal definitions for the Chirp CH101 ultrasonic sensor. + * + * This file contains various hardware-defined values for the CH101 sensor. + * + * You should not need to edit this file or call the driver functions directly. + * Doing so will reduce your ability to benefit from future enhancements and + * releases from Chirp. + * + */ + +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CH101_H_ +#define CH101_H_ + + +#define CH101_DATA_MEM_SIZE 0x800 +#define CH101_DATA_MEM_ADDR 0x0200 +#define CH101_PROG_MEM_SIZE 0x800 +#define CH101_PROG_MEM_ADDR 0xF800 +#define CH101_FW_SIZE CH101_PROG_MEM_SIZE +#define CH101_FW_VERS_SIZE 32 +#define CH101_FREQCOUNTERCYCLES 128 +#define CH101_EXTERNAL_FW 1 +#define CH101_INIT_RAM_MAX_SIZE 32 + +void set_ch101_gpr_fw_ram_init_addr(int addr); +void set_ch101_gpr_fw_ram_write_size(int size); + +#endif diff --git a/drivers/iio/proximity/inv_ch101/src/ch101_gpr.c b/drivers/iio/proximity/inv_ch101/src/ch101_gpr.c new file mode 100644 index 000000000000..58eae793d0b4 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch101_gpr.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "soniclib.h" +#include "ch101_gpr.h" +#include "ch_common.h" + +u8 ch101_gpr_init(struct ch_dev_t *dev_ptr, struct ch_group_t *grp_ptr, + u8 i2c_addr, u8 io_index, u8 i2c_bus_index) +{ + if (io_index < 3) + dev_ptr->part_number = CH101_PART_NUMBER; + else + dev_ptr->part_number = CH201_PART_NUMBER; + + dev_ptr->app_i2c_address = i2c_addr; + dev_ptr->io_index = io_index; + dev_ptr->i2c_bus_index = i2c_bus_index; + + /* Init firmware-specific function pointers */ + if (dev_ptr->part_number == CH101_PART_NUMBER) { + dev_ptr->firmware = ch101_gpr_fw; + dev_ptr->fw_version_string = ch101_gpr_version; + dev_ptr->ram_init = get_ram_ch101_gpr_init_ptr(); + dev_ptr->get_fw_ram_init_size = get_ch101_gpr_fw_ram_init_size; + dev_ptr->get_fw_ram_init_addr = get_ch101_gpr_fw_ram_init_addr; + } else { + dev_ptr->firmware = ch201_gprmt_fw; + dev_ptr->fw_version_string = ch201_gprmt_version; + dev_ptr->ram_init = get_ram_ch201_gprmt_init_ptr(); + dev_ptr->get_fw_ram_init_size = + get_ch201_gprmt_fw_ram_init_size; + dev_ptr->get_fw_ram_init_addr = + get_ch201_gprmt_fw_ram_init_addr; + } + + dev_ptr->prepare_pulse_timer = ch_common_prepare_pulse_timer; + dev_ptr->store_pt_result = ch_common_store_pt_result; + dev_ptr->store_op_freq = ch_common_store_op_freq; + dev_ptr->store_bandwidth = ch_common_store_bandwidth; + dev_ptr->store_scalefactor = ch_common_store_scale_factor; + dev_ptr->get_locked_state = ch_common_get_locked_state; + + /* Init API function pointers */ + dev_ptr->api_funcs.fw_load = ch_common_fw_load; + dev_ptr->api_funcs.set_mode = ch_common_set_mode; + dev_ptr->api_funcs.set_sample_interval = ch_common_set_sample_interval; + dev_ptr->api_funcs.set_num_samples = ch_common_set_num_samples; + dev_ptr->api_funcs.set_max_range = ch_common_set_max_range; + dev_ptr->api_funcs.set_static_range = ch_common_set_static_range; + dev_ptr->api_funcs.get_range = ch_common_get_range; + dev_ptr->api_funcs.get_amplitude = ch_common_get_amplitude; + dev_ptr->api_funcs.get_iq_data = ch_common_get_iq_data; + dev_ptr->api_funcs.samples_to_mm = ch_common_samples_to_mm; + dev_ptr->api_funcs.mm_to_samples = ch_common_mm_to_samples; + dev_ptr->api_funcs.set_thresholds = NULL; // not supported + dev_ptr->api_funcs.get_thresholds = NULL; // not supported + + /* Init device and group descriptor linkage */ + dev_ptr->group = grp_ptr; // set parent group pointer + grp_ptr->device[io_index] = dev_ptr; // add to parent group + + return 0; +} diff --git a/drivers/iio/proximity/inv_ch101/src/ch101_gpr.h b/drivers/iio/proximity/inv_ch101/src/ch101_gpr.h new file mode 100644 index 000000000000..fa598681e0de --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch101_gpr.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*! \file ch101_gpr.h + * + * \brief Internal definitions for the Chirp CH101 GPR sensor firmware. + * + * This file contains register offsets and other values for use with the CH101 + * GPR sensor firmware. These values are subject to change without notice. + * + * You should not need to edit this file or call the driver functions directly. + * Doing so will reduce your ability to benefit from future enhancements and + * releases from Chirp. + */ + +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CH101_GPR_H_ +#define CH101_GPR_H_ + +#include "ch101.h" +#include "soniclib.h" + +/* GPR firmware registers */ +#define CH101_GPR_REG_OPMODE 0x01 +#define CH101_GPR_REG_TICK_INTERVAL 0x02 +#define CH101_GPR_REG_PERIOD 0x05 +#define CH101_GPR_REG_CAL_TRIG 0x06 +#define CH101_GPR_REG_CAL_TRIG 0x06 +#define CH101_GPR_REG_MAX_RANGE 0x07 +#define CH101_GPR_REG_CALC 0x08 +#define CH101_GPR_REG_ST_RANGE 0x12 +#define CH101_GPR_REG_READY 0x14 +#define CH101_GPR_REG_TOF_SF 0x16 +#define CH101_GPR_REG_TOF 0x18 +#define CH101_GPR_REG_AMPLITUDE 0x1A +#define CH101_GPR_REG_CAL_RESULT 0x0A +#define CH101_GPR_REG_DATA 0x1C + +/* XXX need more values (?) */ +#define CMREG_READY_FREQ_LOCKED_BM (0x02) + +#define CH101_GPR_CTR (0x2B368) + +extern char ch101_gpr_version[CH101_FW_VERS_SIZE]; // version string +extern u8 ch101_gpr_fw[CH101_FW_SIZE + 32]; + +uint16_t get_ch101_gpr_fw_ram_init_addr(void); +uint16_t get_ch101_gpr_fw_ram_init_size(void); + +unsigned char *get_ram_ch101_gpr_init_ptr(void); + +u8 ch101_gpr_init(struct ch_dev_t *dev_ptr, struct ch_group_t *grp_ptr, + u8 i2c_addr, u8 dev_num, u8 i2c_bus_index); + +void ch101_gpr_store_pt_result(struct ch_dev_t *dev_ptr); + +#endif diff --git a/drivers/iio/proximity/inv_ch101/src/ch101_gpr_fw.c b/drivers/iio/proximity/inv_ch101/src/ch101_gpr_fw.c new file mode 100644 index 000000000000..bcea48213b13 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch101_gpr_fw.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 + +// DEFINE THIS TO USE SPECIAL SENSOR F/W WITH PATTERN I/Q DATA +#define USE_IQ_DEBUG 0 + + +#if !USE_IQ_DEBUG + +/* REGULAR FIRMWARE */ + +//Chirp Microsystems Firmware Header Generator + +#include "ch101.h" +#include + +char ch101_gpr_version[CH101_FW_VERS_SIZE]; + +//#define RAM_INIT_ADDRESS 1864 +static uint16_t ram_init_addr; + +//static uint8_t RAM_INIT_WRITE_SIZE; +static uint8_t ram_init_write_size; + +uint16_t get_ch101_gpr_fw_ram_init_addr(void) +{ + return (uint16_t)ram_init_addr; +} + +uint16_t get_ch101_gpr_fw_ram_init_size(void) +{ + return (uint16_t)ram_init_write_size; +} + +void set_ch101_gpr_fw_ram_init_addr(int addr) +{ + ram_init_addr = addr; +} + +void set_ch101_gpr_fw_ram_write_size(int size) +{ + ram_init_write_size = size; +} + +unsigned char ram_ch101_gpr_init[CH101_INIT_RAM_MAX_SIZE] = { +/*0x06, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x00, 0x64, 0x00,*/ + /* 0x00, 0x0C, 0x00, 0x00, 0x01, 0x00,*/ +}; + +unsigned char *get_ram_ch101_gpr_init_ptr(void) +{ + return &ram_ch101_gpr_init[0]; +} + +unsigned char ch101_gpr_fw[CH101_FW_SIZE + CH101_INIT_RAM_MAX_SIZE]; + +#else // USE_IQ_DEBUG + +/* SPECIAL DEBUG FIRMWARE - Puts out ascending */ + /* number sequence instead of real I/Q data */ + +//Chirp Microsystems Firmware Header Generator + +#include "ch101.h" + +char ch101_gpr_version[CH101_FW_VERS_SIZE]; + +#define RAM_INIT_ADDRESS 1864 + +#define RAM_INIT_WRITE_SIZE 16 + +uint16_t get_ch101_gpr_fw_ram_init_addr(void) +{ + return (uint16_t)RAM_INIT_ADDRESS; +} + +uint16_t get_ch101_gpr_fw_ram_init_size(void) +{ + return (uint16_t)RAM_INIT_WRITE_SIZE; +} + +unsigned char ram_ch101_gpr_init[CH101_INIT_RAM_MAX_SIZE] = { +/*0x06, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x00, 0x64,*/ +/* 0x00, 0x00, 0x0C, 0x00, 0x00, 0x01, 0x00,*/ +}; + +unsigned char *get_ram_ch101_gpr_init_ptr(void) +{ + return &ram_ch101_gpr_init[0]; +} + +unsigned char ch101_gpr_fw[CH101_FW_SIZE + CH101_INIT_RAM_MAX_SIZE]; + +#endif // USE_IQ_DEBUG diff --git a/drivers/iio/proximity/inv_ch101/src/ch101_gpr_open.c b/drivers/iio/proximity/inv_ch101/src/ch101_gpr_open.c new file mode 100644 index 000000000000..765ebbe78b00 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch101_gpr_open.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "soniclib.h" +#include "ch101_gpr_open.h" +#include "ch_common.h" + +u8 ch101_gpr_open_init(struct ch_dev_t *dev_ptr, struct ch_group_t *grp_ptr, + u8 i2c_addr, u8 io_index, u8 i2c_bus_index) + +{ + if (io_index < 3) + dev_ptr->part_number = CH101_PART_NUMBER; + else + dev_ptr->part_number = CH201_PART_NUMBER; + + dev_ptr->app_i2c_address = i2c_addr; + dev_ptr->io_index = io_index; + dev_ptr->i2c_bus_index = i2c_bus_index; + + /* Init firmware-specific function pointers */ + dev_ptr->firmware = ch101_gpr_open_fw; + + dev_ptr->fw_version_string = ch101_gpr_open_version; + dev_ptr->ram_init = get_ram_ch101_gpr_open_init_ptr(); + dev_ptr->get_fw_ram_init_size = get_ch101_gpr_open_fw_ram_init_size; + dev_ptr->get_fw_ram_init_addr = get_ch101_gpr_open_fw_ram_init_addr; + + dev_ptr->prepare_pulse_timer = ch_common_prepare_pulse_timer; + dev_ptr->store_pt_result = ch101_gpr_open_store_pt_result; + dev_ptr->store_op_freq = ch_common_store_op_freq; + dev_ptr->store_bandwidth = ch_common_store_bandwidth; + dev_ptr->store_scalefactor = ch_common_store_scale_factor; + dev_ptr->get_locked_state = ch_common_get_locked_state; + + /* Init API function pointers */ + dev_ptr->api_funcs.fw_load = ch_common_fw_load; + dev_ptr->api_funcs.set_mode = ch_common_set_mode; + dev_ptr->api_funcs.set_sample_interval = ch_common_set_sample_interval; + dev_ptr->api_funcs.set_num_samples = ch_common_set_num_samples; + dev_ptr->api_funcs.set_max_range = ch_common_set_max_range; + dev_ptr->api_funcs.set_static_range = ch_common_set_static_range; + dev_ptr->api_funcs.get_range = ch_common_get_range; + dev_ptr->api_funcs.get_amplitude = ch_common_get_amplitude; + dev_ptr->api_funcs.get_iq_data = ch_common_get_iq_data; + dev_ptr->api_funcs.samples_to_mm = ch_common_samples_to_mm; + dev_ptr->api_funcs.mm_to_samples = ch_common_mm_to_samples; + dev_ptr->api_funcs.set_thresholds = NULL; // not supported + dev_ptr->api_funcs.get_thresholds = NULL; // not supported + + /* Init device and group descriptor linkage */ + dev_ptr->group = grp_ptr; // set parent group pointer + grp_ptr->device[io_index] = dev_ptr; // add to parent group + + return 0; +} + +void ch101_gpr_open_store_pt_result(struct ch_dev_t *dev_ptr) +{ + u16 rtc_cal_result; + u16 calc_val; + u32 count; + + chdrv_read_word(dev_ptr, CH101_GPR_OPEN_REG_CAL_RESULT, + &rtc_cal_result); + + count = (rtc_cal_result * 1000) / dev_ptr->group->rtc_cal_pulse_ms; + + calc_val = (u16)((u32)CH101_GPR_OPEN_CTR * 16U + * CH101_FREQCOUNTERCYCLES / count); + chdrv_write_word(dev_ptr, CH101_GPR_OPEN_REG_CALC, calc_val); + + dev_ptr->rtc_cal_result = rtc_cal_result; +} + diff --git a/drivers/iio/proximity/inv_ch101/src/ch101_gpr_open.h b/drivers/iio/proximity/inv_ch101/src/ch101_gpr_open.h new file mode 100644 index 000000000000..588d02a29e01 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch101_gpr_open.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*! \file ch101_gpr_open.h + * + * \brief Internal definitions for the Chirp CH101 GPR Open sensor firmware. + * + * This file contains register offsets and other values for use with the CH101 + * GPR Open sensor firmware. These values are subject to change without notice. + * + * You should not need to edit this file or call the driver functions directly. + * Doing so will reduce your ability to benefit from future enhancements and + * releases from Chirp. + */ + +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CH101_GPR_OPEN_H_ +#define CH101_GPR_OPEN_H_ + +#include "ch101.h" +#include "soniclib.h" + +/* GPR firmware registers */ +#define CH101_GPR_OPEN_REG_OPMODE 0x01 +#define CH101_GPR_OPEN_REG_TICK_INTERVAL 0x02 +#define CH101_GPR_OPEN_REG_PERIOD 0x05 +#define CH101_GPR_OPEN_REG_CAL_TRIG 0x06 +#define CH101_GPR_OPEN_REG_CAL_TRIG 0x06 +#define CH101_GPR_OPEN_REG_MAX_RANGE 0x07 +#define CH101_GPR_OPEN_REG_CALC 0x08 +#define CH101_GPR_OPEN_REG_ST_RANGE 0x12 +#define CH101_GPR_OPEN_REG_READY 0x14 +#define CH101_GPR_OPEN_REG_TOF_SF 0x16 +#define CH101_GPR_OPEN_REG_TOF 0x18 +#define CH101_GPR_OPEN_REG_AMPLITUDE 0x1A +#define CH101_GPR_OPEN_REG_CAL_RESULT 0x0A +#define CH101_GPR_OPEN_REG_DATA 0x1C + +/* XXX need more values (?) */ +#define CMREG_READY_FREQ_LOCKED_BM (0x02) + +#define CH101_GPR_OPEN_CTR (0x2B368) + +extern const char *ch101_gpr_open_version; // version string in fw .c file +extern u8 ch101_gpr_open_fw[CH101_FW_SIZE + 32]; + +uint16_t get_ch101_gpr_open_fw_ram_init_addr(void); +uint16_t get_ch101_gpr_open_fw_ram_init_size(void); + +unsigned char *get_ram_ch101_gpr_open_init_ptr(void); + +u8 ch101_gpr_open_init(struct ch_dev_t *dev_ptr, struct ch_group_t *grp_ptr, + u8 i2c_addr, u8 dev_num, u8 i2c_bus_index); + +void ch101_gpr_open_store_pt_result(struct ch_dev_t *dev_ptr); + +#endif diff --git a/drivers/iio/proximity/inv_ch101/src/ch101_gpr_open_fw.c b/drivers/iio/proximity/inv_ch101/src/ch101_gpr_open_fw.c new file mode 100644 index 000000000000..2809844ed085 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch101_gpr_open_fw.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +//Chirp Microsystems Firmware Header Generator + +#include "ch101.h" + +char *ch101_gpr_open_version; + +//#define RAM_INIT_ADDRESS 1864 +#define RAM_INIT_ADDRESS 0x974 + +#define RAM_INIT_WRITE_SIZE 16 + +unsigned short get_ch101_gpr_open_fw_ram_init_addr(void) +{ + return (unsigned short)RAM_INIT_ADDRESS; +} + +unsigned short get_ch101_gpr_open_fw_ram_init_size(void) +{ + return (unsigned short)RAM_INIT_WRITE_SIZE; +} + +unsigned char ram_ch101_gpr_open_init[RAM_INIT_WRITE_SIZE] = { + /*0x06, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x00,*/ + /*0x64, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x01, 0x00,*/ +}; + +unsigned char *get_ram_ch101_gpr_open_init_ptr(void) +{ + return &ram_ch101_gpr_open_init[0]; +} + +unsigned char ch101_gpr_open_fw[CH101_FW_SIZE + 32]; diff --git a/drivers/iio/proximity/inv_ch101/src/ch101_gpr_sr_open.c b/drivers/iio/proximity/inv_ch101/src/ch101_gpr_sr_open.c new file mode 100644 index 000000000000..0e60e075b062 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch101_gpr_sr_open.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "soniclib.h" +#include "ch101_gpr_sr_open.h" +#include "ch_common.h" + +u8 ch101_gpr_sr_open_init(struct ch_dev_t *dev_ptr, struct ch_group_t *grp_ptr, + u8 i2c_addr, u8 io_index, u8 i2c_bus_index) +{ + dev_ptr->part_number = CH101_PART_NUMBER; + dev_ptr->app_i2c_address = i2c_addr; + dev_ptr->io_index = io_index; + dev_ptr->i2c_bus_index = i2c_bus_index; + + /* Init firmware-specific function pointers */ + dev_ptr->firmware = ch101_gpr_sr_open_fw; + dev_ptr->fw_version_string = ch101_gpr_sr_open_version; + dev_ptr->ram_init = get_ram_ch101_gpr_sr_open_init_ptr(); + dev_ptr->get_fw_ram_init_size = get_ch101_gpr_sr_open_fw_ram_init_size; + dev_ptr->get_fw_ram_init_addr = get_ch101_gpr_sr_open_fw_ram_init_addr; + + dev_ptr->prepare_pulse_timer = ch_common_prepare_pulse_timer; + dev_ptr->store_pt_result = ch101_gpr_sr_open_store_pt_result; + dev_ptr->store_op_freq = ch_common_store_op_freq; + dev_ptr->store_bandwidth = ch_common_store_bandwidth; + dev_ptr->store_scalefactor = ch_common_store_scale_factor; + dev_ptr->get_locked_state = ch_common_get_locked_state; + + /* Init API function pointers */ + dev_ptr->api_funcs.fw_load = ch_common_fw_load; + dev_ptr->api_funcs.set_mode = ch_common_set_mode; + dev_ptr->api_funcs.set_sample_interval = ch_common_set_sample_interval; + dev_ptr->api_funcs.set_num_samples = ch_common_set_num_samples; + dev_ptr->api_funcs.set_max_range = ch_common_set_max_range; + dev_ptr->api_funcs.set_static_range = ch_common_set_static_range; + dev_ptr->api_funcs.get_range = ch_common_get_range; + dev_ptr->api_funcs.get_amplitude = ch_common_get_amplitude; + dev_ptr->api_funcs.get_iq_data = ch_common_get_iq_data; + dev_ptr->api_funcs.samples_to_mm = ch_common_samples_to_mm; + dev_ptr->api_funcs.mm_to_samples = ch_common_mm_to_samples; + dev_ptr->api_funcs.set_thresholds = NULL; // not supported + dev_ptr->api_funcs.get_thresholds = NULL; // not supported + + /* This firmware uses oversampling */ + dev_ptr->oversample = 2; // 4x oversampling (value is power of 2) + + /* Init device and group descriptor linkage */ + dev_ptr->group = grp_ptr; // set parent group pointer + grp_ptr->device[io_index] = dev_ptr; // add to parent group + + return 0; +} + +void ch101_gpr_sr_open_store_pt_result(struct ch_dev_t *dev_ptr) +{ + u16 rtc_cal_result; + u16 calc_val; + u32 count; + + chdrv_read_word(dev_ptr, CH101_GPR_SR_OPEN_REG_CAL_RESULT, + &rtc_cal_result); + + count = (rtc_cal_result * 1000) / dev_ptr->group->rtc_cal_pulse_ms; + + calc_val = (u16)((u32)CH101_GPR_SR_OPEN_CTR * 16U + * CH101_FREQCOUNTERCYCLES / count); + chdrv_write_word(dev_ptr, CH101_GPR_SR_OPEN_REG_CALC, calc_val); + + dev_ptr->rtc_cal_result = rtc_cal_result; +} + diff --git a/drivers/iio/proximity/inv_ch101/src/ch101_gpr_sr_open.h b/drivers/iio/proximity/inv_ch101/src/ch101_gpr_sr_open.h new file mode 100644 index 000000000000..6b9f7c3d734b --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch101_gpr_sr_open.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*! \file ch101_gpr_sr_open.h + * + * \brief Internal definitions for the Chirp CH101 GPR Short-range Open sensor + * firmware. + * + * This file contains register offsets and other values for use with the CH101 + * GPR Short-range Open sensor firmware. These values are subject to change + * without notice. + * + * You should not need to edit this file or call the driver functions directly. + * Doing so will reduce your ability to benefit from future enhancements and + * releases from Chirp. + */ + +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef CH101_GPR_SR_OPEN_H_ +#define CH101_GPR_SR_OPEN_H_ + +#include "ch101.h" +#include "soniclib.h" + +/* GPR firmware registers */ +#define CH101_GPR_SR_OPEN_REG_OPMODE 0x01 +#define CH101_GPR_SR_OPEN_REG_TICK_INTERVAL 0x02 +#define CH101_GPR_SR_OPEN_REG_PERIOD 0x05 +#define CH101_GPR_SR_OPEN_REG_CAL_TRIG 0x06 +#define CH101_GPR_SR_OPEN_REG_CAL_TRIG 0x06 +#define CH101_GPR_SR_OPEN_REG_MAX_RANGE 0x07 +#define CH101_GPR_SR_OPEN_REG_CALC 0x08 +#define CH101_GPR_SR_OPEN_REG_ST_RANGE 0x12 +#define CH101_GPR_SR_OPEN_REG_READY 0x14 +#define CH101_GPR_SR_OPEN_REG_TOF_SF 0x16 +#define CH101_GPR_SR_OPEN_REG_TOF 0x18 +#define CH101_GPR_SR_OPEN_REG_AMPLITUDE 0x1A +#define CH101_GPR_SR_OPEN_REG_CAL_RESULT 0x0A +#define CH101_GPR_SR_OPEN_REG_DATA 0x1C + +/* XXX need more values (?) */ +#define CMREG_READY_FREQ_LOCKED_BM (0x02) + +#define CH101_GPR_SR_OPEN_CTR (0x2B368) + +extern const char *ch101_gpr_sr_open_version; // version string in fw .c file +extern u8 ch101_gpr_sr_open_fw[CH101_FW_SIZE + 32]; + +uint16_t get_ch101_gpr_sr_open_fw_ram_init_addr(void); +uint16_t get_ch101_gpr_sr_open_fw_ram_init_size(void); + +unsigned char *get_ram_ch101_gpr_sr_open_init_ptr(void); + +u8 ch101_gpr_sr_open_init(struct ch_dev_t *dev_ptr, struct ch_group_t + *grp_ptr, u8 i2c_addr, u8 dev_num, u8 i2c_bus_index); + +void ch101_gpr_sr_open_store_pt_result(struct ch_dev_t *dev_ptr); + +#endif diff --git a/drivers/iio/proximity/inv_ch101/src/ch101_gpr_sr_open_fw.c b/drivers/iio/proximity/inv_ch101/src/ch101_gpr_sr_open_fw.c new file mode 100644 index 000000000000..aa3e3954eb77 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch101_gpr_sr_open_fw.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +//Chirp Microsystems Firmware Header Generator + +#include "ch101.h" + +char *ch101_gpr_sr_open_version; + +#define RAM_INIT_ADDRESS 1660 +#define RAM_INIT_WRITE_SIZE 18 + +unsigned short get_ch101_gpr_sr_open_fw_ram_init_addr(void) +{ + return (unsigned short)RAM_INIT_ADDRESS; +} + +unsigned short get_ch101_gpr_sr_open_fw_ram_init_size(void) +{ + return (unsigned short)RAM_INIT_WRITE_SIZE; +} + +unsigned char ram_ch101_gpr_sr_open_init[RAM_INIT_WRITE_SIZE] = { + /* 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00,*/ + /* 0x00, 0x64, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x01, 0x00,*/ +}; + +unsigned char *get_ram_ch101_gpr_sr_open_init_ptr(void) +{ + return &ram_ch101_gpr_sr_open_init[0]; +} + +unsigned char ch101_gpr_sr_open_fw[CH101_FW_SIZE + 32]; + diff --git a/drivers/iio/proximity/inv_ch101/src/ch201.h b/drivers/iio/proximity/inv_ch101/src/ch201.h new file mode 100644 index 000000000000..00e388459cf3 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch201.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*! \file ch201.h + * + * \brief Internal definitions for the Chirp CH201 ultrasonic sensor. + * + * This file contains various hardware-defined values for the CH201 sensor. + * + * You should not need to edit this file or call the driver functions directly. + * Doing so will reduce your ability to benefit from future enhancements and + * releases from Chirp. + */ + +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CH201_H_ +#define CH201_H_ + +#define CH201_DATA_MEM_SIZE 0x800 +#define CH201_DATA_MEM_ADDR 0x0200 +#define CH201_PROG_MEM_SIZE 0x800 +#define CH201_PROG_MEM_ADDR 0xF800 +#define CH201_FW_SIZE CH201_PROG_MEM_SIZE +#define CH201_RAM_INIT_WRITE_SIZE 28 + +#define CH201_FREQCOUNTERCYCLES 128 +#define CH201_INIT_RAM_MAX_SIZE 32 + +void set_ch201_gpr_fw_ram_init_addr(int addr); +void set_ch201_gpr_fw_ram_write_size(int size); + +#endif diff --git a/drivers/iio/proximity/inv_ch101/src/ch201_gprmt.h b/drivers/iio/proximity/inv_ch101/src/ch201_gprmt.h new file mode 100644 index 000000000000..f3847ec7b616 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch201_gprmt.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*! \file ch201_gprmt.h + * + * \brief Internal definitions for the Chirp CH201 GPR Multi-threshold sensor + * firmware. + * + * This file contains register offsets and other values for use with the CH201 + * GPR Multi-threshold sensor firmware. These values are subject to change + * without notice. + * + * You should not need to edit this file or call the driver functions directly. + * Doing so will reduce your ability to benefit from future enhancements and + * releases from Chirp. + */ + +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CH201_GPRMT_H_ +#define CH201_GPRMT_H_ + +#include "ch201.h" +#include "soniclib.h" + +/* GPR with multi thresholds firmware registers */ +#define CH201_GPRMT_REG_OPMODE 0x01 +#define CH201_GPRMT_REG_TICK_INTERVAL 0x02 +#define CH201_GPRMT_REG_PERIOD 0x05 +#define CH201_GPRMT_REG_CAL_TRIG 0x06 +#define CH201_GPRMT_REG_MAX_RANGE 0x07 +#define CH201_GPRMT_REG_THRESH_LEN_0 0x08 +#define CH201_GPRMT_REG_THRESH_LEN_1 0x09 +#define CH201_GPRMT_REG_CAL_RESULT 0x0A +#define CH201_GPRMT_REG_THRESH_LEN_2 0x0C +#define CH201_GPRMT_REG_THRESH_LEN_3 0x0D +#define CH201_GPRMT_REG_ST_RANGE 0x12 +#define CH201_GPRMT_REG_READY 0x14 +#define CH201_GPRMT_REG_THRESH_LEN_4 0x15 +/* start of array of six 2-byte threshold levels */ +#define CH201_GPRMT_REG_THRESHOLDS 0x16 +#define CH201_GPRMT_REG_TOF_SF 0x22 +#define CH201_GPRMT_REG_TOF 0x24 +#define CH201_GPRMT_REG_AMPLITUDE 0x26 +#define CH201_GPRMT_REG_DATA 0x28 + +#define CMREG_READY_FREQ_LOCKED_BM (0x02) + +#define CH201_GPRMT_MAX_SAMPLES (450) // max number of samples +/* total number of thresholds */ +#define CH201_GPRMT_NUM_THRESHOLDS (6) + +#define CH201_FW_VERS_SIZE 32 + +extern char ch201_gprmt_version[CH201_FW_VERS_SIZE]; + // version string in fw .c file +extern u8 ch201_gprmt_fw[CH201_FW_SIZE + 32]; + +uint16_t get_ch201_gprmt_fw_ram_init_addr(void); +uint16_t get_ch201_gprmt_fw_ram_init_size(void); + +unsigned char *get_ram_ch201_gprmt_init_ptr(void); + +u8 ch201_gprmt_init(struct ch_dev_t *dev_ptr, struct ch_group_t + *grp_ptr, u8 i2c_addr, u8 dev_num, u8 i2c_bus_index); + +#endif diff --git a/drivers/iio/proximity/inv_ch101/src/ch201_gprmt_fw.c b/drivers/iio/proximity/inv_ch101/src/ch201_gprmt_fw.c new file mode 100644 index 000000000000..4f5ce24d0ba3 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch201_gprmt_fw.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "ch201.h" +#include "ch201_gprmt.h" + +char ch201_gprmt_version[CH201_FW_VERS_SIZE] = "gprmt_gprmt-201_v9"; + +const char *ch201_gprmt_gitsha1 = "7d06f03f0db7165f2e42305143416f7973dedf01"; + +//#define CH201_RAM_INIT_ADDRESS 2392 +static uint16_t ram_init_addr; +static uint8_t ram_init_write_size; + +uint16_t get_ch201_gprmt_fw_ram_init_addr(void) +{ + return (uint16_t)ram_init_addr; +} +uint16_t get_ch201_gprmt_fw_ram_init_size(void) +{ + return (uint16_t)ram_init_write_size; +} + +unsigned char ram_ch201_gprmt_init[CH201_INIT_RAM_MAX_SIZE] = { +/* 0x88, 0x13, 0xD0, 0x07, 0x20, 0x03, 0x90, 0x01, 0xFA, 0x00, 0xAF,*/ +/* 0x00, 0x06, 0x00, 0x00, 0x00,*/ +/*0x00, 0xFA, 0x00, 0x00, 0x64, 0x00, 0x00, 0x0C, 0x00,*/ +/* 0x00, 0x01, 0x00, */}; + +void set_ch201_gpr_fw_ram_init_addr(int addr) +{ + ram_init_addr = addr; +} + +void set_ch201_gpr_fw_ram_write_size(int size) +{ + ram_init_write_size = size; +} + +unsigned char *get_ram_ch201_gprmt_init_ptr(void) +{ + return &ram_ch201_gprmt_init[0]; +} + +unsigned char ch201_gprmt_fw[CH201_FW_SIZE + CH201_INIT_RAM_MAX_SIZE]; + diff --git a/drivers/iio/proximity/inv_ch101/src/ch_api.c b/drivers/iio/proximity/inv_ch101/src/ch_api.c new file mode 100644 index 000000000000..fb9578cb0c2b --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch_api.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0 +/*! \file ch_api.c + * \brief Chirp SonicLib public API functions for using the Chirp ultrasonic + * sensor. + + * The user should not need to edit this file. This file relies on hardware + * interface functions declared in ch_bsp.h and supplied in the board support + * package (BSP) for the specific hardware platform being used. + */ + +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "soniclib.h" +#include "ch_driver.h" +#include "chirp_bsp.h" + +/*! + * \brief Initialize a Chirp ultrasonic sensor descriptor structure + * + * \param dev_ptr a pointer to the ch_dev_t config structure for a sensor + * + * \return 0 (RET_OK) if successful, non-zero otherwise + * + */ + +u8 ch_init(struct ch_dev_t *dev_ptr, struct ch_group_t *grp_ptr, u8 dev_num, + ch_fw_init_func_t fw_init_func) +{ + u8 ret_val = RET_ERR; + + struct ch_i2c_info_t i2c_info; + + if (fw_init_func != NULL) { + /* Get I2C parameters from BSP */ + ret_val = chbsp_i2c_get_info(grp_ptr, dev_num, &i2c_info); + + if (ret_val == RET_OK) { + /* Save special handling flags for Chirp driver */ + grp_ptr->i2c_drv_flags = i2c_info.drv_flags; + + /* Call asic f/w init function passed in as parameter */ + ret_val = (*fw_init_func)(dev_ptr, grp_ptr, + i2c_info.address, dev_num, i2c_info.bus_num); + } + } + + return ret_val; +} + +u8 ch_get_config(struct ch_dev_t *dev_ptr, struct ch_config_t *config_ptr) +{ + u8 ret_val = 0; + + config_ptr->mode = dev_ptr->mode; + config_ptr->max_range = dev_ptr->max_range; + config_ptr->static_range = dev_ptr->static_range; + config_ptr->sample_interval = dev_ptr->sample_interval; + // thresholds not returned here - use ch_get_thresholds() + config_ptr->thresh_ptr = NULL; + + return ret_val; +} + +u8 ch_set_config(struct ch_dev_t *dev_ptr, struct ch_config_t *config_ptr) +{ + u8 ret_val = 0; + + ret_val = ch_set_mode(dev_ptr, config_ptr->mode); // set operating mode + printf("ch_set_mode: %d\n", ret_val); + + if (!ret_val) { + dev_ptr->mode = config_ptr->mode; + // set max range + ret_val = ch_set_max_range(dev_ptr, config_ptr->max_range); + printf("ch_set_max_range: %d\n", ret_val); + } + + if (!ret_val) { + // static rejection only on CH101 + if (dev_ptr->part_number == CH101_PART_NUMBER) { + // set static target rejection range + ret_val = ch_set_static_range(dev_ptr, + config_ptr->static_range); + printf("ch_set_static_range: %d\n", ret_val); + + if (!ret_val) { + dev_ptr->static_range = + config_ptr->static_range; + } + } + } + + if (!ret_val) { + // set sample interval (free-run mode only) + ret_val = ch_set_sample_interval(dev_ptr, + config_ptr->sample_interval); + printf("ch_set_sample_interval: %d\n", ret_val); + } + + if (!ret_val) { + dev_ptr->sample_interval = config_ptr->sample_interval; + // multi threshold only on CH201 + if (dev_ptr->part_number == CH201_PART_NUMBER) { + // set multiple thresholds + ret_val = ch_set_thresholds(dev_ptr, + config_ptr->thresh_ptr); + printf("ch_set_thresholds: %d\n", ret_val); + } + } + + return ret_val; +} + +u8 ch_group_start(struct ch_group_t *grp_ptr) +{ + u8 ret_val; + + ret_val = chdrv_group_start(grp_ptr); + + return ret_val; +} + +void ch_trigger(struct ch_dev_t *dev_ptr) +{ + chdrv_hw_trigger_up(dev_ptr); + chdrv_hw_trigger_down(dev_ptr); +} + +void ch_group_trigger(struct ch_group_t *grp_ptr) +{ + chdrv_group_hw_trigger(grp_ptr); +} + +void ch_reset(struct ch_dev_t *dev_ptr, enum ch_reset_t reset_type) +{ + // TODO need single device hard reset + if (reset_type == CH_RESET_HARD) + chdrv_group_hard_reset(dev_ptr->group); + else + chdrv_soft_reset(dev_ptr); +} + +void ch_group_reset(struct ch_group_t *grp_ptr, enum ch_reset_t reset_type) +{ + if (reset_type == CH_RESET_HARD) + chdrv_group_hard_reset(grp_ptr); + else + chdrv_group_soft_reset(grp_ptr); +} + +u8 ch_sensor_is_connected(struct ch_dev_t *dev_ptr) +{ + return dev_ptr->sensor_connected; +} + +u16 ch_get_part_number(struct ch_dev_t *dev_ptr) +{ + return dev_ptr->part_number; +} + +u8 ch_get_dev_num(struct ch_dev_t *dev_ptr) +{ + return dev_ptr->io_index; +} + +struct ch_dev_t *ch_get_dev_ptr(struct ch_group_t *grp_ptr, u8 dev_num) +{ + return grp_ptr->device[dev_num]; +} + +u8 ch_get_i2c_address(struct ch_dev_t *dev_ptr) +{ + return dev_ptr->i2c_address; +} + +u8 ch_get_i2c_bus(struct ch_dev_t *dev_ptr) +{ + return dev_ptr->i2c_bus_index; +} + +u8 ch_get_num_ports(struct ch_group_t *grp_ptr) +{ + return grp_ptr->num_ports; +} + +char *ch_get_fw_version_string(struct ch_dev_t *dev_ptr) +{ + return (char *)dev_ptr->fw_version_string; +} + +enum ch_mode_t ch_get_mode(struct ch_dev_t *dev_ptr) +{ + return dev_ptr->mode; +} + +u8 ch_set_mode(struct ch_dev_t *dev_ptr, enum ch_mode_t mode) +{ + int ret_val = RET_ERR; + ch_set_mode_func_t func_ptr = dev_ptr->api_funcs.set_mode; + + if (func_ptr != NULL) + ret_val = (*func_ptr)(dev_ptr, mode); + + return ret_val; +} + +u16 ch_get_sample_interval(struct ch_dev_t *dev_ptr) +{ + u16 sample_interval = 0; + + if (dev_ptr->mode == CH_MODE_FREERUN) + sample_interval = dev_ptr->sample_interval; + + return sample_interval; +} + +u8 ch_set_sample_interval(struct ch_dev_t *dev_ptr, u16 sample_interval) +{ + int ret_val = RET_ERR; + ch_set_sample_interval_func_t func_ptr = + dev_ptr->api_funcs.set_sample_interval; + + if (func_ptr != NULL) + ret_val = (*func_ptr)(dev_ptr, sample_interval); + + return ret_val; +} + +u16 ch_get_num_samples(struct ch_dev_t *dev_ptr) +{ + return dev_ptr->num_rx_samples; +} + +u8 ch_set_num_samples(struct ch_dev_t *dev_ptr, u16 num_samples) +{ + u8 ret_val = RET_ERR; + ch_set_num_samples_func_t func_ptr = dev_ptr->api_funcs.set_num_samples; + + if (func_ptr != NULL) + ret_val = (*func_ptr)(dev_ptr, num_samples); + + // store corresponding range in mm + dev_ptr->max_range = ch_samples_to_mm(dev_ptr, num_samples); + + return ret_val; +} + +u16 ch_get_max_range(struct ch_dev_t *dev_ptr) +{ + return dev_ptr->max_range; +} + +u8 ch_set_max_range(struct ch_dev_t *dev_ptr, u16 max_range) +{ + u8 ret_val = RET_ERR; + ch_set_max_range_func_t func_ptr = dev_ptr->api_funcs.set_max_range; + + if (func_ptr != NULL) + ret_val = (*func_ptr)(dev_ptr, max_range); + + return ret_val; +} + +u16 ch_get_static_range(struct ch_dev_t *dev_ptr) +{ + return dev_ptr->static_range; +} + +u8 ch_set_static_range(struct ch_dev_t *dev_ptr, u16 num_samples) +{ + u8 ret_val = RET_ERR; + ch_set_static_range_func_t func_ptr = + dev_ptr->api_funcs.set_static_range; + + if (func_ptr != NULL) + ret_val = (*func_ptr)(dev_ptr, num_samples); + + return ret_val; +} + +u8 ch_get_range(struct ch_dev_t *dev_ptr, enum ch_range_t range_type, + u32 *range) +{ + u8 err = RET_ERR; + ch_get_range_func_t func_ptr = dev_ptr->api_funcs.get_range; + + if (func_ptr != NULL) + err = (*func_ptr)(dev_ptr, range_type, range); + + return err; +} + +u8 ch_get_amplitude(struct ch_dev_t *dev_ptr, u16 *amplitude) +{ + u8 err = RET_ERR; + ch_get_amplitude_func_t func_ptr = dev_ptr->api_funcs.get_amplitude; + + if (func_ptr != NULL) + err = (*func_ptr)(dev_ptr, amplitude); + + return err; +} + +u32 ch_get_frequency(struct ch_dev_t *dev_ptr) +{ + return dev_ptr->op_frequency; +} + +u16 ch_get_rtc_cal_pulselength(struct ch_dev_t *dev_ptr) +{ + return dev_ptr->group->rtc_cal_pulse_ms; +} + +u16 ch_get_rtc_cal_result(struct ch_dev_t *dev_ptr) +{ + return dev_ptr->rtc_cal_result; +} + +u8 ch_get_iq_data(struct ch_dev_t *dev_ptr, struct ch_iq_sample_t *buf_ptr, + u16 start_sample, u16 num_samples, enum ch_io_mode_t mode) +{ + int ret_val = 0; + ch_get_iq_data_func_t func_ptr = dev_ptr->api_funcs.get_iq_data; + + if (func_ptr != NULL) + ret_val = (*func_ptr)(dev_ptr, buf_ptr, start_sample, + num_samples, mode); + + return ret_val; +} + +u16 ch_samples_to_mm(struct ch_dev_t *dev_ptr, u16 num_samples) +{ + int num_mm = 0; + ch_samples_to_mm_func_t func_ptr = dev_ptr->api_funcs.samples_to_mm; + + if (func_ptr != NULL) + num_mm = (*func_ptr)(dev_ptr, num_samples); + + return num_mm; +} + +u16 ch_mm_to_samples(struct ch_dev_t *dev_ptr, u16 num_mm) +{ + int num_samples = 0; + ch_mm_to_samples_func_t func_ptr = dev_ptr->api_funcs.mm_to_samples; + + if (func_ptr != NULL) + num_samples = (*func_ptr)(dev_ptr, num_mm); + + return num_samples; +} + +u8 ch_set_thresholds(struct ch_dev_t *dev_ptr, + struct ch_thresholds_t *thresh_ptr) +{ + int ret_val = RET_ERR; + ch_set_thresholds_func_t func_ptr = dev_ptr->api_funcs.set_thresholds; + + if (func_ptr != NULL && thresh_ptr != NULL) + ret_val = (*func_ptr)(dev_ptr, thresh_ptr); + + return ret_val; +} + +u8 ch_get_thresholds(struct ch_dev_t *dev_ptr, + struct ch_thresholds_t *thresh_ptr) +{ + int ret_val = RET_ERR; + ch_get_thresholds_func_t func_ptr = dev_ptr->api_funcs.get_thresholds; + + if (func_ptr != NULL && thresh_ptr != NULL) + ret_val = (*func_ptr)(dev_ptr, thresh_ptr); + + return ret_val; +} + +/*! + * \brief Start a non-blocking sensor readout + * + * \param grp_ptr pointer to the ch_group_t descriptor structure for + * a group of sensors + * + * This function starts a non-blocking I/O operation on the specified group + * of sensors. + */ +u8 ch_io_start_nb(struct ch_group_t *grp_ptr) +{ + u8 ret_val = 1; + + // only start I/O if there is a callback function + if (grp_ptr->io_complete_callback != NULL) { + chdrv_group_i2c_start_nb(grp_ptr); + ret_val = 0; + } + + return ret_val; +} + +/*! + * \brief Set callback function for Chirp sensor I/O interrupt + * + * \note + */ +void ch_io_int_callback_set(struct ch_group_t *grp_ptr, + ch_io_int_callback_t callback_func_ptr) +{ + grp_ptr->io_int_callback = callback_func_ptr; +} + +/*! + * \brief Set callback function for Chirp sensor I/O operation complete + * + * \note + */ +void ch_io_complete_callback_set(struct ch_group_t *grp_ptr, + ch_io_complete_callback_t callback_func_ptr) +{ + grp_ptr->io_complete_callback = callback_func_ptr; +} + +/*! + * \brief Continue a non-blocking readout + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * \param i2c_bus_index index value identifying I2C bus within group + * + * Call this function once from your I2C interrupt handler each time it + * completes an I/O operation. It will call the function previously specified + * during ch_io_complete_callback_set() when all group transactions are complete + */ +void ch_io_notify(struct ch_group_t *grp_ptr, u8 i2c_bus_index) +{ + chdrv_group_i2c_irq_handler(grp_ptr, i2c_bus_index); +} + diff --git a/drivers/iio/proximity/inv_ch101/src/ch_common.c b/drivers/iio/proximity/inv_ch101/src/ch_common.c new file mode 100644 index 000000000000..8c72ebe6e265 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch_common.c @@ -0,0 +1,797 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "soniclib.h" +#include "ch_common.h" +#include "chirp_bsp.h" + +static u16 chirp_rx_samples = {0}; + +void set_output_samples(int ind, int val) +{ + chirp_rx_samples = val; +} + +u8 ch_common_set_mode(struct ch_dev_t *dev_ptr, enum ch_mode_t mode) +{ + u8 ret_val = 0; + u8 opmode_reg; + u8 period_reg; + u8 tick_interval_reg; + + if (dev_ptr->part_number == CH101_PART_NUMBER) { + opmode_reg = CH101_COMMON_REG_OPMODE; + period_reg = CH101_COMMON_REG_PERIOD; + tick_interval_reg = CH101_COMMON_REG_TICK_INTERVAL; + } else { + opmode_reg = CH201_COMMON_REG_OPMODE; + period_reg = CH201_COMMON_REG_PERIOD; + tick_interval_reg = CH201_COMMON_REG_TICK_INTERVAL; + } + + printf("API: %s: mode: %02x dev_num: %d\n", + __func__, mode, ch_get_dev_num(dev_ptr)); + + if (dev_ptr->sensor_connected) { + switch (mode) { + case CH_MODE_IDLE: + chdrv_write_byte(dev_ptr, opmode_reg, CH_MODE_IDLE); + chdrv_write_byte(dev_ptr, period_reg, 0); + // XXX need define + chdrv_write_word(dev_ptr, tick_interval_reg, 2048); + break; + + case CH_MODE_FREERUN: + chdrv_write_byte(dev_ptr, opmode_reg, CH_MODE_FREERUN); + // XXX need to set period / tick interval (?) + break; + + case CH_MODE_TRIGGERED_TX_RX: + chdrv_write_byte(dev_ptr, opmode_reg, + CH_MODE_TRIGGERED_TX_RX); + break; + + case CH_MODE_TRIGGERED_RX_ONLY: + chdrv_write_byte(dev_ptr, opmode_reg, + CH_MODE_TRIGGERED_RX_ONLY); + break; + + default: + ret_val = RET_ERR; // return non-zero to indicate error + break; + } + } + + return ret_val; +} + +u8 ch_common_fw_load(struct ch_dev_t *dev_ptr) +{ + u8 ch_err = 0; + u16 prog_mem_addr; + u16 fw_size; + + if (dev_ptr->part_number == CH101_PART_NUMBER) { + prog_mem_addr = CH101_PROG_MEM_ADDR; + fw_size = CH101_FW_SIZE; + } else { + prog_mem_addr = CH201_PROG_MEM_ADDR; + fw_size = CH201_FW_SIZE; + } + + printf("API: %s: addr: %02x\n", __func__, prog_mem_addr); + + ch_err = chdrv_prog_mem_write(dev_ptr, prog_mem_addr, + (u8 *)dev_ptr->firmware, fw_size); + return ch_err; +} + +u8 ch_common_set_sample_interval(struct ch_dev_t *dev_ptr, u16 interval_ms) +{ + u8 period_reg; + u8 tick_interval_reg; + u8 ret_val = 0; + + if (dev_ptr->part_number == CH101_PART_NUMBER) { + period_reg = CH101_COMMON_REG_PERIOD; + tick_interval_reg = CH101_COMMON_REG_TICK_INTERVAL; + } else { + period_reg = CH201_COMMON_REG_PERIOD; + tick_interval_reg = CH201_COMMON_REG_TICK_INTERVAL; + } + + printf("API: %s: interval_ms: %u\n", __func__, interval_ms); + + if (dev_ptr->sensor_connected) { + u32 sample_interval = dev_ptr->rtc_cal_result * interval_ms + / dev_ptr->group->rtc_cal_pulse_ms; + u32 period = (sample_interval / 2048) + 1; // XXX need define + + if (period > UINT8_MAX) { /* check if result fits in register */ + ret_val = 1; + } + + if (ret_val == 0) { + u32 tick_interval = sample_interval / period; + +#ifdef CHDRV_DEBUG + printf("Set period=%u, tick_interval=%u\n", + period, tick_interval); +#endif + + printf("API: %s: tick_interval: %u dev_num: %d\n", + __func__, tick_interval, + ch_get_dev_num(dev_ptr)); + + chdrv_write_byte(dev_ptr, period_reg, (u8)period); + chdrv_write_word(dev_ptr, tick_interval_reg, + (u16)tick_interval); + } + } + + return ret_val; +} + +// XXX need comment block +// XXX note uses actual num_samples, even for CH201 +u8 ch_common_set_num_samples(struct ch_dev_t *dev_ptr, u16 num_samples) +{ + u8 max_range_reg; + // default is error (not connected or num_samples too big) + u8 ret_val = 1; + + printf("API: %s: num_samples: %u dev_num: %d\n", + __func__, num_samples, ch_get_dev_num(dev_ptr)); + + + if (dev_ptr->part_number == CH101_PART_NUMBER) { + max_range_reg = CH101_COMMON_REG_MAX_RANGE; + } else { + max_range_reg = CH201_COMMON_REG_MAX_RANGE; + // each internal count for CH201 represents 2 physical samples + num_samples /= 2; + } + + if (dev_ptr->sensor_connected && num_samples <= UINT8_MAX) { + ret_val = chdrv_write_byte(dev_ptr, max_range_reg, + (u8)num_samples); + } + + return ret_val; +} + +u8 ch_common_set_max_range(struct ch_dev_t *dev_ptr, u16 max_range_mm) +{ + u8 ret_val; + u16 num_samples = 0; + u16 max_num_samples = 0; + + printf("API: %s: max_range_mm: %u dev_num: %d\n", + __func__, max_range_mm, ch_get_dev_num(dev_ptr)); + + if (dev_ptr->part_number == CH101_PART_NUMBER) + max_num_samples = CH101_COMMON_NUM_SAMPLES; + else + max_num_samples = CH201_COMMON_NUM_SAMPLES; + + ret_val = (!dev_ptr->sensor_connected); + + printf("part_number=%u sensor_connected=%u\n", + dev_ptr->part_number, dev_ptr->sensor_connected); + printf("max_num_samples=%u\n", max_num_samples); + + if (!ret_val) { + num_samples = ch_common_mm_to_samples(dev_ptr, max_range_mm); + + if (chirp_rx_samples != 0) + num_samples = chirp_rx_samples; + + printf("num_samples=%u max_range_mm=%u\n", + num_samples, max_range_mm); + + if (num_samples > max_num_samples) { + num_samples = max_num_samples; + dev_ptr->max_range = ch_samples_to_mm(dev_ptr, + num_samples); // store reduced max range + printf("max_range=%u num_samples=%u\n", + dev_ptr->max_range, num_samples); + } else { + // store user-specified max range + dev_ptr->max_range = max_range_mm; + } + +#ifdef CHDRV_DEBUG + printf("num_samples=%u\n", num_samples); +#endif + } + + if (dev_ptr->part_number == CH201_PART_NUMBER) + num_samples *= 2; + + if (!ret_val) + ret_val = ch_common_set_num_samples(dev_ptr, num_samples); + + if (!ret_val) + dev_ptr->num_rx_samples = num_samples; + else + dev_ptr->num_rx_samples = 0; + +#ifdef CHDRV_DEBUG + printf("Set samples: ret_val: %u dev_ptr->num_rx_samples: %u\n", + ret_val, dev_ptr->num_rx_samples); +#endif + return ret_val; +} + +u16 ch_common_mm_to_samples(struct ch_dev_t *dev_ptr, u16 num_mm) +{ + u8 err; + u16 scale_factor; + u32 num_samples = 0; + u32 divisor1; + u32 divisor2; + + err = (!dev_ptr) || (!dev_ptr->sensor_connected); + + printf("dev_ptr->rtc_cal_result=%u\n", dev_ptr->rtc_cal_result); + printf("dev_ptr->group->rtc_cal_pulse_ms=%u\n", + dev_ptr->group->rtc_cal_pulse_ms); + + if (!err) { + if (dev_ptr->part_number == CH101_PART_NUMBER) + divisor1 = 0x2000;// (4*16*128) XXX need define(s) + else + divisor1 = 0x4000;// (4*16*128*2) XXX need define(s) + + printf("dev_ptr->scale_factor=%u\n", dev_ptr->scale_factor); + if (dev_ptr->scale_factor == 0) + ch_common_store_scale_factor(dev_ptr); + + printf("dev_ptr->scale_factor=%u\n", dev_ptr->scale_factor); + scale_factor = dev_ptr->scale_factor; + } + + if (!err) { + divisor2 = (dev_ptr->group->rtc_cal_pulse_ms + * CH_SPEEDOFSOUND_MPS); + // Two steps of division to avoid needing a type larger + // than 32 bits + // Ceiling division to ensure result is at least enough samples + // to meet specified range + num_samples = ((dev_ptr->rtc_cal_result * scale_factor) + + (divisor1 - 1)) / divisor1; + num_samples = ((num_samples * num_mm) + (divisor2 - 1)) + / divisor2; + err = (num_samples > UINT16_MAX); + printf("scale_factor=%u\n", scale_factor); + } + + if (!err) { + // each internal count for CH201 represents 2 physical samples + if (dev_ptr->part_number == CH201_PART_NUMBER) + num_samples *= 2; + + /* Adjust for oversampling, if used */ + num_samples <<= dev_ptr->oversample; + } + if (err) + num_samples = 0; // return zero if error + + return (u16)num_samples; +} + +u16 ch_common_samples_to_mm(struct ch_dev_t *dev_ptr, u16 num_samples) +{ + u32 num_mm = 0; + u32 op_freq = dev_ptr->op_frequency; + + if (op_freq != 0) { + num_mm = ((u32)num_samples * CH_SPEEDOFSOUND_MPS * 8 + * 1000) / (op_freq * 2); + } + + /* Adjust for oversampling, if used */ + num_mm >>= dev_ptr->oversample; + + return (u16)num_mm; +} + +u8 ch_common_set_static_range(struct ch_dev_t *dev_ptr, u16 samples) +{ + u8 ret_val = 1; // default is error return + + printf("API: %s: samples: %u dev_num: %d\n", + __func__, samples, ch_get_dev_num(dev_ptr)); + + if (dev_ptr->part_number == CH101_PART_NUMBER) { // CH101 only + if (dev_ptr->sensor_connected) { + ret_val = chdrv_write_byte(dev_ptr, + CH101_COMMON_REG_STAT_RANGE, (u8)samples); + + if (!ret_val) + dev_ptr->static_range = samples; + } + } + return ret_val; +} + +u8 ch_common_get_range(struct ch_dev_t *dev_ptr, enum ch_range_t range_type, + u32 *range) +{ + u8 tof_reg; + u16 time_of_flight; + u16 scale_factor; + u8 err = 1; + + *range = CH_NO_TARGET; + + if (dev_ptr->sensor_connected) { + if (dev_ptr->part_number == CH101_PART_NUMBER) + tof_reg = CH101_COMMON_REG_TOF; + else + tof_reg = CH201_COMMON_REG_TOF; + + err = chdrv_read_word(dev_ptr, tof_reg, &time_of_flight); + + // If object detected + if (!err && time_of_flight != UINT16_MAX) { + if (dev_ptr->scale_factor == 0) + ch_common_store_scale_factor(dev_ptr); + + scale_factor = dev_ptr->scale_factor; + + if (scale_factor != 0) { + u32 tmp_range; + u32 num = (CH_SPEEDOFSOUND_MPS + * dev_ptr->group->rtc_cal_pulse_ms + * (u32)time_of_flight); + u32 den = ((u32)dev_ptr->rtc_cal_result + * (u32)scale_factor) + >> 11; // XXX need define + + tmp_range = (num / den); + + if (dev_ptr->part_number == CH201_PART_NUMBER) + tmp_range *= 2; + + if (range_type == CH_RANGE_ECHO_ONE_WAY) + tmp_range /= 2; + + /* Adjust for oversampling, if used */ + tmp_range >>= dev_ptr->oversample; + *range = tmp_range; + } + } + } + + printf("API: %s: range: %u dev_num: %d\n", + __func__, *range, ch_get_dev_num(dev_ptr)); + + return err; +} + +u8 ch_common_get_amplitude(struct ch_dev_t *dev_ptr, u16 *amplitude) +{ + u8 amplitude_reg; + u8 err = 1; + + if (dev_ptr->sensor_connected) { + if (dev_ptr->part_number == CH101_PART_NUMBER) + amplitude_reg = CH101_COMMON_REG_AMPLITUDE; + else + amplitude_reg = CH201_COMMON_REG_AMPLITUDE; + + err = chdrv_read_word(dev_ptr, amplitude_reg, amplitude); + } + + printf("API: %s: amplitude: %u dev_num: %d\n", + __func__, *amplitude, ch_get_dev_num(dev_ptr)); + + return err; +} + +u8 ch_common_get_locked_state(struct ch_dev_t *dev_ptr) +{ + u8 ready_reg; + u8 lock_mask; + u8 ret_val = 0; + + if (dev_ptr->part_number == CH101_PART_NUMBER) { + ready_reg = CH101_COMMON_REG_READY; + lock_mask = CH101_COMMON_READY_FREQ_LOCKED; + } else { + ready_reg = CH201_COMMON_REG_READY; + lock_mask = CH201_COMMON_READY_FREQ_LOCKED; + } + + if (dev_ptr->sensor_connected) { + u8 ready_value = 0; + + chdrv_read_byte(dev_ptr, ready_reg, &ready_value); + if (ready_value & lock_mask) + ret_val = 1; + } + + printf("API: %s: lock_mask: %u dev_num: %d\n", + __func__, lock_mask, ch_get_dev_num(dev_ptr)); + + return ret_val; +} + +void ch_common_prepare_pulse_timer(struct ch_dev_t *dev_ptr) +{ + u8 cal_trig_reg; + + printf("API: %s: dev_num: %d\n", __func__, ch_get_dev_num(dev_ptr)); + + if (dev_ptr->part_number == CH101_PART_NUMBER) + cal_trig_reg = CH101_COMMON_REG_CAL_TRIG; + else + cal_trig_reg = CH201_COMMON_REG_CAL_TRIG; + + chdrv_write_byte(dev_ptr, cal_trig_reg, 0); +} + +void ch_common_store_pt_result(struct ch_dev_t *dev_ptr) +{ + u8 pt_result_reg; + u16 rtc_cal_result; + + printf("API: %s: dev_num: %d\n", __func__, ch_get_dev_num(dev_ptr)); + + if (dev_ptr->part_number == CH101_PART_NUMBER) + pt_result_reg = CH101_COMMON_REG_CAL_RESULT; + else + pt_result_reg = CH201_COMMON_REG_CAL_RESULT; + + chdrv_read_word(dev_ptr, pt_result_reg, &rtc_cal_result); + dev_ptr->rtc_cal_result = rtc_cal_result; +} + +void ch_common_store_op_freq(struct ch_dev_t *dev_ptr) +{ + u8 tof_sf_reg; + u16 raw_freq; // aka scale factor + u32 freq_counter_cycles; + u32 num; + u32 den; + u32 op_freq; + + printf("API: %s: dev_num: %d\n", __func__, ch_get_dev_num(dev_ptr)); + + if (dev_ptr->part_number == CH101_PART_NUMBER) { + tof_sf_reg = CH101_COMMON_REG_TOF_SF; + freq_counter_cycles = CH101_FREQCOUNTERCYCLES; + } else { + tof_sf_reg = CH201_COMMON_REG_TOF_SF; + freq_counter_cycles = CH201_FREQCOUNTERCYCLES; + } + + chdrv_read_word(dev_ptr, tof_sf_reg, &raw_freq); + + num = (u32)(((dev_ptr->rtc_cal_result) * 1000U) + / (16U * freq_counter_cycles)) * (u32)(raw_freq); + den = (u32)(dev_ptr->group->rtc_cal_pulse_ms); + op_freq = (num / den); + + dev_ptr->op_frequency = op_freq; +} + +void ch_common_store_bandwidth(struct ch_dev_t *dev_ptr) +{ + /* + * Not supported in current GPR firmware + */ +} + +void ch_common_store_scale_factor(struct ch_dev_t *dev_ptr) +{ + u8 err; + u8 tof_sf_reg; + u16 scale_factor; + + printf("API: %s: dev_num: %d\n", __func__, ch_get_dev_num(dev_ptr)); + + if (dev_ptr->part_number == CH101_PART_NUMBER) + tof_sf_reg = CH101_COMMON_REG_TOF_SF; + else + tof_sf_reg = CH201_COMMON_REG_TOF_SF; + + err = chdrv_read_word(dev_ptr, tof_sf_reg, &scale_factor); + printf("tof_sf_reg=%02x scale_factor=%u err=%u\n", + tof_sf_reg, scale_factor, err); + + if (!err) + dev_ptr->scale_factor = scale_factor; + else + dev_ptr->scale_factor = 0; +} + +// XXX Need comment block +u8 ch_common_set_thresholds(struct ch_dev_t *dev_ptr, + struct ch_thresholds_t *thresholds_ptr) +{ + u8 thresh_len_reg = 0;// offset of register for this threshold's length + u8 thresh_level_reg; // threshold level reg (first in array) + u8 max_num_thresholds; + int ret_val = 1; // default return = error + u8 thresh_num; + u8 thresh_len; + u16 thresh_level; + u16 start_sample = 0; + + printf("API: %s: dev_num: %d\n", __func__, ch_get_dev_num(dev_ptr)); + + if (dev_ptr->sensor_connected) { + if (dev_ptr->part_number == CH101_PART_NUMBER) { + return ret_val; // NOT SUPPORTED in CH101 + + } else { + thresh_level_reg = CH201_COMMON_REG_THRESHOLDS; + max_num_thresholds = CH201_COMMON_NUM_THRESHOLDS; + } + + for (thresh_num = 0; thresh_num < max_num_thresholds; + thresh_num++) { + if (thresh_num < (max_num_thresholds - 1)) { + u16 next_start_sample = + thresholds_ptr->threshold + [thresh_num + 1].start_sample; + + thresh_len = (next_start_sample - start_sample); + start_sample = next_start_sample; + } else { + thresh_len = 0; + } + + if (dev_ptr->part_number == CH201_PART_NUMBER) { + if (thresh_num == 0) { + thresh_len_reg = + CH201_COMMON_REG_THRESH_LEN_0; + } else if (thresh_num == 1) { + thresh_len_reg = + CH201_COMMON_REG_THRESH_LEN_1; + } else if (thresh_num == 2) { + thresh_len_reg = + CH201_COMMON_REG_THRESH_LEN_2; + } else if (thresh_num == 3) { + thresh_len_reg = + CH201_COMMON_REG_THRESH_LEN_3; + } else if (thresh_num == 4) { + thresh_len_reg = + CH201_COMMON_REG_THRESH_LEN_4; + } else if (thresh_num == 5) { + // last threshold does not have length + // field - assumed to extend to end + // of data + thresh_len_reg = 0; + } + } + + // set the length field (if any) for this threshold + if (thresh_len_reg != 0) { + chdrv_write_byte(dev_ptr, thresh_len_reg, + thresh_len); + } + + // write level to this threshold's entry in + // register array + thresh_level = + thresholds_ptr->threshold[thresh_num].level; + chdrv_write_word(dev_ptr, + (thresh_level_reg + + (thresh_num * sizeof(u16))), + thresh_level); + } + + ret_val = 0; // return OK + } + return ret_val; +} + +// XXX Need comment block + +u8 ch_common_get_thresholds(struct ch_dev_t *dev_ptr, + struct ch_thresholds_t *thresholds_ptr) +{ + u8 thresh_len_reg = 0; // offset of register for this threshold length + u8 thresh_level_reg; // threshold level reg (first in array) + u8 max_num_thresholds; + u8 ret_val = 1; // default = error return + u8 thresh_num; + u8 thresh_len = 0; // number of samples described by each threshold + u16 start_sample = 0; // calculated start sample for each threshold + + printf("API: %s: dev_num: %d\n", __func__, ch_get_dev_num(dev_ptr)); + + if (dev_ptr->sensor_connected && thresholds_ptr != NULL) { + if (dev_ptr->part_number == CH101_PART_NUMBER) + return ret_val; // NOT SUPPORTED in CH101 + + thresh_level_reg = CH201_COMMON_REG_THRESHOLDS; + max_num_thresholds = CH201_COMMON_NUM_THRESHOLDS; + + for (thresh_num = 0; thresh_num < max_num_thresholds; + thresh_num++) { + if (dev_ptr->part_number == CH201_PART_NUMBER) { + if (thresh_num == 0) { + thresh_len_reg = + CH201_COMMON_REG_THRESH_LEN_0; + } else if (thresh_num == 1) { + thresh_len_reg = + CH201_COMMON_REG_THRESH_LEN_1; + } else if (thresh_num == 2) { + thresh_len_reg = + CH201_COMMON_REG_THRESH_LEN_2; + } else if (thresh_num == 3) { + thresh_len_reg = + CH201_COMMON_REG_THRESH_LEN_3; + } else if (thresh_num == 4) { + thresh_len_reg = + CH201_COMMON_REG_THRESH_LEN_4; + } else if (thresh_num == 5) { + // last threshold does not have length + // field - assumed to extend to end + // of data + thresh_len_reg = 0; + } + } + + // read the length field register for this threshold + if (thresh_len_reg != 0) { + chdrv_read_byte(dev_ptr, thresh_len_reg, + &thresh_len); + } else { + thresh_len = 0; + } + + thresholds_ptr->threshold[thresh_num].start_sample = + start_sample; + // increment start sample for next threshold + start_sample += thresh_len; + + // get level from this threshold's entry in + // register array + chdrv_read_word(dev_ptr, + (thresh_level_reg + (thresh_num * sizeof(u16))), + &thresholds_ptr->threshold[thresh_num].level); + } + ret_val = 0; // return OK + } + return ret_val; +} + +// XXX need comment block +u8 ch_common_get_iq_data(struct ch_dev_t *dev_ptr, + struct ch_iq_sample_t *buf_ptr, u16 start_sample, u16 num_samples, + enum ch_io_mode_t mode) +{ + u16 iq_data_addr; + u16 max_samples; + struct ch_group_t *grp_ptr = dev_ptr->group; + int error = 1; + + printf("API: %s: dev_num: %d\n", __func__, ch_get_dev_num(dev_ptr)); + + if (dev_ptr->part_number == CH101_PART_NUMBER) { + iq_data_addr = CH101_COMMON_REG_DATA; + max_samples = CH101_COMMON_NUM_SAMPLES; + } else { + iq_data_addr = CH201_COMMON_REG_DATA; + max_samples = CH201_COMMON_NUM_SAMPLES; + } + + iq_data_addr += (start_sample * sizeof(struct ch_iq_sample_t)); + + if (num_samples != 0 && (start_sample + num_samples) <= max_samples) { + u16 num_bytes = (num_samples * sizeof(struct ch_iq_sample_t)); + + if (mode == CH_IO_MODE_BLOCK) { +#ifdef USE_STD_I2C_FOR_IQ + /* blocking transfer - use standard I2C interface */ + error = chdrv_burst_read(dev_ptr, iq_data_addr, + (u8 *)buf_ptr, num_bytes); +#else + /* blocking transfer - use low-level programming + * interface for speed + */ + int num_transfers = (num_bytes + CH_PROG_XFER_SIZE - 1) + / CH_PROG_XFER_SIZE; + int bytes_left = num_bytes; // remaining bytes to read + + /* Convert register offsets to full memory addresses */ + if (dev_ptr->part_number == CH101_PART_NUMBER) + iq_data_addr += CH101_DATA_MEM_ADDR + + CH101_COMMON_I2CREGS_OFFSET; + else + iq_data_addr += CH201_DATA_MEM_ADDR + + CH201_COMMON_I2CREGS_OFFSET; + + // assert PROG pin + chbsp_program_enable(dev_ptr); + + for (int xfer = 0; xfer < num_transfers; xfer++) { + int bytes_to_read; + + // read burst command + u8 message[] = {(0x80 | CH_PROG_REG_CTL), 0x09}; + + if (bytes_left > CH_PROG_XFER_SIZE) + bytes_to_read = CH_PROG_XFER_SIZE; + else + bytes_to_read = bytes_left; + + chdrv_prog_write(dev_ptr, CH_PROG_REG_ADDR, + (iq_data_addr + + (xfer * CH_PROG_XFER_SIZE))); + chdrv_prog_write(dev_ptr, CH_PROG_REG_CNT, + bytes_to_read - 1); + error = chdrv_prog_i2c_write(dev_ptr, message, + sizeof(message)); + error |= chdrv_prog_i2c_read(dev_ptr, + (u8 *)(buf_ptr) + + (xfer * CH_PROG_XFER_SIZE), + bytes_to_read); + + bytes_left -= bytes_to_read; + } + + // de-assert PROG pin + chbsp_program_disable(dev_ptr); +#endif // USE_STD_I2C_FOR_IQ + + } else { + /* non-blocking transfer - queue a read transaction + * (must be started using ch_io_start_nb() ) + */ + + if (grp_ptr->i2c_drv_flags & I2C_DRV_FLAG_USE_PROG_NB) { + /* Use low-level programming interface + * to read data + */ + + /* Convert register offsets to full memory + * addresses + */ + if (dev_ptr->part_number == CH101_PART_NUMBER) { + iq_data_addr += (CH101_DATA_MEM_ADDR + + CH101_COMMON_I2CREGS_OFFSET); + } else { + iq_data_addr += (CH201_DATA_MEM_ADDR + + CH201_COMMON_I2CREGS_OFFSET); + } + + error = chdrv_group_i2c_queue(grp_ptr, dev_ptr, + 1, CHDRV_NB_TRANS_TYPE_PROG, + iq_data_addr, num_bytes, + (u8 *)buf_ptr); + } else { + /* Use regular I2C register interface + * to read data + */ + error = chdrv_group_i2c_queue(grp_ptr, dev_ptr, + 1, CHDRV_NB_TRANS_TYPE_STD, + iq_data_addr, num_bytes, + (u8 *)buf_ptr); + } + } + } + + return error; +} + diff --git a/drivers/iio/proximity/inv_ch101/src/ch_common.h b/drivers/iio/proximity/inv_ch101/src/ch_common.h new file mode 100644 index 000000000000..c1e3b28c16dd --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch_common.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*! \file ch_common.h + * + * \brief Internal driver functions for operation with the Chirp ultrasonic + * sensor. + * + * This file contains common implementations of sensor support routines. + * These are suitable for use with most standard sensor firmware images. + * The firmware-specific init function will set up various function pointers to + * either the common implementations in this file, or corresponding + * firmware-specific implementations. + * + * You should not need to edit this file or call the driver functions directly. + * Doing so will reduce your ability to benefit from future enhancements and + * releases from Chirp. + */ + +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef CH_COMMON_H_ +#define CH_COMMON_H_ + +#include "soniclib.h" + +/* CH-101 common definitions */ + +#define CH101_COMMON_REG_OPMODE 0x01 +#define CH101_COMMON_REG_TICK_INTERVAL 0x02 +#define CH101_COMMON_REG_PERIOD 0x05 +#define CH101_COMMON_REG_MAX_RANGE 0x07 +#define CH101_COMMON_REG_TIME_PLAN 0x09 +#define CH101_COMMON_REG_STAT_RANGE 0x12 +#define CH101_COMMON_REG_STAT_COEFF 0x13 +#define CH101_COMMON_REG_READY 0x14 +#define CH101_COMMON_REG_TOF_SF 0x16 +#define CH101_COMMON_REG_TOF 0x18 +#define CH101_COMMON_REG_AMPLITUDE 0x1A +#define CH101_COMMON_REG_CAL_TRIG 0x06 +#define CH101_COMMON_REG_CAL_RESULT 0x0A +#define CH101_COMMON_REG_DATA 0x1C + +#define CH101_COMMON_I2CREGS_OFFSET 0 + +/* XXX need more values (?) */ +#define CH101_COMMON_READY_FREQ_LOCKED (0x02) + +/* max number of I/Q samples */ +#define CH101_COMMON_NUM_SAMPLES (450) + +/* default value for stationary target coefficient */ +#define CH101_COMMON_STAT_COEFF_DEFAULT (6) +/* total number of thresholds */ +#define CH101_COMMON_NUM_THRESHOLDS (6) + +/* CH-201 common definitions */ + +#define CH201_COMMON_REG_OPMODE 0x01 +#define CH201_COMMON_REG_TICK_INTERVAL 0x02 +#define CH201_COMMON_REG_PERIOD 0x05 +#define CH201_COMMON_REG_CAL_TRIG 0x06 +#define CH201_COMMON_REG_MAX_RANGE 0x07 +#define CH201_COMMON_REG_THRESH_LEN_0 0x08 +#define CH201_COMMON_REG_THRESH_LEN_1 0x09 +#define CH201_COMMON_REG_CAL_RESULT 0x0A +#define CH201_COMMON_REG_THRESH_LEN_2 0x0C +#define CH201_COMMON_REG_THRESH_LEN_3 0x0D +#define CH201_COMMON_REG_ST_RANGE 0x12 +#define CH201_COMMON_REG_READY 0x14 +#define CH201_COMMON_REG_THRESH_LEN_4 0x15 +// start of array of six 2-byte threshold levels +#define CH201_COMMON_REG_THRESHOLDS 0x16 +#define CH201_COMMON_REG_TOF_SF 0x22 +#define CH201_COMMON_REG_TOF 0x24 +#define CH201_COMMON_REG_AMPLITUDE 0x26 +#define CH201_COMMON_REG_DATA 0x28 + +#define CH201_COMMON_I2CREGS_OFFSET 0 + +#define CH201_COMMON_READY_FREQ_LOCKED (0x02) // XXX need more values (?) + +#define CH201_COMMON_NUM_SAMPLES (450) // max number of I/Q samples +#define CH201_COMMON_NUM_THRESHOLDS (6) // total number of thresholds + +#define USE_STD_I2C_FOR_IQ + +/* Function prototypes */ + +uint8_t ch_common_set_mode(struct ch_dev_t *dev_ptr, enum ch_mode_t mode); + +uint8_t ch_common_fw_load(struct ch_dev_t *dev_ptr); + +uint8_t ch_common_set_sample_interval(struct ch_dev_t *dev_ptr, + u16 interval_ms); + +uint8_t ch_common_set_num_samples(struct ch_dev_t *dev_ptr, u16 num_samples); + +uint8_t ch_common_set_max_range(struct ch_dev_t *dev_ptr, u16 max_range_mm); + +uint8_t ch_common_set_static_range(struct ch_dev_t *dev_ptr, u16 samples); + +uint8_t ch_common_get_range(struct ch_dev_t *dev_ptr, + enum ch_range_t range_type, uint32_t *range); + +uint8_t ch_common_get_amplitude(struct ch_dev_t *dev_ptr, u16 *amplitude); + +uint8_t ch_common_get_locked_state(struct ch_dev_t *dev_ptr); + +uint32_t ch_common_get_op_freq(struct ch_dev_t *dev_ptr); + +void ch_common_prepare_pulse_timer(struct ch_dev_t *dev_ptr); + +void ch_common_store_pt_result(struct ch_dev_t *dev_ptr); + +void ch_common_store_op_freq(struct ch_dev_t *dev_ptr); + +void ch_common_store_bandwidth(struct ch_dev_t *dev_ptr); + +void ch_common_store_scale_factor(struct ch_dev_t *dev_ptr); + +uint8_t ch_common_set_thresholds(struct ch_dev_t *dev_ptr, + struct ch_thresholds_t *thresholds_ptr); + +uint8_t ch_common_get_thresholds(struct ch_dev_t *dev_ptr, + struct ch_thresholds_t *thresholds_ptr); + +u16 ch_common_mm_to_samples(struct ch_dev_t *dev_ptr, u16 num_mm); + +u16 ch_common_samples_to_mm(struct ch_dev_t *dev_ptr, u16 num_samples); + +uint8_t ch_common_get_iq_data(struct ch_dev_t *dev_ptr, + struct ch_iq_sample_t *buf_ptr, u16 start_sample, u16 num_samples, + enum ch_io_mode_t io_mode); + +#endif + diff --git a/drivers/iio/proximity/inv_ch101/src/ch_driver.c b/drivers/iio/proximity/inv_ch101/src/ch_driver.c new file mode 100644 index 000000000000..4c9db035767b --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch_driver.c @@ -0,0 +1,1461 @@ +// SPDX-License-Identifier: GPL-2.0 +/*! \file ch_driver.c + * \brief Internal driver functions for operation with the ultrasonic sensor + * + * The user should not need to edit this file. This file relies on hardware + * interface functions declared in ch_bsp.h. If switching to a different + * hardware platform, the functions declared in ch_bsp.h must be provided + * by the user. + */ +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "soniclib.h" +#include "chbsp_init.h" +#include "chirp_bsp.h" +#include "ch_driver.h" + +/*! + * \brief Write bytes to a sensor device in programming mode. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param message pointer to a buffer containing the bytes to write + * \param len number of bytes to write + * + * \return 0 if successful, non-zero otherwise + * + * This function writes bytes to the device using the programming I2C address. + * The PROG line for the device must have been asserted before this function + * is called. + */ +int chdrv_prog_i2c_write(struct ch_dev_t *dev_ptr, u8 *message, u16 len) +{ + int ch_err; + + dev_ptr->i2c_address = CH_I2C_ADDR_PROG; + ch_err = chbsp_i2c_write(dev_ptr, message, len); + dev_ptr->i2c_address = dev_ptr->app_i2c_address; + + return ch_err; +} + +/*! + * \brief Read bytes from a sensor device in programming mode. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param message pointer to a buffer where read bytes will be placed + * \param len number of bytes to read + * + * \return 0 if successful, non-zero otherwise + * + * This function reads bytes from the device using the programming I2C address. + * The PROG line for the device must have been asserted before this function + * is called. + */ +int chdrv_prog_i2c_read(struct ch_dev_t *dev_ptr, u8 *message, u16 len) +{ + int ch_err; + + dev_ptr->i2c_address = CH_I2C_ADDR_PROG; + ch_err = chbsp_i2c_read(dev_ptr, message, len); + dev_ptr->i2c_address = dev_ptr->app_i2c_address; + + return ch_err; +} + +/*! + * \brief Read bytes from a sensor device in programming mode, non-blocking. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param message pointer to a buffer where read bytes will be placed + * \param len number of bytes to read + * + * \return 0 if successful, non-zero otherwise + * + * This function temporarily changes the device I2C address to the low-level + * programming interface, and issues a non-blocking read request. The PROG line + * for the device must have been asserted before this function is called. + */ +int chdrv_prog_i2c_read_nb(struct ch_dev_t *dev_ptr, u8 *message, u16 len) +{ + int ch_err; + + dev_ptr->i2c_address = CH_I2C_ADDR_PROG; + ch_err = chbsp_i2c_read_nb(dev_ptr, message, len); + dev_ptr->i2c_address = dev_ptr->app_i2c_address; + + return ch_err; +} + +/*! + * \brief Write byte to a sensor application register. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param mem_addr sensor memory/register address + * \param data_value data value to transmit + * + * \return 0 if successful, non-zero otherwise + */ +int chdrv_write_byte(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 data_value) +{ + int ch_err; + // insert byte count (1) at start of data + u8 message[] = { sizeof(data_value), data_value }; + + ch_err = chbsp_i2c_mem_write(dev_ptr, mem_addr, message, + sizeof(message)); + + return ch_err; +} + +/*! + * \brief Write multiple bytes to a sensor application register location. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param mem_addr sensor memory/register address + * \param data pointer to transmit buffer containing data to send + * \param len number of bytes to write + * + * \return 0 if successful, non-zero otherwise + */ +int chdrv_burst_write(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 *data, u8 len) +{ + int ch_err; + u8 message[CHDRV_I2C_MAX_WRITE_BYTES + 1]; + + message[0] = (u8)mem_addr; + message[1] = len; + memcpy(&message[2], data, len); + + ch_err = chbsp_i2c_write(dev_ptr, message, len + 2); + + return ch_err; +} + +/*! + * \brief Write 16 bits to a sensor application register. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param reg_addr sensor register address + * \param data data value to transmit + * + * \return 0 if successful, non-zero otherwise + */ +int chdrv_write_word(struct ch_dev_t *dev_ptr, u16 mem_addr, u16 data_value) +{ + // First we write the register address, + // then the number of bytes we're writing + + // Place byte count (2) in first byte of message + // Sensor is little-endian, so LSB goes in at the lower address + + int ch_err; + u8 message[] = { sizeof(data_value), (u8)data_value, + (u8)(data_value >> 8) }; + + ch_err = chbsp_i2c_mem_write(dev_ptr, mem_addr, message, + sizeof(message)); + + return ch_err; +} + +/*! + * \brief Read byte from a sensor application register. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param mem_addr sensor memory/register address + * \param data pointer to receive buffer + * + * \return 0 if successful, non-zero otherwise + */ +int chdrv_read_byte(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 *data) +{ + return chbsp_i2c_mem_read(dev_ptr, mem_addr, data, 1); +} + +/*! + * \brief Read multiple bytes from a sensor application register location. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param mem_addr sensor memory/register address + * \param data pointer to receive buffer + * \param len number of bytes to read + * + * \return 0 if successful, non-zero otherwise + */ +int chdrv_burst_read(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 *data, + u16 num_bytes) +{ + return chbsp_i2c_mem_read(dev_ptr, mem_addr, data, num_bytes); +} + +/*! + * \brief Read 16 bits from a sensor application register. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param mem_addr sensor memory/register address + * \param data pointer to receive buffer + * + * \return 0 if successful, non-zero otherwise + */ +int chdrv_read_word(struct ch_dev_t *dev_ptr, u16 mem_addr, u16 *data) +{ + return chbsp_i2c_mem_read(dev_ptr, mem_addr, (u8 *)data, 2); +} + +/*! + * \brief Calibrate the sensor real-time clock against the host microcontroller + * clock. + * + * \param grp_ptr pointer to the ch_group_t config structure for a group of + * sensors + * + * This function sends a pulse (timed by the host MCU) on the INT line to each + * sensor device in the group, then reads back the counts of sensor RTC cycles + * that elapsed during that pulse on each individual device. The result is + * stored in the ch_dev_t config structure for each device and is subsequently + * used during range calculations. + * + * The length of the pulse is \a dev_ptr->rtc_cal_pulse_ms milliseconds + * (typically 100). This value is set during \a ch_init(). + * + * \note The calibration pulse is sent to all devices in the group at the same + * time. + * Therefore all connected devices will see the same reference pulse length. + * + */ +void chdrv_group_measure_rtc(struct ch_group_t *grp_ptr) +{ + u8 i; + u32 pulselength = grp_ptr->rtc_cal_pulse_ms; + + /* Configure the host's side of the IO pin as a low output */ + chbsp_group_io_clear(grp_ptr); + chbsp_group_set_io_dir_out(grp_ptr); + + /* Set up RTC calibration */ + for (i = 0; i < grp_ptr->num_ports; i++) { + if (grp_ptr->device[i]->sensor_connected) { + grp_ptr->device[i]->prepare_pulse_timer + (grp_ptr->device[i]); + } + } + + printf("chbsp_delay_ms, start: %u ms\n", chbsp_timestamp_ms()); + + /* Trigger a pulse on the IO pin */ + chbsp_critical_section_enter(); +#if CHBSP_RTC_CAL_PULSE_PIN + chbsp_reset_ps_assert(); + pulselength *= 2; +#else + chbsp_group_io_set(grp_ptr); +#endif + + chbsp_delay_ms(pulselength); + +#if CHBSP_RTC_CAL_PULSE_PIN + chbsp_reset_ps_release(); +#else + chbsp_group_io_clear(grp_ptr); +#endif + chbsp_critical_section_leave(); + + printf("chbsp_delay_ms, end: %u ms\n", chbsp_timestamp_ms()); + + chbsp_group_set_io_dir_in(grp_ptr); + + chbsp_delay_ms(1); + + for (i = 0; i < grp_ptr->num_ports; i++) { + if (grp_ptr->device[i]->sensor_connected) { + grp_ptr->device[i]->store_pt_result(grp_ptr->device[i]); + grp_ptr->device[i]->store_op_freq(grp_ptr->device[i]); + grp_ptr->device[i]->store_bandwidth(grp_ptr->device[i]); + grp_ptr->device[i]->store_scalefactor + (grp_ptr->device[i]); + } + } +} + +/*! + * \brief Convert register values to a range using the calibration data. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param tof value of sensor TOF register + * \param tof_sf value of sensor TOF_SF register + * + * \return range in millimeters, or \a CH_NO_TARGET (0xFFFFFFFF) if no object + * is detected. + * + * The range result format is fixed point with 5 binary fractional digits + * (divide by 32 to convert to mm). + * + * This function takes the time-of-flight and scale factor values from the + * sensor, and computes the actual one-way range based on the formulas given + * in the sensor datasheet. + */ +u32 chdrv_one_way_range(struct ch_dev_t *dev_ptr, u16 tof, u16 tof_sf) +{ + u32 num = (CH_SPEEDOFSOUND_MPS * dev_ptr->group->rtc_cal_pulse_ms + * (u32)tof); + u32 den = ((u32)dev_ptr->rtc_cal_result * (u32)tof_sf) >> 10; + u32 range = (num / den); + + if (tof == UINT16_MAX) + return CH_NO_TARGET; + + if (dev_ptr->part_number == 201) + range *= 2;// CH-201 range (TOF) encoding is 1/2 of CH-101 value + +#ifdef CHDRV_DEBUG + printf("%u:%u: TOF=%u, scaleFactor=%u, num=%u, den=%u, range=%u\n", + dev_ptr->i2c_bus_index, dev_ptr->i2c_address, + tof, tof_sf, num, den, range); +#endif + + return range; +} + +/*! + * \brief Add an I2C transaction to the non-blocking queue + * + * \param grp_ptr pointer to the ch_group_t config structure for a group + * of sensors + * \param dev_ptr pointer to an individual ch_dev_t config structure for + * a sensor + * \param rd_wrb read/write indicator: 0 if write operation, 1 if read + * operation + * \param type type of I2C transaction (standard, program interface, + * or external) + * \param addr I2C address + * \param nbytes number of bytes to read/write + * \param data pointer to buffer to receive data or containing data + * to send + * + * \return 0 if successful, non-zero otherwise + */ +int chdrv_group_i2c_queue(struct ch_group_t *grp_ptr, struct ch_dev_t *dev_ptr, + u8 rd_wrb, u8 type, u16 addr, u16 nbytes, + u8 *data) +{ + u8 bus_num = ch_get_i2c_bus(dev_ptr); + int ret_val; + + struct chdrv_i2c_queue_t *q = &grp_ptr->i2c_queue[bus_num]; + struct chdrv_i2c_transaction_t *t = &q->transaction[q->len]; + + if (q->len < CHDRV_MAX_I2C_QUEUE_LENGTH) { + t->databuf = data; + t->dev_ptr = dev_ptr; + t->addr = addr; + t->nbytes = nbytes; + t->rd_wrb = rd_wrb; + t->type = type; + t->xfer_num = 0; + q->len++; + ret_val = 0; + } else { + ret_val = 1; + } + + return ret_val; +} + +/*! + * \brief Start a non-blocking sensor readout + * + * \param grp_ptr pointer to the ch_group_t config structure for a group + * of sensors + * + * This function starts a non-blocking I/O operation on the specified group + * of sensors. + */ +void chdrv_group_i2c_start_nb(struct ch_group_t *grp_ptr) +{ + int bus_num = 0; + + for (bus_num = 0; bus_num < grp_ptr->num_i2c_buses; bus_num++) { + struct chdrv_i2c_queue_t *q = &grp_ptr->i2c_queue[bus_num]; + + if (q->len != 0 && !q->running) + chdrv_group_i2c_irq_handler(grp_ptr, bus_num); + } +} + +/*! + * \brief Continue a non-blocking readout + * + * \param grp_ptr pointer to the ch_group_t config structure for a group + * of sensors + * \param i2c_bus_index index value identifying I2C bus within group + * + * Call this function once from your I2C interrupt handler each time it executes + * It will call the user's callback routine (grp_ptr->io_complete_callback) when + * all transactions are complete. + */ +void chdrv_group_i2c_irq_handler(struct ch_group_t *grp_ptr, u8 i2c_bus_index) +{ + int i; + int transactions_pending; + + struct chdrv_i2c_queue_t *q = &grp_ptr->i2c_queue[i2c_bus_index]; + struct chdrv_i2c_transaction_t *t = &q->transaction[q->idx]; + struct ch_dev_t *dev_ptr = q->transaction[q->idx].dev_ptr; + + // de-assert PROG pin, possibly only briefly + chbsp_program_disable(dev_ptr); + + if (q->idx < q->len) { + dev_ptr = q->transaction[q->idx].dev_ptr; + q->running = 1; + + if (t->type == CHDRV_NB_TRANS_TYPE_EXTERNAL) { + /* Externally-requested transfer */ + (q->idx)++; + chbsp_external_i2c_irq_handler(t); + t->xfer_num++; // count this transfer + + } else if (t->type == CHDRV_NB_TRANS_TYPE_PROG) { + /* Programming interface transfer */ + /* programming interface has max transfer size - + * check if still more to do during this transaction + */ + u8 total_xfers = (t->nbytes + + (CH_PROG_XFER_SIZE - 1)) / CH_PROG_XFER_SIZE; + + if (t->xfer_num < total_xfers) { + /* still need to complete this transaction */ + + u16 bytes_left; + u16 xfer_bytes; + + bytes_left = (t->nbytes + - (t->xfer_num * CH_PROG_XFER_SIZE)); + + // only read operations supported for now + if (t->rd_wrb) { + // read burst command + u8 message[] = { + (0x80 | CH_PROG_REG_CTL), 0x09 + }; + + // reset I2C bus if BSP says it's needed + if (grp_ptr->i2c_drv_flags + & I2C_DRV_FLAG_RESET_AFTER_NB) + chbsp_i2c_reset(dev_ptr); + + // assert PROG pin + chbsp_program_enable(dev_ptr); + if (bytes_left > CH_PROG_XFER_SIZE) + xfer_bytes = CH_PROG_XFER_SIZE; + else + xfer_bytes = bytes_left; + + chdrv_prog_write(dev_ptr, + CH_PROG_REG_ADDR, + (t->addr + (t->xfer_num + * CH_PROG_XFER_SIZE))); + chdrv_prog_write(dev_ptr, + CH_PROG_REG_CNT, (xfer_bytes - 1)); + (void)chdrv_prog_i2c_write(dev_ptr, + message, sizeof(message)); + (void)chdrv_prog_i2c_read_nb(dev_ptr, + (t->databuf + (t->xfer_num + * CH_PROG_XFER_SIZE)), + xfer_bytes); + } + + t->xfer_num++; // count this transfer + + /* if this is the last transfer in this + * transaction, advance queue index + */ + if (t->xfer_num >= total_xfers) + (q->idx)++; + } + } else { + /* Standard transfer */ + if (t->rd_wrb) { + (q->idx)++; + chbsp_i2c_mem_read_nb(t->dev_ptr, t->addr, + t->databuf, t->nbytes); + + } else { + (q->idx)++; + chbsp_i2c_mem_write_nb(t->dev_ptr, t->addr, + t->databuf, t->nbytes); + } + t->xfer_num++; // count this transfer + } + transactions_pending = 1; + } else { + if (q->idx >= 1) { + // get dev_ptr for previous completed transaction + dev_ptr = q->transaction[(q->idx - 1)].dev_ptr; + if (dev_ptr != NULL) { + // de-assert PROG pin for completed transaction + chbsp_program_disable(dev_ptr); + + // reset I2C bus if BSP requires it + if (grp_ptr->i2c_drv_flags + & I2C_DRV_FLAG_RESET_AFTER_NB) + chbsp_i2c_reset(dev_ptr); + } + } + + q->len = 0; + q->idx = 0; + q->running = 0; + transactions_pending = 0; + + for (i = 0; i < grp_ptr->num_i2c_buses; i++) { + if (grp_ptr->i2c_queue[i].len) { + transactions_pending = 1; + break; + } + } + } + + if (!transactions_pending) { + ch_io_complete_callback_t func_ptr = + grp_ptr->io_complete_callback; + + if (func_ptr != NULL) + (*func_ptr)(grp_ptr); + } +} + +/*! + * \brief Wait for an individual sensor to finish start-up procedure. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param timeout_ms number of milliseconds to wait for sensor to finish + * start-up before returning failure + * + * \return 0 if startup sequence finished, non-zero if startup sequence timed + * out or sensor is not connected + * + * After the sensor is programmed, it executes an internal start-up and + * self-test sequence. This function waits the specified time in milliseconds + * for the sensor to finish this sequence. + */ +int chdrv_wait_for_lock(struct ch_dev_t *dev_ptr, u16 timeout_ms) +{ + u32 start_time = chbsp_timestamp_ms(); + int ch_err = !(dev_ptr->sensor_connected); + + while (!ch_err && !(dev_ptr->get_locked_state(dev_ptr))) { + chbsp_delay_ms(10); + ch_err = ((chbsp_timestamp_ms() - start_time) > timeout_ms); + } + +#ifdef CHDRV_DEBUG + if (ch_err) + printf("Sensor %hhu initialization timed out or missing\n", + dev_ptr->io_index); +#endif + + return ch_err; +} + +/*! + * \brief Wait for all sensors to finish start-up procedure. + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * \return 0 if startup sequence finished on all detected sensors, non-zero + * if startup sequence timed out on any sensor(s). + * + * After each sensor is programmed, it executes an internal start-up and + * self-test sequence. This function waits for all sensor devices to finish this + * sequence. For each device, the maximum time to wait is + * \a CHDRV_FREQLOCK_TIMEOUT_MS milliseconds. + */ +int chdrv_group_wait_for_lock(struct ch_group_t *grp_ptr) +{ + int ch_err = 0; + u8 i = 0; + + for (i = 0; i < grp_ptr->num_ports; i++) { + struct ch_dev_t *dev_ptr = grp_ptr->device[i]; + + if (dev_ptr->sensor_connected) + ch_err |= chdrv_wait_for_lock(dev_ptr, + CHDRV_FREQLOCK_TIMEOUT_MS); + } + return ch_err; +} + +/*! + * \brief Start a measurement in hardware triggered mode. + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * \return 0 if success, non-zero if \a grp_ptr pointer is invalid + * + * This function starts a triggered measurement on each sensor in a group, + * by briefly asserting the INT line to each device. Each sensor must have + * already been placed in hardware triggered mode before this function is called + */ +//int chdrv_group_hw_trigger(struct ch_group_t *grp_ptr) +//{ +// int ch_err = !grp_ptr; +// +// if (!ch_err) { +// // Disable pin interrupt before triggering pulse +// chbsp_group_io_interrupt_disable(grp_ptr); +// +// // Generate pulse +// printf("%s: Generate pulse - Start", __func__); +// +// chbsp_group_set_io_dir_out(grp_ptr); +// chbsp_group_io_set(grp_ptr); +// chbsp_delay_us(5); // Pulse needs to be a minimum of 800ns long +// chbsp_group_io_clear(grp_ptr); +// chbsp_group_set_io_dir_in(grp_ptr); +// +// // Delay a bit before re-enabling pin interrupt to avoid +// // possibly triggering on falling-edge noise +// chbsp_delay_us(10); +// +// printf("%s: Generate pulse - End", __func__); +// +// chbsp_group_io_interrupt_enable(grp_ptr); +// } +// return ch_err; +//} + +int chdrv_group_hw_trigger(struct ch_group_t *grp_ptr) +{ + int ch_err = !grp_ptr; + u8 dev_num; + struct ch_dev_t *dev_ptr; + + if (ch_err) + return ch_err; + + for (dev_num = 0; dev_num < ch_get_num_ports(grp_ptr); dev_num++) { + dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + if (ch_sensor_is_connected(dev_ptr)) + ch_err |= chdrv_hw_trigger_up(dev_ptr); + } + + chbsp_delay_us(5); // Pulse needs to be a minimum of 800ns long + + for (dev_num = 0; dev_num < ch_get_num_ports(grp_ptr); dev_num++) { + dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + if (ch_sensor_is_connected(dev_ptr)) + ch_err |= chdrv_hw_trigger_down(dev_ptr); + } + // Delay a bit before re-enabling pin interrupt to avoid + // possibly triggering on falling-edge noise + chbsp_delay_us(10); + + for (dev_num = 0; dev_num < ch_get_num_ports(grp_ptr); dev_num++) { + dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + chbsp_io_interrupt_enable(dev_ptr); + } + + return ch_err; +} + +/*! + * \brief Start a measurement in hardware triggered mode on one sensor. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * \return 0 if success, non-zero if \a dev_ptr pointer is invalid + * + * This function starts a triggered measurement on a single sensor, by briefly + * asserting the INT line to the device. The sensor must have already been + * placed in hardware triggered mode before this function is called. + * + * \note This function requires implementing the optional BSP functions + * to control the INT pin direction and level for individual sensors + * (\a chbsp_set_io_dir_in(), \a chbsp_set_io_dir_out(), \a chbsp_io_set(), and + * \a chbsp_io_clear()). + */ +int chdrv_hw_trigger_up(struct ch_dev_t *dev_ptr) +{ + int ch_err = !dev_ptr; + + if (!ch_err) { + // Disable pin interrupt before triggering pulse + chbsp_io_interrupt_disable(dev_ptr); + // Generate pulse + printf("%s: Generate pulse - Start", __func__); + chbsp_set_io_dir_out(dev_ptr); + chbsp_io_set(dev_ptr); + + } + return ch_err; +} + +int chdrv_hw_trigger_down(struct ch_dev_t *dev_ptr) +{ + int ch_err = !dev_ptr; + + if (!ch_err) { + chbsp_io_clear(dev_ptr); + chbsp_set_io_dir_in(dev_ptr); + printf("%s: Generate pulse - End", __func__); + } + return ch_err; +} + +/*! + * \brief Write to a sensor programming register. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param reg_addr sensor programming register address. + * \param data 8-bit or 16-bit data to transmit. + * + * \return 0 if write to sensor succeeded, non-zero otherwise + * + * This local function writes a value to a sensor programming register. + */ +int chdrv_prog_write(struct ch_dev_t *dev_ptr, u8 reg_addr, u16 data) +{ + /* Set register address write bit */ + /* Write the register address, followed by the value to be written */ + u8 message[] = { reg_addr | 0x80, (u8)data, (u8)(data >> 8) }; + + /* For the 2-byte registers, we also need to write MSB after LSB */ + return chdrv_prog_i2c_write(dev_ptr, message, + (1 + CH_PROG_SIZEOF(reg_addr))); +} + +/*! + * \brief Write to sensor memory. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param addr sensor programming register start address + * \param message pointer to data to transmit + * \param nbytes number of bytes to write + * + * \return 0 if write to sensor succeeded, non-zero otherwise + * + * This function writes to sensor memory using the low-level programming + * interface. The type of write is automatically determined based on data length + * and target address alignment. + */ +int chdrv_prog_mem_write(struct ch_dev_t *dev_ptr, u16 addr, u8 *message, + u16 nbytes) +{ + int ch_err = (nbytes == 0); + + if (!ch_err) + ch_err = chdrv_prog_write(dev_ptr, CH_PROG_REG_ADDR, addr); + + if (nbytes == 1 || (nbytes == 2 && !(addr & 1))) { + u16 data = *((u16 *)message); + + if (!ch_err) { + ch_err = chdrv_prog_write(dev_ptr, CH_PROG_REG_DATA, + data); + } + + if (!ch_err) { + // XXX need define + u8 opcode = (0x03 | ((nbytes == 1) ? 0x08 : 0x00)); + + ch_err = chdrv_prog_write(dev_ptr, CH_PROG_REG_CTL, + opcode); + } + } else { + // XXX need define + static const u8 burst_hdr[2] = { 0xC4, 0x0B }; + + if (!ch_err) + ch_err = chdrv_prog_write(dev_ptr, CH_PROG_REG_CNT, + (nbytes - 1)); + + if (!ch_err) + ch_err = chdrv_prog_i2c_write(dev_ptr, + (u8 *)burst_hdr, sizeof(burst_hdr)); + + if (!ch_err) + ch_err = chdrv_prog_i2c_write(dev_ptr, message, nbytes); + } + return ch_err; +} + +/*! + * \brief Read from a sensor programming register. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param reg_addr sensor programming register address + * \param data pointer to a buffer where read bytes will be placed + * + * \return 0 if read from sensor succeeded, non-zero otherwise + * + * This local function reads a value from a sensor programming register. + */ +static int chdrv_prog_read(struct ch_dev_t *dev_ptr, u8 reg_addr, u16 *data) +{ + u8 nbytes = CH_PROG_SIZEOF(reg_addr); + + u8 read_data[2]; + u8 message[1] = { 0x7F & reg_addr }; + + int ch_err = chdrv_prog_i2c_write(dev_ptr, message, sizeof(message)); + + if (!ch_err) + ch_err = chdrv_prog_i2c_read(dev_ptr, read_data, nbytes); + + if (!ch_err) { + *data = read_data[0]; + if (nbytes > 1) + *data |= (((u16)read_data[1]) << 8); + } + + *data = (0x0a) | ((0x02) << 8); + + return ch_err; +} + +/*! + * \brief Transfer firmware to the sensor on-chip memory. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * \return 0 if firmware write succeeded, non-zero otherwise + * + * This local function writes the sensor firmware image to the device. + */ +/*! + */ +static int chdrv_write_firmware(struct ch_dev_t *dev_ptr) +{ + // pointer to firmware load function + ch_fw_load_func_t func_ptr = dev_ptr->api_funcs.fw_load; + int ch_err = ((func_ptr == NULL) || (!dev_ptr->sensor_connected)); + +#ifdef CHDRV_DEBUG + u32 prog_time; + + if (!ch_err) + chbsp_print_str("write_firmware\n"); +#endif + + if (!ch_err) { +#ifdef CHDRV_DEBUG + chbsp_print_str("Programming Chirp sensor...\n"); + prog_time = chbsp_timestamp_ms(); +#endif + if (func_ptr != NULL) + ch_err = (*func_ptr)(dev_ptr); + else + ch_err = 1; // indicate error + } + +#ifdef CHDRV_DEBUG + if (!ch_err) { + prog_time = chbsp_timestamp_ms() - prog_time; + printf("Wrote %u bytes in %u ms.\n", CH101_FW_SIZE, prog_time); + } +#endif + + return ch_err; +} + +/*! + * \brief Initialize sensor memory contents. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * \return 0 if memory write succeeded, non-zero otherwise + * + * This local function initializes memory locations in the Chirp sensor, + * as required by the firmware image. + */ +static int chdrv_init_ram(struct ch_dev_t *dev_ptr) +{ + int ch_err = !dev_ptr || !dev_ptr->sensor_connected; + +#ifdef CHDRV_DEBUG + u32 prog_time; + + if (!ch_err) + chbsp_print_str("init_ram\n"); +#endif + + // if size is not zero, ram init data exists + if (!ch_err && (dev_ptr->get_fw_ram_init_size() != 0)) { + u16 ram_address; + u16 ram_bytecount; + + ram_address = dev_ptr->get_fw_ram_init_addr(); + ram_bytecount = dev_ptr->get_fw_ram_init_size(); + + if (!ch_err) { +#ifdef CHDRV_DEBUG + chbsp_print_str("Loading RAM init data\n"); + printf("%d, %d\n", ram_address, ram_bytecount); + prog_time = chbsp_timestamp_ms(); +#endif + + ch_err = chdrv_prog_mem_write(dev_ptr, ram_address, + (u8 *)dev_ptr->ram_init, ram_bytecount); +#ifdef CHDRV_DEBUG + if (!ch_err) { + prog_time = chbsp_timestamp_ms() - prog_time; + printf("Wrote %u bytes in %u ms.\n", + ram_bytecount, prog_time); + } +#endif + } + } + return ch_err; +} + +/*! + * \brief Reset and halt a sensor + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * \return 0 if write to sensor succeeded, non-zero otherwise + * + * This function resets and halts a sensor device by writing to the control + * registers. In order for the device to respond, the PROG pin for the device + * must be asserted before this function is called. + */ +static int chdrv_reset_and_halt(struct ch_dev_t *dev_ptr) +{ + // reset asic // XXX need define + int ch_err = chdrv_prog_write(dev_ptr, CH_PROG_REG_CPU, 0x40); + + // halt asic and disable watchdog; // XXX need define + ch_err |= chdrv_prog_write(dev_ptr, CH_PROG_REG_CPU, 0x11); + + return ch_err; +} + +/*! + * \brief Detect a connected sensor. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * \return 1 if sensor is found, 0 if no sensor is found + * + * This function checks for a sensor sensor on the I2C bus by attempting + * to reset, halt, and read from the device using the programming + * interface I2C address (0x45). + * + * In order for the device to respond, the PROG pin for the device must be + * asserted before this function is called. + */ +int chdrv_prog_ping(struct ch_dev_t *dev_ptr) +{ + // Try to ping to the sensor to make sure it's connected and working + u16 tmp = 0; + int ch_err; + + ch_err = chdrv_reset_and_halt(dev_ptr); + + ch_err |= chdrv_prog_read(dev_ptr, CH_PROG_REG_PING, &tmp); + +#ifdef CHDRV_DEBUG + if (!ch_err) + printf("Test I2C read: %04X\n", tmp); + else + printf("Test I2C read %d sensor FAIL\n", dev_ptr->io_index); +#endif + + return !(ch_err); +} + +/*! + * \brief Detect, program, and start a sensor. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * \return 0 if write to sensor succeeded, non-zero otherwise + * + * This function probes the I2C bus for the device. If it is found, the sensor + * firmware is programmed into the device, and the application I2C address + * is set. Then the sensor is reset and execution starts. + * + * Once started, the sensor device will begin an internal initialization and + * self-test sequence. The \a chdrv_wait_for_lock() + * function may be used to wait for this sequence to complete. + * + * \note This routine will leave the PROG pin de-asserted when it completes. + */ +int chdrv_detect_and_program(struct ch_dev_t *dev_ptr) +{ + int ch_err = !dev_ptr; + + if (ch_err) + return ch_err; + + chbsp_program_enable(dev_ptr); // assert PROG pin + + if (chdrv_prog_ping(dev_ptr)) { // if device found + chdrv_discovery_hook_t hook_ptr; + + dev_ptr->sensor_connected = 1; + // Call device discovery hook routine, if any + hook_ptr = dev_ptr->group->disco_hook; + // hook routine can return error, will abort device init + if (hook_ptr != NULL) + ch_err = (*hook_ptr)(dev_ptr); + +#ifdef CHDRV_DEBUG + if (!ch_err) { + u16 prog_stat = UINT16_MAX; + + ch_err = chdrv_prog_read(dev_ptr, CH_PROG_REG_STAT, + &prog_stat); + if (!ch_err) + printf("PROG_STAT: 0x%02X\n", prog_stat); + } +#endif + + ch_err |= chdrv_init_ram(dev_ptr); // init ram values + ch_err |= chdrv_write_firmware(dev_ptr); // transfer program + ch_err |= chdrv_reset_and_halt(dev_ptr); // reset asic, since it + // was running mystery code before halt + +#ifdef CHDRV_DEBUG + printf("CH101 INIT ch_err: %d\n", ch_err); + + if (!ch_err) + printf("Changing I2C address to 0x%02x\n", + dev_ptr->i2c_address); +#endif + + if (!ch_err) + ch_err = chdrv_prog_mem_write(dev_ptr, 0x01C5, + &dev_ptr->i2c_address, 1); // XXX need define + + /* Run charge pumps */ + if (!ch_err) { + u16 write_val; + + write_val = 0x0200; // XXX need defines + ch_err |= chdrv_prog_mem_write(dev_ptr, 0x01A6, + (u8 *)&write_val, 2);// PMUT.CNTRL4 = HVVSS_FON + + chbsp_delay_ms(5); + + write_val = 0x0600; + // PMUT.CNTRL4 = (HVVSS_FON | HVVDD_FON) + ch_err = chdrv_prog_mem_write(dev_ptr, 0x01A6, + (u8 *)&write_val, 2); + + chbsp_delay_ms(5); + + write_val = 0x0000; + ch_err |= chdrv_prog_mem_write(dev_ptr, 0x01A6, + (u8 *)&write_val, 2);// PMUT.CNTRL4 = 0 + } + + // Exit programming mode and run the chip + if (!ch_err) + ch_err = chdrv_prog_write(dev_ptr, CH_PROG_REG_CPU, 2); + } else { + printf("prog_ping failed - no device found\n"); + // prog_ping failed - no device found + dev_ptr->sensor_connected = 0; + ch_err = 1; + } + + chbsp_program_disable(dev_ptr); // de-assert PROG pin + + // if error, reinitialize I2C bus associated with this device + if (ch_err) { + chbsp_debug_toggle(CHDRV_DEBUG_PIN_NUM); + chbsp_i2c_reset(dev_ptr); + } + + // only marked as connected if no errors + if (ch_err) { + dev_ptr->sensor_connected = 0; +#ifdef CHDRV_DEBUG + printf("ERROR: 0x%02X\n", dev_ptr->i2c_address); +#endif + } + + return ch_err; +} + +/*! + * \brief Put the sensors on a bus into an idle state + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor, + * used to identify the target I2C bus + * + * \return 0 if successful, non-zero on error + * + * This function loads a tiny idle loop program to all ASICs on a given i2c bus. + * This function assumes that all of the devices on the given bus are halted in + * programming mode (i.e. PROG line is asserted). + * + * \note This routine writes to all devices simultaneously, so I2C signaling + * (i.e. ack's) on the bus may be driven by multiple slaves at once. + */ +int chdrv_set_idle(struct ch_dev_t *dev_ptr) +{ + u16 val; + static const u16 idle_loop[2] = { 0x4003, 0xFFFC }; // XXX need define + + int ch_err = chdrv_prog_mem_write(dev_ptr, 0xFFFC, + (u8 *)&idle_loop[0], sizeof(idle_loop)); + if (!ch_err) + ch_err = chdrv_reset_and_halt(dev_ptr); + + // keep wdt stopped after we exit programming mode + val = 0x5a80; // XXX need define + if (!ch_err) + ch_err = chdrv_prog_mem_write(dev_ptr, 0x0120, (u8 *)&val, + sizeof(val)); // XXX need define + + return ch_err; +} + +/*! + * \brief Detect, program, and start all sensors in a group. + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * \return 0 for success, non-zero if write(s) failed to any sensor initially + * detected as present + * + * This function probes the I2C bus for each device in the group. + * For each detected sensor, the sensor firmware is programmed into the device, + * and the application I2C address is set. + * Then the sensor is reset and execution starts. + * + * Once started, each sensor device will begin an internal initialization and + * self-test sequence. The \a chdrv_group_wait_for_lock() function may be used + * to wait for this sequence to complete on all devices in the group. + * + * \note This routine will leave the PROG pin de-asserted for all devices in the + * group when it completes. + */ +int chdrv_group_detect_and_program(struct ch_group_t *grp_ptr) +{ + int ch_err = 0; + u8 i = 0; + + for (i = 0; i < grp_ptr->num_ports; i++) { + struct ch_dev_t *dev_ptr = grp_ptr->device[i]; + + if (!dev_ptr->sensor_connected) + continue; + + ch_err = chdrv_detect_and_program(dev_ptr); + + if (!ch_err && dev_ptr->sensor_connected) + grp_ptr->sensor_count++; + + if (ch_err) + break; + } + return ch_err; +} + +/*! + * \brief Initialize data structures and hardware for sensor interaction. + * + * \param grp_ptr pointer to the ch_group_t config structure for a group + * of sensors + * \param num_ports the total number of physical sensor ports available + * + * \return 0 if hardware initialization is successful, non-zero otherwise + * + * This function is called internally by \a chdrv_group_start(). + */ +int chdrv_group_prepare(struct ch_group_t *grp_ptr) +{ + int ch_err = !grp_ptr; + u8 i; + + if (!ch_err) { + grp_ptr->sensor_count = 0; + + for (i = 0; i < grp_ptr->num_i2c_buses; i++) { + grp_ptr->i2c_queue[i].len = 0; + grp_ptr->i2c_queue[i].idx = 0; + grp_ptr->i2c_queue[i].read_pending = 0; + grp_ptr->i2c_queue[i].running = 0; + } + + chbsp_group_pin_init(grp_ptr); + + ch_err = chbsp_i2c_init(); + } + + return ch_err; +} + +/*! + * \brief Initialize and start a group of sensors. + * + * \param grp_ptr pointer to the ch_group_t config structure for a group + * of sensors + * \param num_ports the total number of physical sensor ports available + * + * \return 0 if successful, 1 if device doesn't respond + * + * This function resets each sensor in programming mode, transfers the firmware + * image to the sensor's on-chip memory, changes the sensor's application + * I2C address from the default, then starts the sensor and sends a timed pulse + * on the INT line for real-time clock calibration. + * + * This function assumes firmware-specific initialization has already been + * performed for each a ch_dev_t descriptor for each sensor in the group. + * See \a ch_init(). + */ +#define CH_PROG_XFER_RETRY 4 + +int chdrv_group_start(struct ch_group_t *grp_ptr) +{ + int ch_err = !grp_ptr; + int i; + u8 prog_tries = 0; + +#ifdef CHDRV_DEBUG + const u32 start_time = chbsp_timestamp_ms(); + + printf("%s\n", __func__); +#endif + + if (!ch_err) + ch_err = chdrv_group_prepare(grp_ptr); + + printf("%s: ch_err = %d\n", __func__, ch_err); + + if (ch_err) + return ch_err; + +RESET_AND_LOAD: + do { + struct ch_dev_t *c_prev; + + printf("%s: %d\n", __func__, 1); + + chbsp_reset_assert(); + for (i = 0; i < grp_ptr->num_ports; i++) + chbsp_program_enable(grp_ptr->device[i]); + + chbsp_delay_ms(1); + chbsp_reset_release(); + + printf("%s: %d\n", __func__, 2); + + /* For every i2c bus, set the devices idle in parallel, + * then disable programming mode for all devices on that bus + * This is kludgey because we don't have a great way of + * iterating over the i2c buses + */ + c_prev = grp_ptr->device[0]; + if (c_prev->sensor_connected) + chdrv_set_idle(c_prev); + + for (i = 0; i < grp_ptr->num_ports; i++) { + struct ch_dev_t *c = grp_ptr->device[i]; + + if (!c->sensor_connected) + continue; + + if (c->i2c_bus_index != c_prev->i2c_bus_index) + chdrv_set_idle(c); + + chbsp_program_disable(c); + c_prev = c; + } + + ch_err = chdrv_group_detect_and_program(grp_ptr); + } while (ch_err && prog_tries++ < CH_PROG_XFER_RETRY); + + printf("%s: %d\n", __func__, 3); + + if (!ch_err) { + ch_err = (grp_ptr->sensor_count == 0); +#ifdef CHDRV_DEBUG + if (ch_err) { + printf("No Chirp sensor devices are responding\n"); + } else { + printf("Sensor count: %u, %u ms.\n", + grp_ptr->sensor_count, + chbsp_timestamp_ms() - start_time); + for (i = 0; i < grp_ptr->num_ports; i++) { + struct ch_dev_t *dev = grp_ptr->device[i]; + + if (dev->sensor_connected) { + printf("Chirp sensor I2C addr %u:%u.\n", + dev->i2c_bus_index, + dev->i2c_address); + } + } + } +#endif + } + + if (!ch_err) { + ch_err = chdrv_group_wait_for_lock(grp_ptr); + if (ch_err && prog_tries++ < CH_PROG_XFER_RETRY + 1) + goto RESET_AND_LOAD; + } + + // Just for test 100 ms pulse + ch_err = 0; + + if (!ch_err) { +#ifdef CHDRV_DEBUG + printf("Frequency locked, %u ms\n", + chbsp_timestamp_ms() - start_time); + printf("Pulse length %u ms\n", + grp_ptr->rtc_cal_pulse_ms); +#endif + + chbsp_delay_ms(1); + chdrv_group_measure_rtc(grp_ptr); + +#ifdef CHDRV_DEBUG + printf("RTC calibrated, %u ms\n", + chbsp_timestamp_ms() - start_time); + + for (i = 0; i < grp_ptr->num_ports; i++) { + if (grp_ptr->device[i]->sensor_connected) + printf("Cal result: %u\n", + grp_ptr->device[i]->rtc_cal_result); + } +#endif + } + + return ch_err; +} + +/*! + * \brief Perform a soft reset on a sensor + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * \return 0 if successful, non-zero otherwise + * + * This function performs a soft reset on an individual sensor by writing to + * a special control register. + */ +int chdrv_soft_reset(struct ch_dev_t *dev_ptr) +{ + int ch_err = RET_ERR; + + if (dev_ptr->sensor_connected) { + chbsp_program_enable(dev_ptr); + + ch_err = chdrv_reset_and_halt(dev_ptr); + + if (!ch_err) { + ch_err = chdrv_init_ram(dev_ptr) || + chdrv_reset_and_halt(dev_ptr); + } + if (!ch_err) { + // Exit programming mode and run the chip + // XXX need define + ch_err = chdrv_prog_mem_write(dev_ptr, 0x01C5, + &dev_ptr->i2c_address, 1) || + chdrv_prog_write(dev_ptr, CH_PROG_REG_CPU, 2); + } + + if (!ch_err) + chbsp_program_disable(dev_ptr); + } + return ch_err; +} + +/*! + * \brief Perform a hard reset on a group of sensors. + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * \return 0 if successful, non-zero otherwise + * + * This function performs a hardware reset on each device in a group of sensors + * by asserting each device's RESET_N pin. + */ +int chdrv_group_hard_reset(struct ch_group_t *grp_ptr) +{ + u8 i = 0; + + chbsp_reset_assert(); + chbsp_delay_us(1); + chbsp_reset_release(); + + for (i = 0; i < grp_ptr->num_ports; i++) { + struct ch_dev_t *dev_ptr = grp_ptr->device[i]; + + int ch_err = chdrv_soft_reset(dev_ptr); + + // only marked as connected if no errors + if (ch_err) + dev_ptr->sensor_connected = 0; + } + return 0; +} + +/*! + * \brief Perform a soft reset on a group of sensor devices + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * \return 0 if successful, non-zero otherwise + * + * This function performs a soft reset on each device in a group of sensors + * by writing to a special control register. + */ +int chdrv_group_soft_reset(struct ch_group_t *grp_ptr) +{ + int ch_err = 0; + u8 i = 0; + + for (i = 0; i < grp_ptr->num_ports; i++) { + struct ch_dev_t *dev_ptr = grp_ptr->device[i]; + + ch_err |= chdrv_soft_reset(dev_ptr); + } + + return ch_err; +} + +/*! + * \brief Register a hook routine to be called after device discovery. + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * \param hook_func_ptr address of hook routine to be called + * + * This function sets a pointer to a user supplied hook routine, which will be + * called from the Chirp driver when each device is discovered on the I2C bus, + * before the device is initialized. + * + * This function should be called between \a ch_init() and \a ch_group_start(). + */ +void chdrv_discovery_hook_set(struct ch_group_t *grp_ptr, + chdrv_discovery_hook_t hook_func_ptr) +{ + grp_ptr->disco_hook = hook_func_ptr; +} + diff --git a/drivers/iio/proximity/inv_ch101/src/ch_driver.h b/drivers/iio/proximity/inv_ch101/src/ch_driver.h new file mode 100644 index 000000000000..049c40c8499a --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/ch_driver.h @@ -0,0 +1,668 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*! \file ch_driver.h + * + * \brief Internal driver functions for operation with the Chirp ultrasonic + * sensor. + * + * This file contains definitions for the internal Chirp sensor driver functions + * and structures within SonicLib. These functions are provided in source code + * form to simplify integration with an embedded application and for reference + * only. + * + * The Chirp driver functions provide an interface between the SonicLib public + * API layer and the actual sensor devices. The driver manages all software + * defined aspects of the Chirp sensor, including the register set. + * + * You should not need to edit this file or call the driver functions directly. + * Doing so will reduce your ability to benefit from future enhancements and + * releases from Chirp. + */ + +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CH_DRIVER_H_ +#define CH_DRIVER_H_ + +#define CHDRV_DEBUG + +#include "chirp_board_config.h" +#include "soniclib.h" + +#ifndef NULL +#define NULL 0 +#endif + +/* Chirp SonicLib API/driver version number */ +#define CH_DRIVER_VERSION "2.0.0" + +/* I2C address of sensor programming interface */ +#define CH_I2C_ADDR_PROG 0x45 + +/* maximum number of bytes in a single I2C write */ +#define CHDRV_I2C_MAX_WRITE_BYTES 256 + +/* standard non-blocking I/O transaction */ +#define CHDRV_NB_TRANS_TYPE_STD (0) +/* non-blocking I/O via low-level programming interface */ +#define CHDRV_NB_TRANS_TYPE_PROG (1) +/* externally requested non-blocking I/O transaction */ +#define CHDRV_NB_TRANS_TYPE_EXTERNAL (2) + +/* Programming interface register addresses */ +/* Read-only register used during device discovery. */ +#define CH_PROG_REG_PING 0x00 +/* Processor control register address. */ +#define CH_PROG_REG_CPU 0x42 +/* Processor status register address. */ +#define CH_PROG_REG_STAT 0x43 +/* Data transfer control register address. */ +#define CH_PROG_REG_CTL 0x44 +/* Data transfer starting address register address. */ +#define CH_PROG_REG_ADDR 0x05 +/* Data transfer size register address. */ +#define CH_PROG_REG_CNT 0x07 +/* Data transfer value register address. */ +#define CH_PROG_REG_DATA 0x06 + +/* Macro to determine programming register size. */ +#define CH_PROG_SIZEOF(R) ((R) & 0x40 ? 1 : 2) + +/* max size of a read operation via programming interface */ +#define CH_PROG_XFER_SIZE (256) + +/* debug pin number (index) to use for debug indication */ +#define CHDRV_DEBUG_PIN_NUM (0) +/* Max queued non-blocking I2C transactions - value from chirp_board_config.h */ +#define CHDRV_MAX_I2C_QUEUE_LENGTH CHIRP_MAX_NUM_SENSORS +/* Time to wait in chdrv_group_start() for sensor initialization, in millisec.*/ +#define CHDRV_FREQLOCK_TIMEOUT_MS 100 +/* Index of first sample to use for calculating bandwidth. */ +#define CHDRV_BANDWIDTH_INDEX_1 6 +/* Index of second sample to use for calculating bandwidth. */ +#define CHDRV_BANDWIDTH_INDEX_2 (CHDRV_BANDWIDTH_INDEX_1 + 1) +/* Index for calculating scale factor. */ +#define CHDRV_SCALEFACTOR_INDEX 4 + +/* Incomplete definitions of structs in ch_api.h, to resolve include order */ +struct ch_dev_t; +struct ch_group_t; + +//! Hook routine pointer typedefs +typedef u8 (*chdrv_discovery_hook_t)(struct ch_dev_t *dev_ptr); + +//! I2C transaction control structure +struct chdrv_i2c_transaction_t { + /* I2C transaction type: 0 = std, 1 = prog interface, 2 = external */ + u8 type; + /* Read/write indicator: 0 if write operation, 1 if read operation */ + u8 rd_wrb; + /* current transfer within this transaction */ + u8 xfer_num; + /* I2C address */ + u16 addr; + /* Number of bytes to transfer */ + u16 nbytes; + /* Pointer to ch_dev_t descriptor structure for individual sensor */ + struct ch_dev_t *dev_ptr; + /* Pointer to buffer to receive data or containing data to send */ + u8 *databuf; +}; + +//! I2C queue structure, for non-blocking access +struct chdrv_i2c_queue_t { + /* Read transaction status: non-zero if read operation is pending */ + u8 read_pending; + /* I2C transaction status: non-zero if I/O operation in progress */ + u8 running; + /* Number of transactions in queue */ + u8 len; + /* Index of current transaction within queue */ + u8 idx; + /* List of transactions in queue */ + struct chdrv_i2c_transaction_t transaction[CHDRV_MAX_I2C_QUEUE_LENGTH]; +}; + +/*! + * \brief Calibrate the sensor real-time clock against the host microcontroller + * clock. + * + * \param grp_ptr pointer to the ch_group_t descriptor structure for a group + * of sensors + * + * This function sends a pulse (timed by the host MCU) on the INT line to each + * device in the group, then reads back the count of sensor RTC cycles that + * elapsed during that pulse on each individual device. The result is stored in + * the ch_dev_t descriptor structure for each device and is subsequently used + * during range calculations. + * + * The length of the pulse is grp_ptr->rtc_cal_pulse_ms milliseconds + * (typically 100ms). + * + * \note The calibration pulse is sent to all devices in the group at the same + * time. Therefore all connected devices will see the same reference pulse + * length. + * + */ +void chdrv_group_measure_rtc(struct ch_group_t *grp_ptr); + +/*! + * \brief Convert the sensor register values to a range using the calibration + * data in the ch_dev_t struct. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param tof value of TOF register + * \param tof_sf value of TOF_SF register + * + * \return range in millimeters, or \a CH_NO_TARGET (0xFFFFFFFF) if no object is + * detected. The range result format is fixed point with 5 binary fractional + * digits (divide by 32 to convert to mm). + * + * This function takes the time-of-flight and scale factor values from the + * sensor, and computes the actual one-way range based on the formulas given in + * the sensor datasheet. + */ +uint32_t chdrv_one_way_range(struct ch_dev_t *dev_ptr, u16 tof, u16 tof_sf); + +/*! + * \brief Convert the sensor register values to a round-trip range using + * the calibration data in the ch_dev_t struct. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param tof value of TOF register + * \param tof_sf value of TOF_SF register + * + * \return range in millimeters, or \a CH_NO_TARGET (0xFFFFFFFF) if no object is + * detected. The range result format is fixed point with 5 binary fractional + * digits (divide by 32 to convert to mm). + * + * This function takes the time-of-flight and scale factor values from the + * sensor, and computes the actual round-trip range based on the formulas given + * in the sensor datasheet. + */ +uint32_t chdrv_round_trip_range(struct ch_dev_t *dev_ptr, u16 tof, + u16 tof_sf); + +/*! + * \brief Add an I2C transaction to the non-blocking queue + * + * \param grp_ptr pointer to the ch_group_t descriptor structure for + * a group of sensors + * \param instance pointer to the ch_dev_t descriptor structure + * \param rd_wrb read/write indicator: + * 0 if write operation, 1 if read operation + * \param type I2C transaction type: + * 0 = std, 1 = prog interface, 2 = external + * \param addr I2C address for transfer + * \param nbytes number of bytes to read/write + * \param data pointer to buffer to receive data or containing + * data to send + * + * \return 0 if successful, non-zero otherwise + */ +int chdrv_group_i2c_queue(struct ch_group_t *grp_ptr, struct ch_dev_t *instance, + u8 rd_wrb, u8 type, u16 addr, u16 nbytes, + u8 *data); + +/*! + * \brief Add an I2C transaction for an external device to the non-blocking + * queue + * + * \param grp_ptr pointer to the ch_group_t descriptor structure for + * a group of sensors + * \param instance pointer to the ch_dev_t descriptor structure + * \param rd_wrb read/write indicator: + * 0 if write operation, 1 if read operation + * \param addr I2C address for transfer + * \param nbytes number of bytes to read/write + * \param data pointer to buffer to receive data or containing + * data to send + * + * \return 0 if successful, non-zero otherwise + * + * This function queues an I2C transaction for an "external" device (i.e. not + * a Chirp sensor). It is used when the I2C bus is shared between the Chirp + * sensor(s) and other devices. + * + * The transaction is flagged for special handling when the I/O operation + * completes. Specifically, the \a chbsp_external_i2c_irq_handler() will be + * called by the driver to allow the board support packate (BSP) to perform any + * necessary operations. + */ +int chdrv_external_i2c_queue(struct ch_group_t *grp_ptr, + struct ch_dev_t *instance, u8 rd_wrb, u16 addr, u16 nbytes, u8 *data); + +/*! + * \brief Start a non-blocking sensor readout + * + * \param grp_ptr pointer to the ch_group_t descriptor structure for + * a group of sensors + * + * This function starts a non-blocking I/O operation on the specified group + * of sensors. + */ +void chdrv_group_i2c_start_nb(struct ch_group_t *grp_ptr); + +/*! + * \brief Continue a non-blocking readout + * + * \param grp_ptr pointer to the ch_group_t descriptor structure for + * a group of sensors + * \param i2c_bus_index index value identifying I2C bus within group + * + * Call this function once from your I2C interrupt handler each time it + * executes. It will call \a chdrv_group_i2c_complete_callback() when all + * transactions are complete. + */ +void chdrv_group_i2c_irq_handler(struct ch_group_t *grp_ptr, u8 i2c_bus_index); + +/*! + * \brief Wait for an individual sensor to finish start-up procedure. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param timeout_ms number of milliseconds to wait for sensor to finish + * start-up before returning failure + * + * \return 0 if startup sequence finished, non-zero if startup sequence timed + * out or sensor is not connected + * + * After the sensor is programmed, it executes an internal start-up and + * self-test sequence. This function waits the specified time in milliseconds + * for the sensor to finish this sequence. + */ +int chdrv_wait_for_lock(struct ch_dev_t *dev_ptr, u16 timeout_ms); + +/*! + * \brief Wait for all sensors to finish start-up procedure. + * + * \param grp_ptr pointer to the ch_group_t descriptor structure for + * a group of sensors + * + * \return 0 if startup sequence finished on all detected sensors, non-zero if + * startup sequence timed out on any sensor(s). + * + * After each sensor is programmed, it executes an internal start-up and + * self-test sequence. This function waits for all sensor devices to finish this + * sequence. For each device, the maximum time to wait is + * \a CHDRV_FREQLOCK_TIMEOUT_MS milliseconds. + */ +int chdrv_group_wait_for_lock(struct ch_group_t *grp_ptr); + +/*! + * \brief Start a measurement in hardware triggered mode. + * + * \param grp_ptr pointer to the ch_group_t descriptor structure for + * a group of sensors + * + * \return 0 if success, non-zero if \a grp_ptr pointer is invalid + * + * This function starts a triggered measurement on each sensor in a group, by + * briefly asserting the INT line to each device. Each sensor must have already + * been placed in hardware triggered mode before this function is called. + */ +int chdrv_group_hw_trigger(struct ch_group_t *grp_ptr); + +/*! + * \brief Start a measurement in hardware triggered mode on one sensor. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * + * \return 0 if success, non-zero if \a dev_ptr pointer is invalid + * + * This function starts a triggered measurement on a single sensor, by briefly + * asserting the INT line to the device. The sensor must have already been + * placed in hardware triggered mode before this function is called. + * + * \note This function requires implementing the optional chirp_bsp.h functions + * to control the INT pin direction and level for individual sensors + * (\a chbsp_set_io_dir_in(), \a chbsp_set_io_dir_out(), \a chbsp_io_set(), + * and \a chbsp_io_clear()). + */ +int chdrv_hw_trigger_up(struct ch_dev_t *dev_ptr); +int chdrv_hw_trigger_down(struct ch_dev_t *dev_ptr); + +/*! + * \brief Detect a connected sensor. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * + * \return 1 if sensor is found, 0 if no sensor is found + * + * This function checks for a sensor on the I2C bus by attempting to reset, + * halt, and read from the device using the programming interface + * I2C address (0x45). + * + * In order for the device to respond, the PROG pin for the device must be + * asserted before this function is called. If there are multiple sensors in + * an application, only one device's PROG pin should be active at any time. + */ +int chdrv_prog_ping(struct ch_dev_t *dev_ptr); + +/*! + * \brief Detect, program, and start a sensor. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * + * \return 0 if write to sensor succeeded, non-zero otherwise + * + * This function probes the I2C bus for the device. If it is found, the sensor + * firmware is programmed into the device, and the application I2C address is + * set. Then the sensor is reset and execution starts. + * + * Once started, the sensor device will begin an internal initialization and + * self-test sequence. The \a chdrv_wait_for_lock() function may be used to wait + * for this sequence to complete. + * + * \note This routine will leave the PROG pin de-asserted when it completes. + */ +int chdrv_detect_and_program(struct ch_dev_t *dev_ptr); + +/*! + * \brief Detect, program, and start all sensors in a group. + * + * \param grp_ptr pointer to the ch_group_t descriptor structure for + * a group of sensors + * + * \return 0 for success, non-zero if write(s) failed to any sensor initially + * detected as present + * + * This function probes the I2C bus for each device in the group. For each + * detected sensor, the firmware is programmed into the device, and the + * application I2C address is set. Then the sensor is reset and execution starts + * + * Once started, each device will begin an internal initialization and self-test + * sequence. The \a chdrv_group_wait_for_lock() function may be used to wait for + * this sequence to complete on all devices in the group. + * + * \note This routine will leave the PROG pin de-asserted for all devices in + * the group when it completes. + */ +int chdrv_group_detect_and_program(struct ch_group_t *grp_ptr); + +/*! + * \brief Initialize the sensor device configuration. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure to + * be initialized + * \param i2c_addr I2C address to assign to this device. This will be the + * "application I2C address" used to access the device + * after it is initialized. Each sensor on an I2C interface + * must use a unique application I2C address. + * \param io_index index identifying this device. Each sensor in a group + * must have a unique \a io_index value. + * \param i2c_bus_index index identifying the I2C interface (bus) to use with + * this device + * \param part_number integer part number for sensor (e.g. 101 for CH101 + * device, or 201 for CH201)) + * + * \return 0 (always) + * + * This function initializes the ch_dev_t descriptor structure for the device + * with the specified values. + */ +int chdrv_init(struct ch_dev_t *dev_ptr, u8 i2c_addr, u8 io_index, + u8 i2c_bus_index, u16 part_number); + +/*! + * \brief Initialize data structures and hardware for sensor interaction and + * reset sensors. + * + * \param grp_ptr pointer to the ch_group_t descriptor structure for + * a group of sensors + * + * \return 0 if hardware initialization is successful, non-zero otherwise + * + * This function is called internally by \a chdrv_group_start(). + */ +int chdrv_group_prepare(struct ch_group_t *grp_ptr); + +/*! + * \brief Initialize and start a group of sensors. + * + * \param grp_ptr pointer to the ch_group_t descriptor structure for + * a group of sensors + * + * \return 0 if successful, 1 if device doesn't respond + * + * This function resets each sensor in programming mode, transfers the firmware + * image to the sensor's on-chip memory, changes the sensor's application I2C + * address from the default, then starts the sensor and sends a timed pulse on + * the INT line for real-time clock calibration. + * + * This function assumes firmware-specific initialization has already been + * performed for each ch_dev_t descriptor in the sensor group. + * (See \a ch_init()). + */ +int chdrv_group_start(struct ch_group_t *grp_ptr); + +/*! + * \brief Write byte to a sensor application register. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param reg_addr register address + * \param data data value to transmit + * + * \return 0 if successful, non-zero otherwise + */ +int chdrv_write_byte(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 data); + +/*! + * \brief Write 16 bits to a sensor application register. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param mem_addr sensor memory/register address + * \param data data value to transmit + * + * \return 0 if successful, non-zero otherwise + */ +int chdrv_write_word(struct ch_dev_t *dev_ptr, u16 mem_addr, u16 data); + +/*! + * \brief Read byte from a sensor application register. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param mem_addr sensor memory/register address + * \param data pointer to receive buffer + * + * \return 0 if successful, non-zero otherwise + */ +int chdrv_read_byte(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 *data); + +/*! + * \brief Read 16 bits from a sensor application register. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param mem_addr sensor memory/register address + * \param data pointer to receive buffer + * + * \return 0 if successful, non-zero otherwise + */ +int chdrv_read_word(struct ch_dev_t *dev_ptr, u16 mem_addr, u16 *data); + +/*! + * \brief Read multiple bytes from a sensor application register location. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param mem_addr sensor memory/register address + * \param data pointer to receive buffer + * \param len number of bytes to read + * + * \return 0 if successful, non-zero otherwise + * + */ +int chdrv_burst_read(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 *data, + u16 len); + +/*! + * \brief Write multiple bytes to a sensor application register location. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param mem_addr sensor memory/register address + * \param data pointer to transmit buffer containing data to send + * \param len number of bytes to write + * + * \return 0 if successful, non-zero otherwise + * + */ +int chdrv_burst_write(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 *data, + u8 len); + +/*! + * \brief Perform a soft reset on a sensor. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param mem_addr sensor memory/register address + * + * \return 0 if successful, non-zero otherwise + * + * This function performs a soft reset on an individual sensor by writing to + * a special control register. + */ +int chdrv_soft_reset(struct ch_dev_t *dev_ptr); + +/*! + * \brief Perform a hard reset on a group of sensors. + * + * \param grp_ptr pointer to the ch_group_t descriptor structure for + * a group of sensors + * + * \return 0 if successful, non-zero otherwise + * + * This function performs a hardware reset on each device in a group of sensors + * by asserting each device's RESET_N pin. + */ +int chdrv_group_hard_reset(struct ch_group_t *grp_ptr); + +/*! + * \brief Perform a soft reset on a group of sensors. + * + * \param grp_ptr pointer to the ch_group_t descriptor structure for + * a group of sensors + * + * \return 0 if successful, non-zero otherwise + * + * This function performs a soft reset on each device in a group of sensors by + * writing to a special control register. + */ +int chdrv_group_soft_reset(struct ch_group_t *grp_ptr); + +/*! + * \brief Put sensor(s) in idle state + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * + * \return 0 if successful, non-zero otherwise + * + * This function places the sensor in an idle state by loading an idle loop + * instruction sequence. This is used only during early initialization of + * the device. This is NOT the same as putting a running device into + * "idle mode" by using the \a ch_set_mode() function. + */ +int chdrv_set_idle(struct ch_dev_t *dev_ptr); + +/*! + * \brief Write to a sensor programming register. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param reg_addr sensor programming register address. + * \param data 8-bit or 16-bit data to transmit. + * + * \return 0 if write to sensor succeeded, non-zero otherwise + * + * This function writes a value to a sensor programming register. + */ +int chdrv_prog_write(struct ch_dev_t *dev_ptr, u8 reg_addr, u16 data); + +/*! + * \brief Write bytes to a sensor device in programming mode. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param message pointer to a buffer containing the bytes to write + * \param len number of bytes to write + * + * \return 0 if successful, non-zero otherwise + * + * This function writes bytes to the device using the programming I2C address. + * The PROG line for the device must have been asserted before this function + * is called. + */ +int chdrv_prog_i2c_write(struct ch_dev_t *dev_ptr, u8 *message, u16 len); + +/*! + * \brief Read bytes from a sensor device in programming mode. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param message pointer to a buffer where read bytes will be placed + * \param len number of bytes to read + * + * \return 0 if successful, non-zero otherwise + * + * This function reads bytes from the device using the programming I2C address. + * The PROG line for the device must have been asserted before this function + * is called. + */ +int chdrv_prog_i2c_read(struct ch_dev_t *dev_ptr, u8 *message, u16 len); + +/*! + * \brief Read bytes from a sensor device in programming mode, non-blocking. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param message pointer to a buffer where read bytes will be placed + * \param len number of bytes to read + * + * \return 0 if successful, non-zero otherwise + * + * This function temporarily changes the device I2C address to the low-level + * programming interface, and issues a non-blocking read request. The PROG line + * for the device must have been asserted before this function is called. + */ +int chdrv_prog_i2c_read_nb(struct ch_dev_t *dev_ptr, u8 *message, u16 len); + +/*! + * \brief Write to sensor memory. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param addr sensor programming register start address + * \param message pointer to data to transmit + * \param nbytes number of bytes to write + * + * \return 0 if write to sensor succeeded, non-zero otherwise + * + * This function writes to sensor memory using the low-level programming + * interface. The type of write is automatically determined based on data + * length and target address alignment. + */ +int chdrv_prog_mem_write(struct ch_dev_t *dev_ptr, u16 addr, u8 *message, + u16 nbytes); + +/*! + * \brief Register a hook routine to be called after device discovery. + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * \param hook_func_ptr address of hook routine to be called + * + * This function sets a pointer to a hook routine, which will be called from the + * Chirp driver when each device is discovered on the I2C bus, before the device + * is initialized. + * + * This function should be called between \a ch_init() and \a ch_group_start(). + */ +void chdrv_discovery_hook_set(struct ch_group_t *grp_ptr, + chdrv_discovery_hook_t hook_func_ptr); + +#endif /* CH_DRIVER_H_ */ diff --git a/drivers/iio/proximity/inv_ch101/src/chbsp_dummy.c b/drivers/iio/proximity/inv_ch101/src/chbsp_dummy.c new file mode 100644 index 000000000000..bbaf5277b5ed --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/chbsp_dummy.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0 +/*! + * \file chbsp_dummy.c + * + * \brief Dummy implementations of optional board support package IO functions + * allowing platforms to selectively support only needed functionality. + * These are placeholder routines that will satisfy references from other code + * to avoid link errors, but they do not peform any actual operations. + * + * See chirp_bsp.h for descriptions of all board support package interfaces, + * including details on these optional functions. + */ + +/* + * Copyright (c) 2017-2019 Chirp Microsystems. All rights reserved. + */ + +#include "chirp_bsp.h" + +/* Functions supporting debugging */ + +WEAK void chbsp_debug_toggle(u8 dbg_pin_num) +{ +} + +WEAK void chbsp_debug_on(u8 dbg_pin_num) +{ +} + +WEAK void chbsp_debug_off(u8 dbg_pin_num) +{ +} + + +WEAK void chbsp_external_i2c_irq_handler(struct chdrv_i2c_transaction_t *trans) +{ + (void)(trans); +} + +/* Other BSP functions */ + +WEAK void chbsp_proc_sleep(u16 ms) +{ + msleep(ms); +} + +WEAK void chbsp_critical_section_enter(void) +{ +} + +WEAK void chbsp_critical_section_leave(void) +{ +} diff --git a/drivers/iio/proximity/inv_ch101/src/chbsp_init.c b/drivers/iio/proximity/inv_ch101/src/chbsp_init.c new file mode 100644 index 000000000000..9c9ca2bd4762 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/chbsp_init.c @@ -0,0 +1,864 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "soniclib.h" // Chirp SonicLib API definitions +#include "chirp_hal.h" +#include "chbsp_init.h" // header with board-specific defines +#include "chirp_bsp.h" +#include "i2c_hal.h" + +u8 chirp_i2c_addrs[] = CHIRP_I2C_ADDRS; +u8 chirp_i2c_buses[] = CHIRP_I2C_BUSES; + +/* + * Here you set the pin masks for each of the prog pins + */ + +u32 chirp_pin_enabled[] = { 1, 1, 1, 1, 1, 1}; + +u32 chirp_pin_prog[] = { +CHIRP0_PROG_0, CHIRP0_PROG_1, CHIRP0_PROG_2, +CHIRP1_PROG_0, CHIRP1_PROG_1, CHIRP1_PROG_2 +}; + +u32 chirp_pin_io[] = { +CHIRP0_INT_0, CHIRP0_INT_1, CHIRP0_INT_2, +CHIRP1_INT_0, CHIRP1_INT_1, CHIRP1_INT_2 +}; + +u32 chirp_pin_io_irq[] = { +CHIRP0_INT_0, CHIRP0_INT_1, CHIRP0_INT_2, +CHIRP1_INT_0, CHIRP1_INT_1, CHIRP1_INT_2 +}; + +/* Callback function pointers */ +static ch_timer_callback_t periodic_timer_callback_ptr; +static u16 periodic_timer_interval_ms; + +/*! + * \brief Initialize board hardware + * + * \note This function performs all necessary initialization on the board. + */ +void chbsp_board_init(struct ch_group_t *grp_ptr) +{ + /* Initialize group descriptor */ + grp_ptr->num_ports = CHBSP_MAX_DEVICES; + grp_ptr->num_i2c_buses = CHBSP_NUM_I2C_BUSES; + grp_ptr->rtc_cal_pulse_ms = CHBSP_RTC_CAL_PULSE_MS; + + ext_int_init(); +} + +/*! + * \brief Assert the reset pin + * + * This function drives the sensor reset pin low. + */ +void chbsp_reset_assert(void) +{ + ioport_set_pin_level(CHIRP_RST, IOPORT_PIN_LEVEL_LOW); //reset low +} + +/*! + * \brief De-assert the reset pin + * + * This function drives the sensor reset pin high. + */ +void chbsp_reset_release(void) +{ + ioport_set_pin_level(CHIRP_RST, IOPORT_PIN_LEVEL_HIGH); //reset high +} + + +void chbsp_reset_ps_assert(void) +{ + ioport_set_pin_level(CHIRP_RST_PS, IOPORT_PIN_LEVEL_LOW); //reset low +} + +/*! + * \brief De-assert the pulse reset pin + * + * This function drives the pulse reset pin high. + */ +void chbsp_reset_ps_release(void) +{ + ioport_set_pin_level(CHIRP_RST_PS, IOPORT_PIN_LEVEL_HIGH); //reset high +} + +/*! + * \brief Assert the PROG pin + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function drives the sensor PROG pin high on the specified port. + */ +void chbsp_program_enable(struct ch_dev_t *dev_ptr) +{ + u8 dev_num = ch_get_dev_num(dev_ptr); + + // select Chirp chip PROG line according to chip number + ioport_set_pin_level(chirp_pin_prog[dev_num], IOPORT_PIN_LEVEL_HIGH); +} + +/*! + * \brief De-assert the PROG pin + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function drives the sensor PROG pin low on the specified port. + */ +void chbsp_program_disable(struct ch_dev_t *dev_ptr) +{ + u8 dev_num = ch_get_dev_num(dev_ptr); + + // select Chirp chip PROGRAM line according to chip number + ioport_set_pin_level(chirp_pin_prog[dev_num], IOPORT_PIN_LEVEL_LOW); +} + +/*! + * \brief Configure the Chirp sensor INT pin as an output for one sensor. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function configures the Chirp sensor INT pin as an output + * (from the perspective of the host system). + */ +void chbsp_set_io_dir_out(struct ch_dev_t *dev_ptr) +{ + u8 dev_num = ch_get_dev_num(dev_ptr); + + if (ch_sensor_is_connected(dev_ptr)) { + ioport_set_pin_dir(chirp_pin_io[dev_num], + IOPORT_DIR_OUTPUT); + } +} + +/*! + * \brief Configure the Chirp sensor INT pin as an input for one sensor. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function configures the Chirp sensor INT pin as an input + * (from the perspective of the host system). + */ +void chbsp_set_io_dir_in(struct ch_dev_t *dev_ptr) +{ + u8 dev_num = ch_get_dev_num(dev_ptr); + + if (ch_sensor_is_connected(dev_ptr)) { + ioport_set_pin_dir(chirp_pin_io[dev_num], + IOPORT_DIR_INPUT); + } +} + +/* Functions supporting controlling int pins of individual sensors + * (originally only controllable in a group) + */ +void chbsp_io_set(struct ch_dev_t *dev_ptr) +{ + u8 dev_num = ch_get_dev_num(dev_ptr); + + if (ch_sensor_is_connected(dev_ptr)) { + ioport_set_pin_level(chirp_pin_io[dev_num], + IOPORT_PIN_LEVEL_HIGH); + } +} + +void chbsp_io_clear(struct ch_dev_t *dev_ptr) +{ + u8 dev_num = ch_get_dev_num(dev_ptr); + + if (ch_sensor_is_connected(dev_ptr)) { + ioport_set_pin_level(chirp_pin_io[dev_num], + IOPORT_PIN_LEVEL_LOW); + } +} + +/*! + * \brief Configure the Chirp sensor INT pins as outputs for a group of sensors + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * This function configures each Chirp sensor's INT pin as an output + * (from the perspective of the host system). + */ +void chbsp_group_set_io_dir_out(struct ch_group_t *grp_ptr) +{ + u8 dev_num; + + for (dev_num = 0; dev_num < ch_get_num_ports(grp_ptr); dev_num++) { + struct ch_dev_t *dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + + if (ch_sensor_is_connected(dev_ptr)) { + ioport_set_pin_dir(chirp_pin_io[dev_num], + IOPORT_DIR_OUTPUT); //output pin + } + } +} + +/*! + * \brief Configure the Chirp sensor INT pins as inputs for a group of sensors + * + * \param dev_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * \note This function assumes a bidirectional level shifter is interfacing + */ +void chbsp_group_set_io_dir_in(struct ch_group_t *grp_ptr) +{ + u8 dev_num; + + for (dev_num = 0; dev_num < ch_get_num_ports(grp_ptr); dev_num++) { + struct ch_dev_t *dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + + if (ch_sensor_is_connected(dev_ptr)) { + ioport_set_pin_dir(chirp_pin_io[dev_num], + IOPORT_DIR_INPUT); //input pin + } + } +} + +/*! + * \brief Initialize the I/O pins. + * + * \param dev_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * Configure reset and program pins as outputs. Assert reset and program. + * Configure sensor INT pin as input. + */ +void chbsp_group_pin_init(struct ch_group_t *grp_ptr) +{ + u8 dev_num; + int i; + + for (i = 0; i < ARRAY_SIZE(chirp_pin_prog); i++) { + if (!chirp_pin_enabled[i]) + continue; + ioport_set_pin_dir(chirp_pin_prog[i], IOPORT_DIR_OUTPUT); + ioport_set_pin_level(chirp_pin_prog[i], IOPORT_PIN_LEVEL_LOW); + } + + ioport_set_pin_dir(CHIRP_RST, IOPORT_DIR_OUTPUT); //reset=output + chbsp_reset_assert(); + + for (dev_num = 0; dev_num < grp_ptr->num_ports; dev_num++) { + struct ch_dev_t *dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + + chbsp_program_enable(dev_ptr); + } + + /* Initialize IO pins */ + chbsp_group_set_io_dir_in(grp_ptr); +} + +/*! + * \brief Set the INT pins low for a group of sensors. + * + * \param dev_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * This function drives the INT line low for each sensor in the group. + */ +void chbsp_group_io_clear(struct ch_group_t *grp_ptr) +{ + u8 dev_num; + + for (dev_num = 0; dev_num < ch_get_num_ports(grp_ptr); dev_num++) { + struct ch_dev_t *dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + + if (ch_sensor_is_connected(dev_ptr)) { + ioport_set_pin_level(chirp_pin_io[dev_num], + IOPORT_PIN_LEVEL_LOW); + } + } +} + +/*! + * \brief Set the INT pins high for a group of sensors. + * + * \param dev_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * This function drives the INT line high for each sensor in the group. + */ +void chbsp_group_io_set(struct ch_group_t *grp_ptr) +{ + u8 dev_num; + + for (dev_num = 0; dev_num < ch_get_num_ports(grp_ptr); dev_num++) { + struct ch_dev_t *dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + + if (ch_sensor_is_connected(dev_ptr)) { + ioport_set_pin_level(chirp_pin_io[dev_num], + IOPORT_PIN_LEVEL_HIGH); + } + } +} + +/*! + * \brief Disable interrupts for a group of sensors + * + * \param dev_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * For each sensor in the group, this function disables the host interrupt + * associated with the Chirp sensor device's INT line. + */ +void chbsp_group_io_interrupt_enable(struct ch_group_t *grp_ptr) +{ + u8 dev_num; + + for (dev_num = 0; dev_num < ch_get_num_ports(grp_ptr); dev_num++) { + struct ch_dev_t *dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + + chbsp_io_interrupt_enable(dev_ptr); + } +} + +/*! + * \brief Enable the interrupt for one sensor + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function enables the host interrupt associated with the Chirp sensor + * device's INT line. + */ +void chbsp_io_interrupt_enable(struct ch_dev_t *dev_ptr) +{ + u8 dev_num = ch_get_dev_num(dev_ptr); + + if (ch_sensor_is_connected(dev_ptr)) { + os_clear_interrupt(chirp_pin_io_irq[dev_num]); + os_enable_interrupt(chirp_pin_io_irq[dev_num]); + } +} + +/*! + * \brief Disable interrupts for a group of sensors + * + * \param dev_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * For each sensor in the group, this function disables the host interrupt + * associated with the Chirp sensor device's INT line. + */ +void chbsp_group_io_interrupt_disable(struct ch_group_t *grp_ptr) +{ + u8 dev_num; + + for (dev_num = 0; dev_num < ch_get_num_ports(grp_ptr); dev_num++) { + struct ch_dev_t *dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + + chbsp_io_interrupt_disable(dev_ptr); + } +} + +/*! + * \brief Disable the interrupt for one sensor + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function disables the host interrupt associated with the Chirp sensor + * device's INT line. + */ +void chbsp_io_interrupt_disable(struct ch_dev_t *dev_ptr) +{ + u8 dev_num = ch_get_dev_num(dev_ptr); + + if (dev_ptr->sensor_connected) + os_disable_interrupt(chirp_pin_io_irq[dev_num]); +} + +/*! + * \brief Set callback routine for Chirp sensor I/O interrupt + * + * \param callback_func_ptr pointer to application function to be called + * when interrupt occurs + * + * This function sets up the specified callback routine to be called whenever + * the interrupt associated with the sensor's INT line occurs. + * The callback routine address in stored in a pointer variable that will later + * be accessed from within the interrupt handler to call the function. + * + * The callback function will be called at interrupt level from the interrupt + * service routine. + */ +void chbsp_io_callback_set(ch_io_int_callback_t callback_func_ptr) +{ +} + +/*! + * \brief Delay for specified number of microseconds + * + * \param us number of microseconds to delay before returning + * + * This function waits for the specified number of microseconds before + * returning to the caller. + */ +void chbsp_delay_us(u32 us) +{ + os_delay_us(us); +} + +/*! + * \brief Delay for specified number of milliseconds. + * + * \param ms number of milliseconds to delay before returning + * + * This function waits for the specified number of milliseconds before + * returning to the caller. + */ +void chbsp_delay_ms(u32 ms) +{ + os_delay_ms(ms); +} + +/*! + * \brief Initialize the host's I2C hardware. + * + * \return 0 if successful, 1 on error + * + * This function performs general I2C initialization on the host system. + */ +int chbsp_i2c_init(void) +{ + i2c_master_init(); + return 0; +} + +int chbsp_i2c_deinit(void) +{ + return 0; +} + +/*! + * \brief Return I2C information for a sensor port on the board. + * + * \param dev_ptr pointer to the ch_group_t config structure for + * a group of sensors + * \param dev_num device number within sensor group + * \param info_ptr pointer to structure to be filled with I2C config values + * + * \return 0 if successful, 1 if error + * + * This function returns I2C values in the ch_i2c_info_t structure specified by + * \a info_ptr. + * The structure includes three fields. + * - The \a address field contains the I2C address for the sensor. + * - The \a bus_num field contains the I2C bus number (index). + * - The \a drv_flags field contains various bit flags through which the BSP + * can inform + * SonicLib driver functions to perform specific actions during + * I2C I/O operations. + */ +u8 chbsp_i2c_get_info(struct ch_group_t *grp_ptr, u8 io_index, + struct ch_i2c_info_t *info_ptr) +{ + u8 ret_val = 1; + + if (io_index < CHBSP_MAX_DEVICES) { + info_ptr->address = chirp_i2c_addrs[io_index]; + info_ptr->bus_num = chirp_i2c_buses[io_index]; + // no special I2C handling by SonicLib driver is needed + info_ptr->drv_flags = 0; + + ret_val = 0; + } + + return ret_val; +} + +/*! + * \brief Write bytes to an I2C slave. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param data data to be transmitted + * \param num_bytes length of data to be transmitted + * + * \return 0 if successful, 1 on error or NACK + * + * This function writes one or more bytes of data to an I2C slave device. + * The I2C interface must have already been initialized using chbsp_i2c_init(). + */ +int chbsp_i2c_write(struct ch_dev_t *dev_ptr, u8 *data, u16 num_bytes) +{ + int bytes = 0; + + if (dev_ptr->i2c_bus_index == 0) { + bytes = i2c_master_write_register0_sync(dev_ptr->i2c_address, + num_bytes, data); //I2C bus 0 + } else if (dev_ptr->i2c_bus_index == 1) { + bytes = i2c_master_write_register1_sync(dev_ptr->i2c_address, + num_bytes, data); //I2C bus 1 + } else if (dev_ptr->i2c_bus_index == 2) { + bytes = i2c_master_write_register2_sync(dev_ptr->i2c_address, + num_bytes, data); //I2C bus 2 + } + + return (bytes != num_bytes); +} + +/*! + * \brief Write bytes to an I2C slave using memory addressing. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param mem_addr internal memory or register address within device + * \param data data to be transmitted + * \param num_bytes length of data to be transmitted + * + * \return 0 if successful, 1 on error or NACK + * + * This function writes one or more bytes of data to an I2C slave device using + * an internal memory or register address. The remote device will write + * \a num_bytes bytes of data starting at internal memory/register address + * \a mem_addr. + * The I2C interface must have already been initialized using chbsp_i2c_init(). + */ +int chbsp_i2c_mem_write(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 *data, + u16 num_bytes) +{ + int error = 0; + + if (dev_ptr->i2c_bus_index == 0) { + // I2C bus 0 + error = i2c_master_write_register0(dev_ptr->i2c_address, + (unsigned char)mem_addr, num_bytes, data); + } else if (dev_ptr->i2c_bus_index == 1) { + // I2C bus 1 + error = i2c_master_write_register1(dev_ptr->i2c_address, + (unsigned char)mem_addr, num_bytes, data); + } else if (dev_ptr->i2c_bus_index == 2) { + // I2C bus 2 + error = i2c_master_write_register2(dev_ptr->i2c_address, + (unsigned char)mem_addr, num_bytes, data); + } + return error; +} + +/*! + * \brief Write bytes to an I2C slave, non-blocking. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param data pointer to the start of data to be transmitted + * \param num_bytes length of data to be transmitted + * + * \return 0 if successful, 1 on error or NACK + * + * This function initiates a non-blocking write of the specified number + * of bytes to an I2C slave device. + * + * The I2C interface must have already been initialized using chbsp_i2c_init(). + */ +int chbsp_i2c_write_nb(struct ch_dev_t *dev_ptr, u8 *data, u16 num_bytes) +{ + // XXX not implemented + return 1; +} + +/*! + * \brief Write bytes to an I2C slave using memory addressing, non-blocking. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param mem_addr internal memory or register address within device + * \param data pointer to the start of data to be transmitted + * \param num_bytes length of data to be transmitted + * + * \return 0 if successful, 1 on error or NACK + * + * This function initiates a non-blocking write of the specified number of bytes + * to an I2C slave device, using an internal memory or register address. + * The remote device will write \a num_bytes bytes of data starting at internal + * memory/register address \a mem_addr. + * + * The I2C interface must have already been initialized using chbsp_i2c_init(). + */ +int chbsp_i2c_mem_write_nb(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 *data, + u16 num_bytes) +{ + // XXX not implemented + return 1; +} + +/*! + * \brief Read bytes from an I2C slave. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param data pointer to receive data buffer + * \param num_bytes number of bytes to read + * + * \return 0 if successful, 1 on error or NACK + * + * This function reads the specified number of bytes from an I2C slave device. + * The I2C interface must have already been initialized using chbsp_i2c_init(). + */ +int chbsp_i2c_read(struct ch_dev_t *dev_ptr, u8 *data, u16 num_bytes) +{ + int bytes = 0; + u8 i2c_addr = ch_get_i2c_address(dev_ptr); + u8 bus_num = ch_get_i2c_bus(dev_ptr); + + if (bus_num == 0) { + // I2C bus 0 + bytes = i2c_master_read_register0_sync(i2c_addr, num_bytes, + data); + } else if (bus_num == 1) { + // I2C bus 1 + bytes = i2c_master_read_register1_sync(i2c_addr, num_bytes, + data); + } else if (bus_num == 2) { + // I2C bus 2 + bytes = i2c_master_read_register2_sync(i2c_addr, num_bytes, + data); + } + return (bytes != num_bytes); +} + +/*! + * \brief Read bytes from an I2C slave using memory addressing. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param mem_addr internal memory or register address within device + * \param data pointer to receive data buffer + * \param num_bytes number of bytes to read + * + * \return 0 if successful, 1 on error or NACK + * + * This function reads the specified number of bytes from an I2C slave device, + * using an internal memory or register address. The remote device will return + * \a num_bytes bytes starting at internal memory/register address \a mem_addr. + * + * The I2C interface must have already been initialized using chbsp_i2c_init(). + */ +int chbsp_i2c_mem_read(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 *data, + u16 num_bytes) +{ + int error = 1; // default is error return + u8 i2c_addr = ch_get_i2c_address(dev_ptr); + u8 bus_num = ch_get_i2c_bus(dev_ptr); + + if (bus_num == 0) { + // I2C bus 0 + error = i2c_master_read_register0(i2c_addr, + (unsigned char)mem_addr, num_bytes, data); + } else if (bus_num == 1) { + // I2C bus 1 + error = i2c_master_read_register1(i2c_addr, + (unsigned char)mem_addr, num_bytes, data); + } else if (bus_num == 2) { + // I2C bus 2 + error = i2c_master_read_register2(i2c_addr, + (unsigned char)mem_addr, num_bytes, data); + } + return error; +} + +/*! + * \brief Read bytes from an I2C slave, non-blocking. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param data pointer to receive data buffer + * \param num_bytes number of bytes to read + * + * \return 0 if successful, 1 on error or NACK + * + * This function initiates a non-blocking read of the specified number of bytes + * from an I2C slave. + * + * The I2C interface must have already been initialized using chbsp_i2c_init(). + */ +int chbsp_i2c_read_nb(struct ch_dev_t *dev_ptr, u8 *data, u16 num_bytes) +{ + int error = 0; + + error = chbsp_i2c_read(dev_ptr, data, num_bytes); + return error; +} + +/*! + * \brief Read bytes from an I2C slave using memory addressing, non-blocking. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param mem_addr internal memory or register address within device + * \param data pointer to receive data buffer + * \param num_bytes number of bytes to read + * + * \return 0 if successful, 1 on error or NACK + * + * This function initiates a non-blocking read of the specified number of bytes + * from an I2C slave. + * + * The I2C interface must have already been initialized using chbsp_i2c_init(). + */ +int chbsp_i2c_mem_read_nb(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 *data, + u16 num_bytes) +{ + int bytes = 0; + u8 i2c_addr = ch_get_i2c_address(dev_ptr); + u8 bus_num = ch_get_i2c_bus(dev_ptr); + + if (bus_num == 0) { + // I2C bus 0 + bytes = i2c_master_read_register0_nb(i2c_addr, num_bytes, data); + } else if (bus_num == 1) { + // I2C bus 1 + bytes = i2c_master_read_register1_nb(i2c_addr, num_bytes, data); + } else if (bus_num == 2) { + // I2C bus 2 + bytes = i2c_master_read_register2_nb(i2c_addr, num_bytes, data); + } + return (bytes != num_bytes); +} + +/*! + * \brief Reset I2C bus associated with device. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function performs a reset of the I2C interface for the specified device. + */ +void chbsp_i2c_reset(struct ch_dev_t *dev_ptr) +{ + u8 bus_num = ch_get_i2c_bus(dev_ptr); + + if (bus_num == 0) // I2C bus 0 + i2c_master_initialize0(); + else if (bus_num == 1) // I2C bus 1 + i2c_master_initialize1(); + else if (bus_num == 2) // I2C bus 2 + i2c_master_initialize2(); +} + +/*! + * \brief Initialize periodic timer. + * + * \param interval_ms timer interval, in milliseconds + * \param callback_func_ptr address of routine to be called every time the + * timer expires + * + * \return 0 if successful, 1 if error + * + * This function initializes a periodic timer on the board. The timer is + * programmed to generate an interrupt after every \a interval_ms milliseconds. + * + * The \a callback_func_ptr parameter specifies a callback routine that will be + * called when the timer expires (and interrupt occurs). + * The \a chbsp_periodic_timer_handler function will call this function. + */ +u8 chbsp_periodic_timer_init(u16 interval_ms, + ch_timer_callback_t callback_func_ptr) +{ + /* Save timer interval and callback function */ + periodic_timer_interval_ms = interval_ms; + periodic_timer_callback_ptr = callback_func_ptr; + + return 0; +} + +/*! + * \brief Enable periodic timer interrupt. + * + * This function enables the interrupt associated with the periodic timer + * initialized by \a chbsp_periodic_timer_init(). + */ +void chbsp_periodic_timer_irq_enable(void) +{ +} + +/*! + * \brief Disable periodic timer interrupt. + * + * This function enables the interrupt associated with the periodic timer + * initialized by \a chbsp_periodic_timer_init(). + */ +void chbsp_periodic_timer_irq_disable(void) +{ +} + +/*! + * \brief Start periodic timer. + * + * \return 0 if successful, 1 if error + * + * This function starts the periodic timer initialized by + * \a chbsp_periodic_timer_init(). + */ +u8 chbsp_periodic_timer_start(void) +{ + return 0; +} + +/*! + * \brief Periodic timer handler. + * + * \return 0 if successful, 1 if error + * + * This function handles the expiration of the periodic timer, re-arms it and + * any associated interrupts for the next interval, and calls the callback + * routine that was registered using \a chbsp_periodic_timer_init(). + */ +void chbsp_periodic_timer_handler(void) +{ + ch_timer_callback_t func_ptr = periodic_timer_callback_ptr; + + chbsp_periodic_timer_start(); + + if (func_ptr != NULL) + (*func_ptr)(); // call application timer callback routine + + chbsp_periodic_timer_irq_enable(); +} + +/*! + * \brief Turn on an LED on the board. + * + * This function turns on an LED on the board. + * + * The \a dev_num parameter contains the device number of a specific sensor. + * This routine will turn on the LED on the Chirp sensor daughterboard that + * is next to the specified sensor. + */ +void chbsp_led_on(u8 led_num) +{ +} + +/*! + * \brief Turn off an LED on the board. + * + * This function turns off an LED on the board. + * + * The \a dev_num parameter contains the device number of a specific sensor. + * This routine will turn off the LED on the Chirp sensor daughterboard that + * is next to the specified sensor. + */ +void chbsp_led_off(u8 led_num) +{ +} + +u32 chbsp_timestamp_ms(void) +{ + return (u32)os_timestamp_ms(); +} + +void chbsp_print_str(char *str) +{ + os_print_str(str); +} + diff --git a/drivers/iio/proximity/inv_ch101/src/chbsp_init.h b/drivers/iio/proximity/inv_ch101/src/chbsp_init.h new file mode 100644 index 000000000000..dfff1424a0ab --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/chbsp_init.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CHBSPINIT_H_ +#define CHBSPINIT_H_ + +#include "system.h" +#include "chirp_board_config.h" + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +#define CHIRP_RST 1 +#define CHIRP_RST_PS 2 + +#define CHIRP0_PROG_0 10 +#define CHIRP0_PROG_1 11 +#define CHIRP0_PROG_2 12 +#define CHIRP1_PROG_0 13 +#define CHIRP1_PROG_1 14 +#define CHIRP1_PROG_2 15 +#define CHIRP2_PROG_0 16 +#define CHIRP2_PROG_1 17 +#define CHIRP2_PROG_2 18 + +#define CHIRP0_INT_0 20 +#define CHIRP0_INT_1 21 +#define CHIRP0_INT_2 22 +#define CHIRP1_INT_0 23 +#define CHIRP1_INT_1 24 +#define CHIRP1_INT_2 25 +#define CHIRP2_INT_0 26 +#define CHIRP2_INT_1 27 +#define CHIRP2_INT_2 28 + +/* Standard symbols used in BSP - use values from config header */ +#define CHBSP_MAX_DEVICES CHIRP_MAX_NUM_SENSORS +#define CHBSP_NUM_I2C_BUSES CHIRP_NUM_I2C_BUSES + +/* RTC hardware pulse pin */ +#define CHBSP_RTC_CAL_PULSE_PIN 1 +/* Length of real-time clock calibration pulse, in milliseconds */ +/* Length of pulse applied to sensor INT line during clock cal */ +#define CHBSP_RTC_CAL_PULSE_MS 63 + +/* I2C Address assignments for each possible device */ +#define CHIRP_I2C_ADDRS {45, 43, 44, 52, 53, 54} //, 62, 63, 64} +#define CHIRP_I2C_BUSES {0, 0, 0, 1, 1, 1} //, 2, 2, 2} + +extern u8 chirp_i2c_addrs[CHBSP_MAX_DEVICES]; +extern u8 chirp_i2c_buses[CHBSP_MAX_DEVICES]; + +extern u32 chirp_pin_enabled[CHBSP_MAX_DEVICES]; +extern u32 chirp_pin_prog[CHBSP_MAX_DEVICES]; +extern u32 chirp_pin_io[CHBSP_MAX_DEVICES]; +extern u32 chirp_pin_io_irq[CHBSP_MAX_DEVICES]; + +#endif /* CHBSPINIT_H_ */ diff --git a/drivers/iio/proximity/inv_ch101/src/chirp_board_config.h b/drivers/iio/proximity/inv_ch101/src/chirp_board_config.h new file mode 100644 index 000000000000..739d54cd7151 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/chirp_board_config.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * chirp_board_config.h + * + * This file defines required symbols used to build an application with the + * Chirp SonicLib API and driver. These symbols are used for static array + * allocations and counters in SonicLib (and often applications), and are based + * on the number of specific resources on the target board. + * + * Two symbols must be defined: + * CHIRP_MAX_NUM_SENSORS - the number of possible sensor devices + * (i.e. the number of sensor ports) + * CHIRP_NUM_I2C_BUSES - the number of I2C buses on the board that are + * used for those sensor ports + * + * This file must be in the C pre-processor include path when the application + * is built with SonicLib and this board support package. + */ + +#ifndef CHIRP_BOARD_CONFIG_H_ +#define CHIRP_BOARD_CONFIG_H_ + +/* Settings for the Chirp RB5 board */ +/* maximum possible number of sensor devices */ +#define CHIRP_MAX_NUM_SENSORS 6 +/* number of I2C buses used by sensors */ +#define CHIRP_NUM_I2C_BUSES 2 + +#define CH101_MAX_PORTS CHIRP_MAX_NUM_SENSORS +#define CH101_I2C_BUSES CHIRP_NUM_I2C_BUSES +#define CH101_PORTS_PER_BUSES 3 +#define CH101_MAX_I2C_QUEUE_LENGTH 12 + +#endif /* CHIRP_BOARD_CONFIG_H_ */ diff --git a/drivers/iio/proximity/inv_ch101/src/chirp_bsp.h b/drivers/iio/proximity/inv_ch101/src/chirp_bsp.h new file mode 100644 index 000000000000..bb81e7da7521 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/chirp_bsp.h @@ -0,0 +1,1022 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*! \file chirp_bsp.h + * + * \brief User-supplied board support package functions to interface Chirp + * SonicLib to a specific hardware platform. + * + * This file defines the I/O interfaces that allow the standard Chirp SonicLib + * sensor driver functions to manage one or more sensors on a specific hardware + * platform. These include functions to initialize and control the various I/O + * pins connecting the sensor to the host system, the I2C communications + * interface, timer functions, etc. + * + * The board support package developer should not need to modify this file. + * However, that developer is responsible for implementing these support + * functions for the desired platform. Note that some functions are optional, + * depending on the specific runtime requirements (e.g. is non-blocking I/O + * required?) or development needs (e.g. is debugging support needed?). + * + * \note All functions are marked as REQUIRED, RECOMMENDED, or OPTIONAL in their + * indvidual descriptions. RECOMMENDED functions are either not used directly by + * SonicLib (but may be expected by examples and other applications from Chirp) + * or are only required to support certain operating configurations + * (e.g. individual device triggering). + * + * #### Organization + * The file organization for a BSP is intentionally very flexible, so that you + * may efficiently use existing code that supports your hardware or otherwise + * use your own organizing preferences. + * + * By convention, the functions that implement the BSP interfaces defined in + * chirp_bsp.h are placed in a source file called chbsp_VVV_BBB.c, where "VVV" + * is the board vendor name, and "BBB" is the board name. For example, the BSP + * for a Chirp SmartSonic board has a main file called chbsp_chirp_smartsonic.c + * + * #### Required \b chirp_board_config.h Header File + * + * The board support package must supply a header file called + * \b chirp_board_config.h containing definitions of two symbols used + * in the SonicLib driver functions. + * + * The following symbols must be defined in \b chirp_board_config.h: + * - \a CHIRP_MAX_NUM_SENSORS = maximum number of Chirp sensors + * - \a CHIRP_NUM_I2C_BUSES = number of I2C bus interfaces + * + * The \b chirp_board_config.h file must be in the C pre-processor include path + * when you build your application with SonicLib. + * + * + * #### Callback and Notification Functions + * + * In some cases, the BSP is required to call a function to notify SonicLib or + * the application that an event has occurred: + * + * - The BSP's handler routine that detects that a sensor interrupt has + * occurred (typically on a GPIO line) must call the application's callback + * routine whose address was stored in the \b io_int_callback field in the + * ch_group_t group descriptor. The BSP function must pass the device number + * of the sensor which interrupted as a parameter. + * + * - If non-blocking I/O is used, the BSP's handler functions which process the + * completion of an I/O operation must notify SonicLib that the I/O has + * completed by calling the \a ch_io_notify() function. The group pointer and + * I2C bus number must be passed as parameters to identify which I/O channel + * has finished. + * + * + * #### Implementation Hints + * + * Most of the required functions take a pointer to a ch_dev_t device descriptor + * structure as a handle to identify the sensor being controlled. The ch_dev_t + * structure contains various fields with configuration and operating state + * information for the device. In general, these data field values may be + * obtained using various \a ch_get_XXX() functions provided by the SonicLib + * driver API, so it should not be necessary to access fields directly. + * + * Some functions take a pointer to a ch_group_t (sensor group descriptor) + * structure as a parameter but must operate on individual sensors. These + * functions can be implemented using the \a ch_get_dev_ptr() function to access + * the ch_dev_t structure describing each individual sensor in the group, based + * on its device number (I/O index value). The total number of possible + * sensor devices in a group may be obtained by using the \a ch_get_num_ports() + * function. + * + * Similarly, each sensor's ch_dev_t structure contains a \a dev_num field that + * may be used to manage the pin assignments for the various sensors, by using + * it as an index into individual arrays which list the pins assigned to the + * PROG, INT, and RESET_N lines. The \a dev_num value for a sensor may be + * obtained using the \a ch_get_dev_num() function. + * + * Often, an action should only be taken on a sensor port if a sensor is present + * and has been successfully initialized and connected. The \a + * ch_sensor_is_connected() function can be used to obtain the connection status + * + * These functions are often used together to implement a board support routine, + * as in the following pseudo-code which shows how to perform an action on all + * connected devices in a group. This snippet assumes that a sensor group + * pointer (struct ch_group_t *) called \a grp_ptr is an input parameter to the + * function: + * + * struct ch_dev_t *dev_ptr; + * u8 dev_num; + * + * for (dev_num = 0; dev_num < ch_get_num_ports(grp_ptr); dev_num++ { + * dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + * if (ch_sensor_is_connected(dev_ptr)) { + * DO WHAT NEEDS DOING FOR THIS CONNECTED DEVICE + * } + * } + * + * The \a dev_num value for each device is specified by the user application as + * a parameter to \a ch_init(). + * + * \note Example board support packages that implement the chirp_bsp.h functions + * are available from Chirp Microsystems for specific platforms. + * Contact Chirp for more information. + */ + +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __CHIRP_BSP_H_ +#define __CHIRP_BSP_H_ + +#include "soniclib.h" + +#ifdef __GNUC__ +#define WEAK __attribute__((weak)) +#else +#define WEAK +#endif + +/*! + * \brief Main hardware initialization + * + * This function executes the required hardware initialization sequence for the + * board being used. This includes clock, memory, and processor setup as well as + * any special handling that is needed. This function is called at the beginning + * of an application, as the first operation. + * + * Along with the actual hardware initialization, this function must also + * initialize the following fields within the ch_group_t sensor group + * descriptor: + * - \b num_ports = number of ports on the board, usually the same + * as \a CHIRP_MAX_DEVICES in \b chirp_board_config.h. + * - \b num_i2c_buses = number of ports on the board, usually the same + * as \a CHIRP_NUM_I2C_BUSES in \b chirp_board_config.h. + * - \b rtc_cal_pulse_ms = length (duration) of the pulse sent on the INT line + * to each sensor during calibration of the real-time clock + * + * This function is REQUIRED. + */ +void chbsp_board_init(struct ch_group_t *grp_ptr); + +/*! + * \brief Toggle a debug indicator pin + * + * \param dbg_pin_num index value for debug pin to toggle + * + * This function should change the state (high/low) of the specified debug + * indicator pin. The \a dbg_pin_num parameter is an index value that specifies + * which debug pin should be controlled. + * + * This function is OPTIONAL. + * + * \note OPTIONAL - Implementing this function is optional and only needed for + * debugging support. The indicator pins may be any convenient GPIO + * signals on the host system. They are only used to provide a detectable + * indication of the program execution for debugging. If used, the debug + * pin(s) must be initialized during \a chbsp_board_init(). + */ +void chbsp_debug_toggle(u8 dbg_pin_num); + +/*! + * \brief Turn on a debug indicator pin + * + * \param dbg_pin_num index value for debug pin to turn on + * + * This function should drive the specified debug indicator pin high. + * The \a dbg_pin_num parameter is an index value that specifies which debug pin + * should be controlled. + * + * This function is OPTIONAL. + * + * \note OPTIONAL - Implementing this function is optional and only needed for + * debugging support. The indicator pins may be any convenient GPIO + * signals on the host system. They are only used to provide a detectable + * indication of the program execution for debugging. If used, the debug + * pin(s) must be initialized during \a chbsp_board_init(). + */ +void chbsp_debug_on(u8 dbg_pin_num); + +/*! + * \brief Turn off a debug indicator pin + * + * \param dbg_pin_num index value for debug pin to turn off + * + * This function should drive the the specified debug indicator pin low. + * The \a dbg_pin_num parameter is an index value that specifies which debug pin + * should be controlled. + * + * This function is OPTIONAL. + * + * \note OPTIONAL - Implementing this function is optional and only needed for + * debugging support. The indicator pins may be any convenient GPIO + * signals on the host system. They are only used to provide a detectable + * indication of the program execution for debugging. If used, the debug + * pin(s) must be initialized during \a chbsp_board_init(). + */ +void chbsp_debug_off(u8 dbg_pin_num); + +/*! + * \brief Assert the reset pin for all sensors. + * + * This function should drive the Chirp sensor reset pin low (assert RESET_N) + * on all sensors. + * + * This function is REQUIRED. + */ +void chbsp_reset_assert(void); + +/*! + * \brief Deassert the reset pin for all sensors. + * + * This function should drive the Chirp sensor reset pin high (or open drain if + * there is a pull-up) on all sensors. + * + * This function is REQUIRED. + */ +void chbsp_reset_release(void); + +/*! + * \brief Assert the reset pulse pin + * + * This function drives the reset pulse pin low. + */ +void chbsp_reset_ps_assert(void); + +/*! + * \brief De-assert the pulse reset pin + * + * This function drives the pulse reset pin high. + */ +void chbsp_reset_ps_release(void); + +/*! + * \brief Assert the PROG pin + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function should drive the Chirp sensor PROG pin high for the specified + * device. It is used by the driver to initiate I2C communication with a + * specific Chirp sensor device before a unique I2C address is assigned to the + * device or when the programming interface is used. + * + * When the PROG pin is asserted, the device will respond to the standard + * programming I2C address (0x45). + * + * This function is REQUIRED. + */ +void chbsp_program_enable(struct ch_dev_t *dev_ptr); + +/*! + * \brief Deassert the PROG pin + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function should drive the Chirp sensor PROG pin low for the specified + * device. + * + * This function is REQUIRED. + */ +void chbsp_program_disable(struct ch_dev_t *dev_ptr); + +/*! + * \brief Configure the Chirp sensor INT pin as an output for a group of sensors + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * This function should configure each Chirp sensor's INT pin as an output + * (from the perspective of the host system). + * + * This function is REQUIRED. + */ +void chbsp_group_set_io_dir_out(struct ch_group_t *grp_ptr); + +/*! + * \brief Configure the Chirp sensor INT pin as an output for one sensor. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function should configure the Chirp sensor INT pin as an output + * (from the perspective of the host system). + * + * This function is RECOMMENDED. + * + * \note RECOMMENDED - Implementing this function is optional and is only + * required if individual sensor hardware triggering is used. It is not required + * if the sensors are operated only in free-running mode, or if they are always + * triggered as a group, using \a ch_group_trigger(). However, implementing this + * function is required if sensors will be triggered individually, using + * \a ch_trigger(). + */ +void chbsp_set_io_dir_out(struct ch_dev_t *dev_ptr); + +/*! + * \brief Configure the Chirp sensor INT pins as inputs for a group of sensors + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * This function should configure each Chirp sensor's INT pin as an input + * (from the perspective of the host system). + * + * This function is REQUIRED. + */ +void chbsp_group_set_io_dir_in(struct ch_group_t *grp_ptr); + +/*! + * \brief Configure the Chirp sensor INT pin as an input for one sensor. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function should configure the Chirp sensor INT pin as an input + * (from the perspective of the host system). + * + * This function is RECOMMENDED. + * + * \note RECOMMENDED - Implementing this function is optional and is only + * required if individual sensor hardware triggering is used. It is not required + * if the sensors are operated only in free-running mode, or if they are always + * triggered as a group, using \a ch_group_trigger(). However, implementing this + * function is required if sensors will be triggered individually, using + * \a ch_trigger(). + */ +void chbsp_set_io_dir_in(struct ch_dev_t *dev_ptr); + +/*! + * \brief Initialize the set of I/O pins for a group of sensors. + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * This function initializes the set of I/O pins used for a group of sensors. + * It should perform any clock initialization and other setup required to use + * the ports/pins. It should then configure the RESET_N and PROG pins as outputs + * (from the perspective of the host system), and assert both RESET_N and PROG. + * It should also configure the INT pin as an input. + * + * This function is REQUIRED. + */ +void chbsp_group_pin_init(struct ch_group_t *grp_ptr); + +/*! + * \brief Set the INT pins low for a group of sensors. + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * This function should drive the INT line low for each sensor in the group. + * + * This function is REQUIRED. + */ +void chbsp_group_io_clear(struct ch_group_t *grp_ptr); + +/*! + * \brief Set the INT pin low for one sensor. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function should drive the INT line low for the specified sensor. + * + * This function is OPTIONAL. + * + * \note OPTIONAL - Implementing this function is optional and only needed for + * individual sensor hardware triggering using \a ch_trigger(). It is not + * required if sensors are only triggered as a group using \a ch_group_trigger() + */ +void chbsp_io_clear(struct ch_dev_t *dev_ptr); + +/*! + * \brief Set the INT pins high for a group of sensors. + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * This function should drive the INT line high for each sensor in the group. + * + * This function is REQUIRED. + */ +void chbsp_group_io_set(struct ch_group_t *grp_ptr); + +/*! + * \brief Set the INT pin high for one sensor. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function should drive the INT line high for the specified sensor. + * + * This function is OPTIONAL. + * + * \note OPTIONAL - Implementing this function is optional and only needed for + * individual sensor hardware triggering using \a ch_trigger(). It is not + * required if sensors are only triggered as a group using \a ch_group_trigger() + */ +void chbsp_io_set(struct ch_dev_t *dev_ptr); + +/*! + * \brief Enable interrupts for a group of sensors. + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * For each sensor in the group, this function should enable the host interrupt + * associated with the Chirp sensor device's INT line. + * + * This function is REQUIRED. + */ +void chbsp_group_io_interrupt_enable(struct ch_group_t *grp_ptr); + +/*! + * \brief Enable the interrupt for one sensor + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function should enable the host interrupt associated with the Chirp + * sensor device's INT line. + * + * This function is REQUIRED. + */ +void chbsp_io_interrupt_enable(struct ch_dev_t *dev_ptr); + +/*! + * \brief Disable interrupts for a group of sensors + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * + * For each sensor in the group, this function should disable the host interrupt + * associated with the Chirp sensor device's INT line. + * * + * This function is REQUIRED. + */ +void chbsp_group_io_interrupt_disable(struct ch_group_t *grp_ptr); + +/*! + * \brief Disable the interrupt for one sensor + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function should disable the host interrupt associated with the Chirp + * sensor device's INT line. + * + * This function is REQUIRED. + */ +void chbsp_io_interrupt_disable(struct ch_dev_t *dev_ptr); + +/*! + * \brief Set callback routine for Chirp sensor I/O interrupt + * + * \param callback_func_ptr pointer to application function to be called + * when interrupt occurs + * + * This function sets up the specified callback routine to be called whenever + * the interrupt associated with the sensor's INT line occurs. + * The implementation will typically save the callback routine address in a + * pointer variable that can later be accessed from within the interrupt handler + * to call the function. + * + * The callback function will generally be called at interrupt level from the + * interrupt service routine. + * + * This function is REQUIRED. + */ +void chbsp_io_callback_set(ch_io_int_callback_t callback_func_ptr); + +/*! + * \brief Delay for specified number of microseconds + * + * \param us number of microseconds to delay before returning + * + * This function should wait for the specified number of microseconds before + * returning to the caller. + * + * This function is REQUIRED. + */ +void chbsp_delay_us(uint32_t us); + +/*! + * \brief Delay for specified number of milliseconds. + * + * \param ms number of milliseconds to delay before returning + * + * This function should wait for the specified number of milliseconds before + * returning to the caller. + * + * This function is REQUIRED. + * + * \note This function is used during the \a ch_group_start() function to + * control the length of the calibration pulse sent to the Chirp sensor device + * during the real-time clock (RTC) calibration, based on the pulse length + * specified in the board support package. The accuracy of this pulse timing + * will directly affect the accuracy of the range values calculated by the + * sensor. + */ +void chbsp_delay_ms(uint32_t ms); + +/*! + * \brief Return a free-running counter value in milliseconds. + * + * \return a 32-bit free-running counter value in milliseconds + * + * This function should use a running timer to provide an updated timestamp, + * in milliseconds, when called. + * + * If \a CHDRV_DEBUG is defined, this function is used by the SonicLib driver + * to calculate elapsed times for various operations. + * + * This function is OPTIONAL. + * + * \note OPTIONAL - Implementing this function is optional and only needed for + * debugging support. + */ +uint32_t chbsp_timestamp_ms(void); + +/*! + * \brief Initialize the host's I2C hardware. + * + * \return 0 if successful, 1 on error + * + * This function should perform general I2C initialization on the host system. + * This includes both hardware initialization and setting up any necessary + * software structures. Upon successful return from this routine, the system + * should be ready to perform I/O operations such as \a chbsp_i2c_read() and + * \a chbsp_i2c_write(). + * + * This function is REQUIRED. + */ +int chbsp_i2c_init(void); + +/*! + * \brief De-initialize the host's I2C hardware. + * + * \return 0 if successful, 1 on error + * + * This function de-initializes the I2C hardware and control structures on the + * host system. + * + * This function is OPTIONAL. + * + * \note OPTIONAL - Implementing this function is optional and only needed for + * debugging support. + */ +int chbsp_i2c_deinit(void); + +/*! + * \brief Return I2C information for a sensor port on the board. + * + * \param grp_ptr pointer to the ch_group_t config structure for + * a group of sensors + * \param dev_num device number within sensor group + * \param info_ptr pointer to structure to be filled with I2C config values + * + * \return 0 if successful, 1 if error + * + * This function is called by SonicLib functions to obtain I2C operating + * parameters for a specific device on the board. + * + * This function returns I2C values in the ch_i2c_info_t structure specified by + * \a info_ptr. The structure includes three fields. + * - The \a address field contains the I2C address for the sensor. + * - The \a bus_num field contains the I2C bus number (index). + * - The \a drv_flags field contains various bit flags through which the BSP + * can inform SonicLib driver functions to perform specific actions during I2C + * I/O operations. The possible flags include: + * - - \a I2C_DRV_FLAG_RESET_AFTER_NB - the I2C interface needs to be reset + * after non-blocking transfers + * - - \a I2C_DRV_FLAG_USE_PROG_NB - use high-speed programming interface for + * non-blocking transfers + * + * This function is REQUIRED. + */ +u8 chbsp_i2c_get_info(struct ch_group_t *grp_ptr, u8 dev_num, + struct ch_i2c_info_t *info_ptr); + +/*! + * \brief Write bytes to an I2C slave. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param data data to be transmitted + * \param num_bytes length of data to be transmitted + * + * \return 0 if successful, 1 on error or NACK + * + * This function should write one or more bytes of data to an I2C slave device. + * The I2C interface will have already been initialized using + * \a chbsp_i2c_init(). + * + * \note Implementations of this function should use the \a ch_get_i2c_address() + * function to obtain the device I2C address. + */ +int chbsp_i2c_write(struct ch_dev_t *dev_ptr, u8 *data, u16 num_bytes); + +/*! + * \brief Write bytes to an I2C slave using memory addressing. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param mem_addr internal memory or register address within device + * \param data data to be transmitted + * \param num_bytes length of data to be transmitted + * + * \return 0 if successful, 1 on error or NACK + * + * This function should write one or more bytes of data to an I2C slave device + * using an internal memory or register address. The remote device will write + * \a num_bytes bytes of data starting at internal memory/register address + * \a mem_addr. The I2C interface will have already been initialized using + * \a chbsp_i2c_init(). + * + * This function is REQUIRED. + * + * \note Implementations of this function should use the \a ch_get_i2c_address() + * function to obtain the device I2C address. + */ +int chbsp_i2c_mem_write(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 *data, + u16 num_bytes); + +/*! + * \brief Write bytes to an I2C slave, non-blocking. + * \param data pointer to the start of data to be transmitted + * \param num_bytes length of data to be transmitted + * + * \return 0 if successful, 1 on error or NACK + * + * This function should initiate a non-blocking write of the specified number of + * bytes to an I2C slave device. + * + * The I2C interface must have already been initialized using + * \a chbsp_i2c_init(). + * + * This function is OPTIONAL. + * + * \note OPTIONAL - Implementing this function is optional and only needed if + * non-blocking writes on the I2C bus are required. It is not called by SonicLib + * functions. + * + * \note Implementations of this function should use the \a ch_get_i2c_address() + * function to obtain the device I2C address. + */ +int chbsp_i2c_write_nb(struct ch_dev_t *dev_ptr, u8 *data, u16 num_bytes); + +/*! + * \brief Write bytes to an I2C slave using memory addressing, non-blocking. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param mem_addr internal memory or register address within device + * \param data pointer to the start of data to be transmitted + * \param num_bytes length of data to be transmitted + * + * \return 0 if successful, 1 on error or NACK + * + * This function should initiate a non-blocking write of the specified number of + * bytes to an I2C slave device, using an internal memory or register address. + * The remote device will write \a num_bytes bytes of data starting at internal + * memory/register address \a mem_addr. + * + * The I2C interface must have already been initialized using + * \a chbsp_i2c_init(). + * + * This function is OPTIONAL. + * + * \note OPTIONAL - Implementing this function is optional and only needed if + * non-blocking writes on the I2C bus are required. It is not called by + * SonicLib functions. + * + * \note Implementations of this function should use the \a ch_get_i2c_address() + * function to obtain the device I2C address. + */ +int chbsp_i2c_mem_write_nb(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 *data, + u16 num_bytes); + +/*! + * \brief Read bytes from an I2C slave. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param data pointer to receive data buffer + * \param num_bytes number of bytes to read + * + * \return 0 if successful, 1 on error or NACK + * + * This function should read the specified number of bytes from an I2C slave + * device. + * + * The I2C interface must have already been initialized using + * \a chbsp_i2c_init(). + * + * This function is REQUIRED. + * + * \note Implementations of this function should use the \a ch_get_i2c_address() + * function to obtain the device I2C address. + */ +int chbsp_i2c_read(struct ch_dev_t *dev_ptr, u8 *data, u16 num_bytes); + +/*! + * \brief Read bytes from an I2C slave using memory addressing. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param mem_addr internal memory or register address within device + * \param data pointer to receive data buffer + * \param num_bytes number of bytes to read + * + * \return 0 if successful, 1 on error or NACK + * + * This function should read the specified number of bytes from an I2C slave + * device, using an internal memory or register address. The remote device will + * return \a num_bytes bytes starting at internal memory/register address + * \a mem_addr. + * + * The I2C interface must have already been initialized using + * \a chbsp_i2c_init(). + * + * This function is normally implemented such that the device address and memory + * address are written to the I2C bus, then a repeated-start marker is sent, and + * the data is read. Most micro-controller I/O libraries support such an access + * sequence. (If this is not possible, the write and read phases may be + * separated into two discrete operations, without the repeated-start. However, + * this may be a problem if more than one bus master is present, because the bus + * is not held between the two phases.) + * + * This function is REQUIRED. + * + * \note Implementations of this function should use the \a ch_get_i2c_address() + * function to obtain the device I2C address. + */ +int chbsp_i2c_mem_read(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 *data, + u16 num_bytes); + +/*! + * \brief Read bytes from an I2C slave, non-blocking. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param data pointer to receive data buffer + * \param num_bytes number of bytes to read + * + * \return 0 if successful, 1 on error or NACK + * + * This function should initiate a non-blocking read of the specified number of + * bytes from an I2C slave. + * + * The I2C interface must have already been initialized using + * \a chbsp_i2c_init(). + * + * This function is OPTIONAL. + * + * \note OPTIONAL - Implementing this function is optional and only needed if + * non-blocking I/Q readout is required. + * + * \note Implementations of this function should use the \a ch_get_i2c_address() + * function to obtain the device I2C address. + */ +int chbsp_i2c_read_nb(struct ch_dev_t *dev_ptr, u8 *data, u16 num_bytes); + +/*! + * \brief Read bytes from an I2C slave using memory addressing, non-blocking. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * \param mem_addr internal memory or register address within device + * \param data pointer to receive data buffer + * \param num_bytes number of bytes to read + * + * \return 0 if successful, 1 on error or NACK + * + * This function should initiate a non-blocking read of the specified number of + * bytes from an I2C slave. The I2C interface must have already been initialized + * using \a chbsp_i2c_init(). + * + * This function is OPTIONAL. + * + * \note OPTIONAL - Implementing this function is optional and only needed if + * non-blocking I/Q readout is required. + * + * \note Implementations of this function should use the \a ch_get_i2c_address() + * function to obtain the device I2C address. + */ +int chbsp_i2c_mem_read_nb(struct ch_dev_t *dev_ptr, u16 mem_addr, u8 *data, + u16 num_bytes); + +/*! + * \brief Interrupt handler callout for external devices sharing the I2C bus. + * + * \param trans pointer to Chirp sensor I2C transaction control structure + * + * This function is called when a non-blocking I2C operation completes on an + * "external" (non Chirp sensor) device. The \a chbsp_external_i2c_queue() + * function should be called to add such a transaction to the I2C queue. + * + * \note OPTIONAL - Implementing this function is optional and only needed if + * devices other than the Chirp sensor(s) are operating on the same I2C bus and + * sharing the Chirp driver non-blocking I2C I/O mechanism. + */ +void chbsp_external_i2c_irq_handler(struct chdrv_i2c_transaction_t *trans); + +/*! + * \brief Reset I2C bus associated with device. + * + * \param dev_ptr pointer to the ch_dev_t config structure for a sensor + * + * This function should perform a reset of the I2C interface for the specified + * device. + */ +//! +void chbsp_i2c_reset(struct ch_dev_t *dev_ptr); + +/*! + * \brief Initialize periodic timer. + * + * \param interval_ms timer interval, in milliseconds + * \param callback_func_ptr address of routine to be called every time the + * imer expires + * + * \return 0 if successful, 1 if error + * + * This function initializes a periodic timer on the board. The timer should be + * programmed to generate an interrupt after every \a interval_ms milliseconds. + * + * The \a callback_func_ptr parameter specifies a callback routine that will be + * called when the timer expires (and interrupt occurs). The timer interrupt + * handler function within the board support package should call this function. + * + * The period timer is often used to trigger sensor measurement cycles by having + * the application's callback function call \a ch_trigger() or + * \a ch_group_trigger(). + * + * This function is RECOMMENDED. + * + * \note RECOMMENDED - This and other periodic timer functions are not called by + * SonicLib functions, so are not required. However, they are used in examples + * and other applications from Chirp. + */ +u8 chbsp_periodic_timer_init(u16 interval_ms, + ch_timer_callback_t callback_func_ptr); + +/*! + * \brief Enable periodic timer interrupt. + * + * This function enables the interrupt associated with the periodic timer + * initialized by \a chbsp_periodic_timer_init(). + * + * This function is RECOMMENDED. + * + * \note RECOMMENDED - This and other periodic timer functions are not called by + * SonicLib functions, so are not required. However, they are used in examples + * and other applications from Chirp. + */ +void chbsp_periodic_timer_irq_enable(void); + +/*! + * \brief Disable periodic timer interrupt. + * + * This function enables the interrupt associated with the periodic timer + * initialized by \a chbsp_periodic_timer_init(). + * + * This function is RECOMMENDED. + * + * \note RECOMMENDED - This and other periodic timer functions are not called by + * SonicLib functions, so are not required. However, they are used in examples + * and other applications from Chirp. + */ +void chbsp_periodic_timer_irq_disable(void); + +/*! + * \brief Start periodic timer. + * + * \return 0 if successful, 1 if error + * + * This function starts the periodic timer initialized by + * \a chbsp_periodic_timer_init(). + * + * This function is RECOMMENDED. + * + * \note RECOMMENDED - This and other periodic timer functions are not called by + * SonicLib functions, so are not required. However, they are used in examples + * and other applications from Chirp. + */ +u8 chbsp_periodic_timer_start(void); + +/*! + * \brief Stop periodic timer. + * + * \return 0 if successful, 1 if error + * + * This function stops the periodic timer initialized by + * \a chbsp_periodic_timer_init(). + * + * This function is RECOMMENDED. + * + * \note RECOMMENDED - This and other periodic timer functions are not called by + * SonicLib functions, so are not required. However, they are used in examples + * and other applications from Chirp. + */ +u8 chbsp_periodic_timer_stop(void); + +/*! + * \brief Periodic timer handler. + * + * \return 0 if successful, 1 if error + * + * This function handles the expiration of the periodic timer, re-arms it and + * any associated interrupts for the next interval, and calls the callback + * routine that was registered using \a chbsp_periodic_timer_init(). + * + * This function is RECOMMENDED. + * + * \note RECOMMENDED - This and other periodic timer functions are not called by + * SonicLib functions, so are not required. However, they are used in examples + * and other applications from Chirp. + */ +void chbsp_periodic_timer_handler(void); + +/*! + * \brief Put the processor into low-power sleep state. + * + * This function puts the host processor (MCU) into a low-power sleep mode, to + * conserve energy. The sleep state should be selected such that interrupts + * associated with the I2C, external GPIO pins, and the periodic timer (if used) + * are able to wake up the device. + * + * This function is RECOMMENDED. + * + * \note RECOMMENDED - This and other periodic timer functions are not called by + * SonicLib functions, so are not required. However, they are used in examples + * and other applications from Chirp. + */ +void chbsp_proc_sleep(u16 ms); + +/*! + * \brief Turn on an LED on the board. + * + * This function turns on an LED on the board. The implementation of this + * function is flexible to allow for different arrangements of LEDs. + * + * The \a dev_num parameter contains the device number of a specific sensor. + * The device number may be used to select which LED should be turned on + * (e.g. if it is located next to an individual sensor). If the board does not + * have LEDs associated with individual sensors, the BSP may implement this + * routine to combine all device numbers to control a single LED, or any other + * organization that makes sense for the board. + * + * This function is RECOMMENDED. + * + * \note RECOMMENDED - This and other periodic timer functions are not called by + * SonicLib functions, so are not required. However, they are used in examples + * and other applications from Chirp. + */ +void chbsp_led_on(u8 dev_num); + +/*! + * \brief Turn off an LED on the board. + * + * This function turns off an LED on the board. The implementation of this + * function is flexible to allow for different arrangements of LEDs. + * + * The \a dev_num parameter contains the device number of a specific sensor. + * The device number may be used to select which LED should be turned on + * (e.g. if it is located next to an individual sensor). If the board does not + * have LEDs associated with individual sensors, the BSP may implement this + * routine to combine all device numbers to control a single LED, or any other + * organization that makes sense for the board. + * + * This function is RECOMMENDED. + * + * \note RECOMMENDED - This and other periodic timer functions are not called by + * SonicLib functions, so are not required. However, they are used in examples + * and other applications from Chirp. + */ +void chbsp_led_off(u8 dev_num); + +/*! + * \brief Output a text string via serial interface + * + * \param str pointer to a string of characters to be output + * + * This function should print debug information to the console. The user can + * implement this function to print characters over a serial port, for example. + * + * In the Chirp SonicLib sensor driver, debug message output may be enabled by + * defining \a CHDRV_DEBUG and/or \a CHDRV_DEBUG_VERBOSE. + * + * \note OPTIONAL - Implementing this function is optional and only needed for + * debugging support. + */ +void chbsp_print_str(char *str); + +void chbsp_printf(const char *fmt, ...); + +/*! + * \brief Critical section enter/leave. + * + * This functions called to prevent execution break in multi task environment. + * + * This function is RECOMMENDED. + * + * \note RECOMMENDED - This functions required in in multi task environment. + */ +void chbsp_critical_section_enter(void); +void chbsp_critical_section_leave(void); + +#endif /* __CHIRP_BSP_H_ */ diff --git a/drivers/iio/proximity/inv_ch101/src/chirp_hal.c b/drivers/iio/proximity/inv_ch101/src/chirp_hal.c new file mode 100644 index 000000000000..f81886237e7d --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/chirp_hal.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "chirp_hal.h" +#include "chbsp_init.h" + +static struct ch101_client *_ch101_client; + + +void set_chirp_gpios(struct ch101_client *client) +{ + _ch101_client = client; + printf("%s: data: %p\n", __func__, _ch101_client); +} + +struct ch101_client *get_chirp_gpios(void) +{ + return _ch101_client; +} + +struct gpio_desc *os_get_pin_desc(ioport_pin_t pin) +{ + if (!_ch101_client) + return 0; + if (pin == CHIRP_RST) + return _ch101_client->gpios.gpiod_rst; + if (pin == CHIRP_RST_PS) + return _ch101_client->gpios.gpiod_rst_pulse; + if (pin == CHIRP0_INT_0) + return _ch101_client->bus[0].gpiod_int[0]; + if (pin == CHIRP0_INT_1) + return _ch101_client->bus[0].gpiod_int[1]; + if (pin == CHIRP0_INT_2) + return _ch101_client->bus[0].gpiod_int[2]; + if (pin == CHIRP1_INT_0) + return _ch101_client->bus[1].gpiod_int[0]; + if (pin == CHIRP1_INT_1) + return _ch101_client->bus[1].gpiod_int[1]; + if (pin == CHIRP1_INT_2) + return _ch101_client->bus[1].gpiod_int[2]; + if (pin >= CHIRP0_PROG_0 && pin <= CHIRP2_PROG_2) + return 0; + + printf("%s: pin: %d undefined\n", __func__, (u32)pin); + + return 0; +} + +u32 os_get_pin_prog(ioport_pin_t pin) +{ + if (!_ch101_client) + return 0; + if (pin == CHIRP0_PROG_0) + return _ch101_client->bus[0].gpio_exp_prog_pin[0]; + if (pin == CHIRP0_PROG_1) + return _ch101_client->bus[0].gpio_exp_prog_pin[1]; + if (pin == CHIRP0_PROG_2) + return _ch101_client->bus[0].gpio_exp_prog_pin[2]; + if (pin == CHIRP1_PROG_0) + return _ch101_client->bus[1].gpio_exp_prog_pin[0]; + if (pin == CHIRP1_PROG_1) + return _ch101_client->bus[1].gpio_exp_prog_pin[1]; + if (pin == CHIRP1_PROG_2) + return _ch101_client->bus[1].gpio_exp_prog_pin[2]; + + printf("%s: pin: %d undefined\n", __func__, (u32)pin); + + return 0; +} + +void prog_set_dir(ioport_pin_t pin, enum ioport_direction dir) +{ + struct ch101_client *data = get_chirp_data(); + + if (data && data->cbk->set_pin_dir) + data->cbk->set_pin_dir(pin, dir); +} + +void prog_set_level(ioport_pin_t pin, bool level) +{ + struct ch101_client *data = get_chirp_data(); + + if (data && data->cbk->set_pin_level) + data->cbk->set_pin_level(pin, level); +} + +void ioport_set_pin_dir(ioport_pin_t pin, enum ioport_direction dir) +{ + if (pin >= CHIRP0_PROG_0 && pin <= CHIRP2_PROG_2) { + u32 prog_pin; + + prog_pin = os_get_pin_prog(pin); +// printf("%s: prog_pin: %d dir: %d\n", +// __func__, prog_pin, dir); + prog_set_dir(prog_pin, dir); + } else { + struct gpio_desc *desc; + + desc = os_get_pin_desc(pin); +// printf("%s: pin: %d(%p) dir: %d\n", +// __func__, pin, desc, dir); + if (!desc) { + printf("%s: pin: %d(%p) NULL\n", + __func__, pin, desc); + return; + } + if (dir == IOPORT_DIR_INPUT) + gpiod_direction_input(desc); + else + gpiod_direction_output(desc, 0); + } +} + +void ioport_set_pin_level(ioport_pin_t pin, bool level) +{ + if (pin >= CHIRP0_PROG_0 && pin <= CHIRP2_PROG_2) { + u32 prog_pin; + + prog_pin = os_get_pin_prog(pin); +// printf("%s: prog_pin: %d level: %d\n", +// __func__, prog_pin, level); + prog_set_level(prog_pin, level); + } else { + struct gpio_desc *desc; + + desc = os_get_pin_desc(pin); +// printf("%s: pin: %d(%p) level: %d\n", +// __func__, pin, desc, level); + if (!desc) { + printf("%s: pin: %d(%p) NULL\n", + __func__, pin, desc); + return; + } + gpiod_set_value_cansleep(desc, level); + } + +// ioport_get_pin_level(pin); +} + +enum ioport_direction ioport_get_pin_dir(ioport_pin_t pin) +{ + enum ioport_direction dir = IOPORT_DIR_INPUT; + + if (pin >= CHIRP0_PROG_0 && pin <= CHIRP2_PROG_2) { + //u32 prog_pin; + //prog_pin = os_get_pin_prog(pin); + //dir = prog_get_dir(prog_pin); + } else { + struct gpio_desc *desc; + + desc = os_get_pin_desc(pin); + if (!desc) { + printf("%s: pin: %d(%p) NULL\n", + __func__, pin, desc); + return dir; + } + dir = gpiod_get_direction(desc); +// printf("%s: pin: %d(%p) dir: %d\n", __func__, pin, desc, dir); + } + + return dir; +} + +bool ioport_get_pin_level(ioport_pin_t pin) +{ + bool level = false; + + if (pin >= CHIRP0_PROG_0 && pin <= CHIRP2_PROG_2) { + //u32 prog_pin; + //prog_pin = os_get_pin_prog(pin); + //level = prog_set_level(prog_pin, level); + } else { + struct gpio_desc *desc; + + desc = os_get_pin_desc(pin); + if (!desc) { + printf("%s: pin: %d(%p) NULL\n", + __func__, pin, desc); + return level; + } + level = gpiod_get_value_cansleep(desc); + } + + return level; +} + +int32_t os_enable_interrupt(const uint32_t pin) +{ + struct gpio_desc *desc = os_get_pin_desc(pin); + unsigned int irq = gpiod_to_irq(desc); + struct ch101_client *data = get_chirp_data(); + + printf("%s: irq: %d\n", __func__, irq); + + if (data && data->cbk->setup_int_gpio) + data->cbk->setup_int_gpio(data, pin); + + return 0; +} + +int32_t os_disable_interrupt(const uint32_t pin) +{ + struct gpio_desc *desc = os_get_pin_desc(pin); + unsigned int irq = gpiod_to_irq(desc); + struct ch101_client *data = get_chirp_data(); + + printf("%s: irq: %d\n", __func__, irq); + + if (data && data->cbk->free_int_gpio) + data->cbk->free_int_gpio(data, pin); + + return 0; +} + +void os_clear_interrupt(const uint32_t pin) +{ +} + +// delay in microseconds +void os_delay_us(const uint16_t us) +{ + usleep_range(us, us + 1); +} + +// delay in miliseconds +void os_delay_ms(const uint16_t ms) +{ + msleep(ms); +} + +u64 os_timestamp_ns(void) +{ + struct timespec ts; + + get_monotonic_boottime(&ts); + return timespec_to_ns(&ts); +} + +u64 os_timestamp_ms(void) +{ + u64 ns = os_timestamp_ns(); + u32 div = 1e6; + + return div64_ul(ns, div); +} + +void os_print_str(char *str) +{ + printf("%s", str); +} diff --git a/drivers/iio/proximity/inv_ch101/src/chirp_hal.h b/drivers/iio/proximity/inv_ch101/src/chirp_hal.h new file mode 100644 index 000000000000..a85bf470ce9b --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/chirp_hal.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CHIRPHAL_H +#define CHIRPHAL_H + +#include "system.h" +#include "init_driver.h" +#include "chirp_hal.h" +#include "../ch101_client.h" + + +void ioport_set_pin_dir(ioport_pin_t pin, enum ioport_direction dir); +void ioport_set_pin_level(ioport_pin_t pin, bool level); + +enum ioport_direction ioport_get_pin_dir(ioport_pin_t pin); +bool ioport_get_pin_level(ioport_pin_t pin); + +int32_t os_enable_interrupt(const u32 pin); +int32_t os_disable_interrupt(const u32 pin); +void os_clear_interrupt(const u32 pin); + +void os_delay_us(const u16 us); +void os_delay_ms(const u16 ms); + +uint64_t os_timestamp_ms(void); + +void set_chirp_gpios(struct ch101_client *client); + +void os_print_str(char *str); + +#endif /* CHIRPHAL_H */ diff --git a/drivers/iio/proximity/inv_ch101/src/i2c_hal.c b/drivers/iio/proximity/inv_ch101/src/i2c_hal.c new file mode 100644 index 000000000000..65d6a9210b51 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/i2c_hal.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#include "i2c_hal.h" +#include "chirp_hal.h" +#include "init_driver.h" +#include "../ch101_client.h" + + +unsigned long i2c_master_read_register(int bus_index, unsigned char Address, + unsigned char RegAddr, unsigned short RegLen, + unsigned char *RegValue) +{ + int res = 0; + struct ch101_client *data = get_chirp_data(); + const void *client = data->bus[bus_index].i2c_client; + +// printf("%s: Address: %02x, RegAddr: %02x, RegLen: %d\n", +// __func__, (u16)Address, (u8)RegAddr, (u16)RegLen); + + res = data->cbk->read_reg((void *)client, (u16)Address, (u8)RegAddr, + (u16)RegLen, (u8 *)RegValue); + + if (res) + printf("%s: res: %d", __func__, res); + +// { +// int i; +// printf("Read Values: "); +// for (i = 0; i < (RegLen < 3 ? RegLen : 3); i++) +// printf(" %02x ", *(u8 *)(RegValue + i)); +// printf("\n"); +// } + + return res; +} + +unsigned long i2c_master_write_register(int bus_index, unsigned char Address, + unsigned char RegAddr, unsigned short RegLen, + unsigned char *RegValue) +{ + int res = 0; + struct ch101_client *data = get_chirp_data(); + const void *client = data->bus[bus_index].i2c_client; + +// printf("%s: Address: %02x, RegAddr: %02x, RegLen: %d\n", +// __func__, (u16)Address, (u8)RegAddr, (u16)RegLen); +// +// { +// int i; +// printf("Write Values: "); +// for (i = 0; i < (RegLen < 3 ? RegLen : 3); i++) +// printf(" %02x ", *(u8 *)(RegValue + i)); +// printf("\n"); +// } + + res = data->cbk->write_reg((void *)client, (u16)Address, (u8)RegAddr, + (u16)RegLen, (u8 *)RegValue); + + if (res) + printf("%s: res: %d", __func__, res); + + return res; +} + +unsigned long i2c_master_read_sync(int bus_index, unsigned char Address, + unsigned short RegLen, unsigned char *RegValue) +{ + int res = 0; + struct ch101_client *data = get_chirp_data(); + const void *client = data->bus[bus_index].i2c_client; + +// printf("%s: start\n", __func__); +// +// printf("%s: Address: %02x, RegAddr: %02x, RegLen: %d\n", +// __func__, (u16)Address, (u16)RegLen); + + res = data->cbk->read_sync((void *)client, (u16)Address, + (u16)RegLen, (u8 *)RegValue); + + if (res) + printf("%s: res: %d", __func__, res); + +// { +// int i; +// printf("Read Values: "); +// for (i = 0; i < (RegLen < 3 ? RegLen : 3); i++) +// printf(" %02x ", *(u8 *)(RegValue + i)); +// printf("\n"); +// } + + return (res == 0 ? RegLen : res); +} + +unsigned long i2c_master_write_sync(int bus_index, unsigned char Address, + unsigned short RegLen, unsigned char *RegValue) +{ + int res = 0; + struct ch101_client *data = get_chirp_data(); + const void *client = data->bus[bus_index].i2c_client; + +// printf("%s: Address: %02x, RegLen: %d\n", +// __func__, (u16)Address, (u16)RegLen); +// +// { +// int i; +// printf("Write Values: "); +// for (i = 0; i < (RegLen < 3 ? RegLen : 3); i++) +// printf(" %02x ", *(u8 *)(RegValue + i)); +// printf("\n"); +// } + + res = data->cbk->write_sync((void *)client, (u16)Address, + (u16)RegLen, (u8 *)RegValue); + + if (res) + printf("%s: res: %d", __func__, res); + + return (res == 0 ? RegLen : res); +} + +//////////////////////////////////////////////////////////////////////////////// +unsigned long i2c_master_read_register0(unsigned char Address, + unsigned char RegAddr, unsigned short RegLen, + unsigned char *RegValue) +{ + return i2c_master_read_register(0, Address, RegAddr, RegLen, RegValue); +} + +unsigned long i2c_master_read_register1(unsigned char Address, + unsigned char RegAddr, unsigned short RegLen, + unsigned char *RegValue) +{ + return i2c_master_read_register(1, Address, RegAddr, RegLen, RegValue); +} + +unsigned long i2c_master_read_register2(unsigned char Address, + unsigned char RegAddr, unsigned short RegLen, + unsigned char *RegValue) +{ + return i2c_master_read_register(2, Address, RegAddr, RegLen, RegValue); +} + +//////////////////////////////////////////////////////////////////////////////// +unsigned long i2c_master_write_register0(unsigned char Address, + unsigned char RegAddr, unsigned short RegLen, + unsigned char *RegValue) +{ + return i2c_master_write_register(0, Address, RegAddr, RegLen, RegValue); +} + +unsigned long i2c_master_write_register1(unsigned char Address, + unsigned char RegAddr, unsigned short RegLen, + unsigned char *RegValue) +{ + return i2c_master_write_register(1, Address, RegAddr, RegLen, RegValue); +} + +unsigned long i2c_master_write_register2(unsigned char Address, + unsigned char RegAddr, unsigned short RegLen, + unsigned char *RegValue) +{ + return i2c_master_write_register(2, Address, RegAddr, RegLen, RegValue); +} + +//////////////////////////////////////////////////////////////////////////////// +unsigned long i2c_master_read_register0_sync(unsigned char Address, + unsigned short RegLen, unsigned char *RegValue) +{ + return i2c_master_read_sync(0, Address, RegLen, RegValue); +} + +unsigned long i2c_master_read_register1_sync(unsigned char Address, + unsigned short RegLen, unsigned char *RegValue) +{ + return i2c_master_read_sync(1, Address, RegLen, RegValue); +} + +unsigned long i2c_master_read_register2_sync(unsigned char Address, + unsigned short RegLen, unsigned char *RegValue) +{ + return i2c_master_read_sync(2, Address, RegLen, RegValue); +} + +//////////////////////////////////////////////////////////////////////////////// +unsigned long i2c_master_read_register0_nb(unsigned char Address, + unsigned short RegLen, unsigned char *RegValue) +{ + return i2c_master_read_sync(0, Address, RegLen, RegValue); +} + +unsigned long i2c_master_read_register1_nb(unsigned char Address, + unsigned short RegLen, unsigned char *RegValue) +{ + return i2c_master_read_sync(1, Address, RegLen, RegValue); +} + +unsigned long i2c_master_read_register2_nb(unsigned char Address, + unsigned short RegLen, unsigned char *RegValue) +{ + return i2c_master_read_sync(2, Address, RegLen, RegValue); +} + +//////////////////////////////////////////////////////////////////////////////// +unsigned long i2c_master_write_register0_sync(unsigned char Address, + unsigned short RegLen, unsigned char *RegValue) +{ + return i2c_master_write_sync(0, Address, RegLen, RegValue); +} + +unsigned long i2c_master_write_register1_sync(unsigned char Address, + unsigned short RegLen, unsigned char *RegValue) +{ + return i2c_master_write_sync(1, Address, RegLen, RegValue); +} + +unsigned long i2c_master_write_register2_sync(unsigned char Address, + unsigned short RegLen, unsigned char *RegValue) +{ + return i2c_master_write_sync(2, Address, RegLen, RegValue); +} + +void i2c_master_initialize0(void) +{ +} + +void i2c_master_initialize1(void) +{ +} + +void i2c_master_initialize2(void) +{ +} + +void i2c_master_init(void) +{ + i2c_master_initialize0(); + i2c_master_initialize1(); + i2c_master_initialize2(); +} + +void ext_int_init(void) +{ +} + diff --git a/drivers/iio/proximity/inv_ch101/src/i2c_hal.h b/drivers/iio/proximity/inv_ch101/src/i2c_hal.h new file mode 100644 index 000000000000..06ad0125317d --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/i2c_hal.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef I2CHAL_H +#define I2CHAL_H + +unsigned long i2c_master_read_register0(unsigned char Address, + unsigned char RegisterAddr, unsigned short RegisterLen, + unsigned char *RegisterValue); +unsigned long i2c_master_read_register1(unsigned char Address, + unsigned char RegisterAddr, unsigned short RegisterLen, + unsigned char *RegisterValue); +unsigned long i2c_master_read_register2(unsigned char Address, + unsigned char RegisterAddr, unsigned short RegisterLen, + unsigned char *RegisterValue); + +unsigned long i2c_master_read_register0_sync(unsigned char Address, + unsigned short RegisterLen, unsigned char *RegisterValue); +unsigned long i2c_master_read_register1_sync(unsigned char Address, + unsigned short RegisterLen, unsigned char *RegisterValue); +unsigned long i2c_master_read_register2_sync(unsigned char Address, + unsigned short RegisterLen, unsigned char *RegisterValue); + +unsigned long i2c_master_read_register0_nb(unsigned char Address, + unsigned short RegisterLen, unsigned char *RegisterValue); +unsigned long i2c_master_read_register1_nb(unsigned char Address, + unsigned short RegisterLen, unsigned char *RegisterValue); +unsigned long i2c_master_read_register2_nb(unsigned char Address, + unsigned short RegisterLen, unsigned char *RegisterValue); + +unsigned long i2c_master_write_register0(unsigned char Address, + unsigned char RegisterAddr, unsigned short RegisterLen, + unsigned char *RegisterValue); +unsigned long i2c_master_write_register1(unsigned char Address, + unsigned char RegisterAddr, unsigned short RegisterLen, + unsigned char *RegisterValue); +unsigned long i2c_master_write_register2(unsigned char Address, + unsigned char RegisterAddr, unsigned short RegisterLen, + unsigned char *RegisterValue); + +unsigned long i2c_master_write_register0_sync(unsigned char Address, + unsigned short RegisterLen, unsigned char *RegisterValue); +unsigned long i2c_master_write_register1_sync(unsigned char Address, + unsigned short RegisterLen, unsigned char *RegisterValue); +unsigned long i2c_master_write_register2_sync(unsigned char Address, + unsigned short RegisterLen, unsigned char *RegisterValue); + +void i2c_master_initialize0(void); +void i2c_master_initialize1(void); +void i2c_master_initialize2(void); +void i2c_master_init(void); + +void ext_int_init(void); + +#endif /* I2CHAL_H */ diff --git a/drivers/iio/proximity/inv_ch101/src/idlprotocol.h b/drivers/iio/proximity/inv_ch101/src/idlprotocol.h new file mode 100644 index 000000000000..f54796ba1860 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/idlprotocol.h @@ -0,0 +1,259 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef IDLPROTOCOL_H_ +#define IDLPROTOCOL_H_ + +#include + +#define IDL_PACKET_DEFAULTDATASIZE 1 + +#define IDL_PACKET_INVALID 0x00 +#define IDL_PACKET_FWVERSION 0x01 +#define IDL_PACKET_SINGLEREGREAD 0x02 +#define IDL_PACKET_SINGLEREGWRITE 0x03 +#define IDL_PACKET_MULREGREAD 0x04 +#define IDL_PACKET_MULREGWRITE 0x05 +#define IDL_PACKET_RESET 0x06 +#define IDL_PACKET_OPENSENSOR 0x07 +#define IDL_PACKET_CLOSESENSOR 0x08 +#define IDL_PACKET_ENABLEDATABUF 0x09 +#define IDL_PACKET_GETDATABUF 0x0A +#define IDL_PACKET_DISABLEDATABUF 0x0B +#define IDL_PACKET_RAWREAD 0x0C +#define IDL_PACKET_RAWWRITE 0x0D +#define IDL_PACKET_OPENSENSORSEC 0x0E +#define IDL_PACKET_CUSTOMSENSINIT 0x0F + +#define IDL_SENSORID_ERROR 0x00 +#define IDL_SENSORID_OK 0x01 + +#define IDL_INTERFACEID_I2C 0 +#define IDL_INTERFACEID_SPI1 1 +#define IDL_INTERFACEID_SPI2 2 + +#define IDL_PACKET_RESULT_ERROR 0x00 + +#define IDL_PACKET_RAWREADSIZE 9 //max size +#define IDL_PACKET_RAWWRITESIZE 5 //max size + +/* HEADER */ +struct IDLP_HEADER { + u8 size; + u8 size1; + u8 packetid; +}; + +/* FWVERSION */ +struct IDLPREQ_FWVERSION { + struct IDLP_HEADER header; +}; + +struct IDLPRSP_FWVERSION { + struct IDLP_HEADER header; + u8 major; + u8 minor; + u8 build; +}; + +/* ** RESET * */ +struct IDLPREQ_RESET { + struct IDLP_HEADER header; +}; + +struct IDLPRSP_RESET { + struct IDLP_HEADER header; +}; + +/* REGREAD */ +struct IDLPREQ_REGREAD { + struct IDLP_HEADER header; + u8 sensorid; + u8 addr; +}; + +struct IDLPRSP_REGREAD { + struct IDLP_HEADER header; + u8 sensorid; + u8 addr; + u8 value; +}; + +/* REGWRITE */ +struct IDLPREQ_REGWRITE { + struct IDLP_HEADER header; + u8 sensorid; + u8 addr; + u8 value; +}; + +struct IDLPRSP_REGWRITE { + struct IDLP_HEADER header; + u8 sensorid; + u8 addr; + u8 result; +}; + +/* OPENSENSOR */ +struct IDLPREQ_OPENSENSOR { + struct IDLP_HEADER header; + u8 interfaceid; + u8 intopt; + u8 options; +}; + +struct IDLPREQ_OPENSENSORSEC { + struct IDLP_HEADER header; + u8 interfaceid; + u8 intopt; + u8 options; + u8 options1; //press +}; + +struct IDLPRSP_OPENSENSOR { + struct IDLP_HEADER header; + u8 sensorid; +}; + +/* CLOSESENSOR */ +struct IDLPREQ_CLOSESENSOR { + struct IDLP_HEADER header; + u8 sensorid; +}; + +struct tag_idlpacket_rsp_closesensor { + struct IDLP_HEADER header; + u8 result; +} IDLPRSP_CLOSESENSOR; + +/* ENABLEDATABUF */ +struct IDLPREQ_ENABLEDATABUF { + struct IDLP_HEADER header; + u8 sensorid; + u8 intregaddr; + u8 intregdata; + u8 buffersize0; + u8 buffersize1; + u8 regscount; +// u8 regs[248]; +}; + +struct IDLPRSP_ENABLEDATABUF { + struct IDLP_HEADER header; + u8 sensorid; + u8 result; +}; + +/* GETDATABUF */ +struct IDLPREQ_GETDATABUF { + struct IDLP_HEADER header; + u8 sensorid; +}; + +#define MAXDATABUF 16002//3600//250//1008 //250 +struct IDLPRSP_GETDATABUF { + struct IDLP_HEADER header; + u8 sensorid; + u8 eom; + u8 data[MAXDATABUF]; +}; + +/* DISABLEDATABUF */ +struct IDLPREQ_DISABLEDATABUF { + struct IDLP_HEADER header; + u8 sensorid; +}; + +struct tag_idlpacket_rsp_disabledatabuf { + struct IDLP_HEADER header; + u8 sensorid; + u8 result; +}; + +/* MULREGREAD */ +struct IDLPREQ_MULREGREAD { + struct IDLP_HEADER header; + u8 sensorid; + u8 addr; + u8 stride; + u8 size; +}; + +struct IDLPRSP_MULREGREAD { + struct IDLP_HEADER header; + u8 sensorid; + u8 addr; + u8 stride; + u8 size; + u8 values[250]; +}; + +/* MULREGWRITE */ +struct IDLPREQ_MULREGWRITE { + struct IDLP_HEADER header; + u8 sensorid; + u8 addr; + u8 stride; + u8 size; +// u8 values[250]; +}; + +struct IDLPRSP_MULREGWRITE { + struct IDLP_HEADER header; + u8 sensorid; + u8 addr; + u8 stride; + u8 size; +}; + +struct IDLPREQ_RAWWRITE { + struct IDLP_HEADER header; + u8 sensorid; + u8 addr; + u8 stride; + u8 size; + u8 values[IDL_PACKET_RAWWRITESIZE]; +}; + +struct IDLPRSP_RAWWRITE { + struct IDLP_HEADER header; + u8 sensorid; + u8 addr; + u8 stride; + u8 size; + u8 result; +}; + +struct IDLPREQ_RAWREAD { + struct IDLP_HEADER header; + u8 sensorid; + u8 addr; + u8 stride; + u8 size; +}; + +struct IDLPRSP_RAWREAD { + struct IDLP_HEADER header; + u8 sensorid; + u8 addr; + u8 stride; + u8 size; + u8 values[IDL_PACKET_RAWREADSIZE]; +}; + +#endif /* IDLPROTOCOL_H_ */ diff --git a/drivers/iio/proximity/inv_ch101/src/init_driver.c b/drivers/iio/proximity/inv_ch101/src/init_driver.c new file mode 100644 index 000000000000..54189edb359e --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/init_driver.c @@ -0,0 +1,1291 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#include "chbsp_init.h" +#include "chirp_bsp.h" +#include "chirp_hal.h" +#include "i2c_hal.h" +#include "soniclib.h" +#include "init_driver.h" +#include "../ch101_client.h" + +/* Forward declarations */ +static bool check_sensor(int reg, ioport_pin_t prog_pin, ioport_pin_t int_pin); +static void sensor_int_callback(struct ch_group_t *grp_ptr, u8 dev_num); +static void set_ch101_pitch_catch_config(void); +static u8 display_config_info(struct ch_dev_t *dev_ptr); +static u8 handle_data_ready(struct ch_group_t *grp_ptr); +static void trigger_driver(void); +static void show_config(void); + + +struct chirp_drv { + bool driver_active; + u32 active_devices; + u32 data_ready_devices; + u8 ch101_pitch; + u16 chirp_odr_ms; +}; + +struct chirp_drv drv_data = { 0 }; + +/* Task flag word + * This variable contains the DATA_READY_FLAG and IQ_READY_FLAG bit flags + * that are set in I/O processing routines. The flags are checked in the + * main() loop and, if set, will cause an appropriate handler function to + * be called to process sensor data. + */ +static u32 taskflags; + +/* Chirp application version */ +#define APP_VERSION_MAJOR 1 +#define APP_VERSION_MINOR 0 + +#define CH_PART_NUMBER 101 + +/* Select sensor firmware to use + * The sensor firmware is specified during the call to ch_init(), by + * giving the name (address) of the firmware initialization function + * that will be called. + */ + +/* SHORT RANGE OPTION: + * Uncomment the following line to use different sensor f/w optimized for + * short range. The short range firmware has 4 times the resolution, but + * only 1/4 the maximum range. If you use this option, you should redefine + * the CHIRP_SENSOR_MAX_RANGE_MM symbol, below, to 250mm or less. + */ + +//#define USE_RANGE /* use range firmware */ +//#define USE_SHORT_RANGE /* use short-range firmware */ +#ifdef USE_RANGE + +#ifndef USE_SHORT_RANGE +/* use CH101 GPR OPEN firmware (normal) */ +#define CHIRP_SENSOR_FW_INIT_FUNC ch101_gpr_open_init +#else +/* use CH101 GPR SR OPEN firmware (short range) */ +#define CHIRP_SENSOR_FW_INIT_FUNC ch101_gpr_sr_open_init +#endif + +#else + +#define CHIRP_SENSOR_FW_INIT_FUNC ch101_gpr_init + +#endif + +#define INT_LINE_LATCH_TIME_US 50 /* latch time in microseconds */ + + +static int total_time_ms; + +static bool chirp_present[CHIRP_MAX_NUM_SENSORS] = { 0 }; +static u8 connected_sensor_array[CHIRP_MAX_NUM_SENSORS] = { 0 }; +static int num_connected_device; +static int num_connected_ch101_device; +static int num_connected_ch201_device; + +/* Array of interrupts timestamp in ms*/ +static u64 chirp_timestamps_ms[CHIRP_MAX_NUM_SENSORS] = { 0 }; + +/* Array of structs to hold measurement data, one for each possible device */ +static struct chirp_data_t chirp_data[CHIRP_MAX_NUM_SENSORS] = { { 0 } }; +static int chirp_data_mode[CHIRP_MAX_NUM_SENSORS]; + +/* Array of ch_dev_t device descriptors, one for each possible device */ +static struct ch_dev_t chirp_devices[CHIRP_MAX_NUM_SENSORS] = { { 0 } }; + +/* Configuration structure for group of sensors */ +static struct ch_group_t chirp_group = { 0 }; + +/* Chirp sensor group pointer */ +static struct ch_group_t *sensor_group_ptr; + +static struct ch101_buffer *_ch101_buffer; +static struct ch101_client *_ch101_data; + +void set_chirp_buffer(struct ch101_buffer *buffer) +{ + _ch101_buffer = buffer; +} + +struct ch101_buffer *get_chirp_buffer(void) +{ + return _ch101_buffer; +} + +void set_chirp_data(struct ch101_client *data) +{ + _ch101_data = data; + + if (data) + set_chirp_gpios(data); +} + +struct ch101_client *get_chirp_data(void) +{ + return _ch101_data; +} + +int find_sensors(void) +{ + int i; + bool find = false; + enum ioport_direction dir; + bool level; + + ioport_set_pin_dir(CHIRP_RST, IOPORT_DIR_OUTPUT); //reset=output + ioport_set_pin_level(CHIRP_RST, IOPORT_PIN_LEVEL_HIGH); //reset=H + + printf("%s: max devices: %x\n", __func__, + (u32)ARRAY_SIZE(chirp_pin_prog)); + + /* check sensors*/ + for (i = 0; i < ARRAY_SIZE(chirp_pin_prog); i++) { + if (chirp_pin_enabled[i]) + chirp_present[i] = check_sensor(chirp_i2c_buses[i], + chirp_pin_prog[i], chirp_pin_io[i]); + find |= chirp_present[i]; + } + + dir = ioport_get_pin_dir(CHIRP_RST); + level = ioport_get_pin_level(CHIRP_RST); + printf("%s: Reset - dir: %d level: %d\n", __func__, dir, level); + + return find ? 0 : -ENODEV; +} + +static int detect_prog_read(struct ch_dev_t *dev_ptr, u8 reg_addr, u16 *data) +{ + u8 nbytes = CH_PROG_SIZEOF(reg_addr); + + u8 read_data[2]; + u8 message[1] = { 0x7F & reg_addr }; + + int ch_err = chdrv_prog_i2c_write(dev_ptr, message, sizeof(message)); + + if (!ch_err) + ch_err = chdrv_prog_i2c_read(dev_ptr, read_data, nbytes); + + if (!ch_err) { + *data = read_data[0]; + if (nbytes > 1) + *data |= (((u16)read_data[1]) << 8); + } + + *data = (0x0a) | ((0x02) << 8); + + return ch_err; +} + +static int detect_sensors(struct ch_group_t *grp_ptr) +{ + int ch_err = 0; + int found = 0; + u16 prog_stat = UINT16_MAX; + u8 i = 0; + + printf("%s\n", __func__); + + for (i = 0; i < grp_ptr->num_ports; i++) { + struct ch_dev_t *dev_ptr = grp_ptr->device[i]; + + chbsp_program_enable(dev_ptr); // assert PROG pin + + found = chdrv_prog_ping(dev_ptr); // if device found + printf("found: 0x%02X\n", found); + + ch_err = detect_prog_read(dev_ptr, CH_PROG_REG_STAT, + &prog_stat); + if (!ch_err) + printf("PROG_STAT: 0x%02X\n", prog_stat); + + chbsp_program_disable(dev_ptr); // de-assert PROG pin + } + return ch_err; +} + +int is_sensor_connected(int ind) +{ + struct ch_group_t *grp_ptr = &chirp_group; + struct ch_dev_t *dev_ptr = ch_get_dev_ptr(grp_ptr, ind); + + if (ch_sensor_is_connected(dev_ptr)) + return dev_ptr->op_frequency; + else + return 0; +} + +static struct ch_group_t *init_group(void) +{ + struct ch_group_t *grp_ptr = &chirp_group; + u8 chirp_error = 0; + u8 num_ports = 0; + u8 dev_num = 0; + +/* Initialize board hardware functions + * This call to the board support package (BSP) performs all necessary + * hardware initialization for the application to run on this board. + * This includes setting up memory regions, initialization clocks and + * peripherals (including I2C and serial port), and any + * processor-specific startup sequences. + * + * The chbsp_board_init() function also initializes fields within the + * sensor group descriptor, including number of supported sensors and + * the RTC clock calibration pulse length. + */ + + /* Make local copy of group pointer */ + sensor_group_ptr = grp_ptr; + + printf("%s: sensor_group_ptr: %p\n", __func__, sensor_group_ptr); + + chbsp_board_init(grp_ptr); + + printf("CH-%d driver", CH_PART_NUMBER); + printf(" Version: %u.%u\n\n", + APP_VERSION_MAJOR, APP_VERSION_MINOR); + printf(" num_ports: %d\n", grp_ptr->num_ports); + printf(" num_i2c_buses: %d\n", grp_ptr->num_i2c_buses); + + /* Get the number of (possible) sensor devices on the board + * Set by the BSP during chbsp_board_init() + */ + num_ports = ch_get_num_ports(grp_ptr); + +/* Initialize sensor descriptors. + * This loop initializes each (possible) sensor's ch_dev_t descriptor, + * although we don't yet know if a sensor is actually connected. + * + * The call to ch_init() specifies the sensor descriptor, the sensor + * group it will be added to, the device number within the group, and + * the sensor firmware initialization routine that will be used. + * (The sensor firmware selection effectively specifies whether it is + * a CH101 or CH201 sensor, as well as the exact feature set.) + */ + printf("Initializing sensor(s)... "); + num_connected_device = 0; + num_connected_ch101_device = 0; + num_connected_ch201_device = 0; + + for (dev_num = 0; dev_num < num_ports; dev_num++) { + if (chirp_pin_enabled[dev_num]) { + // init struct in array + struct ch_dev_t *dev_ptr = &chirp_devices[dev_num]; + + /* Init device descriptor + * Note that this assumes all sensors will use the same + * sensor firmware. + */ + chirp_error |= ch_init(dev_ptr, grp_ptr, dev_num, + CHIRP_SENSOR_FW_INIT_FUNC); + dev_ptr->sensor_connected = chirp_present[dev_num]; + printf("dev_num: %d, sensor_connected: %d\n", + dev_num, dev_ptr->sensor_connected); + + if (dev_ptr->sensor_connected) { + connected_sensor_array[num_connected_device++] = + dev_num; + + if (dev_num < 3) + num_connected_ch101_device++; + else + num_connected_ch201_device++; + } + } + } + + return grp_ptr; +} + +void test_detect(void) +{ + struct ch_group_t *grp_ptr = init_group(); + + detect_sensors(grp_ptr); + + printf("start group... "); + ch_group_start(grp_ptr); + printf("end group... "); +} + +void init_driver(void) +{ + u8 chirp_error = 0; + u8 num_ports = 0; + u8 dev_num = 0; + + struct ch_group_t *grp_ptr = init_group(); + + /* Start all sensors. + * The ch_group_start() function will search each port (that was + * initialized above) for a sensor. If it finds one, it programs it(with + * the firmware specified above during ch_init()) and waits for it to + * perform a self-calibration step. Then, once it has found all the + * sensors, ch_group_start() completes a timing reference calibration by + * applying a pulse of known length to the sensor's INT line. + */ + if (chirp_error == 0) { + printf("starting group... "); + chirp_error = ch_group_start(grp_ptr); + } + + if (chirp_error == 0) + printf("OK\n"); + else + printf("starting group FAILED: %d\n", chirp_error); + printf("\n"); + + /* Get and display the initialization results for each connected sensor. + * This loop checks each device number in the sensor group to determine + * if a sensor is actually connected. If so, it makes a series of + * function calls to get different operating values, including the + * operating frequency, clock calibration values, and firmware version. + */ + num_ports = ch_get_num_ports(grp_ptr); + + printf("num_ports: %d\n", num_ports); + printf("Sensor\tType \t Freq\t\t RTC Cal \tFirmware\n"); + + for (dev_num = 0; dev_num < num_ports; dev_num++) { + struct ch_dev_t *dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + + if (ch_sensor_is_connected(dev_ptr)) { + printf("num_rx_sample=%d\n", + ch_get_num_samples(dev_ptr)); + printf("%d\tCH%d\t %u Hz\t%u@%u ms\t%s\n", dev_num, + ch_get_part_number(dev_ptr), + ch_get_frequency(dev_ptr), + ch_get_rtc_cal_result(dev_ptr), + ch_get_rtc_cal_pulselength(dev_ptr), + ch_get_fw_version_string(dev_ptr)); + } + } + printf("\n"); + + // Register callback function to be called when Chirp sensor interrupts + ch_io_int_callback_set(grp_ptr, sensor_int_callback); +} + +void start_driver(int period_ms, int time_ms) +{ + printf("%s\n", __func__); + total_time_ms = time_ms; + drv_data.chirp_odr_ms = period_ms; + trigger_driver(); +} + +void stop_driver(void) +{ + printf("%s\n", __func__); + drv_data.driver_active = 0; +} + +static void ext_int_handler(u32 gpio_pin) +{ + struct ch_group_t *grp_ptr = &chirp_group; + ch_io_int_callback_t func_ptr = + sensor_group_ptr ? sensor_group_ptr->io_int_callback : + NULL; + u8 pin_found = 0; + u8 idx; + +// printf("%s: pin: %d func_ptr: %px\n", +// __func__, gpio_pin, func_ptr); + + if (sensor_group_ptr == NULL) { + sensor_group_ptr = grp_ptr; + func_ptr = sensor_group_ptr->io_int_callback; + printf("%s: pin: %d func_ptr: %p\n", + __func__, gpio_pin, func_ptr); + } + + /* Clear the interrupt */ + // set to low level + ioport_set_pin_level(gpio_pin, IOPORT_PIN_LEVEL_LOW); +// // set pin direction as output +// ioport_set_pin_dir(gpio_pin, IOPORT_DIR_OUTPUT); + + // Drive this INT line down for latching low via TI level shifter + // (TI TXB0104QPWRQ1) + chbsp_delay_us(INT_LINE_LATCH_TIME_US); + + // set pin direction as input + ioport_set_pin_dir(gpio_pin, IOPORT_DIR_INPUT); + + /* Check if this pin is a Chirp sensor, and notify application */ + if (func_ptr != NULL) { + for (idx = 0; idx < CHBSP_MAX_DEVICES; idx++) { +// printf("%s: idx: %d chirp_pin_io[idx]: %d\n", +// __func__, idx, chirp_pin_io[idx]); + + if (gpio_pin == chirp_pin_io[idx]) { + pin_found = 1; + break; + } + } + + if (pin_found) { + if (func_ptr != NULL) { + // Call application callback function - pass I/O + // index to identify interrupting device + (*func_ptr)(sensor_group_ptr, idx); + } + } else { + printf("%s: %d pin not found\n", __func__, gpio_pin); + } + } +} + +void ext_ChirpINT0_handler(int index) +{ + ext_int_handler(chirp_pin_io[index]); +} + +static bool check_sensor(int reg, ioport_pin_t prog_pin, ioport_pin_t int_pin) +{ + u8 sig_bytes[2]; + bool good; + const char *ch; + unsigned char r; + bool level; + + sig_bytes[0] = 0; + sig_bytes[1] = 0; + + /* check sensor */ + ioport_set_pin_dir(prog_pin, IOPORT_DIR_OUTPUT); + ioport_set_pin_level(prog_pin, IOPORT_PIN_LEVEL_HIGH); + + r = CH_I2C_ADDR_PROG; + + printf("%s: prog_pin: %d\n", __func__, prog_pin); + + if (reg == 0) + i2c_master_read_register0(r, 0x00, 2, sig_bytes); + else if (reg == 1) + i2c_master_read_register1(r, 0x00, 2, sig_bytes); + else if (reg == 2) + i2c_master_read_register2(r, 0x00, 2, sig_bytes); + + good = ((sig_bytes[0] == CH_SIG_BYTE_0) && + (sig_bytes[1] == CH_SIG_BYTE_1)); + + printf("%s: addr: %x sig: %02x %02x\n", + __func__, r, sig_bytes[0], sig_bytes[1]); + + printf("%s: good: %d\n", __func__, good); + + ch = ""; + if (prog_pin == CHIRP0_PROG_0) + ch = "CH-0"; + else if (prog_pin == CHIRP0_PROG_1) + ch = "CH-1"; + else if (prog_pin == CHIRP0_PROG_2) + ch = "CH-2"; + else if (prog_pin == CHIRP1_PROG_0) + ch = "CH-3"; + else if (prog_pin == CHIRP1_PROG_1) + ch = "CH-4"; + else if (prog_pin == CHIRP1_PROG_2) + ch = "CH-5"; + else if (prog_pin == CHIRP2_PROG_0) + ch = "CH-6"; + else if (prog_pin == CHIRP2_PROG_1) + ch = "CH-7"; + else if (prog_pin == CHIRP2_PROG_2) + ch = "CH-8"; + + printf("Chirp sensor I2C_%d %s %02X %s found\n", reg, ch, prog_pin, + good ? "" : " not"); + + ioport_set_pin_level(prog_pin, IOPORT_PIN_LEVEL_LOW); + + /* check int pin */ + ioport_set_pin_dir(int_pin, IOPORT_DIR_OUTPUT); + ioport_set_pin_level(int_pin, IOPORT_PIN_LEVEL_HIGH); + + chbsp_delay_us(10); // Pulse needs to be a minimum of 800ns long + + level = ioport_get_pin_level(int_pin); + if (!level) { + printf("Chirp sensor I2C_%d %s INT %d error\n", + reg, ch, int_pin); + good = false; + + } + ioport_set_pin_level(int_pin, IOPORT_PIN_LEVEL_LOW); + + return good; +} + +static void set_ch101_pitch_catch_config(void) +{ + u8 num_ports; + u8 dev_num; + struct ch_group_t *grp_ptr; + u8 last_ch101_pitch; + int count; + u8 ret_val; + bool excluded = false; + + grp_ptr = &chirp_group; + num_ports = ch_get_num_ports(grp_ptr); + last_ch101_pitch = drv_data.ch101_pitch; + + //printf("%s: last_ch101_pitch: %d\n", __func__, last_ch101_pitch); + + count = 0; + if (num_connected_ch101_device) { + do { + drv_data.ch101_pitch = (drv_data.ch101_pitch + 1) + % num_connected_ch101_device; + } while (excluded == true && ++count < CHIRP_MAX_NUM_SENSORS); + + if (count >= CHIRP_MAX_NUM_SENSORS) + return; + + for (dev_num = 0; dev_num < 3; dev_num++) { + // init struct in array + enum ch_mode_t mode; + struct ch_dev_t *dev_ptr = &chirp_devices[dev_num]; + + if (dev_num == + connected_sensor_array[drv_data.ch101_pitch]) + mode = CH_MODE_TRIGGERED_TX_RX; + else if (dev_num == + connected_sensor_array[last_ch101_pitch]) + mode = CH_MODE_TRIGGERED_RX_ONLY; + else + continue; + + ret_val = ch_set_mode(dev_ptr, mode); + if (!ret_val) + dev_ptr->mode = mode; + } + } + + if (num_connected_ch201_device) { + for (dev_num = 3; dev_num < num_ports; dev_num++) { + // init struct in array + enum ch_mode_t mode; + struct ch_dev_t *dev_ptr = &chirp_devices[dev_num]; + + mode = CH_MODE_TRIGGERED_TX_RX; + //printf("%s: mode: %02x\n", __func__, mode); + + ret_val = ch_set_mode(dev_ptr, mode); + if (!ret_val) + dev_ptr->mode = mode; + } + } + show_config(); +} + +/* + * display_config_info() - display the configuration values for a sensor + * + * This function displays the current configuration settings for an individual + * sensor. The operating mode, maximum range, and static target rejection + * range (if used) are displayed. + * + * For CH201 sensors only, the multiple detection threshold values are also + * displayed. + */ +static u8 display_config_info(struct ch_dev_t *dev_ptr) +{ + struct ch_config_t read_config; + struct ch_thresh_t *tr; + u8 chirp_error; + int i = 0; + + u8 dev_num = ch_get_dev_num(dev_ptr); + + /* Read configuration values for the device into ch_config_t structure*/ + chirp_error = ch_get_config(dev_ptr, &read_config); + + if (!chirp_error) { + char *mode_string; + + switch (read_config.mode) { + case CH_MODE_IDLE: + mode_string = "IDLE"; + break; + case CH_MODE_FREERUN: + mode_string = "FREERUN"; + break; + case CH_MODE_TRIGGERED_TX_RX: + mode_string = "TRIGGERED_TX_RX"; + break; + case CH_MODE_TRIGGERED_RX_ONLY: + mode_string = "TRIGGERED_RX_ONLY"; + break; + default: + mode_string = "UNKNOWN"; + } + + /* Display sensor number, mode and max range */ + printf("Sensor %d:\tmax_range=%dmm \tmode=%s ", dev_num, + read_config.max_range, mode_string); + + /* Display static target rejection range, if used */ + if (read_config.static_range != 0) { + printf("static_range=%d samples", + read_config.static_range); + } + + /* Display detection thresholds (only supported on CH201) */ + if (ch_get_part_number(dev_ptr) == CH201_PART_NUMBER) { + struct ch_thresholds_t read_thresholds; + + /* Get threshold values in structure */ + chirp_error = ch_get_thresholds(dev_ptr, + &read_thresholds); + + if (!chirp_error) { + printf("\n Detection thresholds:\n"); + for (i = 0; i < CH_NUM_THRESHOLDS; i++) { + tr = &read_thresholds.threshold[i]; + printf(" %d\tstart: %2d\tlevel: %d\n", + i, + tr->start_sample, + tr->level); + } + } else { + printf(" Device %d: Error ch_get_thresholds()", + dev_num); + } + } + printf("\n"); + + } else { + printf(" Device %d: Error during ch_get_config()\n", dev_num); + } + + return chirp_error; +} + +static void show_config(void) +{ + struct ch_group_t *grp_ptr = &chirp_group; + u8 num_ports = ch_get_num_ports(grp_ptr); + u8 dev_num = 0; + + printf("Sensors configuration\n"); + for (dev_num = 0; dev_num < num_ports; dev_num++) { + struct ch_dev_t *dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + + if (ch_sensor_is_connected(dev_ptr)) { + /* Read back and display config settings */ + display_config_info(dev_ptr); + } + } +} + +void config_driver(void) +{ + int num_samples; + struct ch_group_t *grp_ptr = &chirp_group; + u8 chirp_error = 0; + u8 num_ports = 0; + u8 dev_num = 0; + + num_ports = ch_get_num_ports(grp_ptr); + + printf("Configuring sensors...\n"); + for (dev_num = 0; dev_num < num_ports; dev_num++) { + struct ch_config_t dev_config; + struct ch_dev_t *dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + + if (ch_sensor_is_connected(dev_ptr)) { + /* Select sensor mode + * All connected sensors are placed in hardware + * triggered mode. The first connected (lowest numbered) + * sensor will transmit and receive, all others will + * only receive. + */ + + // add to active device bit mask + drv_data.active_devices |= (1 << dev_num); + dev_config.mode = CH_MODE_TRIGGERED_RX_ONLY; + + /* Init config structure with default values */ + dev_config.max_range = CHIRP_SENSOR_MAX_RANGE_MM; + dev_config.static_range = CHIRP_SENSOR_STATIC_RANGE; + dev_config.sample_interval = + CHIRP_SENSOR_SAMPLE_INTERVAL; + + /* Set detection thresholds (CH201 only) */ + dev_config.thresh_ptr = 0; + + /* Apply sensor configuration */ + chirp_error = ch_set_config(dev_ptr, &dev_config); + if (chirp_error) + printf("Device %d: Error ch_set_config()\n", + dev_num); + + num_samples = ch_get_num_samples(dev_ptr); + printf("ch101: get sample=%d\n", num_samples); + if (num_samples > MAX_RX_SAMPLES) { + ch_set_num_samples(dev_ptr, MAX_RX_SAMPLES); + dev_ptr->num_rx_samples = MAX_RX_SAMPLES; + } + + /* Enable sensor interrupt if using free-running mode + * Note that interrupt is automatically enabled if + * using triggered modes. + */ + if (!chirp_error && + dev_config.mode == CH_MODE_FREERUN) + chbsp_io_interrupt_enable(dev_ptr); + + /* Turn on an LED to indicate device connected */ + if (!chirp_error) + chbsp_led_on(dev_num); + } + } + + set_ch101_pitch_catch_config(); +} + +void set_complete(void) +{ + struct ch101_client *data = get_chirp_data(); + + if (data->cbk->data_complete) + data->cbk->data_complete(data); +} + +static void trigger_driver(void) +{ + u64 cur_time; + struct ch_group_t *grp_ptr = &chirp_group; + + u64 last_chirp_time_ms = 0; + u64 start_time = os_timestamp_ms(); + + printf("%s: start\n", __func__); + + taskflags = 0; + drv_data.driver_active = 1; + while (drv_data.driver_active) { + if (taskflags == 0) { + // 1 ms - put processor in low-power sleep mode + chbsp_proc_sleep(1); + cur_time = os_timestamp_ms(); + // 100 ms (10 Hz) + if (cur_time - last_chirp_time_ms >= + drv_data.chirp_odr_ms) { + ch_group_trigger(grp_ptr); + last_chirp_time_ms = os_timestamp_ms(); + } + } + + /* Check for sensor data-ready interrupt(s) */ + if (taskflags & DATA_READY_FLAG) { + printf("%s: taskflags: %02x\n", __func__, taskflags); + + // All sensors have interrupted - handle sensor data + // clear flag + taskflags &= ~DATA_READY_FLAG; + + // Disable interrupt unless in free-running mode + // It will automatically be re-enabled during the next + // trigger + chbsp_group_io_interrupt_disable(grp_ptr); + + // read and display measurement + handle_data_ready(grp_ptr); + drv_data.data_ready_devices = 0; + + set_ch101_pitch_catch_config(); + } + + cur_time = os_timestamp_ms(); + if (cur_time - start_time >= total_time_ms) + stop_driver(); + } + printf("%s: stop\n", __func__); +} + +void single_shot_driver(void) +{ + struct ch101_buffer *buffer = get_chirp_buffer(); + struct ch_group_t *grp_ptr = &chirp_group; + u8 dev_num = 0; + u8 count = 3; + bool data_ready = false; + + /* Register callback function to be called when sensor interrupts */ + ch_io_int_callback_set(grp_ptr, sensor_int_callback); + + printf("%s: begin\n", __func__); + + taskflags = 0; + ch_group_trigger(grp_ptr); + + printf("%s: while\n", __func__); + + data_ready = false; + + while (--count > 0) { + chbsp_proc_sleep(10); // 10 ms - put processor in sleep mode + printf("%s: count: %d, taskflags: %02x\n", + __func__, count, taskflags); + + /* Check for sensor data-ready interrupt(s) */ + if ((taskflags & DATA_READY_FLAG) || (count == 1)) { + // All sensors have interrupted - handle sensor data + // clear flag + taskflags &= ~DATA_READY_FLAG; + + // Disable interrupt unless in free-running mode + // It will automatically be re-enabled during the next + // trigger + chbsp_group_io_interrupt_disable(grp_ptr); + + // read and display measurement + handle_data_ready(grp_ptr); + + data_ready = true; + break; + } + } + + set_ch101_pitch_catch_config(); + + if (data_ready) { + for (dev_num = 0; dev_num < ch_get_num_ports(grp_ptr); + dev_num++) { + struct ch_dev_t *dev_ptr = + ch_get_dev_ptr(grp_ptr, dev_num); + + if (ch_sensor_is_connected(dev_ptr)) { + buffer->distance[dev_num] = + chirp_data[dev_num].range / 32; //mm + buffer->amplitude[dev_num] = + chirp_data[dev_num].amplitude; + buffer->nb_samples[dev_num] = + chirp_data[dev_num].num_samples; + buffer->mode[dev_num] = + chirp_data_mode[dev_num]; + memcpy(buffer->iq_data[dev_num], + chirp_data[dev_num].iq_data, + chirp_data[dev_num].num_samples * + sizeof(struct ch101_iq_data)); + + printf("%s: %d: %d %d, %d\n", __func__, + dev_num, + buffer->distance[dev_num], + buffer->amplitude[dev_num], + buffer->mode[dev_num]); + } + } + + } else { + printf("%s: No data\n", __func__); + memset(buffer, 0, sizeof(*buffer)); + } + + set_complete(); + drv_data.data_ready_devices = 0; + + printf("%s: end\n\n", __func__); +} + +/* + * sensor_int_callback() - sensor interrupt callback routine + * + * This function is called by the board support package's interrupt handler for + * the sensor's INT line every time that the sensor interrupts. The device + * number parameter, dev_num, is used to identify the interrupting device + * within the sensor group. (Generally the device number is same as the port + * number used in the BSP to manage I/O pins, etc.) + * + * This callback function is registered by the call to ch_io_int_callback_set() + * in main(). + */ +static void sensor_int_callback(struct ch_group_t *grp_ptr, u8 dev_num) +{ + // time of interrupt in ms + chirp_timestamps_ms[dev_num] = os_timestamp_ms(); + + // add to data-ready bit mask + drv_data.data_ready_devices |= (1 << dev_num); + + printf("%s: dev: %d data_ready_devices: %02x active_devices: %02x\n", + __func__, + dev_num, drv_data.data_ready_devices, + drv_data.active_devices); + + if (drv_data.data_ready_devices == drv_data.active_devices) { + + /* All active sensors have interrupted + * after performing a measurement + */ + drv_data.data_ready_devices = 0; + + /* Set data-ready flag - it will be checked in main() loop */ + taskflags |= DATA_READY_FLAG; + } +} + +/* + * handle_data_ready() - get data from all sensors + * + * This routine is called from the main() loop after all sensors have + * interrupted. It shows how to read the sensor data once a measurement is + * complete. This routine always reads out the range and amplitude, and + * optionally performs either a blocking or non-blocking read of the raw I/Q + * data. See the comments in hello_chirp.h for information about the + * I/Q readout build options. + * + * If a blocking I/Q read is requested, this function will read the data from + * the sensor into the application's "chirp_data" structure for this device + * before returning. + * + * Optionally, if a I/Q blocking read is requested and the OUTPUT_IQ_DATA_CSV + * build symbol is defined, this function will output the full I/Q data as a + * series of comma-separated value pairs (Q, I), each on a separate line. This + * may be a useful step toward making the data available in an external + * application for analysis (e.g. by copying the CSV values into a spreadsheet + * program). + * + * If a non-blocking I/Q is read is initiated, a callback routine will be called + * when the operation is complete. The callback routine must have been + * registered using the ch_io_complete_callback_set function. + */ +static u8 handle_data_ready(struct ch_group_t *grp_ptr) +{ + u8 dev_num; + int error; + int num_samples = 0; + u16 start_sample = 0; + u8 ret_val = 0; + + /* Read and display data from each connected sensor + * This loop will write the sensor data to this application's + * "chirp_data" array. + * Each sensor has a separate chirp_data_t structure in that + * array, so the device number is used as an index. + */ + + for (dev_num = 0; dev_num < ch_get_num_ports(grp_ptr); dev_num++) { + struct ch_dev_t *dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + + if (ch_sensor_is_connected(dev_ptr)) { + /* Get measurement results from each connected sensor + * For sensor in transmit/receive mode, report one-way + * echo distance. For sensor(s) in receive-only mode, + * report direct one-way distance from transmitting + * sensor + */ + + printf("%s: dev_num: %d\n", __func__, dev_num); + + if (ch_get_mode(dev_ptr) == CH_MODE_TRIGGERED_RX_ONLY) { + chirp_data_mode[dev_num] = + CH_MODE_TRIGGERED_RX_ONLY; + error = ch_get_range(dev_ptr, + CH_RANGE_DIRECT, + &chirp_data[dev_num].range); + } else { + chirp_data_mode[dev_num] = + CH_MODE_TRIGGERED_TX_RX; + error = ch_get_range(dev_ptr, + CH_RANGE_ECHO_ONE_WAY, + &chirp_data[dev_num].range); + } + + printf("%s: range: %x\n", + __func__, chirp_data[dev_num].range); + + if (chirp_data[dev_num].range == CH_NO_TARGET) { + /* No target object was detected - no range + * value, no updated amplitude + */ + chirp_data[dev_num].amplitude = 0; + printf("Port %d: no target found", + dev_num); + /* Check if no target condition is due to i2c + * read error + */ + if (error) { + printf("Read error detected"); + continue; + } + } else { + /* Target object was successfully detected + * (range available). Get the new amplitude + * value - it's only updated if range + * was successfully measured. + */ + error = ch_get_amplitude(dev_ptr, + &chirp_data[dev_num].amplitude); + + printf("Port %d: Range: %d Amplitude: %6u ", + dev_num, chirp_data[dev_num].range / 32, + chirp_data[dev_num].amplitude); + if (error) { + printf("Read error detected"); + continue; + } + } + printf("\n\r"); + + /* Get number of active samples in this measurement */ + num_samples = ch_get_num_samples(dev_ptr); + if (num_samples > MAX_NB_SAMPLES) + num_samples = MAX_NB_SAMPLES; + chirp_data[dev_num].num_samples = num_samples; + + /* Read full IQ data from device into buffer or queue + * read request, based on build-time options + */ + +#ifdef READ_IQ_DATA_BLOCKING + /* Reading I/Q data in normal, blocking mode */ + if (dev_num < 3) { + error = ch_get_iq_data(dev_ptr, + chirp_data[dev_num].iq_data, start_sample, + num_samples, CH_IO_MODE_BLOCK); + + if (!error) { + printf("%4d IQ samples", num_samples); +// { +// struct ch_iq_sample_t *p = chirp_data[dev_num].iq_data; +// int i; +// for (i = 0; i < num_samples; i++, p++) +// printf(" %4d IQ: %4d %4d", i, p->i, p->q); +// } + } else { + printf("Error reading %d IQ samples", + num_samples); + } + printf("\n\r"); + } + +#elif defined(READ_IQ_DATA_NONBLOCK) + /* Reading I/Q data in non-blocking mode - + * queue a read operation + */ + printf("queuing %d IQ samples..", num_samples); + + error = ch_get_iq_data(dev_ptr, + chirp_data[dev_num].iq_data, + start_sample, num_samples, + CH_IO_MODE_NONBLOCK); + + if (!error) { + // record a pending non-blocking read + num_queued++; + printf("OK"); + } else { + printf("**ERROR**"); + } +#endif // IQ_DATA_NONBLOCK + + printf("\n\r"); + } + } + + return ret_val; +} + +#define TEST_WRITE_READ 0 + +#if TEST_WRITE_READ + +#define FINALTEST_MEM_TEST_DATA 1 +#define FINALTEST_MEM_TEST_PROG 1 +#define FINALTEST_MAX_MEM_ERRORS 1 + +/*!< CH-101 data transfer starting address register address. */ +#define CH101_PROG_ADDR 0x05 +/*!< CH-101 data transfer size register address. */ +#define CH101_PROG_CNT 0x07 + +//! Memory write/read test values +#define FINALTEST_MEM_NUM_PATTERNS (2) +#define FINALTEST_MEM_TEST_CHAR_1 (0x5A) +#define FINALTEST_MEM_TEST_CHAR_2 (0xA5) +#define FINALTEST_PROG_MEM_ADDR (0xF800) +#define FINALTEST_PROG_MEM_SIZE 128 //2048 +#define FINALTEST_DATA_MEM_ADDR (0x0200) +#define FINALTEST_DATA_MEM_SIZE 128 //2048 +#define FINALTEST_MEM_READ_TRANSFER_SIZE (256) + +static uint16_t finaltest_mem_pattern(struct ch_dev_t *self, uint16_t addr, + uint16_t num_bytes, uint8_t pattern_char, uint16_t *offset_ptr); + +static uint16_t finaltest_mem_test(struct ch_dev_t *self) +{ + uint8_t pattern_num; + uint16_t ret_val = 0; + uint16_t err_count = 0; + uint16_t err_offset = 0; + uint8_t pattern_chars[FINALTEST_MEM_NUM_PATTERNS] = { + FINALTEST_MEM_TEST_CHAR_1, FINALTEST_MEM_TEST_CHAR_2 + }; + +#if (FINALTEST_MEM_TEST_DATA) + printf("%s: FINALTEST_MEM_TEST_DATA\n", __func__); + + for (pattern_num = 0; pattern_num < + FINALTEST_MEM_NUM_PATTERNS; pattern_num++) + err_count += finaltest_mem_pattern(self, + FINALTEST_DATA_MEM_ADDR, + FINALTEST_DATA_MEM_SIZE, + pattern_chars[pattern_num], &err_offset); +#endif + +#if (FINALTEST_MEM_TEST_PROG) + printf("%s: FINALTEST_MEM_TEST_PROG\n", __func__); + + for (pattern_num = 0; pattern_num < + FINALTEST_MEM_NUM_PATTERNS; pattern_num++) { + + err_count += finaltest_mem_pattern(self, + FINALTEST_PROG_MEM_ADDR, FINALTEST_PROG_MEM_SIZE, + pattern_chars[pattern_num], &err_offset); + } +#endif + + if (err_count > FINALTEST_MAX_MEM_ERRORS) + ret_val = 1; // report error status + + return ret_val; +} + + +static uint16_t finaltest_mem_pattern(struct ch_dev_t *self, uint16_t addr, + uint16_t num_bytes, uint8_t pattern_char, uint16_t *offset_ptr) +{ + uint16_t err_count = 0; + uint16_t err_offset = 0; + uint8_t tx_buf[FINALTEST_PROG_MEM_SIZE];// note: assumes that prog & + // data memory regions are same size, or PROG is larger + uint8_t rx_buf[FINALTEST_PROG_MEM_SIZE]; + int i; + int ch_err = 0; + int num_transfers = + (num_bytes + (FINALTEST_MEM_READ_TRANSFER_SIZE - 1)) / + FINALTEST_MEM_READ_TRANSFER_SIZE; + int bytes_left = num_bytes; // remaining bytes to read + + chbsp_program_enable(self); // assert PROG pin + + // Fill buffer with pattern to write + for (i = 0; i < num_bytes; i++) + tx_buf[i] = pattern_char; + + // Write data to device + ch_err = chdrv_prog_mem_write(self, addr, tx_buf, num_bytes); + + printf("%s: chdrv_prog_mem_write: ch_err=%d", __func__, ch_err); + + // Read back data + if (!ch_err) { + for (i = 0; i < num_transfers; i++) { + int bytes_to_read; + + // read burst command + uint8_t message[] = { (0x80 | CH_PROG_REG_CTL), 0x09 }; + + if (bytes_left > FINALTEST_MEM_READ_TRANSFER_SIZE) + bytes_to_read = + FINALTEST_MEM_READ_TRANSFER_SIZE; + else + bytes_to_read = bytes_left; + + chdrv_prog_write(self, CH101_PROG_ADDR, + (addr + (i * FINALTEST_MEM_READ_TRANSFER_SIZE))); + chdrv_prog_write(self, CH101_PROG_CNT, + (FINALTEST_MEM_READ_TRANSFER_SIZE - 1)); + + ch_err = chdrv_prog_i2c_write(self, + message, sizeof(message)); + printf("%s: chdrv_prog_i2c_write: %d", + __func__, ch_err); + + ch_err |= chdrv_prog_i2c_read(self, &(rx_buf[i * + FINALTEST_MEM_READ_TRANSFER_SIZE]), bytes_to_read); + printf("%s: chdrv_prog_i2c_read: %d", __func__, ch_err); + + bytes_left -= bytes_to_read; + } + } + + // Check read data + if (!ch_err) { + for (i = 0; i < num_bytes; i++) { + if (rx_buf[i] != tx_buf[i]) { + err_count++; + if (err_offset == 0) + err_offset = i; + } + } + } + + if ((err_count != 0) && (offset_ptr != NULL)) + *offset_ptr = err_offset; + + //wdt_reset(); + // reset watchdog to avoid timeout (data write/read takes approx 100ms) + + printf("%s: err_count: %d", __func__, err_count); + + chbsp_program_disable(self); // de-assert PROG pin + + return err_count; +} + +void test_write_read(void) +{ + struct ch_group_t *grp_ptr = init_group(); + u8 num_ports = ch_get_num_ports(grp_ptr); + u8 dev_num = 0; + + printf("%s\n", __func__); + + ioport_set_pin_dir(CHIRP_RST, IOPORT_DIR_OUTPUT); + ioport_set_pin_level(CHIRP_RST, IOPORT_PIN_LEVEL_HIGH); + + /* check sensor */ + ioport_set_pin_dir(CHIRP0_PROG_0, IOPORT_DIR_OUTPUT); + ioport_set_pin_level(CHIRP0_PROG_0, IOPORT_PIN_LEVEL_HIGH); + + for (dev_num = 0; dev_num < num_ports; dev_num++) { + struct ch_dev_t *dev_ptr = ch_get_dev_ptr(grp_ptr, dev_num); + + printf("%s: ch_sensor_is_connected: %d\n", __func__, + ch_sensor_is_connected(dev_ptr)); + if (ch_sensor_is_connected(dev_ptr)) { + printf("%s: finaltest_mem_test\n", __func__); + finaltest_mem_test(dev_ptr); + } + } + + ioport_set_pin_level(CHIRP0_PROG_0, IOPORT_PIN_LEVEL_LOW); +} + +#else + +void test_write_read(void) +{ +} + +#endif diff --git a/drivers/iio/proximity/inv_ch101/src/init_driver.h b/drivers/iio/proximity/inv_ch101/src/init_driver.h new file mode 100644 index 000000000000..9b59ee8a4244 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/init_driver.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef INITDRIVER_H_ +#define INITDRIVER_H_ + +#include "soniclib.h" +#include "../ch101_client.h" + +#define READ_IQ_DATA_BLOCKING +#define IQ_DATA_MAX_NUM_SAMPLES CH101_MAX_NUM_SAMPLES // use CH201 I/Q size + +/* Bit flags used in main loop to check for completion of sensor I/O. */ +#define DATA_READY_FLAG (1 << 0) +#define IQ_READY_FLAG (1 << 1) + +#define MAX_RX_SAMPLES 450 +#define MAX_NB_SAMPLES 450 + +/* Define configuration settings for the Chirp sensors. + * The following symbols define configuration values that are used to + * initialize the ch_config_t structure passed during the ch_set_config() call. + */ + +/* maximum range, in mm */ +#define CHIRP_SENSOR_MAX_RANGE_MM 1000 +/* static target rejection sample range, in samples (0=disabled) */ +#define CHIRP_SENSOR_STATIC_RANGE 0 +/* internal sample interval -NOT USED IF TRIGGERED */ +#define CHIRP_SENSOR_SAMPLE_INTERVAL 0 + +struct chirp_data_t { + // from ch_get_range() + u32 range; + // from ch_get_amplitude() + u16 amplitude; + // from ch_get_num_samples() + u16 num_samples; + // from ch_get_iq_data() + struct ch_iq_sample_t iq_data[IQ_DATA_MAX_NUM_SAMPLES]; +}; + +void set_chirp_data(struct ch101_client *data); +struct ch101_client *get_chirp_data(void); +void set_chirp_buffer(struct ch101_buffer *buffer); +int find_sensors(void); +void init_driver(void); +void config_driver(void); +void start_driver(int period_ms, int time_ms); +void stop_driver(void); +void ext_ChirpINT0_handler(int index); +void single_shot_driver(void); +void test_detect(void); +void test_write_read(void); + + + +#endif /* INITDRIVER_H_ */ diff --git a/drivers/iio/proximity/inv_ch101/src/soniclib.h b/drivers/iio/proximity/inv_ch101/src/soniclib.h new file mode 100644 index 000000000000..0e1e604f34c8 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/soniclib.h @@ -0,0 +1,1214 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*! \file soniclib.h + * + * \brief Chirp SonicLib public API and support functions for Chirp ultrasonic + * sensors. + * + * Chirp SonicLib is a set of API functions and sensor driver routines designed + * to easily control Chirp ultrasonic sensors from an embedded C application. + * It allows an application developer to obtain ultrasonic range data from one + * or more devices, without needing to develop special low-level code to + * interact with the sensors directly. + * + * The SonicLib API functions provide a consistent interface for an application + * to use Chirp sensors in various situations. This is especially important, + * because all Chirp sensors are completely programmable, including the register + * map. The SonicLib interfaces allow an application to use new Chirp sensor + * firmware images, without requiring code changes. Only a single initialization + * parameter must be modified to use the new sensor firmware. + * + * \note All operation of the sensor is controlled through the set of functions, + * data structures, and symbolic values defined in this header file. You should + * not need to modify this file or the SonicLib functions, or use lower-level + * internal functions such as described in the ch_driver.h file. Using any of + * these non-public methods will reduce your ability to benefit from future + * enhancements and releases from Chirp. + * + * + * #### Board Support Package + * SonicLib also defines a set of board support package (BSP) functions that + * must be provided by the developer, board vendor, or Chirp. The BSP functions + * are NOT part of SonicLib - they are external interface routines that allow + * the SonicLib functions to access the peripherals on the target board. These + * functions, which all begin with a "chbsp_" prefix, are described in the + * chirp_bsp.h header file. See the descriptions in that file for more detailed + * information on the BSP interfaces. + * + * The BSP also provides the required \a chirp_board_config.h header file, which + * contains definitions of how many (possible) sensors and I2C buses are present + * on the board. These values are used for static array allocations in SonicLib. + * + * + * #### Basic Operating Sequence + * At a high level, an application using SonicLib will do the following: + * -# Initialize the hardware on the board, by calling the BSP's + * \a chbsp_board_init() function. + * -# Initialize the SonicLib data structures, by calling \a ch_init() for each + * sensor. + * -# Program and start the sensor(s), by calling \a ch_group_start(). + * -# Set up a handler function to process interrupts from the sensor. + * -# Set up a triggering mechanism using a board timer, using + * \a chbsp_periodic_timer_init() etc., (unless the sensor will be used in + * free-running mode, in which no external trigger is needed). A timer + * handler routine will typically trigger the sensor(s) using + * \a ch_group_trigger(). + * -# Configure the sensor's operating mode and range, using \a ch_set_config() + * (or equivalent single-setting functions). + * + * At this point, the sensor will begin to perform measurements. At the end of + * each measurement cycle, the sensor will interrupt the host controller using + * its INT line. The handler routine set up in step #4 above will be called, and + * it should cause the application to read the measurement results from the + * sensor(s), using \a ch_get_range() and optionally \a ch_get_amplitude() + * and/or \a ch_get_iq_data(). + * + * Do not trigger a new measurement until the previous measurement has completed + * and all needed data has been read from the device (including I/Q data, if + * \a ch_get_iq_data() is used). If any I/O operations are still active, + * the new measurement may be corrupted. + */ +/* + * Copyright (c) 2016-2019, Chirp Microsystems. All rights reserved. + * + * Chirp Microsystems CONFIDENTIAL + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CHIRP MICROSYSTEMS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __SONICLIB_H_ +#define __SONICLIB_H_ + +//#define CHDRV_DEBUG // uncomment this line to enable debug messages + +#include "system.h" +#include "chirp_board_config.h" +#include "ch_driver.h" + +/* Chirp header files for installed sensor firmware packages + * If you are installing a new Chirp sensor firmware package, you must add + * the name of the firmware include file to the list below. + */ +#include "ch101_gpr_open.h" +#include "ch101_gpr_sr_open.h" +#include "ch201_gprmt.h" +#include "ch101_gpr.h" + +/* Chirp sensor part numbers */ +#define CH101_PART_NUMBER (101) +#define CH201_PART_NUMBER (201) + +/* Max number of samples per measurement */ +#define CH101_MAX_NUM_SAMPLES 450 +#define CH201_MAX_NUM_SAMPLES 450 + +/* Range value returned if no target detected. */ +#define CH_NO_TARGET 0xFFFFFFFF +/* Speed of sound in meters per second. */ +#define CH_SPEEDOFSOUND_MPS 343 + +/* Signature bytes in sensor*/ +#define CH_SIG_BYTE_0 (0x0a) +#define CH_SIG_BYTE_1 (0x02) + +#define CH_NUM_THRESHOLDS 6 + +/* Preliminary definitions to resolve function pointer parameter types */ +struct ch_dev_t; +struct ch_group_t; + +//! Return value codes. +enum ch_retval_t { + RET_OK = 0, RET_ERR = 1 +}; + +//! Range data types. +enum ch_range_t { + /* One way - gets full pulse/echo distance & divides by 2. */ + CH_RANGE_ECHO_ONE_WAY = 0, + /* Round trip - full pulse/echo distance. */ + CH_RANGE_ECHO_ROUND_TRIP = 1, + /* Direct - for receiving node in pitch-catch mode. */ + CH_RANGE_DIRECT = 2, +}; + +//! Sensor operating modes. +enum ch_mode_t { + /* Idle mode - low-power sleep, no sensing is enabled. */ + CH_MODE_IDLE = 0x00, + /* Free-running mode - sensor uses internal clock to wake and measure.*/ + CH_MODE_FREERUN = 0x02, + /* Triggered transmit/receive mode - transmits and receives when + * INT line triggered. + */ + CH_MODE_TRIGGERED_TX_RX = 0x10, + /* Triggered receive-only mode - for pitch-catch operation with + * another sensor. + */ + CH_MODE_TRIGGERED_RX_ONLY = 0x20 +}; + +//! Sensor reset types. +enum ch_reset_t { + CH_RESET_HARD = 0, /*!< Hard reset. */ + CH_RESET_SOFT = 1 /*!< Soft reset. */ +}; + +//! I/O blocking mode flags. +enum ch_io_mode_t { + CH_IO_MODE_BLOCK = 0, /*!< Blocking mode. */ + CH_IO_MODE_NONBLOCK = 1 /*!< Non-blocking mode. */ +}; + +//! I2C info structure. +struct ch_i2c_info_t { + u8 address; /*!< I2C device address */ + u8 bus_num; /*!< I2C bus index */ + u16 drv_flags; /*!< flags for special handling by Chirp driver */ +}; + +/* Flags for special I2C handling by Chirp driver */ +/* I2C interface needs reset after non-blocking transfer */ +#define I2C_DRV_FLAG_RESET_AFTER_NB 0x00000001 +/* Use programming interface for non-blocking transfer */ +#define I2C_DRV_FLAG_USE_PROG_NB 0x00000002 + +/* Sensor I/Q data value. */ +struct ch_iq_sample_t { + s16 q; /*!< Q component of sample */ + s16 i; /*!< I component of sample */ +}; + +/* Detection old value (CH201 only). */ +struct ch_thresh_t { + u16 start_sample; + u16 level; +}; + +/* Multiple detection threshold structure (CH201 only). */ +struct ch_thresholds_t { + struct ch_thresh_t threshold[CH_NUM_THRESHOLDS]; +}; + +//! Combined configuration structure. +struct ch_config_t { + /* operating mode */ + enum ch_mode_t mode; + /* maximum range, in mm */ + u16 max_range; + /* static target rejection range, in mm (0 if unused) */ + u16 static_range; + /* sample interval, only used if in free-running mode */ + u16 sample_interval; + /* ptr to detection thresholds structure (if supported), should be + * NULL (0) for CH101 + */ + struct ch_thresholds_t *thresh_ptr; +}; + +//! ASIC firmware init function pointer typedef. +typedef u8 (*ch_fw_init_func_t)(struct ch_dev_t *dev_ptr, + struct ch_group_t *grp_ptr, u8 i2c_addr, u8 dev_num, u8 i2c_bus_index); + +//! API function pointer typedefs. +typedef u8 (*ch_fw_load_func_t)(struct ch_dev_t *dev_ptr); +typedef u8 (*ch_get_config_func_t)(struct ch_dev_t *dev_ptr, + struct ch_config_t *config_ptr); +typedef u8 (*ch_set_config_func_t)(struct ch_dev_t *dev_ptr, + struct ch_config_t *config_ptr); +typedef u8 (*ch_set_mode_func_t)(struct ch_dev_t *dev_ptr, enum ch_mode_t mode); +typedef u8 (*ch_set_sample_interval_func_t)(struct ch_dev_t *dev_ptr, + u16 sample_interval); +typedef u8 (*ch_set_num_samples_func_t)(struct ch_dev_t *dev_ptr, + u16 num_samples); +typedef u8 (*ch_set_max_range_func_t)(struct ch_dev_t *dev_ptr, + u16 max_range); +typedef u8 (*ch_set_static_range_func_t)(struct ch_dev_t *dev_ptr, + u16 static_range); +typedef u8 (*ch_get_range_func_t)(struct ch_dev_t *dev_ptr, + enum ch_range_t range_type, u32 *range); +typedef u8 (*ch_get_amplitude_func_t)(struct ch_dev_t *dev_ptr, + u16 *amplitude); +typedef u32 (*ch_get_frequency_func_t)(struct ch_dev_t *dev_ptr); +typedef u8 (*ch_get_iq_data_func_t)(struct ch_dev_t *dev_ptr, + struct ch_iq_sample_t *buf_ptr, u16 start_sample, u16 num_samples, + enum ch_io_mode_t io_mode); +typedef u16 (*ch_samples_to_mm_func_t)(struct ch_dev_t *dev_ptr, + u16 num_samples); +typedef u16 (*ch_mm_to_samples_func_t)(struct ch_dev_t *dev_ptr, u16 num_mm); +typedef u8 (*ch_set_thresholds_func_t)(struct ch_dev_t *dev_ptr, + struct ch_thresholds_t *thresh_ptr); +typedef u8 (*ch_get_thresholds_func_t)(struct ch_dev_t *dev_ptr, + struct ch_thresholds_t *thresh_ptr); + +//! API function pointer structure (internal use). +struct ch_api_funcs_t { + ch_fw_load_func_t fw_load; + ch_get_config_func_t get_config; + ch_set_config_func_t set_config; + ch_set_mode_func_t set_mode; + ch_set_sample_interval_func_t set_sample_interval; + ch_set_num_samples_func_t set_num_samples; + ch_set_max_range_func_t set_max_range; + ch_set_static_range_func_t set_static_range; + ch_get_range_func_t get_range; + ch_get_amplitude_func_t get_amplitude; + ch_get_frequency_func_t get_frequency; + ch_get_iq_data_func_t get_iq_data; + ch_samples_to_mm_func_t samples_to_mm; + ch_mm_to_samples_func_t mm_to_samples; + ch_set_thresholds_func_t set_thresholds; + ch_get_thresholds_func_t get_thresholds; +}; + +//! Data-ready interrupt callback routine pointer. +typedef void (*ch_io_int_callback_t)(struct ch_group_t *grp_ptr, u8 io_index); +// +//! Non-blocking I/O complete callback routine pointer. +typedef void (*ch_io_complete_callback_t)(struct ch_group_t *grp_ptr); +// +//! Periodic timer callback routine pointer. +typedef void (*ch_timer_callback_t)(void); + +//! Chirp sensor group configuration structure. + +/*! \note The \a CHIRP_MAX_NUM_SENSORS and \a CHIRP_NUM_I2C_BUSES symbols must + * be defined by the user. Normally this is done in the \b chirp_board_config.h + * header file that is part of the board support package. + */ +struct ch_group_t { /* [note tag name matches type to help Doxygen linkage ] */ + /* Number of ports (max possible sensor connections) */ + u8 num_ports; + /* Number of I2C buses on this board */ + u8 num_i2c_buses; + /* Number of sensors detected */ + u8 sensor_count; + /* Flags for special I2C handling by Chirp driver, + * from \a chbsp_get_i2c_info() + */ + u16 i2c_drv_flags; + /* Real-time clock calibration pulse length (in ms) */ + u16 rtc_cal_pulse_ms; + /* Addr of hook routine to call when device found on bus */ + chdrv_discovery_hook_t disco_hook; + /* Addr of routine to call when sensor interrupts */ + ch_io_int_callback_t io_int_callback; + /* Addr of routine to call when non-blocking I/O completes */ + ch_io_complete_callback_t io_complete_callback; + /* Array of pointers to ch_dev_t structures for individual sensors */ + struct ch_dev_t *device[CHIRP_MAX_NUM_SENSORS]; + /* Array of I2C non-blocking transaction queues (one per bus) */ + struct chdrv_i2c_queue_t i2c_queue[CHIRP_NUM_I2C_BUSES]; +}; + +//! Chirp sensor device structure. +struct ch_dev_t { /* [note tag name matches type to help Doxygen linkage ] */ + /* Pointer to parent group structure. */ + struct ch_group_t *group; + /* Sensor operating mode. */ + enum ch_mode_t mode; + /* Maximum range, in mm */ + u16 max_range; + /* Static target rejection range, in samples (0 if unused) */ + u16 static_range; + /* Sample interval (in ms), only if in free-running mode */ + u16 sample_interval; + /* Real-time clock calibration result for the sensor. */ + u16 rtc_cal_result; + /* Operating frequency for the sensor. */ + u32 op_frequency; + /* Bandwidth for the sensor. */ + u16 bandwidth; + /* Scale factor for the sensor. */ + u16 scale_factor; + /* Current I2C addresses. */ + u8 i2c_address; + /* Assigned application I2C address for device in normal operation*/ + u8 app_i2c_address; + /* Flags for special I2C handling by Chirp driver */ + u16 i2c_drv_flags; + /* Integer part number (e.g. 101 for a CH101 device). */ + u16 part_number; + /* Oversampling factor (power of 2) */ + s8 oversample; + /* Sensor connection status: + * 1 if discovered and successfully initialized, + * 0 otherwise. + */ + u8 sensor_connected; + /* Index value (device number) identifying device within group */ + u8 io_index; + /* Index value identifying which I2C bus is used for this device. */ + u8 i2c_bus_index; + /* Number of receiver samples for the current max range setting. */ + u16 num_rx_samples; + + /* Sensor Firmware-specific Linkage Definitions */ + /* Pointer to string identifying sensor firmware version. */ + const char *fw_version_string; + /* Pointer to start of sensor firmware image to be loaded */ + const u8 *firmware; + /* Pointer to ram initialization data */ + const u8 *ram_init; + + /* Pointer to function preparing sensor pulse timer to measure + * real-time clock (RTC) calibration pulse sent to device. + */ + void (*prepare_pulse_timer)(struct ch_dev_t *dev_ptr); + /* Pointer to function to read RTC calibration pulse timer result from + * sensor and place value in the \a rtc_cal_result field. + */ + void (*store_pt_result)(struct ch_dev_t *dev_ptr); + /* Pointer to function to read operating frequency and place value + * in the \a op_frequency field. + */ + void (*store_op_freq)(struct ch_dev_t *dev_ptr); + /* Pointer to function to read operating bandwidth and place value in + * the \a bandwidth field. + */ + void (*store_bandwidth)(struct ch_dev_t *dev_ptr); + /* Pointer to function to calculate scale factor and place value + * in \a scalefactor field. + */ + void (*store_scalefactor)(struct ch_dev_t *dev_ptr); + /* Pointer to function returning locked state for sensor. */ + u8 (*get_locked_state)(struct ch_dev_t *dev_ptr); + /* Pointer to function returning ram init size for sensor. */ + u16 (*get_fw_ram_init_size)(void); + /* Pointer to function returning start address of ram initialization + * area in the sensor. + */ + u16 (*get_fw_ram_init_addr)(void); + + /* API and callback functions */ + /* Structure containing API function pointers. */ + struct ch_api_funcs_t api_funcs; +}; + +/* API function prototypes and documentation */ + +/*! + * \brief Initialize the device descriptor for a sensor. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param grp_ptr pointer to the ch_group_t descriptor for sensor group + * to join + * \param dev_num number of the device within the sensor group (identifies + * which physical sensor) + * \param fw_init_func pointer to the sensor firmware initialization function + * (determines sensor feature set) + * + * \return 0 if success, 1 if error + * + * This function is used to initialize various Chirp SonicLib structures before + * using a sensor. The ch_dev_t device descriptor is the primary data structure + * used to manage a sensor, and its address will subsequently be used as + * a handle to identify the sensor when calling most API functions. + * + * The \a dev_ptr parameter is the address of the ch_dev_t descriptor structure + * that will be initialized and then used to identify and manage this sensor. + * The \a grp_ptr parameter is the address of a ch_group_t structure describing + * the sensor group that will include the new sensor. Both the ch_dev_t + * structure and the ch_group_t structure must have already been allocated + * before this function is called. + * + * Generally, an application will require only one ch_group_t structure to + * manage all Chirp sensors. However, a separate ch_dev_t structure must be + * allocated for each sensor. + * + * \a dev_num is a simple index value that uniquely identifies a sensor within + * a group. Each possible sensor (i.e. each physical port on the board that + * could have a Chirp sensor attached) has a number, starting with zero (0). + * The device number is constant - it remains associated with a specific port + * even if no sensor is actually attached. Often, the dev_num value is used by + * an application as an index into arrays containing per-sensor information + * (e.g. data read from the sensors). + * + * The Chirp sensor is fully re-programmable, and the specific features and + * capabilities can be modified by using different sensor firmware images. + * The \a fw_init_func parameter is the address (name) of the sensor firmware + * initialization routine that should be used to program the sensor and prepare + * it for operation. The selection of this routine name is the only required + * change when switching from one sensor firmware image to another. + * + * \note This function only performs internal initialization of data structures, + * etc. It does not actually initialize the physical sensor device(s). + * See \a ch_group_start(). + */ +u8 ch_init(struct ch_dev_t *dev_ptr, struct ch_group_t *grp_ptr, u8 dev_num, + ch_fw_init_func_t fw_init_func); + +/*! + * \brief Program and start a group of sensors. + * + * \param grp_ptr pointer to the ch_group_t descriptor for sensor group + * to be started + * + * \return 0 if successful, 1 if error + * + * This function performs the actual discovery, programming, and initialization + * sequence for all sensors within a sensor group. Each sensor must have + * previously been added to the group by calling \a ch_init(). + * + * In brief, this function does the following for each sensor: + * - Probe the possible sensor ports using I2C bus and each sensor's PROG line, + * to discover if sensoris connected. + * - Reset sensor. + * - Program sensor with firmware (version specified during \a ch_init()). + * - Assign unique I2C address to sensor (specified by board support package, + * see \a chbsp_i2c_get_info()). + * - Start sensor execution. + * - Wait for sensor to lock (complete initialization, including self-test). + * - Send timed pulse on INT line to calibrate sensor Real-Time Clock (RTC). + * + * After this routine returns successfully, the sensor configuration may be set + * and ultrasonic measurements may begin. + */ +u8 ch_group_start(struct ch_group_t *grp_ptr); + +/*! + * \brief Get current configuration settings for a sensor + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param config_ptr pointer to a ch_config_t structure to receive + * configuration values + * + * \return 0 if successful, 1 if error + * + * This function obtains the current configuration settings from the sensor and + * returns them in a ch_config_t structure, whose address is specified by + * \a config_ptr. + * + * \note The individual configuration values returned in the ch_config_t + * structure may also be obtained by using dedicated single-value functions. + * See \a ch_get_mode(), \a ch_get_max_range(), \a ch_get_sample_interval(), + * \a ch_get_static_range(), and \a ch_get_thresholds(). + */ +u8 ch_get_config(struct ch_dev_t *dev_ptr, struct ch_config_t *config_ptr); + +/*! + * \brief Set multiple configuration settings for a sensor + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param config_ptr pointer to a ch_config_t structure containing new + * configuration values + * + * \return 0 if successful, 1 if error + * + * This function sets multiple configuration options within the sensor. + * The configuration settings are passed in a ch_config_t structure, whose + * address is specified by \a config_ptr. The fields in the ch_config_t + * structure must have been set with your new configuration values before this + * function is called. + * + * \note The individual configuration values set by this function may also be + * set using dedicated single-value functions. These two methods are completely + * equivalent and may be freely mixed. + * See \a ch_set_mode(), \a ch_set_max_range(), \a ch_set_sample_interval(), + * \a ch_set_static_range(), and \a ch_set_thresholds(). + */ +u8 ch_set_config(struct ch_dev_t *dev_ptr, struct ch_config_t *config_ptr); + +/*! + * \brief Trigger a measurement on one sensor + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * + * This function generates a pulse on the INT line for a single sensor. + * If the sensor is in either \a CH_MODE_TRIGGERED_TX_RX or + * \a CH_MODE_TRIGGERED_RX_ONLY mode, this pulse will begin a measurement cycle. + * + * To simultaneously trigger all sensors in a group, use \a ch_group_trigger(). + * + * \note Do not trigger a new measurement until the previous measurement has + * completed and all needed data has been read from the device (including + * I/Q data, if \a ch_get_iq_data() is used). If any I/O operations are still + * active, the new measurement may be corrupted. + */ +void ch_trigger(struct ch_dev_t *dev_ptr); + +/*! + * \brief Trigger a measurement on a group of sensors + * + * \param grp_ptr pointer to the ch_group_t descriptor for this group of sensors + * + * This function generates a pulse on the INT line for each sensor in the sensor + * group. If a sensor is in either \a CH_MODE_TRIGGERED_TX_RX or + * \a CH_MODE_TRIGGERED_RX_ONLY mode, this pulse will begin a measurement cycle. + * + * If a two or more sensors are operating in pitch-catch mode (in which one + * transmits and the others receive), this function must be used to start + * a measurement cycle, so that the devices are synchronized. + * + * To trigger a single sensor, use \a ch_trigger(). + * + * \note Do not trigger a new measurement until the previous measurement has + * completed and all needed data has been read from the device (including + * I/Q data, if \a ch_get_iq_data() is used). If any I/O operations are still + * active, the new measurement may be corrupted. + */ +void ch_group_trigger(struct ch_group_t *grp_ptr); + +/*! + * \brief Reset a sensor + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param reset_type type of reset (\a CH_RESET_HARD or \a CH_RESET_SOFT) + * + * This function resets a sensor. The \a reset_type parameter indicates if + * a software reset or full hardware reset is requested. + */ +void ch_reset(struct ch_dev_t *dev_ptr, enum ch_reset_t reset_type); + +/*! + * \brief Reset a group of sensors + * + * \param grp_ptr pointer to the ch_group_t descriptor for this group of sensors + * \param reset_type type of reset (\a CH_RESET_HARD or \a CH_RESET_SOFT) + * + * This function resets all sensors in a sensor group. The \a reset_type + * parameter indicates if a software reset or full hardware reset is requested. + */ +void ch_group_reset(struct ch_group_t *grp_ptr, enum ch_reset_t reset_type); + +/*! + * \brief Indicate if a sensor is connected + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \return 1 if the sensor is connected, 0 otherwise + */ +u8 ch_sensor_is_connected(struct ch_dev_t *dev_ptr); + +/*! + * \brief Get part number for a sensor. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \return integer part number + * + * This function returns the Chirp part number for the specified device. The + * part number is a simple integer value, for example 101 for a CH101 device. + */ +u16 ch_get_part_number(struct ch_dev_t *dev_ptr); + +/*! + * \brief Get device number (I/O index values) for a sensor + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * + * \return device number + * + * This function returns the device number (I/O index) of the sensor within its + * sensor group. Normally, this also corresponds to the sensor's port number on + * the board, and is used for indexing arrays of pin definitions etc. within the + * board support package routines. + */ +u8 ch_get_dev_num(struct ch_dev_t *dev_ptr); + +/*! + * \brief Get device descriptor pointer for a sensor + * + * \param grp_ptr pointer to the ch_group_t descriptor for this group of sensors + * \param dev_num device number within sensor group + * + * \return pointer to ch_dev_t descriptor structure + * + * This function returns the address of the ch_dev_t device descriptor for + * a certain sensor in a sensor group. The sensor is identified within + * the group by the \a dev_num device number. + */ +struct ch_dev_t *ch_get_dev_ptr(struct ch_group_t *grp_ptr, u8 dev_num); + +/*! + * \brief Get the total number of sensor ports (possible sensors) in + * a sensor group + * + * \param grp_ptr pointer to the ch_group_t descriptor for this group of sensors + * + * \return total number of ports (possible sensors) in the sensor group + * + * This function returns the maximum number of possible sensors within a sensor + * group. Typically, the number of sensors is limited by the physical + * connections on the board being used, so the number of sensor ports on + * the board is returned by this function. + */ +u8 ch_get_num_ports(struct ch_group_t *grp_ptr); + +/*! + * \brief Get the active I2C address for a sensor + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * + * \return I2C address, or 0 if error + * + * This function returns the currently active I2C address for a sensor device. + * This function may be used by board support package routines to determine the + * proper I2C address to use for a specified sensor. + */ +u8 ch_get_i2c_address(struct ch_dev_t *dev_ptr); + +/*! + * \brief Get the active I2C bus for a sensor + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * + * \return I2C bus index + * + * This function returns the I2C bus index for a sensor device. This function + * may be used by board support package routines to determine the proper I2C bus + * to use for a specified sensor. + */ +u8 ch_get_i2c_bus(struct ch_dev_t *dev_ptr); + +/*! + * \brief Get the firmware version description string for a sensor + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * + * \return pointer to character string describing sensor firmware version + * + * This function returns a pointer to a string that describes the sensor + * firmware being used on the device. + */ +char *ch_get_fw_version_string(struct ch_dev_t *dev_ptr); + +/*! + * \brief Get the current operating mode for a sensor. + * + * \param dev_ptr a pointer to the ch_dev_t config structure + * \return mode sensor operating mode + * + * This function returns the current operating mode for the sensor, one of: + * - \a CH_MODE_IDLE - low power idle mode, no measurements take place + * - \a CH_MODE_FREERUN - free-running mode, sensor uses internal clock + * to wake and measure + * - \a CH_MODE_TRIGGERED_TX_RX - hardware-triggered, sensor both transmits + * and receives + * - \a CH_MODE_TRIGGERED_RX_ONLY - hardware triggered, sensor only receives + */ +enum ch_mode_t ch_get_mode(struct ch_dev_t *dev_ptr); + +/*! + * \brief Configure a sensor for the specified operating mode. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param mode the new operating mode for the sensor + * \return 0 if successful. + * + * This function sets the sensor to operate in the specified mode, which + * must be one of the following: + * - \a CH_MODE_IDLE - low power idle mode, no measurements take place + * - \a CH_MODE_FREERUN - free-running mode, sensor uses internal clock + * to wake and measure + * - \a CH_MODE_TRIGGERED_TX_RX - hardware-triggered, sensor both transmits + * and receives + * - \a CH_MODE_TRIGGERED_RX_ONLY - hardware triggered, sensor only receives + */ +u8 ch_set_mode(struct ch_dev_t *dev_ptr, enum ch_mode_t mode); + +/*! + * \brief Get the internal sample timing interval for a sensor + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \return interval between samples (in ms), or 0 if device is not in + * free-running mode + * + * This function returns the interval between measurements, in milliseconds, + * for for a sensor operating in free-running mode. If the sensor is in a + * different operating mode (e.g. a triggered mode), zero is returned. + */ +u16 ch_get_sample_interval(struct ch_dev_t *dev_ptr); + +/*! + * \brief Configure the internal sample interval for a sensor in freerunning + * mode. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param interval_ms interval between samples, in milliseconds. + * \return 0 if successful, 1 if arguments are invalid. + * + * This function sets the sample interval for a sensor operating in freerunning + * mode (\a CH_MODE_FREERUN). The sensor will use its internal clock to wake + * and perform a measurement every \a interval_ms milliseconds. + * + * \note This function has no effect for a sensor operating in one of the + * triggered modes. The sample interval for a triggered device is determined + * by the external trigger timing. + */ +u8 ch_set_sample_interval(struct ch_dev_t *dev_ptr, u16 interval_ms); + +/*! + * \brief Get the number of samples per measurement cycle + * + * \param dev_ptr pointer to the ch_dev_t descriptor struct + * + * \return number of samples per measurement cycle + * + * This function returns the current number of samples which the Chirp sensor + * will perform during each measurement cycle. The number of samples directly + * corresponds to the range at which the sensor can detect, so this value is + * determined by the current maximum range setting for the sensor. + * Also see \a ch_get_max_range(). + */ +u16 ch_get_num_samples(struct ch_dev_t *dev_ptr); + +/*! + * \brief Set the sensor sample count directly. + * + * \param dev_ptr pointer to the ch_dev_t descriptor struct + * \param num_samples number of samples during each measurement cycle + * + * \return 0 if successful + * + * This function directly sets the number of samples which the Chirp sensor will + * perform during a single measurement cycle. The number of samples directly + * corresponds to the range at which the sensor can detect. + * + * Also see \a ch_set_max_range(). + * + * \note Normally, the sample is count is not set using this function, but is + * instead set indirectly using either \a ch_set_max_range() or + * \a ch_set_config(), both of which automatically set the sample count based on + * a specified range in millimeters. + */ +u8 ch_set_num_samples(struct ch_dev_t *dev_ptr, u16 num_samples); + +/*! + * \brief Get the maximum range setting for a sensor. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * + * \return Maximum range setting, in millimeters + * + * This function returns the current maximum detection range setting for + * the sensor, in millimeters. + * + * \note The maximum range may also be obtained, along with other settings, + * using the \a ch_get_config() function. + */ +u16 ch_get_max_range(struct ch_dev_t *dev_ptr); + +/*! + * \brief Set the maximum range for a sensor. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param max_range maximum range, in millimeters + * + * \return 0 if successful, non-zero if error + * + * This function sets the maximum detection range for the sensor, in + * millimeters. The detection range setting controls how long the sensor will + * listen (i.e. how many samples it will capture) during each measurement cycle. + * (The number of samples is automatically calculated for the specified range.) + * + * \note The maximum range may also be specified, along with other settings, + * using the \a ch_set_config() function. These two methods are completely + * equivalent and may be freely mixed. + */ +u8 ch_set_max_range(struct ch_dev_t *dev_ptr, u16 max_range); + +/*! + * \brief Get static target rejection range setting. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * + * \return Static target rejection range setting, in samples, + * or 0 if not enabled + * + * This function returns the number of samples at the beginning of a measurement + * cycle over which static target rejection filtering will be applied. + * Also see \a ch_set_static_range(). + * + * To calculate the physical distance that corresponds to the number of samples, + * use the \a ch_samples_to_mm() function. + */ +u16 ch_get_static_range(struct ch_dev_t *dev_ptr); + +/*! + * \brief Configure static target rejection. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param num_samples number of sensor samples (at beginning of measurement + * cycle) over which static targets will be rejected + * + * \return 0 if successful, non-zero if error + * + * Static target rejection is a special processing mode in which the sensor will + * actively filter out signals from close, non-moving objects, so that they do + * not continue to generate range readings. This allows detection and reporting + * of target objects that are farther away than the static objects. (Normally, + * the sensor reports the range value for the closest detected object.) + * + * Static target rejection is applied for a specified number of samples, + * starting at the beginning of a measurement cycle* (i.e. for the closest + * objects). The num_samples parameter specifies the number of samples that + * will be filtered. To calculate the appropriate value for \a num_samples + * to filter over a certain physical distance, use the \a ch_mm_to_samples() + * function. + */ +u8 ch_set_static_range(struct ch_dev_t *dev_ptr, u16 num_samples); + +/*! + * \brief Get the measured range from a sensor. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param range_type the range type to be reported (e.g. one-way vs. + * round-trip) + * \param range range in millimeters times 32, or \a CH_NO_TARGET + * (0xFFFFFFFF) if no target was detected, or 0 if error + * + * \return 0 if successful, non-zero if error + * + * This function reads the measurement result registers from the sensor and then + * computes the actual range. It should be called after the sensor has indicated + * that a measurement cycle is complete by generating a signal on the INT line. + * (Typically, this will be set up by an interrupt handler associated with that + * input line.) + * + * The \a range_type parameter indicates whether the measurement is based on the + * one-way or round-trip distance to/from a target, or the direct distance + * between two sensors operating in pitch-catch mode. + * The possible values are: + * - \a CH_RANGE_ECHO_ONE_WAY - gets full pulse/echo round-trip distance, + * then divides by 2 + * - \a CH_RANGE_ECHO_ROUND_TRIP - full pulse/echo round-trip distance + * - \a CH_RANGE_DIRECT - for receiving sensor in pitch-catch mode (one-way) + * + * This function returns the measured range as a 32-bit integer. For maximum + * precision, the range value is returned in a fixed-point format with 5 + * fractional bits. So, the return value is the number of millimeters times 32. + * Divide the value by 32 (shift right 5 bits) to get whole mm, or use floating + * point (i.e. divide by 32.0f) to preserve the full sub-millimeter precision. + * + * If the sensor did not successfully find the range of a target during the most + * recent measurement, the returned range value will be \a CH_NO_TARGET. If an + * error occurs when getting or calculating the range, zero (0) will be returned + * + * \note This function only reports the results from the most recently completed + * measurement cycle. It does not actually trigger a measurement. + * + * \note The \a range_type parameter only controls how this function interprets + * the results from the measurement cycle. It does not change the sensor mode. + * + */ +u8 ch_get_range(struct ch_dev_t *dev_ptr, enum ch_range_t range_type, + u32 *range); + +/*! + * \brief Get the measured amplitude from a sensor. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param amplitude amplitude value for most recent range reading + .* + * \return 0 if successful, non-zero if error + * + * This function returns the amplitude value for the most recent successful + * range measurement by the sensor. The amplitude is representative of the + * incoming sound pressure. The value is expressed in internal sensor counts + * and is not calibrated to any standard units. + * + * The amplitude value is not updated if a measurement cycle resulted in + * \a CH_NO_TARGET, as returned by \a ch_get_range(). + */ +u8 ch_get_amplitude(struct ch_dev_t *dev_ptr, u16 *amplitude); + +/*! + * \brief Get the operating frequency of a sensor. + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * + * This function returns the operating frequency of the sensor. This is the + * primary frequency of the ultrasonic pulse that is emitted by the device when + * transmitting. + * + * \return frequency, in Hz + */ +u32 ch_get_frequency(struct ch_dev_t *dev_ptr); + +/*! + * \brief Get the real-time clock calibration value + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * + * \return RTC calibration value + * + * This function returns the real-time clock (RTC) calibration value read from + * the sensor during \a ch_group_start(). The RTC calibration value is + * calculated by the sensor during the RTC calibration pulse, and it is used + * internally in calculations that convert between time and distance. + */ +u16 ch_get_rtc_cal_result(struct ch_dev_t *dev_ptr); + +/*! + * \brief Get the real-time clock calibration pulse length + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * + * \return RTC pulse length, in ms + * + * This function returns the length (duration), in milliseconds, of the the + * real-time clock (RTC) calibration pulse used for the sensor. The pulse is + * applied to the sensor's INT line during \a ch_group_start() to calibrate the + * sensor's internal clock. The pulse length is specified by the board support + * package during the \a chbsp_board_init() function. + * + * The RTC calibration pulse length is used internally in calculations that + * convert between time and distance. + */ +u16 ch_get_rtc_cal_pulselength(struct ch_dev_t *dev_ptr); + +/*! + * \brief Get the raw I/Q measurement data from a sensor + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param buf_ptr pointer to data buffer where I/Q data will be written + * \param start_sample starting sample number within measurement data + * (0 = start of data) + * \param num_samples number of samples to read from sensor + * \param mode whether I/Q read should block: + * \a CH_IO_MODE_BLOCK (0) = blocking, + * \a CH_IO_MODE_NONBLOCK (1) = non-blocking + * + * \return 0 if successful, 1 if error + * + * This function reads the raw I/Q measurement data from the sensor. + * The I/Q data set includes a discrete pair of values for each of the samples + * that make up a full measurement cycle. Each individual sample is reported + * as a pair of values, I and Q, in a quadrature format. The I/Q values may be + * used to calculate the relative amplitude of the measured ultrasound signal. + * + * The contents of the I/Q trace are updated on every measurement cycle, even + * if no target was detected (i.e. even if \a ch_get_range() returns + * \a CH_NO_TARGET). (Note that this is different than the regular amplitude + * value, as returned by \a ch_get_amplitude(), which is \a not updated unless + * a target is detected.) + * + * Each sample I/Q pair consists of two signed 16-bit integers and is described + * by the \a ch_iq_sample_t structure. To convert any given pair of I/Q values + * to the amplitude value for that sample, square both I and Q, and take the + * square root of the sum: + * \f[Amp_n = \sqrt{(I_n)^2 + (Q_n)^2}\f] + * Amplitude values in the sensor are expressed only in internal ADC counts + * (least-significant bits, or LSBs) and are not calibrated to any standard + * units. + * + * The number of samples used in each I/Q trace is determined by the maximum + * range setting for the device. If it is set to less than the maximum possible + * range, not all samples will contain valid data. To determine the number of + * active samples within the trace, use \a ch_get_num_samples(). + * + * - To read all valid I/Q data, set \a start_sample to zero (0), and set + * \a num_samples to the value returned by \a ch_get_num_samples(). + * + * To determine what sample number corresponds to a physical distance, use + * \a ch_mm_to_samples(). + * + * To allow more flexibilty in your application, the I/Q data readout from the + * device may be done in a non-blocking mode, by setting \a mode to + * \a CH_IO_MODE_NONBLOCK (1). In non-blocking mode, the I/O operation takes + * place using DMA access in the background. This function will return + * immediately, and a notification will later be issued when the I/Q has been + * read. To use the \a non_block option, the board support package (BSP) you + * are using must provide the \a chbsp_i2c_read_nb() and + * \a chbsp_i2c_read_mem_nb() functions. To use non-blocking reads of the I/Q + * data, you must specify a callback routine that will be called when the I/Q + * read completes. See \a ch_io_complete_callback_set(). + * + * Non-blocking reads are managed together for a group of sensors. To perform + * a non-blocking read: + * + * -# Register a callback function using \a ch_io_complete_callback_set(). + * -# Define and initialize a handler for the DMA interrupts generated. + * -# Synchronize with all sensors whose I/Q data should be read by waiting for + * all to indicate data ready. + * -# Set up a non-blocking read on each sensor, using \a ch_get_iq_data() + * with \a mode = \a CH_IO_MODE_NONBLOCK (1). + * -# Start the non-blocking reads on all sensors in the group, using + * \a ch_io_start_nb(). + * -# Your callback function (set in step #1 above) will be called as each + * individual sensor's read completes. Your callback function should + * initiate any further processing of the I/Q data, possibly by setting + * a flag that will be checked from within the application's main execution + * loop. The callback function will likely be called at interrupt level, + * so the amount of processing within it should be kept to a minimum. + * + * For the CH101 sensor, up to 150 samples are taken during each measurement + * cycle. So, a complete CH101 I/Q trace will contain up to 600 bytes of data + * (150 samples x 4 bytes per sample). The buffer specified by \a buf_ptr must + * be large enough to hold this amount of data. + * + * When the I/Q data is read from the sensor, the additional time required to + * transfer the I/Q data over the I2C bus must be taken into account when + * planning how often the sensor can be read (sample interval). + * + * \note It is important that any data I/O operations to or from the sensor, + * including reading the I/Q data, complete before a new measurement cycle is + * triggered, or the new measurement may be affected. + * + * \note This function only obtains the data from the most recently completed + * measurement cycle. It does not actually trigger a measurement. + */ +u8 ch_get_iq_data(struct ch_dev_t *dev_ptr, struct ch_iq_sample_t *buf_ptr, + u16 start_sample, u16 num_samples, enum ch_io_mode_t mode); + +/*! + * \brief Convert sample count to millimeters for a sensor + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param num_samples sample count to be converted + * + * \return number of millimeters + * + * This function converts the sample count specified in \a num_samples and + * converts it to the corresponding physical distance in millimeters. + * The conversion uses values set during device initialization and calibration + * that describe the internal timing of the sensor. + * + * This function may be helpful when working with both physical distances + * (as reported by the \a ch_get_range() function) and sample-oriented values, + * such as data obtained from \a ch_get_iq_data() or parameters for static + * target rejection (see \a ch_set_static_range()). + */ +u16 ch_samples_to_mm(struct ch_dev_t *dev_ptr, u16 num_samples); + +/*! + * \brief Convert millimeters to sample count for a sensor + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param num_mm number of millimeters to be converted + * + * \return number of samples + * + * This function converts the distance in millimeters specified in \a num_mm and + * converts it to the corresponding number of sensor samples. The conversion + * uses values set during device initialization and calibration that describe + * the internal timing of the sensor, along with the current maximum range + * setting for the device. + * + * This function may be helpful when working with both physical distances + * (as reported by the \a ch_get_range() function) and sample-oriented values, + * such as data obtained from \a ch_get_iq_data() or parameters for static + * target rejection (see \a ch_set_static_range()). + */ +u16 ch_mm_to_samples(struct ch_dev_t *dev_ptr, u16 num_mm); + +/*! + * \brief Start non-blocking I/O operation(s) for a group of sensors + * + * \param grp_ptr pointer to the ch_group_t descriptor for sensor group + * + * \return 0 if success, 1 if error + * + * This function starts one or more non-blocking I/O operations on a group of + * sensors. Generally, the I/O operations are non-blocking I/Q data read + * requests individually generated using \a ch_get_iq_data(). + * + * This function will return immediately after the I/O operations are started. + * When the I/O operations complete, the callback function registered using + * \a ch_io_callback_set() will be called. + * + * See \a ch_get_iq_data() for more information. + */ +u8 ch_io_start_nb(struct ch_group_t *grp_ptr); + +/*! + * \brief Register sensor interrupt callback routine for a group of sensors + * + * \param grp_ptr pointer to the ch_group_t sensor group descriptor structure + * \param callback_func_ptr pointer to callback function to be called when + * sensor interrupts + * + * This function registers the routine specified by \a callback_func_ptr to be + * called whenever the sensor interrupts. Generally, such an interrupt indicates + * that a measurement cycle has completed and the sensor has data ready to be + * read. All sensors in a sensor group use the same callback function, which + * receives the interrupting device's device number (port number) as an input + * parameter to identify the specific interrupting device. + * + */ +void ch_io_int_callback_set(struct ch_group_t *grp_ptr, + ch_io_int_callback_t callback_func_ptr); + +/*! + * \brief Register non-blocking I/O complete callback routine for a group + * of sensors + * + * \param grp_ptr pointer to the ch_group_t group descriptor structure + * \param callback_func_ptr pointer to callback function to be called when + * non-blocking I/O operations complete + * + * This function registers the routine specified by \a callback_func_ptr to be + * called when all outstanding non-blocking I/O operations complete for a group + * of sensors. The non-blocking I/O operations must have previously been + * initiated using \a ch_io_start_nb(). + */ +void ch_io_complete_callback_set(struct ch_group_t *grp_ptr, + ch_io_complete_callback_t callback_func_ptr); + +/*! + * \brief Notify SonicLib that a non-blocking I/O operation has completed + * + * \param grp_ptr pointer to the ch_group_t sensor group descriptor structure + * \param i2c_bus_index identifier indicating on which I2C bus the I/O operation + * was completed + * + * This function should be called from your non-blocking I/O interrupt handler + * each time a non-blocking I/O operation completes. The \a i2c_bus_index + * parameter should indicate which I2C bus is being reported. + * + * When all outstanding non-blocking I/O operations are complete, SonicLib will + * call the callback function previously registered using + * \a ch_io_complete_callback_set(). + */ +void ch_io_notify(struct ch_group_t *grp_ptr, u8 i2c_bus_index); + +/*! + * \brief Get detection thresholds (CH201 only). + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param thresh_ptr pointer to ch_thresholds_t structure to receive + * threshold data + * + * \return 0 if success, 1 if error + * + * This function obtains the current detection threshold values from the sensor + * and returns them in a ch_thresholds_t structure specified by \a thresh_ptr. + * The ch_thresholds_t structure holds an array of ch_thresh_t structures, each + * of which contains a starting sample number and amplitude threshold value. + * + * \note This function is not supported on CH101 devices. + */ +u8 ch_get_thresholds(struct ch_dev_t *dev_ptr, + struct ch_thresholds_t *thresh_ptr); + +/*! + * \brief Set detection thresholds (CH201 only). + * + * \param dev_ptr pointer to the ch_dev_t descriptor structure + * \param thresh_ptr pointer to ch_thresholds_t structure containing + * threshold data + * + * \return 0 if success, 1 if error + * + * This function obtains the current detection threshold values from the sensor + * and returns them in a ch_thresholds_t structure specified by \a thresh_ptr. + * The ch_thresholds_t structure holds an array of ch_thresh_t structures, each + * of which contains a starting sample number and amplitude threshold value. + * + * To use this function, first initialize the ch_thresh_t sample/level pair of + * values for each threshold. A CH201 device supports six (6) thresholds. + * Each threshold has a maximum sample length of 255. + * + * \note This function is not supported on CH101 devices. + */ +u8 ch_set_thresholds(struct ch_dev_t *dev_ptr, + struct ch_thresholds_t *thresh_ptr); + +#endif /* __SONICLIB_H_ */ diff --git a/drivers/iio/proximity/inv_ch101/src/system.h b/drivers/iio/proximity/inv_ch101/src/system.h new file mode 100644 index 000000000000..53b703c3d153 --- /dev/null +++ b/drivers/iio/proximity/inv_ch101/src/system.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SYSTEM_H_ +#define SYSTEM_H_ + +#if defined(__KERNEL__) + +#include +#include +#include "../ch101_client.h" + +#define printf(...) pr_info(TAG __VA_ARGS__) + +#define UINT8_MAX 0xFF +#define UINT16_MAX 0xFFFF + +#else + +#include +#include +#include +#include + +#define printf(...) + +typedef signed char s8; +typedef unsigned char u8; + +typedef signed short s16; +typedef unsigned short u16; + +typedef signed int s32; +typedef unsigned int u32; + +typedef signed long long s64; +typedef unsigned long long u64; + +#endif + +#endif diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig index 82e4a62745e2..822f522257fd 100644 --- a/drivers/iio/temperature/Kconfig +++ b/drivers/iio/temperature/Kconfig @@ -3,6 +3,21 @@ # menu "Temperature sensors" +config ADS7052_TDK_THERMISTOR + tristate "TI ADS7052 coupled with B57861S0103A039" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the B57861S0103A039 + thermistor coupled with an external ADC converter ADS7052 via SPI. + + Supported sensors: + * B57861S0103A039 + + This driver can also be built as a module. If so, the module will + be called tdk_thermistor. + config MAXIM_THERMOCOUPLE tristate "Maxim thermocouple sensors" depends on SPI diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile index 34a31db0bb63..af6e1d503a7c 100644 --- a/drivers/iio/temperature/Makefile +++ b/drivers/iio/temperature/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_HID_SENSOR_TEMP) += hid-sensor-temperature.o obj-$(CONFIG_MAXIM_THERMOCOUPLE) += maxim_thermocouple.o +obj-$(CONFIG_ADS7052_TDK_THERMISTOR) += tdk_thermistor.o obj-$(CONFIG_MLX90614) += mlx90614.o obj-$(CONFIG_MLX90632) += mlx90632.o obj-$(CONFIG_TMP006) += tmp006.o diff --git a/drivers/iio/temperature/tdk_thermistor.c b/drivers/iio/temperature/tdk_thermistor.c new file mode 100644 index 000000000000..1bd061f1a123 --- /dev/null +++ b/drivers/iio/temperature/tdk_thermistor.c @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "tdk-therm: " + +#define TDK_THERMISTOR_DRV_NAME "tdk_thermistor" + +enum { + TDK_THERM +}; + + +static const struct iio_chan_spec tdk_therm_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 14, + .storagebits = 16, + .shift = 3, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +struct tdk_thermistor_chip { + const struct iio_chan_spec *channels; + const unsigned long *scan_masks; + u8 num_channels; +}; + +static const struct tdk_thermistor_chip tdk_thermistor_chips[] = { + [TDK_THERM] = { + .channels = tdk_therm_channels, + .num_channels = ARRAY_SIZE(tdk_therm_channels), + }, +}; + +struct tdk_thermistor_data { + struct spi_device *spi; + struct device *dev; + const struct tdk_thermistor_chip *chip; + struct iio_trigger *trig; + struct hrtimer timer; + ktime_t period; + u8 buffer[16] ____cacheline_aligned; +}; + +static bool current_state; +static int tdk_thermistor_read(struct tdk_thermistor_data *data, + struct iio_chan_spec const *chan, int *val); + +#ifdef TEST_DRIVER +static struct task_struct *thread_st; +#endif + +static int temp_trig_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct tdk_thermistor_data *data = iio_priv(indio_dev); + struct device *dev = data->dev; + + dev_info(dev, "%s: state: %d\n", __func__, state); + + current_state = state; + if (state) + hrtimer_start(&data->timer, data->period, HRTIMER_MODE_REL); + else + hrtimer_cancel(&data->timer); + + return 0; +} + +static const struct iio_trigger_ops temp_trigger_ops = { + .set_trigger_state = temp_trig_set_state, +}; + +/*HR Timer callback function*/ +static enum hrtimer_restart tdk_thermistor_hrtimer_handler(struct hrtimer *t) +{ + struct tdk_thermistor_data *data = + container_of(t, struct tdk_thermistor_data, timer); + + if (!data) + return HRTIMER_NORESTART; + + pr_info(TAG "%s: t: %lld\n", + __func__, ktime_get_boot_ns()); + + if (data->trig != NULL) + iio_trigger_poll(data->trig); + else + pr_info(TAG "%s: Trigger is NULL\n", __func__); + + hrtimer_forward_now(t, data->period); + + return HRTIMER_RESTART; +} + + +#ifdef TEST_DRIVER /*Test thread function to read and display sensor data*/ +static int test_thread_fn(void *input_data) +{ + int cycle_cnt = 0; + int value = 0; + struct tdk_thermistor_data *data = + (struct tdk_thermistor_data *)input_data; + struct device *dev = data->dev; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + if (indio_dev == NULL || dev == NULL) + pr_info(TAG "%s: indio_dev or dev NULL\n", __func__); + + pr_info(TAG "%s: Starting test thread\n", __func__); + pr_info(TAG "%s: input data: %p spidev: %p\n", + __func__, input_data, data->spi); + + + msleep(10000); + + pr_info(TAG "%s: Triggered calibration of TDK thermistor\n", __func__); + tdk_thermistor_calibrate(data); + msleep(500); + + while (1) { + int temp_data = 0; + int temp_voltage; + int temp_resistance; + + + cycle_cnt++; + msleep(1000); + pr_info(TAG "%s: Reading TDK thermistor\n", __func__); + tdk_thermistor_read(data, NULL, &temp_data); + pr_info(TAG "%s: Read TDK thermistor: %i\n", + __func__, temp_data); + + /* multiply by 100*/ + temp_voltage = ((temp_data) * 330) / 16368; + temp_resistance = (3300000 / temp_voltage) - 10000; + + pr_info( + TAG "%s: Read TDK thermistor voltage %i resistance: %i\n", + __func__, temp_voltage, temp_resistance); + } + + do_exit(0); + return 0; +} +#endif + + +static int tdk_thermistor_read(struct tdk_thermistor_data *data, + struct iio_chan_spec const *chan, int *val) +{ + u8 raw_data[3]; + int ret; + + pr_info(TAG "%s: Reading thermistor\n", __func__); + + /* Requires 18 clock cycles to get ADC data. First falling edge + * is zero, then 14 bits of data, followed by 3 additional zeroes. + * Use of 3 bytes provide 24 clock cycles. + */ + ret = spi_read(data->spi, (void *)raw_data, 3); + + if (ret) { + pr_info(TAG "%s: Failed SPI read: %i\n", __func__, ret); + return ret; + } + + pr_info(TAG "%s: Read SPI: 0x%x 0x%x 0x%x\n", __func__, + raw_data[0], raw_data[1], raw_data[2]); + + /* check to be sure this is a valid reading */ + if (raw_data[0] == 0 && raw_data[1] == 0 && raw_data[2] == 0) + return -EINVAL; + + *val = ((raw_data[0] & 0x7F) << 7) | (raw_data[1] >> 1); + + pr_info(TAG "%s: Read temp: %i\n", __func__, *val); + + return 0; +} + +static int tdk_thermistor_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tdk_thermistor_data *data = iio_priv(indio_dev); + int ret = -EINVAL; + + pr_info(TAG "%s: Read raw TDK thermistor, mask: %li\n", __func__, mask); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ret = tdk_thermistor_read(data, chan, val); + iio_device_release_direct_mode(indio_dev); + + if (!ret) + return IIO_VAL_INT; + + break; + case IIO_CHAN_INFO_SCALE: + pr_info(TAG "%s: Read TDK thermistor scale chan: val: %i %i\n", + __func__, *val, chan->channel2); + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: + *val = 62; + *val2 = 500000; /* 1000 * 0.0625 */ + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + *val = 250; /* 1000 * 0.25 */ + ret = IIO_VAL_INT; + }; + break; + } + + return ret; +} + +static const struct iio_info tdk_thermistor_info = { + .read_raw = tdk_thermistor_read_raw, +}; + +/*IIO trigger callback function, top half*/ +static irqreturn_t tdk_thermistor_store_time(int irq, void *p) +{ + struct iio_poll_func *pf = p; + + pf->timestamp = ktime_get_boot_ns(); + pr_info(TAG "%s: t: %llx\n", __func__, pf->timestamp); + + return IRQ_WAKE_THREAD; +} + +/*IIO trigger callback function(bottom half), */ +/*reads data over SPI and pushes to buffer*/ +static irqreturn_t tdk_thermistor_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct tdk_thermistor_data *data = iio_priv(indio_dev); + int ret; + + pr_info(TAG "%s: Triggered read TDK thermistor\n", __func__); + + ret = spi_read(data->spi, data->buffer, 3); + if (!ret) { + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + iio_get_time_ns(indio_dev)); + } + pr_info(TAG "%s: Read SPI: 0x%x 0x%x 0x%x\n", __func__, + data->buffer[0], data->buffer[1], data->buffer[2]); +#ifdef TEST_DRIVER + /* check to be sure this is a valid reading */ + if (data->buffer[0] == 0 && data->buffer[1] == 0 && + data->buffer[2] == 0) + pr_info(TAG "%s: Invalid reading\n", __func__);//return -EINVAL; + + val = ((data->buffer[0] & 0x7F) << 7) | (data->buffer[1] >> 1); + + pr_info(TAG "%s: Read temp: %i\n", __func__, val); +#endif + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + + +static int tdk_thermistor_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct iio_dev *indio_dev; + struct tdk_thermistor_data *data; + const struct tdk_thermistor_chip *chip = + &tdk_thermistor_chips[id->driver_data]; + struct iio_buffer *buffer; + int ret = 0; + + pr_info(TAG "%s: Probing call for TDK thermistor\n", __func__); + pr_info(TAG "%s: SPI Device detected: %s offset: %lu\n", + __func__, id->name, id->driver_data); + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); + if (!indio_dev) { + pr_info( + TAG "%s: Failed to alloc memory TDK thermistor\n", __func__); + return -ENOMEM; + } + + buffer = devm_iio_kfifo_allocate(&spi->dev); + if (!buffer) { + pr_info( + TAG "%s: Failed to alloc memory TDK thermistor\n", __func__); + return -ENOMEM; + } + + + iio_device_attach_buffer(indio_dev, buffer); + + indio_dev->info = &tdk_thermistor_info; + indio_dev->name = TDK_THERMISTOR_DRV_NAME; + indio_dev->channels = chip->channels; + indio_dev->available_scan_masks = chip->scan_masks; + indio_dev->num_channels = chip->num_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->dev.parent = &spi->dev; + + data = iio_priv(indio_dev); + data->spi = spi; + data->dev = &spi->dev; + data->chip = chip; + + /*IIO Triggered buffer setting*/ + ret = iio_triggered_buffer_setup(indio_dev, tdk_thermistor_store_time, + tdk_thermistor_trigger_handler, NULL); + if (ret) + return ret; + + data->trig = iio_trigger_alloc("%s-hrtimer%d", indio_dev->name, + indio_dev->id); + if (data->trig == NULL) { + ret = -ENOMEM; + dev_err(&spi->dev, "iio trigger alloc error\n"); + goto error_unreg_buffer; + } + + data->trig->dev.parent = &spi->dev; + data->trig->ops = &temp_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + + ret = iio_trigger_register(data->trig); + if (ret) { + dev_err(&spi->dev, "iio trigger register error %d\n", ret); + goto error_unreg_buffer; + } + + iio_trigger_get(data->trig); + indio_dev->trig = data->trig; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unreg_buffer; + + /*Initialize timer for iio buffer updating */ + hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + data->period = ns_to_ktime((NSEC_PER_SEC / 1)); + /*TBD: test and set appropriate period for timer interrupt*/ + data->timer.function = tdk_thermistor_hrtimer_handler; + +#ifdef TEST_DRIVER + pr_info( +TAG "%s: Creating test thread for thermistor with data: %p spidev: %p\n", + __func__, data, data->spi); + + thread_st = kthread_run(test_thread_fn, data, + "TDK Thermistor Test Thread"); + + if (thread_st) + pr_info(TAG "%s: Thread Created successfully\n", __func__); + else + pr_info(TAG "%s: Thread creation failed\n", __func__); +#endif + return 0; + +error_unreg_buffer: + iio_triggered_buffer_cleanup(indio_dev); + + return ret; +} + +static int tdk_thermistor_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + + return 0; +} + +static const struct spi_device_id tdk_thermistor_id[] = { + {"tdktherm", TDK_THERM}, + {}, +}; +MODULE_DEVICE_TABLE(spi, tdk_thermistor_id); + +static struct spi_driver tdk_thermistor_driver = { + .driver = { + .name = TDK_THERMISTOR_DRV_NAME, + }, + .probe = tdk_thermistor_probe, + .remove = tdk_thermistor_remove, + .id_table = tdk_thermistor_id, +}; + +module_spi_driver(tdk_thermistor_driver); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense thermistor driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index a78c8309bb3f..8ddb8cbd58fb 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -213,6 +213,8 @@ source "drivers/input/misc/Kconfig" source "drivers/input/rmi4/Kconfig" +source "drivers/input/fingerprint/Kconfig" + endif menu "Hardware I/O ports" diff --git a/drivers/input/Makefile b/drivers/input/Makefile index d670bfc10bbe..1b4dc5ed87cf 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -31,3 +31,4 @@ obj-$(CONFIG_INPUT_KEYRESET) += keyreset.o obj-$(CONFIG_INPUT_KEYCOMBO) += keycombo.o obj-$(CONFIG_RMI4_CORE) += rmi4/ +obj-$(CONFIG_MICROARRAY_FINGERPRINT) += fingerprint/ diff --git a/drivers/input/fingerprint/Kconfig b/drivers/input/fingerprint/Kconfig new file mode 100755 index 000000000000..79068f497290 --- /dev/null +++ b/drivers/input/fingerprint/Kconfig @@ -0,0 +1,13 @@ +# Copyright (C) MicroArray +# MicroArray Fprint Driver Code +# Kconfig +# Date: 2017-3-15 +# Version: v4.0.06 +# Author: guq +# Contact: guq@microarray.com.cn + +config MICROARRAY_FINGERPRINT + tristate "MICROARRAY Fingerprint" + default y + ---help--- + MICROARRAY Fingerprint chip AFS121 AFS112 AFS83 TEE driver \ No newline at end of file diff --git a/drivers/input/fingerprint/Makefile b/drivers/input/fingerprint/Makefile new file mode 100755 index 000000000000..d1e92c2850cb --- /dev/null +++ b/drivers/input/fingerprint/Makefile @@ -0,0 +1,15 @@ +# Copyright (C) Microarray +# MicroArray Fprint Driver Code +# MAKEFILE +# Date: 2017-3-15 +# Version: v4.0.06 +# Author: guq +# Contact: guq@microarray.com.cn + + + +obj-$(CONFIG_MICROARRAY_FINGERPRINT) += qcom-settings.o +obj-$(CONFIG_MICROARRAY_FINGERPRINT) += madev.o + + + diff --git a/drivers/input/fingerprint/ioctl_cmd.h b/drivers/input/fingerprint/ioctl_cmd.h new file mode 100755 index 000000000000..214454072d4b --- /dev/null +++ b/drivers/input/fingerprint/ioctl_cmd.h @@ -0,0 +1,64 @@ +/* Copyright (C) MicroArray + * MicroArray Fprint Driver Code + * ioctl_cmd.h + * Date: 2017-3-9 + * Version: v4.0.05 + * Author: guq + * Contact: guq@microarray.com.cn + */ +#ifndef __IOCTL_CMD_H__ +#define __IOCTL_CMD_H__ + +//#define MA_DRV_VERSION (0x00004005) + +#define MA_IOC_MAGIC 'M' +//#define MA_IOC_INIT _IOR(MA_IOC_MAGIC, 0, unsigned char) +#define TIMEOUT_WAKELOCK _IO(MA_IOC_MAGIC, 1) +#define SLEEP _IO(MA_IOC_MAGIC, 2) //陷入内核 +#define WAKEUP _IO(MA_IOC_MAGIC, 3) //唤醒 +#define ENABLE_CLK _IO(MA_IOC_MAGIC, 4) //打开spi时钟 +#define DISABLE_CLK _IO(MA_IOC_MAGIC, 5) //关闭spi时钟 +#define ENABLE_INTERRUPT _IO(MA_IOC_MAGIC, 6) //å¼€å¯ä¸­æ–­ä¸ŠæŠ¥ +#define DISABLE_INTERRUPT _IO(MA_IOC_MAGIC, 7) //关闭中断上报 +#define TAP_DOWN _IO(MA_IOC_MAGIC, 8) +#define TAP_UP _IO(MA_IOC_MAGIC, 9) +#define SINGLE_TAP _IO(MA_IOC_MAGIC, 11) +#define DOUBLE_TAP _IO(MA_IOC_MAGIC, 12) +#define LONG_TAP _IO(MA_IOC_MAGIC, 13) + +#define MA_IOC_VTIM _IOR(MA_IOC_MAGIC, 14, unsigned char) //version time +#define MA_IOC_CNUM _IOR(MA_IOC_MAGIC, 15, unsigned char) //cover num +#define MA_IOC_SNUM _IOR(MA_IOC_MAGIC, 16, unsigned char) //sensor type +#define MA_IOC_UKRP _IOW(MA_IOC_MAGIC, 17, unsigned char) //user define the report key + +#define MA_KEY_UP/*KEY_UP*/ _IO(MA_IOC_MAGIC, 18) //nav up +#define MA_KEY_LEFT/*KEY_LEFT*/ _IO(MA_IOC_MAGIC, 19) //nav left +#define MA_KEY_DOWN/*KEY_DOWN*/ _IO(MA_IOC_MAGIC, 20) //nav down +#define MA_KEY_RIGHT/*KEY_RIGHT*/ _IO(MA_IOC_MAGIC, 21) //nav right + +#define MA_KEY_F14/*KEY_F14*/ _IO(MA_IOC_MAGIC, 23) //for chuanyin +#define SET_MODE _IOW(MA_IOC_MAGIC, 33, unsigned int) //for yude +#define GET_MODE _IOR(MA_IOC_MAGIC, 34, unsigned int) //for yude + + +#define ENABLE_IRQ/*ENABLE_IQ*/ _IO(MA_IOC_MAGIC, 31) +#define DISABLE_IRQ/*DISABLE_IQ*/ _IO(MA_IOC_MAGIC, 32) + +#define MA_IOC_GVER _IOR(MA_IOC_MAGIC, 35, unsigned int) //get the driver version,the version mapping in the u32 is the final 4+4+8,as ******** ******* ****(major verson number) ****(minor version number) ********(revised version number), the front 16 byte is reserved. +#define SCREEN_OFF _IO(MA_IOC_MAGIC, 36) +#define SCREEN_ON _IO(MA_IOC_MAGIC, 37) +#define SET_SPI_SPEED _IOW(MA_IOC_MAGIC, 38, unsigned int) + + +#define WAIT_FACTORY_CMD _IO(MA_IOC_MAGIC, 39)//for fingerprintd +#define WAKEUP_FINGERPRINTD _IO(MA_IOC_MAGIC, 40)//for factory test +#define WAIT_FINGERPRINTD_RESPONSE _IOR(MA_IOC_MAGIC, 41, unsigned int)//for factory test +#define WAKEUP_FACTORY_TEST_SEND_FINGERPRINTD_RESPONSE _IOW(MA_IOC_MAGIC, 42, unsigned int)//for fingerprintd +#define WAIT_SCREEN_STATUS_CHANGE _IOR(MA_IOC_MAGIC, 43, unsigned int) +#define GET_INTERRUPT_STATUS _IOR(MA_IOC_MAGIC, 44, unsigned int) +#define SYNC _IO(MA_IOC_MAGIC, 45) +#define SYNC2 _IO(MA_IOC_MAGIC, 46) +#define GET_SCREEN_STATUS _IOR(MA_IOC_MAGIC, 47, unsigned int) + +#endif /* __IOCTL_CMD_H__ */ + diff --git a/drivers/input/fingerprint/madev.c b/drivers/input/fingerprint/madev.c new file mode 100755 index 000000000000..f5c1f374acdf --- /dev/null +++ b/drivers/input/fingerprint/madev.c @@ -0,0 +1,858 @@ +/* Copyright (C) MicroArray + * MicroArray Fprint Driver Code for REE enviroment + * madev.c + * Date: 2017-3-9 + * Version: v4.0.05 + * Author: guq + * Contact: guq@microarray.com.cn + */ +#include "madev.h" + + +//spdev use for recording the data for other use +static unsigned int irq, ret; +static unsigned int ma_drv_reg; +static unsigned int ma_speed; +static unsigned int is_screen_on; +static struct notifier_block notifier; +static unsigned int int_pin_state; +static unsigned int compatible; +static unsigned int screen_flag; +static DECLARE_WAIT_QUEUE_HEAD(screenwaitq); +static DECLARE_WAIT_QUEUE_HEAD(gWaitq); +static DECLARE_WAIT_QUEUE_HEAD(U1_Waitq); +static DECLARE_WAIT_QUEUE_HEAD(U2_Waitq); +#ifdef CONFIG_PM_WAKELOCKS +struct wakeup_source *gIntWakeLock = NULL; +struct wakeup_source *gProcessWakeLock = NULL; +#else +struct wake_lock gIntWakeLock; +struct wake_lock gProcessWakeLock; +#endif +struct work_struct gWork; +struct workqueue_struct *gWorkq; +// +static LIST_HEAD(dev_list); +static DEFINE_MUTEX(dev_lock); +static DEFINE_MUTEX(drv_lock); +static DEFINE_MUTEX(ioctl_lock); +#ifdef COMPATIBLE_VERSION3 +static DECLARE_WAIT_QUEUE_HEAD(drv_waitq); +#endif + +static struct fprint_dev *sdev = NULL; +static struct fprint_spi *smas = NULL; + +//static u8 stxb[FBUF]; +//static u8 srxb[FBUF]; +u8 *stxb = NULL; +u8 *srxb = NULL; +//extern char *Fingerprint_name; + +int MA_DRV_VERSION=0x00004005; + +static void mas_work(struct work_struct *pws) { + smas->f_irq = 1; + wake_up(&gWaitq); +#ifdef COMPATIBLE_VERSION3 + wake_up(&drv_waitq); +#endif +} + +static irqreturn_t mas_interrupt(int irq, void *dev_id) { +#ifdef DOUBLE_EDGE_IRQ + if(mas_get_interrupt_gpio(0)==1){ + //TODO IRQF_TRIGGER_RISING + }else{ + //TODO IRQF_TRIGGER_FALLING + } +#else + printk("mas_interrupt.\n"); +#ifdef CONFIG_PM_WAKELOCKS + __pm_wakeup_event(gIntWakeLock, 2000); +#else + wake_lock_timeout(&gIntWakeLock, msecs_to_jiffies(2000)); +#endif + queue_work(gWorkq, &gWork); +#endif + return IRQ_HANDLED; +} + + +/*---------------------------------- fops ------------------------------------*/ + +/* è¯»å†™æ•°æ® + * @buf æ•°æ® + * @len 长度 + * @返回值:0æˆåŠŸï¼Œå¦åˆ™å¤±è´¥ + */ +int mas_sync(u8 *txb, u8 *rxb, int len) { + int ret = 0; +#if 0 //kingsun/zlc: fixed the iuuse which the fingerprint menu disappear from setting. + mutex_lock(&dev_lock); + mas_select_transfer(smas->spi, len); + // smas->xfer.tx_nbits=SPI_NBITS_SINGLE; + smas->xfer.tx_buf = txb; + // smas->xfer.rx_nbits=SPI_NBITS_SINGLE; + smas->xfer.rx_buf = rxb; + smas->xfer.delay_usecs = 1; + smas->xfer.len = len; + smas->xfer.bits_per_word = 8; + smas->xfer.speed_hz = smas->spi->max_speed_hz; + spi_message_init(&smas->msg); + spi_message_add_tail(&smas->xfer, &smas->msg); + ret = spi_sync(smas->spi, &smas->msg); + mutex_unlock(&dev_lock); +#else +struct spi_message m; +struct spi_transfer t = { + .tx_buf = txb, + .rx_buf = rxb, + .len = len, + .delay_usecs = 1, + .bits_per_word = 8, + .speed_hz = smas->spi->max_speed_hz, + }; + + mutex_lock(&dev_lock); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + printk("[%s][%d] spi_message %p \r\n",__func__,__LINE__, &m); + ret = spi_sync(smas->spi, &m); + mutex_unlock(&dev_lock); +#endif + return ret; +} + + + +/* è¯»æ•°æ® + * @return æˆåŠŸ:count, -1count太大,-2通讯失败, -3æ‹·è´å¤±è´¥ + */ +static ssize_t mas_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { + int val, ret = 0; + // MALOGD("start"); + ret = mas_sync(stxb, srxb, count); + if(ret) { + MALOGW("mas_sync failed."); + return -2; + } + ret = copy_to_user(buf, srxb, count); + if(!ret) val = count; + else { + val = -3; + MALOGW("copy_to_user failed."); + } + // MALOGD("end."); + return val; +} + + +static void mas_set_input(void) { + struct input_dev *input = NULL; + input = input_allocate_device(); + if (!input) { + MALOGW("input_allocate_device failed."); + return ; + } + set_bit(EV_KEY, input->evbit); + set_bit(EV_SYN, input->evbit); + set_bit(FINGERPRINT_SWIPE_UP, input->keybit); //å•è§¦ + set_bit(FINGERPRINT_SWIPE_DOWN, input->keybit); + set_bit(FINGERPRINT_SWIPE_LEFT, input->keybit); + set_bit(FINGERPRINT_SWIPE_RIGHT, input->keybit); + set_bit(FINGERPRINT_TAP, input->keybit); + set_bit(FINGERPRINT_DTAP, input->keybit); + set_bit(FINGERPRINT_LONGPRESS, input->keybit); + + set_bit(KEY_POWER, input->keybit); + + input->name = MA_CHR_DEV_NAME; + input->id.bustype = BUS_SPI; + ret = input_register_device(input); + if (ret) { + input_free_device(input); + MALOGW("failed to register input device."); + return; + } + smas->input = input; +} + + + +//static int mas_ioctl (struct inode *node, struct file *filp, unsigned int cmd, uns igned long arg) +//this function only supported while the linux kernel version under v2.6.36,while the kernel version under v2.6.36, use this line +static long mas_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { + printk("mas_ioctl: cmd=0x%x\n",cmd); + switch(cmd){ + case TIMEOUT_WAKELOCK: //å»¶æ—¶é” timeout lock +#ifdef CONFIG_PM_WAKELOCKS + __pm_wakeup_event(gProcessWakeLock, 5000); +#else + wake_lock_timeout(&gProcessWakeLock, 5*HZ); +#endif + break; + case SLEEP: //remove the process out of the runqueue + smas->f_irq = 0; + ret = wait_event_freezable(gWaitq, smas->f_irq != 0); + break; + case WAKEUP: //wake up, schedule the process into the runqueue + smas->f_irq = 1; + wake_up(&gWaitq); + break; + case ENABLE_CLK: + mas_enable_spi_clock(smas->spi); //if the spi clock is not opening always, do this methods + break; + case DISABLE_CLK: + mas_disable_spi_clock(smas->spi); //disable the spi clock + break; + case ENABLE_INTERRUPT: + enable_irq(irq); //enable the irq,in fact, you can make irq enable always + break; + case DISABLE_INTERRUPT: + disable_irq(irq); //disable the irq + break; + case TAP_DOWN: + input_report_key(smas->input, FINGERPRINT_TAP, 1); + input_sync(smas->input); //tap down + break; + case TAP_UP: + input_report_key(smas->input, FINGERPRINT_TAP, 0); + input_sync(smas->input); //tap up + break; + case SINGLE_TAP: + input_report_key(smas->input, FINGERPRINT_TAP, 1); + input_sync(smas->input); + input_report_key(smas->input, FINGERPRINT_TAP, 0); + input_sync(smas->input); //single tap + break; + case DOUBLE_TAP: + input_report_key(smas->input, FINGERPRINT_DTAP, 1); + input_sync(smas->input); + input_report_key(smas->input, FINGERPRINT_DTAP, 0); + input_sync(smas->input); //double tap + break; + case LONG_TAP: + input_report_key(smas->input, FINGERPRINT_LONGPRESS, 1); + input_sync(smas->input); + input_report_key(smas->input, FINGERPRINT_LONGPRESS, 0); + input_sync(smas->input); //long tap + break; + case MA_KEY_UP: + input_report_key(smas->input, FINGERPRINT_SWIPE_UP, 1); + input_sync(smas->input); + input_report_key(smas->input, FINGERPRINT_SWIPE_UP, 0); + input_sync(smas->input); + break; + case MA_KEY_LEFT: + input_report_key(smas->input, FINGERPRINT_SWIPE_LEFT, 1); + input_sync(smas->input); + input_report_key(smas->input, FINGERPRINT_SWIPE_LEFT, 0); + input_sync(smas->input); + break; + case MA_KEY_DOWN: + input_report_key(smas->input, FINGERPRINT_SWIPE_DOWN, 1); + input_sync(smas->input); + input_report_key(smas->input, FINGERPRINT_SWIPE_DOWN, 0); + input_sync(smas->input); + break; + case MA_KEY_RIGHT: + input_report_key(smas->input, FINGERPRINT_SWIPE_RIGHT, 1); + input_sync(smas->input); + input_report_key(smas->input, FINGERPRINT_SWIPE_RIGHT, 0); + input_sync(smas->input); + break; + case SET_MODE: + mutex_lock(&ioctl_lock); + + + ret = copy_from_user(&ma_drv_reg, (unsigned int*)arg, sizeof(unsigned int)); + mutex_unlock(&ioctl_lock); + break; + case GET_MODE: + mutex_lock(&ioctl_lock); + ret = copy_to_user((unsigned int*)arg, &ma_drv_reg, sizeof(unsigned int)); + mutex_unlock(&ioctl_lock); + break; + case MA_IOC_GVER: + mutex_lock(&ioctl_lock); + //*((unsigned int*)arg) = MA_DRV_VERSION; + ret = copy_to_user((unsigned int *)arg, &MA_DRV_VERSION, sizeof(unsigned int)); + mutex_unlock(&ioctl_lock); + break; + case SCREEN_ON: + mas_fingerprint_power(1); + break; + case SCREEN_OFF: + mas_fingerprint_power(0); + break; + case SET_SPI_SPEED: + ret = copy_from_user(&ma_speed, (unsigned int*)arg, sizeof(unsigned int)); + //ma_spi_change(smas->spi, ma_speed, 0); + break; + case WAIT_FACTORY_CMD: + smas->u2_flag = 0; + ret = wait_event_freezable(U2_Waitq, smas->u2_flag != 0); + break; + case WAKEUP_FINGERPRINTD: + smas->u2_flag = 1; + wake_up(&U2_Waitq); + break; + case WAIT_FINGERPRINTD_RESPONSE: + smas->u1_flag = 0; + ret = wait_event_freezable(U1_Waitq, smas->u1_flag != 0); + mutex_lock(&ioctl_lock); + ret = copy_to_user((unsigned int*)arg, &ma_drv_reg, sizeof(unsigned int)); + mutex_unlock(&ioctl_lock); + break; + case WAKEUP_FACTORY_TEST_SEND_FINGERPRINTD_RESPONSE: + mutex_lock(&ioctl_lock); + ret = copy_from_user(&ma_drv_reg, (unsigned int*)arg, sizeof(unsigned int)); + mutex_unlock(&ioctl_lock); + msleep(4); + smas->u1_flag = 1; + wake_up(&U1_Waitq); + break; + case WAIT_SCREEN_STATUS_CHANGE: + screen_flag = 0; + ret = wait_event_freezable(screenwaitq, screen_flag != 0); + mutex_lock(&ioctl_lock); + ret = copy_to_user((unsigned int*)arg, &is_screen_on, sizeof(unsigned int)); + mutex_unlock(&ioctl_lock); + break; + case GET_INTERRUPT_STATUS: + int_pin_state = mas_get_interrupt_gpio(0); + if(int_pin_state == 0 || int_pin_state == 1){ + mutex_lock(&ioctl_lock); + ret = copy_to_user((unsigned int*)arg, &int_pin_state, sizeof(unsigned int)); + mutex_unlock(&ioctl_lock); + } + break; + case GET_SCREEN_STATUS: + mutex_lock(&ioctl_lock); + ret = copy_to_user((unsigned int*)arg, &is_screen_on, sizeof(unsigned int)); + mutex_unlock(&ioctl_lock); + break; + default: + ret = -EINVAL; + MALOGW("mas_ioctl no such cmd"); + } + printk("mas_ioctl: end.\n"); + return ret; +} + +#ifdef CONFIG_COMPAT +static long mas_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + retval = filp->f_op->unlocked_ioctl(filp, cmd, arg); + return retval; +} +#endif + +#ifdef COMPATIBLE_VERSION3 +int version3_ioctl(int cmd, int arg){ + int ret = 0; + + pr_info("%s: start cmd=0x%.3x arg=%d\n", __func__, cmd, arg); + + switch (cmd) { + case IOCTL_DEBUG: + sdeb = (u8) arg; + break; + case IOCTL_IRQ_ENABLE: + break; + case IOCTL_SPI_SPEED: + smas->spi->max_speed_hz = (u32) arg; + spi_setup(smas->spi); + break; + case IOCTL_COVER_NUM: + ret = COVER_NUM; + break; + case IOCTL_GET_VDATE: + ret = 20160425; + break; + case IOCTL_CLR_INTF: + smas->f_irq = FALSE; + break; + case IOCTL_GET_INTF: + ret = smas->f_irq; + break; + case IOCTL_REPORT_FLAG: + smas->f_repo = arg; + break; + case IOCTL_REPORT_KEY: + input_report_key(smas->input, arg, 1); + input_sync(smas->input); + input_report_key(smas->input, arg, 0); + input_sync(smas->input); + break; + case IOCTL_SET_WORK: + smas->do_what = arg; + break; + case IOCTL_GET_WORK: + ret = smas->do_what; + break; + case IOCTL_SET_VALUE: + smas->value = arg; + break; + case IOCTL_GET_VALUE: + ret = smas->value; + break; + case IOCTL_TRIGGER: + smas->f_wake = TRUE; + wake_up_interruptible(&drv_waitq); + break; + case IOCTL_WAKE_LOCK: +#ifdef CONFIG_PM_WAKELOCKS + if(&smas->wl != NULL) { + if(&smas->wl->active) { + __pm_stay_awake(&smas->wl); + } + } +#else + if(!wake_lock_active(&smas->wl)) + wake_lock(&smas->wl); +#endif + break; + case IOCTL_WAKE_UNLOCK: +#ifdef CONFIG_PM_WAKELOCKS + if(&smas->wl != NULL) { + if(&smas->wl->active) { + __pm_relax(&smas->wl); + } + } +#else + if(wake_lock_active(&smas->wl)) + wake_unlock(&smas->wl); +#endif + break; + case IOCTL_KEY_DOWN: + input_report_key(smas->input, KEY_F11 1); + input_sync(smas->input); + break; + case IOCTL_KEY_UP: + input_report_key(smas->input, KEY_F11, 0); + input_sync(smas->input); + break; + } + + printd("%s: end. ret=%d f_irq=%d, f_repo=%d\n", __func__, ret, smas->f_irq, smas->f_repo); + + return ret; + +} +#endif + +/* å†™æ•°æ® + * @return æˆåŠŸ:count, -1count太大,-2æ‹·è´å¤±è´¥ + */ +static ssize_t mas_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { + int val = 0; + // MALOGD("start"); + if(count==6) { //cmd ioctl, old version used the write interface to do ioctl, this is only for the old version + int cmd, arg; + u8 tmp[6]; + ret = copy_from_user(tmp, buf, count); + cmd = tmp[0]; + cmd <<= 8; + cmd += tmp[1]; + arg = tmp[2]; + arg <<= 8; + arg += tmp[3]; + arg <<= 8; + arg += tmp[4]; + arg <<= 8; + arg += tmp[5]; +#ifdef COMPATIBLE_VERSION3 + val = (int)version3_ioctl(NULL, (unsigned int)cmd, (unsigned long)arg); +#endif + } else { + memset(stxb, 0, FBUF); + ret = copy_from_user(stxb, buf, count); + if(ret) { + MALOGW("copy form user failed"); + val = -2; + } else { + val = count; + } + } + // MALOGD("end"); + return val; +} +void * kernel_memaddr = NULL; +unsigned long kernel_memesize = 0; + +int mas_mmap(struct file *filp, struct vm_area_struct *vma){ + unsigned long page; + if ( !kernel_memaddr ) { + kernel_memaddr = kmalloc(128*1024, GFP_KERNEL); + if( !kernel_memaddr ) { + return -1; + } + } + page = virt_to_phys((void *)kernel_memaddr) >> PAGE_SHIFT; + vma->vm_page_prot=pgprot_noncached(vma->vm_page_prot); + if( remap_pfn_range(vma, vma->vm_start, page, (vma->vm_end - vma->vm_start), + vma->vm_page_prot) ) + return -1; + vma->vm_flags |= VM_RESERVED; + printk("remap_pfn_rang page:[%lu] ok.\n", page); + return 0; +} + +#ifdef COMPATIBLE_VERSION3 +static unsigned int mas_poll(struct file *filp, struct poll_table_struct *wait) { + unsigned int mask = 0; + + printd("%s: start. f_irq=%d f_repo=%d f_wake=%d\n", + __func__, smas->f_irq, smas->f_repo, smas->f_wake); + + poll_wait(filp, &drv_waitq, wait); + if(smas->f_irq && smas->f_repo) { + smas->f_repo = FALSE; + mask |= POLLIN | POLLRDNORM; + } else if( smas->f_wake ) { + smas->f_wake = FALSE; + mask |= POLLPRI; + } + + printd("%s: end. mask=%d\n", __func__, mask); + + return mask; +} +#endif + +/*---------------------------------- fops ------------------------------------*/ +static const struct file_operations sfops = { + .owner = THIS_MODULE, + .write = mas_write, + .read = mas_read, + .unlocked_ioctl = mas_ioctl, + .mmap = mas_mmap, + //.ioctl = mas_ioctl, + //using the previous line replacing the unlock_ioctl while the linux kernel under version2.6.36 +#ifdef CONFIG_COMPAT + .compat_ioctl = mas_compat_ioctl, +#endif +#ifdef COMPATIBLE_VERSION3 + .poll = mas_poll, +#endif +}; +/*---------------------------------- fops end ---------------------------------*/ + +static int init_file_node(void) +{ + int ret; + //MALOGF("start"); + ret = alloc_chrdev_region(&sdev->idd, 0, 1, MA_CHR_DEV_NAME); + if(ret < 0) + { + MALOGW("alloc_chrdev_region error!"); + return -1; + } + sdev->chd = cdev_alloc(); + if (!sdev->chd) + { + MALOGW("cdev_alloc error!"); + return -1; + } + sdev->chd->owner = THIS_MODULE; + sdev->chd->ops = &sfops; + cdev_add(sdev->chd, sdev->idd, 1); + sdev->cls = class_create(THIS_MODULE, MA_CHR_DEV_NAME); + if (IS_ERR(sdev->cls)) { + MALOGE("class_create"); + return -1; + } + sdev->dev = device_create(sdev->cls, NULL, sdev->idd, NULL, MA_CHR_FILE_NAME); + ret = IS_ERR(sdev->dev) ? PTR_ERR(sdev->dev) : 0; + if(ret){ + MALOGE("device_create"); + } + //MALOGF("end"); + return 0; +} + +static int deinit_file_node(void) +{ + cdev_del(sdev->chd); + sdev->chd = NULL; + kfree(sdev->chd); + device_destroy(sdev->cls, sdev->idd); + unregister_chrdev_region(sdev->idd, 1); + class_destroy(sdev->cls); + return 0; +} + +static int init_interrupt(void) +{ + const char*tname = MA_EINT_NAME; + irq = mas_get_irq(); + if(irq<=0){ + ret = irq; + MALOGE("mas_get_irq"); + } +#ifdef DOUBLE_EDGE_IRQ + ret = request_irq(irq, mas_interrupt, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, tname, NULL); +#else + ret = request_irq(irq, mas_interrupt, IRQF_TRIGGER_RISING, tname, NULL); +#endif + if(ret<0){ + MALOGE("request_irq"); + } + return ret; +} +static int deinit_interrupt(void) +{ + if(irq) + {disable_irq(irq); + free_irq(irq, NULL); + } + return 0; +} + +static int init_vars(void) +{ + sdev = kmalloc(sizeof(struct fprint_dev), GFP_KERNEL); + smas = kmalloc(sizeof(struct fprint_spi), GFP_KERNEL); + stxb = kmalloc(FBUF, GFP_KERNEL); + srxb = kmalloc(FBUF, GFP_KERNEL); + if (sdev==NULL || smas==NULL ) { + MALOGW("smas kmalloc failed."); + if(sdev!=NULL) kfree(sdev); + if(smas!=NULL) kfree(smas); + return -ENOMEM; + } + + printk("[%s][%d]smas %p \r\n",__func__,__LINE__, smas); + printk("[%s][%d]sdev %p \r\n",__func__,__LINE__, sdev); + printk("[%s][%d]stxb %p \r\n",__func__,__LINE__, stxb); + printk("[%s][%d]srxb %p \r\n",__func__,__LINE__, srxb); + +#ifdef CONFIG_PM_WAKELOCKS + gIntWakeLock = wakeup_source_register(NULL, "microarray_int_wakelock"); + gProcessWakeLock = wakeup_source_register(NULL, "microarray_process_wakelock"); +#else + wake_lock_init(&gIntWakeLock, WAKE_LOCK_SUSPEND,"microarray_int_wakelock"); + wake_lock_init(&gProcessWakeLock, WAKE_LOCK_SUSPEND,"microarray_process_wakelock"); +#endif + INIT_WORK(&gWork, mas_work); + gWorkq = create_singlethread_workqueue("mas_workqueue"); + if (!gWorkq) { + MALOGW("create_single_workqueue error!"); + return -ENOMEM; + } + return 0; +} +static int deinit_vars(void) +{ + destroy_workqueue(gWorkq); +#ifdef CONFIG_PM_WAKELOCKS + wakeup_source_unregister(gIntWakeLock); + wakeup_source_unregister(gProcessWakeLock); +#else + wake_lock_destroy(&gIntWakeLock); + wake_lock_destroy(&gProcessWakeLock); +#endif + if(sdev!=NULL) kfree(sdev); + if(smas!=NULL) kfree(smas); + // if(stxb!=NULL) kfree(stxb); + // if(srxb!=NULL) kfree(srxb); + return 0; +} + +static int init_spi(struct spi_device *spi){ + msleep(50); + smas->spi = spi; + smas->spi->max_speed_hz = SPI_SPEED; + smas->spi->mode = SPI_MODE_0; //CPOL=CPHA=0 + smas->spi->bits_per_word = 8; + spi_setup(spi); + INIT_LIST_HEAD(&smas->dev_entry); + return 0; +} + +static int deinit_spi(struct spi_device *spi){ + smas->spi = NULL; + mas_disable_spi_clock(spi); + return 0; +} +/* + * init_connect function to check whether the chip is microarray's + * @return 0 not 1 yes + * param void + */ +int init_connect(void){ + int i; + int res = 0; + + for(i=0; i<4; i++){ + stxb[0] = 0x8c; + stxb[1] = 0xff; + stxb[2] = 0xff; + stxb[3] = 0xff; + mas_sync(stxb, srxb, 4); + msleep(8); + stxb[0] = 0x00; + stxb[1] = 0xff; + stxb[2] = 0xff; + stxb[3] = 0xff; + ret = mas_sync(stxb, srxb, 4); + if(ret!=0) MALOGW("do init_connect failed!"); + printk("guq srxb[3] = %d srxb[2] = %d\n", srxb[3], srxb[2]); + if(srxb[3] == 0x41 || srxb[3] == 0x45 ) { + res = 1; + } else { + res = 0; + } + } + if(res == 1) { + stxb[0] = 0x80; + stxb[1] = 0xff; + stxb[2] = 0xff; + stxb[3] = 0xff; + mas_sync(stxb, srxb, 4); + } + return res; +} + + +int deinit_connect(void){ + return 0; +} + +static int mas_fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data){ + struct fb_event *evdata = data; + unsigned int blank; + if(event != FB_EVENT_BLANK) { + return 0; + } + blank = *(int *)evdata->data; + switch(blank){ + case FB_BLANK_UNBLANK: + is_screen_on = 1; + break; + case FB_BLANK_POWERDOWN: + is_screen_on = 0; + break; + default: + break; + } + screen_flag = 1; + wake_up(&screenwaitq); + return 0; +} + +static int init_notifier_call(void); +static int deinit_notifier_call(void); + +static int init_notifier_call(){ + notifier.notifier_call = mas_fb_notifier_callback; + fb_register_client(¬ifier); + is_screen_on = 1; + return 0; +} +static int deinit_notifier_call(){ + fb_unregister_client(¬ifier); + return 0; +} + +int mas_probe(struct spi_device *spi) { + int ret; + + ret= mas_qcm_platform_init(spi); + if(ret) + goto err0; + + ret = init_vars(); + if(ret){ + goto err1; + } + + ret = init_interrupt(); + if(ret){ + goto err2; + } + + ret = init_file_node(); + if(ret){ + goto err3; + } + + ret = init_spi(spi); + if(ret){ + goto err4; + } +#ifdef REE + ret = init_connect(); +#elif defined TEE + ret = 1; +#endif + if(ret == 0){//not chip + compatible = 0; + pr_info("%s:init_connect failed.\n", __func__); + goto err5; + } + else + pr_info("%s:init_connect successfully.\n", __func__); + mas_set_input(); + MALOGF("end"); + ret = init_notifier_call(); + if(ret != 0){ + ret = -ENODEV; + goto err6; + } + mas_set_wakeup(spi); + //Fingerprint_name="Microarray_A121N"; + pr_info("%s:completed.\n", __func__); + return ret; + +err6: + deinit_notifier_call(); +err5: + deinit_connect(); +err4: + deinit_spi(spi); +err3: + deinit_file_node(); +err2: + mas_qcm_platform_uninit(spi); +err1: + deinit_interrupt(); + deinit_vars(); +err0: + return -EINVAL; +} + +int mas_remove(struct spi_device *spi) { + deinit_file_node(); + deinit_interrupt(); + deinit_vars(); + return 0; +} + + +static int __init mas_init(void) +{ + int ret = 0; + MALOGF("start"); + compatible = 1; + ret = mas_get_platform(); + if(ret){ + MALOGE("mas_get_platform"); + } + + return ret; +} + +static void __exit mas_exit(void) +{ +} + +late_initcall_sync(mas_init); +module_exit(mas_exit); + +MODULE_AUTHOR("Microarray"); +MODULE_DESCRIPTION("Driver for microarray fingerprint sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/fingerprint/madev.h b/drivers/input/fingerprint/madev.h new file mode 100755 index 000000000000..d7ce8975e72a --- /dev/null +++ b/drivers/input/fingerprint/madev.h @@ -0,0 +1,228 @@ +/* Copyright (C) MicroArray + * MicroArray Fprint Driver Code * madev.h + * Date: 2017-3-9 + * Version: v4.0.05 + * Author: guq + * Contact: guq@microarray.com.cn + */ + +#ifndef __MADEV_H_ +#define __MADEV_H_ + + +//settings macro +#define QUALCOMM //[MTK|QUALCOMM|SPRD] + +#define REE //[TEE|REE] //select platform + +#define MALOGD_LEVEL KERN_EMERG //[KERN_DEBUG|KERN_EMERG] usually, the debug level is used for the release version + +#define MA_CHR_FILE_NAME "madev0" //do not neeed modify usually +#define MA_CHR_DEV_NAME "madev" //do not neeed modify usually + +#define MA_EINT_NAME "afs121_irq" + + +//#define DOUBLE_EDGE_IRQ + +//#define COMPATIBLE_VERSION3 + +//key define just modify the KEY_FN_* for different platform +#define FINGERPRINT_SWIPE_UP KEY_FN_F1//827 +#define FINGERPRINT_SWIPE_DOWN KEY_FN_F2//828 +#define FINGERPRINT_SWIPE_LEFT KEY_FN_F3//829 +#define FINGERPRINT_SWIPE_RIGHT KEY_FN_F4//830 +#define FINGERPRINT_TAP KEY_FN_F5// 831 +#define FINGERPRINT_DTAP KEY_FN_F6// 832 +#define FINGERPRINT_LONGPRESS KEY_FN_F7//833 + +//key define end +#define VM_RESERVED (VM_DONTEXPAND | VM_DONTDUMP) //kingsun/zlc: + +//old macro +#define SPI_SPEED (6*1000000) //120/121:10M, 80/81:6M + +//表é¢ç±»åž‹ +#define COVER_T 1 +#define COVER_N 2 +#define COVER_M 3 +#define COVER_NUM COVER_N + +//指纹类型 +#define AFS120 0x78 +//#define AFS80 0x50 + +#define FBUF (32*1024) + +#if defined(AFS120) + #define W 120 //宽 + #define H 120 //高 + #define WBUF 121 +#elif defined(AFS80) + #define W 80 //宽 + #define H 192 //高 + #define WBUF 81 + #define FIMG (W*H) +#endif + +//settings macro end +#include +#include +#include +//this two head file for the screen on/off test +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_PM_WAKELOCKS +#include +#else +#include +#endif +#include +#include "ioctl_cmd.h" + +#ifdef MTK +#include "mtk-settings.h" +#elif defined QUALCOMM +#include "qcom-settings.h" +#elif defined SPRD +#include "sprd-settings.h" +#elif defined X86 +#include "x86-settings.h" +#endif + + +//value define + //fprint_spi struct use to save the value +struct fprint_spi { + u8 do_what; //工作内容 + u8 f_wake; //唤醒标志 + int value; + volatile u8 f_irq; //中断标志 + volatile u8 u1_flag; //reserve for ours thread interrupt + volatile u8 u2_flag; //reserve for ours thread interrupt + volatile u8 f_repo; //上报开关 + spinlock_t spi_lock; + struct spi_device *spi; + struct list_head dev_entry; + struct spi_message msg; + struct spi_transfer xfer; + struct input_dev *input; + struct work_struct work; + struct workqueue_struct *workq; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend suspend; +#endif +#ifdef CONFIG_PM_WAKELOCKS + struct wakeup_source *wl; +#else + struct wake_lock wl; +#endif +}; +//end + +struct fprint_dev { + dev_t idd; + int major; + int minor; + struct cdev *chd; + struct class *cls; + struct device *dev; +}; + + +//function define + +//extern the settings.h function +extern int mas_qcm_platform_init(struct spi_device *spi); +extern int mas_qcm_platform_uninit(struct spi_device *spi); +extern int mas_fingerprint_power(bool flags); + +extern void mas_select_transfer(struct spi_device *spi, int len); +extern int mas_finger_get_gpio_info(struct platform_device *pdev); +extern int mas_finger_set_gpio_info(int cmd); +extern void mas_enable_spi_clock(struct spi_device *spi); +extern void mas_disable_spi_clock(struct spi_device *spi); +extern unsigned int mas_get_irq(void); +extern int mas_get_platform(void); +extern int mas_remove_platform(void); +extern void ma_spi_change(struct spi_device *spi, unsigned int speed, int flag); +extern void mas_set_wakeup(struct spi_device *spi); +extern int mas_get_interrupt_gpio(unsigned int index); +//end + +//use for the log print +#define MALOG_TAG "MAFP_" +#define MALOGE(x) printk(KERN_ERR "%s%s: error log! the function %s is failed, ret = %d\n", MALOG_TAG, __func__, x, ret); //error log +#define MALOGF(x) printk(MALOGD_LEVEL "%s%s: debug log! %s!\n", MALOG_TAG, __func__, x); //flag log +#define MALOGD(x) MALOGF(x) //debug log +#define MALOGW(x) printk(KERN_WARNING "%s%s: warning log! the function %s's ret = %d\n", MALOG_TAG, __func__,x, ret); //warning log +//use for the log print + + +/** + * the old ioctl command, compatible for the old version + */ +//ioctl cmd +#ifdef COMPATIBLE_VERSION3 +#define IOCTL_DEBUG 0x100 //è°ƒè¯•ä¿¡æ¯ //debug message +#define IOCTL_IRQ_ENABLE 0x101 //中断使能 //enable interrupt +#define IOCTL_SPI_SPEED 0x102 //SPI速度 //spi speed +#define IOCTL_READ_FLEN 0x103 //读帧长度(ä¿ç•™) //the length of one frame +#define IOCTL_LINK_DEV 0x104 //连接设备(ä¿ç•™) //connect the device +#define IOCTL_COVER_NUM 0x105 //ææ–™ç¼–å· //the index of the material +#define IOCTL_GET_VDATE 0x106 //版本日期 //the date fo the version + +#define IOCTL_CLR_INTF 0x110 //清除中断标志 +#define IOCTL_GET_INTF 0x111 //获å–中断标志 +#define IOCTL_REPORT_FLAG 0x112 //上报标志 +#define IOCTL_REPORT_KEY 0x113 //上报键值 +#define IOCTL_SET_WORK 0x114 //设置工作 +#define IOCTL_GET_WORK 0x115 //获å–工作 +#define IOCTL_SET_VALUE 0x116 //设值 +#define IOCTL_GET_VALUE 0x117 //å–值 +#define IOCTL_TRIGGER 0x118 //è‡ªè§¦å‘ +#define IOCTL_WAKE_LOCK 0x119 //å”¤é†’ä¸Šé” +#define IOCTL_WAKE_UNLOCK 0x120 //å”¤é†’è§£é” + +#define IOCTL_SCREEN_ON 0x121 + +#define IOCTL_KEY_DOWN 0x121 //按下 +#define IOCTL_KEY_UP 0x122 //抬起 +#define IOCTL_SET_X 0x123 //å移X +#define IOCTL_SET_Y 0x124 //å移Y +#define IOCTL_KEY_TAP 0x125 //å•å‡» +#define IOCTL_KEY_DTAP 0x126 //åŒå‡» +#define IOCTL_KEY_LTAP 0x127 //长按 + +#define IOCTL_ENABLE_CLK 0x128 +#define TRUE 1 +#define FALSE 0 +#endif + +#endif /* __MADEV_H_ */ + diff --git a/drivers/input/fingerprint/qcom-settings.c b/drivers/input/fingerprint/qcom-settings.c new file mode 100755 index 000000000000..8b0364027cf4 --- /dev/null +++ b/drivers/input/fingerprint/qcom-settings.c @@ -0,0 +1,546 @@ +/* Copyright (C) MicroArray + * MicroArray Fprint Driver Code + * mtk-settings.c + * Date: 2016-11-17 + * Version: v4.0.03 + * Author: guq + * Contact: guq@microarray.com.cn + */ + +#include "qcom-settings.h" + +struct mas_platform_data { + int irq_gpio; + int finger_en_gpio; + int reset_gpio; + struct regulator *vcc_io_l6; + int vcc_en_gpio; + int cs_gpio; + int external_supply_mv; + int txout_boost; + int force_hwid; +//kingsun/zlc: + int clk_enabled; + struct clk *core_clk; + struct clk *iface_clk; + unsigned int int_irq; +//end +}; + +struct mas_platform_data *mas_pdata; +int first_int_after_suspend=0; //kingsun/zlc: +#ifdef CONFIG_OF +/* -------------------------------------------------------------------- */ +#ifdef CONFIG_HAS_EARLYSUSPEND +int mas_suspend(struct spi_device *spi, pm_message_t mesg) +{ + printk("%s start\n",__func__); + if (device_may_wakeup(&spi->dev)) + enable_irq_wake(mas_pdata->int_irq); + + printk("mas_ioctl_clk_disable\n"); + mas_disable_spi_clock(spi); + first_int_after_suspend=1; + printk("%s end\n",__func__); + return 0; +} + + +/* -------------------------------------------------------------------- */ +int mas_resume(struct spi_device *spi) +{ + printk("%s start\n",__func__); + if (device_may_wakeup(&spi->dev)) + disable_irq_wake(mas_pdata->int_irq); + + printk("mas_enable_spi_clock\n"); + mas_enable_spi_clock(spi); + printk("%s end\n",__func__); + return 0; +} +#else +int mas_suspend(struct device *dev) +{ + struct spi_device *spi = NULL; + printk("%s start\n",__func__); + + spi = to_spi_device(dev); + if (spi == NULL) { + printk("%s get spi device failed\n",__func__); + return -1; + } + + if (device_may_wakeup(dev)) + enable_irq_wake(mas_pdata->int_irq); + + printk("mas_ioctl_clk_disable\n"); + mas_disable_spi_clock(spi); + first_int_after_suspend=1; + printk("%s end\n",__func__); + return 0; +} + +int mas_resume(struct device *dev) +{ + struct spi_device *spi = NULL; + printk("%s start\n",__func__); + + spi = to_spi_device(dev); + if (spi == NULL) { + printk("%s get spi device failed\n",__func__); + return -1; + } + + if (device_may_wakeup(dev)) + disable_irq_wake(mas_pdata->int_irq); + + printk("mas_enable_spi_clock\n"); + mas_enable_spi_clock(spi); + printk("%s end\n",__func__); + return 0; +} +#endif + + +static struct of_device_id mas_of_match[] = { + {.compatible = "microarray,fingerprint",}, + {} +}; + +MODULE_DEVICE_TABLE(of, mas_of_match); + +#ifndef CONFIG_HAS_EARLYSUSPEND +const struct dev_pm_ops mas_pm_ops = { + .suspend = mas_suspend, + .resume = mas_resume, +}; +#endif +#endif + +struct spi_device_id sdev_id = {MA_DRV_NAME, 0}; +struct spi_driver sdrv = { + .driver = { + .name = MA_DRV_NAME, + //.bus = &spi_bus_type, + .owner = THIS_MODULE, +#ifndef CONFIG_HAS_EARLYSUSPEND + .pm = &mas_pm_ops, +#endif +#ifdef CONFIG_OF + .of_match_table = mas_of_match, +#endif + }, + .id_table = &sdev_id, + .probe = mas_probe, + .remove = mas_remove, +#ifdef CONFIG_HAS_EARLYSUSPEND + .suspend = mas_suspend, + .resume = mas_resume, +#endif +}; +//driver end + +/** + * the spi struct date start,for getting the spi_device to set the spi clock enable end + */ + + +void mas_select_transfer(struct spi_device *spi, int len) { + return ; +} + +/* + * set spi speed, often we must check whether the setting is efficient + */ + +void ma_spi_change(struct spi_device *spi, unsigned int speed, int flag) +{ + struct qcom_chip_conf *mcc = (struct qcom_chip_conf *)spi->controller_data; + if(flag == 0) { + mcc->com_mod = 0; + } else { + mcc->com_mod = 1; + } + mcc->high_time = speed; + mcc->low_time = speed; + if(spi_setup(spi) < 0){ + printk("change the spi error!\n"); + } +} + + +static long spi_clk_max_rate(struct clk *clk, unsigned long rate) +{ + long lowest_available, nearest_low, step_size, cur; + long step_direction = -1; + long guess = rate; + int max_steps = 10; + + // FUNC_ENTRY(); + cur = clk_round_rate(clk, rate); + if (cur == rate) + return rate; + + /* if we got here then: cur > rate */ + lowest_available = clk_round_rate(clk, 0); + if (lowest_available > rate) + return -EINVAL; + + step_size = (rate - lowest_available) >> 1; + nearest_low = lowest_available; + + while (max_steps-- && step_size) { + guess += step_size * step_direction; + cur = clk_round_rate(clk, guess); + + if ((cur < rate) && (cur > nearest_low)) + nearest_low = cur; + /* + * if we stepped too far, then start stepping in the other + * direction with half the step size + */ + if (((cur > rate) && (step_direction > 0)) + || ((cur < rate) && (step_direction < 0))) { + step_direction = -step_direction; + step_size >>= 1; + } + } + return nearest_low; +} + +static void spi_clock_set(struct mas_platform_data *data, int speed) +{ + long rate; + int rc; + // FUNC_ENTRY(); + return ; //kingsun/zlc: bypass the operation of spi clock + rate = spi_clk_max_rate(data->core_clk, speed); + if (rate < 0) { + pr_info("%s: no match found for requested clock frequency:%d", + __func__, speed); + return; + } + + rc = clk_set_rate(data->core_clk, rate); +} + + +static int mas_ioctl_clk_init(struct spi_device *spi, struct mas_platform_data *data) +{ return 0; //kingsun/zlc: bypass the operation of spi clock + pr_info("%s: enter\n", __func__); + + data->clk_enabled = 0; + data->core_clk = clk_get(&spi->dev, "core_clk"); + if (IS_ERR_OR_NULL(data->core_clk)) { + pr_err("%s: fail to get core_clk\n", __func__); + return -1; + } + data->iface_clk = clk_get(&spi->dev, "iface_clk"); + if (IS_ERR_OR_NULL(data->iface_clk)) { + pr_err("%s: fail to get iface_clk\n", __func__); + clk_put(data->core_clk); + data->core_clk = NULL; + return -2; + } + return 0; +} + +static int mas_ioctl_clk_uninit(struct mas_platform_data *data) +{ return 0; //kingsun/zlc: bypass the operation of spi clock + pr_info("%s: enter\n", __func__); + + if (!IS_ERR_OR_NULL(data->core_clk)) { + clk_put(data->core_clk); + data->core_clk = NULL; + } + + if (!IS_ERR_OR_NULL(data->iface_clk)) { + clk_put(data->iface_clk); + data->iface_clk = NULL; + } + + return 0; +} + +static int mas_ioctl_clk_enable(struct mas_platform_data *data) +{ + int err; + return 0; //kingsun/zlc: bypass the operation of spi clock + pr_debug("%s: enter\n", __func__); + + if (data->clk_enabled) + return 0; + + err = clk_prepare_enable(data->core_clk); + if (err) { + pr_err("%s: fail to enable core_clk\n", __func__); + return -1; + } + + err = clk_prepare_enable(data->iface_clk); + if (err) { + pr_err("%s: fail to enable iface_clk\n", __func__); + clk_disable_unprepare(data->core_clk); + return -2; + } + + data->clk_enabled = 1; + + return 0; +} + +static int mas_ioctl_clk_disable(struct mas_platform_data *data) +{ return 0; //kingsun/zlc: bypass the operation of spi clock + if (!data->clk_enabled) + return 0; + + clk_disable_unprepare(data->core_clk); + clk_disable_unprepare(data->iface_clk); + data->clk_enabled = 0; + + return 0; +} + + +int mas_get_platform(void) { + int ret; + ret = spi_register_driver(&sdrv); + pr_err("MAFP_ spi_register_driver ret = %d",ret); + + if(ret) { + printk("spi_register_driver ok"); + } + return ret; +} + +int mas_remove_platform(void){ + spi_unregister_driver(&sdrv); + return 0; +} + +static int mas_get_of_pdata(struct device *dev) +{ + struct device_node *node = dev->of_node; + mas_pdata->irq_gpio = of_get_named_gpio(node, "microarray,gpio_irq", 0); + pr_info("%s:irq-gpio = %d\n", __func__, mas_pdata->irq_gpio); + if (mas_pdata->irq_gpio < 0) { + pr_err("irq gpio is missing\n"); + goto of_err; + } + + mas_pdata->finger_en_gpio = of_get_named_gpio(node, "microarray,gpio_pwr", 0); + pr_info("%s:power-gpio = %d\n", __func__,mas_pdata->finger_en_gpio); + if (mas_pdata->finger_en_gpio < 0) { + pr_err("finger_en gpio is missing\n"); + goto of_err; + } + + /*mas_pdata->vcc_io_l6 = regulator_get(dev, "vcc_io"); + if (IS_ERR(mas_pdata->vcc_io_l6)){ + pr_info("%s:Failed to get the regulator for vcc_io. \n", __func__); + goto of_err; + } + else + pr_info("%s:Get the regulator for vcc_io sucessfully.\n", __func__); + */ + + dev_info(dev, "end parse_dt\n"); + return 0; + +of_err: + return -ENODEV; +} + +static int mas_gpio_configure(bool on) +{ + int err = 0; + if (on) { + if (gpio_is_valid(mas_pdata->irq_gpio)) { + err = gpio_request(mas_pdata->irq_gpio, + "mas_irq_gpio"); + if (err) { + pr_info("%s:irq gpio request failed\n",__func__); + goto err_irq_gpio_req; + } + err = gpio_direction_input(mas_pdata->irq_gpio); + if (err) { + pr_info("%s:set_direction for irq gpio failed\n",__func__); + goto err_irq_gpio_dir; + } + } + + if (gpio_is_valid(mas_pdata->finger_en_gpio)) { + err = gpio_request(mas_pdata->finger_en_gpio, + "mas_en_gpio"); + if (err) { + pr_info("%s:en gpio request failed\n",__func__); + goto err_irq_gpio_dir; + } + + err = gpio_direction_output(mas_pdata->finger_en_gpio, 0); + if (err) { + pr_info("%s:set_direction for en gpio failed\n",__func__); + goto err_en_gpio_dir; + } + } + return 0; + } else { + if (gpio_is_valid(mas_pdata->irq_gpio)) + gpio_free(mas_pdata->irq_gpio); + if (gpio_is_valid(mas_pdata->finger_en_gpio)) { + gpio_free(mas_pdata->finger_en_gpio); + } + return 0; + } + +err_en_gpio_dir: + if (gpio_is_valid(mas_pdata->finger_en_gpio)) + gpio_free(mas_pdata->finger_en_gpio); +err_irq_gpio_dir: + if (gpio_is_valid(mas_pdata->irq_gpio)) + gpio_free(mas_pdata->irq_gpio); +err_irq_gpio_req: + return err; +} + + + int mas_fingerprint_power(bool flags) + { + int rc=0; + //int vcc_io; + + if (flags) { + if (gpio_is_valid(mas_pdata->finger_en_gpio)) { + gpio_set_value(mas_pdata->finger_en_gpio, 1); + } + msleep(10); + #if 0 + rc=regulator_enable(mas_pdata->vcc_io_l6); + if (rc < 0){ + pr_err("%s: regulator_enable failed\n", __func__); + goto enable_err; + } + else + pr_info("%s: regulator_enable is successful.\n", __func__); + + vcc_io=regulator_get_voltage(mas_pdata->vcc_io_l6); + pr_info("%s:regulator_get_voltage =%d\n", __func__,vcc_io); + #endif + pr_info("---- power on ok ----\n"); + } else { + if (gpio_is_valid(mas_pdata->finger_en_gpio)) { + gpio_set_value(mas_pdata->finger_en_gpio, 0); + } + msleep(10); + #if 0 + rc=regulator_disable(mas_pdata->vcc_io_l6); + if (rc < 0){ + pr_err("%s:regulator_disable failed\n", __func__); + goto enable_err; + } + else + pr_info("%s:regulator_disable is successful.\n", __func__); + #endif + pr_info("---- power off ok ----\n"); + } +//enable_err: + return rc; +} + + +int mas_qcm_platform_uninit(struct spi_device *spi) +{ int ret=0; + mas_ioctl_clk_uninit(mas_pdata); + mas_fingerprint_power(false); + #if 0 + if(mas_pdata->vcc_io_l6) + regulator_put(mas_pdata->vcc_io_l6); + #endif + mas_gpio_configure(false); + mas_pdata->int_irq=0; + kfree(mas_pdata); + return ret; +} + + +int mas_qcm_platform_init(struct spi_device *spi) +{ + int ret=0; + mas_pdata = kmalloc(sizeof(struct mas_platform_data), GFP_KERNEL); + if (mas_pdata == NULL) { + pr_info("%s:Failed to allocate buffer\n", __func__); + ret=-ENOMEM; + goto err_devm_kzalloc; + } + ret = mas_get_of_pdata(&spi->dev); + if(ret<0) + goto get_of_pdata_err; + + ret = mas_gpio_configure(true); + if(ret<0) + goto fingeriprnt_gpio_configure_err; + + ret = mas_fingerprint_power(true); + if(ret<0) + goto fingeriprnt_power_err; + + if (mas_ioctl_clk_init(spi, mas_pdata)) + goto fingeriprnt_power_err; + + if (mas_ioctl_clk_enable(mas_pdata)) + goto fingeriprnt_clk_enable_failed; + spi_clock_set(mas_pdata, 9600000); + return ret; + +fingeriprnt_clk_enable_failed: + mas_ioctl_clk_uninit(mas_pdata); +fingeriprnt_power_err: + //if(mas_pdata->vcc_io_l6) + //regulator_put(mas_pdata->vcc_io_l6); +fingeriprnt_gpio_configure_err: +get_of_pdata_err: + kfree(mas_pdata); +err_devm_kzalloc: + return ret; +} + +void mas_enable_spi_clock(struct spi_device *spi) +{ + mas_ioctl_clk_enable(mas_pdata); +} + +void mas_disable_spi_clock(struct spi_device *spi) +{ + mas_ioctl_clk_disable(mas_pdata); +} + + +unsigned int mas_get_irq(void){ + unsigned int irq=0; + irq = gpio_to_irq(mas_pdata->irq_gpio); + if(irq) + printk("mas_get_irq: %d\n",irq); + mas_pdata->int_irq=irq; + return irq; +} + + +void mas_set_wakeup(struct spi_device *spi) +{ + device_init_wakeup(&spi->dev, 1); +} + +/* + * this function used for check the interrupt gpio state + * @index 0 gpio level 1 gpio mode, often use 0 + * @return 0 gpio low 1 gpio high if index = 1,the return is the gpio mode + * under 0 the of_property_read_u32_index return errno,check the dts as below: + * last but not least use this function must checkt the label on dts file, after is an example: + * ma_finger: ma_finger{ + * compatible = "mediatek,afs120x"; + * finger_int_pin = <100 0>; + * } + */ +int mas_get_interrupt_gpio(unsigned int index){ + return gpio_get_value(mas_pdata->irq_gpio); +} diff --git a/drivers/input/fingerprint/qcom-settings.h b/drivers/input/fingerprint/qcom-settings.h new file mode 100755 index 000000000000..18c14ed737f6 --- /dev/null +++ b/drivers/input/fingerprint/qcom-settings.h @@ -0,0 +1,125 @@ +/* Copyright (C) MicroArray + * MicroArray Fprint Driver Code + * mtk-settings.h + * Date: 2016-11-17 + * Version: v4.0.03 + * Author: guq + * Contact: guq@microarray.com.cn + */ + +#ifndef __QCOM_SETTINGS_H_ +#define __QCOM_SETTINGS_H_ + + + +#include +#include +#include +#include +#include +#include +//#include "madev.h" +#include +#ifdef CONFIG_PM_WAKELOCKS +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +//macro settings +#define MA_DRV_NAME "madev" + +#define MA_DTS_NAME "mediatek,hct_finger" + +#define MA_EINT_DTS_NAME "mediatek,hct_finger" +//macro settings end + +extern int mas_probe(struct spi_device *spi); +extern int mas_remove(struct spi_device *spi); + +/* add for spi cls ctl start */ +struct mt_spi_t { + struct platform_device *pdev; + void __iomem *regs; + int irq; + int running; +#ifdef CONFIG_PM_WAKELOCKS + struct wakeup_source wk_lock; +#else + struct wake_lock wk_lock; +#endif + struct mt_chip_conf *config; + struct spi_master *master; + + struct spi_transfer *cur_transfer; + struct spi_transfer *next_transfer; + + spinlock_t lock; + struct list_head queue; +#if !defined(CONFIG_MTK_CLKMGR) + struct clk *clk_main; +#endif +}; + +//kingsun/zlc: This struct is porting from mtk, It's no use actually at qcom platform +struct qcom_chip_conf { + int setuptime; + int holdtime; + int high_time; + int low_time; + int cs_idletime; + int ulthgh_thrsh; + int cpol; + int cpha; + int rx_mlsb; + int tx_mlsb; + int tx_endian; + int rx_endian; + int com_mod; + int pause; + int finish_intr; + int deassert; + int ulthigh; + int tckdly; +}; + +/* add for spi cls ctl end this func only used in tee enviroment*/ +//packaging +//void mas_enable_spi_clock(struct spi_device *spi); +//void mas_diasble_spi_clock(struct spi_device *spi); +//packaging end + +//the interface called by madev +void mas_select_transfer(struct spi_device *spi, int len); +int mas_finger_get_gpio_info(struct platform_device *pdev); +int mas_finger_set_gpio_info(int cmd); +void mas_enable_spi_clock(struct spi_device *spi); +void mas_disable_spi_clock(struct spi_device *spi); +unsigned int mas_get_irq(void); +int mas_get_platform(void); +int mas_remove_platform(void); +int mas_power(int cmd); +int get_screen(void); +void ma_spi_change(struct spi_device *spi, unsigned int speed, int flag); +#endif diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 4713957b0cbb..cca76898d488 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -756,4 +756,6 @@ config KEYBOARD_MTK_PMIC To compile this driver as a module, choose M here: the module will be called pmic-keys. +source "drivers/input/keyboard/aw9523/Kconfig" + endif diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 182e92985dbf..06e14cbd80e7 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -67,3 +67,4 @@ obj-$(CONFIG_KEYBOARD_TM2_TOUCHKEY) += tm2-touchkey.o obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o obj-$(CONFIG_KEYBOARD_W90P910) += w90p910_keypad.o +obj-$(CONFIG_KEYBOARD_AW9523B) += aw9523/ diff --git a/drivers/input/keyboard/aw9523/Kconfig b/drivers/input/keyboard/aw9523/Kconfig new file mode 100755 index 000000000000..4479fe0a9557 --- /dev/null +++ b/drivers/input/keyboard/aw9523/Kconfig @@ -0,0 +1,8 @@ +config KEYBOARD_AW9523B + tristate "Awinic aw9523b Qwerty keyboard" + default n + depends on I2C + help + Say Y here if you want to use Awinic aw9523b Qwerty + keyboard as input device. + diff --git a/drivers/input/keyboard/aw9523/Makefile b/drivers/input/keyboard/aw9523/Makefile new file mode 100755 index 000000000000..ced658d48305 --- /dev/null +++ b/drivers/input/keyboard/aw9523/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_KEYBOARD_AW9523B) += aw9523b.o diff --git a/drivers/input/keyboard/aw9523/aw9523b.c b/drivers/input/keyboard/aw9523/aw9523b.c new file mode 100755 index 000000000000..284822984ab4 --- /dev/null +++ b/drivers/input/keyboard/aw9523/aw9523b.c @@ -0,0 +1,2356 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +#include +#include + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#else +#include +#include +#include +#endif +#include + + +#include "aw9523b.h" +#define AWINIC_NAME "aw9523b" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/****************** GPIO Keys driver Start *******************/ +struct gpio_button_data { + const struct gpio_keys_button *button; + struct input_dev *input; + struct gpio_desc *gpiod; + + unsigned short *code; + + struct timer_list release_timer; + unsigned int release_delay; /* in msecs, for IRQ-only buttons */ + + struct delayed_work work; + unsigned int software_debounce; /* in msecs, for GPIO-driven buttons */ + + unsigned int irq; + unsigned int wakeup_trigger_type; + spinlock_t lock; + bool disabled; + bool key_pressed; + bool suspended; +}; + +struct gpio_keys_drvdata { + const struct gpio_keys_platform_data *pdata; + struct pinctrl *key_pinctrl; + struct input_dev *input; + struct mutex disable_lock; + unsigned short *keymap; + struct gpio_button_data data[0]; +}; + +static struct device *global_dev; +//static struct syscore_ops gpio_keys_syscore_pm_ops; +//static void gpio_keys_syscore_resume(void); +/****************** GPIO Keys driver End *******************/ + + +struct aw9523b_platform_data { + +}; + +struct aw9523b_data { + struct i2c_client *client; + struct pinctrl *aw9523_ctrl; + struct pinctrl_state *aw9523_rst_h; + struct pinctrl_state *aw9523_rst_l; + struct pinctrl_state *aw9523_int_active; + struct pinctrl_state *aw9523_int_suspend; + spinlock_t irq_lock; + bool irq_is_disabled; +#ifdef USEVIO + struct regulator *vio; + bool power_enabled; +#endif + int gpio_rst; + int gpio_caps_led; + int gpio_irq; + unsigned char have_suspend; + struct delayed_work work; + struct work_struct fb_notify_work; + struct notifier_block pm_notif; + bool resume_in_workqueue; +}; + + +static u8 capslock_led_enable = 0; + +#define AW9523B_VIO_MIN_UV (1800000) +#define AW9523B_VIO_MAX_UV (1800000) + +static u8 aw9523b_chip_id = 0; + +static struct drm_panel *active_panel; + +static struct aw9523b_data *g_aw9523_data=NULL; +static struct input_dev *aw9523b_input_dev = NULL; +static struct i2c_client *g_client = NULL; + +static const unsigned short key_array[Y_NUM][X_NUM] = { + { 0xFFFF, KEY_H, KEY_B, KEY_7, KEY_UP, KEY_ENTER, KEY_Y, KEY_COMMA }, + { KEY_3, KEY_S, KEY_Z, KEY_M, KEY_I, KEY_9, KEY_W, KEY_J }, + { KEY_LEFT, KEY_G, KEY_V, KEY_6, KEY_RIGHT, KEY_DELETE, KEY_T, KEY_DOT }, + { KEY_SYM, KEY_A, KEY_RIGHTBRACE, KEY_HOMEPAGE, KEY_P, KEY_MINUS, KEY_Q, KEY_L }, + { KEY_BACKSPACE, KEY_D, KEY_X, KEY_K, KEY_SEMICOLON, KEY_EQUAL, KEY_E, KEY_APOSTROPHE }, + { KEY_CAPSLOCK, KEY_BACKSLASH, KEY_LEFTBRACE, KEY_DOWN, KEY_O, KEY_0, KEY_GRAVE, KEY_K }, + { KEY_SPACE, KEY_F, KEY_C, KEY_N, KEY_U, KEY_8, KEY_R, KEY_5 }, + { KEY_ESC, KEY_1, 0xFFFF, 0xFFFF, KEY_2, KEY_4, KEY_TAB, 0xFFFF } +}; + +// This macro sets the interval between polls of the key matrix for ghosted keys (in milliseconds). +// Note that polling only happens while one key is already pressed, to scan the matrix for keys in the same row. +#define POLL_INTERVAL (15) + +#define P1_DEFAULT_VALUE (0) /*p1用æ¥è¾“出,这个值是p1çš„åˆå§‹å€¼*/ + +static int aw9523b_i2c_read(struct i2c_client *client, char *writebuf, + int writelen, char *readbuf, int readlen) +{ + int ret; + + if (writelen > 0) { + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = writelen, + .buf = writebuf, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = readlen, + .buf = readbuf, + }, + }; + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret < 0) + dev_err(&client->dev, "%s: i2c read error.\n", + __func__); + } else { + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = readlen, + .buf = readbuf, + }, + }; + ret = i2c_transfer(client->adapter, msgs, 1); + if (ret < 0) + dev_err(&client->dev, "%s:i2c read error.\n", __func__); + } + return ret; +} + +static int aw9523b_i2c_write(struct i2c_client *client, char *writebuf, + int writelen) +{ + int ret; + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = writelen, + .buf = writebuf, + }, + }; + ret = i2c_transfer(client->adapter, msgs, 1); + if (ret < 0) + dev_err(&client->dev, "%s: i2c write error.\n", __func__); + + return ret; +} + +static int aw9523b_write_reg(u8 addr, const u8 val) +{ + u8 buf[2] = {0}; + + buf[0] = addr; + buf[1] = val; + + return aw9523b_i2c_write(g_client, buf, sizeof(buf)); +} + + + +static int aw9523b_read_reg(u8 addr, u8 *val) +{ + int ret; + ret = aw9523b_i2c_read(g_client, &addr, 1, val, 1); + if (ret < 0) + dev_err(&g_client->dev, "%s:i2c read error.\n", __func__); + return ret; +} +#if 1 +static u8 aw9523b_read_byte(u8 addr) +{ + u8 val; + aw9523b_read_reg(addr,&val); + return val; +} +#endif +static int aw9523b_hw_reset(struct aw9523b_data *data) +{ + int ret = 0; + + #if 0 + ret = gpio_direction_output(data->gpio_rst, 1); + if(ret){ + dev_err(&data->client->dev,"set_direction for pdata->gpio_rst failed\n"); + } + #else + pinctrl_select_state(data->aw9523_ctrl, data->aw9523_rst_h); + #endif + + udelay(50); + + ret = aw9523b_write_reg(REG_SOFT_RESET,0x00);//softrest + if(ret < 0) + { + //can not communicate with aw9523b + dev_err(&data->client->dev,"*****can not communicate with aw9523b\n"); + return 0xff; + }else{ + ret = 0; + } + + #if 0 + ret = gpio_direction_output(data->gpio_rst, 0); + if(ret){ + dev_err(&data->client->dev,"set_direction for pdata->gpio_rst failed\n"); + } + #else + pinctrl_select_state(data->aw9523_ctrl, data->aw9523_rst_l); + #endif + + udelay(250); + #if 0 + ret = gpio_direction_output(data->gpio_rst, 1); + if(ret){ + dev_err(&data->client->dev,"set_direction for pdata->gpio_rst failed\n"); + } + #else + pinctrl_select_state(data->aw9523_ctrl, data->aw9523_rst_h); + #endif + + udelay(50); + + return ret; +} + + +static int aw9523b_i2c_test(struct aw9523b_data *data) +{ + aw9523b_read_reg(IC_ID, &aw9523b_chip_id); + if(aw9523b_chip_id == 0x23 ) // read chip_id =0x23h reg_addr=0x10h + { + printk("aw9523b get chip_id success,chip_id = %d\n", aw9523b_chip_id); + return 0; + } + else + { + printk("aw9523b get chip_id failed, error chip_id = %d\n", aw9523b_chip_id); + return -1; + } +} + +static void aw9523b_config_P1_output(void) +{ + aw9523b_write_reg(CONFIG_PORT1, 0x00); +} + +static void aw9523b_config_P0_input(void) +{ + aw9523b_write_reg(CONFIG_PORT0, 0xFF); +} + +static void aw9523b_enable_P0_interupt(void) +{ + aw9523b_write_reg(INT_PORT0, 0x00); +} + +static void aw9523b_disable_P0_interupt(void) +{ + aw9523b_write_reg(INT_PORT0, 0xff); +} + +static void aw9523b_disable_P1_interupt(void) +{ + aw9523b_write_reg(INT_PORT1, 0xff); +} + +static u8 aw9523b_get_P0_value(void) +{ + u8 value = 0; + aw9523b_read_reg(INPUT_PORT0, &value); + return value; +} + + +static u8 aw9523b_get_P1_value(void) +{ + u8 value = 0; + aw9523b_read_reg(INPUT_PORT1, &value); + return value; +} + +static void aw9523b_set_P1_value(u8 data) +{ + aw9523b_write_reg(OUTPUT_PORT1, data); +} + +static void default_p0_p1_settings(void) +{ + aw9523b_config_P0_input(); + aw9523b_enable_P0_interupt(); + aw9523b_config_P1_output(); + aw9523b_disable_P1_interupt(); + + aw9523b_set_P1_value(P1_DEFAULT_VALUE); + //aw9523b_set_P1_value(0x55); +} + +void aw9523b_irq_disable(struct aw9523b_data *data) +{ + unsigned long irqflags; + + spin_lock_irqsave(&data->irq_lock, irqflags); + if (!data->irq_is_disabled) { + data->irq_is_disabled = true; + disable_irq_nosync(data->client->irq); + } + spin_unlock_irqrestore(&data->irq_lock, irqflags); +} + +/******************************************************* +Function: + Enable irq function +Input: + ts: goodix i2c_client private data +Output: + None. +*********************************************************/ +void aw9523b_irq_enable(struct aw9523b_data *data) +{ + unsigned long irqflags = 0; + + spin_lock_irqsave(&data->irq_lock, irqflags); + if (data->irq_is_disabled) { + enable_irq(data->client->irq); + data->irq_is_disabled = false; + } + spin_unlock_irqrestore(&data->irq_lock, irqflags); +} + + + + +static void aw9523b_work_func(struct work_struct *work) +{ + u8 state[Y_NUM] = { 0, 0, 0, 0, 0, 0, 0, 0 }; // State of the matrix. + static u8 down[Y_NUM] = { 0, 0, 0, 0, 0, 0, 0, 0 }; // Which keys keydown events are actually sent for (excludes ghosted keys). + + struct aw9523b_data *pdata = NULL; + u16 keycode = 0xFF; + int i, j, k; + u8 keymask = 0; + + pdata = container_of((struct delayed_work *) work, struct aw9523b_data, work); + //AW9523_LOG("aw9523b_work_func enter \n"); + + aw9523b_disable_P0_interupt(); + + // Scan the matrix. + for (i = 0; i < Y_NUM; i++) { + // This sets the direction of the register. + // We do this so that there is only one row drive and the rest is hi-z. + // This is important as otherwise another key in the same column will drive the row positive. + aw9523b_write_reg(CONFIG_PORT1, ~(1 << i)); + state[i] = ~aw9523b_get_P0_value(); + } + + aw9523b_write_reg(0x30,0x38); + // Scan the matrix again to verify there was no state change during the scan, as this could mess with the anti-ghosting. + for (i = 0; i < Y_NUM; i++) { + aw9523b_write_reg(CONFIG_PORT1, ~(1 << i)); + if (state[i] != (u8) ~aw9523b_get_P0_value()) { + //AW9523_LOG("mid-scan key state change"); + schedule_delayed_work(&pdata->work, 0); + return; + } + } + + // Restore P1 configuration and set keymask to reflect used columns. + aw9523b_write_reg(CONFIG_PORT1, 0); + for (i = 0; i < Y_NUM; i++) { + keymask |= state[i]; + //AW9523_LOG("p1_value=%x p0_value=%x\n", ~(1 << i), ~state[i]); + } + + // Find changed keys and send keycodes for them. + for (i = 0; i < Y_NUM; i++) { + for (j = 0; j < X_NUM; j++) { + keycode = key_array[i][j]; + if (state[i] & (1 << j) && !(down[i] & (1 << j))) { // Keypress. + // Check if the key is possibly a ghost. + // Talking from the point of view that P1 is the row driver and P0 are columns. + // To avoid ghosting follow the first unambiguous keys that are pressed, and block ambiguous ones. + // - If both the same row and column already have a key pressed, then a key is ambiguous. + // - If the column has another row pressed that is also pressed on another column, it is ambiguous. + // - If the row has another column pressed that is also pressed on another row, it is ambiguous. + // - However we can simplify this to mean if the row that has the same column and another column + // that also exists in the current row (i.e. a rectangle on the matrix). + //if (state[i] & ~(1 << j)) + // * * + // * + for (k = 0; k < Y_NUM; k++) + if (k != i && state[k] & (1 << j) && (state[i] & state[k]) & ~(1 << j)) + goto next; + // For key release we should just store and check whether a keydown event was sent on press. + down[i] |= (1 << j); + // Handle the keycode. + if (keycode == KEY_CAPSLOCK) { + if (capslock_led_enable == 0) + gpio_direction_output(pdata->gpio_caps_led, 1); + capslock_led_enable++; + } + + if(keycode != 0xFFFF) + { + AW9523_LOG("(press) keycode = %d \n", keycode); + input_report_key(aw9523b_input_dev, keycode, 1); + } + + + } else if (!(state[i] & (1 << j)) && down[i] & (1 << j)) { // Keyrelease. + down[i] &= ~(1 << j); + if (capslock_led_enable >= 2) { + gpio_direction_output(pdata->gpio_caps_led, 0); + capslock_led_enable = 0; + } + + if(keycode != 0xFFFF) + { + AW9523_LOG("(released) keycode = %d \n", keycode); + input_report_key(aw9523b_input_dev, keycode, 0); + } + } + next:; + } + } + input_sync(aw9523b_input_dev); + + // We re-schedule ourselves to poll for changes if a key is pressed + // because the pressed key could obscure others when not scanning. + if (keymask) + schedule_delayed_work(&pdata->work, msecs_to_jiffies(POLL_INTERVAL)); + + // Re-enabled the interrupt and make sure all is right. + aw9523b_disable_P1_interupt(); + aw9523b_set_P1_value(P1_DEFAULT_VALUE); + aw9523b_config_P1_output(); + aw9523b_irq_enable(pdata); + aw9523b_config_P0_input(); + aw9523b_enable_P0_interupt(); + + // Check if there wasn't a key pressed while we had interrupts disabled. + if (aw9523b_get_P0_value() != (u8) ~keymask) { + AW9523_LOG("missed state change"); + schedule_delayed_work(&pdata->work, 0); + } +} + +static irqreturn_t aw9523b_irq_handler(int irq, void *dev_id) +{ + struct aw9523b_data *pdata = dev_id; + + //AW9523_LOG("%s enter\n",__func__); + + aw9523b_irq_disable(pdata); + + schedule_delayed_work(&pdata->work, 0); + + return IRQ_HANDLED; +} +#if 1 + +static ssize_t aw9523b_show_chip_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t res; + //struct aw9523b_data *data = dev_get_drvdata(dev); + + res = snprintf(buf, PAGE_SIZE, "0x%04X\n", aw9523b_chip_id); + + return res; +} + +static ssize_t aw9523b_show_reg(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t res = 0; + char *ptr = buf; + + ptr += sprintf(ptr, "INPUT_PORT0: 0x%x\n", aw9523b_read_byte(INPUT_PORT0)); + ptr += sprintf(ptr, "INPUT_PORT1: 0x%x\n", aw9523b_read_byte(INPUT_PORT1)); + ptr += sprintf(ptr, "OUTPUT_PORT0: 0x%x\n", aw9523b_read_byte(OUTPUT_PORT0)); + ptr += sprintf(ptr, "OUTPUT_PORT1: 0x%x\n", aw9523b_read_byte(OUTPUT_PORT1)); + ptr += sprintf(ptr, "CONFIG_PORT0: 0x%x\n", aw9523b_read_byte(CONFIG_PORT0)); + ptr += sprintf(ptr, "CONFIG_PORT1: 0x%x\n", aw9523b_read_byte(CONFIG_PORT1)); + ptr += sprintf(ptr, "INT_PORT0: 0x%x\n", aw9523b_read_byte(INT_PORT0)); + ptr += sprintf(ptr, "INT_PORT1: 0x%x\n", aw9523b_read_byte(INT_PORT1)); + ptr += sprintf(ptr, "IC_ID: 0x%x\n", aw9523b_read_byte(IC_ID)); + ptr += sprintf(ptr, "CTL: 0x%x\n", aw9523b_read_byte(CTL)); + ptr += sprintf(ptr, "\n"); + res = ptr - buf; + + return res; +} + +static DEVICE_ATTR(aw9523b_reg, (S_IRUGO | S_IWUSR | S_IWGRP), + aw9523b_show_reg, + NULL); +static DEVICE_ATTR(aw9523b_chip_id, (S_IRUGO | S_IWUSR | S_IWGRP), + aw9523b_show_chip_id, + NULL); + + +static struct attribute *aw9523b_attrs[] = { + &dev_attr_aw9523b_reg.attr, + &dev_attr_aw9523b_chip_id.attr, + NULL +}; + +static const struct attribute_group aw9523b_attr_grp = { + .attrs = aw9523b_attrs, +}; + +#endif +#if 0 +static DRIVER_ATTR(aw9523b_reg, S_IRUGO, aw9523b_show_reg, NULL); +static DRIVER_ATTR(aw9523b_chip_id, S_IRUGO, aw9523b_show_chip_id, NULL); + +static struct driver_attribute *aw9523b_attr_list[] = { + &driver_attr_aw9523b_chip_id, + &driver_attr_aw9523b_reg, +}; + +static int aw9523b_create_attr(struct device_driver *driver) +{ + int idx,err=0; + int num = (int)(sizeof(aw9523b_attr_list)/sizeof(aw9523b_attr_list[0])); + + if (driver == NULL) + return -EINVAL; + + for(idx = 0; idx < num; idx++) { + if((err = driver_create_file(driver, aw9523b_attr_list[idx]))) { + printk("driver_create_file (%s) = %d\n", aw9523b_attr_list[idx]->attr.name, err); + break; + } + } + + return err; +} + +static struct platform_driver aw9523b_pdrv; +#endif + +static int register_aw9523b_input_dev(struct device *pdev) +{ + int i,j; + //int r; + + AW9523_FUN(f); + + aw9523b_input_dev = input_allocate_device(); + //aw9523b_input_dev = devm_input_allocate_device(pdev); + if (!aw9523b_input_dev) + { + printk("aw9523b_input_dev alloct failed\n"); + return -ENOMEM; + } + + aw9523b_input_dev->name = "Fxtec Pro1"; + aw9523b_input_dev->id.bustype = BUS_I2C; + aw9523b_input_dev->id.vendor = 0x9523; + aw9523b_input_dev->id.product = 0x0701; + aw9523b_input_dev->id.version = 0x0001; + + __set_bit(EV_KEY, aw9523b_input_dev->evbit); + + for (i=0;idev.parent = pdev; + //r = input_register_device(aw9523b_input_dev); + //if (r) { + // input_free_device(aw9523b_input_dev); + // return r; + //} + return 0; +} + +static int aw9523b_power_ctl(struct aw9523b_data *data, bool on) +{ + int ret = 0; +#ifdef USEVIO + if (!on && data->power_enabled) { + ret = regulator_disable(data->vio); + if (ret) { + dev_err(&data->client->dev, + "Regulator vio disable failed ret=%d\n", ret); + return ret; + } + + data->power_enabled = on; + } else if (on && !data->power_enabled) { + ret = regulator_enable(data->vio); + if (ret) { + dev_err(&data->client->dev, + "Regulator vio enable failed ret=%d\n", ret); + return ret; + } + data->power_enabled = on; + } else { + dev_info(&data->client->dev, + "Power on=%d. enabled=%d\n", + on, data->power_enabled); + } +#endif + return ret; + +} + +static int aw9523b_power_init(struct aw9523b_data *data) +{ + int ret = 0; +#ifdef USEVIO + data->vio = regulator_get(&data->client->dev, "vio"); + if (IS_ERR(data->vio)) { + ret = PTR_ERR(data->vio); + dev_err(&data->client->dev, + "Regulator get failed vdd ret=%d\n", ret); + return ret; + } + + if (regulator_count_voltages(data->vio) > 0) { + ret = regulator_set_voltage(data->vio, + AW9523B_VIO_MIN_UV, + AW9523B_VIO_MAX_UV); + if (ret) { + dev_err(&data->client->dev, + "Regulator set failed vio ret=%d\n", + ret); + goto reg_vio_put; + } + } + + return 0; + +reg_vio_put: + regulator_put(data->vio); +#endif + return ret; + +} + +static int aw9523b_power_deinit(struct aw9523b_data *data) +{ +#ifdef USEVIO + if (regulator_count_voltages(data->vio) > 0) + regulator_set_voltage(data->vio, + 0, AW9523B_VIO_MAX_UV); + + regulator_put(data->vio); +#endif + return 0; +} + +#ifdef CONFIG_OF +static int aw9523b_parse_dt(struct device *dev, + struct aw9523b_data *pdata) +{ + int err = 0; + int ret = 0; + struct device_node *np = dev->of_node; + + pdata->aw9523_ctrl = devm_pinctrl_get(dev); + if (IS_ERR(pdata->aw9523_ctrl)) { + if (PTR_ERR(pdata->aw9523_ctrl) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + printk("Target does not use pinctrl\n"); + pdata->aw9523_ctrl = NULL; + } + + pdata->aw9523_rst_h = pinctrl_lookup_state(pdata->aw9523_ctrl, "aw9523_reset_high"); + if (IS_ERR(pdata->aw9523_rst_h)) { + ret = PTR_ERR(pdata->aw9523_rst_h); + pr_debug("%s : pinctrl err, data->aw9523_rst_h\n", __func__); + } + + pdata->aw9523_rst_l = pinctrl_lookup_state(pdata->aw9523_ctrl, "aw9523_reset_low"); + if (IS_ERR(pdata->aw9523_rst_l)) { + ret = PTR_ERR(pdata->aw9523_rst_l); + pr_debug("%s : pinctrl err, aw9523_rst_l\n", __func__); + } + + pdata->aw9523_int_active= pinctrl_lookup_state(pdata->aw9523_ctrl, "aw9523_int_active"); + if (IS_ERR(pdata->aw9523_int_active)) { + ret = PTR_ERR(pdata->aw9523_int_active); + pr_debug("%s : pinctrl err, data->aw9523_int_active\n", __func__); + } + + pinctrl_select_state(pdata->aw9523_ctrl, pdata->aw9523_int_active); + + + pdata->aw9523_int_suspend= pinctrl_lookup_state(pdata->aw9523_ctrl, "aw9523_int_suspend"); + if (IS_ERR(pdata->aw9523_int_suspend)) { + ret = PTR_ERR(pdata->aw9523_int_suspend); + pr_debug("%s : pinctrl err, aw9523_int_suspend\n", __func__); + } + + pinctrl_select_state(pdata->aw9523_ctrl, pdata->aw9523_int_active); + + //TODO å¤ä½GPIO 无法æ“作,用pinctrlæ–¹å¼æŽ§åˆ¶ + #if 0 + pdata->gpio_rst = of_get_named_gpio(np, "awinic,reset-gpio", 0); + if (gpio_is_valid(pdata->gpio_rst)) { + err = gpio_request(pdata->gpio_rst, "aw9523b_reset_gpio"); + if (err) { + dev_err(&pdata->client->dev, "pdata->gpio_rst gpio request failed"); + return -ENODEV; + } + err = gpio_direction_output(pdata->gpio_rst, 1); + if (err) { + dev_err(&pdata->client->dev, + "set_direction for pdata->gpio_rst failed\n"); + return -ENODEV; + } + } + else + { + dev_err(dev, "pdata->gpio_rst is error\n"); + return pdata->gpio_rst; + } + #endif + pdata->gpio_caps_led = of_get_named_gpio(np, "awinic,caps-gpio", 0); + if (gpio_is_valid(pdata->gpio_caps_led)) { + err = gpio_request(pdata->gpio_caps_led, "aw9523b_gpio_caps_led"); + if (err) { + dev_err(&pdata->client->dev, "pdata->gpio_caps_led gpio request failed"); + return -ENODEV; + } + } + else + { + dev_err(dev, "pdata->gpio_caps_led is error\n"); + return pdata->gpio_caps_led; + } + + pdata->gpio_irq = of_get_named_gpio(np, "awinic,irq-gpio", 0); + if (gpio_is_valid(pdata->gpio_irq)) { + err = gpio_request(pdata->gpio_irq, "aw9523b_irq_gpio"); + if (err) { + dev_err(&pdata->client->dev, "pdata->gpio_irq gpio request failed"); + return -ENODEV; + } + err = gpio_direction_input(pdata->gpio_irq); + //err = gpio_direction_output(pdata->gpio_rst,0); + if (err) { + dev_err(&pdata->client->dev, + "set_direction for pdata->gpio_irq failed\n"); + return -ENODEV; + } + } + else + { + dev_err(dev, "pdata->gpio_irq is error\n"); + return pdata->gpio_irq; + } + + return 0; +} +#else +static int aw9523b_parse_dt(struct device *dev, + struct aw9523b_platform_data *pdata) +{ + return -EINVAL; +} +#endif + + +static int aw9523b_suspend(struct device *dev) +{ + struct aw9523b_data *data = dev_get_drvdata(dev); + //int err; + + dev_err(&data->client->dev, "%s start have_suspend = %d \n", __func__,data->have_suspend); + + if(data->have_suspend == 0) + { + /* + err = aw9523b_hw_reset(data); + if(err == 0xff) + { + err = -EINVAL; + printk("%s reset error\n",__func__); + return err; + } + */ + cancel_delayed_work_sync(&data->work); + disable_irq(data->client->irq); + pinctrl_select_state(data->aw9523_ctrl, data->aw9523_rst_l); + + data->have_suspend = 1; + dev_err(&data->client->dev, "capslock_led_enable = %d have_suspend = %d\n", + capslock_led_enable,data->have_suspend); + if (capslock_led_enable == 1 ) { + gpio_direction_output(data->gpio_caps_led, 0); + } + } + return 0; +} + +static int aw9523b_resume(struct device *dev) +{ + struct aw9523b_data *data = dev_get_drvdata(dev); + int err,devic_id; + dev_err(&data->client->dev, "%s start have_suspend = %d \n", __func__,data->have_suspend); + + if(data->have_suspend) + { + err = aw9523b_hw_reset(data); + if(err == 0xff) + { + err = -EINVAL; + printk("%s reset error\n",__func__); + return err; + } + + devic_id = aw9523b_i2c_test(data); + + if(devic_id < 0) + { + printk("%s aw9523b_i2c_test error\n",__func__); + err = aw9523b_hw_reset(data); + if(err == 0xff) + { + err = -EINVAL; + printk("%s reset error\n",__func__); + return err; + } + } + + dev_err(&data->client->dev, "capslock_led_enable = %d\n", capslock_led_enable); + if (capslock_led_enable == 1) { + gpio_direction_output(data->gpio_caps_led, 1); + } + + default_p0_p1_settings(); //io_init + aw9523b_get_P0_value(); + aw9523b_get_P1_value(); + udelay(50); + enable_irq(data->client->irq); + udelay(10); + data->have_suspend = 0; + } + return 0; +} + +static void fb_notify_resume_work(struct work_struct *work) +{ + struct aw9523b_data *aw9523b_data = + container_of(work, struct aw9523b_data, fb_notify_work); + aw9523b_resume(&aw9523b_data->client->dev); +} + +#if defined(CONFIG_DRM) + +static int aw9523b_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct drm_panel_notifier *evdata = data; + int *blank; + //struct nvt_ts_data *ts = + // container_of(self, struct nvt_ts_data, drm_notif); + struct aw9523b_data *aw9523b_data = + container_of(self, struct aw9523b_data, pm_notif); + + if (!evdata || !evdata->data ) + return 0; + + blank = evdata->data; + + if (event == DRM_PANEL_EARLY_EVENT_BLANK) { + if (*blank == DRM_PANEL_BLANK_POWERDOWN) { + dev_err(&aw9523b_data->client->dev, "event=%lu, *blank=%d\n", event, *blank); + aw9523b_suspend(&aw9523b_data->client->dev); + } + } else if (event == DRM_PANEL_EVENT_BLANK) { + if (*blank == DRM_PANEL_BLANK_UNBLANK) { + dev_err(&aw9523b_data->client->dev, "event=%lu, *blank=%d\n", event, *blank); + aw9523b_resume(&aw9523b_data->client->dev); + } + } + + return 0; +} + +#elif defined(CONFIG_FB) +static int aw9523b_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct fb_event *evdata = data; + int *blank; + struct aw9523b_data *aw9523b_data = + container_of(self, struct aw9523b_data, pm_notif); + + if (evdata && evdata->data && aw9523b_data && aw9523b_data->client) { + blank = evdata->data; + if (1) { + if (event == FB_EARLY_EVENT_BLANK && + *blank == FB_BLANK_UNBLANK) + schedule_work(&aw9523b_data->fb_notify_work); + else if (event == FB_EVENT_BLANK && + *blank == FB_BLANK_POWERDOWN) { + flush_work(&aw9523b_data->fb_notify_work); + aw9523b_suspend(&aw9523b_data->client->dev); + } + } else { + if (event == FB_EVENT_BLANK) { + if (*blank == FB_BLANK_UNBLANK) + aw9523b_resume( + &aw9523b_data->client->dev); + else if (*blank == FB_BLANK_POWERDOWN) + aw9523b_suspend( + &aw9523b_data->client->dev); + } + } + } + + return 0; +} +#else + +#endif + +static int aw9523_register_powermanger(struct aw9523b_data *pdata) +{ + pdata->pm_notif.notifier_call = aw9523b_notifier_callback; +#if defined(CONFIG_DRM) + if (active_panel && drm_panel_notifier_register(active_panel,&pdata->pm_notif) < 0) { + printk("register notifier failed!\n"); + } +#elif defined(CONFIG_FB) + err = fb_register_client(&pdata->pm_notif); + +#elif defined(CONFIG_HAS_EARLYSUSPEND) + +#endif + return 0; +} + +static int aw9523_unregister_powermanger(struct aw9523b_data *pdata) +{ +#if defined(CONFIG_DRM_PANEL) + if (active_panel) + drm_panel_notifier_unregister(active_panel, &pdata->pm_notif); + +#elif defined(CONFIG_FB) + fb_unregister_client(&pdata->pm_notif); + +#elif defined(CONFIG_HAS_EARLYSUSPEND) + +#endif + return 0; +} + + +#if defined(CONFIG_DRM_PANEL) +static int aw9523b_check_dt(struct device_node *np) +{ + int i; + int count; + + + struct device_node *node; + struct drm_panel *panel; + + + //需è¦åœ¨å±dtsåˆå§‹åŒ–åŽæ‰èƒ½æ‰¾åˆ°panel的字段 + count = of_count_phandle_with_args(np, "panel", NULL); + + if (count <= 0) + return 0; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + active_panel = panel; + printk(" %s:find\n", __func__); + return 0; + } + } + + printk(" %s: not find\n", __func__); + return -ENODEV; +} + +static int aw9523b_check_default_tp(struct device_node *dt, const char *prop) +{ + const char **active_tp = NULL; + int count, tmp, score = 0; + const char *active; + int ret, i; + + count = of_property_count_strings(dt->parent, prop); + if (count <= 0 || count > 3) + return -ENODEV; + + active_tp = kcalloc(count, sizeof(char *), GFP_KERNEL); + if (!active_tp) { + printk("FTS alloc failed\n"); + return -ENOMEM; + } + + ret = of_property_read_string_array(dt->parent, prop, + active_tp, count); + if (ret < 0) { + printk("fail to read %s %d\n", prop, ret); + ret = -ENODEV; + goto out; + } + + for (i = 0; i < count; i++) { + active = active_tp[i]; + if (active != NULL) { + tmp = of_device_is_compatible(dt, active); + if (tmp > 0) + score++; + } + } + + if (score <= 0) { + printk("not match this driver\n"); + ret = -ENODEV; + goto out; + } + ret = 0; +out: + kfree(active_tp); + return ret; +} +#endif + +static int aw9523b_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err = 0; + int devic_id = 0; + + struct aw9523b_data *pdata=NULL; + + + + pr_err("hjc++ %s begin\n",__func__); + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "i2c_check_functionality error\n"); + err = -EPERM; + goto exit; + } + pdata = kzalloc(sizeof(struct aw9523b_data), GFP_KERNEL); + if (!pdata) { + err = -ENOMEM; + goto exit; + } + + memset(pdata, 0, sizeof(struct aw9523b_data)); + g_aw9523_data = pdata; + + if (client->dev.of_node) { + err = aw9523b_parse_dt(&client->dev, pdata); + if (err) { + dev_err(&client->dev, "Failed to parse device tree\n"); + err = -EINVAL; + goto pdata_free_exit; + } + } + + printk ("rst_gpio=%d irq_gpio=%d irq=%d\n",pdata->gpio_rst,pdata->gpio_irq,client->irq); + + if (!pdata) { + dev_err(&client->dev, "Cannot get device platform data\n"); + err = -EINVAL; + goto kfree_exit; + } + + spin_lock_init(&pdata->irq_lock); + g_client = client; + i2c_set_clientdata(client, pdata); + pdata->client = client; + + + + err = aw9523b_power_init(pdata); + if (err) { + dev_err(&client->dev, "Failed to get aw9523b regulators\n"); + err = -EINVAL; + goto free_i2c_clientdata_exit; + } + err = aw9523b_power_ctl(pdata, true); + if (err) { + dev_err(&client->dev, "Failed to enable aw9523b power\n"); + err = -EINVAL; + goto deinit_power_exit; + } + + + err = aw9523b_hw_reset(pdata); + if(err == 0xff) + { + dev_err(&client->dev, "aw9523b failed to write \n"); + err = -EINVAL; + goto deinit_power_exit; + } + if (err) { + dev_err(&client->dev, "aw9523b failed to reset\n"); + } + + devic_id = aw9523b_i2c_test(pdata); + if(devic_id < 0) + { + dev_err(&client->dev, "aw9523b failed to read \n\n\n\n"); + err = -EINVAL; + goto deinit_power_exit; + } + + err = register_aw9523b_input_dev(&client->dev); + if (err) { + dev_err(&client->dev, "Failed to get aw9523b regulators\n"); + err = -EINVAL; + goto deinit_power_exit; + } + + // err = sysfs_create_group(&client->dev.kobj, &aw9523b_attr_grp); + // if (err < 0) { + // dev_err(&client->dev, "sys file creation failed.\n"); + // goto deinit_power_exit; + // } + + + default_p0_p1_settings(); //io_init + aw9523b_get_P0_value(); + aw9523b_get_P1_value(); + + // debug_printk("%s device_id = %d\n",__func__,devic_id); + INIT_DELAYED_WORK(&pdata->work, aw9523b_work_func); + pdata->irq_is_disabled = true; + err = request_irq(client->irq, + aw9523b_irq_handler, + IRQ_TYPE_EDGE_FALLING, + client->name, pdata); + + //schedule_delayed_work(&pdata->keypad_work, 0); + //aw9523b_irq_enable(pdata); + + INIT_WORK(&pdata->fb_notify_work, fb_notify_resume_work); + + printk("hjc-- %s exit success\n",__func__); + + return 0; + +//exit_remove_sysfs: +// sysfs_remove_group(&client->dev.kobj, &aw9523b_attr_grp); +//free_input_dev: + //input_free_device(aw9523b_input_dev); +deinit_power_exit: + aw9523b_power_deinit(pdata); + //if (pdata && (client->dev.of_node)) + //devm_kfree(&client->dev, pdata); +free_i2c_clientdata_exit: + i2c_set_clientdata(client, NULL); +pdata_free_exit: +kfree_exit: + kfree(pdata); + g_aw9523_data = NULL; + +exit: + return err; +} + + +static int aw9523b_remove(struct i2c_client *client) +{ + struct aw9523b_data *data = i2c_get_clientdata(client); + + //cancel_delayed_work_sync(&data->p1_012_work); + aw9523b_power_deinit(data); + i2c_set_clientdata(client, NULL); + + + kfree(data); + + return 0; +} + +static const struct i2c_device_id aw9523b_id[] = { + { AWINIC_NAME, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, aw9523b_id); + +static const struct of_device_id aw9523b_of_match[] = { + { .compatible = "awinic,aw9523b", }, + { }, +}; + +static struct i2c_driver aw9523b_driver = { + .driver = { + .owner = THIS_MODULE, + .name = AWINIC_NAME, + .of_match_table = aw9523b_of_match, + }, + .id_table = aw9523b_id, + .probe = aw9523b_probe, + .remove = aw9523b_remove, +}; + +static int __init AW9523B_init(void) +{ + return i2c_add_driver(&aw9523b_driver); +} + +static void __exit AW9523B_exit(void) +{ + i2c_del_driver(&aw9523b_driver); +} + +MODULE_AUTHOR("contact@AWINIC TECHNOLOGY"); +MODULE_DESCRIPTION("AW9523B LED OF GPIO DRIVER"); +MODULE_LICENSE("GPL v2"); + +module_init(AW9523B_init); +module_exit(AW9523B_exit); +/* + * SYSFS interface for enabling/disabling keys and switches: + * + * There are 4 attributes under /sys/devices/platform/gpio-keys/ + * keys [ro] - bitmap of keys (EV_KEY) which can be + * disabled + * switches [ro] - bitmap of switches (EV_SW) which can be + * disabled + * disabled_keys [rw] - bitmap of keys currently disabled + * disabled_switches [rw] - bitmap of switches currently disabled + * + * Userland can change these values and hence disable event generation + * for each key (or switch). Disabling a key means its interrupt line + * is disabled. + * + * For example, if we have following switches set up as gpio-keys: + * SW_DOCK = 5 + * SW_CAMERA_LENS_COVER = 9 + * SW_KEYPAD_SLIDE = 10 + * SW_FRONT_PROXIMITY = 11 + * This is read from switches: + * 11-9,5 + * Next we want to disable proximity (11) and dock (5), we write: + * 11,5 + * to file disabled_switches. Now proximity and dock IRQs are disabled. + * This can be verified by reading the file disabled_switches: + * 11,5 + * If we now want to enable proximity (11) switch we write: + * 5 + * to disabled_switches. + * + * We can disable only those keys which don't allow sharing the irq. + */ + +/** + * get_n_events_by_type() - returns maximum number of events per @type + * @type: type of button (%EV_KEY, %EV_SW) + * + * Return value of this function can be used to allocate bitmap + * large enough to hold all bits for given type. + */ +static int get_n_events_by_type(int type) +{ + BUG_ON(type != EV_SW && type != EV_KEY); + + return (type == EV_KEY) ? KEY_CNT : SW_CNT; +} + +/** + * get_bm_events_by_type() - returns bitmap of supported events per @type + * @input: input device from which bitmap is retrieved + * @type: type of button (%EV_KEY, %EV_SW) + * + * Return value of this function can be used to allocate bitmap + * large enough to hold all bits for given type. + */ +static const unsigned long *get_bm_events_by_type(struct input_dev *dev, + int type) +{ + BUG_ON(type != EV_SW && type != EV_KEY); + + return (type == EV_KEY) ? dev->keybit : dev->swbit; +} + +/** + * gpio_keys_disable_button() - disables given GPIO button + * @bdata: button data for button to be disabled + * + * Disables button pointed by @bdata. This is done by masking + * IRQ line. After this function is called, button won't generate + * input events anymore. Note that one can only disable buttons + * that don't share IRQs. + * + * Make sure that @bdata->disable_lock is locked when entering + * this function to avoid races when concurrent threads are + * disabling buttons at the same time. + */ +static void gpio_keys_disable_button(struct gpio_button_data *bdata) +{ + if (!bdata->disabled) { + /* + * Disable IRQ and associated timer/work structure. + */ + disable_irq(bdata->irq); + + if (bdata->gpiod) + cancel_delayed_work_sync(&bdata->work); + else + del_timer_sync(&bdata->release_timer); + + bdata->disabled = true; + } +} + +/** + * gpio_keys_enable_button() - enables given GPIO button + * @bdata: button data for button to be disabled + * + * Enables given button pointed by @bdata. + * + * Make sure that @bdata->disable_lock is locked when entering + * this function to avoid races with concurrent threads trying + * to enable the same button at the same time. + */ +static void gpio_keys_enable_button(struct gpio_button_data *bdata) +{ + if (bdata->disabled) { + enable_irq(bdata->irq); + bdata->disabled = false; + } +} + +/** + * gpio_keys_attr_show_helper() - fill in stringified bitmap of buttons + * @ddata: pointer to drvdata + * @buf: buffer where stringified bitmap is written + * @type: button type (%EV_KEY, %EV_SW) + * @only_disabled: does caller want only those buttons that are + * currently disabled or all buttons that can be + * disabled + * + * This function writes buttons that can be disabled to @buf. If + * @only_disabled is true, then @buf contains only those buttons + * that are currently disabled. Returns 0 on success or negative + * errno on failure. + */ +static ssize_t gpio_keys_attr_show_helper(struct gpio_keys_drvdata *ddata, + char *buf, unsigned int type, + bool only_disabled) +{ + int n_events = get_n_events_by_type(type); + unsigned long *bits; + ssize_t ret; + int i; + + bits = bitmap_zalloc(n_events, GFP_KERNEL); + if (!bits) + return -ENOMEM; + + for (i = 0; i < ddata->pdata->nbuttons; i++) { + struct gpio_button_data *bdata = &ddata->data[i]; + + if (bdata->button->type != type) + continue; + + if (only_disabled && !bdata->disabled) + continue; + + __set_bit(*bdata->code, bits); + } + + ret = scnprintf(buf, PAGE_SIZE - 1, "%*pbl", n_events, bits); + buf[ret++] = '\n'; + buf[ret] = '\0'; + + bitmap_free(bits); + + return ret; +} + +/** + * gpio_keys_attr_store_helper() - enable/disable buttons based on given bitmap + * @ddata: pointer to drvdata + * @buf: buffer from userspace that contains stringified bitmap + * @type: button type (%EV_KEY, %EV_SW) + * + * This function parses stringified bitmap from @buf and disables/enables + * GPIO buttons accordingly. Returns 0 on success and negative error + * on failure. + */ +static ssize_t gpio_keys_attr_store_helper(struct gpio_keys_drvdata *ddata, + const char *buf, unsigned int type) +{ + int n_events = get_n_events_by_type(type); + const unsigned long *bitmap = get_bm_events_by_type(ddata->input, type); + unsigned long *bits; + ssize_t error; + int i; + + bits = bitmap_zalloc(n_events, GFP_KERNEL); + if (!bits) + return -ENOMEM; + + error = bitmap_parselist(buf, bits, n_events); + if (error) + goto out; + + /* First validate */ + if (!bitmap_subset(bits, bitmap, n_events)) { + error = -EINVAL; + goto out; + } + + for (i = 0; i < ddata->pdata->nbuttons; i++) { + struct gpio_button_data *bdata = &ddata->data[i]; + + if (bdata->button->type != type) + continue; + + if (test_bit(*bdata->code, bits) && + !bdata->button->can_disable) { + error = -EINVAL; + goto out; + } + } + + mutex_lock(&ddata->disable_lock); + + for (i = 0; i < ddata->pdata->nbuttons; i++) { + struct gpio_button_data *bdata = &ddata->data[i]; + + if (bdata->button->type != type) + continue; + + if (test_bit(*bdata->code, bits)) + gpio_keys_disable_button(bdata); + else + gpio_keys_enable_button(bdata); + } + + mutex_unlock(&ddata->disable_lock); + +out: + bitmap_free(bits); + return error; +} + +#define ATTR_SHOW_FN(name, type, only_disabled) \ +static ssize_t gpio_keys_show_##name(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct platform_device *pdev = to_platform_device(dev); \ + struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdev); \ + \ + return gpio_keys_attr_show_helper(ddata, buf, \ + type, only_disabled); \ +} + +ATTR_SHOW_FN(keys, EV_KEY, false); +ATTR_SHOW_FN(switches, EV_SW, false); +ATTR_SHOW_FN(disabled_keys, EV_KEY, true); +ATTR_SHOW_FN(disabled_switches, EV_SW, true); + +/* + * ATTRIBUTES: + * + * /sys/devices/platform/gpio-keys/keys [ro] + * /sys/devices/platform/gpio-keys/switches [ro] + */ +static DEVICE_ATTR(keys, S_IRUGO, gpio_keys_show_keys, NULL); +static DEVICE_ATTR(switches, S_IRUGO, gpio_keys_show_switches, NULL); + +#define ATTR_STORE_FN(name, type) \ +static ssize_t gpio_keys_store_##name(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, \ + size_t count) \ +{ \ + struct platform_device *pdev = to_platform_device(dev); \ + struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdev); \ + ssize_t error; \ + \ + error = gpio_keys_attr_store_helper(ddata, buf, type); \ + if (error) \ + return error; \ + \ + return count; \ +} + +ATTR_STORE_FN(disabled_keys, EV_KEY); +ATTR_STORE_FN(disabled_switches, EV_SW); + +/* + * ATTRIBUTES: + * + * /sys/devices/platform/gpio-keys/disabled_keys [rw] + * /sys/devices/platform/gpio-keys/disables_switches [rw] + */ +static DEVICE_ATTR(disabled_keys, S_IWUSR | S_IRUGO, + gpio_keys_show_disabled_keys, + gpio_keys_store_disabled_keys); +static DEVICE_ATTR(disabled_switches, S_IWUSR | S_IRUGO, + gpio_keys_show_disabled_switches, + gpio_keys_store_disabled_switches); + +static struct attribute *gpio_keys_attrs[] = { + &dev_attr_keys.attr, + &dev_attr_switches.attr, + &dev_attr_disabled_keys.attr, + &dev_attr_disabled_switches.attr, + NULL, +}; + +static const struct attribute_group gpio_keys_attr_group = { + .attrs = gpio_keys_attrs, +}; + +static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata) +{ + const struct gpio_keys_button *button = bdata->button; + struct input_dev *input = bdata->input; + unsigned int type = button->type ?: EV_KEY; + int state; + + state = gpiod_get_value_cansleep(bdata->gpiod); + if (state < 0) { + dev_err(input->dev.parent, + "failed to get gpio state: %d\n", state); + return; + } + + if (type == EV_ABS) { + if (state) + input_event(input, type, button->code, button->value); + } else { + input_event(input, type, *bdata->code, state); + } + input_sync(input); +} + +static void gpio_keys_gpio_work_func(struct work_struct *work) +{ + struct gpio_button_data *bdata = + container_of(work, struct gpio_button_data, work.work); + + gpio_keys_gpio_report_event(bdata); + + if (bdata->button->wakeup) + pm_relax(bdata->input->dev.parent); +} + +static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id) +{ + struct gpio_button_data *bdata = dev_id; + + BUG_ON(irq != bdata->irq); + + if (bdata->button->wakeup) { + const struct gpio_keys_button *button = bdata->button; + + pm_stay_awake(bdata->input->dev.parent); + if (bdata->suspended && + (button->type == 0 || button->type == EV_KEY)) { + /* + * Simulate wakeup key press in case the key has + * already released by the time we got interrupt + * handler to run. + */ + input_report_key(bdata->input, button->code, 1); + } + } + + mod_delayed_work(system_wq, + &bdata->work, + msecs_to_jiffies(bdata->software_debounce)); + + return IRQ_HANDLED; +} + +static void gpio_keys_irq_timer(struct timer_list *t) +{ + struct gpio_button_data *bdata = from_timer(bdata, t, release_timer); + struct input_dev *input = bdata->input; + unsigned long flags; + + spin_lock_irqsave(&bdata->lock, flags); + if (bdata->key_pressed) { + input_event(input, EV_KEY, *bdata->code, 0); + input_sync(input); + bdata->key_pressed = false; + } + spin_unlock_irqrestore(&bdata->lock, flags); +} + +static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id) +{ + struct gpio_button_data *bdata = dev_id; + struct input_dev *input = bdata->input; + unsigned long flags; + + BUG_ON(irq != bdata->irq); + + spin_lock_irqsave(&bdata->lock, flags); + + if (!bdata->key_pressed) { + if (bdata->button->wakeup) + pm_wakeup_event(bdata->input->dev.parent, 0); + + input_event(input, EV_KEY, *bdata->code, 1); + input_sync(input); + + if (!bdata->release_delay) { + input_event(input, EV_KEY, *bdata->code, 0); + input_sync(input); + goto out; + } + + bdata->key_pressed = true; + } + + if (bdata->release_delay) + mod_timer(&bdata->release_timer, + jiffies + msecs_to_jiffies(bdata->release_delay)); +out: + spin_unlock_irqrestore(&bdata->lock, flags); + return IRQ_HANDLED; +} + +static void gpio_keys_quiesce_key(void *data) +{ + struct gpio_button_data *bdata = data; + + if (bdata->gpiod) + cancel_delayed_work_sync(&bdata->work); + else + del_timer_sync(&bdata->release_timer); +} + +static int gpio_keys_setup_key(struct platform_device *pdev, + struct input_dev *input, + struct gpio_keys_drvdata *ddata, + const struct gpio_keys_button *button, + int idx, + struct fwnode_handle *child) +{ + const char *desc = button->desc ? button->desc : "gpio_keys"; + struct device *dev = &pdev->dev; + struct gpio_button_data *bdata = &ddata->data[idx]; + irq_handler_t isr; + unsigned long irqflags; + int irq; + int error; + + bdata->input = input; + bdata->button = button; + spin_lock_init(&bdata->lock); + + if (child) { + bdata->gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, + child, + GPIOD_IN, + desc); + if (IS_ERR(bdata->gpiod)) { + error = PTR_ERR(bdata->gpiod); + if (error == -ENOENT) { + /* + * GPIO is optional, we may be dealing with + * purely interrupt-driven setup. + */ + bdata->gpiod = NULL; + } else { + if (error != -EPROBE_DEFER) + dev_err(dev, "failed to get gpio: %d\n", + error); + return error; + } + } + } else if (gpio_is_valid(button->gpio)) { + /* + * Legacy GPIO number, so request the GPIO here and + * convert it to descriptor. + */ + unsigned flags = GPIOF_IN; + + if (button->active_low) + flags |= GPIOF_ACTIVE_LOW; + + error = devm_gpio_request_one(dev, button->gpio, flags, desc); + if (error < 0) { + dev_err(dev, "Failed to request GPIO %d, error %d\n", + button->gpio, error); + return error; + } + + bdata->gpiod = gpio_to_desc(button->gpio); + if (!bdata->gpiod) + return -EINVAL; + } + + if (bdata->gpiod) { + bool active_low = gpiod_is_active_low(bdata->gpiod); + + if (button->debounce_interval) { + error = gpiod_set_debounce(bdata->gpiod, + button->debounce_interval * 1000); + /* use timer if gpiolib doesn't provide debounce */ + if (error < 0) + bdata->software_debounce = + button->debounce_interval; + } + + if (button->irq) { + bdata->irq = button->irq; + } else { + irq = gpiod_to_irq(bdata->gpiod); + if (irq < 0) { + error = irq; + dev_err(dev, + "Unable to get irq number for GPIO %d, error %d\n", + button->gpio, error); + return error; + } + bdata->irq = irq; + } + + INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func); + + isr = gpio_keys_gpio_isr; + irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; + + switch (button->wakeup_event_action) { + case EV_ACT_ASSERTED: + bdata->wakeup_trigger_type = active_low ? + IRQ_TYPE_EDGE_FALLING : IRQ_TYPE_EDGE_RISING; + break; + case EV_ACT_DEASSERTED: + bdata->wakeup_trigger_type = active_low ? + IRQ_TYPE_EDGE_RISING : IRQ_TYPE_EDGE_FALLING; + break; + case EV_ACT_ANY: + /* fall through */ + default: + /* + * For other cases, we are OK letting suspend/resume + * not reconfigure the trigger type. + */ + break; + } + } else { + if (!button->irq) { + dev_err(dev, "Found button without gpio or irq\n"); + return -EINVAL; + } + + bdata->irq = button->irq; + + if (button->type && button->type != EV_KEY) { + dev_err(dev, "Only EV_KEY allowed for IRQ buttons.\n"); + return -EINVAL; + } + + bdata->release_delay = button->debounce_interval; + timer_setup(&bdata->release_timer, gpio_keys_irq_timer, 0); + + isr = gpio_keys_irq_isr; + irqflags = 0; + + /* + * For IRQ buttons, there is no interrupt for release. + * So we don't need to reconfigure the trigger type for wakeup. + */ + } + + bdata->code = &ddata->keymap[idx]; + *bdata->code = button->code; + input_set_capability(input, button->type ?: EV_KEY, *bdata->code); + + /* + * Install custom action to cancel release timer and + * workqueue item. + */ + error = devm_add_action(dev, gpio_keys_quiesce_key, bdata); + if (error) { + dev_err(dev, "failed to register quiesce action, error: %d\n", + error); + return error; + } + + /* + * If platform has specified that the button can be disabled, + * we don't want it to share the interrupt line. + */ + if (!button->can_disable) + irqflags |= IRQF_SHARED; + + error = devm_request_any_context_irq(dev, bdata->irq, isr, irqflags, + desc, bdata); + if (error < 0) { + dev_err(dev, "Unable to claim irq %d; error %d\n", + bdata->irq, error); + return error; + } + + return 0; +} +/* +static void gpio_keys_report_state(struct gpio_keys_drvdata *ddata) +{ + struct input_dev *input = ddata->input; + int i; + + for (i = 0; i < ddata->pdata->nbuttons; i++) { + struct gpio_button_data *bdata = &ddata->data[i]; + if (bdata->gpiod) + gpio_keys_gpio_report_event(bdata); + } + input_sync(input); +} +*/ +static int gpio_keys_pinctrl_configure(struct gpio_keys_drvdata *ddata, + bool active) +{ + struct pinctrl_state *set_state; + int retval; + + if (active) { + set_state = + pinctrl_lookup_state(ddata->key_pinctrl, + "idea_gpio_key_active"); + if (IS_ERR(set_state)) { + dev_err(&ddata->input->dev, + "cannot get ts pinctrl active state\n"); + return PTR_ERR(set_state); + } + } else { + set_state = + pinctrl_lookup_state(ddata->key_pinctrl, + "idea_gpio_key_suspend"); + if (IS_ERR(set_state)) { + dev_err(&ddata->input->dev, + "cannot get gpiokey pinctrl sleep state\n"); + return PTR_ERR(set_state); + } + } + retval = pinctrl_select_state(ddata->key_pinctrl, set_state); + if (retval) { + dev_err(&ddata->input->dev, + "cannot set ts pinctrl active state\n"); + return retval; + } + + return 0; +} + +static int gpio_keys_open(struct input_dev *input) +{ + struct gpio_keys_drvdata *ddata = input_get_drvdata(input); + const struct gpio_keys_platform_data *pdata = ddata->pdata; + int error; + + if (pdata->enable) { + error = pdata->enable(input->dev.parent); + if (error) + return error; + } + + /* Report current state of buttons that are connected to GPIOs */ + //gpio_keys_report_state(ddata); + + return 0; +} + +static void gpio_keys_close(struct input_dev *input) +{ + struct gpio_keys_drvdata *ddata = input_get_drvdata(input); + const struct gpio_keys_platform_data *pdata = ddata->pdata; + + if (pdata->disable) + pdata->disable(input->dev.parent); +} + +/* + * Handlers for alternative sources of platform_data + */ + +/* + * Translate properties into platform_data + */ +static struct gpio_keys_platform_data * +gpio_keys_get_devtree_pdata(struct device *dev) +{ + struct gpio_keys_platform_data *pdata; + struct gpio_keys_button *button; + struct fwnode_handle *child; + int nbuttons; + + nbuttons = device_get_child_node_count(dev); + if (nbuttons == 0) + return ERR_PTR(-ENODEV); + + pdata = devm_kzalloc(dev, + sizeof(*pdata) + nbuttons * sizeof(*button), + GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + button = (struct gpio_keys_button *)(pdata + 1); + + pdata->buttons = button; + pdata->nbuttons = nbuttons; + + pdata->rep = device_property_read_bool(dev, "autorepeat"); + + device_property_read_string(dev, "label", &pdata->name); + + device_for_each_child_node(dev, child) { + if (is_of_node(child)) + button->irq = + irq_of_parse_and_map(to_of_node(child), 0); + + if (fwnode_property_read_u32(child, "linux,code", + &button->code)) { + dev_err(dev, "Button without keycode\n"); + fwnode_handle_put(child); + return ERR_PTR(-EINVAL); + } + + fwnode_property_read_string(child, "label", &button->desc); + + if (fwnode_property_read_u32(child, "linux,input-type", + &button->type)) + button->type = EV_KEY; + + button->wakeup = + fwnode_property_read_bool(child, "wakeup-source") || + /* legacy name */ + fwnode_property_read_bool(child, "gpio-key,wakeup"); + + fwnode_property_read_u32(child, "wakeup-event-action", + &button->wakeup_event_action); + + button->can_disable = + fwnode_property_read_bool(child, "linux,can-disable"); + + if (fwnode_property_read_u32(child, "debounce-interval", + &button->debounce_interval)) + button->debounce_interval = 5; + + button++; + } + + return pdata; +} + +static const struct of_device_id gpio_keys_of_match[] = { + { .compatible = "idea-keys", }, + { }, +}; +MODULE_DEVICE_TABLE(of, gpio_keys_of_match); + +static int gpio_keys_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev); + struct fwnode_handle *child = NULL; + struct gpio_keys_drvdata *ddata; + struct input_dev *input; + size_t size; + int i, error; + int wakeup = 0; + #if defined(CONFIG_DRM) + struct device_node *dp = pdev->dev.of_node; + int ret; + #endif + + printk("hjc++ %s \n", __func__); + + #if defined(CONFIG_DRM) + if (aw9523b_check_dt(dp)) { + if (!aw9523b_check_default_tp(dp, "qcom,i2c-touch-active")) + ret = -EPROBE_DEFER; + else + ret = -ENODEV; + + return ret; + } + #endif + + if (!pdata) { + pdata = gpio_keys_get_devtree_pdata(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } + + size = sizeof(struct gpio_keys_drvdata) + + pdata->nbuttons * sizeof(struct gpio_button_data); + ddata = devm_kzalloc(dev, size, GFP_KERNEL); + if (!ddata) { + dev_err(dev, "failed to allocate state\n"); + return -ENOMEM; + } + + ddata->keymap = devm_kcalloc(dev, + pdata->nbuttons, sizeof(ddata->keymap[0]), + GFP_KERNEL); + if (!ddata->keymap) + return -ENOMEM; + + input = aw9523b_input_dev;//devm_input_allocate_device(dev); + if (!input) { + dev_err(dev, "failed to allocate input device\n"); + return -ENOMEM; + } + + global_dev = dev; + ddata->pdata = pdata; + ddata->input = input; + mutex_init(&ddata->disable_lock); + + platform_set_drvdata(pdev, ddata); + input_set_drvdata(input, ddata); + + //input->name = GPIO_KEYS_DEV_NAME; + //input->phys = "gpio-keys/input0"; + //input->dev.parent = &pdev->dev; + input->open = gpio_keys_open; + input->close = gpio_keys_close; + + //input->id.bustype = BUS_HOST; + //input->id.vendor = 0x181d; + //input->id.product = 0x5018; + //input->id.version = 0x0001; + + input->keycode = ddata->keymap; + input->keycodesize = sizeof(ddata->keymap[0]); + input->keycodemax = pdata->nbuttons; + + + + ddata->key_pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(ddata->key_pinctrl)) { + if (PTR_ERR(ddata->key_pinctrl) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + pr_debug("Target does not use pinctrl\n"); + ddata->key_pinctrl = NULL; + } + + if (ddata->key_pinctrl) { + error = gpio_keys_pinctrl_configure(ddata, true); + if (error) { + dev_err(dev, "cannot set ts pinctrl active state\n"); + return error; + } + } + + /* Enable auto repeat feature of Linux input subsystem */ + if (pdata->rep) + __set_bit(EV_REP, input->evbit); + + for (i = 0; i < pdata->nbuttons; i++) { + const struct gpio_keys_button *button = &pdata->buttons[i]; + + if (!dev_get_platdata(dev)) { + child = device_get_next_child_node(dev, child); + if (!child) { + dev_err(dev, + "missing child device node for entry %d\n", + i); + return -EINVAL; + } + } + + error = gpio_keys_setup_key(pdev, input, ddata, + button, i, child); + if (error) { + fwnode_handle_put(child); + return error; + } + + if (button->wakeup) + wakeup = 1; + } + + fwnode_handle_put(child); + + error = devm_device_add_group(dev, &gpio_keys_attr_group); + if (error) { + dev_err(dev, "Unable to export keys/switches, error: %d\n", + error); + return error; + } + + printk("hjc++ %s input point %p\n", __func__, input); + error = input_register_device(input); + if (error) { + dev_err(dev, "Unable to register input device, error: %d\n", + error); + return error; + } + + device_init_wakeup(dev, wakeup); + + if(g_aw9523_data != NULL) + { + aw9523_register_powermanger(g_aw9523_data); + } + + return 0; +} + +static int __maybe_unused +gpio_keys_button_enable_wakeup(struct gpio_button_data *bdata) +{ + int error; + + error = enable_irq_wake(bdata->irq); + if (error) { + dev_err(bdata->input->dev.parent, + "failed to configure IRQ %d as wakeup source: %d\n", + bdata->irq, error); + return error; + } + + if (bdata->wakeup_trigger_type) { + error = irq_set_irq_type(bdata->irq, + bdata->wakeup_trigger_type); + if (error) { + dev_err(bdata->input->dev.parent, + "failed to set wakeup trigger %08x for IRQ %d: %d\n", + bdata->wakeup_trigger_type, bdata->irq, error); + disable_irq_wake(bdata->irq); + return error; + } + } + + return 0; +} + +static void __maybe_unused +gpio_keys_button_disable_wakeup(struct gpio_button_data *bdata) +{ + int error; + + /* + * The trigger type is always both edges for gpio-based keys and we do + * not support changing wakeup trigger for interrupt-based keys. + */ + if (bdata->wakeup_trigger_type) { + error = irq_set_irq_type(bdata->irq, IRQ_TYPE_EDGE_BOTH); + if (error) + dev_warn(bdata->input->dev.parent, + "failed to restore interrupt trigger for IRQ %d: %d\n", + bdata->irq, error); + } + + error = disable_irq_wake(bdata->irq); + if (error) + dev_warn(bdata->input->dev.parent, + "failed to disable IRQ %d as wake source: %d\n", + bdata->irq, error); +} + +static int __maybe_unused +gpio_keys_enable_wakeup(struct gpio_keys_drvdata *ddata) +{ + struct gpio_button_data *bdata; + int error; + int i; + + for (i = 0; i < ddata->pdata->nbuttons; i++) { + bdata = &ddata->data[i]; + if (bdata->button->wakeup) { + error = gpio_keys_button_enable_wakeup(bdata); + if (error) + goto err_out; + } + bdata->suspended = true; + } + + return 0; + +err_out: + while (i--) { + bdata = &ddata->data[i]; + if (bdata->button->wakeup) + gpio_keys_button_disable_wakeup(bdata); + bdata->suspended = false; + } + + return error; +} + +static void __maybe_unused +gpio_keys_disable_wakeup(struct gpio_keys_drvdata *ddata) +{ + struct gpio_button_data *bdata; + int i; + + for (i = 0; i < ddata->pdata->nbuttons; i++) { + bdata = &ddata->data[i]; + bdata->suspended = false; + if (irqd_is_wakeup_set(irq_get_irq_data(bdata->irq))) + gpio_keys_button_disable_wakeup(bdata); + } +} + +static int __maybe_unused gpio_keys_suspend(struct device *dev) +{ + struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev); + struct input_dev *input = ddata->input; + int error; + + if (device_may_wakeup(dev)) { + error = gpio_keys_enable_wakeup(ddata); + if (error) + return error; + } else { + mutex_lock(&input->mutex); + if (input->users) + gpio_keys_close(input); + mutex_unlock(&input->mutex); + } + + return 0; +} + +static int __maybe_unused gpio_keys_resume(struct device *dev) +{ + struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev); + struct input_dev *input = ddata->input; + int error = 0; + + if (device_may_wakeup(dev)) { + gpio_keys_disable_wakeup(ddata); + } else { + mutex_lock(&input->mutex); + if (input->users) + error = gpio_keys_open(input); + mutex_unlock(&input->mutex); + } + + if (error) + return error; + + //gpio_keys_report_state(ddata); + return 0; +} + +static SIMPLE_DEV_PM_OPS(gpio_keys_pm_ops, gpio_keys_suspend, gpio_keys_resume); + +static struct platform_driver gpio_keys_device_driver = { + .probe = gpio_keys_probe, + .driver = { + .name = "idea-keys", + .pm = &gpio_keys_pm_ops, + .of_match_table = gpio_keys_of_match, + } +}; + +static int __init gpio_keys_init(void) +{ + return platform_driver_register(&gpio_keys_device_driver); +} + +static void __exit gpio_keys_exit(void) +{ + if(g_aw9523_data != NULL) + { + aw9523_unregister_powermanger(g_aw9523_data); + } + platform_driver_unregister(&gpio_keys_device_driver); +} + +late_initcall(gpio_keys_init); +module_exit(gpio_keys_exit); diff --git a/drivers/input/keyboard/aw9523/aw9523b.h b/drivers/input/keyboard/aw9523/aw9523b.h new file mode 100755 index 000000000000..26dd3dfcc03d --- /dev/null +++ b/drivers/input/keyboard/aw9523/aw9523b.h @@ -0,0 +1,69 @@ +#ifndef AW9523B_H +#define AW9523B_H + +#ifndef BOOL + #define BOOL char +#endif + +#ifndef U32 + #define U32 unsigned long +#endif + +#ifndef U8 + #define U8 unsigned char +#endif + +#ifndef TRUE + #define TRUE (1) +#endif + +#ifndef FALSE + #define FALSE (0) +#endif + +#ifndef NULL + #define NULL ((void*)0) +#endif + +#define AW9523_KEY_NAME "aw9523-key" + +#define AW9523_TAG "[aw9523] " + +#define AW9523_ERR(fmt, args...) printk(KERN_ERR AW9523_TAG"%s %d : "fmt, __FUNCTION__, __LINE__, ##args) +#define AW9523_DEBUG + +#ifdef AW9523_DEBUG +#define AW9523_FUN(f) printk(KERN_ERR AW9523_TAG"%s\n", __FUNCTION__) +#define AW9523_LOG(fmt, args...) printk(KERN_ERR AW9523_TAG fmt,##args) +#else +#define AW9523_LOG(fmt, args...) +#define AW9523_FUN(f) +#endif + + + +//IIC的写地å€ï¼Œ={1011,0,AD1,AD0,0},AD0,AD1接低则为0xB0 ,接高则是0xB6 +#define IIC_ADDRESS_WRITE (0xB0) //0x58 +#define IIC_ADDRESS_READ (0xB1) + +#define X_NUM (8) //列 +#define Y_NUM (8) //è¡Œ + +//i2c tranfer ,repeat try times +#define AW9523_I2C_MAX_LOOP (50) + + +//reg address +#define INPUT_PORT0 (0x00) +#define INPUT_PORT1 (0x01) +#define OUTPUT_PORT0 (0x02) +#define OUTPUT_PORT1 (0x03) +#define CONFIG_PORT0 (0x04) +#define CONFIG_PORT1 (0x05) +#define INT_PORT0 (0x06) +#define INT_PORT1 (0x07) +#define IC_ID (0x10) +#define CTL (0x11) +#define REG_SOFT_RESET (0x7F) + +#endif diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c index 492a971b95b5..746beb86c217 100644 --- a/drivers/input/keyboard/gpio_keys.c +++ b/drivers/input/keyboard/gpio_keys.c @@ -55,6 +55,7 @@ struct gpio_button_data { struct gpio_keys_drvdata { const struct gpio_keys_platform_data *pdata; + struct pinctrl *key_pinctrl; struct input_dev *input; struct mutex disable_lock; unsigned short *keymap; @@ -647,7 +648,7 @@ static int gpio_keys_setup_key(struct platform_device *pdev, return 0; } - +/* static void gpio_keys_report_state(struct gpio_keys_drvdata *ddata) { struct input_dev *input = ddata->input; @@ -660,6 +661,41 @@ static void gpio_keys_report_state(struct gpio_keys_drvdata *ddata) } input_sync(input); } +*/ +static int gpio_keys_pinctrl_configure(struct gpio_keys_drvdata *ddata, + bool active) +{ + struct pinctrl_state *set_state; + int retval; + + if (active) { + set_state = + pinctrl_lookup_state(ddata->key_pinctrl, + "tlmm_gpio_key_active"); + if (IS_ERR(set_state)) { + dev_err(&ddata->input->dev, + "cannot get ts pinctrl active state\n"); + return PTR_ERR(set_state); + } + } else { + set_state = + pinctrl_lookup_state(ddata->key_pinctrl, + "tlmm_gpio_key_suspend"); + if (IS_ERR(set_state)) { + dev_err(&ddata->input->dev, + "cannot get gpiokey pinctrl sleep state\n"); + return PTR_ERR(set_state); + } + } + retval = pinctrl_select_state(ddata->key_pinctrl, set_state); + if (retval) { + dev_err(&ddata->input->dev, + "cannot set ts pinctrl active state\n"); + return retval; + } + + return 0; +} static int gpio_keys_open(struct input_dev *input) { @@ -674,7 +710,7 @@ static int gpio_keys_open(struct input_dev *input) } /* Report current state of buttons that are connected to GPIOs */ - gpio_keys_report_state(ddata); + //gpio_keys_report_state(ddata); return 0; } @@ -777,6 +813,7 @@ static int gpio_keys_probe(struct platform_device *pdev) size_t size; int i, error; int wakeup = 0; + dev_err(dev, "gpio_keys_probe ++\n"); if (!pdata) { pdata = gpio_keys_get_devtree_pdata(dev); @@ -826,6 +863,25 @@ static int gpio_keys_probe(struct platform_device *pdev) input->keycodesize = sizeof(ddata->keymap[0]); input->keycodemax = pdata->nbuttons; + + + ddata->key_pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(ddata->key_pinctrl)) { + if (PTR_ERR(ddata->key_pinctrl) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + pr_debug("Target does not use pinctrl\n"); + ddata->key_pinctrl = NULL; + } + + if (ddata->key_pinctrl) { + error = gpio_keys_pinctrl_configure(ddata, true); + if (error) { + dev_err(dev, "cannot set ts pinctrl active state\n"); + return error; + } + } + /* Enable auto repeat feature of Linux input subsystem */ if (pdata->rep) __set_bit(EV_REP, input->evbit); @@ -871,6 +927,7 @@ static int gpio_keys_probe(struct platform_device *pdev) } device_init_wakeup(dev, wakeup); + dev_err(dev, "gpio_keys_probe --\n"); return 0; } @@ -1009,7 +1066,7 @@ static int __maybe_unused gpio_keys_resume(struct device *dev) if (error) return error; - gpio_keys_report_state(ddata); + //gpio_keys_report_state(ddata); return 0; } diff --git a/drivers/input/misc/qpnp-power-on.c b/drivers/input/misc/qpnp-power-on.c index 40db59095b6d..0fd3003490a5 100755 --- a/drivers/input/misc/qpnp-power-on.c +++ b/drivers/input/misc/qpnp-power-on.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #include @@ -26,7 +25,6 @@ #include #include #include -#include #define PMIC_VER_8941 0x01 #define PMIC_VERSION_REG 0x0105 @@ -61,7 +59,6 @@ ((pon)->base + PON_OFFSET((pon)->subtype, 0xA, 0xC2)) #define QPNP_POFF_REASON1(pon) \ ((pon)->base + PON_OFFSET((pon)->subtype, 0xC, 0xC5)) -#define QPNP_POFF_REASON2(pon) ((pon)->base + 0xD) #define QPNP_PON_WARM_RESET_REASON2(pon) ((pon)->base + 0xB) #define QPNP_PON_OFF_REASON(pon) ((pon)->base + 0xC7) #define QPNP_FAULT_REASON1(pon) ((pon)->base + 0xC8) @@ -373,8 +370,6 @@ int qpnp_pon_set_restart_reason(enum pon_restart_reason reason) if (!pon->store_hard_reset_reason) return 0; - pr_err("pon_restart_reason = 0x%x\n", reason); - if (is_pon_gen2(pon)) rc = qpnp_pon_masked_write(pon, QPNP_PON_SOFT_RB_SPARE(pon), GENMASK(7, 1), (reason << 1)); @@ -491,54 +486,6 @@ static ssize_t debounce_us_store(struct device *dev, return size; } static DEVICE_ATTR_RW(debounce_us); -static ssize_t qpnp_kpdpwr_reset_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct qpnp_pon *pon = dev_get_drvdata(dev); - int val; - int rc; - - rc = qpnp_pon_read(pon, QPNP_PON_KPDPWR_S2_CNTL2(pon), &val); - if (rc) { - dev_err(pon->dev, "Unable to pon_dbc_ctl rc=%d\n", rc); - return rc; - } - - val &= QPNP_PON_S2_RESET_ENABLE; - val = val >> 7; - - return snprintf(buf, QPNP_PON_BUFFER_SIZE, "%d\n", val); -} - -static ssize_t qpnp_kpdpwr_reset_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - struct qpnp_pon *pon = dev_get_drvdata(dev); - u32 value; - int rc; - - if (size > QPNP_PON_BUFFER_SIZE) - return -EINVAL; - - rc = kstrtou32(buf, 10, &value); - if (rc) - return rc; - - value = value << 7; - - printk("qpnp_kpdpwr_reset_store set value: %d\n", value); - - rc = qpnp_pon_masked_write(pon, QPNP_PON_KPDPWR_S2_CNTL2(pon), - QPNP_PON_S2_RESET_ENABLE, value); - if (rc) { - dev_err(pon->dev, "Unable to configure kpdpwr reset\n"); - return rc; - } - - return size; -} -static DEVICE_ATTR(kpdpwr_reset, 0664, qpnp_kpdpwr_reset_show, qpnp_kpdpwr_reset_store); static int qpnp_pon_reset_config(struct qpnp_pon *pon, enum pon_power_off_type type) @@ -830,73 +777,6 @@ int qpnp_pon_is_warm_reset(void) } EXPORT_SYMBOL(qpnp_pon_is_warm_reset); -int qpnp_pon_is_ps_hold_reset(void) -{ - struct qpnp_pon *pon = sys_reset_dev; - int rc; - int reg = 0; - - if (!pon) - return 0; - - rc = regmap_read(pon->regmap, QPNP_POFF_REASON1(pon), ®); - if (rc) { - dev_err(pon->dev, - "Unable to read addr=%x, rc(%d)\n", - QPNP_POFF_REASON1(pon), rc); - return 0; - } - /* The bit 1 is 1, means by PS_HOLD/MSM controlled shutdown */ - if (reg & (1<dev, - "hw_reset reason1 is 0x%x\n", - reg); - - rc = regmap_read(pon->regmap, QPNP_POFF_REASON2(pon), ®); - - dev_info(pon->dev, - "hw_reset reason2 is 0x%x\n", - reg); - return 0; -} -EXPORT_SYMBOL(qpnp_pon_is_ps_hold_reset); - -int qpnp_pon_is_lpk(void) -{ - struct qpnp_pon *pon = sys_reset_dev; - int rc; - int reg = 0; - - if (!pon) - return 0; - - rc = regmap_read(pon->regmap, QPNP_POFF_REASON1(pon), ®); - if (rc) { - dev_err(pon->dev, - "Unable to read addr=%x, rc(%d)\n", - QPNP_POFF_REASON1(pon), rc); - return 0; - } - - /* The bit 7 is 1, means the off reason is powerkey */ - if (reg & (1<dev, - "hw_reset reason1 is 0x%x\n", - reg); - - rc = regmap_read(pon->regmap, QPNP_POFF_REASON2(pon), ®); - - dev_info(pon->dev, - "hw_reset reason2 is 0x%x\n", - reg); - return 0; -} -EXPORT_SYMBOL(qpnp_pon_is_lpk); - /** * qpnp_pon_wd_config() - configure the watch dog behavior for warm reset * @enable: to enable or disable the PON watch dog @@ -2281,7 +2161,6 @@ static int qpnp_pon_read_hardware_info(struct qpnp_pon *pon, bool sys_reset) dev_info(dev, "PMIC@SID%d: Power-off reason: %s\n", to_spmi_device(dev->parent)->usid, qpnp_poff_reason[index]); - set_poweroff_reason(index); } if ((pon->pon_trigger_reason == PON_SMPL || @@ -2493,12 +2372,6 @@ static int qpnp_pon_probe(struct platform_device *pdev) return rc; } - rc = device_create_file(dev, &dev_attr_kpdpwr_reset); - if (rc) { - dev_err(dev, "sysfs kpdpwr_reset file creation failed rc=%d\n", - rc); - return rc; - } if (sys_reset) sys_reset_dev = pon; if (modem_reset) diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 2ae2ce9e73fc..4ddc070c6388 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -11,6 +11,7 @@ menuconfig INPUT_TOUCHSCREEN if INPUT_TOUCHSCREEN +source "drivers/input/touchscreen/gt1x_v1_6_revised/Kconfig" config TOUCHSCREEN_PROPERTIES def_tristate INPUT depends on INPUT @@ -1313,7 +1314,7 @@ config TOUCHSCREEN_ROHM_BU21023 config TOUCHSCREEN_ST tristate "STMicroelectronics Touchscreen Driver" depends on I2C - default y + default n help Say Y here if you have a STMicroelectronics Touchscreen. @@ -1326,7 +1327,7 @@ source "drivers/input/touchscreen/st/Kconfig" config TOUCHSCREEN_SYNAPTICS_DSX bool "Synaptics DSX Touchscreen Driver" depends on I2C - default y + default n help Say Y here if you have a Synaptics Touchscreen. @@ -1340,7 +1341,7 @@ source "drivers/input/touchscreen/synaptics_dsx/Kconfig" config TOUCHSCREEN_SYNAPTICS_TCM bool "Synaptics TCM Touchscreen Driver" depends on I2C - default y + default n help Say Y here if you have a Synaptics Touchscreen. @@ -1355,35 +1356,4 @@ source "drivers/input/touchscreen/focaltech_touch/Kconfig" source "drivers/input/touchscreen/nt36xxx/Kconfig" -config TOUCHSCREEN_NT36672A - bool "NT36672A Touchscreen" - default n - help - Say Y here if you have NT36672A Touchscreen. - - If unsure, say N. - -config VITURALSAR - bool "Vitural Sar series" - depends on I2C - help - Say Y here if you have a Goodix GT9xx touchscreen. - Gt9xx controllers are multi touch controllers which can - report 5 touches at a time. - - If unsure, say N. - -source "drivers/input/touchscreen/NT36672A/Kconfig" - -config TOUCHSCREEN_FT8719 - bool "FT8719 Touchscreen" - default n - help - Say Y here if you have FT8719 Touchscreen. - - If unsure, say N. - -source "drivers/input/touchscreen/FT8719/Kconfig" -source "drivers/input/touchscreen/xiaomi/Kconfig" - endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 6bf966574769..e7b8b0dad4b0 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -114,7 +114,4 @@ obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX) += synaptics_dsx/ obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_TCM) += synaptics_tcm/ obj-$(CONFIG_TOUCHSCREEN_FTS) += focaltech_touch/ obj-$(CONFIG_TOUCHSCREEN_NT36XXX) += nt36xxx/ -obj-$(CONFIG_TOUCHSCREEN_NT36672A) += NT36672A/ -obj-$(CONFIG_TOUCHSCREEN_FT8719) += FT8719/ -obj-$(CONFIG_VITURALSAR) += vituralsar_driver.o -obj-$(CONFIG_TOUCHSCREEN_XIAOMI_TOUCHFEATURE) += xiaomi/ +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GT1X) += gt1x_v1_6_revised/ diff --git a/drivers/input/touchscreen/focaltech_touch/focaltech_common.h b/drivers/input/touchscreen/focaltech_touch/focaltech_common.h index edf7b6b57685..2065760487b8 100644 --- a/drivers/input/touchscreen/focaltech_touch/focaltech_common.h +++ b/drivers/input/touchscreen/focaltech_touch/focaltech_common.h @@ -153,11 +153,16 @@ struct ts_ic_info { printk("[FTS_TS]%s: Exit(%d)\n", __func__, __LINE__); \ } while (0) #else /* #if FTS_DEBUG_EN*/ -#define FTS_DEBUG(fmt, args...) ((void)0) +#define FTS_DEBUG(fmt, args...) #define FTS_FUNC_ENTER() #define FTS_FUNC_EXIT() #endif -#define FTS_INFO(fmt, args...) ((void)0) -#define FTS_ERROR(fmt, args...) ((void)0) +#define FTS_INFO(fmt, args...) do { \ + printk(KERN_INFO "[FTS_TS/I]%s:"fmt"\n", __func__, ##args); \ +} while (0) + +#define FTS_ERROR(fmt, args...) do { \ + printk(KERN_ERR "[FTS_TS/E]%s:"fmt"\n", __func__, ##args); \ +} while (0) #endif /* __LINUX_FOCALTECH_COMMON_H__ */ diff --git a/drivers/input/touchscreen/gt1x_v1_6_revised/Kconfig b/drivers/input/touchscreen/gt1x_v1_6_revised/Kconfig new file mode 100755 index 000000000000..026a104d2f91 --- /dev/null +++ b/drivers/input/touchscreen/gt1x_v1_6_revised/Kconfig @@ -0,0 +1,220 @@ +# +# Touchscreen driver configuration +# +menuconfig TOUCHSCREEN_GOODIX_GT1X + bool "GOODIX GT1X Touchscreen" + default y + help + Say Y here if you have GOODIX GT1X Touchscreen. + + If unsure, say N. + +config GTP_INCELL_PANEL + bool "GTP_INCELL_PANEL" + default n + help + Say Y here if you have GT1152 incell panel.. + + If unsure, say N. + +config GTP_DRIVER_SEND_CFG + bool "GTP_DRIVER_SEND_CONFIG" + default y + help + Say Y here if you want touch driver send chip config + data to hardware. + + If unsure, say N. + +config GTP_CUSTOM_CFG + bool "GTP_CUSTOM_CONFIG" + default n + help + Say Y here if you want to customize the resolution and + INT trigger type. + + If unsure, say N. + +config GTP_CHANGE_X2Y + bool "GTP_CHANGE_X2Y" + default n + help + Say Y here if you want to change x/y coordinate. + + If unsure, say N. + +config GTP_WARP_X_ON + bool "GTP_WARP_X_ON" + default n + help + Say Y here if you want to mirror x. + + If unsure, say N. + +config GTP_WARP_Y_ON + bool "GTP_WARP_Y_ON" + default n + help + Say Y here if you want to mirror y. + + If unsure, say N. + +config GTP_GESTURE_WAKEUP + bool "GTP_GESTURE_WAKEUP Feature" + default n + help + Say Y here if you want to enable gesture feature. + + If unsure, say N. + +config GTP_HOTKNOT + bool "GTP_HOTKNOT Feature" + default n + help + Say Y here if you want to enable hotknot feature. + + If unsure, say N. + +config HOTKNOT_BLOCK_RW + bool "HOTKNOT_BLOCK_RW" + default n + help + Say Y here if you have want to enable blocking rw + feature when using hotknot. + + If unsure, say N. + +config GTP_FW_UPDATE_VERIFY + bool "GTP_FW_UPDATE_VERIFY" + default n + help + Say Y here if you want verify firmware in chip flash + during firmware update, it's unnecessary to enable + verification if you use the latest firmwrae. + + If unsure, say N. + +config GTP_HAVE_TOUCH_KEY + bool "GTP_HAVE_TOUCH_KEY" + default n + help + Say Y here if you have touch key. + + If unsure, say N. + +config GTP_PROXIMITY + bool "GTP_PROXIMITY Feature" + default n + help + Say Y here if you want enable proximity feature. + + If unsure, say N. + +config GTP_WITH_STYLUS + bool "GTP_WITH_STYLUS" + default n + help + Say Y here if you have pen. + + If unsure, say N. + +config GTP_HAVE_STYLUS_KEY + bool "GTP_HAVE_STYLUS_KEY" + default n + help + Say Y here if you have GTP_HAVE_STYLUS_KEY. + + If unsure, say N. + +config GTP_AUTO_UPDATE + bool "GTP_AUTO_UPDATE" + default n + help + Say Y here if you want to check and update firmware + during kernel booting. + + If unsure, say N. + +config GTP_CREATE_WR_NODE + bool "GTP_CREATE_WR_NODE DebugTools Node" + default y + help + Say Y here if you want to use Goodix debug tools.. + + If unsure, say N. + +config GTP_ESD_PROTECT + bool "GTP_ESD_PROTECT" + default y + help + Say Y here if you want ESD protection. + + If unsure, say N. + +config GTP_CHARGER_SWITCH + bool "GTP_CHARGER_SWITCH" + default n + help + Say Y here if you want ot turn on charger detector. + + If unsure, say N. + +config GTP_POWER_CTRL_SLEEP + bool "GTP_POWER_CTRL_SLEEP" + default y + help + Say Y here if you want ot power off touch panel + after suspend. + + If unsure, say N. + +config GTP_TYPE_B_PROTOCOL + bool "GTP_TYPE_B_PROTOCOL" + default y + help + Say Y here if you want to use input type B protocol. + + If unsure, say N. + +config GTP_SMART_COVER + bool "GTP_SMART_COVER" + default n + help + Say Y here if you want to use smart cover feature. + + If unsure, say N. + +config GTP_DEBUG_ON + bool "GTP_DEBUG_ON" + default n + help + Say Y here if you want to keep debug logs. + + If unsure, say N. + +config GTP_DEBUG_ARRAY_ON + bool "GTP_DEBUG_ARRAY_ON" + depends on GTP_DEBUG_ON + default n + help + Say Y here if you have GTP_DEBUG_ARRAY_ON. + + If unsure, say N. + +config GTP_DEBUG_FUNC_ON + bool "GTP_DEBUG_FUNC_ON" + depends on GTP_DEBUG_ON + default n + help + Say Y here if you have GTP_DEBUG_FUNC_ON. + + If unsure, say N. + +config TPD_HAVE_BUTTON + bool "TPD_HAVE_BUTTON" + depends on MTK_PLATFORM + default n + help + Say Y here if you have tpd button. + + If unsure, say N. \ No newline at end of file diff --git a/drivers/input/touchscreen/gt1x_v1_6_revised/Makefile b/drivers/input/touchscreen/gt1x_v1_6_revised/Makefile new file mode 100755 index 000000000000..fdc1bda9fad5 --- /dev/null +++ b/drivers/input/touchscreen/gt1x_v1_6_revised/Makefile @@ -0,0 +1,5 @@ +# Goodix GT1x Touch Driver + +obj-$(CONFIG_TOUCHSCREEN_GOODIX_GT1X) += gt1.o +gt1-objs += gt1x.o gt1x_generic.o gt1x_extents.o gt1x_update.o gt1x_tools.o + diff --git a/drivers/input/touchscreen/gt1x_v1_6_revised/docs/GT1X_Driver_Porting_Guide_for_Android_20150710_Rev.02.pdf b/drivers/input/touchscreen/gt1x_v1_6_revised/docs/GT1X_Driver_Porting_Guide_for_Android_20150710_Rev.02.pdf new file mode 100755 index 0000000000000000000000000000000000000000..1835e4038a0bc1129d72f55bfcfbd141eceee39f GIT binary patch literal 355764 zcmeFYWprH4vL#run3n3-jN5j!%o)`_fLg8w#6ftr& zvURWkDjHdsxezgbij{$~M)np|-j?(t^3+5?6<23B6IWGdGc#oe2Uj9au78He89CcX z*_%6js`=YBGbQ>QX2$mM6H5eGMWS0W}xpp2ENiw+UXUwuR@ zjQ?s_|7lqNx~WXW_K!P6Y;6C!;{2y!|CfxN_qH;ZGQ^H9qj+!6=x#)$2Xuj^CzyqGN3pM5!1g+iL(+h{aeaL#Qbk5I}!6gWaQzG!(?5>s5dr1QOs$NB9XxeD zZ88$Eaj`M5FcWdIGBI#5e@aC@z59ghLiA5iffCLRZjPVc{xj|WTmV&os?J9CE{=ag zW8wuAQ2~mYxm%f-DN6`{no~70cKICguLbb0@VO8%{^v;lRs0{37Pqo>HFE}v+kQq; z)Xc=e)C?$VW^dtYNyN#?!twVh7guL9BRhB)&&&ngHD{fnBtMO$caRuC@o(~*?IpbB zwPb7iswh0Q#ql^A1XL&}T%=!Efl|B%0ydTFkeo!|ie+9n?V|Y#o9>sVt1qmNH>Vlx zV~8ad9`)5--cR@I>&GjAlySseX#fg$MwJd zQAzjf@O!t_Ohz*&%n@)Q2C8H{5i^Kn>8%7ax>VBHq_rX?PZaE@Ak%MhE-tjWTDgbT z@-whNEY-MrUR?3!?Oz&*<0&7*}##GJe86aJo0%3QQq|htdekO^?XbtuT zme$=lsWSqDMN^W|=^0Kf2y{w@ds`VGW9(y3Zt0rMoV*TpYwpa{ck0ZVYRZ;I-O^-w zf}8G))cM#MSaR@UG{$Dt;N-K1dbYwFL-TuArg}gxg;WO^hd>jD)%T!nYk=0lr04>C zf9}UJ&D}H?V+DyPQ(m4Rlrxhjr=+!;!a+lfLJzruxCw_z5p2g8Me#agQw+_1U_nnF zU_aO(`mh#2r@(ttdx#h&c2Dhysp#+fhNYPBq=?+?=%_M6?wsm4 z4U}KG@CUZJ$Q0~~_LxG{gTRT9zOR=aYWg#V*BWg*)!%`xue6*_f2q7eHRVUsprv$P zT%&Gv8Xr^urZbbHTM1?DSqG8r)|eE)uoAe$j12lfLL@LNgk@AqHvprJn?)JytVfkMeE&Y+gV0#O}V*+ksZpj06_)qfyJhkG+Kyt zxlrd)2R@N8$oVkwU#1}6VlSDJLjUCJ8@EnM;GqR@3P6+JMYC(_2pQ(8j7Q(o0-C0Z(C3~pLKycFF`cof{p1?=%8Vp@x-62?ZR;g>Zh#bw3ap2R?ko232WfUSqw&jN1EW;d%Ulecqq651`rM32S zxB8Bb!bJ~}r5xcr7f@@OB z_rrS@v#hy)#8WU9`)mOGd1ucwH;XLt9fjVqQe+;sn13C72W`PuQOUQMzK{f895V@e z-kb*YF(#59{95Cvnuhb3qePJcNoE=PYr3Xz-kSaLdg-s&be zI$v7LGP{9kD|~vWFDp&DS9&GB&qzK2o_P_2g9c{e8!LttrFyYoJ7U(2XUXNAYa|t0 zf}MtMY=hV#ysYHBSnU^#gY-!=rzrW5Uf6Xs5MGlxVf_+VXfPY{q+QxL9 zICIIe&cO|*D+y(UuB;7JcZyn8sFsEm?exNKnNm)$gtVbF-ss#~vy7-Pmj z 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�&(plvq9^D5l}%Zjl>5P?+R>3%_WkHZ~9kczICepi#aY}L?1C{Pr;!ISdKG{X1L}l)0dRkSNg^yGdLs(-g?&AACuMbNmCpwD7 z;4GPq;(Nuxl#3#O@K<60`d^6w+`kfo>!<~jT|-4iN6P|Y=i9yt(wnaBkUxn5RMtmg zU>@F=)P(+qVhJXSWyN`{A7YxTkv>8h(yer~Ll?dwxSsos=1Z$WCoqf@M;AN9jtN|u zAa;WC$gikN(*q4ITuUUw%~C%^BKM4F!2#>tJPK z_L<}wjo(?D?(BU(nCfE!>v^?UxgF;`F=BJHrC2bM5u&a&Pz*qhXMvv`B3Q~!csYaW}w`!QfBLf z32@8Kwk1Fxh+_Pa@oH%WB0{|O8RpkvZ$VR$nDvh#b*zzszp&DvKm_1vhROXZea2FZ zU3?$`1O}gq+=R`))Rx=!f_|4}=?{@ZbJ%vemdT&#NntWB=SuQ_gIGXos2zZY0vn)M z7DTa;sB$EeGZ*q7N`*GKn&Er2SivDL2J-fda|p$$b<%89Nrd%{!1 z3m(DT6R?D}KDt^@Dj=u|Mc9Bu(9rcm+xQDGlM~ZIMc#FV$s|R>95qdtY0@efC2%l^ z$}aDT+*JjuvDpyVVmTQ7@C+$V{tx8`Rf%1E6YK?LMV$095S#F}wy_&T^IHH=_56^^WP;V* zvkdlWscj1~tC9X*&KHW`jOLVT^pRry z5O-DRRE#7rt>d*qx5ZQqy_h7fLnDkbYgd%)&A$JFst>sF+vkYDW<%D*&oKjqDkw zKS^Aqou>W86q4WLUm;Lh-cjQcAQ=R0t}oRaEWBT? zYot-);|%MZsL`X0dt*xf2ddWnfT}lybi%6uQ1!LteqaynEC8y$l%A2IcVyAJqa6?% zUxe&&>`OFJ9usXgmpU?cAiajz(+cY8fa$oAtJWMQ$QMV(>bo0=A?A*3Cw<6B;_7~Jc~q#C zBh*2QXqxK54KVc_`11y)vG~maOFLosVEqwf+*S6qwIG3eedyAJp)$c7tm~PeY(KSe ztT=tzRYif1Ib~5_=Ffg0$1KNRO-T62b;r3DGf}?+gM6u;8j5oqG;{?()lU6?py~xa zwySmX2ypjB=^+;6#l{|cd=Lp7C?4+N5VnV@Q%3o7A%l~VUE+sOMtF2TVMtfB!kHWo z6r@MUlITNB{$pdyeBv50zftUcFq9NUNTf1xdNSW{Z6$sk@)?TCY(47{s|=?=v9+KT z&7%uWDgamgBwRyzfsUfrF%l|bMhN}(G!NA=;tLoa>1lK7BGd%M+;@BTAUR6+i_}o| zXTK)SM(&NQ;h~k@p0l3fJ`%ux6sa)|o3JpbA!2$)>|j=;O?{47d)f6G)$tyufgZ`q zDbxd_FDX~+Eo$;}*;Tyf(OCpwG_cM|cc6Y4Kngii4j4etumX$))9#O_tpujLs@rKKCUjhY<$IH~7kJe7 zhnUn{AlB`$8nwfcFw`_1PP}3{!Az+7>OE5d6q3n42F$=0F#5K%9NNXn_Ywg) zI%QY`(N^R{Djn(G@;01ETm`WWx!?%sO2%&O8Z26dpwfkVEKz|9f?J{ zZNlHTd{0GsLVMP%lm)_o*LKB}*7tialc**2(5%U0jwI=^pP%TERSuVyZmj7V4E%uZ zG_)T>EU^nS9uR)0;`SK3r@?+|OrLU}JVJ+%gQA+B1GAoQUqL>hFU&@KTEkBHW90v8c+Pr&Cs@c~fA(#WUG_=43_tAvQZ? z0?t=qsSZwEEG=3wYV4=FxI?>V#U0Eg+EZ^3=L6Y`YJ3aWwV z2A=z#$4(bJE|4GS~f|n(^_O2m6nb zhmXuG?wz?_F5|`Z@J2JuzN(sucYEH9+>noxDFd$O24BhSursij>#O12PuIo|T(Xt(?>LShtE5e2^SHdcnSYCs{h za6WFAHG$Ba;qYwe=F#4<{&fQSR%6;QNHlh4QsA9I!u@;K`^yW@?GIPN7iJttuQYEL z04_S%Yh-oEkVO#ibo+U9OI6qV{7_!kllEH{KJWeQk$m^-{c%s*lat#yz~b&K^uW~+ z!hx%m9NS^x*t`D=slEju)uu~T&bC{G2~R)T&kjyrK(!7j9bG)GIHv;*v`SF?B`p|x zzCviIoC>b(m4l`q6$e8`&wIM*&%e3{0n1+>4a#!7{{nY?dVZrz)7kzJ_p|omCfT3H zJJ%w$dq;Y87)4g@>d2$RF|>Q?YI%`vt0h}1gT{caUi@GXUDv4EoBzt?-Sp|+S}d-* z`70@n6?^;6w$_%!Ct3JS*KgY!uaD1P*JxYKd#w;Jo%Y7Q1Dxun&JU-$Fn#FsEIi)7 z;>PV|EsB5jeJ|2YaABMSvzjg}9TMjy)9mal-E7~7lwEj;r#A#-sR)Jj8h^97*NeChs9LbcIE|!tL2yQ=3-3l zg)nA?sRy*xsAUO1%4IXNRd6bJQ?JPKJ*5W4_}|~$>N>dB%FabOqADz2owbyP;eFnU zv>KOBhT3)K2{#oy)qaD2cXMij&#qO83y=QpPSE_sztg^p+>9f8z=4p>Q%b%9Z@p3%Sj&$P4|Y=fGjDbVuo4c8Wu+=1%il`-q{zO3;%f z09CE~9z2qRrgo(V#)8sN@m^n;U*G{am%yg0qJ zhvO>BPM(76Yv4OJ@ng^5J2wKGo4`Lh0wK&t2B@~Fp^}U0p+?d1F z9R(i4DgHsLJZ^Wl8bsXbIk;?~5|rQHa(_d-A3Nl>?_Ge=d3 zMqa{}TBb2CxA!im$oUC~X(OVO(*I3CZ+|;CYxIsAHRI4!7N3;?#1VSo@ zEB5Tui}m(R$Kzqy0q5nsj}nP}7FQ2MBF+VZ;1lI^8ovQ5xJ%2oN=;8!q^D__cP{tq zyTDb$jO?s8lGM$?-aW?dI3SV<@AyZvE>8x*crr=}qlgr(1PlwWs3Sv1U^cUOx}ZjWehIcwfYu&@5zpt>GTL9SDw-_&q8NnZLE6Ghlm%(?K#UJy{<`k9!H zvWOsnL4<Ca?;!ObO9o^GoU*buFSK^s;M{ z!p)XZ0&mW8FC|XLme}a@=jhlQpOr~F@s((dX$qM=>w7Ooo8OrBtB(GI8})GseEJtJeSw&LYsQSRBjSf_pecls$!8rAx(H*-#uOUpvmZrwIQJ)JSMNDVLF|yZdYaoaI%O&sax`wNb_uxC!0a) zyaP2m-4%klIt$nyLD@P7Ow`j73N#S@jH8|Qr|;&va@tP_Vq(}M85#&tvOCD;fQ!#= zc6}BNH_N^xBa1-p``NNcOx!v03ZlGThY@*pfG)uap`up&t@MYeBwjukrBDsZkK+Bo zDf)TyCAZ(o zOQ6qx>r&hnl8MOpdhxGtJyR*@Pbc3JG7@(Ok2*!Nodj`R#Px2c8y!C{KK+P*RaT&} zY$*Vrcq3>wzJQT`T(%j9OjhGCZyE4U8oMBIKoffWQL;*@s8pkCQ}k^Ed^2vkYhXLh zXO;Cg1{lfy_g^UG$r0Aw!`)F|=h$KRb(1F{&)4`i6t8+u2;(8=FIv-#iZ+Yehp`6H z7nX(Jp^^~dAWnJf-tyIM7&g5H6A2H_sMhtBNSb^vrk534EQ-~euh7TZ16HeQUya?q zPl6|;f0IsqSUd?_Paysp#{?sl*?U5L5abT#nD;;pZv%ggyY#E&$Kv8-UZNrpbJ$C9 zvCx6oTDiO;yl8tohZ&_p=I{ME{>xVl>tB;U=*k%J?ljf%U7p(yIiuoyw+gT(u7kWz zvHCL&TBLy|>&vxbzSVoQIH;&!J^mEzYg0>~- z*4^MetFUa1_fgc|9~L={@!4>~L9D(??2F`jYpNet-0m~OY)x-t(4|W+1J=&YChqa| z>hyrE$Lw{w06Cb?G>V2)w)y$h`+Pi8Cn{K>4gcHBLEc~!W0=Elf}vTG>wy)3X|!2a zzq9wPuS~A9#JPHF?4n+N?KYlc7vt>p)f#@AM-oRo;R}b1M0O9$sopo*?r-7E9!7$P zZhNxH&X`QCKlZ*JrFi7{6Xd8zhjucxx8Vf`s%gAxIz>WTH!joMTgr0HK<0t z)QGFmPJNYk#G|F@)@98xudN?k^btXV8e& z-o~Uoyh=sa;TV^pu~k2;^kxIPWN@XKI5Vxg1vkuGyQh;Q3y0i_$tC*ceJ`MfIcs zFVF!zZ&CN+EbfP3`Yk^!61ojwQg!9^ygK-im|jtjh5a6&yt#WQhVstz84eI(1niBC zaUA+sA5YfWaw(Ky>zNfh+v;B}_OJ{rU<{(N&$D{A1SijQpKr*X>$0$Zn?H*!)>{i? zE>;s=fV(KgwEJ_vJ+Jr+Y^-p%rUp1`yPUVBu0B#EAbM%&HWibkny~cQT&CcvwQe+*EU-#KF z!={+wC`+^eos96(wIO63SMq5SX6Xk_@s97Ila~_4@w+>ITj{xALXdQsx!%B?c0`D z7;Qb4ro1%*j~)uHu+uy9v#f#j^1SqMLyvt7lQ{1lMEXVQIl{ml=26)Ozy0BP4ohAK zsD94Oi-&-n8>Z{moqFe=x}<*0UHr)WmU-FOw@;XlN71CWnfu(@Sz-q*oa~04o@)kM zhH7p;ScH1|BiD9&=w}T?*}ApzKOtR3v(PMbsjbQ1uR6SI?|mz}Zx%7Szmz&m zyER`p+2UjPPt0^43L&83`6sU|8^eRwl;mt%n?h}ftJ@&ev=wkX^|!sADZx*6OOfL4 zPqwB7HCgB$m*h7Q9-GF6Pd7^y(ce>+%D3x+DcR%!k%zIJgGOOa@gvwH8!3vY8lc5Y|h&cMd; zzQ6dyLq>4MmsoAvf*nWv;Bn62&Lee$2I)&UDuGM(6T0RrN)04}D5eYQ7BE5Y1X)-dM@I2@61a_4Nl`rP1z^JQnG)mCq{YlMdp= zXY5UL&PJC9@$EZ&s}BMG~X2SkL!H%3$sK>K9-vHStX_Rc0#@eUluBh{FmYO z7d*a3Upq(5t)EUyu)SGPBX$cRnO?u$X81fV>4Y!%bIpelU9KS$IUa8u&9e8*svpl{ zy;hE{e=&Uxx#h%(*3|yVOQkw^DX8 zc(lIPcw1A8|Jgn;8~&-sEh5c==h`p3)=^cZI2)h$x5>BP`9m<@)FC&v-u8F--l8;1 z;a_9(b%#H@WDoRhdV?tqtG$s1OY;f3H*%9PhNbTX*Jiw%U-*Q?hDNSAzMPzUWe-d) z`r0iCU)G{zV05}oqjK1}Vgwg=X*5tU)4R1@ytR34WG>C&G~GMW$#32w$!EJA2UJc? zPgniKn^@=C;nVgy_iAr+M!oE+wRVV$7~aIj+NzM#xfU5=;xE$|e)&qA^#+a#D!=)S zQuArC?jgB4&^lA@H=3t)wv5)K$)G64-4V`{c_ zFzVLi#C5;%V{MLl@b|d&^1WrqU$&+W(^&a4!+PUXgdz`|4dh!l_kSJM_MAQmr99&qkvmSJESd@1w_G2RYUaANPN# zks8~APuu7z-8{nk^B0!)hk{+8v9@-uJf$MwHv66Nom)^ke)i#e0D3hw!20aib$M0% z6mEy8=Mz1hHHnasn!Wpz<*fwvtCsfl?GMMF^T)vmf2O2h7_X9UnXC&a34bcE8@reVL<@;C{mo1E%iY+BkZAkiMG7bXz&X zPkKXaK^ac?-#3+9{}?3x-(E*@GXCSo{2$hlOiUcC|Fw?%Ze>R%{+0Yy#phiZ_@z*) zylTo-JOa{B>onM%@5?yK0k)PA6x!%zQFFR=tz@(*{O^a(qxF%p2}2bVzBqrj8kU%W ziin672f`zM66Txh`1G~$^YzV`NAcMN+1lQZ&BK)5Z}!)Z?eiST zYQ@dT%W8U_Oiwo_YVpGiUQqEIgrDCEQj51K{^-n{h$e=(pTD`?=z80uD$Vg0hdS8m zR%ridO{UeFS=#62FmR->smvC)$kDd6Z?4!B(HD9 zY?C1H$Kf`IYUjd zWHQvZCT!BkJ~JSzF_)N3yAj046OOI7Nj5RtrvYr{&Wu9}={=lS>>1rN3xq3+jwWRP5>yC>v4~5@2!|QM3$u4X;3|uyl^Tl_CW+(W22J zF?Gow32q-U^XA_&v&_F_=Gx>g>om*|F)~Wb1+cjs<|Z^h8{?t{$XRMD5m5UB#2`bG+2LF8p#lRvO64f68Z~f&a#o%YX-O_&nyQ}C4$MU*ly}~%WY+kBv2=_4%#)V zLOXZ`{TAbTDy1ZL970w(IKD>Ax+D6Wq4npSnqkuFh=*c3wGA#9ONRNq#*)G8lo6(>x_L;aj-*I^) zafXHy_%M98?g(f~Gm{+RUh&C7QYoN-_ftt+hDwZnMud6}2>ARtlZKmJKyS{3&~BIt z@O>aV!8E4*^inOJnYrxve_ZBNSK43OGf<{ShCHEpZ>H-}MALpJj{W2}6lZWE_CC0? z2_w4HXF(YIq8@&R(KoZYk^QCmbhP_<)1LV9xqYq){)%E*}|EzQJ&k1_k4@tUT$qPie&fCoZBe17J2G+J`dG9R_!G9fO z=M_l7mV@W!YWy#infA|65;ax@FqHg<%KT?2`5%=T?%ygi`9D?WwpFFb zf2+)DNk&ose^lmAuA>4A(1Iz92AjlwOss10)cIqQ5T8`d1&}2*vDg{+QxDHM&po^K_21& zQUPTO+Yf)FJ?^F`7OI@-9LE|SkU~aMA5}-+l3d2)hcaI+|Gb?&%oD!Vx@5>lpR5G^Rb4SD(cd`J-DeIMGth#yqRzXCw#3G zL6IY~<-}}oY?30mhuA}tP(N_Rn=Awva>G#_$W>6u>f~H#7}Jp-cJTO6d>}qyT&nn) zxR$Zn_}d>*n~JmnnGVJvX(bajGI>_PMUQ&8j3#;E5R@9`ABZq9uJfemxq^#0(R@p` zWDELi{FZz5bsIdMD}Oa9C%e(81nCoNO$tZ`Pz2Uws{P{Oh&}`u%8B&sACfuyj?K&B zZ<4wC-z0PNzewf^$uPPGo4Nd`1*$oW$Wv0^Mky5%*z)aC1AJ&RsPUZ1P~2r0cxUjO zIIJ$=We2SqSl|RBi9;!^NTfF(VREI1Q-VqRGXX{~I zQ5=jCc}|UHj7?}QmU0wy-$oHkH%HwTBWGt$TZ%1BauYOs7GkQixIaZ>%LrV)?+ACr z3RNj85q|II_P(^eI zSMl+HmCj%a0h;FksUKj7wtW6drO>9U+JA7^n;=c|i*)Vx_k(!*|r&RLnXMBHc~&N&uMU z+2r&XNkhbD?aE*(wwJl3G#U#-&O!Q2g$Qe#!WLJX_R|(uL4?VinZvI?9y9r;PuV-H zP*xvXNeyu;tW-{sR6ID*KAHSaQrINl!EjUGd#(gUafmo}fn;ZUX||;e-q0V+r&>+g ziJj*%@Og?FNcUiB8K6TtnAjFXSBR0>4-}3C^lQ_;qZcX6fK4vg81Su;>Jxv~-$*(I zc`NQUmB2bGUSzgyORMdYJ*(rmDi3OAYBGOp(cbc>Rss) z4QhNz4hh%j9Vv@--wO)F1_-*EMhKFLUroety5 zFJ3h@lI1r=4+RlSu~7KY18sDcKGV#NX2{J@fzk1OCr+SpSnXOx?#Es?sfLKf=NZG< zh(XkJIB1+FjN6PHqv)h^&-I)k;er@jWw=AF3$gx0wWp{uQOQ_P+%u9Sw(F*X<&}%; zz-8b|X-GrxOB-VCc+uVzOKap;Erc}6I+P%lPCvYoDFm{V9zKsHO*0d5t;(=SxUbQV@c{i z8uQU#8uPCYjTvJDpfRTaH0I3zO=C7U3PnYN3X!GvCtHotE)>X^34n-1MHTv)<8d`# z!!e<{VHUFAEe3AiMXVqUTp|I+6fs{Qkq^MH6JX%UJ6n#CM(YA-rL*bt1#b(xEx$yy z1g=5R;Ux>qsLgbM`@%|jEvn<%_vRNEswis|+oj$ZGnN5UEDUYS&1KO6!_)~q<^P~5okUyksN?37A|@xvS?gb)aUZS^66!_*EZlb>8k*@5b`mLcCnEfP5=;hV?)RgRRj84?`>10yUE?ywbXaG zU8*Z0YK2o825WqSFa2N(EQR{Ry2!&Ir;6o*O8>)RR{Uczw*f5XPx@u(Zm7xswwP;e z0T#2`|6(zx(LH}y%%^`_%y|Hd`Ru$SrV%jwu6J>JwKgI#tZLu&(9H z1klN;?`+#|A9>0=PLK={0Y5H~tUAT5+7Z>5If~O(HQ4;+r`Y@2yN>Ryr)Ns6ap&Kf3iMRKUGer0NeM&xI}9n>L& z1{4%Hv!t|aK%lv7*rbrK-8bY69w^zu3E_UHkgS0WB}lp|BSX!AgKlI6Yi9uwx*q(k zQ>Z|N%l;v7nT|@(>U#pUk_%F3ompt*=uKF!q`*K9Nh4IL-(&!rNtw|p&~-RUpM>{= zV*WCfPwI^iC(`O&a3wVAHxNq~EoCF_0mM|)Va&5I8F_j#xj`-x<%};yCfpOBYWC7* zG_`9%w-`yGlW4WAD+CN$?FZ+?$>w+9aWAKQ)0*ic+3Fkj3inOnRw9$E?agWHIkq>kF#)GZCQsv{uhZk&BMwih4SlP z67$Pn5;OQ8iJ9*|5;Jq*+_na3?>^4ogGtDL3?>x-gGro5y#-(pBS>|-#L~Sopq24C z%=(fXEt!rT9AvEAK$^ov4N7C|LMTp%1v}Jog+*) zYXk{e&G!=~+%@qR4b4^qH4Of&J&j)6}6IKslbL?at5G#~R4k#5O z_F5A6j6$G>P|$1to#xnhrVP@VWWVh@95sjtc1s{gEt+q5)zF!gOXFvn7!}w>A$3(- zFmkEoFIOd1VtbHO`L@NvLtsDATgV1qsbk?PsY!ZREaOXB5&(-y^O;qZ=ctdxo>zInV|Nr{Y#s6e_-M%$EbvHHcvrh`a@HS6^g=`J$Q}rXDW|R#^a! zeah}MOfa}XZrAJ0t-gMB{2XWo5 z#SY~`In@7BoaEIC9X=IoUn9_%Qw*M7dS32746|VgJDVPL#oR!U2X3rLFDKx@ROW{G zpkJgTrlXSBvKmDS21O9YPT#?F*a z)q9RlG_54zu7rs-yVT}EPDwD~_Mvz+N)dDuu}Hp7jtR02oc8+5Uwv+k7C*sviim*53#-2LNHV`U_#Uy!eAK|2>)v zg!vm`zN6Be+P@AbMA7^CgpPx|lZ*S*Z5H;Evh?yeh8l1h?)%-OXa;X**6Vz6wx=6O zH!nv#Gnd=j31tu>HbpKoR`yLis#{qvuJGjkU~%>|J(i#=wMWT^9ZPO zUw(_t@V-CMdftCNi%skFdO2H-PpO&x;+JKz-t>hZ$*TkBa19a@ z1}D!0S3_QAknb-hei*H#Xmrf90Qg5|!x7mR4_%q#i%cDyxjhpKd#d4;!@B#HaP^o2 zBB-3J&X4EE@{gZ)m-9F}GE+D@QuO{fzPUfw;C~*Vw7>0NV2sn*U#3phoZp{n#CA=w z$Zg&bpYP(xOk5q}6Szlr(f!RPv()9jZT!t9h4=T-3BIz)lolp{=lg|$P@FB>S2np? z?$X=M&WEg#dcu3He{gnpci=Kh`rWeI67p|0$>pD*pGIJ?UG_LNIC7FUTBo7--2Qr+ z#CP(soA{MYF51%j_(jm!V&-yEXbl^^-=E*F7cRHWnZ4VFEa$1?shUhG*f?Po2pB0T z8y1JF{kAA$Jbe|-@dH1>Hxn?=DWB3xR1@>^v)6%ER7a$gnC0IN;rv)x(w}>>PVR1j z#>}Z>^K|g`b_Cuq{6)w(blDgxZxCqpiqDgLa9L??U{MRP*E+d+?DdGzBs(lT)pa)_n^7% zwrKv+tM%*h(I2w=2){*xCe(^8;^imC*aM!-_SFSfrk;OAuNmP}6Z zDQpx|H~vKLj}5Jf@wq&uFvokB5v59~{Wh5N+4#uz`qOG$H|6c1lif*4o$kWr)(Uw( zT?gf{QP6EYzt&5~$EH_H2~;4`NoN-P1Ju8geR1(H=5F71=GiOhtB`vy{0b*iKjgIx zCg}QqJ0WDSz9XdwBiM{6h4 z7B+K}zdm7?g_iP9F6%a##FGp4!l=Y+f(~hnv}yGl<{_UWP!B_=tcLq#-4OG$T3B(S z@?3T#RglQNX9M>#1(aR`g+KHOZr1@P*Si{9{8@m;(Jq4f$}UfT<*Qc>v`LM^1J^jb z&bZC44BneFRLse&T?-PQFhLv<8HivUvR}!YRZnSm2d-pWA6+l4cG2B7JP?Mi(Q>a$ zZcGB%y3+*hq_gvxEiQ z%h*vRkqO?Pdac@9wq-DPJseMobDB2`UkNRf)K|kK2+NRzfx3Xb_(5;V0~(OW5aHoc0q_R=Hm9V z;Y!*)?j2^=Hh0#FbegrL)jsu-V>VI#=!F-D+d7h+c1l7%?n@JV zjdO=bdbL|Mz@e{_HQmwQ+4QeIdE9V>=t9j90Gp6FxY(slCfPuY<;{&Q zttz~fe2AG}K~^9%1wWfyioUazhp&DlRT0Lt%3d)orGmA6g5dwSS;S7B?YTQc@Cjq- zLFmC^jO;F(U_576!8jF?C~pVwwRrK0T>9}@(ssDL>f4s#D`z6(qe+P`{jMhrQp5Zc z6Mp-QR6uGX_>yNf=`0)TlU<1& zR?8eS$1buTK82%!++mJUUFhx04b1ee-t$0Y=oJUBZQ^Dxgq~u9uWja=d6LL>M%sr0 zlhT=FtfySgtAu>l0?6JUCov~T9zYWwX~?|C1kgDt7gT2dTpr-3y^((bx9WlDpyMnY zGKWD_UjWp){TP0>i%4=HX??r9yoI=v2#4_$Kc($^Xk~Db3lB|0*+nR1`uSee8)!3W zG6s4{h=ceC@Y-~a(c{`Q(t4f;sZGV@H1A#{#50!(XSv5H;7Dti_8ZTe=1!lf-sO)L zzt9`Y`xH<|?AdjQWe)0Y6K@`r4S7Xl8pq`z}=(%Ia z_1Otwm|-MYqL)S_eWApA%KpytJtE@ph>&QkzB9E{##tC#ZVH3y&C zaA6okm!3l`1jBCrFe68vBC5qHfc+s_Z=d0!FR|aV{h`Saz#WzH zoALsT7i60Vw9_pl8Q5vr!feK{kcPwU1*-@0Tm25Uf98GlF8LTor{h*PGU6HD&eO!U zUhSg;g1y(_6zQ9-Hn*(d3wQ-SA#upN{|rm12_W@G}Ml@mE3S;tFgWFroTxBcS``ogug0Kfr2%1}Ce*jnh9T38?{MR!i zj)CzMldrmr-PxkgBHehFw_EER-P!nkDKuwzgDFN3x55zAE(N_S$!RkwWX}WnpMh&J zCiCnF(R^-xfB(^+_4QIr@2R4n=^`q1?vMCfzOIj>sCX`fH>2)%{Ujq=S{3EGc(gg$ z+MAgff{@phD~MbR?bN|rB93k9{k_n%Ad7!$Odo!qhW=x6$f`{*{h zL{CV(cKv-1MJSd5{4#LI7Mmru6y6zIwrV_P45+Xn%-d*FZ5k~udr_4W(1`(TrG*=| z;(XoLcL>1K))=ytTt7Z9GI=ZT%53u@YSq@xm}JrwAn*|GW(hgObtz~*ch8*OHoCOS z^fNzeUDRi1MlFKZoRkv;bF?LGOoO#@`>@=tq4r>Uxq0Txu%vWKqttd7IWcc)dpr(# zontm@15~y{A2uN(Lxe~~dN-N4*6!M6T@ZL;5A1|Cn>-V|r?uhQCve<fww{`hE%%ysV$Sk$>vx{o#)VG?G8lVvTz$hfVlOnJ8w;D zlUZ^TIh~krmUcL&-(+< z1N!<*257Bs&vB?#K;N4)9JryzD-JZhofTVLJX~A=jtB^}li6-{+1YXljZdNMZnk|P zYZ=$;M-!eKgwGQfE>w`+8#}w+g>w7gcH1xXb!J752H9_dKF%MP4jQ^M3$9^RE0__J zvXZEhB+r*Y2eY@~!>mt$`7E41dspYe%L>p_BPw!v@6{saGp&T`7E*4M%ERSv>sBLhs>Hz_WgMV9b%E~+kAoz1BcE`N{5U#9|`kxDqr_LP_ zooZi!TIcEGoW-~>GEn%>R}BYY3D~F@x~_l$F}lru6wK+CQgWTn!Ja+d??$e!aX-^v zB+w0!VxN=lV~UyruB}y!`7)DPa4=?Mh2uSMeB4RX0x7h=@bc+Dl=fL^(9RHJKG_4UV^X+xIJ zCDo_pfa#&R%OR}Rl5S5QNQYU+Wy#MBT&-sZX}j|mHS-(I^#dJFO3 z?d7@f5lj+-ZgC)8QZl*5NNC1F@%hx(iQGWR>!a}fa62P*`m9%EIwdFY3#(gQzFHPAxwbeHM4+5%Ad-hvAE@D31)DcSM79lqLzjapmdCcMpUL#_rmot0Aj#)WjFFOJ*As zo;}$f#^0TY*ykJ(pK9`1A&j>nr)qwoYJ;2%=oQ1ZL>u|=d4)9=;IA9deRRbdk^*hEIsV=bcf%stf zq4F3QCI|VQXCT@w?RvN&Bl5lrqJ(q6(rr)R0@Wef(+m*d`YpJlmUEFJ~sJT6wLeW{#hb3uZ zy_h|`$`oE-SUSCZ{>G2V>DWrYMNjfRi`z_5S!t;M8S&$Cm5m2}inZAmxs;UYe7ZmS zzzb+8D=T-b7SN=GvsQ3~@4UM>eDc%%Wvc$C_{|mp8s-g8YsYVHC~7(!tE;p7k~TFHnqcLR%*^Sd%BJW> zWY*@CZatYU9|lj8qgp)A$Dxixj)GIqcdO@!?Qoa)0oSGC$3Vu)J*Fz}4~5FE09oF| zQwrv;=iTCg@$k)z8`GIZFN~1<24rIP42Hai^($ervNXY9nfw5qwV8!Y^B{ndsV%+z zBY(4|Q|B7b!iAMkRv(H>=dMF$JvCncwdWcy1BvD>D!eIO{(o$`yoy+ zM;2>V_|HfE%s{v7*Q>irHcp+vcnl9~$iiCC;zN}k&(J0$ii)R$mkW3o@Xqs#_*JPt z50UYYR!glAqN50$tF-zEiaz=>hTO!)dAdt4FDlV71&lN#0j@Wb{VwRVy`H$FKM*{J z8qTV_tM%$oJ4H7w-iKFTCbFS0vw&P@qcPJuZRq@ay|*TAL-b4Jf7Q8pIO$a#&}-wj zuH5}*mEQI2t=j*{E6s@My-zVKgI=#FYHnB)TTc2tmet7Yx-`m)Trj**4gto71>9Ch zM0Iwzyt>>BU^ZC>xi>!-WQI<$&~0}#jJ0j9cXAxnWwWx=79L?BSryy-b!vxr8g)q3 zIXh^VXY0%N1Y)0)@ALY6(L2T#exx4w%IuI(D!%%C#Zf7L@D8`NqB&=UC{+(5=H_wru%=h+R)SkQj&BIJ%`wz>=w_qoH!{s0ePdKLa%>|c`sg-W zJAAuIamEG)O-Cm>hQ*wn2eogyYERI)Fv8`_0NPL2F~Jl#(`^*T-d^Wd?Tfl1(OhX? zA6ZOWp^DYGt~EqYdX5botfg&N_>5h5=GQULMPK@&e$#GtFt5?7@^wHt z1i4ghg>~V642#IC8f}Zr|FPi5lZ$|7Knl_~Z=FhSg-X}Ut3&Iyy%)Tw{phuc>hA>h zD5>s?gq~OT#omzMgnFy7Pe1W0*579KA+qRm)YbPl5q?YarEYrboUKlsq(qV|iBI{4 zw=BK7pl-e~9YSE@p)zGOynU2(o*CZW>6#RrX#<|SMuQ$(|BwAK2jl-pe*d5M$4viY zF#osyn1SOTV)j2e>pZ|Repk$8-&OR8|BBSSYImjQJOH9w63Mryk_glURfDwq-`~AoEfrIlk!|VE`j?ep5 zb!+oRxha3wM|0b2^dg~8+iLrVzO!@PPu{h&#;(uzv$Q5#ld7&yBonnqUGHfcCA+#Z z`Yr}KI^mS%0s5br>nGss%-?7oQ?U<|`lUwcI_j+ib>nYm0+)mC8oaR!99@2pE=FoU z&M=Ld<dlkhYDMae6h~uknaC+HM`l_z8ky`< zm-VXK)04=59O~7;9j6GjX~Vdk3ZiL!FVF*|W?M|NJX-ij9OA zMLt(vq#HVUpGrhwtrmHJEhv|=R@#NNM{1;yrfz7QZIt}R1(cXu-rUGfs=Ow)C)uwf z!g#h;;0tFpmq(Qd7+F$;YK%?%i?fy^!wZb+yHH~~TMrC*F~xmV$J(wd8aT)&`UN31 zrQA#b2;icYaP=rK`n}~tB>p!;_*?-w3pBJ4vZG|Tnl1zRl42M#jVsvUkfUkF*V-|l ze{t44Q{0*7mr;7UPgzb73kEV7F#?SDhDxeVC?T;g)wR4)o&)mOZuv${rF3ZZ3ui6< z!db^KnZ9sV8px=me{fa>HyS`jP{Wd0wYt+^oV71_G}vH7o`hRN)r_O)ZP1QbtTKEr zry{1E2ZTBnScFK-Bp`G2DN>OJm7IoMk$=xc`2xory22k-ualBa9AT?`Ud6zGFfl8M zMO0#>*Pm)w0~~r_I0BqXzIu!xKJifu3vPsltWq6aPxol|+6(y6Dr3|>XraCxt!>=T zAEOT?JUOUI5fLFn&xNiroRZ6C;2i3P<~JR?Hi>O*r2rUYGqOw=USh3{nV?QR1?2-J zVMpkQ0*rdr5oC?Hislro$M;?pz7dij7HZT15fcG0zZH-r=e`p8*VJ!*Kl-?Z14Klm zA>jSzp^;Q*V?`CYnIZa`kqUR~0Us`RyfKJGvS>hFnEU7r{-wa!)j}zxViK4_5Y5mQ z)bt%EKXP3ZX=;)DnCNk6nIwhW_1J%tAaZa63e^u(A>G)Li}x3_j-2aSoDbM?>Uw;Ne? z%LYesaj07^`aiw5*fD5sW%)^4#Rwogy&go#aDk`H%~Wmjfi?Yq5)P;1t&Ty@k*mZ3 zIUcwg*cg;XTtDqm0E}SR)3Qhq?FmU{706YG$^a^`5vp1P`COl1+CUNfBrSRJ9JUiG z5xYNSSd{$NRxzv@mD0k@^*c@~o2WtHoO+QxwjA^+_?d*12E~wxi9jgko<}TCz}Z8} z7{4j3q<3J{USZNpq{0$&MT6r1m+08o=wH#X_pMbQXo>6wLirQ%=mz?Qc0W%R zfvD_s9B@gHjA-y+FsKB?0rWtccm3eoRU*nxwlgzg^77@hWdL*SMPkH7+?q@I2e`=*mx*oV|KA)7ZCP&a_7EPj+95^8o}$?6gOIkzhs%yF<|o0~gCms24(lU_uDDTGc>CtR}xE%hf^<~@}CM7q^?$@)$N88tf_1wVhSMnL-q|zL?Q}!A31EW zI2_w5=z$dnj3|>e0wPD)0XIEPlxtMnx&inELu|m=y!$e2gLpx(Srwk#!nCi`4(2MK ziGd5pLHFP!yAS{dKSm}v$29|MdM6w$Y}l_j zQCUrgvP7gB2ro0^7;Pt`Z_9^lFoC(8bR3JTxg43OTMBZ{^ohT~&a*!-Cvfdnr+*w| zd)=S|sv+#Rn+0G^SR(yOOn%Z)KO*~t+G`AR8p!`)y}D4|5#aoRvG6QMzDk03xAP?5?OE)<{iHD zJaLdI-~OHsscLDt#6oMN3!eT63Cus`SSSXkH+e3>lZnSQZdG*4GjhG8DpvK{l;pvl zkhic}0xU%it|h95Zp5cSJ*$53eP&GZ<-Ie@VkQ<-!A6C7h7&>*L0 z-Lrm}{W~~rpNGAAmfz3LPUj@?w`SC6E=RJAGk~>DyDKJT{q=zBO%7fVK51rm=KPZzk zJ)Q(SPA}HWb(M$2W3e*2}ErYGgNDkwcwe zCY(d4Pa?5E*3^8r8c-E<`$Pjn0)|vpHsl__FFf)&+6uq)`Jp>owdTC9-J zS{>W@IHSPz;P?XQTX7z+{>a?wNzBz)<71%}Q+jpfPz02EVqMbs_$1-Q09?K?Rwi`_ z_Iu>sp6@p19WY>^Ykk%MmO=a6Aqh2OFEo{%+I>{;YCYXI*s=lDV%^vh3C@kdv0=gq z!^84qV=N=N3B)tv)v?EjaD>YFzz(DcQ)u|*Af0Fny7tir=~|7rLKNf6_Igs>OqEEl zalS*iO2R(_@lo`Z5P}fEFv^zI^kKkdrli*~7V3CGFm6B>tV`&od~mFS`${OGZ=czY zl}M02#K@IFhC0o^56sTVP3Ggwo2s+_XaEqn%Bzhr3AU)egI|Ixrhjt|YyOQMQI1pc zc-~rl;@du{WdK6L9R}TQIE|qI^O9R#01)j8#1TXuz`@`;60Vyom)YCx-z>Sc3+}g} zznIcMFJV!(_4Q|ppiUHY!-}_%hxmr>knyc0={ww*R6Hx0y#n?n>Q9qhsF9%rv}5AU zd{ECq51oWbY`O_kO+{7y4Y$(ZriyRa4FD&$JG;z(aMlZ%`7fOHz`H%dKvS)p8!=QE4NLp8?7e1X;waluHqE~YwA#bQ#_ zGx|Y#ZG|4JizTKl%dr*q`~ZUQOrM_b-dxBM9g3Zj?5jzR)A_4~tSyeQzRIAqcql z?C|>?`C2j29-u5QnduTfQCm_frWfsO2j75}k`1KPgmHcKD*?jUF$O&(QM@PZwOPtJ zW|O;NvnXTzE&^8Ya~gXP5a60&!x$0VI8~TFXc@C9PDbK8gjPs?c+Xyoc|fqlhov&m zAub3jn(r?Wq*)G`Uo2k|u1Lw0{7iT_LM+V>GmRIg6!=v7U6pu;KH4=gv+ZVZ{~G@T zS}1M#o3eowUs!@~=w-PhtzM%E6afnG3^^_ku5nRT(DE_pJ&?44o)xyLnAj=;W7;?t zstaxl)h4@+Ht6W0%)-|G@xH=-RQ4H86wS%hwakY8C+Hx_#z)AhVs#}ziDExo|G`;% zXuoh)f=Dzex%IYx;jB&QGbE<}##!-(mh%6T8#m4UD>r6{V75~EW(T(-^bgKDDpp1| z!phN61m~>Gu6>otSY4;~J>KVceIj*}m~byG>7mA7oRv`SR}Ib=&I-IGp9he0H@p}7 z!ddAfd^N+R(PZbyii-ciS=UBs|KhBEe{t3tB;LD!;jA=&aaOFqIIEZZe{j~^FPxQc z_6uh{O4s-YXB{pW_`+GsUVcqT+EZ9~C>?#_tn;Eq3xquX!dZ#OJnjh4hnPujO2%uv z?*9vC9b=GIx~5S6Z=7|T4?pDJIIGha&UyqsxyoiSYBpL^zz1QE<`#oS>70z&7{ySc z4|0;CXs1^dY6yH^JfBV74I7I$#lp1q&9x8dxp-C`gNmpZ!Meb`Z%#tlkhLtS+DsONIl97TdZKs30B4ipO#KU zN6%{%={N0UQr$`^DEHX^78^hQcWgYRm*lTUpNFocn}RM_s>$M%!oc4gT6v*dMoH6{ z8=%mXt}zGA>N}ak>RlcR4(lLwxHAl+;|qyiVM(JEx{lI|o!u83ui*#M!n0SQVu+ZA%h-NG!oAV*pOHig}|(E_9z7X8)pp$w{`i#S+V|uv-18I&MFE` zclL#|{{9DNMRZR42WK7s!dY__7^51mlK$eXZj00+nu(xtx^BGwDE0r1vnDO2(rCfS zf{;LDo;M+0_D}qLa z`OT9Zm%(AY&oCh;p*y-OC83)Qo|I<7d#_Mo&|TUgr`vO5ETGaI$6jqS_)~O&6i!f5 zAt6k43*}uOnzdY5GiaWj_6ukA16>SU$n6LA`odY&FYIdSK!WxMzHrvqFPzoNjSJFZ zqm^aB4#>6P7symiD!EJYVht%#73p?ZEKr6w@wKX<;u9!XzHCM$@&2#=D`lj%T|Yu2 z4U(;FQ;V&ZX>8oP(&PwJkW$OcZ7wh*R-{I!C}7Wvih2DEj-Z~I*lw9jRDB#Ouvd&L zN~s*dNiw3VM|r|=`t-&A1gQYiNU(6r7*q*8F?@kyg63GTjpIU?8$Nq6!FeD{%Q*Wy$+Sas5THi`X9Pux&8!<*TJ45dVv_Fztv2C#uz7%Y+=cF$&`!fvHD#Ej9MN! z`On6tm>JTTg{zd!?z*2vpu$;sZlGh-BsQQY+OD9ZBaaDd>oFmi6Sa4nlWeCp>sKWC z9ftblX=ad)gcLJuyPW_%jtNfe6kf4lT&B2(4E0cR>QI{Ung-5sQ}$#1KvV&B0JawM3^CE+NbO6=VnnStE9l9K2LK zcN|x(Y!+w=V9uA1PG?Z=2mh6pQ(8@2;{?wJ#GvSzIeF?%<`gXcfKx<-Z(Bx0+%ONm z2Ck*3dZ`zK8=^diebuf%V|YuO^_l$l{XuecyZTI z0-0#!4=Gw`0hG2AtBz}F*}H;Yxn?m&Pptzfr9x}6zqm6y4rX?QM>+yFYP;H2654H% z2vdDwD9~iFOzw^%PXB1OprzU|_dqrUjA8E!azuAsI-zrXV61Rb(!9|x^iN7(Gz2$F zDij&oCUpTbmhv&fY={iQ3W{z2uhiI@Kw6tdiWXTQ$j!euispP5WYnl)G9+}Lqeey%l9<=b-OuNM~I5GoneE;CAHD5TZO6w37Gn&j< z83k>sj~E7=;q2V))|9*vt_JrZ=v{TGK3aac4SEfEJd~y+EYqCzK@{(=HU%Kq@|x|~ zqE(M>>P6O+pXyJ?g|?747`(ljA6Je9aTyD6Y=Y6Yl6NBY{n;1-;ki#_s!b^MJ|ydaaXFj96h!HTA% zb>L;A1wv>8{@|uX-L^R`6!05)I}TG(nZPEw9!%5-U|w6^y^itCMN)WoJSf|`WoAQU zM3Yoxy_^%%B#c z;{*<%O*FeyJtOjtEM^ikii?2Jat#bQ9r`5#H#9r5TNQZ)z=doTbcz%QA;jbsHGdfR z62eU1sLH?=hify$kX{)TaJi`%gi1uVUlo)0SG3^lmuzeGqIMwZCYlPX2W=GD&PHETIiN#3V zAc7k5b0}))K^0U)2GCfBLFiD&g8Of6&?k|;K*}bsq_l}1jkS`^7R$Spc1<4SJ(K$F zyg9KpWz!XJGeuGI6r*JfY=#X+8D?Xa*Cy$)Hxk5nvj=C3!VsMOB3=MuHTy=?I=50{ z0PP5Okz%-vgG(|Di%vxA7$J6zI(Dq!r9FIl$xqg~acgR36&PZ(=ac!pGpoQOS2*eE zW&Qlb0@c*kwa(}DCft^9B=K^re2h=W`#ES^I9Re+dZ6g-ERSb?E0S0m<^Egl^4G5O z^{rRqQ}puS=lee9ruSZ-0Zt_HbcayE8pk)K>TjoftKPT!m_Bc9%Z*VOp|RhqWe@K0 zXihDULRg{Q>uf*w$ItHntjcbl5fHdE3DfiF8xP)(RrBtwHVl1&DLtwUcKLYKX2u`d z3+3JH96mq7q8?mgR!KAae7bBOUVi)&8z*LTxqntU?6>t(v->FQ{T}o+PE2M9zo9^$7h|y&?RV zIRBa!E~@zK^_puC2mOk zykk;_Y#v&u)h`P;XZ!GiMx5KXW=hU#WdH;`B~t>bN7>Ct#HL~9qd(dLD9p?k&syE5 z8vrU6xi^uLK4_0DV%b!+c=`^}K8SM3ILsdrDxVu<^$O}mvCe;K_OHyiYGwb;E>ery z0ZIG)fI2Gc(BHWbs?7fN1Z9Ri#MUnQbfYrlid3A}&?|qZMr8{IwQLen_#MKBcf(6( z^GF$TM1V6($xI7B7R>b_EfN7(3c$RX*Pxjfo%+@_nz8P}TzsYx9fN27W>H+Wukgoa zQSiLM1FUNHzL&MX>7D*)^xgz2%ib(*HRk8niB|WW+jTq0kY9;$?=2jRJ0-SXmdh*0 zT;%gr4aGl+@r+mdlY8eg&lQ%-wv{VH%P+{_VtwZdx#`n|FPgQii_bp&qGY%42W<`e z$R*Az+s6l7R(Re|nJnL~kj+W^DfR`m`Y6P9oU@T$aA9d_VmMwiMW3jV{_e25n12f6 zkRc+|wCjIpR=f{p2Kl+!y9_^BLY}{cam{1!I~~Gt7LB*tnV&uk?BKe7C$9)=8Sc&o zR^Z|;)!^a11MWsD4v>_aX&Xv2ecKR7bmx=pj=!4_V1IP%m6vUt8Pdp4+HKGPHcT7k-QD9HY#2QAaI-SX zziCP2IRjZEh|j&OG@jxRlH~JhD;rq)IK@-iG^jQ|#k@dvRXw{vI;_U_nms zIxk0vkFF&rj#f$3@#1)@r2fonlU8m!{pZvG*^@-|?uXzaw6`Ce-LF4&r4vFwsOT z4jtf75s#c6VshIVhx>pr1Q;pxv{17UK9)faN4g1e=X~zs2hlc(yk26KgM+aKuY*@@ z(EMJGn!=-)uebsfB8+=Lre^!_imp@CM!Soy+;r7Xihg0(ESCgWw>BO>6VSMkp6 zb)n#pD}6i-pZ9*(JljvEra@DX;!LEl`x&ViRGLKPkKa$o4Bh<17PzDyO}!i8Ocv9IJIuehqX3q( z@ES1j0b{*>w(pP=;eK8BCK%F9amjvEx1y^(iBYMLv2=|3dpDIdqRJd;0AV}tA}f=! z*N&wtA&g#Pc;0P|CCp-}0Zy0=h?&nW-?`9mHUr){tV*c(>S(Bv4RJB;on%UjkW3Jw zx9m3Bo^JJ+Xxa|4lKGh>nSXtVKd;+ALyT#V^!bZ4bNbQ<2gYN?dm;0o$?may`b+rwQP@2%AM2`T6kZ^`tx@9&2M@Kdg;_>cdS|6 zT{l?{Xw#<{a0G0p5UYLS3vnwFS1W%>nB{q{!aVpVzQR}4nfV2_M{(SU$rPe)cToC< zZni_5MOAA`G2Q0H^){DQP@psY-%YKj$d>I73h#Q+EsehZ_8=URsD)KoE z4XJSUEQPXTxtihKK6Sr#9BO(4v>tp~S)xm`H$VgHM<{>yB5Cxvo2&C|ysb?en?v$2 zdGonhTNx^~?A~9KdzctiM-O=g9Kz}OGX&4$lQ>=V0^xIgoWwk;U4y>)94IlqOXO5% z+Ml?)y8@5)a%U`=QxykK{%37e_@Ji%3*^eWL+|{-GrHIKft#rTjFy~l&%QW-&OJ+^}wp2VC)j8%i%2 z#Ay#031yPlr%Lni7KXxE!z)kNvM*os!zf02#pz@87`(5!&{>sg;fzq(xv^c%aCxzK zyK`(p)3MzvL&(ry z__L)o);TNAh*H4M0u@EJdg~TsbyZKl>%bIl*@>m?RQCI2Mk~_T#f|{C`uZjt9sLGU ztQ!}%kbmUS?XDyAkoN-!byIRzu4kxOZ2eUG`VO_d4kTZ4X*uO?m#zN3uW`A}wzL+PimmX<~SI>jJq z^Rd<7ZHCq@-Y;H!8-`i;cl^#huM?7|h8SOVu&6;rI~z#vQN)yX9$(b_b;v=)$1Pr< zRnIL`=uSAeee{oOr1;|C;A|UjkA-dt7k^@kI>x-sALsDTekG_FP5u0j+;8Dr^(kGR++6EUg~5nStI6eh zMq1MoFcYzNrQB}9zL)exQ`0Ny%+6Qq`$yXG-?6+kfiG`e27F4_PKz=ItgaM*6cRax&`(|Ve39+5|EfWvWu5I6|G_x*{mmwO#+)O(PV=#xp#eIjr6OkB3W>nPJeU(e;(T z!nv9X@RT#?OHR|-r(Z{71St}suuTe%o0$}~WcwY8WVCgQPWk^cI_ zko(+i;Zm7b2ZB%Mh{7y@$`e%<-oBh5Ae<$z!eYQFNrTwy0v>G1=^U7j|Gr z(l-5)b~}(;aFL;KZ%I~^%Q<`fu3fX~C`4etH zkfn2D<|k3^f_aqXqF14aJ|YW|4QFMqvIbQn0g4y&o`X zpZ)7EyEX^gdP^3th`$dPX)-tJ%mz?B>iMWJehjwSOA>}XHLibe?+Z*`lju$|Nmsve z8Y@UTc^*Fvu#{{~Ki};(Qp41|2@&5oyNrdE8i)i3lu{sK@kmU*lFBvmRNfXRPoI0# z=}cfu04+OsR#%3@BI9Lo_$&G7PspEb!`JXGWHIMw@4A1HHxjblo2D40iiqwd z6v1-+-osZpxYb|u@v1a8Q@&?;gUOY>cU!d4+~6g65+hxH2fxNGE`~HLrWoX%R5=w? zQ|7TzKTp}qo0FjB<;ggFO!J@;&+iTB_FM5K4ZbY%y|}}Mv3JUt9rVcXw(e@1vR{z# zw}00G*bX(ktfkSaI;lK%X}ijWD6FXvYV#95EM9wWh)^aLMoWR2B;xmcBq^26hLHGs zr+sk9G`*~Gue|P2!bHWGm`5TrKvz7;2Am@^7`9?JNYt)I(c5_}#y0vz%Tg@Lu-~t% zfsh*y;1BBF-z|+pj24z@G>98IJ*$a>bp^_uy#8kBY8I0pR|0z+E&b%jn|Q1`8eFv}wY<=)W_f2AsW8WidO?*Dcabqu&*b|>y{&&7 z?Z%Ome0UmJxgC7iIY`b0YTq(Fon}4r_y`5Ny7hKJn9B~a~4#Pxqoli z(8QgKH}ZuvV_gy(ba%yD$uwd?+yebposdiFjN(O?rg!wJTs#C2{K>hMYM`|d`Rc#)A8J+y9#>3pdtqdo@-`yUMiO34l7Ylnx}df9%PV2bGI)NvSwZ5 z^g+MaQnx0G$>UF%bgSA-s~M7)yE42B8EIH-?Hyv8mPqI9#w%k9UK$d zy8qg^M~|8ecbFrfO)jrwTkE%1QkEeEt+4Y&jN;|2;0^6)YlDZc14P@iSTT&VP@;E2 ztriIRLCjYMC{4ZXsV{9X3?HjzN|mRB2$rVQYw7Ro7oR8x+r^vpAMKzZj|-2ubfF zY1hQ|AiZdO>5Rm|dpvsvxjo{UJ<846IhSSWJ~S_uc*_{NhyhJn0d#!T;SZ770RS~P zSPu;Yt4!3CxCLx2khhi?`35?*=4MbFCXO^zPdy5tM7v zU6M!lo?AajD%Ja!ZP2<(B40d)`ulCV#16b_iB-V4#fCWPF>Xkce+uz^WE1+~;6GWp zK|Q5Bx0<@r(jEDiad)n`f6D8swf@#SxeE_?!gjayRNCO5k-XXOo1X)lZ3}!M_}Yzi z+@$5*g%{L%G93PSLWt42dav%9JRsb4+;nv{0pdO0#yqIDD=ga;8~1eTx=u{N!I;rN z_5o(-h&r&c-NMupSolAFBNSIc-%^jDpbKs=cW`pj2;Yr2=skuTVJ<Dw*5FvYbIi&qP(oygD0}T1c_>=PNJjST5;*o5B?#sJay?XdDPYHVl zUDowRIu>JfGxH)OV>iFv+2b4C-qO>o?ew*pU~e&V5oZ1$Rqp@uM407& z4Ceom2s1G<{VzoEJYY3$Z@~GSoAe3se(AioL{KO8Se_EWR`|>E(!|r4$-#?Az1VRI zF>MuFpGDcPdepI`kQYGr6^ZUe9E5Si2@*M&0BF3%rTn>-yWHITsBQD;dLFmU^m+Ms zzc?}R>3BYV9_?-Wbo;m=ynLASS?cKQSncTIU3-6MN}KF_^16L1BkVo6?;Rv$U1e(? zKaJ*RqO%d+SQ@bXxzavQxHhoolC~0QZ;1Fhqzoh;c^ zgAoWK4OiGyZD32-Yz#IU$!*GQ8ynZ_z0*i5jpRo&XW1HMy@#|;*@j2{oDV<*92dA) z#<3t^%*G5zzF26oMY)jjRI7&G%#7)QR178#!qwMUyVK%H-spzgpR)zaSTqiU=g1#m zj=tf@<24T0tW=LnegVai{|ywopMC+ww%tGU3vOo=IdIvTbTE)t#K=TI9MFDIa%|VW z!6+CtSqIk06n>S$@iLLsakay%WDa%XjbZKU0z(MOI4RjEmR9*!1%~v1WtTK!oY-{j z|D?hx|4xPfB=#Pude60BlkQ5&kyAz9w9O7UAmmQ3uiU69{HGO;z(o1m3jcq^-Bnv%+k&ob+}+*X-CYLmZo%DM zgS$&`cY<4Rg1bX-hv05O0^}RXTx->=s#W#veYF1o7bm^LXybkE#{VB%Vf#N@VHO>Z zzqY~);U= z@VTh%oHwvZ(=saPqq@KDJO(K(GCUmxH6{fPHMvEF$S{YLyyh0ZF$+yoVw;IZ;K#za zkvrUtCUb@nT-}8m)pIULI9(Juh7VL*GJ-rD!K>{n3S_oDu&v~yO-3CXPK@3uZct=A zy>V8uW_6$3#o=#@1VtO9o{uHf<5hcKC4?gdE9Zkh%*vhAUaL^imgwNJwERsdF0ENb z0}93Xe-ny}{s_fle+k7NliNZ6ze2I~=Uj{!d5MC_7(k4?E~&sn89YT=JfuRe`y5)f z#-rqv-iB2mrTTcZ96!?8Q~?_*&~Mc`O?cwm5S`5c1{{r6b-%lF*+ zC7XSTc$7nT{lJJLb`siaO$YwqVm_E_gu5VpG!%!)ouYFw)E6vpwLB@M#!|#)sV<_2 zuv2?8i;RNA6Sv`-;~3J>NRMEaol^a%CA=FS#l>=wf=M!3s4w?3^!-Buf9UwXpQR*1~Ilt%diZ;izZK)=2#9ZTTdFG6RRx)@TLA z3n0ZK1cXlc6Oo80CF?I3{b2}-t^x9q-@F^e7$(i8<=*f_AgARV#36!XGY$I0Adf4q za7AKTo`D73DLcO#XMNUy;GEbjAF;X8^zO%br7Ej zNR~bM5OCq%2Yc5O>jII#(`y0mYDJ6Ij4mLo?<$2U?2iPku?d*>Z3wXhRTPj%EN31_ z4U>=@BZHp$ydWE?4KAclnV9p%l5(}2>Gxt-ktg-{Vi^4QVt6Jj8f#7TZH9n*r=>Vu zUiA4p{EHFNUjGd^W5yf@Us)DnJMf0K%x`A{J#i#4l96T+d zwV6{HTMhe(v>l^42zO3yEGQOLw3Tc;EP^ocW_+CB2`KY2X1E;ZA)grCrvQ9b%VGW3 zABIuC;f%6axGYs*&(z~w%cf|zxrap53^PTI57wgOfDlsDSuMJgirMr211ZM+JsHOR zCsM5aPo($^@4q6&82<(-Cj56uaoK-GioaA+Nt@-KsvB{UUk>p4P=Fw6Gi=a9OkAs5 zuR-ljt4&6_flfw~l6o)++n7G-;}WGjo+dNCaR{jP0VbkMjM_X|gzw-Y57jSg85eQh z6YnD?*MqR-4`Xfg;NKVs#lwsWgN876AdCjBrbDZkq@dVI5~U5JhgmG6c3v&2Z=~b` zM?m!@Ob+$lK!u1a2{1mP`%2U^Qo*NVn#icymD#5dxt9QvxNOylVXGx8(R8#Sl@QOQ zdBnA$>=ifKG_4BgK~mFp`P~RxAv;k-c*D(@JQwLIB4ST%!E^$&ZXnH`ot z7+^AeCgjD| zgqb#(*v-F3!(0mgX*4YRdo(<%@}<&Ty8u!O+M4cI><|TWt^YH8sp6hq8$T-C9)~sj zHPPoakXUwfC2HDYiAoUDOr{NW3yq{30ER!Wf?P(Ec)ubqghPcH$297@Xe^8^hs=!$ zbS2#$-O#Xr5r?%MnOIZodf}Sr)#r|2UE?I|=7}`ae50^d8zi(Ap8-+MG;k|;zCgNO z$_Fe6a*uijOCz?dOjNzXJ_|h*0p;FU|4cqU2`rU$F24*{vw8%FQ}XaIwT*cnOcH?6eaS6pQ#K>^0TZM zM@dasFvUha-*}T@G2UB1#DH}IYkcUG*^4=GkKwazRE*4DO7YbHiBhchKP$!UEdQfY zESJ8@6U@d&Dd{uXx)?90CdfQ~gg||S&Pe;2*ik@ZV0}6{c1;Km--l-=1EvGS?Xm2* zN}Af4PqY}N`TjmkxhQSn$IzeEurF{myrMGy*J`*f_kH$sv%+pZrW$)3nlah5pyW+V zm^5Z|IGBw{W9S>KxDQ;coi#PQ*82YnBt*-F~u^!nPQ6HOz|fmQ(Pse2Zmu5 zb}b6O`{68mND_dF!k!+UmCBLFS-tx7iBD2;Zc;gWhhj&$KsJYZ6ZWz+_z$dfU z9M>nbY|f5Oq|~{TUR+tt2ymE;jS7(ffHN<-6lb567B$Ung`6jo`p0ebRHDfa>75A0A}Kj_SF zkwZLO4(6?_+RVNSoXr&A?Ci_1S`^EW_=E6#vf5VL%IH>+51M2rFPJ9NgQK=dPSH>| ztH{E-ERDB#l70R&9R7+T82e{9Jf};R!S>aB6!C80PV}v11+6A)5wb!(!lN4SB;gw> zQ|N3G1S*6PPkn%PHSnqClpe1&nqOrhTiz0>nu&?5;eBV<`O75~42&^*(PM>39%_WPq46J`6tzVIj;tZtX$?J)#&$uCA_xDaKmk`;PZIpqtI- z!0WH&@GRZ`y;BV7t;YnKNLGfecmNJC*%UK~sSRZ(bhh~()k{Bz-JE+VH!I7uX3}!3 z6^R&Lg&cSgNk35WDaN5tCbxJ9IZ2r1t2xXh{S3Y3L#5j}iw{Z=seA$gZt!9ofA23y z%ZW8;SKtj4fEK)|kf+F|015A_Fv|R+{z0t7}ksy7dTh(<-C}}O^*rhjUzYa>^ zcGwzLnnkKtjqQ~G<@a`2PVmvj`2Ho!$7CU@&>+2pr1exDPiTR|hAiuh6Ar(I(E?c` ztk|COe}jrY{RdQR#-3a^mtJVp z%1%kp%x4JrQw}ggQUW7IC2deuA+up@$JIx^f$m{bh9-r7dw@T7q7;2V(M^7K)+Mh0GPn>53n@>eJm$b_w#)KENm_5>;;->=j0O2$OH*&WoS3ci5YnIVaII5A18_Xt|IhJo8$WP7-2P`gT>95|7^$4Q{?B-r zksOroO@6Otxa1ldMmm=dUwj-hX7@9R#|WbEXrn~0=;OCu*J4(zV-05983^@dfdO|I z5*re`69Vw14gRVDfd=@nQu2`O4eEXlb(kN7J1U>VLM~-QcCHGAvJ83ZQpUodX7FIC zmPx1+dt>1tF2E}KS`@{7Enw8FQEWYGVD(_RUc0NU zj|6^VDY{k2E+Pwdj)a#VCPfV8UQwXXN9V3yWVI|JwQ@u3E+dMJ3>|utcUh zYmOdEk}h2g2oN_(3G;q z%53AG_3)a5aQ^a}v=u_aIj5p1-{$>)xjGs!RX>q_@qjU;Y(CURV?=>C4LnIA2m9)_jY`cZAX8{(R1}FQ6~-Ji z@5=>{Ai!yY!eCHv{I62+h1>s;R9w>d-=*S)e@exw|D#m=nG#o5O(M|p8BD9E<2}T^iKNJH|3%ycr5vw=B} z!}c9lhb2})?MH~Xc#=JZeU8tuvBm(6w2lFti9#8r)fe5wLI-wF0u_N+Fm0na#8!cB zMq#gm4l499Wur;kLUMysfB+_w?w&x152}>28cbZVY%&27Ucq4a2aG2oX;F?3ldxzLGID6xn$6rvGDaGj8bR1$8=UdMym8%dhehiDch*TKi`et z!D*(u1kv0>!Ye9WxK;8?O>PvEh~?{wd6asE@~nlo`KLVN8pyilbq73qYa55?^r83UN{5TmfYP5U=X39l8MGT8E zQeNc^e;SO(v>!PcCp%~Ty6LhG{1Xr?xrBB;JPkg7HH^|M-4|!TI*3*PRRbZh?BP+V z-A@o=dzjHO94>t;1}Z&4P^|I_;R?VYw!8(NChHMhJ=1e=^^0@g3eR9{JFYbriHeN*AWNYAIKq5L(So!uCRX_iBsEyP)V+>-MkWhKTi17+)n=yg3J_OoA9pVXQ~Eg z^MHJ4#Z-FFPrp0A!`KDOh{r2r$D?>&W1$fpyLy_vPF&!8Smw%*_}BZ}s-I%s$k*8Z zpnRsU9UQxRKPN29+MnKIzm(5LstRH(X&j(Fj&TSkwd;TGybvH*B;@t0Vj9$;DH-cH4 zds{(3uXs0of+3*Lx}$AZd~F0&q4xTSoX9(-=f~68rC@$W8Ctol_5^{lw{OJA%Goxv z!Phq4u2o0r+c!JzqQ)s}ZUVm_QKR$Uj-x!&r$3xuZqEqLQ!#qg>zH|C1HD-y z1fz#`&3+IZe27qili(T09qR6_j^o_dqW?4*G&@k5E%Sp zPwB}xrpJOm7O$rUHz(T(mCv?Gsjlt{IGHJX3KV_!77axsr{KTb3F8QGEr|L&hL?CI zbW(OVfaq{B3e<|5lvV;dP^#H=wBy$60)m>ycJ(&)yRK@FuE`}gb$;)N)po$pJGr|H z{lj~R9JlqiPn8AD3vy;?n=j&}gZUqSc6$JK#IDW>Rhyh^6=T1I+g;n%XXt$A8)%9{>^2_4zw^h9R+bYf|4(I&BDmla73PO72FX)R8(a*dSswRDb^TP?D%GFJ@ z$25{IJ5^qxI$_V<8d}f-0AJ56WxF%izH^6}&D|?>iAvAU*E43WsXIqc&pPhC zhWxkPkqOZ)h#hn^#YAMO%P`wK^MZxl4+J2=k0GKn;q0yhh$`|OM6oP+!#W8qYsKbC z7T@+U(P9Azq|%A6lZIXXUEAH|ggd7sK}8X$I2p2uHz5+}qOaFvl(hiZX#!sEGBYW$ z+AHMNolSka&~$|-ftgfioBsZzikj{5(5$e@5>|Ttk;)lZ($Qp!LtpSESnRi4 zJR(XwK|y?B&T5UCqG)~xrHU7fSHDN3jgPLl8ueNfS3lUWQ0d2h1r|KU*qqrG4GB?f z=S|CN97!qPUOcjGO~|b?*t%9A4nm&)P|s!!8F+iB0eQ-eHnL9CCZupVy6_>ls}elj z%2P>rOusbwbxCsKh?;N@l0Vo(j5Q*sQLL52rhkHi@VGppSB8dT8Gu+782UBs7x46( zS<|~yLZTKe>8UXR6*E9jVnl~S$Rp}vv6$%banyon%M*C4R&L^$C@LVtP#OFgj15Ud zhn?uwxM6~h>~0E%x=9?dIBXXyuu!L;`lt z7{A7j@%WC{qfZE7=NqK@65WB^*6DlfR_jY-bO+Uq{fiv=EzZ=|^62=DB|*ORHXmA_ zu(p=yidd-KF2P2Q>e3T=_Vz$~s9Iun<+fKuG^=xy{#zK&xXG2f)gZtBe7$dXNaTlK zd^1GT;yrx&r&K@sSO&blpeIF2ErJVl1-Bu`3@nAL42Er^-lbWpCYUswEyY{(Q&=T> zA=ajP8;-qx_;46Qj1u_z`laIAQBgmFl>e}LM3Br zeji83k?rut9qXPFY^XJA!FRw@;iBZqygp=%UYOKd&N_o#XDC*Ow1{8XoPlSWPX;dN zy8a&es*v8yknVZLiXvEWHFN6(o~nBgXvE=b4o%0#qy|RAsXrXk4ulGKjO}g(izl=d z8IM~M^X3R@LbP@>onM_TU;oQHl%6;f#0X&!?q-FvjfHdP8A>F{=+W^l4y3{HccOA2llHX0*u6B+jxGgFVMa7>Qynd)h`Y+ z32haej+DH^4L1;Qu5?T+E{h>RY16HLnkVg=AuMy~>IW(B>`XJGEzrkcs-+mvEvmY# zVs8b0u0-RTH?rocae>lE2wz@s(o)Q!`#|7-LMB2dewF9x8B?q&n7e3j7NQc+VZ?L% z;>3anyOT^&<=An&Q;AHgC^oZ*TUGS}$TM;??Qe-O(0n7Bmi}U~ao{pma{8EaDfX6s z7Lg69tl4p1F&V}vy@LL2g;wK6du98jozryyZ);~2&s<{?coO%sgh^{BZm~wr1BS;u zX?KFSNBjIEtaED#mteE@vIpS)Od=1`bayC~%tZ zjt!4Ga{2Teu1X6|42vX754CmJBbCFl2L~-mMv8(=s=#Y&+s{nltA~r<>rY5NVt7bb zIDZWI$qn02Z$4V7bB8H-NQ+*z*0{JpP|TE|M#H-JCX7Pf>#O_vQV-l#7y2#EKeM%Z z2%K)Zk5yCB8-Me1Z|V`F`H{a7&N;ny!oGbG;r`V@$je4A zcy)|ycb-72WTkR{1 z((bo@CkrlvuC5>zcXzE!o}=f{*mIjA5H2fk`JB`tM}Q=TI&o)pZNkI$rnvP63CfjK zQ!>DP#=j+5c4IdANn;GlSsw>uR%`1wTdO{O3o&8yp-}8ZJ>>_!))0a*)9@PQ7 zPh&hsJu!4!R`0P8(br-xW31U8soj&0gd25z=4RNncptYWm?3LP@> zANFKW{Raf+$`^N;Qz=W?=1=zD3&|TMsM4h?d~*gb4#?4dASZMPXpB$NC6yXieu!kA zL)bU|dKRa}zElc&id06BfroTGqsG(7Bfohf@fFLBW0KAGl!%f~kecn-MUlK4FSy6c zeG>dcDV{b8I z>*gvK*RL=}%Gd%@zbaBHXUT?e>UEP%=Yged*H;i=mI{dx+*d8fUwB;hD0SuZ==eqn z-ncDi`iaN-lHd`=&7^J)vNO zyb`8{oaRd?(;3ZYos)`*ZR6c|nRLy=gSsgbAH1`Mj-!*fITqh9x}(1?TV_bp!#^Lb zy!01Rvshta=l57qCsTBdpi;XyTMQyZlZ$$JF(mBB#`uIp@0~rygnnapUZRI4PrsjJ z@aP1~xG(wMu$|B3Z*bm7^n4QY05Eue%|hMGEzvWWda`j25dT<8do4&L9&WU4kw^e# zx4OmPVQ8pAXE{If)ZN9n|5?V9!4U)hc%k8ZNZU%E@v2D^5i1o$Prz6%H7qz?Ld6t0q&-y|9rQiElep!#n6W?M>jz1MyXgK;|Tm4IK{GCUCBkKeww`?&OG*ULI=e+V%M9h zzRR>TA&Enx&zn`;=|C~5fhzknlk&sSLf&{QcaaiTOrfN%%82~i&%k~g?5tDoQu?|x zq8nso&Z*vR2OeMdOs(@084VWK0}UT?@%an!kASb|v}%}J`rfpQPO;_Z8DkG_w5PU2 z5#(0Eem&a+@NFH?JDY;1I9I;idN~HQ!Ty7llg^JU$+#ij9RStibk$xqazriy!)nxUS5Ty&557_TS@pJYZR0RD>2=>dG!u@y} zt;V_tPc;hzk=I`Rt0CJlwC9Xo5^|}=?tnFUm)*p$v(^!Kt*u6x7S7Dcu4Lj?KAMnK z(2){eY9Ao()w)JH^j{oxf}@b-mVozR=Y;r6zoXR-S2{7IU+xZAhc|{p(yXFTMTS+=(6ojRKA}+1nd~> zC1`QKv3++wyBi3CPA8h!&B;=yZW=>2!svlBqQ)s@H+Im3Cs$y{Mit1!8>zFcS!%ce zZ99Yj0T%{s#2`@fdko7i?Y3TdlR6x|%7K71nk7^@Kv=&FhL5_RU(v05J;vsJ)!FY7u=&70Sx< zjMGD2!Z*0|i6l&CSk+#&Z{Ac`PP?*(PMh!ChDa_|?@rsNOE{)-Jz&j>Y4-Ez{N_wvONy$5#RDn7p7VT zy7s4r{8D{=Jq?!|W38rkFXLqfKcvffUmH$Vb}i z=cC1I-j8K>;E@+Qcd;vcesq@0y_S@a%!9QNGwdcayLYQ%PtQFeg{k-64;1_e29!yx zu&FuEuoN`mc}drOID8-X?!~3)aXK^&N7OS|!*BIEjZwwrQp^zu@0$V(OBdO~<2$lO zl+Bj~!ilbCOpfbiAjLm6p^29#oFf^HaVrfd`=wYQi9auG_}6i5soq35Xh8S8`&nP0 zn)cAz3IvcWEj7DO?+d%*E#1D294%B7SKazSb|~hf2%VhJ9{T_oTwgm0Np3I=2Y}yT z3WDE7GU70zdukH@a{cw>5W;R%t#K*mW4P~0>H0ws(hrJ7{zh-K8cjtec^N(F>7Z#* zj9AS%nR4GqR{MhstpXj2`?)1-QL_eQQMkdB_@~AyL&p|N3_^UjiK!oU6~_1h^M@Fv z2*R>LEVAucO_{otc29sHYCFS*y)PH5(XPB2)JFH2II#6cLoqJB64=?osre7uh>2BY zgK1s4IrSl){6gc+Zvz}Yh_@9stKQ@#R7wAM#l6zgjlJ^7BiHBN+lG8f z?Ilf2Z6Xg&_=zyRCfZ`j9KqnUJsCZ7S9ZJ2y z(k#7v14Tm`JR_Kt&$es*w|jh&cuSxh{5}qgZpXN8b{~dzN=2gZFtzx$Jx@s9rBBRn ziyFFBA5l+#z;O067~M(rdwNp$l(=EWo-a+>vYD@;Sg*njcjOFz=qE%+9ZocL^5f72Po<@ziVe{7Ypp22G2RN66}mlw#>=W#Wl zAHgGy^?a&fr}bPGKzVj`qf#WSso};X??ragvOB^#6IHjLz~+Pa{tXOnzJtiW3|A0T zc!RjLbIXZ3?CYiV@&G)kbtJ}Hc&7_XDl?r4ZI>`hVW;{us|}3;-l86(Gvn6KL=CDvxrahoed66{-?9k>TF8opSK+!mLfHH=Vw4RhrB^;%dZ3HtGU5V_$b-01aZ zV+X#&ivkI;2y=g~NAwoo292v$@6kEJIFuE zCFjZR$$98f(gThZ`7LR9^WiysXY*#!*7FU~e=(3bbm0-Ty9%xeYqRBgGb>P|maX3%DB zs^I34IvT z{$cN@boJ9)A^$oyh|T;!#~yTV$ph<`TmfPhcx-&XY!A+f5%rvEm`@2@O#LCiXJG*N zEO^aoVtU~Kp9SPF19aQ45hySYc6PnF5^jin+Z5tim#+-ob_;P|dJE=lGDVAb6}5O0 z?BfS=(ZAMWavt}9j-|Z4{QGKv^KV1_|L4^J*WZ4b|D_sW<>urR5<-OgJ1l;cvN4?W z8x{{ZPGIjeL4QEkz(yL8EulD@Zn28mGRXH3XU)P*LYJF%vXox$=y|{GW+(`9z^Ghp zijMn2J_wpvF+M)-^US6qBK-FH{x&s(oqoj)EC&{5wtITo{62pC^1OR8(dqf?{r-UY zsy6>?o0q$*@6G$+<&V~$O~0Gh)x(qJ?TD$W2qoc@mXA)^TKWDa`WxY04Y}JL&E8ei zz;M7n`{hqKKmZH}$g@dpRoAlW+c9*`T2(?**70~9zjvjRH^l+s`J{KV2t?bO^sMQ^ z?2oxu9r@cc_&I3GUlPC5*sBFwZ99Inl=$p#`&O3zv#7$VpKA;?>kB@;0W|V%MoVt$ zL{Vj2c1CM=6#iLf%>{S&_F*LX$+yOX=D2R)-IunTR5~YZ+bc#aUEdrP&~}Yj5l|K9 zHJ8J@Le6^*bAQ0cHch zixcB=5NB|^qyc77B@L&uby+7R!@eA-1Tt{OQzHS|5iX@RR9 zFKkOn(me4XkQhhqO%lJ-R6JdYO?oe(MAwkwy;9dkBHI{<4sAH)pAX@JlsZG`LPIhJ z{`W=_yfzxw&r-pu7Zd)|p7~7|u6W^tE|yXYU2NaL_f0Neq)}1oOl}p^7A}dW2jG36 zugEs2Rv}dFX5TGt49}wXCnTg};@|qeVIdu{@DsGWYUI$}x=?pOb)Yg(y@f%9sPRsO z7azDqofvP>aAE0KHt5=M;4p*^b=R|)j2I8FmeOYO<7ytSlmoi~6!IhU!`+$;?{>&4 z3A{vD)_MF_{k1D~)06rUC-OIOCaEvAN#t`HPGG~>BjyvJXr8|aKaiVp9;Bb=?yr|-km;M&oiaX~6JlcZI9 z;XbjP^@+kvOb;2O=&UQW5ftWy%^d`vS!b7!Cq{v9M3zgmjJIF51$TKVDW9wG`()CM z==44f0?UC@U^#FtXI;CZ0!3S?bHeh21FDQWJ|f$^dX?6UO1DKk=ggX)wMJuCA_nE~ ziy<%_5LBkUR=tD{D~5tuLD=u>q9EDW=p9;>ByVPfm$xSpT*SilN{|qIf!|yo9c2+w zSvZOGy}=ewM7;_%>6PO}tK&p_$mviD62X;JML5OHZ@>~$B2JTYqMGA6r7_zJ)M9md zr)V^;#dRyiXA-9u)q3kbGBYM(0W@$2r{Bkq0^5Q65EEcKK;ZktaSmD_?S{pmRL#1J zSvv}B2js@+hENF=FURylBENLw5KL<9jjG^ZO%rLN4Q4PZPl3fl5rro;#%lRCo70{3=M!rVTY3@|#*XU%l{0;mbSA}qW8)(tkD3bc ztZ-*1sfN@Qn{!e_f`d}l4DP!UG;U~;_DXk=sLr_WOjTsPlitmgQ5EQ6EI@1=JcL!h zmdZIkno>r)`WVbcVjk{%F}2PJO0{}_%E-bAfu|J zZ-!NO!hEgol_LCZEF-ji(3nT*>rc&GsVtYgkT7FbMzGcPqolxO@b52u5v&ox!mQV9 zhg!YRcl1)Q{V=dY^yWptbl?c1KdcY5#wTFj);SQE4s6IHTQUqJ4aG*MNP1)~?4)Lx zQ2O!6D)ZV-R5mJs!-p30BJre7?D6Cg?r17`&Pc`(ZHfMxIp^MKH2owmdht6QKtStv z4yMnr!$Vw{D$~?F3kewAYfN?>EC{qk**qBxx?`XihOG~tw3tUc+A#8V1{hZ=;+=5D z406DdH^sZ=I@d7og1m=m*mt|E1wQxbg6UwW4q_H4o*%z(RUySEUvoBPp7A3Sut{KN zrWg~%gF>aO+D2DGA;}{L7iTG^2SJCq^)rOb3r#kb)~$|$TtLyq9-9-0#z}>{@Y%>r(|-RD;Y%zJNglDqKNVc|+k& zmOVCSG1#$4)xm!zUo1h(%(EQL7G33+YN9Q~KHW3x@Nq|{XjJ@2$C90Ny-Prj76?U! zb%q&M>f6Dw3=9Wcv-!ZlFPa=F^85hMN%2k5I3AePvw`WdQ&%jfy% z6yfCN4=G}8!=S!`DWAw}@1C_WtU!VtNRR0gRcbgPpDZ?6nY-@+vGM!g*f^1Ogav67 zhPJC+rkG>nA8fo+^*1(7OTf9HghIt;LKy~Eg@2NnaKz*cIZqQ zN&GZjb?2+^VX~T&+0c7*JE~$%ICf6G1?&cfx3E)xcLV6Zy8&bMwxqe{c)*uQc%L%n z96&Txov6wQLs83-#g-UJD-bs-Kh+G^Ug6#He5%Bt30L|>NiO>>Q}5?MJh-E z1vAhscCG9{XN$cP3UiDyIF__sGI?`N%rWkwl?iLz{v1N~^KWAH;nT6V1P#PxsLg;B z4CXTt14*&cYF1<%g>eP5c?NHOeg0!kcP%S=s@q_X)#MvkO|Pm6QL8HMbMz36o>EY(l*)8k%-qH&L|Oj8P-aLxxTpf%0}w8r7U zHz@1|Y%+ga ze5gkPUli2^_;uNR;kwNd)!jbjmi6@?YkYTBCs&nMxd#+ZmYoS3eAmLQvIIeq?vzi? zoly;kfnH%LaZr-8>k5~RUrHHJRwAA7eDTG2Mtvscj(4E%c`)@8N4#fV?e0Z=a%}BK z7<>Sak|Km5NQbZ5tA+)&Gmoea427U6X->sa{YM?C685%nO~edBeA#gaE9jcG+m(~3 z4|M>}6e2T0Wqu$wPDp8w{T|gGiSY*;2l^u=r6Lv+l6&5tWO`;}LvvX6q8Km}e3-_# zbU6udU$ERiGMLGX#?ge%bM~_wO$HWDZaVLkx!{5)e>u&tN}~B802&B&(r4ai9wV@1 z$={oV=E8I+Qi9u53yD;*>EGUb6yJ8AAG zSS~o0yg^j>b&1)qg)w-y0{cK90K*x@)mcHOg4*)FdyZhjbB28tEw5o7HLzcJ}(!E zltgvrY=*l~%I)>FSu5=mxx+47Mc6($N;4*hBdD|JSRKO>lIAtdXYJg|l7UuvZ#0BO zQ1ob)>IK`0G?1`%u#dB1ZObnfut_S^k(qK*GqU5Bh_+lprF|+Y9PIa05U>y!ZD=E7 zPQkXHN%+~R+CES@>8K{mx@RdswsFMYkD+W}WF$S0#=r#YkyD!=UM`py$cwg5uY*}9 z2?l4!;6gObLbGx4qFLbaEycy8IxX6BI>IVP4AFAzBP2wl^CY*-($+>-jn+GV@}0JI z@=-uMJ$B&P}Hv^W(td6Jm48d$6=ZxW4&@Q#?n3LJfixcHukC4RFLz zV>!x1u+mNvr2ZWZxK~;!8|b#BLFJ;-A3)qhiGtV%2~Jj5FG2SlGos2PZl}1IKJUI4 zIO{E;X>nG;JADSd(2l!~IzyF%>yLtj8dQeJ`vku|qS|s=hjNMOSyaZI*+-ylASPk* z)uMaIM39&@HMAosO3Biaykg^}gW4<6|ePacEkf&X#*BWdH^!a8(B6|&% z0-gn!H_(;BB6D;fH{S)DXPDIh1eNM0wjPm#*bLtHpS<=adN~vpO6LEL20BL3gS1w# zyK;Ad(E!p*qupFB4L(A?5J(z%Jc14_aYq;vVh|QJnNs3*GT7bd-KdTr9U}idky*dU z0y(XC{dgz%o}p8Vz)vFwN-c9#>Zuy1UIs`r=3N@31~%GYjMifuwuV;}va+vgnvxa` zm|%bb7ZVj~46ywE`X^R0ECwsuix_@-FECW~uG%I6FwZj?%Rn@KIVwoF??A52;O8U= z?rG3m$FD%oM+s+?oV>u$0~jw^fayp^4N2+6$iMSFEB1S^*gj zZN{6~=LtjDM4MUdm&pErYVb@08QqPT4(yW%vKH8sGuJ!m6k# zuQTa zRj{@Dm%tjzJh=?z-tVBP=XnNL@TR{)M|B;v#9qLd>;0^_T!|m(u&Tq)VksF6q0})M z>_ZkWp45{bV8oSGG0clFc_f693TOFBG;lt0;}A|T)bA$G>Jl`7ZnKiZrYW`2RErot z??jg-NEegid2Hl@a}$f<+42N-s!D<$Yb&6a4`f#Zvw`A0!Ay`4|FT<6UfMlSn9mDF zrWNfbtYnyFo6ZBsjSQJP1->>Y?WD2L_owOK*+CaMl{(^KBV|JwLri zAf{(-zq5f+!g`;H96lk;KyRHF6D&-(m?&;P0EDO~yq=fG>6^w;ee}(?Y(X1^IA z#EjUmpkpcBue^(z_b@dZ(RdK5j!Dh4SyE{do>yv{0U9`m{PVf%QYx5*tWhg3*v+pZ zIMly_X@_odo;nT*s@?gx2@3p@8$p*TtfVhVQ_bg3Hc-tC(ugCK-(qYXxOP0OIa%cP z`HT1<-5lpgvXv;Q6|{W9IqG%k&v+iI1{pYWaP;!uZzD9|)ekH<&LozIG}>9tagrax zlfY~T+b}PyL{QtfJ+3j$+{OGr{b?o&OvzAmhx>^Z zl?{}!Il)7=VFbk#4xgo*HohN=Xlen~@nl`{3e^_|$kx*ZtDHdgQx85f`N$7J2_+fA z59;E!(Ij4gWePf`cC{`9S>19)VI72yViu2TVq;dXw7X&<3Q|{T7c{7D$pI*2D;-ir{;XJ8DO08d@0RN%E4&-0F^}d0!$AKuUifY z2izhdSy0Noj1vw7U^c)2eui-bC56<1i)|F78^82rR(S%z<}pk_rT zma8s((#npEhO-7IM6+Pu^m3L@s`o*MCzr!hC4!0B=5A}47z>zO`b1(#k`K^!7Nb}t zJAQWqE*+d&`+jsE&)SNy7qkwW$g4>mNN%kZ@y$St1ae(^zoib+(^QjG8(o}Ua_9qC zSOJcNR)k(W7OW~q%515F<-=`I4j{QraP%P($--Q*M}0}t!C{FJYdqf?)2w#Bpk=b6 z`u&L6jr`R&-wtzUjT)+xZia`+=J_x))us0?U9ci7KBZ@c?Y5gNW#*tBt@!LYV?V;m z0+-f~)rcPfGunnPK1&A4?JUw=% zP(pBIlNT#x-#XxCKBz@xjv6ff<=MywoPrK)5uISTW1p`bvnjG`OS3YrJ#0 z{6zQtalS4*o>@1Y-_``=c%DbnkB0L;lBaL39`)35ID*MZH!=Ot@P0|%box2#=P}q_ zzMssS7h6qmvhtN*$#VJX@~T!SC)>?~R_bad|K8H1K3cQU$Rx*InLAOXJa6p7Mqc*& z`RU1XikF9{!`|u}6yb;6ne2$dUqA6zZp(o_tw)HhXIDa_r*OXIGknp>Ij7@1*CmNd zl(6tlTI|3}Jn>!Ql%*6aP5xFz6cIAraS*WbqvDd2H9OzrVC<@U(dru#~*FHX; zq@}b<_#F1w?^E3F?0SW8aJ=`h3ZcH!tRc@4mw=$PFzBX2Z89^x zaWChe8s91h0Yh!=FI(r&B73Z?&Q&uwdsNni20baOs@Zu0D&4=dU*7O+n|eC~TS`TwhP2DG@MQo0@(;LK`x*m{{WxYVBBAio&ghImQD#fooAk+0Swns+VHT!7@(v zcf51~36_1`_>2PTLp~HR!kdD;W~Sdmw|ME_!`kJ;T9%XBSe~7i;*6<>duEI}(G^E~ z!7a_8yGHS2SKi#*Ga3ruISiTE7(m|;1?P*Kn<&O_N7r*NPh3)c`|Ii|Kcxd5}2BIkY7sF5*`_c}H8OpdDNx#Yib_!a8ZCU)a+ep16n_B; z6R#UczhUFJIu^&`IUM@Sv?-Lz%bmxF+?LFl4)-F{E-C&dAjctL;=114L4Hb)^0(yL zeS^!(PxBmZy34KWH0$eY?`ct_?=7C!$4C2b`5ZjYpl_$(ymJMwEY3FJN{F1LAe!;~ zdDE!ZXEzU=39U{BFN6ti7N^S-?O!Q^h`%K-^7^0GQ`xSq_!!I2d|Syr2&dcoH`B** z(v!hcb~3{T-kG=Po6_Ia z^|26tTz%Y$mQcG6rhgM9e{bnBd_kV;@Fw=Vyhya}PhH1kc9&{*mGrIOw22?9ZN&PhHthL;dRl1HPL(W2uulCR zTUU%nZJWA?C=pgHNHHYw3-M-|!n(zyW`GV(I!3t=y8Yr?mdn{+XJJgu%?^v)PF@|R z>z@pYZ&X)f$gMqqfeISGY(SXEk**8Z-P`C!GJN~!h%x!5vP&v2jn?TO{0`{uLVoRL zN)q}|8XA74)w1%m5}IF4tr!@)c!&b2T%&ECAF4OHM7y;_$o|?-Ghwm+lWXo}BB z7K9B&4YntHqP@JxAah>vN&JTm3H~P9`0IubN|oCU?Mgq@34G^=`7E_ zPh#$!@XVQP3;-9>6THm!Q3X+!wvJ$j7L?L}LLBozI!j%o>!7~UK)Pl%!Ior!pX=M6 z7Y1t=i`@MwXu6(Y?VK#ut-J0hf+0-Dxr6K5z5UH5dPO%!vlhgm#|Y7v^xx;YFiz&V z`xgGaeZ&>V8nzLZiv$~@5|v6Rxd0F3%VaF`{;9ktXuj(zUk;%toU@H=PUOl+|0&S; z837`OPR_y1^2ykNzYV)%7A#+BVdsO1fpO;^Tufw@B84FdZXim~zrLkaRIk{?yG8Xh zzmzT@rIXPn$L{RmWBB#V-`jGtD(&J#%R(;eYb&=+Bw)Gbt3`OfEH)6=x!^Yb9`I&v zqHe+At=N5|?T`S01A8E@yr62U$_h?hf+^tn|%i?YM{sT}g9c^)w*@w7(Jly&h za*a<%H{@r6s^Hfjr12h|3FbN(4}edkFh8F);G1lSSFc>JmuGK(OG@ZrW(xXpn8sYZ zYU)%}FpxVR)+B?Wva18XaQOMSgfE2XNcbG#^k7O>b2?JkYA%*?=<2BNHH&ngHW38l z##|Ods`75t_s+JjKO~3!<++~rF3+Gc^T;gQ{7*Orz&&v%ZjBx{yj)W6&5T#7pSG{= z0ez!`{i<4S&-)%3{P{9_tNqHDE%-e9yx+atFW=5vX=fsOhe8^=!XEc=jNI!CGm``n zHfYBP^>3%qC2>h7wBnI1%t)B@2A0LT%UOzk=Hiz(79>oB;{~Ptuwa9W-uv}F9yznp zx~@+)aBXR0FWjlz(EU*C?09lgPf?=sKg!ZW>ySH8LjyZ|O$)$LjL=SbR1()CKio;g z8PROo>TSEqJMHn^Y#z)kgU$(+&snq^USJ`c{3xqVUEUq4ybPxzt`*eu)OLS@SR#bj zK)GW^7#UGbYYnpH`l?68=%ehqON_ByMd($F;bLvB!D<_z)#`T8^Sq3v(%-8Uf&T`2 z?q2Z7(dFpI2dAT>)8`moE94Mqs$x!ibkGF$<>uoP{cMaYYn1cH776Cz3|OFVu(NAC zU+F;$%WzCR=Y=xT@fK9XBQ|B{cMBgFC{*892&?%_aIbk|;V_1!>!kgv?xdQfVx+WU zI4#>-nmp#8OF>4FHxZ>{#mk$*gqYeD@5l|xvz>C3O7v2~DD+kf6Kat%MIsUD^s=E5 z{ciJIH~UUy)2y%xw6xc(Zgvtr77DI%7-rz#I;lLKc{O}@P5~J^%m(|*p;7n6RomI= z9o>H7Hk!}pGh`1LY7$ok8)qP3u&@RgR7XYDFV@iU5fvz8l*D%_l0sMxAI%=OA9HbuolCqG-Ldipl z+ROKLbY3NsQ5SC{n+g+u6zLYJDM?t z_*Huc4C`660Htw#l6VW)*`#?ndH-aD!C3kk(_|+Z-pk2r8&fs{HG>7;+kk!n)KMrF z$`1Dc;Gwqyw3V<9_kiTG;#VtSBkqB*)M7veW)1Fv^Kv=AHN_mb{maq{eLI<8{zCnl zEa%ASF(?+l^@`{~-%Iu|{xeaQ!AI?3bEEq7{=@Jz%3;~pC3;b3lY~;PoVS`)Nfgoe z8LNbn%}`)Wh%1KHTpwIo^NpJMsse=zcD-R?kSpr$H=zKKrof8&2#Q6)iCsShoEl}nIZ^V{nn=}( zKA#0lW1NNBr_mGj%p)M0_U#D_1$DJ5r43N>sptXX?dDX&@nP>P!u;g~fN|8RHA2Pw z#h^{};)Ii(+xD+T<-TaOU@KTDoo&_yViVj>aYqnWYqwP3u|q)j8Z=PVkqu1gMvW7Ym8 z4f_ekXJKQSsoq0%>6SHBxmHpbQ?<#$1ZG^Z%+F}Ps7zH^l1+bgHz_h~z}GlLgTYw9 zTgMb{)_+^DVQtK8hMdKbL1Ms$xp6mg{rU@lLhh__#p*e~<*{DxIjCzta?h$i9Vu6x&bhq(t+k4P}?Bi>xaW zMVW%L@Cmlg&6qlK#vr5O)ecolpwS^3XgWakO#*KpqKdaAEaFRF*B z&QkTQcNH0h7lBCBp z67T%73b6UWWC+G+db= zV~lac@mcY%ib@?AkLGP;X|1}o|5Lzo^oP>UD1Gc_8@QTU*EX)FLS7*E6%B+ryBLA` zq>L}JiM1Yl|Lqz);gy~`F|UhwWk3Lt=j!k}<;Fb9{2w$X>asOb9N~7Nb8@WKj|evfh2DtkF$!A*`x-@+v-3aCv3POk_x@%Y>uvT?i9G5AUl*5UooyTP zRld;o(UTBM=Cd>LD0^@;xD*DrdrRNs%wNW>N!;XoQX=x^%i*|2aSnFe|g4gQk@8OuqJ!Fe->~d zZ`K^6B<+GWv&71xsc<`yxHdab5}nBUi33rB#wm_X9gMG@bCEcXO*~Ph!@GpDBBTB^ zk@F?oLgh;CKM`id#8bRu$c&YTAe73Os^{T}Bm>Ta;`s|@i6VsDSl@dbiWRd?m_2w; zX!c6FHv=eukFS{kjVYthjEjH$Od=V?UriYLLzmYuESs06yw7HYJ670t=Ubo1gEjM; zftf`W;*%s@4)QDha-Jj$tJs6hzcr{XQ)FkA>HWclWwIoN8|-L5$TioVSIYK~SxdXQ zG-w?5Ki~9!(qX+^rO3~!kQ*Y26pX4_?D@cvdH;Il5>t2hwspuFPY#|$y>?|*vR+h? zQC!71G{)Nn5?o?v@inWWlPluS2!A>v!f_qZcxu?5))1I)qdxM;^A_B**WE-#VbzLw z%c_Vorm;XhS9i-gRTp|uyc9pP_wO`+Ocb?fXyJL{swmZ>9^g`L|I3Em?tYBtdG*8q zW^qfD|2VM|dfr=U2)h(jIHrC?XO^eDwXD3v2xevcO5Z!;O_7f=I_=t-#(n|Xki>p5 zv=BAn!k^h@M}oN=$@m*&knKEGuy*QhJ}_>1F@$%njlUp^Ju+W?5_`xGMt&T#Qr#o( zIlPfHtgV`x8r>*7H7ScDvZB$tFs@;#l4-Y*QaO#OLBB9eB>tARO3;7uW}rthSygRB z$*&hl<=4WosNWrS57mT;1ZWoh2=p}MMBjfqNBc-uzK^E(sR&fz)6hHj(2`tP-TVj! zv^y$u=`NVWtiqe%I}Xx(&9+3B(F)yied9#;OlLT|0a@i4sp0@Sq>R-e_>J`fuM}P( zSTHC!A-7Q332%RYA^gR4*IzyHqi__xTqoEkBa7JU6LPKrYN6YNf%UG;3;ZE$3$&S5OeGx93 zS2=euh`xU5s~UrbY}qx(rOZC93A@w$n4`6`q40iRMvtn9SX5KSj@at-hI0JeY`h>$ zeJ!uDqL_MZ!hKC1YjULH;~|7MNq7jbvXHu+{->iHN;TDFoQjP@-v604$nhgIb|Wgy z9=b4}WT#keHqKVF*kH7|hSaH2!JPyVp1Cxa)GKLWHXfcRGNjo&M^*O z-*X&Ps*Hv>As&U%@Ufv5$;xjRZ#AzLW`+51@kyDl=m=UTf}$3>vT14{0dJw>gjwL& z)+1>nDT+>=e^eUfqF@#$RhDk>>)K{Q;;LyL7UL2@+Tzt#%vs{-OV6_6x0EfJnv__3 z(CH!om6dPUz7kP7I6ms`ltXx%7-_-+oLE^D@D>Y-QHXF!8+&+A6{;Fl+E|O=*1Lwt zP~BB0vYvb*IQFD&$Hw9Bk5JK+1fjrS)1USIGGt5{d_NIZ=~n=Gx6&iKnlPNzyQD=zy`qK=VJ`u2v7xp z51{fhwg)i$88HCR0nh``{kM>rjkS=TqY;@92Ll~FGaV~E9X%@@3j+%!9UCbf9qG?B zDI3H8ml?A5Hik|HM*rX7|H;!gvNp8QH-~{_|IcCrMK@a`S{Z$F1xG7dX?$inI$9Ak zdk05+CN@U)pP{9rkv*-5lBU(u#YZFIP{GYRQ|L5HQN-eFJ{Sl|jV*s~% z1E7U{%#(@O$3gua^ct7TN7u^>V;6t)5l}x7eV6g_B*Q&Crc^oXp zgGGwXXWp3}=gKH*SV|>plqzLy@=@V~QX=m32wa zIBR!EOy*nF3D2O}{7S*k7APxKREi}tf??4aL&e56B+g+XA0S^d_$o;!%A8-QKIrgd z5}sC`VNlRhcOk@AZT{W8yehj*L(5x#LBT=4gwGlWb=Lfo$KuN7+Kz%$)?8&56X%ll z7XdxhWfyff(pT!|P(sYaHGh2!nHE|^&!B5Aob;(0iO=x^8>}SXj;W4UQy6Lb3Q777 zh^;P+EnwGE+x8x{$u`}X1|^|p?)Fhy&;`yZx8NIL;hWY$Auh=ez_&XcEabMv@CGwb zn1+Yfqb%;*lGf0kOo2{w=4b{aBF;5C)gY()gTE?4;Mh;RWX==Q+KT2MBL+AGQ}{<> zuk^%N99A5uD6@-dwfaW*=DPpvLy6Y1ZxK1OKa8SEJ{tvf07TU)yGv<%Za&N@{pt@4 z>?FfUnM)y4n@9(xvvh)!w9m{zH9Lo;4ybrV4bw2!lJglT?`GoW#~Jw!&uvOEF`~^g%jfxr21`M|Sas zmW`CwmUJfslL-6;frhU&IMss9poHKzOnwl`eY6rYjB@|1`P__cgL(izjA%K`h(%;6 zOKfeh%2JXyaM(iC0S+OcP&o|3$UTq?7e-+IVk`~~AN!0K{RW5P2c*_QmJ3kt=u8}h z<}ZAybaL@Kv0YR}zouWrFoFvteQ2QNX6HfFt}*aN8`)_O_iGgxMys-HZo1#4~D)SO&zIUL|{YZ^W5mY)<;vJ?@nK%A; z=o{;WIS*(6aTW%a!z0A@Dv8JB#7Htn%A#3Fu&oz9-CDSDJ()h z0B3aQumNb3;8>%uc?2?QuT@`?suN;IPd<+BMj`Vxc!LTMDoUh&KZhTcLuv;@P5jKj zsW0;V>j!3_42PQoO|Y=9*+D@}hlG%6(DN`Dw0dLvQ%-dgTJ+KJHL*mbMU$i9&&2%x z_M;qFhx$+bg$tG~LEL5t<{0)ZTm{p`P@zwA{fh2XicY|Y0rpJK*eg3ixkliO)QiA; z%f;z+)T<00Il<_P(sL!aLFlywp`?G1NP(|*o#ulI_P#1r66Af3t^;$@bo-(KMRU!h zhvq^VMm10e*~HA_6SiP@xC+nR7-$&~(>)nQnGN73976ukie$lt@zfclSIb;6&;VgDdE>p)^n2Qbp*EjNmWVUq^SwM-t421zDeq2fW((>k5y~9~Bb@ zjRfg#nnR8C=D4$>KNALSb28s+_wZnr0_vJi3bxh~Hd__GJw!I#5$KJPGMa+Eh#o8j ztp;+B{^qD9tMPD<=4yTtz-$0O4Wiuf8O4eFQ|V_>;21`LcJFD%GKgWf|VfC~MqC>pTdki*DT7>g9Omwy)P zLE3k}89FXnc{o(|WhLn$REngi4aG>OzPSe>2gRR4#M;19sHO+Szy$DZf)idhHI+X& zjW3K6RKmTf5+A%3#xt}*xDlpR${f9!kfF)k z*otl_f0C-TDFN!*P$;@-a@n)<9?P=w9zeE6_AHP6JN~E0O%_~>42v%ZVYV-#*PgZ5sxmUO2Gy2 zbuBe1*V5nwfPG!${YZyckX3$Q3U;f%7hMyFv@+Xg4@%=$#}GYUT@C*M%mRBnohV#= zmID1;U8EK)?=Noe^AaTy(&PPKz6n2aJZ+{@DDU7IT8agI(DMv`IfvAtF7dlVyA6zc z!j|ceb%AU!0=Nw@<}GpLRvGHt%;D;xwR^j$iCRs9%`h0_W_PvmjbwB}-Nb}TgHpU^ z>`#sXB)_8FVD8&e$_Rfu{O!vCz6zx@#>`+*dvhDCi1Ln#3`8VkP#j1yz!k_GjI6Q; zSy%4J^FBRBIR=i#RVTO{M)3)m(3std_0oNa_J(Bx&mf`K@yk{x6x-2UoZT19m`0{| z@N49BH`O)XKiTw~_4DeN+mWgY#rV_vN4b}BEMX<^1Cve212&_BK8ql|cbmpoG%`0s zhoIU|l5JGv*N$i8mX^cRaVQY;yp1SwiBrZ1qXfYlhr6d#x${Hanws<(Taa*#QUk6g ze-=Gw8U1Mw$~*oX8=<@w*j9PUF5e!_5nTof`{ae-uuW>~j0yH^cg{4OsIalzJ#IY) zVYfj8CEK8FB%LqijDaDgxxI7&*jgP3BiiqS7X2i|_o#SFX`K2!0PM;vLcC)o8w`U(?UOcA)YLPxxDxb^w6x9fjX;oa?TbPNSdMKeG?dNK7S8RA%n zkPvY+2PH#UPE&C}TFi`- zC!A7TIEF z*ZWY0`!=SKI8#@N382~J-&^JrE5>4o6VmC z5m~)ec2+PXzS`XSoJAaZccoF|rDt+%v7f0vzw@y;*%5nz1UsPYl{9h`GAlpb58Zy{ z%8fs68b7mfQn6YvZ&w^oT6b!0mjidHGx5?K|2(;>UoAeJ_M5Zwej)nMEQc6(nxaBp zF5Vygv05rOVb-91Is7{9&rW~k$6+!5qSM%Br5^wMO5@&(8Sn2uI?ZWnCwAAefkofJ zdc5l4p%2bnV}8Fh?$q&Ctx5_pd7{O6_Hc0?o^+4FFGvdMOtWexWuIV;dC6a;gq6fe zd+o>6sa)`0pGbDOEBA`XY{SE<1G{bR-u864x1Hapu=%3MK09(@m0=xE2l=YBxw?J! z*vQ^rf1Err4)S?soo;z&uiDz!*gJz`1^ak|sp|TsLR{V5zBqzycB|amcDYScf!bit zsqM3QVio_KpG;=e+5gtr=xBe9e>+aU_Mz!`1ikg#Qb%|M+p%PbsmIHSdISxw38H2? z@0XLSK|RgkWifHF{bpL9)P0j`c<&F8y@Z=zRm|tj#J+99vEU6q#JZG8==~C#cnDZh z1WEns$&>;W&=AR1+bD}^KLo@VIQ&?-;F(ZvHiz@##$jnU+1daIa>h%h2Cg1}C0zA< zY{2jE0tg0QQ~?5}6RS*$83&HNnK5xb26X`Zd6zmZ{;z^IrFCO87yx+Ba(KJY^pMn@ zl~K=0N`eDxH`V^%d#c9foa=!6*~;}eGhWS`7l1`N-k*$AJKYw_lQHOSoLee=Kmv1Juj2~9rF^#ShgoX|zg9L%0~~L~NB9an)>wbH z_@wbVVz}pjN$g2kl-jYKue{4iH_wm-#6TY=?$m8s;o|Gn5=VEA&{wXryC;Nxn(B4 z@o!C;BFdPbdaYl%Dx=DQR>CVkU3=Kuq3i`yw5I<=N?qV_i$$>y9a^{p`C#j;nyvgPXYSXvFY0n-P4b*G`Al{E^okB~x=u*9v2ukmsDRhq)ECEpA;2^KAZ)8Vrri1W z7{Rwd6IR6nmC-C}W>rZm`}?}vs*Q0P*LLnR%=`jJ)Be`F{c-`cWA+<~sozwuo$t}x>pw6} zK*{p$%S}JT`hJMTllnOI>4Pj-X$K7w7emtgfH?ij>;DBZOhL5;D34itNhmf1R{trq)D3H4HEJch3~RfB{%{Ul1aE!2!=c(nA~a({fX2oZrK z`|O5-nmobcbwrGq1kvg8u@$4F+JM%Q@YebCV_%e{f7v&)#ud;hTSd=q z)Utl8%1)7`SqUybEYL{c$05}slvIg5g$vh>6$~flIeqd{&>)M85E8^W+J{Q2n&wLU zDyz)V6#uxLDX!+=8JELbCrVkB;=ftx{aa-BEU*w!7Vd{tb_PY@1*YiCf0^oE!HZs5 z^Xngh^OZ?QM9I5*MPFZtn=F^e*|Uf;h9teLUMXp?|JzyM z;YcHc@Q*)=iy(_es_Yj2rTagD;<9bVtgJ_GKQbovxivx&KtBIp*7{d8woZc4cGwy81OSgzXXefZ0=qpP00)s*=(Oynd<3qXH>$>aq()~tl7Vz9dZri%(=2&i^W1kcTL}01I=0@ z(yFnH+Oxso#F=Bs_{r+;$`O{;fjMB!W4Fvt~UI>cu=%v}E4L<;UTR(Ks z?g+*kHAjCHW7^j48ladEm2g(p4R5leGt8CcZ@8d|ve67@NQmliu^p&$Wb0%!v?NU( z=5tjQ-VfgHQgo|Ry@UoAar1j~FyF4-5@*~SQhrJk7rRAf9MS(($5i8@|GS2%wnwjr zPm#R@jx?9$IR!7DvQS0&QyIiPDl-Pkx^(QzX}C>G@)9|Lf6I@07`3 zIucJOn7xsqwUQ@@=V!APOOCg_g#JcHo^Ds^p+-j<88S_Y1yg1>{8OncYwVzU z!+4A?=1NCRXGLW^eVLZ6S8$`R3pCS+1;1y$Q~t0A8XUq$b8`kVN)ae0Z5f^rF%1b@ z-1kbuUPIL1mTsEGqi;6bVfISs+l+e(-nCXv-KD_EJfD3U*PBn~;o;tqHyO{yfYHjI zutKpf85FJ z!Q~GT7C$E#NX4j#tjckdID7f^^v8Xvmi2|qpBx|n6ei=sJtb9m>2PVO)zuW;Q zmS1>I%4#sE#`~0be}5>r|4=ynp-?bY>ldetL;Pj!@M7L+CY8tgJzf0t6(c~ld$Afw z)4Z=Eaa@&(ljRJ=&x3GG8dJu5?IUN~w>KzW`CC_+iw||XBEzB9c_etv-mMSGP--FV zL2@aYH}Rt3Yi1PO(z&L5Y(tscv<&fk`6fU`B)L$uyc#FG zP4IMYW*7_aHkyTHYSgvspXeSVw7YU?5js7B@t=D>jR%v*Th$xed#>ABS$hQcV=~pj z-%CP)M7}^v{p9y;1^Y`@CbhUa`>CNZ^JFLBu=CSdM~HxUlX11=tOE7yOk>BULyhcA z%V36PB}hO4Ai%m(tt+d4tVLgU-8(iG^9bCBtq@vKuF5OxkKwbm^QQp`zNb-BtmhWk z0pmwL5Ecj;FO=Ql@MmF=+Pmp+Un{T2$3^_&eYBJoYw5z1);QFSPMoW)1f5gnWf)E< zo>}!nVNLILM&)4ir{G+?u++gbdn_&Y{$8zS4FO|L$4la636C+{ntJlkY>q&7(l?LY zlxx>u^#di{{q0CxLm?sFgz39TuQh6)SQ+d|#wlDzx&Z9S>E4@1r%U}Bp_-X_&EkVl z?%Z0*A^u^^%z4AV{Ql%NTU<#Trq;K>T)_bd(Ql!uyi`+Bp||SCOob(4oKqKU1h!OT zITE}wHRYV%eWkeETG>{`<*~tAd2RCz%zBhQJiPvVZJplVvDpw`f?yc`ZD`^yPV!0d z_YjKm*xi!bw<(_jv7s{LgVg6;#^w1f1#0{)&lXE(CmCBH6K2zBmK+HhZ=T)8*dg5s z8RA_PZ|@!OYC*5RcsZZH{>^azjhGcF_^=v%2|}*x`=Z^rbNgbUomSf7fYxJzG^%Xc z6KPa{n`=KANy-yp|F%9shB_PgNHa!~$|!MW=`^*Avd*uQ>P))c6Y1gtAJ1%QbJ?ji z;KP1J!DTb~^R*Z1`}5jAesflu;;IUL1WsgO*YLO=OI@kF{&?Jooq#{q$zYeI%bSro zFd62g95)nnyC#yDjKFJ-%bbKV_SM;x^{IO4F%?0e%j%I5vm`ZZT= zV7b=wW4UR5*?r#Z;ZvIF&RLcn^mGhW9AxpZp(q~3n3Qrtda9X?-Rt-lawZiNyBfLU z9hX4yyzWWxbXulx$dq-|l_u~E$L@I5O*lndi`{w+U?H(&2_i8|biWW`;>3i{KQ`mK5xJ_Rl)rHi$3{YSZ z&07-z`3NZ`q zQ9L(!C9iLcTjFnH=_S7mDi{tofqX__ZDR+R^2*BwK`Hz{ES)}Pa2dbr0xSh0K)V(Y zC0b5@>7NI#RBV2)X%0sJ8uz^fX1++SetF zn`~P&*OxK)$^lmkT?S)5-mSFKFtng2s$Hhf{xZw?OdSF@9@%S(AG9K!ha?3%$fyu+ z^AilG@fmHsuoT2Lb4Eb;e(q9ZZ9n{&>=+KR0>UV&5=;)u3^$;mf|`{^&D-n|Xc*@) zNrjC(?#fs}N!^|uO-9Ozl`?Jhw#zYGIC>;Pv}#=U?LN$^K76+oH`W4@A&CU6^0-?mQjwhN{D0`R-ri92zaPPr3ddHy{cc@DWHE*q}w`P=G(;@at6Nszsv~gz_ zaVJiy1PV7^!1V))$}8g%H3gdKe$mp!bJUsgdjNLqqZY95X7*yKKJm@2Nu!R8^fOEM zDutix5|9g^`95~h-9#VC_=`^(`%!Qx)MbnYg7nxRGb6@H9@5df2kSG1bu56jOJ3MI zziYLx_MWJc@`Ue1MkB|D2+6c6VNhi*J9@vfF5o^yL^Bq{!PJc-B%T{WI-0ZX%5Hc( zPN|hML%$7v5)QD@Yxi2Keej(uk!4-$j=2*|D354w&_(AOu6^>&`!TYev~;t~7)HY< zOz9~xz>tZjO1$%(t81DKma+@zok?-yXxdi+=C3iT*el^_4BVu8Wx8Aa_O$b~&D6HH z`=jerwo#nCNjK}LpDJlPn&$JMeKs`Nx<}cDNOIgo&piuyJ=`*Mxe4GmMSXI7ue36z z=JRfKG7)5}*owtfL;C%&s;1QUS4|Y5mBm^0?`9vy^{<(vTkSvRd&C7Q^J)VQ>V|hr zpKip1NNsd{^7MD09)V-4XKvj>UtSjpx%E=m+N&l)VN#m4ve(^m^_;jwhj@5+Y`uI# zH@+VPu;$%1xb!uHul*ENSFcg-XgUvDAsNGA>wed2F=`Xz(8caX)%AmWrVpEcl z|Luvq@bh-K#hhDSJ^hq`pR0bf3N>`L#yyrPat#vB;7!|Wq^T0Ui~Q>O4fknZXEQe1 zMNT?x=Umr5I4WKn4_qwGa?7?luz&DTAAdhBJ;mGZjMM&pvz3hSh1dVWdGJ4`wXo39 z{bwr4|6RTWI}6MIL%sw%12fD2HDAIa28%W6vuouOI6})eMP{^OnC7pNu)VXfpQW~e zA;~QVj;^n(?udM&M!a>YsQmB~%~7??6ZTo%Lk*c+8SG9dH45D5xCagG=(s+Tf0?3& z+2XF~yGP_aqrQ7Av6eYWw%_UiOSpB_5a`J2g4hqi5b%omD8EB6^FW=Vz}AP# z+IAUFCfgw3amLCumRbSrk&S^3W%!x-V38Gp58zjl6U`~W48a>_$%?I_%%RtVv@Iq1 z)JhZ#AVQy5oh11T($c17sZ~u*g!ZYRKypG@8em~cazrZg({g*3Y6Dg`Eq$L#QVe8< zv-5;fD8u2Vg=TAl6Aboml36PNy`Ka5rU$eiW+sx#kb?evf?wd|v_obFS0~P2Ae+|C z5Aa!J>J_uW;E3=x@VQ6{yK>F*0a*3OEL5|g|5haz+umjUZGOnbR0uPI(g?y}@Aup9 zp)-2g^$^Aayvprv)DLSb&l#5Ea#ltR{SeC(r>vCg^{|I~+L=>qlJg-ExXnZjme(Wc zFo%ODXA@Qm(hv}{*k6Pao7^<#8nz9c3eu5`O_c@h1A-ZO(k~!)wt@oD@Gmf!AQjMd za8H@PyDZF3tUF5MHb$L#{nAJtC9srpY;+Y>8n7Ni5RdIncGX!@xBr@w0@BnxVGbxP zvQYS)ptlw~p+?f~?EO?-r0xr)l;~hmj+z(uMBa5ss7==J4Rkk2TTalDM zBwgFprQic3u{{FB7fr*0eI1O;*roR2pYfN&jrod(}*bmZJWASZ)$Zx&ORQf>#<9UvC4^XQuRFuuwXQTuyt#KYx^Ah@}a?yhY?D`5`v3NRoy(nsORZ+b8t%nkllTm z1=VKQx^YQId7_o5siVPkoW{5AM+)_#dQ5E!95IDwC5 zl+DSQ?QJgt>)zP%2vOLY@@Qs%yTN8h_s5uA;z%VN1DvfiFBrni(u0kMGVh?D2{Z_& z<&(RQ#gF3tE%Fk8+$WW1R+tfzA@P91xiPG(&i%}ie1d0+ebqz?^=II6+KL~|s(do6 zD@HgH0~Szw0+JO#@hIA*lhy+Mj?<4Ax$nsh4xp81%06JP%cD7PD>>DPJkv9Cl_nA z+8?B-Y*qj+D;?52B*7RG6D)29#t*hPoJd&f31<98x7J2g#8;Bb)moS9wcH>)Er8Ti zQ5LjeqDRvd7EnxW3B!qr!y0Ayo8o|9GqH#tUtC9{ZER% zjoM#S4 zQMd^`g>2Cb05B`uw%@TBJ*^=y7>p|VWNh69+zob7eCOF|m)*tVPHSyL7p{!&dfN6Z zBg~A^wVFuIKreJl!UP~HpZhImo2OjFZE`7fpz=S%T(I zLT{48#5pys2IBtd1RM*^s)4-Bn6Rg3Q=K3F0^bSR$&y(4+cgmNBF22efPs{%qH$Sp ziEi>u2D2MVz2aB`f1pXx;t&U=eYsC^1Z;vP9Ju_%A5Ap2th9K*#S``?@am(K|ierlO(|wlkIfn$R3pj(d+2n@RgW>cuvGK7-<*#ulCMN=`FToR4~9*0~KisJ1f0UTUdjXzCHPB}wQk$L?nWseQD z$FShW#daWHxX8NKNS6}l7RUnRNiQL97x zz6xFaXa}3XcT;&DN(eYrfQRj`*et`Ga3j;HSgt0zw)Zl_AkSTf(5wP1*=!&Dtx5tY zl0PP24ArQN4{|ByT<{6oWgcq4=8~;8W{-7&>U^5}Q{(C)Ahfhv+uSVnb5&@4sYVRXCrj(&5yk+-XRdt{E&mzIm3|{2l5Z~n;l6k%c_A@| zQle;Mp@vA=jh;zW0Ds9^cDdx|KMZybb~NnvHiW7J>*;$Nr_oTK}Lhvb0x`x7&A z)igf#m#X7zEVQJEW&;WhJdPgpt5_Nd35>~&+@C$etSkrio7hz2|Jq0%kSt6&O+hI^%v#fFyw}Ot9#WVG`#6<7nB! zX4}Ak0Y#rLxLqfnb?~EWfDlxYsfqO=SjIa*F6z zeq=e#`Cbn+=eT(M$}a0)A3b&7OcMf&5w_uXZmW*N|7@ay-7QAI`YtvhMN8q6QFfy_ z#m#AbFo*bZ;@0i;KvGqvdU{ZQneF2zkjB^k@@I?h)wj_dySl3bbdlBR9uc|+zSZ;o zRQ>sq>_;!vsXSDy zcr(iPl<)%R!m*n@PT6kT3?yybI32cZX9IUAl4et?w`)*Xn!j+3HvkDPt7p^Mm+(c* zp4&d2D&Hf?w1JYOV^1zF4A}`{B&ZrshrTWadILcpZ#X|-hClx5QUtrS%cgpO2ijNL z7lhyU(Bwrxp8QH2k25;BP-rDzsaM#Wjf919eXG%#yevrRmtkZ)_Ua)Xa< zg=+}8N~sAoBs{2Lm7EQ}$d4M z+Z@b~7AMQsHvCas%r6mnRML`${0NvW^D3)S2yn4gxNr_( zi2u&aUe@A23%c{0ec>QNKNj$8(N#pL4_(tHHqNY-8-yg2#BY+L9I*%%6_+px7A=}A z>k&`CBOc*4E|K9n5VowMr}pOW{`?tl!Q3Uxw5T2kaKH$6$SDd;*l}Z0vRW~HWB(FG zzBL(1R%$3&-+DLSa}j{$W3EWT1=Kd1l&kQGEEv6sxoyz5t+Xfu2JepSh-+{>G5d>7 zO*J$k!IkDiWDWL*YaBZn%)CR4)?JK~r;F-eLxJ_jBy__ORq1?y@666T+F&@@IoQwy zUE)?OceGdsvUL}>ApwI50xt0G%0`xJ2=LPws$PMAL+L9q#;peVYIiR*9Na2m8!+P+ zh3B*+v?23?NQ8n0=@mOkBQR-qr#RA$RH zEzyc>vJcfY)6+$A??Y;zcv92p0jSI)REGy}PcTBz^q1DZva|fd*gIF+Ks*pCTU5Y} zG_(FGkN}RxmR6xIvwgchwdEjW+ID)8ba!7EnftaJ8SYEPwflB5Y=2aK(QLJ{5rdiX zZE&^wW&h+2SiIZYnO zZHp7i{U69}OU!M<=uo@T>a~Wf{vU>q?;nRYXR1e#T2($RCr_)+*UK-R-g;Br&&vJ7 z6i-+BjPN(YfxqPQsdLwHeEu1i$)rUmk}3^@*_(bS%v#XCU9&kQ`gP1S#mn`8S%$aT zvF?s3<_2vWfiWULzxwc2yLSx1WKcu8cQn#ykhAlSrGI&4j;QR7het1vD*d7Zo_lAIdqO!YdCPk%^ozz%V)Q%HpdKQ%mb&g1OX`rhy(=feJX z`PuVs)z-&5iIkwOVe~abw`;y>@h)zPl(g*abw7bD%+l)OJQ}1usCafQCOOwRvDRl0 zm{E~5Z!YiE5(&#&41C|Ls$w3h#s-CfokU*KLP>$^oHPCZQ)|JUPF6CJ~A}Fpaq_g^D%%;rAeX&QKQ2>4NenSL)=!uJ9d`^n6ZlJeUD3@0s!UH>rHhMP& zPR6;IEC^h)16w$R*3*ApL=7-rQ!TwHj~D;Pk2QL;->i%k1LPUA11a0@mN;_NybYwk%J}iB)|=4TGpPi(dNQdZO9P^*5T0qfai)wTu;`|?p~~HSa_30 zV1W~xHo$=!Cb`Nm@Sge92e_w0o#xs|>|HA$(Opsq)JDkFK$$s~jB70e21J*n8rd=DG+lI1C$ zuZQ4S+d9H5=&`nQE8>7Ow-OD%L@X6M?J3%K)`QAf z6Tq>DO9-dkL@Gm-#t;hoj;uU*URv|>=&_MHn%vuafg+~nq47IsyB5hS3cSyx0hivs&8`uTig&?(NhGWm%h!u(O2Aj~vkA|_tMxi%nzEg^ zF?sE3BX%{y!9=~%w;)xRF9Ah;>r#9K>7>bT3o)L6*9ZUnmhLgogs8XOI;8B;#(Bx1 zWCKynCo+vMete7fe;$+ep;k{Oo$qz7)7c|&!Rd+_K~YC;kIh7j-zpJqhz9_J>!b*={Ci))J3i#Uqb z_S#IWd|SEikw*Y@WlndqbgM^+sxF?2vte~mW)jeaEAC)lG>N6I%+5qrHyXsr)wH+2 z9L-WEWe+htLerT6=HU2!U=$XGl2R}OgO}DFwc>(IV?(m04Pf9x*?TG%2+V{i=Ydso z`y#A*%OU0aDl&sLd0^{Z8@Jg4DSvp%k=$j79&?4wIHBeqkqJ*r24?|a`2Kf9)qnhC z#x_?kz#sVtLOxI}1EP`>R?7jkVVBHeP;w?5kb^-fL@W1S5=W4XFj=B>oM9S&3~7IU z3GVw9CofodU!jba$6QW1mnf2HCmZ<=w4&7JukAXneq#vS-X)A{EV9rH+PfKtgsP31 z+gLMAEkx=M^W`nguiSfc2)q9|U{<@Y80!USsR?|Db^15fNgD@NYF zLL^+%PE{!k6(6U%EecJO1sFU~K$ z&gp0eK6P(gZQe==pg5$Gt|qQowomK%UcWpy>-#*O0yeI;BAEOJAu}!EWcXz!9UB@}JX6vR zIMug4tvKMGj0(^@P#npqAy;hz?`FHPj5}ZMMztq@~aM2OKS!!atu*}JGgR3V3P<8y$>BqX6$S^3HCCFn;CC^5M4<>@Cm z`EkaFl%*$;$AM{mgsaGXeAaTupg6^|r`dyzs{6u~CMyO?-&XXix@?!?jb|F;mhjov zmG&u!$^;P*^u?j5H)r44xcAb3$6Cj(2Oiwn1IXK9xVe*8@pc6Sy|SNrefQg2TG)?4 z$$ZyjO!_oPh9yggX~wtYYEaN#Ins^ddlA0mB_ymq!rg}@(Pn}J)D)KT5Ec~_u5Dh2 zQvr9gTWUiKtpd%}FDRx zoriB0sn{uw+l;SHtK4YNP5~;R@!;{=u2%O9Vvpv1h72Vu3gI@J?+GZO%!M(d(*xZZ zTO%@H6Pm7qvWpI2l9#A=3SE&yd(TF7zCe9C19ttw07^7Ur;i>0Yyvg(>VO+RCOI<` zGi3&Y=JG>b4an4RZM?53`L8V_{F)wSQzxB`*Z*tc)%DC`wF_oV**jEC$Q z;UFTQtNZ0f4EKShSMX?w1`SaK>8`H5n;>dw*pxqex~kZo2=gr2O0SUQOYh$TBlr8| z#q6Ix!o%0lNB?6Qd0OB1Sx&VU5sYfj$$i@(ZRRQLbfbx&E+qzfK9lY@R&`A`#z)zm`z}Q37v0=op6v3a}_*v#3kZZx~ z^TX#m-q>E3nn)?F;`Uzp37uk$u5;=h%d(aT!dOZbP=AtVE zA7R_)aHDlOwrNKg!@hF1sn&=qR=a8~Rib2Ppj$%Kch)}0aFe#zlFyaSEg{O7K$X9w5U66$p{FGjW2C`3X=;NCk$ z2lT-3+4WV7`qA}VhdUx6W6$1Sw~p;-^|fmy9zCPsvom)Gg6fJr4400K=%ughuiScq z!{tZrz;$v}<_Z&sjmCynsMaaL6zyI8;P*GF+;W2;9K)N+{T9 zSX6wpeKAC#2c00BROl6EWbW?4NxauGQm-1r*Z7S3M*}g`)OE_e6kMihV1P`hY%cSA zIYyd{B{a)onPR2m7_HuZf%{;}3ciW}e7L&NHT&XZ+p6tw`4KM;Y22Me=$sDGQu`Kwr~IxHW7e5O z19@Ooz{Zz)N-eVI)_kjwqbD_EGr8}kD#KkdJnu-(m z9I-dk)bMyG#c+~SIc9YI#g&nLtYGfX0AZmiLI_&$m<&D`$A_O1bmFlZkw0s(|5NKV z5W7$qP?)`SLB16yMUtE*r=XL)w=*Bpo`+#5=L97g94yGR>&W4~7 znXA`&zIn}5GduQhrDeIJ8{%@$(5hk4p&#lx!o%XQx>e!J9^`Pps;0E^o{#cVy0mL+ z8LH8&sl~Cv!WtL(vGI}^6PRM?+DcPVjpNNC{1B?|p-0**l5E zE-iN<_V4sz98@~R5j~ieST4Et1k-&ivh%iK0R6JBNcC!DM#QuzNtVm4m zMs(~ZK+ubI-ONjGp#j>F03H7{iiQK{JHd2BQ?91P58NQv9M}?{bS7NNoQ^*5DkV8u z^Mv{_V7hU;xe%AkYo?=KYmRqehuS8UJqP9bl<;lT4d@VM#lxfXM(8ixt!InEF}7)?VW=Z)wjh6z{JZj749}vS^N^Vd)Z_rpaS-CAnWElqiNbcl}HWK8ftJM)sDmrEW2By`*qQBkh&03b%lhDRP1uX&su7sXfSB5 z@AxLlWFtiqv9fVL(@A;>Qim$`jBaR|K(&nt32g1C^**2xUpxJdtfDB!NmnX-*7@8) zK^@rI*X#7;Kp*sO4{C*Q=reFymi*&qw(-c6K|$@;p;l@wrbXoOJ2{*4>nKd?#xyM7J?if5 z!LXB)C3B~Eu%Az>E~U$-9qDEnIjE!Itvz~v^5jOYGGBI6{*v&bsV~z&bF+#T^YZDK zg8I!^z`(JD%&C6VD929x(NI9k@lPYO+D-*h7hV+BdwPr$%jn)e9$y({eTBHYCW_2- z?kweihd?-mUUqA|HLY$UUH8%oH>Ak(-?&I8J?m>>E_PBHksoXCf_=GgYlrMB(bLH$ zC~;Vx1~)2&!E&$wRCe;y1Pi4+S=eM$esa`0xe;#Sv)~G}FJsM(@w^d6AZz{GeO?VP zSBJ8B?p@Dg__BG6p}LDo-AS>kiF2u`$wu#DhrtRoJgoDska6c2hAmMdC|)C z;U-Kk)vJ)IvUUPk9aC8i{)0P!Y*3o)^zvP#tm`O6y9q$IZj@ogB-sX+!B)5m%`r3f z3^daaqXpQFN2=~8wvq3N-uFN>coDq`iQM@-1A|=G%MM1PPfK&YM>k9slik}$ndZC5+!O9q{K8< zP$03+9MPhzTMVmk`H#6uR>vMzG09ZNURZgOtnNG&jv;a%IGXxwM8<_rLJKb48h%`S z(T{d|LAUv1OkM8}Glf0OG0_Si`5aN@JeMAp`sp}G6)cnV;dyTC$1CSt^yoZ@ob9+6 z#Ql5Sscf6E^gHE6?eAfpq0-9<-|y8<4fKpwpNYurESC)!04Lh;@g4M!(ifYXrO*SK zO>Yju$z5wrakPt&lFiqwb{RxNR-1zC2+f!0W+PzYytbl$GhK*u?%`2xiv$$S}rcq&B{vn;OZ zD(hH-Wr8K3eucukUUtM**bdDxJ$5a4ox&tJwz>N5LxOvF`WW<}!kNIx!dBIi+1d#H ztk_D4MfK`f+4)P~$FS?L&)e3as}q?1UfF*ee1Cm2X|{G{RL~{YYPcQzU4?TLv{b+>?B5P~}@qN(r?T-M?eKnX9*E@grb!gP_sHBlZ z6BU^@`dkfO{0GRU+D~O(Y2K49S-qhE_bBpy@9WPnwak<%XJ^{K8!cH z`AoqJv_BJ7yM73q>fu8Cmx?4NreCc84=V^1^cK%ksbVsd-8v+M%N;GrGw`5LjQ(v}f^5%3GZ!kS8N^DT z$sO5~B5IB)|2029*@7%j4?P;9Ql+WRihv8}C%&Z>@ne~Fwl%~AwHh80QS&ta%DqpC zDB9el6nXkK$Az_YXX_x$5W~DFC+Wyu*GQ%J#@mSXGkS3a`$Jc*+>UMkAbc_Vcbc`; z^@QcD>`sfA4=16%oR52!y&f5RW0R&<`gMv^wnn0nxR?}u7M_cp%QtGs)Fh@@rQ-oD z_J}?*_UTzhA47OT={UfFWn=?Y$-y-}y*N9yrVqf(Z8*FL<*ASD>2sm50yIX0wmk=l&O7877x&Qnvr$ zn=LE+V85l>!XCIUn5(SpB4jmSXD3(Q^!eK_96T%~HVP^2AhbC%vG z7Z#{UNuJ5+q6qtHDw#iwX%#P&gq zHvNLH;G$;|2w{M$D3zQ23r@zmO!>HNeZ#+xs9X^{44$jad=$IWl8dkl;w%$^KuF?T~Wjk#Sg_Cax(;tiAkwjit3emP=imwgkH^X@)Nqh6d8h;{-$YLu)y~Qa9 z{OD;{n!h$)(C+O;ZHaw@U#FcBng14rZG>$VnE2x&w)|T$t_>CLMGy#3&h$pj4zp}y zavb_5IOfQgmg5Kh-9ZrACwxm7?~dBnV4DV^q)$_i_zx_I{Rx^?V%Uoayex+*Jd>1+ z^H10}RnF-P3>Bfn@)a*7mOV{>H}8A`lz;Y^@MMRnXJ9zEMnkf^X4`I&2R=w%roSSm z5kLJs^QV3y2tGz*VXEwKeP_ynyIct}&KCYbND(|Ky-J695}S7A2$i0bK#Bv{gEkn9E&mfQ9ZED7wu8ClzpRk-Rqj6KshYbaRQ$Mzoy!N;q{EB%L`zB}A9 z>Pb~&*k2TucDF|1%ED^9@kAw}R)tfsP_U_?yKuLB6u<`4yf7ie9x{=b*0GMHTXV@S zs*daXu3T;@V%(#e9>U z=I(b8n-U5-xP=qP>=DXp`*G-y^^H@L1jXv+CoKMcX)igZiekzk(`jW*g^Dmam#rF2 zN#1tPOdxD{-ygJ2uGoCY0M_18$Tqydrk{A>4xD|(oA}v8nkfIQL%@Ci;FoD%MF)Kr zQrsURWQ%Ir)j0&fW*j6!3!n%YV+wNoXxPaFPs>U_j*5{*asT0SHT4Zb7^=|ST1eJR zlum%lXfkH@5AX0%)3!1p#2?Im_Xdp!5E2gG7~x@pziZkZTI_;>UGqG9e$ZR|C84p~ zWjgYBjHf^+>G>PalsjrI6!F{PoBTh_v~`wr{5WK`TeEo)*-a896Mtn&cKI|~VN#iY zp2bCbMa5=F(In$C{<`PRN+T{XSvTm8e0e|5n4qS0b{!lOxqTypuWM0!%AQOji29M2 zT~X$-kswOL@)_L+1U{b!EuE@TDa;wC^fR`S%CWHAnt>6wATy4&EV}Y^EJpJ+-PVqh zF1yc~cNKDu1DpB}1&w|O|kLvxw6Bl7=x$hr0$$sz2RAC!vjJ`t`6fdnoac?!t z>)zBHsy92XdUoX|YE|(us!CJ*^kvgPUd@VlP_oNSVMlp4pm#MN%4^1wB>skyJZ=+@ zt2icjyL91pdiF||G{4afFO@ZcwSl#Q)zR|GJv$2_Z_;8CYj&D|Dp&RCVcx8>{Uf1= zOqBitFLIQKrKo(cF5dLvL^0ppDAfluHsM8WZtZgEww)3Sk2=t-ky&$AMlE4ocddpG zK9&1qUp6O{^~u>h%s$(fGR2O=aOxEE3#6K;l1GGXN|Gi*V_dx!xS|RfCwII=%5LQ~+7ccjl zSVugf?UGc~1}6WS6kU=yQd~N4iH0NnhCx%m7QWqTFYK})0!3mN`N1+4NizdgMA^tf z3qdv_*OG~~(yKx;-J$&9nqdVP0d%QTkGCjJSN=!$^2$vHzxqtTGIVD9aIX|{t0O}T ze=X9mel%)-rdwA=<-A^HnzrI#k_)-7MZ!*hm#e!a{5;aDR(`Xmgqfe|nH3ek$oFlN z-wa`&OH>vBofCs2xYq7b_n1r097s-z!rTWGZ&}3`HIJb%HzJsZ46ta@)1|L+OLuJ7 zsE(%~DFGaK4iT~5)+qB>-N(r%*6aE$h0_#;w-LdzX|;Isx3}5tx7)Lt3aCzPCe@>) z?H;YejTOOpqF@kMTzagD!MGdWC!drCZt55oZ%BFJ#{EXHW+tQJeYsRyG|?Ezp$PiQ z*?HjY(_VQvz1%;=5it#kV_3XQ9~=x3)2#QArXP=3pdOEx?ZjBq@0)WXnntYt1d#-{x@UaqmEiCINxmQ%i9oNnUs__z==0x@}=yjcOjH)bz^xebIxLnzYq0X?}E!rs}C}1cB z9lw+Pr%+sKHg_Too525VQUw!jXy3G=Je{WCy_45GHzX?XFc2(K5{2okS7`;Ox7A?> z8Il(?OX`Jd=;c#ZywnE|@5MyOIJD;yQ~I>*l+U3K0u%Wel|;sZ7{IE9qVtxn-k4#t zc!0qE)JHvm%(;k= zH^zxJB7zY_x~;3ai7YjD6f4=0tFatWm~`I=n3q|v_g(8hvaOm9p6mwGE`B8~WG}WK z;45Zj{8GJTPpc=UicwRA>6;DQrWU9?vz_xq#Jfql=GK}#3yO-%jO_G+cCMTm zv>0~4ze56#Tqj_8Gas!gdbqjJ#Ns{Nf-@L2@sG=0zp!XuA4`v{*#OmxZ+pN0)$j)B z&@UaZQv);`SCZgAN~-wP(;ZVgJg)g$bt|+A(YMC>F#v|kt1q(8?fc)84>mlTZ3$$U zaI%=^MoYX69xIneV@cl{@+f#JFhjG)C-cexH)gW98%_h~^Se#HSs4uFbyXI4?th)q zLfQov_TRCxVb2em$THN|Totxxw&^JLP=QZXuREa6k zx`isLQe8!*_PhdDF-cH({3-@{BdwH`dcqe$9-kjyCwrg`>?m{!eHXl>=GRGYW^hNyw^)Vf zAJOi?6g7ipmAxf6oYt24kk8MYf5{zM)%?}TCLg*d;m9Ox{5}#QqQTS9_|uRT{4k3e zy^npzY8EJ!#!B6^45e1&P~Rim!`wKDS1vfnKtf^Jk(KO(_W&j!hMG*sl;NpVuv~~D zN5^Q7WosS{$9Q|eX3`QcE3M9%*^|2hjUCxXB1sNPvEIoh+M+E#@fB}$E#AF$)#$Qy zOVv*=VdISg{31#sEs~i$o+AG&Q6deYSWX^rednOwT&hFOnubuZcAsoTI!8fzDj+&% zB3h|#lWKIF%UpO}jzXh|J1iCqhQdw51dE19_uTQ|T0J>hqp>O3s9q9$^6x|*dWg^Q zI4_1yr3{_%xlOB<@JNy?y^_O;-%G>ohO+-?SHxZEDVT81kaQO*m_&xC zKV9uB#Rco|8Ic7+Ms*5^^Z#bAi*-G@;eq6X(%uyD6#a$i2PQQr(dMeUP7ocd8{TAn z(n|FBT0WbyM2_KXA_9eRa!XMyGA z;>LX}u>NGLR&)jgAWalU!d`e{0{R^-JtdLWluAJZes#AMUR`Svt`j-i;n+xOcpM7` zb&!%0RAB*vlq602l`1|_A!0SRcgu11@36Ki1}JWTf44;n7Y zlc-MTO?D!RsiF=QWQxNoT#uz6dRE#5p1jL4 zWJ9+Z^caiA-?lcsi&2iI)3#2`5Dv*u6^kpIN-ePCAn4|> ztD-n}ZzSA8DyT95)?6khO}bBIk#2CluTFZ->blP}2DTgNUC z4CP*JEB2~XIzn%#dI*K)QLnY}LbIdzDJK}ACqiMgb2)M~1OZ<%h0|uLaPkKwco~0k zIlU#)Yqg{1{O=`GC$c#WJtZIUcR{b4H`m%#pF+R&izdm=SB)*;n$#E*(8QT={V@|^ zoCAALQ+29*@^;16%T!Qy=%}Ao^f$a!e!LQcw5+&Ne+wfrc64efh5(MfW6+WIj+7tm za8b6ajYzw7>8zTbuzBf#f>Ky+0jh{3c}TnzLdSEnt;w9i+|1msxth6=LPd#wNf9z> z@{lAc1o}j5EgGf<6(ft%ftjyHg{UI`H32k~`sl^AWV`E1@@e>QJpfDaNL0q-{SJB@ z;5T6$fg-TK1Mu7%70o5hB*7nZV+b{p9TRY&ArUV{iC8}Y*8rb1_K=4{t#H={bdZHd z|Gy-TNQ+YVStYDIC(HEcPKaF$SlF_7DBAt3ALN#G^OR5c$=vxaQTCdNS>J!{9k`8d%W0A zrGL-%)V6cm*hu22Zp$=ZbAzdz^>I77r)X`~vbP&E^?Ef@&fS*a?h^dz*Qhi6aEnz z6AJNZ(u)gVL}vVu5r5U!@5W^C5W^NDf9RJM8+PIZr94E=hW!Rktc*U7LXLeLG%Yp@ zTYs=>L4UWSx38GH8KR+wW%&O9+t)w?JOYR#%{ycH5*|^4*+AdkT8W#hi-p4u!@${ZAcD16&~ zS;}BVA{I%6g~!ci2g&|gJ~>|pvr9vcmjgqqU)|mXy@(i$>FnIuebiGVNpP)&Av9&G z7>xcksWs$Zo!jHY6q^qn1v>kf9gU_=SoUiEi>hiYR6|U_HL*xmB(g`jA`=ddW8yZa z0sad{GQqsx>Vv?_t|th;X#tF2REhQYNB!u{KW`uL2}E*62w_xHHsRRo)uukp}d zAcb8_p5gFB2>EPd$2dNas&?k_i1}SJr&(oK_CJJuQZHn`)P* z?b8+M2i_IVwGol-!Bv2_fc6({Rbahd%a7z5$;7cde0bbG8y%S zQ?{XA)f*;xWDy$aVL>SxsK^9JY#_jvZ8pp(WI>*B>)qY)nd3fvfiC39#pZ(xUX>o# z6k1I-MdY3-g&91e@Fhd*4i8ACmc~+n9-MHwK^y9|AwJE$CDYObpGNsKk`e$ zU%(Ox6{Q66o7kbdzZUFCU_1sNP0(f~hirPM`RS!GYWDtWA%K<)Z>p)gV^?8ZSg=IZL0-bYyKWk5) zF+PJ+8-!ov+4f|X_BQ%D1U^>q1|RN@zSleF!C)`9qoK-*-v6|R31Q5+2P&Vzn#WRW zMqgF3ioxNN9u0Y7sw!-y%2u04C_&n5)n$&f^9Ybxv%2TipP^-Ld!~GCg)b6wJt4wK zYf<=3=^abkQ0lqUc!!;lenjl~-(_39Y{}9M!uhKhJe%`FYq+?&-#+@01u`;pyS(3D zWD*FB<}6w_>i)ts98c$COFE|HG3GGT1psMY*vG|waHZc`t$jP(L#jTM8GH6t20E@V zsx;tliMi!-qU&Nl`}YlWMDX7ds%hw_sNCsYx0r8=M={w;I^v9);4GPMGOTut(!Sk& z>>hoEs7r-smyQX$LSfz$C>iZKo1y#ECOj?bCG%B=#S1OBt9j4YTPu2opDJ$zj1Rk5 zKQMMd7K)i&A^}DXWSp&?_HYjP+C;_dT*|tBuoAx-Z=CP?F#sC=$vFnFjX< z$+o!di8cWpe(&a;y7p<^*ba?!XYziZA}@~u?3dW~R>$dXb552UTc5G-?=g8%VDdvT z1@qSbD1APQ7RM0z(jSBjB&tFFp;^)X{siFPsPTTqV>MeML)dU*e&6c$`8+)uuh$nF z{gxJmwN9_@Z)UrS3{J}>u$JuAaI)=q-}@5}9=wb4Z*x9}>lDs`t}c1=*+G0ow+Og8 zr>$$V_7GS%liJ;nKWNv+C#-mX(AWvU0(GbweO49>C(ZZ-Yu^7tKl;bUTtljzB2*N# z*e*f`+sJ%ByE}j1+I+h?6uwdS=)oC~25mzB_yfg`Cm5i!*QiMZSJ&HkDiRPrrO(#8 z0Wy1caj0M3B)5eK-VGlt+x(7yR$ibp+g-&@{x(oa^>g5JHgmk4U*r*CmmK)^J4#gN zCwmmuQl-N`milkJQo<`^Y&Nt@HI=7aJ2-kSz|a{R2;t!jdX3fyyMw+~P`ij7FokSU zp7GPu(c}7~zdW14kN?B~H*djWb^|qpO7-5nu44bpMt^grEMPwbB(0=P;MKF-*HC)= z+IecQD#z@MM%op0LBLGxyYNn#q88uK#3;#A!LV`Ey}KnFi(!m}Rgr+MA<4g%MF#2; z{9%1lAV^=G^XujmhLq9$*9|TdY4SnjNi>0}HDzJvkc$^DDZdh~(u3hE98N_vebG(> zFg+rhVVcp`>vgAtDRi+SEoi{FVABt@X3X^t$*pz)x7v@sV@w^mpQHGkRV;fawht9V z{>hh#)oiM}_bzk)VAooHxu+-#lG>a_iJ#9#sf$l;4E0#Kwql_;JVC)sr@Z12L(V?E zA`;A7$7+nXZ3@*~`JIoQm}n|LpYpf~TrQ2j>^8W()=lk~U^`WvI}4aOCJRd-Won105Ofm0+_Ee|`)9B&Yt}W;wHs_dO4h`atPfb@~q8()#Q z^;GKQ)U}@Bi2F&M=EiV(JimMe(NO(MJg(?rG}y z#RhS=Lf5#$U7kA5!<0>YU!l6QV!FhLd)Z$-pSR^NgDvvPREVb{FN8vHcPEU%9^!c$9T8`wl$OWX1kXG@{>=2fU=v?%aNsis&KYA1 zU5ILx!q!wx&upZuX%=7lkqf)KI6OSuyn#j~wOI><2k_`$d(VCm!Q;ZX?|(0H@%a{m z*8;T=&2i^^ZaGf<0fn;i9&ry;RUS~^Y2ht(WBYk+qzr7wTOs&6!rImhZGY-bXR&SZ z+p%=6y-yOqv;D#gzjRC&#B1*1{0+#nNsnTS!`{#82+QcvVg;DLbh%j_ z>T{*Fu*c)6F<2&YeZ1V>%PkrK?iN(JFL=wEC{7X86?J`8_88!N)$|8iiH}A&ajj1c z_0wY&t?6JEN24p=#Z~y*;6b%{^5tf?9XLEe&Vu`Bo$oiPNob{Zk5$<_NuN)F?Tn<0 z|61k4{)_AXR^`LQ!Sp5V`A?#zFYAAosEM7G^?%uT2&k)TPte@x!#MNVGmPF6)$+_l z7Ley(4I&eZ9f;k)#F9107@=%JksRjamIC5MB+KJRa|%B^g?J)H(Cw2%CXOK2vSO60B&i*!|ahRHeF zxtXdNKgV13HFX=l733k!rHTSISm4-@q@5bml&OBDK}O7EHV4DNOS?f-#9Y?z>VKt8 zT*n?FHfpR4&!^w6&rqm!R|kB7b4wudm5|8Me}kh+D28$YfLR<%i*DAPu>VE`j^pE4I=S8 zaqc206hsTUO1Jut#)4H$#7Bc+Ym6p#bsH>e^g7@iQD`l4gJpP~6-Kq1mm|n1%C$3{ zUG}wmNF=?ebCa&q$bwIELHfoLqNu5sKko3ar+w09bMPX~yY=iinPn(>xj-3)O#DlN zBwkjAy&ndk!&XhH+(>7doq9n}8g~fYdW&-MkaeL;e)UQPye!F6@;)q{!fi~6?@WMt ziBcZ!R3+qLsds;oQL!{@C=g_fBBhxKua;2- zR6RZ5!wsVj-9nM4En34BD22(qVn#*iw~EHMNJF?zLQWF2B-ieOgb>!Vff7RsQ&~^wKof`1E5S(MW`JXmv#@ zYqElJz%>Hb6Q^HgZR>k}QMFl+F-Upzr8zB9sUa-!uMVGlz4)cZYi)c}6dQz9FkH&2 zQg)-VHjgl9Z<%V{>Q?y-y<8xf+ooiGFB zvIo1`_u1FUX(6Y+jU+%#O zY*0q7={wvMA_+Gq|6h!K1C*r8vTje?_Oxx=wrxz?wr$%sr)^H#_O$Kp zYE1L>-sj$Z?pf=-x9+N{sHn*N^UsXa5JPh7qXM6Sx%pCRx_X9)Yfo~pTGQp!= ztC!RJ_5I*l=Y3Tx4izjzeBskvKokr)ZTb#qD9^$h(q zLN5c4vPjGNeebqS-Q?QUnUsafG5k97^{RZ^5UP4j57-9Rkm_%GfryCf3{k$howkGH z;S!VNshF+zGf}zlLB?zbQw6_|+Z5jptp2)-t8u8A+|TQ6e!usrm6Jb|Y}ssS4bv=a)!2rn9FjZzN{WStKF=fS%a@xL4OQ;I~OYkmH~JyX<3c$@H2d( z6;;!*xF^?vKJi9TS#t@)zQw}kwUMeV;98HPTqd>BcT8yDN+ z1rnf8CA$Y{ATf-X(DTJxt9dSy<|E6^G=KTrA*RqKzl?3x?&=;${k$Xyfms2H6K zc`0IkY+;`E0m({+z!mMy8=1{07VDjWipU(Apuv%=A*WGH-vodn*FPeT2~aO-UwP%( zFhU6a-0d%=k!ijI;8f_pgzz}qWMU`ZY3=0Ne!-k?TQ>$FzKp}SnI9mMyZ77`51A36 zaD+_gCL>28lq((Fz*arevSRjn{}i_Ik&&5N=Q{TBHE=?7|E`>FZ4!%(@me<7-vev) zYUZ@M=TQnCxzw}DsH~#1Co~f8J+5<-#%*fbHQtEelqPs@|J2Y^%H5ye-kS5KGHPgY zq}#66sBZSBY`HLuTuph|%cz{Jbh){S52I2m-3}q`0yfmj$B|9N{CaW8%HiopZ#PFT zPw)DLA7j}@)a6lTeD2QjL+zZrNuWwnPM9+qS$>`=L3iO7$?;5ZIyKBr@juSkd3@-p zuhk)2b?zqC-mwUji?f>tjl-Rjw`b`$I>$BP$V^wNbrZpqJ9_t~B!QBk{JE183`G+l zR?Ik?uBoFjTs;~6(W*{AZ$BnJ%ivx`ZhD=mSiroMyREYd!s~JG0iW_TB3|iYtg%_D4IOeNL@8AuF;tz{0*O+CG0;~&9Nce7@0F_qHvVS16|2~^6-DeHG3R*tC@LuHNb`q8qCADv6NRQL;P zsQuNhuF@9=5E+G;OMG7ErRijob*`m-EJiL2U`*cWM$d-|)7=C^2nOyMg?&rY5~WN- z{$IHjQCCKFcP`poR55QRCmiggb7`|+|x{PcBH4+4U2%+RdE)U*MZz0M~y*ew~*v!hvp zw0GDcHO;ocA7E+&`)MHtj#mfgrTr7~y-SY0LyjMb_#>Q25$RE)Iv9*hA|!4iwETqv zqtN0aV6=rysLUU+Bj4-8#VnXp$`DPl(fRY?m=)--PSjwdDm2X4bsP?0ys=l(WQQ-9 zygqpJ(iE4cjUuK|0ZxKm&@Tn~xL`B-;9^kxPFu?j`?O(wlQb*hv~|c)*fTbp z&yJ+oU@#V9LUFV~F=~sKm@FspDUgEl;ldJ2ePgkz6eFP?J^LQNx0H`}G@b5vI9&w^ z-WPLJ7`W<+cnv~wGq$k~#&Wvco40M{-8u)yPOpBvE**7a1!>=#c`bcdD(IV6*~xT$ ztkkjQ^=^Lr;7OC^Q#MoY9DF=Df0?wcXb z9KGv3-mMj~)xGYHC3tMj`GI*)VfXlKmQ8sd?HixZ@3v3-V~tyDQzdMb??*3Nu3ng& zJ5^NTZTI&JZZFq4d3iZ@bgp*SZ+nMBf3hxfoJL+xEbN}LCX!~Rw|`%>x-Zw+(bavH z=ni~s-N3%&Xc;qi<5FeZC{)7}3#C~VVFbO@Zz6*yY+%w1WD;JgDj{6M(CQQXwzlU$@3{T+y2|iP~Lq7&zg@rzc-jViv^*1{1DT+G2|%5)?;sisg_w#5f2ql^B6Qp+=Am zB+|bD#Wl*19Kb996F3Z92q817GX&WKg@91tpg3%AS|H4&OJLr?xOEOMn;=1c z7UjX+I~B-=CvNz<>WeFbXT}I~=lZn}WW;Ogjdu%3RRI$dn)mv(0P_c?IPTyvyo@|4 zVK{#AD=557{LYi4PMn8#?=+zpPx6cxt6`Ju@X{GY4oM`A;6>siSQ#{_5gy+I4?D=f zk!dd>8gGF!F5eS$iMSk3@IgW;Z*-;&{x2qu;NQ5*Hv$ zU;)JV#cz_hK#@QUp#Fpe?I4NVK&aowaQ%b)58w$-{VfFg;6wb45&Fb|EFgABcAAL^ z)ZW`ijm6{59V-4oC-dhX@Gc zTN@-nz!x9y@h^E85RORH3jGdYAS#hPQb1TZmLMpn$PQ5;j;;QaJ+3J*(sMXL6lAu@ z4hb+h5=TTjmdMD8*fU6k$PPI$I}&psyJwFbt=->Rqh;GpO#gU#P`_w^uh@L5wy0pg_ z1EY!h(7&~WBasIt6A55^>wv5mjYk>^Mq&>tg#U-1Hm?Jm&BlGu* zNVd(b2;S1rERFWC6m5;4K@ZbfAhjbBBU4GM+!k%Z(cHUjNU^UwZE*0NJFsktSZ8eQ zui&p`u&qW8%}?8ITM}5YseBDY+fd|(M6W2I!7A1V)Gph%0}Ut+s)EpA+dm|A zFqIR+4W%S!2!feYPz9IH3V@oGSA1_f0_}2CObLXd_z4@LJL`gVMv*VTgvuf~V@y%! zzdop7Ayfe6LYAB%V8TSk4@wpa(@BFN6l+j!>)+O29~oZRZpfNDV>C{pI&&yulyHR+2QN1&PLOBC(`igH+$5 zK&z4JMf#w>L4$%*pNGOoYecf?=nquZ_cJB+1J~f~A`r*}c>s^12;?%LNDv$cV+jI3 zqpV28gF;Eh|1XHZZ>X{Oc-Suj^Z`0+=FbNLz{;Ni@&P|#7RdYd0{dSggBc(%@Kt6B zuApLO`&`DP(LOKm-^@3;0Z*`9W;?tf*I4c|U!v)+z<_&LZ?hdPkQJ=9={^_mT;`kX zfMZx5vmH*587%kdJ|}oUkSox?*#8XWbPT}$@c$<+$8?_qcr3GhcECO?j@b?e$Oxwc zA&#&AHtd_}4!e^m;`3CW9k?&^O;*6aL?FFb|3fqh<}Jg=B*FWYk>cEdYhWCV@943& zAaR3!e;8?T?tlH{@kV`3h-5;oOV(`O2!FB@$I?QC4 zzuEfou)lwYsM@oDTg&^T(UwbM-Kq`J_Zj~Dl^uLy^} zeS~ToK5tJ!;*W*LF^7&|S*(xtW+~#C0H9Be;QmO2X0c(Kj4RN((4~^v!YS&L1Vrn-|0%K}dPyQ`)jHz8Y34|+R?{F-VW)GH>=S{+P9=*8kh^4zY8HYD~R0dt@;Y0|^&3M;+nhU`nxF~a!g7Y+e zbW@c`3vt#BSAdsjPr(LEUR?i3s6|Jb_h)$R^e?8C=m;*hk|_V(Q3OMB)0tZgBNdEA z0P4Gv?wla0o}pk`I9gO4kF}HjEjb^Ol|l$9QX!;J45LzsnODdmPGO5U7XQP1Qrx7lt#-h~R`Hq`^{sr)#7UQ3d>6+l z%95Y}N1_~}GEcjVaW>yKwq5jRyaFvrGEDR55IC%-sqG)S3u9YF84?wg2s2wng9xB9HoAxv`V7FRxvje3z$qUT$iM9Lq5y-QkN-miM-HdlENE_3P04L z{MWFL0#FG!E>lu@gwmJUC6mRW9u$QFk`)L^lO>>7l^%>fztlh}x|OWJL8>ALRibP; z%N#~iSsL6xSs*6`%p4L)Ss)@+o`4)#OoI59EDiObWO-vu!~?)Z{pEDBXS$bhN5M+L zMygg3k1SOjj7`b9I#6px%2W}B3|+LiGxp2y1Xw+g7STFjwvbPXA|9ElI2f6-fK7@b zRw;-ag?~}}^dOp+M1mp)*|IRWkfMM}3NClD4MHpux4A{M!_D7b-w)t(bf zQAjjd0hx4B2x>?{=po6zHA(@36qN@GONnxJ^l@m%;PADVk7o8)yrs5pQrXcYw zo1yVeD0V@xWVt4CWNL7hO>n18uqSDRZu6$eL^K1dq>y5wy;H10EXg9Vq;W8%5OQUF z@Evi%hXjQH^3@rVR6?*8Nx`#3IUn+POt2?$gbxH1O6ELKL8%1!L2Br1K4KP9X}&}` zMaL6=Gs7FcaJttgN(j}((O8szXC3(ULzaA1mQhc<&2x%{({<70(Qk}UxZZiIE1&6Q z89&)(zXZTXEBCcuP=nvd?FT!;wck>mA6A{;{iQm$uRBxG+lHOb*5|0|C*R_G9M#sR z_bhSm!90JUX-B8=?wRE9}x6v-4t5Nng&hOc8-zxgY z|9xPFo$a6H*V+V3tbZL3@&9|VejOn3zxn)U8thC=U#YUOu@n5ou`x0du(GoK%`*@% zGc*5v$G@Zuf6M+A`(G*iOZpf8CDkSPy9{M~@^Qye@()Vb!v<@}6J1 z!OD364O<~^f6{TU#kY=BvxevKxM4oM;_(nu&6c5sn;zqkorwfdf@?O>8PlZM)ya-^rp}cXV-(U9o8a$j z7#B^-zc+e7g+d_J<%pniQbQe+79MD&34f-f>qrLclKdg2%!(g&uV!@3xX=}J@TQ%y zrH+(ajBz{h&Q051g|Fz2UCV9Z;Mf=~7<@yhz~y=6vsIp=*UAcTjT@D1$l28zxXh_g zTcde>2iBp4HTi#mkL`bDLpBx$c20U36I(N9b4CItMn)C}j{h|3MGlmK%F^0Xp3`-X zPu`euVuqw?s3h4}hZ329P$1o!M$A~U4^$8t9SltnYkoQpfJa^x;W51T;Y_Fr~9OfrOCbw`TMR1XV z$Mw)L6nY+~;U9^OpOOcDMn7z}f+u?0ESV%@oWhI4$mO!u?XRjkR}!49(@*d|e}!f& z*{Qb5kZa;eqqgmqhYmwY->03-eTqhB`Zya67We8k&B>`?lQ&Fdtt;s4;rM<`VNQfU zrdqIyv-`T(n!pXu2zo45uT#n{)N-Gq6h7C8)ShF|*yw&}8f{8kkYnm+_U=TZ?QR6t zy``+yPlUe->u_+__f?}m@G&ZofdkVO`hxc2z4i#u=eX}@VRPQ zqs#tO6GjY;BiuBaK+Qy_tK4Om8?7TQVl~D4GfHun?x8F8j+o+@Mz3S9Itw2`*WZVp z4EynHEJ}`Fb*p4Kf#oNXi)jNguH$e&(k5LcEr{JNyA(0vr_>z?G}n}w^Ods z1^F-#zl=!h_1MJmX>3Z(+|2donFu*n4Mir4AH0(AVUJPyuw>r$=O0eFGaJbt4!77F z4vQiUYJ>?%krwE=k6!iqu{8R_wWPRME>A2#uvPB&Z@wjk%+zNC;oMEn`_?Qj34UG&N4L*#wP&KHl1JZUoGoB8O?i?Pk zM=%RbIyBGJSGTdZjGTcl&=2g?A7?$^&9x%$a8FuUIm=ivtztgtI=(%y7x;WTvlUzq zzOxyf5$rV_>l(91Gr2=86@N#Jb`rh2J+inrSKhvoJf&DAg`Q(6MlpD}xh1Fx)g8wx znpibDyEGSv0aByyzB+&seIr%VM z6Z>SVG$twPh?6l^>Lp^%C_rxzs4)&%9|do=IwRm-l!Pybc_D2d1AB=7o+*Y`j`0Fk zqKv&Lu4fJoa2hAUzw;dz;S=yRS6HBd7HFg__Z1_Q9ZUv`QGnzMADE>8Y$ppNnbW}q z4(I17;fWRmQ9^J9|Csfbg`tpt$ABy8pB0jgqQKXZ0h={cfy9M>50)y2)RG#SMPiY8 zCy!ZlMuiJYRitdn7AyZ0E@G|8Pnc!Qj4e@|bx;AXDRw+@$FBJZ+Ee*fQ^+`Li49W| z;49P20;hVo#yfLOG3hMx3CT&utlKFK7nUa*ZAh+g znb6DypEo6D@nEcJ=^q7IZ#R&Al`Y}G0739B<9Ky#u==wl?6m)QXT@P>*Em zLR>*_{&~LJtbBfN@%a+_iQG%Y3;7FLO+al~o}gdNsjf2!z_tU)6X6}lv##`MPImV7 z)YLrCHo#R#zl=@`VoRV^h<*O@%nAVXF65KKX7Td917uycH`un6yFi=8Cw^HNzfixb zZr1LU?&O+fOUg~Sb&hv-d!c(a?3C}swL{-01FrxSfUsE6BNAu%g6SfNUj}y;1pu`P z=95Ga$SahcEjwkOH7o!ALSqwpH4ETe2Yx5!1@+1Gt?Ch=7sxBaICYy%Gp7cate1Wy z=*k0m1=867-$6LlHeQovf?H%a_!&A>E^h1hX;Is5J3_t|91~j~|T$ZTk<6FEic0<2I zbc4U+d*Z&x%kmenFP5F?1IPf%o-i+VCrJRT%V1x*&V2pZjahou_sGX``B^?sh|2j+ zg`cJPAFS{D*MBlUR)X{v_)m;vKXGi3R>~>Mj zsaH>P>Gkq5U~5}r!OhC*#nwi4`#pyB{nD=Hw6lrTC#|LQ`US7{v<_}I^P^B>VJP#X zTYI7RdgXL`o3*>{-1%o|6zhBE3ScD8pEaj`FR^AQ(UwFr6_qz309ABuB9g}MeXNz{ zUcQ!A4CIqz(>#6@h;;zgGic750o!;=_+Ay@DLF4WoXZ2wcn?ci@v2WE&7}H*t1Pu8 z2PTdlW^aiJzfI$7&mjFGApf^t4dg>oj}1iqF~vaGlZXb=OWIs`0?X6G1PDBVqE!o; zGiJh;AJ?A*kfguY3JOe|E?ziQ!q(SJviQ7B$?nL3j-`P&{Kksc zqxrTIUyTLA3--4^ei6~(gjNclvJ-0~I){Brt47qvLc4MC4v_%I$Y4VmF&Fa=DIYf; zHWkV?5sn74qwQfL*{2@-c$O0M9%8PDuCpFf`K2ECnJLV{D&n2OMUv&0U-q}V7t9N% z78iteFxGb0C>bm%&T!`HmYk!E{D~pLM8=UZi5|6P%s64prZuV7xHMoy1M!1SG^)?j zJ4Osof6X1wAv3|R$7C^qE+k2|w8M7a#lj#R-ztkL=rRif3jbEGgAW!yh8AOAjR0bWaaxLSGU~Le`%)n z{Wj&w{~3Dejap61d&mdr$L(|E@q<{1AOD20%qcy0TVh4eHls4;BGlh{Zw6cwaY&G? z%gb$1`*?}m)qg|i zHiaO>{XSBG;LFp_aB~;l_2L);SfGFJ0%M zfuyAQ06Bu%xOb3ei-l;TiDMcy7C)Y)+Pvt!SgLQk8sX5sP@1O!J-(!`CNKeYMifp}!|)y?eR7Y-*~-nPYP zfgU+??VLMf!@3HSU7NXL=CIXGnX;GNky&ewEpA??Q^KBsez|~pf6u>_Dy25`pY{vh z$NEA_RF+@o*pHu{Uzi@-iW>Vx&oro4H*+jI0mv9Vhks`3&Va1<)>A3V*x4mZn>cmM z(6tTfw}5QZrAr&KaOC!O4I4}x&#DheswmlJ6!j)O2@-zhGg#lv8bW|4#linasgzy1 z{sM><(+9UHXt%0dd*~Z(!|Flv3(QRO3vXCtQf?4TZ>Jy8&URui`E~o$>}-u*e%^L~`~%ZqrEN)Wb-gZ;?^TeqjokW{PU z-LhuQ>h4aTg6yP7&A@^*erW(E8wZ2}Yi4LtfH4_m@a19Q=}Fytc3GU5GI+oe{+>Bw z^V0p6Hen-sC|m24RMGt3x1=MS4X zdJ;u6=T!1YIxjRnJ8_di(uaE4LA+{BT; zbm-($w@g|gynb%{5jC|SNeqgoq92h<*on{XlE9PVOh5+Jb#(`&#GDMaV=!km(}KA7 zt(Ea&V9rd)IF7IhHdpMyBl6YZR#;iU#N>q{$EZyFJ7d%VGKlJiqk$pp+zr zA`1?jJ-ZAGdG6f3gR=3;m^Y&q#^{+w4h|2Gk40hbag#CVrs*fCMf_9p)Dyl(aqS|N z+H=74?AA}E+I*jW^=Qc6ttRXj`1-qtkF@>?!8sX)ZZ=?lS&O1fclq{Avi(V z5RezJCsA~ggc?Wz!0Je$D|vSC&m>*l9z1?wD6YJv-hQ=yfRH%Uqnx4DdNq6ii2{zg znvMczFF>5`@CZK8&bD@tag;IhNgDrPECw}3HYq6X5-ESvomcUGQ6`j=dM7E-e%pS67M2Qbkg6h=M-l zAnv=Wq7q|evAv4q`^~PXp8J8auP`PU1%gN2YiGf-y_uHscUI z2GmQR$4%`Z{L0s$RQlP&>yiQ>^~&{JnVn*P?+ zF9=;~-!R6qdY#_0c&TD<>;MUKGCIf}g^zO-%Kh(V{dt015ISQ)SKPOkUrCB3R;VCb z@gsg)e?4k@#*T{7ZL{s{%;7Mp@|~r&smF7^O>${pOg)af)=`J^MTs&c6+I;tTr9cN zBWj^Ee)&Hq^5ve{~d`R zyZaBQu;5PPUlocdp)1mW1e4~P6!U;Rh2v{qvH9XmhY=ZQC0UQbVk67g3>9V03e#_a zbbt~^DtdZK`WY%}d3kzCJCW7~3CViz=U=;nVd4~tUm#zQ_y^=~r{-_;u#-G+^vGjJcOz|*Vw+(kRkJIl4RfvMjz^nRGo?v=TCS*u+d%K*SEDA!RZ zkr18oIW#%lZ*Oc2HLt z!xn~uZ~2E(>qCpyPM8l_Fw9(G|i1kpLBsKv97jInWzXZN$R<%*(GAqoHkETXu8HqH$9!LyP}p2F@gmHUba3H zZ0-+Kq#8L6SSHMpd8g!w6*9OD`!4C*+$`GebioE!?SM3HxVr>{uHsCs9UtKazpG9qNvK7W@#AS3qg5WBL(92Zf%_Y<9es z`$Wq-?eC1P5+6TlyQfr&>4TW{iOnpAVb4#_?9<$iAvCDek6sq~GmARA@a3;(ps2Oy z=8||DFT)^GyqAaFHe)&i$_peA#l~{bqgvE*I1xC;WwsB*zon(Wj_u1fE&STe>@FQ-971MhozQ4NN~Y2-duz^+FCVT< zYX+X293}g9aTOJ6g~XgpFQff=rN)-}co|3Hm&2%K8_0BTZl&=p(1-xjHth47CWkXH zdPi(5)Z9&QY-;SXZL^Or%SE)Sbt+ye-j#;lO2g-`Q?*tdBng?e2CNZF#;U$3p~ACi zicV`!vuxdEhE9t{lxwUsRyG$&nM#Wa+p9@I?S&%-bhjF{z2>e&jDaqJ2Uvs#X(r2$ zfGLMU*BaG1s;0X@TQRL12S3~*uq`F|94Eh=V6Kufy4od&XKdw#RlroExR)dw2C_}5 zn8!`1hS*7a*a2Y^4B41)2Xuo_)*%mPm6Z&|Z(4Eowr_I(e7j7sE`V3nR>ifXbsk-G)g#+qJ8EYW;mC883emK^BAt#b zhu}uoo{E)ApNgB4yf&#kH$&r2D(EV&B=18Xi?22lU2`*SNH;3y51n~Non^c^lO^Ng zgBYRbY8ujz86=mGtd{!5T zeA@c2s5I4GlAE%w!fZmu=54L*r0o{K=IMMV3-%4q4YUop4ZI9JEihVgR(~h{CJF

i5+%iadf}ACP1cyD4mkWF1wy%_|Fg9a!jEkjN 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 +- pinctrl-1 = <&ts_int_nopull &ts_reset_pullup>; +- pinctrl-2 = <&ts_int_pulldown &ts_reset_pulldown>; +- pinctrl-3 = <&ts_int_pulldown &ts_reset_pullup>; + + +Optional properties +------------------- + - goodix,charger-configx: chip configuration data used in charger mode, if you + hava enabled CONFIG_GTP_CHAGER_SWITCH, you need to add this property. + x stands for sendor ID. + - goodix,smartcover-configx: chip configuration data used in smartcover mode, if + you have enabled CONFIG_GTP_SMARTCOVER, you need to add this property. + +Example +------- +``` + i2c@f9927000 { /*Goodix BLSP1 QUP5 */ + goodix_ts@5d { + compatible = "goodix,gt1x"; + reg = <0x5d>; + interrupt-parent = <&msmgpio>; + interrupts = <17 0x2008>; + vdd_ana-supply = <&pm8226_l19>; + pinctrl-names = "pmx_ts_wakeup","pmx_ts_normal","pmx_ts_poweroff","pmx_ts_sleep"; + pinctrl-0 = <&ts_int_pullup &ts_reset_pullup>; + pinctrl-1 = <&ts_int_nopull &ts_reset_pullup>; + pinctrl-2 = <&ts_int_pulldown &ts_reset_pulldown>; + pinctrl-3 = <&ts_int_pulldown &ts_reset_pullup>; + goodix,reset-gpio = <&msmgpio 16 0x00>; + goodix,irq-gpio = <&msmgpio 17 0x00>; + goodix,default-config0 = [ + 5c 00 12 11 10 11 5f 00 cc bb + 22 00 11 00 00 00 00 00 00 00 + ... + ]; + goodix,charger-config2 = [ + 5f 00 12 11 10 11 5f 00 cc bb + 23 00 11 00 00 00 00 00 00 00 + ... + ]; + /* if you have disable CONFIG_GTP_INT_SEL_SYNC, + * please add properties below. + * You should config goodix_int_pull_up node in + * the pinctrl dtsi file */ + pinctrl-names = "default"; + pinctrl-0 = <&goodix_int_pull_up>; + +}; +``` diff --git a/drivers/input/touchscreen/gt1x_v1_6_revised/docs/Pingroup-for-Goodix-TP-in-pinctrl-dts-reference.txt b/drivers/input/touchscreen/gt1x_v1_6_revised/docs/Pingroup-for-Goodix-TP-in-pinctrl-dts-reference.txt new file mode 100755 index 000000000000..329b575bd7b4 --- /dev/null +++ b/drivers/input/touchscreen/gt1x_v1_6_revised/docs/Pingroup-for-Goodix-TP-in-pinctrl-dts-reference.txt @@ -0,0 +1,129 @@ + Drive Goodix TPIC without INT GPIO Output +======================================================== + +1.Background +------------ + +Goodix touch IC has a special mechanism that INT GPIO is used for I2C address +selection and GPIO synchronization during the hardware reset process. +The reason for designing this mechanism is to prevent i2c address conflict. +The main flow of this mechanism is as follows: + + reset pin output 0 -> INT pin output 1 if i2c address is 0x14, or output 0 if + i2c address is 0x5D -> reset pin output 1 -> delay and wait until hardware + address selection circuit initialization completes. -> INT pin output 0 to inform the + firmware that it can safely output INT pulse. + +But in kernal3.13 or later versions, the GPIO subsystem has restricted the output +of GPIO which had been tied to IRQ line. Therefore, we add pinctrl method to set pins' states. + +2.How-to +--------- + +If your kernel has the restriction on the output of GPIO tied to IRQ line, + follow the steps below: + + - Add pinctrl-names, pinctrl-x to TP dts as described at GT1x-dts-bindings.txt + - Add pingroup for Goodix touch as suggested below. + See below for example: + + ``` + i2c@f9927000 { /*Goodix BLSP1 QUP5 */ + goodix_ts@14 { + compatible = "goodix,gt1x"; + reg = <0x14>; + goodix,reset-gpio = <&msmgpio 16 0x00>; + goodix,irq-gpio = <&msmgpio 17 0x00>; + pinctrl-names = "pmx_ts_wakeup","pmx_ts_normal","pmx_ts_poweroff","pmx_ts_sleep"; + pinctrl-0 = <&ts_int_pullup &ts_reset_pullup>; + pinctrl-1 = <&ts_int_nopull &ts_reset_pullup>; + pinctrl-2 = <&ts_int_pulldown &ts_reset_pulldown>; + pinctrl-3 = <&ts_int_pulldown &ts_reset_pullup>; + + }; + + //e.g. in msm8937-pinctrl.dtsi + /* add pingrp for touchscreen */ + pmx_ts_int_pullup { + ts_int_pullup: ts_int_pullup { + mux { + pins = "gpio65"; + function = "gpio"; + }; + + config { + pins = "gpio65"; + drive-strength = <8>; + bias-pull-up; + }; + }; + }; + + pmx_ts_int_nopull { + ts_int_nopull: ts_int_nopull { + mux { + pins = "gpio65"; + function = "gpio"; + }; + + config { + pins = "gpio65"; + drive-strength = <2>; + bias-disable; + }; + }; + }; + + pmx_ts_int_pulldown { + ts_int_pulldown: ts_int_pulldown { + mux { + pins = "gpio65"; + function = "gpio"; + }; + + config { + pins = "gpio65"; + drive-strength = <2>; + bias-pull-down; + }; + }; + }; + + pmx_ts_reset_pullup { + ts_reset_pullup: ts_reset_pullup { + mux { + pins = "gpio64"; + function = "gpio"; + }; + + config { + pins = "gpio64"; + drive-strength = <8>; + bias-pull-up; + }; + }; + }; + + pmx_ts_reset_pulldown { + ts_reset_pulldown: ts_reset_pulldown { + mux { + pins = "gpio64"; + function = "gpio"; + }; + + config { + pins = "gpio64"; + drive-strength = <2>; + bias-pull-down; + }; + }; + }; + + ``` + + + + + + + diff --git a/drivers/input/touchscreen/gt1x_v1_6_revised/docs/RevisionLog.txt b/drivers/input/touchscreen/gt1x_v1_6_revised/docs/RevisionLog.txt new file mode 100755 index 000000000000..1b999d4a754a --- /dev/null +++ b/drivers/input/touchscreen/gt1x_v1_6_revised/docs/RevisionLog.txt @@ -0,0 +1,44 @@ +Goodix GT1x series Driver - Android platform +============================================ + +Revision Information +-------------------- +v1.6+ 2017/12/12 + -Add pinctrl method to set the pins' states. + -Modify the risk whitch read or write user point directly. + +V1.6 2016/11/02 + - Move macros to Kconfig + - Support extended configuration data. + - Using request_firmware to obtain chip firmware and hotknot auth-firmwrae. + - Add fallback flow to probe function. + - Move configuration data to devictreee node. + - Using threaded IRQ to do the bottom half work. + - Fix issue of coordinates report when using Input typeB protocol. + +v1.4 2015/07/10 + - Free resource of gpio and regulator when module removed. + - Add gesture debug command to proc/gt1x_debug + - Modify chip reset function to support GT2x + - Make wake-up process simplified. + - Adjustment for gt1x_init. + - Calculate checksum of gesture package. + - Fixed bugs in hotknot ioctl function. + - Support incell touch ic + - Double check firmware update status ret + +v1.2 2015/3/28 + - Add device tree support. + - Add mutex lock for gt1x_send_cfg + - Calculate checksum of coordinates package + - Add P-sensor module for non-mtk platform + - Add Smartcover module + - Move suspend/resume functions into gt1x_generic.c + - Fixed some bugs + - New rules for firmware patch id + - Add a thread to run GTP Tools firmware upgrade function. + - Add LCD notify callback. + - 64bit kernel compatibility + +v1.0 2014/11/28 + - First Release. diff --git a/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x.c b/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x.c new file mode 100755 index 000000000000..2b625dbf4cc5 --- /dev/null +++ b/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x.c @@ -0,0 +1,1090 @@ +/* drivers/input/touchscreen/gt1x.c + * + * 2010 - 2017 Goodix Technology. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + * Version: 1.6 + */ + +#include "gt1x_generic.h" +#ifdef CONFIG_GTP_TYPE_B_PROTOCOL +#include +#endif + +static struct input_dev *input_dev; +static spinlock_t irq_lock; +static int irq_disabled; + +struct goodix_pinctrl *gt_pinctrl; + +#ifdef CONFIG_OF +static struct regulator *vdd_ana =NULL; +static struct regulator *vcc_i2c =NULL; +int gt1x_rst_gpio; +int gt1x_pwr_gpio; +int gt1x_int_gpio; +int gt1x_pwrio_gpio; +#endif + +#if defined(CONFIG_DRM_PANEL) +static struct drm_panel *active_panel; +#endif + +static int gt1x_register_powermanger(void); +static int gt1x_unregister_powermanger(void); + +/** + * gt1x_i2c_write - i2c write. + * @addr: register address. + * @buffer: data buffer. + * @len: the bytes of data to write. + *Return: 0: success, otherwise: failed + */ +s32 gt1x_i2c_write(u16 addr, u8 * buffer, s32 len) +{ + struct i2c_msg msg = { + .flags = 0, + .addr = gt1x_i2c_client->addr, + }; + return _do_i2c_write(&msg, addr, buffer, len); +} + +/** + * gt1x_i2c_read - i2c read. + * @addr: register address. + * @buffer: data buffer. + * @len: the bytes of data to write. + *Return: 0: success, otherwise: failed + */ +s32 gt1x_i2c_read(u16 addr, u8 * buffer, s32 len) +{ + u8 addr_buf[GTP_ADDR_LENGTH] = { (addr >> 8) & 0xFF, addr & 0xFF }; + struct i2c_msg msgs[2] = { + { + .addr = gt1x_i2c_client->addr, + .flags = 0, + .buf = addr_buf, + .len = GTP_ADDR_LENGTH}, + { + .addr = gt1x_i2c_client->addr, + .flags = I2C_M_RD} + }; + return _do_i2c_read(msgs, addr, buffer, len); +} + +/** + * gt1x_irq_enable - enable irq function. + * + */ +void gt1x_irq_enable(void) +{ + unsigned long irqflags = 0; + //GTP_ERROR("gt1x_irq_enable Enter\n"); + spin_lock_irqsave(&irq_lock, irqflags); + if (irq_disabled) { + irq_disabled = 0; + spin_unlock_irqrestore(&irq_lock, irqflags); + enable_irq(gt1x_i2c_client->irq); + } else { + spin_unlock_irqrestore(&irq_lock, irqflags); + } + //GTP_ERROR("gt1x_irq_enable Leave\n"); +} + +/** + * gt1x_irq_enable - disable irq function. + * disable irq and wait bottom half + * thread(gt1x_ts_work_thread) + */ +void gt1x_irq_disable(void) +{ + unsigned long irqflags; + + /* because there is an irq enable action in + * the bottom half thread, we need to wait until + * bottom half thread finished. + */ + //GTP_ERROR("gt1x_irq_disable Enter\n"); + + synchronize_irq(gt1x_i2c_client->irq); + spin_lock_irqsave(&irq_lock, irqflags); + if (!irq_disabled) { + irq_disabled = 1; + spin_unlock_irqrestore(&irq_lock, irqflags); + disable_irq(gt1x_i2c_client->irq); + } else { + spin_unlock_irqrestore(&irq_lock, irqflags); + } + //GTP_ERROR("gt1x_irq_disable Leave\n"); +} + +#ifndef CONFIG_OF +int gt1x_power_switch(s32 state) +{ + return 0; +} +#endif + +int gt1x_debug_proc(u8 * buf, int count) +{ + return -1; +} + +#ifdef CONFIG_GTP_CHARGER_SWITCH +u32 gt1x_get_charger_status(void) +{ + /* + * Need to get charger status of + * your platform. + */ + return 0; +} +#endif + +/** + * gt1x_ts_irq_handler - External interrupt service routine + * for interrupt mode. + * @irq: interrupt number. + * @dev_id: private data pointer. + * Return: Handle Result. + * IRQ_WAKE_THREAD: top half work finished, + * wake up bottom half thread to continue the rest work. + */ +static irqreturn_t gt1x_ts_irq_handler(int irq, void *dev_id) +{ + unsigned long irqflags; + + /* irq top half, use nosync irq api to + * disable irq line, if irq is enabled, + * then wake up bottom half thread */ + spin_lock_irqsave(&irq_lock, irqflags); + if (!irq_disabled) { + irq_disabled = 1; + spin_unlock_irqrestore(&irq_lock, irqflags); + disable_irq_nosync(gt1x_i2c_client->irq); + return IRQ_WAKE_THREAD; + } else { + spin_unlock_irqrestore(&irq_lock, irqflags); + return IRQ_HANDLED; + } +} + +/** + * gt1x_touch_down - Report touch point event . + * @id: trackId + * @x: input x coordinate + * @y: input y coordinate + * @w: input pressure + * Return: none. + */ +void gt1x_touch_down(s32 x, s32 y, s32 size, s32 id) +{ +#ifdef CONFIG_GTP_CHANGE_X2Y + GTP_SWAP(x, y); +#endif + + input_report_key(input_dev, BTN_TOUCH, 1); +#ifdef CONFIG_GTP_TYPE_B_PROTOCOL + input_mt_slot(input_dev, id); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, true); +#else + input_report_abs(input_dev, ABS_MT_TRACKING_ID, id); +#endif + + input_report_abs(input_dev, ABS_MT_POSITION_X, x); + input_report_abs(input_dev, ABS_MT_POSITION_Y, y); + input_report_abs(input_dev, ABS_MT_PRESSURE, size); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, size); + +#ifndef CONFIG_GTP_TYPE_B_PROTOCOL + input_mt_sync(input_dev); +#endif +} + +/** + * gt1x_touch_up - Report touch release event. + * @id: trackId + * Return: none. + */ +void gt1x_touch_up(s32 id) +{ +#ifdef CONFIG_GTP_TYPE_B_PROTOCOL + input_mt_slot(input_dev, id); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false); +#else + input_mt_sync(input_dev); +#endif +} + +/** + * gt1x_ts_work_thread - Goodix touchscreen work function. + * @iwork: work struct of gt1x_workqueue. + * Return: none. + */ +static irqreturn_t gt1x_ts_work_thread(int irq, void *data) +{ + u8 point_data[11] = { 0 }; + u8 end_cmd = 0; + u8 finger = 0; + s32 ret = 0; + + if (update_info.status) { + GTP_DEBUG("Ignore interrupts during fw update."); + return IRQ_HANDLED; + } + +#ifdef CONFIG_GTP_GESTURE_WAKEUP + ret = gesture_event_handler(input_dev); + if (ret >= 0) + goto exit_work_func; +#endif + + if (gt1x_halt) { + GTP_DEBUG("Ignore interrupts after suspend"); + return IRQ_HANDLED; + } + + ret = gt1x_i2c_read(GTP_READ_COOR_ADDR, point_data, sizeof(point_data)); + if (ret < 0) { + GTP_ERROR("I2C transfer error!"); +#ifndef CONFIG_GTP_ESD_PROTECT + gt1x_power_reset(); +#endif + goto exit_work_func; + } + + finger = point_data[0]; + if (finger == 0x00) + gt1x_request_event_handler(); + + if ((finger & 0x80) == 0) { +#ifdef CONFIG_HOTKNOT_BLOCK_RW + if (!hotknot_paired_flag) +#endif + { + goto exit_eint; + } + } + +#ifdef CONFIG_HOTKNOT_BLOCK_RW + ret = hotknot_event_handler(point_data); + if (!ret) + goto exit_work_func; +#endif + +#ifdef CONFIG_GTP_PROXIMITY + ret = gt1x_prox_event_handler(point_data); + if (ret > 0) + goto exit_work_func; +#endif + +#ifdef CONFIG_GTP_WITH_STYLUS + ret = gt1x_touch_event_handler(point_data, input_dev, pen_dev); +#else + ret = gt1x_touch_event_handler(point_data, input_dev, NULL); +#endif + +exit_work_func: + if (!gt1x_rawdiff_mode && (ret >= 0 || ret == ERROR_VALUE)) { + ret = gt1x_i2c_write(GTP_READ_COOR_ADDR, &end_cmd, 1); + if (ret < 0) + GTP_ERROR("I2C write end_cmd error!"); + } +exit_eint: + gt1x_irq_enable(); + return IRQ_HANDLED; +} + +/* + * Devices Tree support, +*/ +#ifdef CONFIG_OF +/** + * gt1x_parse_dt - parse platform infomation form devices tree. + */ +static int gt1x_parse_dt(struct device *dev) +{ + struct device_node *np; + int ret = 0; + + if (!dev) + return -ENODEV; + + np = dev->of_node; + gt1x_int_gpio = of_get_named_gpio(np, "goodix,irq-gpio", 0); + gt1x_rst_gpio = of_get_named_gpio(np, "goodix,rst-gpio", 0); + gt1x_pwr_gpio = of_get_named_gpio(np, "goodix,pwr-gpio", 0); + gt1x_pwrio_gpio = of_get_named_gpio(np, "goodix,pwr-gpioio", 0); + + if (!gpio_is_valid(gt1x_int_gpio) || !gpio_is_valid(gt1x_rst_gpio))// ||!gpio_is_valid(gt1x_pwr_gpio)) + { + GTP_ERROR("Invalid GPIO, irq-gpio:%d, rst-gpio:%d", + gt1x_int_gpio, gt1x_rst_gpio); + return -EINVAL; + } + + vdd_ana = regulator_get(dev, "vdd_ana"); + if (IS_ERR(vdd_ana)) { + GTP_ERROR("regulator get of vdd_ana failed"); + ret = PTR_ERR(vdd_ana); + vdd_ana = NULL; + //return ret; + } + else + {//disable first + ret = regulator_disable(vdd_ana); + if (ret) { + GTP_ERROR("Failed to disable VDD33 vdd_ana\n"); + } + regulator_set_load(vdd_ana,0); + } + vcc_i2c = regulator_get(dev, "vcc_i2c"); + if (IS_ERR(vcc_i2c)) { + GTP_ERROR("regulator get of vcc_i2c failed"); + ret = PTR_ERR(vcc_i2c); + vcc_i2c = NULL; + // return ret; + } + + return 0; +} + +/** + * goodix_pinctrl_init - pinctrl init + */ +static int goodix_pinctrl_init(struct i2c_client *client) +{ + int ret = 0; + + if(gt_pinctrl == NULL){ + gt_pinctrl =(struct goodix_pinctrl*)kmalloc(sizeof(struct goodix_pinctrl),GFP_KERNEL); + if (!gt_pinctrl) + return -ENOMEM; + } + + gt_pinctrl->ts_pinctrl = devm_pinctrl_get(&client->dev); + if (IS_ERR_OR_NULL(gt_pinctrl->ts_pinctrl)) { + GTP_ERROR("Failed to get pinctrl"); + ret = PTR_ERR(gt_pinctrl->ts_pinctrl); + gt_pinctrl->ts_pinctrl= NULL; + return ret; + } + + gt_pinctrl->pinctrl_wakeup = pinctrl_lookup_state(gt_pinctrl->ts_pinctrl, "pmx_ts_wakeup"); + if (IS_ERR_OR_NULL(gt_pinctrl->pinctrl_wakeup)) { + GTP_ERROR("Pin state[wakeup] not found"); + ret = PTR_ERR(gt_pinctrl->pinctrl_wakeup); + goto exit_put; + } + + gt_pinctrl->pinctrl_normal = pinctrl_lookup_state(gt_pinctrl->ts_pinctrl, "pmx_ts_normal"); + if (IS_ERR_OR_NULL(gt_pinctrl->pinctrl_normal)) { + GTP_ERROR("Pin state[normal] not found"); + ret = PTR_ERR(gt_pinctrl->pinctrl_normal); + } + + gt_pinctrl->pinctrl_poweroff = pinctrl_lookup_state(gt_pinctrl->ts_pinctrl, "pmx_ts_poweroff"); + if (IS_ERR_OR_NULL(gt_pinctrl->pinctrl_poweroff)) { + GTP_ERROR("Pin state[poweroff] not found"); + ret = PTR_ERR(gt_pinctrl->pinctrl_poweroff); + goto exit_put; + } + + gt_pinctrl->pinctrl_sleep = pinctrl_lookup_state(gt_pinctrl->ts_pinctrl, "pmx_ts_sleep"); + if (IS_ERR_OR_NULL(gt_pinctrl->pinctrl_sleep)) { + GTP_ERROR("Pin state[sleep] not found"); + ret = PTR_ERR(gt_pinctrl->pinctrl_sleep); + goto exit_put; + } + + return 0; +exit_put: + devm_pinctrl_put(gt_pinctrl->ts_pinctrl); + gt_pinctrl->ts_pinctrl = NULL; + gt_pinctrl->pinctrl_wakeup = NULL; + gt_pinctrl->pinctrl_normal = NULL; + gt_pinctrl->pinctrl_poweroff = NULL; + gt_pinctrl->pinctrl_sleep = NULL; + return ret; +} + + +/** + * gt1x_power_switch - power switch . + * @on: 1-switch on, 0-switch off. + * return: 0-succeed, -1-faileds + */ +int gt1x_power_switch(int on) +{ + //static int vdd_count = 0; + int ret; + struct i2c_client *client = gt1x_i2c_client; + + if (!client)// || !vdd_ana) + return -1; + GTP_ERROR("gt1x_power_switch on=%d,by eric.wang\n",on); + if (on) { + GTP_DEBUG("gt1x_power_switch power on."); + GTP_GPIO_OUTPUT(gt1x_pwr_gpio,1); + GTP_GPIO_OUTPUT(gt1x_pwrio_gpio,1);//by eric.wang + if(vdd_ana!=NULL) + { + ret = regulator_enable(vdd_ana); + //vdd_count++; + } + } else { + GTP_DEBUG("gt1x_power_switch power off."); + GTP_GPIO_OUTPUT(gt1x_pwr_gpio,0); + GTP_GPIO_OUTPUT(gt1x_pwrio_gpio,0); + if(vdd_ana!=NULL) + { + ret = regulator_disable(vdd_ana); + regulator_set_load(vdd_ana,0); + //vdd_count--; + } + + } + + usleep_range(10000, 10100); + return ret; + +} + +int gt1x_vcc_i2c_switch(int on) +{ + + int ret; + struct i2c_client *client = gt1x_i2c_client; + + if (!client )//|| !vcc_i2c) + return -1; + GTP_ERROR("gt1x_vcc_i2c_switch on=%d,by eric.wang\n",on); + if (on) { + + GTP_GPIO_OUTPUT(gt1x_pwrio_gpio,1); + GTP_DEBUG("gt1x_vcc_i2c_switch power on."); + if(vcc_i2c!=NULL) + ret = regulator_enable(vcc_i2c); + } else { + GTP_GPIO_OUTPUT(gt1x_pwrio_gpio,0); + GTP_DEBUG("gt1x_vcc_i2c_switch power off."); + if(vcc_i2c!=NULL) + ret = regulator_disable(vcc_i2c); + } + + usleep_range(10000, 10100); + return ret; + +} + +#endif + +static void gt1x_release_resource(void) +{ + if (gpio_is_valid(GTP_INT_PORT)) { + gpio_direction_input(GTP_INT_PORT); + gpio_free(GTP_INT_PORT); + } + + if (gpio_is_valid(GTP_RST_PORT)) { + gpio_direction_output(GTP_RST_PORT, 0); + gpio_free(GTP_RST_PORT); + } + +#ifdef CONFIG_OF + if (vdd_ana) { + gt1x_power_switch(SWITCH_OFF); + regulator_put(vdd_ana); + vdd_ana = NULL; + } +#endif + + if (gt_pinctrl->ts_pinctrl) + devm_pinctrl_put(gt_pinctrl->ts_pinctrl); + gt_pinctrl->ts_pinctrl = NULL; + gt_pinctrl->pinctrl_wakeup = NULL; + gt_pinctrl->pinctrl_normal = NULL; + gt_pinctrl->pinctrl_poweroff = NULL; + gt_pinctrl->pinctrl_sleep = NULL; + + if (input_dev) { + input_unregister_device(input_dev); + input_dev = NULL; + } +} + +/** + * gt1x_request_gpio - Request gpio(INT & RST) ports. + */ +static s32 gt1x_request_gpio(void) +{ + s32 ret = 0; + + ret = gpio_request(GTP_INT_PORT, "GTP_INT_IRQ"); + if (ret < 0) { + GTP_ERROR("Failed to request GPIO:%d, ERRNO:%d", (s32) GTP_INT_PORT, ret); + ret = -ENODEV; + } else { + GTP_GPIO_AS_INT(GTP_INT_PORT); + gt1x_i2c_client->irq = gpio_to_irq(GTP_INT_PORT); + } + + ret = gpio_request(GTP_RST_PORT, "GTP_RST_PORT"); + if (ret < 0) { + GTP_ERROR("Failed to request GPIO:%d, ERRNO:%d", (s32) GTP_RST_PORT, ret); + ret = -ENODEV; + } + + GTP_GPIO_AS_INPUT(GTP_RST_PORT); + + ret = gpio_request(gt1x_pwr_gpio, "gt1x_pwr_gpio"); + if (ret < 0) { + GTP_ERROR("Failed to request gt1x_pwr_gpio:%d, ERRNO:%d,by eric.wang\n", (s32) gt1x_pwr_gpio, ret); + ret = -ENODEV; + } + + ret = gpio_request(gt1x_pwrio_gpio, "gt1x_pwrio_gpio"); + if (ret < 0) { + GTP_ERROR("Failed to request gt1x_pwrio_gpio:%d, ERRNO:%d,by eric.wang\n", (s32) gt1x_pwrio_gpio, ret); + ret = -ENODEV; + } + + return ret; +} + +/** + * gt1x_request_irq - Request interrupt. + * Return + * 0: succeed, -1: failed. + */ +static s32 gt1x_request_irq(void) +{ + s32 ret = -1; + const u8 irq_table[] = GTP_IRQ_TAB; + + GTP_DEBUG("INT trigger type:%x", gt1x_int_type); + ret = devm_request_threaded_irq(>1x_i2c_client->dev, + gt1x_i2c_client->irq, + gt1x_ts_irq_handler, + gt1x_ts_work_thread, + irq_table[gt1x_int_type], + gt1x_i2c_client->name, + gt1x_i2c_client); + if (ret) { + GTP_ERROR("Request IRQ failed!ERRNO:%d.", ret); + return -1; + } else { + gt1x_irq_disable(); + return 0; + } +} + +/** + * gt1x_request_input_dev - Request input device Function. + * Return + * 0: succeed, -1: failed. + */ +static s8 gt1x_request_input_dev(void) +{ + s8 ret = -1; +#ifdef CONFIG_GTP_HAVE_TOUCH_KEY + u8 index = 0; +#endif + + input_dev = input_allocate_device(); + if (input_dev == NULL) { + GTP_ERROR("Failed to allocate input device."); + return -ENOMEM; + } + + input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); +#ifdef CONFIG_GTP_TYPE_B_PROTOCOL +#if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 7, 0)) + input_mt_init_slots(input_dev, GTP_MAX_TOUCH, INPUT_MT_DIRECT); +#else + input_mt_init_slots(input_dev, GTP_MAX_TOUCH); +#endif +#endif + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + +#ifdef CONFIG_GTP_HAVE_TOUCH_KEY + for (index = 0; index < GTP_MAX_KEY_NUM; index++) + input_set_capability(input_dev, EV_KEY, gt1x_touch_key_array[index]); +#endif + +#ifdef CONFIG_GTP_GESTURE_WAKEUP + input_set_capability(input_dev, EV_KEY, KEY_GES_REGULAR); + input_set_capability(input_dev, EV_KEY, KEY_GES_CUSTOM); +#endif + +#ifdef CONFIG_GTP_CHANGE_X2Y + input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, gt1x_abs_y_max, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, gt1x_abs_x_max, 0, 0); +#else + input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, gt1x_abs_x_max, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, gt1x_abs_y_max, 0, 0); +#endif + input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 255, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, 255, 0, 0); + + input_dev->name = "goodix-ts"; + input_dev->phys = "input/ts"; + input_dev->id.bustype = BUS_I2C; + input_dev->id.vendor = 0xDEAD; + input_dev->id.product = 0xBEEF; + input_dev->id.version = 10427; + + ret = input_register_device(input_dev); + if (ret) { + GTP_ERROR("Register %s input device failed", input_dev->name); + return -ENODEV; + } + + return 0; +} + +#if defined(CONFIG_DRM_PANEL) +static int gtp_ts_check_dt(struct device_node *np) +{ + int i; + int count; + struct device_node *node; + struct drm_panel *panel; + + count = of_count_phandle_with_args(np, "panel", NULL); + if (count <= 0) + return 0; + + for (i = 0; i < count; i++) { + node = of_parse_phandle(np, "panel", i); + panel = of_drm_find_panel(node); + of_node_put(node); + if (!IS_ERR(panel)) { + active_panel = panel; + GT_LOG(" %s:find\n", __func__); + return 0; + } + } + + GT_ERR(" %s: not find\n", __func__); + return -ENODEV; +} + +static int gtp_ts_check_default_tp(struct device_node *dt, const char *prop) +{ + const char **active_tp = NULL; + int count, tmp, score = 0; + const char *active; + int ret, i; + + count = of_property_count_strings(dt->parent, prop); + if (count <= 0 || count > 3) + return -ENODEV; + + active_tp = kcalloc(count, sizeof(char *), GFP_KERNEL); + if (!active_tp) { + GT_ERR("FTS alloc failed\n"); + return -ENOMEM; + } + + ret = of_property_read_string_array(dt->parent, prop, + active_tp, count); + if (ret < 0) { + GT_ERR("fail to read %s %d\n", prop, ret); + ret = -ENODEV; + goto out; + } + + for (i = 0; i < count; i++) { + active = active_tp[i]; + if (active != NULL) { + tmp = of_device_is_compatible(dt, active); + if (tmp > 0) + score++; + } + } + + if (score <= 0) { + GT_ERR("not match this driver\n"); + ret = -ENODEV; + goto out; + } + ret = 0; +out: + kfree(active_tp); + return ret; +} +#endif + +/** + * gt1x_ts_probe - I2c probe. + * @client: i2c device struct. + * @id: device id. + * Return 0: succeed, <0: failed. + */ +static int gt1x_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int ret = -1; +#if defined(CONFIG_DRM) + struct device_node *dp = client->dev.of_node; +#endif + + //GT_LOG("start\n"); + +#if defined(CONFIG_DRM) + if (gtp_ts_check_dt(dp)) { + if (!gtp_ts_check_default_tp(dp, "qcom,i2c-touch-active")) + ret = -EPROBE_DEFER; + else + ret = -ENODEV; + + return ret; + } +#endif + + /* do NOT remove these logs */ + GTP_INFO("GTP Driver Version: %s,slave addr:%02xh", + GTP_DRIVER_VERSION, client->addr); + + gt1x_i2c_client = client; + spin_lock_init(&irq_lock); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + GTP_ERROR("I2C check functionality failed."); + return -ENODEV; + } + +#ifdef CONFIG_OF /* device tree support */ + if (client->dev.of_node) { + ret = gt1x_parse_dt(&client->dev); + if (ret < 0) + return -EINVAL; + } +#else +#error [GOODIX]only support devicetree platform +#endif + + /* init pinctrl states */ + ret = goodix_pinctrl_init(client); + if (ret < 0) { + GTP_ERROR("Init pinctrl states failed."); + goto exit_clean; + } + + /* Set pin state as poweroff */ + if ( gt_pinctrl->ts_pinctrl && gt_pinctrl->pinctrl_poweroff) + ret = pinctrl_select_state(gt_pinctrl->ts_pinctrl, gt_pinctrl->pinctrl_poweroff); + if (ret < 0) { + GTP_ERROR("Set pin state as poweroff error: %d", ret); + goto exit_clean; + } + + /* gpio resource */ + ret = gt1x_request_gpio(); + if (ret < 0) { + GTP_ERROR("GTP request IO port failed."); + goto exit_clean; + } + + /* power on */ + ret = gt1x_power_switch(SWITCH_ON); + if (ret < 0) { + GTP_ERROR("Power on failed"); + goto exit_clean; + } + + /* reset ic & do i2c test */ + ret = gt1x_reset_guitar(); + if (ret != 0) { + ret = gt1x_power_switch(SWITCH_OFF); + if (ret < 0) + goto exit_clean; + ret = gt1x_power_switch(SWITCH_ON); + if (ret < 0) + goto exit_clean; + ret = gt1x_reset_guitar(); /* retry */ + if (ret != 0) { + GTP_ERROR("Reset guitar failed!"); + goto exit_clean; + } + } + + /* check firmware, initialize and send + * chip configuration data, initialize nodes */ + gt1x_init(); + + ret = gt1x_request_input_dev(); + if (ret < 0) + goto err_input; + + ret = gt1x_request_irq(); + if (ret < 0) + goto err_irq; + +#ifdef CONFIG_GTP_ESD_PROTECT + /* must before auto update */ + gt1x_init_esd_protect(); + gt1x_esd_switch(SWITCH_ON); +#endif + +#ifdef CONFIG_GTP_AUTO_UPDATE + do { + struct task_struct *thread = NULL; + thread = kthread_run(gt1x_auto_update_proc, + (void *)NULL, + "gt1x_auto_update"); + if (IS_ERR(thread)) + GTP_ERROR("Failed to create auto-update thread: %d.", ret); + } while (0); +#endif + + gt1x_register_powermanger(); + gt1x_irq_enable(); + return 0; + +err_irq: +err_input: + gt1x_deinit(); +exit_clean: + gt1x_release_resource(); + GTP_ERROR("GTP probe failed:%d", ret); + return -ENODEV; +} + +/** + * gt1x_ts_remove - Goodix touchscreen driver release function. + * @client: i2c device struct. + * Return 0: succeed, -1: failed. + */ +static int gt1x_ts_remove(struct i2c_client *client) +{ + GTP_INFO("GTP driver removing..."); + gt1x_unregister_powermanger(); + + gt1x_deinit(); + gt1x_release_resource(); + + return 0; +} + +#if defined(CONFIG_DRM_PANEL) +static struct notifier_block gt1x_drm_notifier; + +static int gtp_drm_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct drm_panel_notifier *evdata = data; + int *blank; + //struct nvt_ts_data *ts = + // container_of(self, struct nvt_ts_data, drm_notif); + + if (!evdata || !evdata->data ) + return 0; + + blank = evdata->data; + + if (event == DRM_PANEL_EARLY_EVENT_BLANK) { + if (*blank == DRM_PANEL_BLANK_POWERDOWN) { + GT_LOG("event=%lu, *blank=%d\n", event, *blank); + gt1x_suspend(); + } + } else if (event == DRM_PANEL_EVENT_BLANK) { + if (*blank == DRM_PANEL_BLANK_UNBLANK) { + GT_LOG("event=%lu, *blank=%d\n", event, *blank); + gt1x_resume(); + } + } + + return 0; +} + +#elif defined(CONFIG_FB) +/* frame buffer notifier block control the suspend/resume procedure */ +static struct notifier_block gt1x_fb_notifier; + +static int gtp_fb_notifier_callback(struct notifier_block *noti, unsigned long event, void *data) +{ + struct fb_event *ev_data = data; + int *blank; + +#ifdef CONFIG_GTP_INCELL_PANEL +#ifndef FB_EARLY_EVENT_BLANK + #error Need add FB_EARLY_EVENT_BLANK to fbmem.c +#endif + + if (ev_data && ev_data->data && event == FB_EARLY_EVENT_BLANK) { + blank = ev_data->data; + if (*blank == FB_BLANK_UNBLANK) { + GTP_DEBUG("Resume by fb notifier."); + gt1x_resume(); + } + } +#else + if (ev_data && ev_data->data && event == FB_EVENT_BLANK) { + blank = ev_data->data; + if (*blank == FB_BLANK_UNBLANK) { + GTP_DEBUG("Resume by fb notifier."); + gt1x_resume(); + } + } +#endif + + if (ev_data && ev_data->data && event == FB_EVENT_BLANK) { + blank = ev_data->data; + if (*blank == FB_BLANK_POWERDOWN) { + GTP_DEBUG("Suspend by fb notifier."); + gt1x_suspend(); + } + } + + return 0; +} +#elif defined(CONFIG_PM) +/** + * gt1x_ts_suspend - i2c suspend callback function. + * @dev: i2c device. + * Return 0: succeed, -1: failed. + */ +static int gt1x_pm_suspend(struct device *dev) +{ + return gt1x_suspend(); +} + +/** + * gt1x_ts_resume - i2c resume callback function. + * @dev: i2c device. + * Return 0: succeed, -1: failed. + */ +static int gt1x_pm_resume(struct device *dev) +{ + return gt1x_resume(); +} + +/* bus control the suspend/resume procedure */ +static const struct dev_pm_ops gt1x_ts_pm_ops = { + .suspend = gt1x_pm_suspend, + .resume = gt1x_pm_resume, +}; + +#elif defined(CONFIG_HAS_EARLYSUSPEND) +/* earlysuspend module the suspend/resume procedure */ +static void gt1x_ts_early_suspend(struct early_suspend *h) +{ + gt1x_suspend(); +} + +static void gt1x_ts_late_resume(struct early_suspend *h) +{ + gt1x_resume(); +} + +static struct early_suspend gt1x_early_suspend = { + .level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1, + .suspend = gt1x_ts_early_suspend, + .resume = gt1x_ts_late_resume, +}; +#endif + + +static int gt1x_register_powermanger(void) +{ +#if defined(CONFIG_DRM) + gt1x_drm_notifier.notifier_call = gtp_drm_notifier_callback; + if (active_panel && + drm_panel_notifier_register(active_panel, + >1x_drm_notifier) < 0) { + GT_ERR("register notifier failed!\n"); + //goto err_register_drm_notif_failed; + } + +#elif defined(CONFIG_FB) + gt1x_fb_notifier.notifier_call = gtp_fb_notifier_callback; + fb_register_client(>1x_fb_notifier); + +#elif defined(CONFIG_HAS_EARLYSUSPEND) + register_early_suspend(>1x_early_suspend); +#endif + return 0; +} + +static int gt1x_unregister_powermanger(void) +{ +#if defined(CONFIG_DRM_PANEL) + if (active_panel) + drm_panel_notifier_unregister(active_panel, >1x_drm_notifier); + +#elif defined(CONFIG_FB) + fb_unregister_client(>1x_fb_notifier); + +#elif defined(CONFIG_HAS_EARLYSUSPEND) + unregister_early_suspend(>1x_early_suspend); +#endif + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id gt1x_match_table[] = { + {.compatible = "goodix,gt1x",}, + { }, +}; +#endif + +static const struct i2c_device_id gt1x_ts_id[] = { + {GTP_I2C_NAME, 0}, + {} +}; + +static struct i2c_driver gt1x_ts_driver = { + .probe = gt1x_ts_probe, + .remove = gt1x_ts_remove, + .id_table = gt1x_ts_id, + .driver = { + .name = GTP_I2C_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = gt1x_match_table, +#endif +#if !defined(CONFIG_FB) && defined(CONFIG_PM) + .pm = >1x_ts_pm_ops, +#endif + }, +}; + +/** + * gt1x_ts_init - Driver Install function. + * Return 0---succeed. + */ +static int __init gt1x_ts_init(void) +{ + GTP_INFO("GTP driver installing..."); + return i2c_add_driver(>1x_ts_driver); +} + +/** + * gt1x_ts_exit - Driver uninstall function. + * Return 0---succeed. + */ +static void __exit gt1x_ts_exit(void) +{ + GTP_DEBUG_FUNC(); + GTP_INFO("GTP driver exited."); + i2c_del_driver(>1x_ts_driver); +} + +module_init(gt1x_ts_init); +module_exit(gt1x_ts_exit); + +MODULE_DESCRIPTION("GTP Series Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_extents.c b/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_extents.c new file mode 100755 index 000000000000..e222d27c9b3a --- /dev/null +++ b/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_extents.c @@ -0,0 +1,1005 @@ +/* drivers/input/touchscreen/gt1x_extents.c + * + * 2010 - 2017 Goodix Technology. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + * Version: 1.6 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include /*proc */ + +#include +#include "gt1x_generic.h" + +#ifdef CONFIG_GTP_GESTURE_WAKEUP + +#define GESTURE_NODE "goodix_gesture" +#define GESTURE_MAX_POINT_COUNT 64 + +#pragma pack(1) +typedef struct { + u8 ic_msg[6]; /*from the first byte */ + u8 gestures[4]; + u8 data[3 + GESTURE_MAX_POINT_COUNT * 4 + 80]; /*80 bytes for extra data */ +} st_gesture_data; +#pragma pack() + +#define SETBIT(longlong, bit) (longlong[bit/8] |= (1 << bit%8)) +#define CLEARBIT(longlong, bit) (longlong[bit/8] &=(~(1 << bit%8))) +#define QUERYBIT(longlong, bit) (!!(longlong[bit/8] & (1 << bit%8))) + +#define CHKBITS_32 32 +#define CHKBITS_16 16 +#define CHKBITS_8 8 + +int gesture_enabled = 1; /* module switch */ +DOZE_T gesture_doze_status = DOZE_DISABLED;//DOZE_DISABLED; /* doze status */ + +static u8 gestures_flag[32]; /* gesture flag, every bit stands for a gesture */ +static st_gesture_data gesture_data; /* gesture data buffer */ +static struct mutex gesture_data_mutex; /* lock for gesture data */ + +static ssize_t gt1x_gesture_data_read(struct file *file, char __user * page, size_t size, loff_t * ppos) +{ + s32 ret = -1; + GTP_DEBUG("visit gt1x_gesture_data_read. ppos:%d", (int)*ppos); + if (*ppos) { + return 0; + } + if (size == 4) { + ret = copy_to_user(((u8 __user *) page), "GT1X", 4); + return 4; + } + ret = simple_read_from_buffer(page, size, ppos, &gesture_data, sizeof(gesture_data)); + + GTP_DEBUG("Got the gesture data."); + return ret; +} + +static ssize_t gt1x_gesture_data_write(struct file *filp, const char __user * buff, size_t len, loff_t * off) +{ + s32 ret = 0; + + GTP_DEBUG_FUNC(); + + ret = copy_from_user(&gesture_enabled, buff, 1); + if (ret) { + GTP_ERROR("copy_from_user failed."); + return -EPERM; + } + + GTP_DEBUG("gesture enabled:%x, ret:%d", gesture_enabled, ret); + + return len; +} + +/** + * calc_checksum - Calc checksum. + * @buf: data to be calc + * @len: length of buf. + * @bits: checkbits + * Return true-pass, false:not pass. + */ +static bool calc_checksum(u8 *buf, int len, int bits) +{ + int i; + + if (bits == CHKBITS_16) { + u16 chksum, *b = (u16 *)buf; + + if (len % 2) { + return false; + } + + len /= 2; + for (i = 0, chksum = 0; i < len; i++) { + if (i == len - 1) + chksum += le16_to_cpu(b[i]); + else + chksum += be16_to_cpu(b[i]); + } + return chksum == 0 ? true : false; + } else if (bits == CHKBITS_8) { + u8 chksum; + + for (i = 0, chksum =0; i < len; i++) { + chksum += buf[i]; + } + return chksum == 0 ? true : false; + } + + return false; +} + +int gesture_enter_doze(void) +{ + int retry = 0; + + GTP_DEBUG_FUNC(); + GTP_DEBUG("Entering doze mode..."); + while (retry++ < 5) { + if (!gt1x_send_cmd(0x08, 0)) { + gesture_doze_status = DOZE_ENABLED; + GTP_DEBUG("Working in doze mode!"); + return 0; + } + msleep(10); + } + GTP_ERROR("Send doze cmd failed."); + return -1; +} + +s32 gesture_event_handler(struct input_dev * dev) +{ + u8 doze_buf[4] = { 0 }, ges_type; + static int err_flag1 = 0, err_flag2 = 0; + int len, extra_len, need_chk; + unsigned int key_code; + s32 ret = 0; + + if (DOZE_ENABLED != gesture_doze_status) { + return -1; + } + + /** package: -head 4B + track points + extra info- + * - head - + * doze_buf[0]: gesture type, + * doze_buf[1]: number of gesture points , + * doze_buf[2]: protocol type, + * doze_buf[3]: gesture extra data length. + */ + ret = gt1x_i2c_read(GTP_REG_WAKEUP_GESTURE, doze_buf, 4); + if (ret < 0) { + return 0; + } + + ges_type = doze_buf[0]; + len = doze_buf[1]; + need_chk = doze_buf[2] & 0x80; + extra_len = doze_buf[3]; + + GTP_DEBUG("0x%x = 0x%02X,0x%02X,0x%02X,0x%02X", GTP_REG_WAKEUP_GESTURE, + doze_buf[0], doze_buf[1], doze_buf[2], doze_buf[3]); + + if (len > GESTURE_MAX_POINT_COUNT) { + GTP_ERROR("Gesture contain too many points!(%d)", len); + len = GESTURE_MAX_POINT_COUNT; + } + + if (extra_len > 32) { + GTP_ERROR("Gesture contain too many extra data!(%d)", extra_len); + extra_len = 32; + } + + /* get gesture extra info */ + if (extra_len >= 0) { + u8 ges_data[extra_len + 1]; + + /* head 4 + extra data * 4 + chksum 1 */ + ret = gt1x_i2c_read(GTP_REG_WAKEUP_GESTURE + 4, + ges_data, extra_len + 1); + if (ret < 0) { + GTP_ERROR("Read extra gesture data failed."); + return 0; + } + + if (likely(need_chk)) { /* calc checksum */ + bool val; + + ges_data[extra_len] += doze_buf[0] + doze_buf[1] + + doze_buf[2] + doze_buf[3]; + + val = calc_checksum(ges_data, extra_len + 1, CHKBITS_8); + if (unlikely(!val)) { /* check failed */ + GTP_ERROR("Gesture checksum error."); + if (err_flag1) { + err_flag1 = 0; + ret = 0; + goto clear_reg; + } else { + /* just return 0 without clear reg, + this will receive another int, we + check the data in the next frame */ + err_flag1 = 1; + return 0; + } + } + + err_flag1 = 0; + } + + mutex_lock(&gesture_data_mutex); + memcpy(&gesture_data.data[4 + len * 4], ges_data, extra_len); + mutex_unlock(&gesture_data_mutex); + } + + /* check gesture type (if available?) */ + if (ges_type == 0 || !QUERYBIT(gestures_flag, ges_type)) { + GTP_ERROR("Gesture[0x%02X] has been disabled.", doze_buf[0]); + doze_buf[0] = 0x00; + gt1x_i2c_write(GTP_REG_WAKEUP_GESTURE, doze_buf, 1); + gesture_enter_doze(); + return 0; + } + + /* get gesture point data */ + if (len > 0) { /* coor num * 4 + chksum 2*/ + u8 ges_data[len * 4 + 2]; + + ret = gt1x_i2c_read(GES_BUFFER_ADDR, ges_data, len * 4); + if (ret < 0) { + GTP_ERROR("Read gesture data failed."); + return 0; + } + + /* checksum reg for gesture point data */ + ret = gt1x_i2c_read(0x819F, &ges_data[len * 4], 2); + if (ret < 0) { + GTP_ERROR("Read gesture data failed."); + return 0; + } + + if (likely(need_chk)) { + bool val = calc_checksum(ges_data, + len * 4 + 2, CHKBITS_16); + if (unlikely(!val)) { /* check failed */ + GTP_ERROR("Gesture checksum error."); + if (err_flag2) { + err_flag2 = 0; + ret = 0; + goto clear_reg; + } else { + err_flag2 = 1; + return 0; + } + } + + err_flag2 = 0; + } + + mutex_lock(&gesture_data_mutex); + memcpy(&gesture_data.data[4], ges_data, len * 4); + mutex_unlock(&gesture_data_mutex); + } + + mutex_lock(&gesture_data_mutex); + gesture_data.data[0] = ges_type; // gesture type + gesture_data.data[1] = len; // gesture points number + gesture_data.data[2] = doze_buf[2] & 0x7F; // protocol type + gesture_data.data[3] = extra_len; // gesture date length + mutex_unlock(&gesture_data_mutex); + + /* get key code */ + key_code = ges_type < 16? KEY_GES_CUSTOM : KEY_GES_REGULAR; + GTP_DEBUG("Gesture: 0x%02X, points: %d", doze_buf[0], doze_buf[1]); + + input_report_key(dev, key_code, 1); + input_sync(dev); + input_report_key(dev, key_code, 0); + input_sync(dev); + +clear_reg: + doze_buf[0] = 0; // clear ges flag + gt1x_i2c_write(GTP_REG_WAKEUP_GESTURE, doze_buf, 1); + return ret; +} + +void gesture_clear_wakeup_data(void) +{ + mutex_lock(&gesture_data_mutex); + memset(gesture_data.data, 0, 4); + mutex_unlock(&gesture_data_mutex); +} + +void gt1x_gesture_debug(int on) +{ + if (on) { + gesture_enabled = 1; + memset(gestures_flag, 0xFF, sizeof(gestures_flag)); + } else { + gesture_enabled = 0; + memset(gestures_flag, 0x00, sizeof(gestures_flag)); + gesture_doze_status = DOZE_DISABLED; + } + GTP_DEBUG("Gesture debug %s", on ? "on":"off"); +} + +#endif // GTP_GESTURE_WAKEUP + +//HotKnot module +#ifdef CONFIG_GTP_HOTKNOT +#include +#define HOTKNOT_NODE "hotknot" +#define HOTKNOT_VERSION "GOODIX,GT1X" +#define HN_AUTH_HEADER 14 +#define GT1X_HN_JUMP_FW "gt1x_hotknot_jump_fw.bin" +#define GT1X_HN_AUTH_FW "gt1x_hotknot_auth_fw.bin" +#define GT2X_HN_AUTH_FW "gt2x_hotknot_auth_fw.bin" + +u8 hotknot_enabled = 0; +u8 hotknot_transfer_mode = 0; + +static int hotknot_open(struct inode *node, struct file *flip) +{ + GTP_DEBUG("Hotknot is enabled."); + hotknot_enabled = 1; + return 0; +} + +static int hotknot_release(struct inode *node, struct file *filp) +{ + GTP_DEBUG("Hotknot is disabled."); + hotknot_enabled = 0; + return 0; +} + +static s32 hotknot_enter_transfer_mode(void) +{ + int ret = 0; + u8 buffer[5] = { 0 }; + + hotknot_transfer_mode = 1; +#ifdef CONFIG_GTP_ESD_PROTECT + gt1x_esd_switch(SWITCH_OFF); +#endif + + gt1x_irq_disable(); + gt1x_send_cmd(GTP_CMD_HN_TRANSFER, 0); + msleep(100); + gt1x_irq_enable(); + + ret = gt1x_i2c_read(0x8140, buffer, sizeof(buffer)); + if (ret) { + hotknot_transfer_mode = 0; + return ret; + } + + buffer[4] = 0; + GTP_DEBUG("enter transfer mode: %s ", buffer); + if (strcmp(buffer, "GHot")) { + hotknot_transfer_mode = 0; + return ERROR_HN_VER; + } + + return 0; +} + +static s32 hotknot_load_hotknot_subsystem(void) +{ + return hotknot_enter_transfer_mode(); +} + +static int gt1x_load_auth_subsystem(void) +{ + const struct firmware *jump_fw, *auth_fw; + int ret; + + GTP_INFO("Request %s", GT1X_HN_JUMP_FW); + ret = request_firmware(&jump_fw, GT1X_HN_JUMP_FW, + >1x_i2c_client->dev); + if (ret < 0) { + GTP_ERROR("Failed to get %s", GT1X_HN_JUMP_FW); + return ret; + } else if (jump_fw->size != SZ_4K) { + ret = -EINVAL; + GTP_ERROR("Firmware size not match"); + goto out1; + } + + GTP_INFO("Request %s", GT1X_HN_AUTH_FW); + ret = request_firmware(&auth_fw, GT1X_HN_AUTH_FW, + >1x_i2c_client->dev); + if (ret < 0) { + GTP_ERROR("Failed to get %s", GT1X_HN_AUTH_FW); + goto out1; + } else if (auth_fw->size != SZ_4K + HN_AUTH_HEADER) { + ret = -EINVAL; + GTP_ERROR("Firmware size not match"); + goto out0; + } + + GTP_INFO("hotknot load jump code."); + ret = gt1x_load_patch((u8 *)jump_fw->data, SZ_4K, 0, SZ_8K); + if (ret < 0) { + GTP_ERROR("Load jump code fail!"); + goto out0; + } + + GTP_INFO("hotknot load auth code."); + ret = gt1x_load_patch((u8 *)&auth_fw->data[HN_AUTH_HEADER], + SZ_4K, 4096, SZ_8K); + if (ret < 0) { + GTP_ERROR("Load auth system fail!"); + goto out0; + } + +out0: + release_firmware(auth_fw); +out1: + release_firmware(jump_fw); + return ret; +} + +static int gt2x_load_auth_subsystem(void) +{ + const struct firmware *auth_fw; + int ret; + + GTP_INFO("Request %s", GT2X_HN_AUTH_FW); + ret = request_firmware(&auth_fw, GT2X_HN_AUTH_FW, + >1x_i2c_client->dev); + if (ret < 0) { + GTP_ERROR("Failed to get %s", GT2X_HN_AUTH_FW); + return ret; + } else if (auth_fw->size != SZ_4K + HN_AUTH_HEADER) { + ret = -EINVAL; + GTP_ERROR("Firmware size not match"); + goto out0; + } + + GTP_INFO("hotknot load auth code."); + ret = gt1x_load_patch((u8 *)&auth_fw->data[HN_AUTH_HEADER], + SZ_4K, 0, SZ_1K * 6); + if (ret < 0) + GTP_ERROR("Load auth system fail!"); + +out0: + release_firmware(auth_fw); + return ret; +} + +static s32 hotknot_load_authentication_subsystem(void) +{ + s32 ret = 0; + u8 buffer[5] = { 0 }; + + ret = gt1x_hold_ss51_dsp_no_reset(); + if (ret < 0) { + GTP_ERROR("Hold ss51 fail!"); + return ERROR; + } + + if (gt1x_chip_type == CHIP_TYPE_GT1X) + ret = gt1x_load_auth_subsystem(); + else /* GT2X */ + ret = gt2x_load_auth_subsystem(); + + if (ret < 0) + return ret; + + ret = gt1x_startup_patch(); + if (ret < 0) { + GTP_ERROR("Startup auth system fail!"); + return ret; + } + + ret = gt1x_i2c_read(GTP_REG_VERSION, buffer, 4); + if (ret < 0) { + GTP_ERROR("i2c read error!"); + return ERROR_IIC; + } + buffer[4] = 0; + GTP_INFO("Current System version: %s", buffer); + return 0; +} + +static s32 hotknot_recovery_main_system(void) +{ + gt1x_irq_disable(); + gt1x_reset_guitar(); + gt1x_irq_enable(); +#ifdef CONFIG_GTP_ESD_PROTECT + gt1x_esd_switch(SWITCH_ON); +#endif + hotknot_transfer_mode = 0; + return 0; +} + +#ifdef CONFIG_HOTKNOT_BLOCK_RW +DECLARE_WAIT_QUEUE_HEAD(bp_waiter); +static u8 got_hotknot_state = 0; +static u8 got_hotknot_extra_state = 0; +static u8 wait_hotknot_state = 0; +static u8 force_wake_flag = 0; +static u8 block_enable = 0; +s32 hotknot_paired_flag = 0; + +static s32 hotknot_block_rw(u8 rqst_hotknot_state, s32 wait_hotknot_timeout) +{ + s32 ret = 0; + + wait_hotknot_state |= rqst_hotknot_state; + GTP_DEBUG("Goodix tool received wait polling state:0x%x,timeout:%d, all wait state:0x%x", rqst_hotknot_state, wait_hotknot_timeout, wait_hotknot_state); + got_hotknot_state &= (~rqst_hotknot_state); + + set_current_state(TASK_INTERRUPTIBLE); + if (wait_hotknot_timeout <= 0) { + wait_event_interruptible(bp_waiter, force_wake_flag || rqst_hotknot_state == (got_hotknot_state & rqst_hotknot_state)); + } else { + wait_event_interruptible_timeout(bp_waiter, force_wake_flag || rqst_hotknot_state == (got_hotknot_state & rqst_hotknot_state), wait_hotknot_timeout); + } + + wait_hotknot_state &= (~rqst_hotknot_state); + + if (rqst_hotknot_state != (got_hotknot_state & rqst_hotknot_state)) { + GTP_ERROR("Wait 0x%x block polling waiter failed.", rqst_hotknot_state); + ret = -1; + } + if(force_wake_flag) { + got_hotknot_extra_state = 0x07; // force wakeup report as departed + } + force_wake_flag = 0; + return ret; +} + +void hotknot_wakeup_block(void) +{ + GTP_DEBUG("Manual wakeup all block polling waiter!"); + got_hotknot_state = 0; + wait_hotknot_state = 0; + force_wake_flag = 1; + wake_up_interruptible(&bp_waiter); +} + +s32 hotknot_event_handler(u8 * data) +{ + u8 hn_pxy_state = 0; + u8 hn_pxy_state_bak = 0; + static u8 hn_paired_cnt = 0; + u8 hn_state_buf[10] = { 0 }; + u8 finger = data[0]; + u8 id = 0; + + if (block_enable && !hotknot_paired_flag && (finger & 0x0F)) { + id = data[1]; + hn_pxy_state = data[2] & 0x80; + hn_pxy_state_bak = data[3] & 0x80; + if ((32 == id) && (0x80 == hn_pxy_state) && (0x80 == hn_pxy_state_bak)) { +#ifdef HN_DBLCFM_PAIRED + if (hn_paired_cnt++ < 2) { + return 0; + } +#endif + GTP_DEBUG("HotKnot paired!"); + if (wait_hotknot_state & HN_DEVICE_PAIRED) { + GTP_DEBUG("INT wakeup HN_DEVICE_PAIRED block polling waiter"); + got_hotknot_state |= HN_DEVICE_PAIRED; + wake_up_interruptible(&bp_waiter); + } + block_enable = 0; + hotknot_paired_flag = 1; + return 0; + } else { + got_hotknot_state &= (~HN_DEVICE_PAIRED); + hn_paired_cnt = 0; + } + } + + if (hotknot_paired_flag) { + s32 ret = -1; + ret = gt1x_i2c_read(GTP_REG_HN_STATE, hn_state_buf, 6); + if (ret < 0) { + GTP_ERROR("I2C transfer error. errno:%d\n ", ret); + return 0; + } + + got_hotknot_state = 0; + + GTP_DEBUG("wait_hotknot_state:%x", wait_hotknot_state); + GTP_DEBUG("[0x8800~0x8803]=0x%x,0x%x,0x%x,0x%x", hn_state_buf[0], hn_state_buf[1], hn_state_buf[2], hn_state_buf[3]); + + if (wait_hotknot_state & HN_MASTER_SEND) { + if ((0x03 == hn_state_buf[0]) || (0x04 == hn_state_buf[0]) + || (0x07 == hn_state_buf[0])) { + GTP_DEBUG("Wakeup HN_MASTER_SEND block polling waiter"); + got_hotknot_state |= HN_MASTER_SEND; + got_hotknot_extra_state = hn_state_buf[0]; + wake_up_interruptible(&bp_waiter); + } + } else if (wait_hotknot_state & HN_SLAVE_RECEIVED) { + if ((0x03 == hn_state_buf[1]) || (0x04 == hn_state_buf[1]) + || (0x07 == hn_state_buf[1])) { + GTP_DEBUG("Wakeup HN_SLAVE_RECEIVED block polling waiter:0x%x", hn_state_buf[1]); + got_hotknot_state |= HN_SLAVE_RECEIVED; + got_hotknot_extra_state = hn_state_buf[1]; + wake_up_interruptible(&bp_waiter); + } + } else if (wait_hotknot_state & HN_MASTER_DEPARTED) { + if (0x07 == hn_state_buf[0]) { + GTP_DEBUG("Wakeup HN_MASTER_DEPARTED block polling waiter"); + got_hotknot_state |= HN_MASTER_DEPARTED; + wake_up_interruptible(&bp_waiter); + } + } else if (wait_hotknot_state & HN_SLAVE_DEPARTED) { + if (0x07 == hn_state_buf[1]) { + GTP_DEBUG("Wakeup HN_SLAVE_DEPARTED block polling waiter"); + got_hotknot_state |= HN_SLAVE_DEPARTED; + wake_up_interruptible(&bp_waiter); + } + } + return 0; + } + + return -1; +} +#endif //HOTKNOT_BLOCK_RW +#endif //GTP_HOTKNOT + +#define GOODIX_MAGIC_NUMBER 'G' +#define NEGLECT_SIZE_MASK (~(_IOC_SIZEMASK << _IOC_SIZESHIFT)) + +#define GESTURE_ENABLE _IO(GOODIX_MAGIC_NUMBER, 1) // 1 +#define GESTURE_DISABLE _IO(GOODIX_MAGIC_NUMBER, 2) +#define GESTURE_FLAG_SET _IO(GOODIX_MAGIC_NUMBER, 3) +#define GESTURE_FLAG_CLEAR _IO(GOODIX_MAGIC_NUMBER, 4) +//#define SET_ENABLED_GESTURE (_IOW(GOODIX_MAGIC_NUMBER, 5, u8) & NEGLECT_SIZE_MASK) +#define GESTURE_DATA_OBTAIN (_IOR(GOODIX_MAGIC_NUMBER, 6, u8) & NEGLECT_SIZE_MASK) +#define GESTURE_DATA_ERASE _IO(GOODIX_MAGIC_NUMBER, 7) + +//#define HOTKNOT_LOAD_SUBSYSTEM (_IOW(GOODIX_MAGIC_NUMBER, 6, u8) & NEGLECT_SIZE_MASK) +#define HOTKNOT_LOAD_HOTKNOT _IO(GOODIX_MAGIC_NUMBER, 20) +#define HOTKNOT_LOAD_AUTHENTICATION _IO(GOODIX_MAGIC_NUMBER, 21) +#define HOTKNOT_RECOVERY_MAIN _IO(GOODIX_MAGIC_NUMBER, 22) +//#define HOTKNOT_BLOCK_RW (_IOW(GOODIX_MAGIC_NUMBER, 6, u8) & NEGLECT_SIZE_MASK) +#define HOTKNOT_DEVICES_PAIRED _IO(GOODIX_MAGIC_NUMBER, 23) +#define HOTKNOT_MASTER_SEND _IO(GOODIX_MAGIC_NUMBER, 24) +#define HOTKNOT_SLAVE_RECEIVE _IO(GOODIX_MAGIC_NUMBER, 25) +//#define HOTKNOT_DEVICES_COMMUNICATION +#define HOTKNOT_MASTER_DEPARTED _IO(GOODIX_MAGIC_NUMBER, 26) +#define HOTKNOT_SLAVE_DEPARTED _IO(GOODIX_MAGIC_NUMBER, 27) +#define HOTKNOT_VENDOR_VERSION (_IOR(GOODIX_MAGIC_NUMBER, 28, u8) & NEGLECT_SIZE_MASK) +#define HOTKNOT_WAKEUP_BLOCK _IO(GOODIX_MAGIC_NUMBER, 29) + +#define IO_IIC_READ (_IOR(GOODIX_MAGIC_NUMBER, 100, u8) & NEGLECT_SIZE_MASK) +#define IO_IIC_WRITE (_IOW(GOODIX_MAGIC_NUMBER, 101, u8) & NEGLECT_SIZE_MASK) +#define IO_RESET_GUITAR _IO(GOODIX_MAGIC_NUMBER, 102) +#define IO_DISABLE_IRQ _IO(GOODIX_MAGIC_NUMBER, 103) +#define IO_ENABLE_IRQ _IO(GOODIX_MAGIC_NUMBER, 104) +#define IO_GET_VERISON (_IOR(GOODIX_MAGIC_NUMBER, 110, u8) & NEGLECT_SIZE_MASK) +#define IO_PRINT (_IOW(GOODIX_MAGIC_NUMBER, 111, u8) & NEGLECT_SIZE_MASK) +#define IO_VERSION "V1.3-20150420" + +#define CMD_HEAD_LENGTH 20 +static s32 io_iic_read(u8 * data, void __user * arg) +{ + s32 err = ERROR; + s32 data_length = 0; + u16 addr = 0; + + err = copy_from_user(data, arg, CMD_HEAD_LENGTH); + if (err) { + GTP_ERROR("Can't access the memory."); + return ERROR_MEM; + } + + addr = data[0] << 8 | data[1]; + data_length = data[2] << 8 | data[3]; + + err = gt1x_i2c_read(addr, &data[CMD_HEAD_LENGTH], data_length); + if (!err) { + err = copy_to_user(&((u8 __user *) arg)[CMD_HEAD_LENGTH], &data[CMD_HEAD_LENGTH], data_length); + if (err) { + GTP_ERROR("ERROR when copy to user.[addr: %04x], [read length:%d]", addr, data_length); + return ERROR_MEM; + } + err = CMD_HEAD_LENGTH + data_length; + } + //GTP_DEBUG("IIC_READ.addr:0x%4x, length:%d, ret:%d", addr, data_length, err); + //GTP_DEBUG_ARRAY((&data[CMD_HEAD_LENGTH]), data_length); + + return err; +} + +static s32 io_iic_write(u8 * data) +{ + s32 err = ERROR; + s32 data_length = 0; + u16 addr = 0; + + addr = data[0] << 8 | data[1]; + data_length = data[2] << 8 | data[3]; + + err = gt1x_i2c_write(addr, &data[CMD_HEAD_LENGTH], data_length); + if (!err) { + err = CMD_HEAD_LENGTH + data_length; + } + + //GTP_DEBUG("IIC_WRITE.addr:0x%4x, length:%d, ret:%d", addr, data_length, err); + //GTP_DEBUG_ARRAY((&data[CMD_HEAD_LENGTH]), data_length); + return err; +} + +//@return, 0:operate successfully +// > 0: the length of memory size ioctl has accessed, +// error otherwise. +static long gt1x_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + u32 value = 0; + s32 ret = 0; //the initial value must be 0 + u8 *data = NULL; + int cnt = 30; + + /* Blocking when firmwaer updating */ + while (cnt-- && update_info.status) { + ssleep(1); + } + + //GTP_DEBUG("IOCTL CMD:%x", cmd); + /* GTP_DEBUG("command:%d, length:%d, rw:%s", + _IOC_NR(cmd), + _IOC_SIZE(cmd), + (_IOC_DIR(cmd) & _IOC_READ) ? "read" : (_IOC_DIR(cmd) & _IOC_WRITE) ? "write" : "-"); + */ + + if (_IOC_DIR(cmd)) { + s32 err = -1; + s32 data_length = _IOC_SIZE(cmd); + data = (u8 *) kzalloc(data_length, GFP_KERNEL); + memset(data, 0, data_length); + + if (_IOC_DIR(cmd) & _IOC_WRITE) { + err = copy_from_user(data, (void __user *)arg, data_length); + if (err) { + GTP_ERROR("Can't access the memory."); + kfree(data); + return -1; + } + } + } else { + value = (u32) arg; + } + + switch (cmd & NEGLECT_SIZE_MASK) { + case IO_GET_VERISON: + if ((u8 __user *) arg) { + ret = copy_to_user(((u8 __user *) arg), IO_VERSION, sizeof(IO_VERSION)); + if (!ret) { + ret = sizeof(IO_VERSION); + } + GTP_INFO("%s", IO_VERSION); + } + break; + case IO_IIC_READ: + ret = io_iic_read(data, (void __user *)arg); + break; + + case IO_IIC_WRITE: + ret = io_iic_write(data); + break; + + case IO_RESET_GUITAR: + gt1x_irq_disable(); + gt1x_reset_guitar(); + gt1x_irq_enable(); + break; + + case IO_DISABLE_IRQ: + gt1x_irq_disable(); +#ifdef CONFIG_GTP_ESD_PROTECT + gt1x_esd_switch(SWITCH_OFF); +#endif + break; + + case IO_ENABLE_IRQ: + gt1x_irq_enable(); +#ifdef CONFIG_GTP_ESD_PROTECT + gt1x_esd_switch(SWITCH_ON); +#endif + break; + + //print a string to syc log messages between application and kernel. + case IO_PRINT: + if (data) + GTP_INFO("%s", (char *)data); + break; + +#ifdef CONFIG_GTP_GESTURE_WAKEUP + case GESTURE_ENABLE: + GTP_DEBUG("Gesture switch ON."); + gesture_enabled = 1; + break; + + case GESTURE_DISABLE: + GTP_DEBUG("Gesture switch OFF."); + gesture_enabled = 0; + break; + + case GESTURE_FLAG_SET: + SETBIT(gestures_flag, (u8) value); + GTP_DEBUG("Gesture flag: 0x%02X enabled.", value); + break; + + case GESTURE_FLAG_CLEAR: + CLEARBIT(gestures_flag, (u8) value); + GTP_DEBUG("Gesture flag: 0x%02X disabled.", value); + break; + + case GESTURE_DATA_OBTAIN: + GTP_DEBUG("Obtain gesture data."); + mutex_lock(&gesture_data_mutex); + ret = copy_to_user(((u8 __user *) arg), &gesture_data.data, 4 + gesture_data.data[1] * 4 + gesture_data.data[3]); + if (ret) { + GTP_ERROR("ERROR when copy gesture data to user."); + ret = ERROR_MEM; + } else { + ret = 4 + gesture_data.data[1] * 4 + gesture_data.data[3]; + } + mutex_unlock(&gesture_data_mutex); + break; + + case GESTURE_DATA_ERASE: + GTP_DEBUG("ERASE_GESTURE_DATA"); + gesture_clear_wakeup_data(); + break; +#endif // GTP_GESTURE_WAKEUP + +#ifdef CONFIG_GTP_HOTKNOT + case HOTKNOT_VENDOR_VERSION: + ret = copy_to_user(((u8 __user *) arg), HOTKNOT_VERSION, sizeof(HOTKNOT_VERSION)); + if (!ret) { + ret = sizeof(HOTKNOT_VERSION); + } + break; + case HOTKNOT_LOAD_HOTKNOT: + ret = hotknot_load_hotknot_subsystem(); + break; + + case HOTKNOT_LOAD_AUTHENTICATION: +#ifdef CONFIG_GTP_ESD_PROTECT + gt1x_esd_switch(SWITCH_OFF); +#endif + ret = hotknot_load_authentication_subsystem(); + break; + + case HOTKNOT_RECOVERY_MAIN: + ret = hotknot_recovery_main_system(); + break; +#ifdef CONFIG_HOTKNOT_BLOCK_RW + case HOTKNOT_DEVICES_PAIRED: + hotknot_paired_flag = 0; + force_wake_flag = 0; + block_enable = 1; + ret = hotknot_block_rw(HN_DEVICE_PAIRED, (s32) value); + break; + + case HOTKNOT_MASTER_SEND: + ret = hotknot_block_rw(HN_MASTER_SEND, (s32) value); + if (!ret) + ret = got_hotknot_extra_state; + break; + + case HOTKNOT_SLAVE_RECEIVE: + ret = hotknot_block_rw(HN_SLAVE_RECEIVED, (s32) value); + if (!ret) + ret = got_hotknot_extra_state; + break; + + case HOTKNOT_MASTER_DEPARTED: + ret = hotknot_block_rw(HN_MASTER_DEPARTED, (s32) value); + break; + + case HOTKNOT_SLAVE_DEPARTED: + ret = hotknot_block_rw(HN_SLAVE_DEPARTED, (s32) value); + break; + + case HOTKNOT_WAKEUP_BLOCK: + hotknot_wakeup_block(); + break; +#endif //HOTKNOT_BLOCK_RW +#endif //GTP_HOTKNOT + + default: + GTP_INFO("Unknown cmd."); + ret = -1; + break; + } + + if (data != NULL) { + kfree(data); + } + + return ret; +} + +#ifdef CONFIG_COMPAT +static long gt1x_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *arg32 = compat_ptr(arg); + + if (!file->f_op || !file->f_op->unlocked_ioctl) + return -ENOTTY; + + return file->f_op->unlocked_ioctl(file, cmd, (unsigned long)arg32); +} +#endif + +static const struct file_operations gt1x_fops = { + .owner = THIS_MODULE, +#ifdef CONFIG_GTP_GESTURE_WAKEUP + .read = gt1x_gesture_data_read, + .write = gt1x_gesture_data_write, +#endif + .unlocked_ioctl = gt1x_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = gt1x_compat_ioctl, +#endif +}; + +#ifdef CONFIG_GTP_HOTKNOT +static const struct file_operations hotknot_fops = { + .open = hotknot_open, + .release = hotknot_release, + .unlocked_ioctl = gt1x_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = gt1x_compat_ioctl, +#endif +}; + +static struct miscdevice hotknot_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = HOTKNOT_NODE, + .fops = &hotknot_fops, +}; +#endif + +s32 gt1x_init_node(void) +{ +#ifdef CONFIG_GTP_GESTURE_WAKEUP + struct proc_dir_entry *proc_entry = NULL; + mutex_init(&gesture_data_mutex); + + memset(gestures_flag, 0xFF, sizeof(gestures_flag)); + memset((u8 *) & gesture_data, 0, sizeof(st_gesture_data)); + + proc_entry = proc_create(GESTURE_NODE, 0666, NULL, >1x_fops); + if (proc_entry == NULL) { + GTP_ERROR("CAN't create proc entry /proc/%s.", GESTURE_NODE); + return -1; + } else { + GTP_INFO("Created proc entry /proc/%s.", GESTURE_NODE); + } +#endif + +#ifdef CONFIG_GTP_HOTKNOT + if (misc_register(&hotknot_misc_device)) { + GTP_ERROR("CAN't create misc device in /dev/hotknot."); + return -1; + } else { + GTP_INFO("Created misc device in /dev/hotknot."); + } +#endif + return 0; +} + +void gt1x_deinit_node(void) +{ +#ifdef CONFIG_GTP_GESTURE_WAKEUP + remove_proc_entry(GESTURE_NODE, NULL); +#endif + +#ifdef CONFIG_GTP_HOTKNOT + misc_deregister(&hotknot_misc_device); +#endif +} diff --git a/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_generic.c b/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_generic.c new file mode 100755 index 000000000000..1b00cb4033b6 --- /dev/null +++ b/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_generic.c @@ -0,0 +1,2612 @@ +/* drivers/input/touchscreen/gt1x_generic.c + * + * 2010 - 2017 Goodix Technology. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + * Version: 1.6 + */ + +#include "gt1x_generic.h" +#if defined(CONFIG_GTP_PROXIMITY) && defined(PLATFORM_MTK) +#include +#include +#include +#endif +#ifdef CONFIG_GTP_TYPE_B_PROTOCOL +#include +#endif + +static struct workqueue_struct *gt1x_workqueue; +struct i2c_client *gt1x_i2c_client; +static u8 gt1x_config[GTP_CONFIG_ORG_LENGTH + GTP_CONFIG_EXT_LENGTH]; +static u32 gt1x_cfg_length = GTP_CONFIG_MAX_LENGTH; +gt1x_chip_type_t gt1x_chip_type = CHIP_TYPE_NONE; +struct gt1x_version_info gt1x_version = {{0}}; +#ifdef CONFIG_GTP_HAVE_TOUCH_KEY +static const u16 gt1x_touch_key_array[] = GTP_KEY_TAB; +#endif +#if defined(CONFIG_GTP_WITH_STYLUS) && defined(CONFIG_GTP_HAVE_STYLUS_KEY) +static const u16 gt1x_stylus_key_array[] = GTP_STYLUS_KEY_TAB; +#endif +#define GOODIX_SYSFS_DIR "goodix" +static struct kobject *sysfs_rootdir = NULL; + + +//#define CONFIG_GTP_POWER_CTRL_SLEEP 1 + + +volatile int gt1x_rawdiff_mode = 0; +static u8 gt1x_wakeup_level = 0; +u8 gt1x_init_failed = 0; +u8 gt1x_int_type = 0; +u32 gt1x_abs_x_max = 0; +u32 gt1x_abs_y_max = 0; +int gt1x_halt = 0; + +static ssize_t gt1x_debug_read_proc(struct file *, char __user *, size_t,loff_t *); +static ssize_t gt1x_debug_write_proc(struct file *, const char __user *, size_t, loff_t *); + +static struct proc_dir_entry *gt1x_debug_proc_entry = NULL; +static const struct file_operations gt1x_debug_fops = { + .owner = THIS_MODULE, + .read = gt1x_debug_read_proc, + .write = gt1x_debug_write_proc, +}; + +static s32 gt1x_init_debug_node(void) +{ + gt1x_debug_proc_entry = proc_create(GT1X_DEBUG_PROC_FILE, + 0660, NULL, >1x_debug_fops); + if (gt1x_debug_proc_entry == NULL) { + GTP_ERROR("Create proc entry /proc/%s FAILED!", + GT1X_DEBUG_PROC_FILE); + return -1; + } + GTP_INFO("Created proc entry /proc/%s.", GT1X_DEBUG_PROC_FILE); + return 0; +} + +static void gt1x_deinit_debug_node(void) +{ + if (gt1x_debug_proc_entry != NULL) { + remove_proc_entry(GT1X_DEBUG_PROC_FILE, NULL); + } +} + +static ssize_t gt1x_debug_read_proc(struct file *file, char __user * page, + size_t size, loff_t * ppos) +{ + char *ptr = page; + char *temp_buff = NULL; + char temp_data[1024]; + struct irq_desc *irq_desc = NULL; + int i; + + if (*ppos) { + return 0; + } + temp_buff = (char *)kmalloc(GTP_CONFIG_MAX_LENGTH * 2 + GTP_CONFIG_EXT_LENGTH * 2 + 350, GFP_KERNEL); + if (!temp_buff) { + GTP_ERROR("error when alloc mem."); + return -ENOMEM; + } + + ptr = temp_buff; + ptr += sprintf(ptr, "==== GT1X default config setting in driver====\n"); //47 bytes + + for (i = 0; i < gt1x_cfg_length; i++) { + ptr += sprintf(ptr, "0x%02X,", gt1x_config[i]); + if (i % 10 == 9 && i != GTP_CONFIG_ORG_LENGTH) + ptr += sprintf(ptr, "\n"); //24 bytes + + if (i == GTP_CONFIG_ORG_LENGTH - 1) + ptr += sprintf(ptr, "\n-------------\n"); //15 bytes + } + + ptr += sprintf(ptr, "\n"); //one bytes + + ptr += sprintf(ptr, "==== GT1X config read from chip====\n"); //36 bytes + i = gt1x_i2c_read(GTP_REG_CONFIG_DATA, temp_data, + GTP_CONFIG_ORG_LENGTH); + if (i == 0 && gt1x_cfg_length == GTP_CONFIG_ORG_LENGTH + + GTP_CONFIG_EXT_LENGTH) { + i = gt1x_i2c_read(GTP_REG_EXT_CONFIG, + &temp_data[GTP_CONFIG_ORG_LENGTH], + GTP_CONFIG_EXT_LENGTH); + } + + for (i = 0; i < gt1x_cfg_length; i++) { + ptr += sprintf(ptr, "0x%02X,", temp_data[i]); + if (i % 10 == 9 && i != GTP_CONFIG_ORG_LENGTH) + ptr += sprintf(ptr, "\n"); //37 bytes + + if (i == GTP_CONFIG_ORG_LENGTH - 1) + ptr += sprintf(ptr, "\n-------------\n"); //15 bytes + } + + ptr += sprintf(ptr, "\n"); //one bytes + /* Touch PID & VID */ + ptr += sprintf(ptr, "==== GT1X Version Info ====\n"); //28 bytes + + gt1x_i2c_read(GTP_REG_VERSION, temp_data, 12); + ptr += sprintf(ptr, "ProductID: GT%c%c%c%c\n", temp_data[0], //18 bytes + temp_data[1], temp_data[2], temp_data[3]); + ptr += sprintf(ptr, "PatchID: %02X%02X\n", temp_data[4], temp_data[5]); //14 bytes + ptr += sprintf(ptr, "MaskID: %02X%02X\n", temp_data[7], temp_data[8]); //13 bytes + ptr += sprintf(ptr, "SensorID: %02X\n", temp_data[10] & 0x0F); //13 bytes + + irq_desc = irq_to_desc(gt1x_i2c_client->irq); + if (irq_desc) { + ptr += sprintf(ptr, "IRQ: %d, irq_desc->disable-depth:%d\n", //36 bytes + gt1x_i2c_client->irq, irq_desc->depth); + } + + i = ptr-temp_buff; + if (copy_to_user(page, temp_buff, i)) + { + GTP_ERROR("[READ]copy_to_user failed."); + return -1; + } + *ppos += i; + kfree(temp_buff); + + return i; +} + +static ssize_t gt1x_debug_write_proc(struct file *file, + const char __user *buffer, size_t count, loff_t * ppos) +{ + s32 ret = 0; + u8 buf[512]; + char mode_str[50] = { 0 }; + int mode; + int cfg_len; + char arg1[50] = { 0 }; + u8 temp_config[512]; + + GTP_DEBUG("write count %ld\n", (unsigned long)count); + + if (count > gt1x_cfg_length) { + GTP_ERROR("Too much data, buffer size: %d, data:%ld", + gt1x_cfg_length, (unsigned long)count); + return -EFAULT; + } + + if (copy_from_user(buf, buffer, count)) { + GTP_ERROR("copy from user fail!"); + return -EFAULT; + } + // send config + if (count == gt1x_cfg_length) { + memcpy(gt1x_config, buf, count); + ret = gt1x_send_cfg(gt1x_config, gt1x_cfg_length); + if (ret < 0) { + GTP_ERROR("send gt1x_config failed."); + return -EFAULT; + } + gt1x_abs_x_max = (gt1x_config[RESOLUTION_LOC + 1] << 8) + + gt1x_config[RESOLUTION_LOC]; + gt1x_abs_y_max = (gt1x_config[RESOLUTION_LOC + 3] << 8) + + gt1x_config[RESOLUTION_LOC + 2]; + + return count; + } + + sscanf(buf, "%s %d", (char *)&mode_str, &mode); + + //force clear gt1x_config + if (strcmp(mode_str, "clear_config") == 0) { + GTP_INFO("Force clear gt1x_config"); + gt1x_send_cmd(GTP_CMD_CLEAR_CFG, 0); + return count; + } + if (strcmp(mode_str, "init") == 0) { + GTP_INFO("Init panel"); + gt1x_init_panel(); + return count; + } + if (strcmp(mode_str, "chip") == 0) { + GTP_INFO("Get chip type:"); + gt1x_get_chip_type(); + return count; + } + if (strcmp(mode_str, "int") == 0) { + if (mode == 0) { + GTP_INFO("Disable irq."); + gt1x_irq_disable(); + } else { + GTP_INFO("Enable irq."); + gt1x_irq_enable(); + } + return count; + } + + if (strcmp(mode_str, "poweron") == 0) { + gt1x_power_switch(1); + return count; + } + + if (strcmp(mode_str, "poweroff") == 0) { + gt1x_power_switch(0); + return count; + } + + if (strcmp(mode_str, "version") == 0) { + gt1x_read_version(NULL); + return count; + } + + if (strcmp(mode_str, "reset") == 0) { + gt1x_irq_disable(); + gt1x_reset_guitar(); + gt1x_irq_enable(); + return count; + } +#ifdef CONFIG_GTP_CHARGER_SWITCH + if (strcmp(mode_str, "charger") == 0) { + gt1x_charger_config(mode); + return count; + } +#endif + sscanf(buf, "%s %s", (char *)&mode_str, (char *)&arg1); + if (strcmp(mode_str, "update") == 0) { + if(strcmp(arg1, "request") == 0){ + GTP_INFO("Update firmware from request!"); + gt1x_update_firmware(NULL); + }else{ + gt1x_update_firmware(arg1); + } + return count; + } + + if (strcmp(mode_str, "sendconfig") == 0) { + cfg_len = gt1x_parse_config(arg1, temp_config); + if (cfg_len < 0) { + return -1; + } + gt1x_send_cfg(temp_config, gt1x_cfg_length); + return count; + } + + if (strcmp(mode_str, "debug_gesture") == 0) { +#ifdef CONFIG_GTP_GESTURE_WAKEUP + gt1x_gesture_debug(!!mode); +#endif + } + + if (strcmp(mode_str, "force_update") == 0) { + update_info.force_update = !!mode; + } + return gt1x_debug_proc(buf, count); +} + +static u8 ascii2hex(u8 a) +{ + s8 value = 0; + if (a >= '0' && a <= '9') { + value = a - '0'; + } else if (a >= 'A' && a <= 'F') { + value = a - 'A' + 0x0A; + } else if (a >= 'a' && a <= 'f') { + value = a - 'a' + 0x0A; + } else { + value = 0xff; + } + return value; +} + +int gt1x_parse_config(char *filename, u8 * config) +{ + mm_segment_t old_fs; + struct file *fp = NULL; + u8 *buf; + int i; + int len; + int cur_len = -1; + u8 high, low; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + + fp = filp_open(filename, O_RDONLY, 0); + if (IS_ERR(fp)) { + GTP_ERROR("Open config file error!(file: %s)", filename); + goto parse_cfg_fail1; + } + len = fp->f_op->llseek(fp, 0, SEEK_END); + if (len > gt1x_cfg_length * 6 || len < gt1x_cfg_length) { + GTP_ERROR("Config is invalid!(length: %d)", len); + goto parse_cfg_fail2; + } + buf = (u8 *) kzalloc(len, GFP_KERNEL); + if (buf == NULL) { + GTP_ERROR("Allocate memory failed!(size: %d)", len); + goto parse_cfg_fail2; + } + fp->f_op->llseek(fp, 0, SEEK_SET); + if (fp->f_op->read(fp, (char *)buf, len, &fp->f_pos) != len) { + GTP_ERROR("Read %d bytes from file failed!", len); + } + + GTP_INFO("Parse config file: %s (%d bytes)", filename, len); + + for (i = 0, cur_len = 0; i < len && cur_len < gt1x_cfg_length;) { + if (buf[i] == ' ' || buf[i] == '\r' || buf[i] == '\n' || buf[i] == ',') { + i++; + continue; + } + if (buf[i] == '0' && (buf[i + 1] == 'x' || buf[i + 1] == 'X')) { + + high = ascii2hex(buf[i + 2]); + low = ascii2hex(buf[i + 3]); + + if (high != 0xFF && low != 0xFF) { + config[cur_len++] = (high << 4) + low; + i += 4; + continue; + } + } + GTP_ERROR("Illegal config file!"); + cur_len = -1; + break; + } + + if (cur_len < GTP_CONFIG_MIN_LENGTH || config[cur_len - 1] != 0x01) { + cur_len = -1; + } else { + for (i = 0; i < cur_len; i++) { + if (i % 10 == 0) { + printk("\n<>:"); + } + printk("0x%02x,", config[i]); + } + printk("\n"); + } + + kfree(buf); +parse_cfg_fail2: + filp_close(fp, NULL); +parse_cfg_fail1: + set_fs(old_fs); + + return cur_len; +} + +s32 _do_i2c_read(struct i2c_msg * msgs, u16 addr, u8 * buffer, s32 len) +{ + s32 ret = -1; + s32 pos = 0; + s32 data_length = len; + s32 transfer_length = 0; + u8 *data = NULL; + u16 address = addr; + + data = (u8 *) kmalloc(IIC_MAX_TRANSFER_SIZE < + (len + GTP_ADDR_LENGTH) ? + IIC_MAX_TRANSFER_SIZE : + (len + GTP_ADDR_LENGTH), GFP_KERNEL); + if (data == NULL) { + return ERROR_MEM; + } + msgs[1].buf = data; + + while (pos != data_length) { + if ((data_length - pos) > IIC_MAX_TRANSFER_SIZE) { + transfer_length = IIC_MAX_TRANSFER_SIZE; + } else { + transfer_length = data_length - pos; + } + msgs[0].buf[0] = (address >> 8) & 0xFF; + msgs[0].buf[1] = address & 0xFF; + msgs[1].len = transfer_length; + + ret = i2c_transfer(gt1x_i2c_client->adapter, msgs, 2); + if (ret != 2) { + GTP_ERROR("I2c Transfer error! (%d)", ret); + kfree(data); + return ERROR_IIC; + } + memcpy(&buffer[pos], msgs[1].buf, transfer_length); + pos += transfer_length; + address += transfer_length; + } + + kfree(data); + return 0; +} + +s32 _do_i2c_write(struct i2c_msg * msg, u16 addr, u8 * buffer, s32 len) +{ + s32 ret = -1; + s32 pos = 0; + s32 data_length = len; + s32 transfer_length = 0; + u8 *data = NULL; + u16 address = addr; + + data = (u8 *) kmalloc(IIC_MAX_TRANSFER_SIZE < + (len + GTP_ADDR_LENGTH) ? IIC_MAX_TRANSFER_SIZE : + (len + GTP_ADDR_LENGTH), GFP_KERNEL); + if (data == NULL) { + return ERROR_MEM; + } + msg->buf = data; + + while (pos != data_length) { + if ((data_length - pos) > (IIC_MAX_TRANSFER_SIZE - GTP_ADDR_LENGTH)) { + transfer_length = IIC_MAX_TRANSFER_SIZE - GTP_ADDR_LENGTH; + } else { + transfer_length = data_length - pos; + } + + msg->buf[0] = (address >> 8) & 0xFF; + msg->buf[1] = address & 0xFF; + msg->len = transfer_length + GTP_ADDR_LENGTH; + memcpy(&msg->buf[GTP_ADDR_LENGTH], &buffer[pos], transfer_length); + + ret = i2c_transfer(gt1x_i2c_client->adapter, msg, 1); + if (ret != 1) { + GTP_ERROR("I2c transfer error! (%d)", ret); + kfree(data); + return ERROR_IIC; + } + pos += transfer_length; + address += transfer_length; + } + + kfree(data); + return 0; +} + +/** + * gt1x_i2c_read_dbl_check - read twice and double check + * @addr: register address + * @buffer: data buffer + * @len: bytes to read + * Return <0: i2c error, 0: ok, 1:fail + */ +s32 gt1x_i2c_read_dbl_check(u16 addr, u8 * buffer, s32 len) +{ + u8 buf[16] = { 0 }; + u8 confirm_buf[16] = { 0 }; + int ret; + + if (len > 16) { + GTP_ERROR("i2c_read_dbl_check length %d is too long, exceed %zu", + len, sizeof(buf)); + return ERROR; + } + + memset(buf, 0xAA, sizeof(buf)); + ret = gt1x_i2c_read(addr, buf, len); + if (ret < 0) { + return ret; + } + + msleep(5); + memset(confirm_buf, 0, sizeof(confirm_buf)); + ret = gt1x_i2c_read(addr, confirm_buf, len); + if (ret < 0) { + return ret; + } + + if (!memcmp(buf, confirm_buf, len)) { + memcpy(buffer, confirm_buf, len); + return 0; + } + GTP_ERROR("i2c read 0x%04X, %d bytes, double check failed!", addr, len); + return 1; +} + +/** + * gt1x_get_info - Get information from ic, such as resolution and + * int trigger type + * Return <0: i2c failed, 0: i2c ok + */ +s32 gt1x_get_info(void) +{ + u8 opr_buf[4] = { 0 }; + s32 ret = 0; + + ret = gt1x_i2c_read(GTP_REG_CONFIG_DATA + 1, opr_buf, 4); + if (ret < 0) { + return ret; + } + + gt1x_abs_x_max = (opr_buf[1] << 8) + opr_buf[0]; + gt1x_abs_y_max = (opr_buf[3] << 8) + opr_buf[2]; + + ret = gt1x_i2c_read(GTP_REG_CONFIG_DATA + 6, opr_buf, 1); + if (ret < 0) { + return ret; + } + gt1x_int_type = opr_buf[0] & 0x03; + + GTP_INFO("X_MAX = %d, Y_MAX = %d, TRIGGER = 0x%02x", + gt1x_abs_x_max, gt1x_abs_y_max, gt1x_int_type); + + return 0; +} + +/** + * gt1x_send_cfg - Send gt1x_config Function. + * @config: pointer of the configuration array. + * @cfg_len: length of configuration array. + * Return 0--success,non-0--fail. + */ +s32 gt1x_send_cfg(u8 * config, int cfg_len) +{ +#ifdef CONFIG_GTP_DRIVER_SEND_CFG + static DEFINE_MUTEX(mutex_cfg); + int i; + s32 ret = 0; + s32 retry = 0; + u16 checksum = 0; + + if (update_info.status) { + GTP_DEBUG("Ignore cfg during fw update."); + return -1; + } + + mutex_lock(&mutex_cfg); + GTP_DEBUG("Driver send config, length:%d", cfg_len); + + if (cfg_len != GTP_CONFIG_ORG_LENGTH + && cfg_len != GTP_CONFIG_ORG_LENGTH + GTP_CONFIG_EXT_LENGTH) { + GTP_ERROR("Invalid config length"); + mutex_unlock(&mutex_cfg); + return -1; + } + + /* Extends config */ + if (config[GTP_REG_EXT_CFG_FLAG - GTP_REG_CONFIG_DATA] & 0x40) { + int total_len = GTP_CONFIG_EXT_LENGTH + GTP_CONFIG_ORG_LENGTH; + + /* bit0 of first byte of extended config + * must be set to 1, otherwise the firmware + * will not accept the extended config data + */ + config[GTP_CONFIG_ORG_LENGTH] |= 0x01; + GTP_DEBUG("ext_cfg, debug info:%X", config[GTP_CONFIG_ORG_LENGTH]); + + for (i = GTP_CONFIG_ORG_LENGTH; i < total_len - 2; i+=2) + checksum += (config[i] << 8) + + config[i + 1]; + + if (!checksum){ + GTP_ERROR("Invalid config, all of the bytes is zero!"); + mutex_unlock(&mutex_cfg); + return -1; + } + checksum = 0 - checksum; + config[total_len - 2] = (checksum >> 8) & 0xFF; + config[total_len - 1] = checksum & 0xFF; + + do { + ret = gt1x_i2c_write(GTP_REG_EXT_CONFIG, + &config[GTP_CONFIG_ORG_LENGTH], GTP_CONFIG_EXT_LENGTH); + } while (ret < 0 && retry++ < 3) ; + + if (ret < 0) { + GTP_ERROR("Send ext_config failed!"); + mutex_unlock(&mutex_cfg); + return -1; + } else { + GTP_DEBUG("Send ext_config successfully"); + } + } + + /* Original Config */ + cfg_len = GTP_CONFIG_ORG_LENGTH; + for (i = 0, checksum = 0; i < cfg_len - 3; i += 2) + checksum += (config[i] << 8) + config[i + 1]; + + if (!checksum) { + GTP_ERROR("Invalid config, all of the bytes is zero!"); + mutex_unlock(&mutex_cfg); + return -1; + } + + checksum = 0 - checksum; + GTP_DEBUG("Config checksum: 0x%04X", checksum); + config[cfg_len - 3] = (checksum >> 8) & 0xFF; + config[cfg_len - 2] = checksum & 0xFF; + config[cfg_len - 1] = 0x01; + retry = 0; + while (retry++ < 3) { + ret = gt1x_i2c_write(GTP_REG_CONFIG_DATA, config, cfg_len); + if (!ret) { + /* at least 200ms, wait for storing config into flash. */ + msleep(200); + mutex_unlock(&mutex_cfg); + GTP_DEBUG("Send config successfully!"); + return 0; + } + } + GTP_ERROR("Send config failed!"); + mutex_unlock(&mutex_cfg); + return ret; +#endif + return 0; +} + +#ifdef CONFIG_OF +/** + * gt1x_parse_tp_config - get config data of touch panel + * @of_node: device node + * @cfg_name: config name prefix, e.g. "default-config" + * @sensor_id: config group ID + * @config: config data buffer + * return: > 0 size of config data, <0 error + */ +int gt1x_find_tp_config(struct device *dev, + char *cfg_name, u8 sensor_id, u8 *config) +{ + struct property *prop; + char name[32]; + u8 extcfg_flag; + int size, requ_size; + + if (!dev || !dev->of_node || !cfg_name || + !config || sensor_id > 6) + return -EINVAL; + + snprintf(name, sizeof(name), "goodix,%s%d", cfg_name, sensor_id); + prop = of_find_property(dev->of_node, name, &size); + if (!prop || !prop->value || size < GTP_CONFIG_ORG_LENGTH) { + GTP_ERROR("Property %s not found", name); + return -ENOENT; + } + + extcfg_flag = ((u8 *)prop->value)[GTP_REG_EXT_CFG_FLAG - + GTP_REG_CONFIG_DATA]; + if (extcfg_flag & 0x40) /* has extended config data*/ + requ_size = GTP_CONFIG_ORG_LENGTH + GTP_CONFIG_EXT_LENGTH; + else + requ_size = GTP_CONFIG_ORG_LENGTH; + + if (size != requ_size) { + GTP_ERROR("Invalid config size:%d", size); + return -EINVAL; + } + + memcpy(config, prop->value, size); + GTP_INFO("Find %s, size:%d, ver:%02xh ", + name, size, config[0]); + return size; +} +#else +int gt1x_find_tp_config(struct device *dev, + char *cfg_name, u8 sensor_id, u8 *config) +{ + return -ENOENT; +} +#endif + +/** + * gt1x_init_panel - Prepare config data for touch ic, + * don't call this function after initialization. + * + * Return 0--success,<0 --fail. + */ +s32 gt1x_init_panel(void) +{ + u16 cfg_len = 0; + s32 ret = 0; + +#ifdef CONFIG_GTP_DRIVER_SEND_CFG +#ifdef CONFIG_MTK_PLATFORM + struct device *dev = tpd->tpd_dev; +#else + struct device *dev = >1x_i2c_client->dev; +#endif + + cfg_len = gt1x_find_tp_config(dev, "default-config", + gt1x_version.sensor_id, gt1x_config); + if (cfg_len < 0) { + GTP_ERROR("Failed to obtain config data:%d", cfg_len); + return -EINVAL; + } + + /* clear the flag, avoid failure when send + * the_config of driver. */ + gt1x_config[0] &= 0x7F; + +#ifdef CONFIG_GTP_CUSTOM_CFG + gt1x_config[RESOLUTION_LOC] = (u8) GTP_MAX_WIDTH; + gt1x_config[RESOLUTION_LOC + 1] = (u8) (GTP_MAX_WIDTH >> 8); + gt1x_config[RESOLUTION_LOC + 2] = (u8) GTP_MAX_HEIGHT; + gt1x_config[RESOLUTION_LOC + 3] = (u8) (GTP_MAX_HEIGHT >> 8); + + if (GTP_INT_TRIGGER == 0) { /* RISING */ + gt1x_config[TRIGGER_LOC] &= 0xfe; + } else if (GTP_INT_TRIGGER == 1) { /* FALLING */ + gt1x_config[TRIGGER_LOC] |= 0x01; + } + set_reg_bit(gt1x_config[MODULE_SWITCH3_LOC], 5, !gt1x_wakeup_level); +#endif /* END GTP_CUSTOM_CFG */ + +#else /* DRIVER NOT SEND CONFIG */ + cfg_len = GTP_CONFIG_ORG_LENGTH; + ret = gt1x_i2c_read(GTP_REG_CONFIG_DATA, gt1x_config, cfg_len); + if (ret < 0) + return ret; + + if (gt1x_config[GTP_REG_EXT_CFG_FLAG - GTP_REG_CONFIG_DATA] & 0x40) { + ret = gt1x_i2c_read(GTP_REG_EXT_CONFIG, + >1x_config[cfg_len], GTP_CONFIG_EXT_LENGTH); + if (ret < 0) + return ret; + + cfg_len += GTP_CONFIG_EXT_LENGTH; + } +#endif /* END GTP_DRIVER_SEND_CFG */ + + /* match resolution when gt1x_abs_x_max & gt1x_abs_y_max + * have been set already */ + if ((gt1x_abs_x_max == 0) && (gt1x_abs_y_max == 0)) { + gt1x_abs_x_max = (gt1x_config[RESOLUTION_LOC + 1] << 8) + + gt1x_config[RESOLUTION_LOC]; + gt1x_abs_y_max = (gt1x_config[RESOLUTION_LOC + 3] << 8) + + gt1x_config[RESOLUTION_LOC + 2]; + gt1x_int_type = (gt1x_config[TRIGGER_LOC]) & 0x03; + gt1x_wakeup_level = !(gt1x_config[MODULE_SWITCH3_LOC] & 0x20); + } else { + gt1x_config[RESOLUTION_LOC] = (u8) gt1x_abs_x_max; + gt1x_config[RESOLUTION_LOC + 1] = (u8) (gt1x_abs_x_max >> 8); + gt1x_config[RESOLUTION_LOC + 2] = (u8) gt1x_abs_y_max; + gt1x_config[RESOLUTION_LOC + 3] = (u8) (gt1x_abs_y_max >> 8); + set_reg_bit(gt1x_config[MODULE_SWITCH3_LOC], 5, !gt1x_wakeup_level); + gt1x_config[TRIGGER_LOC] = (gt1x_config[TRIGGER_LOC] & 0xFC) | gt1x_int_type; + } + + GTP_INFO("X_MAX=%d,Y_MAX=%d,TRIGGER=0x%02x,WAKEUP_LEVEL=%d", + gt1x_abs_x_max, gt1x_abs_y_max, gt1x_int_type, gt1x_wakeup_level); + + gt1x_cfg_length = cfg_len; + ret = gt1x_send_cfg(gt1x_config, gt1x_cfg_length); + return ret; +} + +void gt1x_select_addr(void) +{ + int ret = 0; + /* Set pinctrl state as wakeup */ + if ( gt_pinctrl->ts_pinctrl && gt_pinctrl->pinctrl_wakeup) + ret = pinctrl_select_state(gt_pinctrl->ts_pinctrl, gt_pinctrl->pinctrl_wakeup); + if (ret < 0) + GTP_ERROR("Set pinctrl state as wakeup error: %d", ret); + + GTP_GPIO_OUTPUT(GTP_RST_PORT, 0); + usleep_range(1000, 1030); + GTP_GPIO_OUTPUT(GTP_INT_PORT, gt1x_i2c_client->addr == 0x14); + usleep_range(3000, 3030); + GTP_GPIO_OUTPUT(GTP_RST_PORT, 1); + usleep_range(2000, 2030); +} + +static s32 gt1x_set_reset_status(void) +{ + /* 0x8040 ~ 0x8043 */ + u8 value[] = {0xAA, 0x00, 0x56, 0xAA}; + int ret; + + ret = gt1x_i2c_write(GTP_REG_CMD + 1, &value[1], 3); + if (ret < 0) + return ret; + + return gt1x_i2c_write(GTP_REG_CMD, value, 1); +} + +#ifdef CONFIG_GTP_INCELL_PANEL +int gt1x_write_and_readback(u16 addr, u8 * buffer, s32 len) +{ + int ret; + u8 d[len]; + + ret = gt1x_i2c_write(addr, buffer, len); + if (ret < 0) + return -1; + + ret = gt1x_i2c_read(addr, d, len); + if (ret < 0 || memcmp(buffer, d, len)) + return -1; + + return 0; +} + +int gt1x_incell_reset(void) +{ +#define RST_RETRY 5 + int ret, retry = RST_RETRY; + u8 d[2]; + + do { + /* select i2c address */ + gt1x_select_addr(); + + /* test i2c */ + ret = gt1x_i2c_read(0x4220, d, 1); + + } while (--retry && ret < 0); + + if (ret < 0) { + return -1; + } + + /* Important! */ + usleep_range(10000, 11000); /* delay 10ms */ + + /* Stop cpu of the touch ic */ + retry = RST_RETRY; + do { + d[0] = 0x0C; + ret = gt1x_write_and_readback(0x4180, d, 1); + } while (--retry && ret < 0); + + if (ret < 0) { + GTP_ERROR("Hold error."); + return -1; + } + + /* skip sensor id check. [start] */ + retry = RST_RETRY; + do { + d[0] = 0x00; + ret = gt1x_write_and_readback(0x4305, d, 1); + if (ret < 0) + continue; + + d[0] = 0x2B; + d[1] = 0x24; + ret = gt1x_write_and_readback(0x42c4, d, 2); + if (ret < 0) + continue; + + d[0] = 0xE1; + d[1] = 0xD3; + ret = gt1x_write_and_readback(0x42e4, d, 2); + if (ret < 0) + continue; + + d[0] = 0x01; + ret = gt1x_write_and_readback(0x4305, d, 1); + if (ret < 0) + continue; + else + break; + } while (--retry ); + + if (!retry) + return -1; + /* skip sensor id check. [end] */ + + /* release hold of cpu */ + retry = RST_RETRY; + do { + d[0] = 0x00; + ret = gt1x_write_and_readback(0x4180, d, 1); + + } while (--retry && ret < 0); + + if (ret < 0) + return -1; + + return 0; + +} +#endif + +s32 gt1x_reset_guitar(void) +{ + int ret; + + GTP_INFO("GTP RESET!"); + +#ifdef CONFIG_GTP_INCELL_PANEL + ret = gt1x_incell_reset(); + if (ret < 0) + return ret; +#else + gt1x_select_addr(); + usleep_range(8000, 8000); //must >= 6ms +#endif + + GTP_INFO("%s ===========sss======= \n",__func__); + GTP_GPIO_OUTPUT(GTP_INT_PORT, 0); + msleep(50); + + GTP_INFO("%s ================== \n",__func__); + /* Set pinctrl state as normal */ + if ( gt_pinctrl->ts_pinctrl && gt_pinctrl->pinctrl_normal) + { + ret = pinctrl_select_state(gt_pinctrl->ts_pinctrl, gt_pinctrl->pinctrl_normal); + GTP_INFO("%s ret=%d \n",__func__,ret); + } + if (ret < 0) + GTP_ERROR("Set pinctrl state as normal error: %d", ret); + GTP_GPIO_AS_INT(GTP_INT_PORT); + + ret = gt1x_set_reset_status(); + return ret; +} + +/** + * gt1x_read_version - Read gt1x version info. + * @ver_info: address to store version info + * Return 0-succeed. + */ +s32 gt1x_read_version(struct gt1x_version_info * ver_info) +{ + s32 ret = -1; + u8 buf[12] = { 0 }; + u32 mask_id = 0; + u32 patch_id = 0; + u8 product_id[5] = { 0 }; + u8 sensor_id = 0; + u8 match_opt = 0; + int i, retry = 3; + u8 checksum = 0; + + GTP_DEBUG_FUNC(); + + while (retry--) { + ret = gt1x_i2c_read_dbl_check(GTP_REG_VERSION, buf, sizeof(buf)); + if (!ret) { + checksum = 0; + + for (i = 0; i < sizeof(buf); i++) { + checksum += buf[i]; + } + + if (checksum == 0 && /* first 3 bytes must be number or char */ + IS_NUM_OR_CHAR(buf[0]) && IS_NUM_OR_CHAR(buf[1]) && + IS_NUM_OR_CHAR(buf[2]) && buf[10] != 0xFF) { + /*sensor id == 0xFF, retry */ + break; + } else { + GTP_ERROR("Read version failed!(checksum error)"); + } + } else { + GTP_ERROR("Read version failed!"); + } + GTP_DEBUG("Read version : %d", retry); + msleep(100); + } + + if (retry <= 0) { + if (ver_info) + ver_info->sensor_id = 0; + return -1; + } + + mask_id = (u32) ((buf[7] << 16) | (buf[8] << 8) | buf[9]); + patch_id = (u32) ((buf[4] << 16) | (buf[5] << 8) | buf[6]); + memcpy(product_id, buf, 4); + sensor_id = buf[10] & 0x0F; + match_opt = (buf[10] >> 4) & 0x0F; + + GTP_INFO("IC VERSION:GT%s_%06X(Patch)_%04X(Mask)_%02X(SensorID)", + product_id, patch_id, mask_id >> 8, sensor_id); + + if (ver_info != NULL) { + ver_info->mask_id = mask_id; + ver_info->patch_id = patch_id; + memcpy(ver_info->product_id, product_id, 5); + ver_info->sensor_id = sensor_id; + ver_info->match_opt = match_opt; + } + return 0; +} + +/** + * gt1x_get_chip_type - get chip type . + * + * different chip synchronize in different way, + */ +s32 gt1x_get_chip_type(void) +{ + u8 opr_buf[4] = { 0x00 }; + u8 gt1x_data[] = { 0x02, 0x08, 0x90, 0x00 }; + u8 gt9l_data[] = { 0x03, 0x10, 0x90, 0x00 }; + s32 ret = -1; + + /* chip type already exist */ + if (gt1x_chip_type != CHIP_TYPE_NONE) { + return 0; + } + + /* read hardware */ + ret = gt1x_i2c_read_dbl_check(GTP_REG_HW_INFO, opr_buf, sizeof(opr_buf)); + if (ret) { + GTP_ERROR("I2c communication error."); + return -1; + } + + /* find chip type */ + if (!memcmp(opr_buf, gt1x_data, sizeof(gt1x_data))) { + gt1x_chip_type = CHIP_TYPE_GT1X; + } else if (!memcmp(opr_buf, gt9l_data, sizeof(gt9l_data))) { + gt1x_chip_type = CHIP_TYPE_GT2X; + } + + if (gt1x_chip_type != CHIP_TYPE_NONE) { + GTP_INFO("Chip Type: %s", + (gt1x_chip_type == CHIP_TYPE_GT1X) ? + "GT1X" : "GT2X"); + return 0; + } else { + return -1; + } +} + +/** + * gt1x_enter_sleep - Eter sleep function. + * + * Returns 0--success,non-0--fail. + */ +static s32 gt1x_enter_sleep(void) +{ + int ret; + +#ifdef CONFIG_GTP_POWER_CTRL_SLEEP + /* Set pin state as poweroff */ + if ( gt_pinctrl->ts_pinctrl && gt_pinctrl->pinctrl_poweroff) + ret = pinctrl_select_state(gt_pinctrl->ts_pinctrl, gt_pinctrl->pinctrl_poweroff); + if (ret < 0) + GTP_ERROR("Set pin state as poweroff error: %d", ret); + gt1x_power_switch(SWITCH_OFF); + gt1x_vcc_i2c_switch(0); + //ret = gt1x_vcc_i2c_switch(0); + + return 0; +#else + { + s32 retry = 0; + //gt1x_vcc_i2c_switch(1); + + if (gt1x_wakeup_level == 1) { + /* Set pin state as sleep */ + if ( gt_pinctrl->ts_pinctrl && gt_pinctrl->pinctrl_sleep) + ret = pinctrl_select_state(gt_pinctrl->ts_pinctrl, gt_pinctrl->pinctrl_sleep); + if (ret < 0) + GTP_ERROR("Set pin state as sleep error: %d", ret); + /* high level wakeup */ + GTP_GPIO_OUTPUT(GTP_INT_PORT, 0); + } else { + /* Set pinctrl state as wakeup */ + if ( gt_pinctrl->ts_pinctrl && gt_pinctrl->pinctrl_wakeup) + ret = pinctrl_select_state(gt_pinctrl->ts_pinctrl, gt_pinctrl->pinctrl_wakeup); + if (ret < 0) + GTP_ERROR("Set pinctrl state as wakeup error: %d", ret); + /* low level wakeup */ + GTP_GPIO_OUTPUT(GTP_INT_PORT, 1); + } + msleep(5); + + while (retry++ < 3) { + if (!gt1x_send_cmd(GTP_CMD_SLEEP, 0)) { + GTP_INFO("Enter sleep mode!"); + return 0; + } + msleep(10); + } + /*ret = gt1x_vcc_i2c_switch(0); + if (ret < 0) + GTP_ERROR("gt1x_vcc_i2c_switch: %d", ret); + + msleep(10); + ret = gt1x_power_switch(0); + if (ret < 0) + GTP_ERROR("gt1x_power_switch: %d", ret); + */ + GTP_ERROR("Enter sleep mode failed."); + return -1; + } +#endif +} + +/** + * gt1x_wakeup_sleep - wakeup from sleep mode Function. + * + * Return: 0--success,non-0--fail. + */ +static s32 gt1x_wakeup_sleep(void) +{ +#ifndef CONFIG_GTP_POWER_CTRL_SLEEP + u8 retry = 0; + s32 ret = -1; + int flag = 0; +#endif + + GTP_DEBUG("Wake up begin."); + gt1x_irq_disable(); + +#ifdef CONFIG_GTP_POWER_CTRL_SLEEP + /* power manager unit control the procedure */ + gt1x_power_reset(); + GTP_INFO("Wakeup by poweron"); + return 0; +#else + /* gesture wakeup & int port wakeup */ + while (retry++ < 2) { +#ifdef CONFIG_GTP_GESTURE_WAKEUP + if (gesture_enabled) { +#ifndef CONFIG_MTK_PLATFORM + disable_irq_wake(gt1x_i2c_client->irq); +#endif + gesture_doze_status = DOZE_DISABLED; + ret = gt1x_reset_guitar(); + if(!ret) + break; + } else +#endif + { + if (1 == gt1x_wakeup_level) { + /* Set pinctrl state as wakeup */ + if ( gt_pinctrl->ts_pinctrl && gt_pinctrl->pinctrl_wakeup) + ret = pinctrl_select_state(gt_pinctrl->ts_pinctrl, gt_pinctrl->pinctrl_wakeup); + if (ret < 0) + GTP_ERROR("Set pinctrl state as wakeup error: %d", ret); + } + + /* wake up through int port */ + GTP_GPIO_OUTPUT(GTP_INT_PORT, gt1x_wakeup_level); + msleep(5); + /* Synchronize int IO */ + GTP_GPIO_OUTPUT(GTP_INT_PORT, 0); + msleep(50); + + /* Set pinctrl state as normal */ + if ( gt_pinctrl->ts_pinctrl && gt_pinctrl->pinctrl_normal) + ret = pinctrl_select_state(gt_pinctrl->ts_pinctrl, gt_pinctrl->pinctrl_normal); + if (ret < 0) + GTP_ERROR("Set pinctrl state as normal error: %d", ret); + GTP_GPIO_AS_INT(GTP_INT_PORT); + flag = 1; + + ret = gt1x_set_reset_status(); + if (!ret) + break; +#if 0 + /* wakeup throuth hw reset */ + ret = gt1x_reset_guitar(); + if (!ret) + break; +#endif + } /* end int wakeup */ + } + + if (ret < 0 && flag) { + /* int wakeup failed , try waking + * up by reset */ + while (retry--) { + ret = gt1x_reset_guitar(); + if(!ret) + break; + } + } + + if (ret) { + GTP_ERROR("Wake up sleep failed."); + return -1; + } else { + GTP_INFO("Wake up end."); + return 0; + } +#endif /* END GTP_POWER_CTRL_SLEEP */ +} + +/** + * gt1x_send_cmd - seng cmd + * must write data & checksum first + * byte content + * 0 cmd + * 1 data + * 2 checksum + * Returns 0 - succeed,non-0 - failed + */ +s32 gt1x_send_cmd(u8 cmd, u8 data) +{ + s32 ret; + static DEFINE_MUTEX(cmd_mutex); + u8 buffer[3] = { cmd, data, 0 }; + + mutex_lock(&cmd_mutex); + buffer[2] = (u8) ((0 - cmd - data) & 0xFF); + ret = gt1x_i2c_write(GTP_REG_CMD + 1, &buffer[1], 2); + ret |= gt1x_i2c_write(GTP_REG_CMD, &buffer[0], 1); + msleep(50); + mutex_unlock(&cmd_mutex); + + return ret; +} + +void gt1x_power_reset(void) +{ + static int rst_flag; + s32 i = 0; + + if (rst_flag || update_info.status) { + return; + } + GTP_INFO("force_reset_guitar"); + rst_flag = 1; + gt1x_irq_disable(); + gt1x_power_switch(SWITCH_OFF); + gt1x_vcc_i2c_switch(SWITCH_OFF); + msleep(30); + gt1x_power_switch(SWITCH_ON); + gt1x_vcc_i2c_switch(SWITCH_ON); + + for (i = 0; i < 5; i++) { + if(gt1x_reset_guitar()) { + continue; + } + if(gt1x_send_cfg(gt1x_config, gt1x_cfg_length)) { + msleep(500); + continue; + } + break; + } + gt1x_irq_enable(); + rst_flag = 0; +} + +s32 gt1x_request_event_handler(void) +{ + s32 ret = -1; + u8 rqst_data = 0; + + ret = gt1x_i2c_read(GTP_REG_RQST, &rqst_data, 1); + if (ret) { + GTP_ERROR("I2C transfer error. errno:%d", ret); + return -1; + } + GTP_DEBUG("Request state:0x%02x.", rqst_data); + switch (rqst_data & 0x0F) { + case GTP_RQST_CONFIG: + GTP_INFO("Request Config."); + ret = gt1x_send_cfg(gt1x_config, gt1x_cfg_length); + if (ret) { + GTP_ERROR("Send gt1x_config error."); + } else { + GTP_INFO("Send gt1x_config success."); + rqst_data = GTP_RQST_RESPONDED; + gt1x_i2c_write(GTP_REG_RQST, &rqst_data, 1); + } + break; + case GTP_RQST_RESET: + GTP_INFO("Request Reset."); + gt1x_reset_guitar(); + rqst_data = GTP_RQST_RESPONDED; + gt1x_i2c_write(GTP_REG_RQST, &rqst_data, 1); + break; + case GTP_RQST_BAK_REF: + GTP_INFO("Request Ref."); + break; + case GTP_RQST_MAIN_CLOCK: + GTP_INFO("Request main clock."); + break; +#ifdef CONFIG_GTP_HOTKNOT + case GTP_RQST_HOTKNOT_CODE: + GTP_INFO("Request HotKnot Code."); + break; +#endif + default: + break; + } + return 0; +} + +/** + * gt1x_touch_event_handler - handle touch event + * (pen event, key event, finger touch envent) + * @data: + * Return <0: failed, 0: succeed + */ +s32 gt1x_touch_event_handler(u8 * data, struct input_dev * dev, + struct input_dev * pen_dev) +{ + u8 touch_data[1 + 8 * GTP_MAX_TOUCH + 2] = { 0 }; + static u16 pre_event = 0; + static u16 pre_index = 0; + u8 touch_num = 0; + u8 key_value = 0; + u16 cur_event = 0; + u8 *coor_data = NULL; + u8 check_sum = 0; + s32 input_x = 0; + s32 input_y = 0; + s32 input_w = 0; + s32 id = 0; + s32 i = 0; + s32 ret = -1; + + GTP_DEBUG_FUNC(); + touch_num = data[0] & 0x0f; + if (touch_num > GTP_MAX_TOUCH) { + GTP_ERROR("Illegal finger number!"); + return ERROR_VALUE; + } + GTP_DEBUG("finger num %d",touch_num); + + memcpy(touch_data, data, 11); + + /* read the remaining coor data + * 0x814E(touch status) + 8(every coordinate + * consist of 8 bytes data) * touch num + + * keycode + checksum + */ + if (touch_num > 1) { + ret = gt1x_i2c_read((GTP_READ_COOR_ADDR + 11), + &touch_data[11], 1 + 8 * touch_num + 2 - 11); + if (ret) { + return ret; + } + } + + /* cacl checksum */ + for (i = 0; i < 1 + 8 * touch_num + 2; i++) { + check_sum += touch_data[i]; + } + if (check_sum) { /* checksum error*/ + ret = gt1x_i2c_read(GTP_READ_COOR_ADDR, + touch_data, 3 + 8 * touch_num); + if (ret) { + return ret; + } + + for (i = 0, check_sum = 0; i < 3 + 8 * touch_num; i++) { + check_sum += touch_data[i]; + GTP_ERROR("touch_data[%d]=%x",i,touch_data[i]); + } + if (check_sum) { + GTP_ERROR("Checksum error[%x]",check_sum); + return ERROR_VALUE; + } + } +/* + * cur_event , pre_event bit defination + * bits: bit4 bit3 bit2 bit1 bit0 + * event: hover stylus_key stylus key touch + */ + key_value = touch_data[1 + 8 * touch_num]; +/* start check current event */ + if ((touch_data[0] & 0x10) && key_value) { +#if defined(CONFIG_GTP_HAVE_STYLUS_KEY) || defined(CONFIG_GTP_HAVE_TOUCH_KEY) || defined(CONFIG_TPD_HAVE_BUTTON) + /* get current key states */ + if (key_value & 0xF0) { + SET_BIT(cur_event, BIT_STYLUS_KEY); + } else if (key_value & 0x0F) { + SET_BIT(cur_event, BIT_TOUCH_KEY); + } +#endif + } +#ifdef CONFIG_GTP_WITH_STYLUS + else if (touch_data[1] & 0x80) { + SET_BIT(cur_event, BIT_STYLUS); + } +#endif + else if (touch_num) { + SET_BIT(cur_event, BIT_TOUCH); + } + +/* start handle current event and pre-event */ +#ifdef CONFIG_GTP_HAVE_STYLUS_KEY + if (CHK_BIT(cur_event, BIT_STYLUS_KEY) || CHK_BIT(pre_event, BIT_STYLUS_KEY)) { + /* + * 0x10 -- stylus key0 down + * 0x20 -- stylus key1 down + * 0x40 -- stylus key0 & stylus key1 both down + */ + u8 temp = (key_value & 0x40) ? 0x30 : key_value; + for (i = 4; i < 6; i++) { + input_report_key(pen_dev, gt1x_stylus_key_array[i - 4], + temp & (0x01 << i)); + } + GTP_DEBUG("Stulus key event."); + } +#endif + +#ifdef CONFIG_GTP_WITH_STYLUS + if (CHK_BIT(cur_event, BIT_STYLUS)) { + coor_data = &touch_data[1]; + id = coor_data[0] & 0x7F; + input_x = coor_data[1] | (coor_data[2] << 8); + input_y = coor_data[3] | (coor_data[4] << 8); + input_w = coor_data[5] | (coor_data[6] << 8); + + input_x = GTP_WARP_X(gt1x_abs_x_max, input_x); + input_y = GTP_WARP_Y(gt1x_abs_y_max, input_y); + + GTP_DEBUG("Pen touch DOWN."); + gt1x_pen_down(input_x, input_y, input_w, 0); + } else if (CHK_BIT(pre_event, BIT_STYLUS)) { + GTP_DEBUG("Pen touch UP."); + gt1x_pen_up(0); + } +#endif + +#ifdef CONFIG_GTP_HAVE_TOUCH_KEY + if (CHK_BIT(cur_event, BIT_TOUCH_KEY) || CHK_BIT(pre_event, BIT_TOUCH_KEY)) { + for (i = 0; i < GTP_MAX_KEY_NUM; i++) { + input_report_key(dev, gt1x_touch_key_array[i], + key_value & (0x01 << i)); + } + if (CHK_BIT(cur_event, BIT_TOUCH_KEY)) { + GTP_DEBUG("Key Down."); + } else { + GTP_DEBUG("Key Up."); + } + } +#elif defined(CONFIG_TPD_HAVE_BUTTON) + if (tpd_dts_data.use_tpd_button) { + if (CHK_BIT(cur_event, BIT_TOUCH_KEY) || CHK_BIT(pre_event, BIT_TOUCH_KEY)) { + for (i = 0; i < tpd_dts_data.tpd_key_num; i++) + input_report_key(dev, tpd_dts_data.tpd_key_local[i], + key_value & (0x01 << i)); + if (CHK_BIT(cur_event, BIT_TOUCH_KEY)) + GTP_DEBUG("Key Down."); + else + GTP_DEBUG("Key Up."); + } + } +#endif + +/* finger touch event*/ + if (CHK_BIT(cur_event, BIT_TOUCH)) { + u8 report_num = 0; + coor_data = &touch_data[1]; + id = coor_data[0] & 0x0F; + for (i = 0; i < GTP_MAX_TOUCH; i++) { + if (i == id) { + input_x = coor_data[1] | (coor_data[2] << 8); + input_y = coor_data[3] | (coor_data[4] << 8); + input_w = coor_data[5] | (coor_data[6] << 8); + + input_x = GTP_WARP_X(gt1x_abs_x_max, input_x); + input_y = GTP_WARP_Y(gt1x_abs_y_max, input_y); + + GTP_DEBUG("(%d)(%d,%d)[%d]", id, input_x, input_y, input_w); + gt1x_touch_down(input_x, input_y, input_w, i); + if (report_num++ < touch_num) { + coor_data += 8; + id = coor_data[0] & 0x0F; + } + pre_index |= 0x01 << i; + } else if (pre_index & (0x01 << i)) { +#ifdef CONFIG_GTP_TYPE_B_PROTOCOL + gt1x_touch_up(i); +#endif + pre_index &= ~(0x01 << i); + } + } + } else if (CHK_BIT(pre_event, BIT_TOUCH)) { +#ifdef CONFIG_GTP_TYPE_B_PROTOCOL + int cycles = pre_index < 3 ? 3 : GTP_MAX_TOUCH; + input_report_key(dev, BTN_TOUCH, 0); + for (i = 0; i < cycles; i++) { + if (pre_index >> i & 0x01) { + gt1x_touch_up(i); + } + } +#else + input_report_key(dev, BTN_TOUCH, 0); + gt1x_touch_up(0); +#endif + GTP_DEBUG("Released Touch."); + pre_index = 0; + } + + /* start sync input report */ + if (CHK_BIT(cur_event, BIT_STYLUS_KEY | BIT_STYLUS) + || CHK_BIT(pre_event, BIT_STYLUS_KEY | BIT_STYLUS)) { + input_sync(pen_dev); + } + + if (CHK_BIT(cur_event, BIT_TOUCH_KEY | BIT_TOUCH) + || CHK_BIT(pre_event, BIT_TOUCH_KEY | BIT_TOUCH)) { + input_sync(dev); + } + + if (unlikely(!pre_event && !cur_event)) { + GTP_DEBUG("Additional Int Pulse."); + } else { + pre_event = cur_event; + } + + return 0; +} + +#ifdef CONFIG_GTP_WITH_STYLUS +struct input_dev *pen_dev; + +static void gt1x_pen_init(void) +{ + s32 ret = 0; + + pen_dev = input_allocate_device(); + if (pen_dev == NULL) { + GTP_ERROR("Failed to allocate input device for pen/stylus."); + return; + } + + pen_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + pen_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + set_bit(BTN_TOOL_PEN, pen_dev->keybit); + set_bit(INPUT_PROP_DIRECT, pen_dev->propbit); + +#ifdef CONFIG_GTP_HAVE_STYLUS_KEY + input_set_capability(pen_dev, EV_KEY, BTN_STYLUS); + input_set_capability(pen_dev, EV_KEY, BTN_STYLUS2); +#endif + + input_set_abs_params(pen_dev, ABS_MT_POSITION_X, 0, gt1x_abs_x_max, 0, 0); + input_set_abs_params(pen_dev, ABS_MT_POSITION_Y, 0, gt1x_abs_y_max, 0, 0); + input_set_abs_params(pen_dev, ABS_MT_PRESSURE, 0, 255, 0, 0); + input_set_abs_params(pen_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + input_set_abs_params(pen_dev, ABS_MT_TRACKING_ID, 0, 255, 0, 0); + + pen_dev->name = "goodix-pen"; + pen_dev->phys = "input/ts"; + pen_dev->id.bustype = BUS_I2C; + + ret = input_register_device(pen_dev); + if (ret) { + GTP_ERROR("Register %s input device failed", pen_dev->name); + return; + } +} + +void gt1x_pen_down(s32 x, s32 y, s32 size, s32 id) +{ + input_report_key(pen_dev, BTN_TOOL_PEN, 1); +#ifdef CONFIG_GTP_CHANGE_X2Y + GTP_SWAP(x, y); +#endif + +#ifdef CONFIG_GTP_TYPE_B_PROTOCOL + input_mt_slot(pen_dev, id); + input_report_abs(pen_dev, ABS_MT_PRESSURE, size); + input_report_abs(pen_dev, ABS_MT_TOUCH_MAJOR, size); + input_report_abs(pen_dev, ABS_MT_TRACKING_ID, id); + input_report_abs(pen_dev, ABS_MT_POSITION_X, x); + input_report_abs(pen_dev, ABS_MT_POSITION_Y, y); +#else + input_report_key(pen_dev, BTN_TOUCH, 1); + if ((!size) && (!id)) { + /* for virtual button */ + input_report_abs(pen_dev, ABS_MT_PRESSURE, 100); + input_report_abs(pen_dev, ABS_MT_TOUCH_MAJOR, 100); + } else { + input_report_abs(pen_dev, ABS_MT_PRESSURE, size); + input_report_abs(pen_dev, ABS_MT_TOUCH_MAJOR, size); + input_report_abs(pen_dev, ABS_MT_TRACKING_ID, id); + } + input_report_abs(pen_dev, ABS_MT_POSITION_X, x); + input_report_abs(pen_dev, ABS_MT_POSITION_Y, y); + input_mt_sync(pen_dev); +#endif +} + +void gt1x_pen_up(s32 id) +{ + input_report_key(pen_dev, BTN_TOOL_PEN, 0); +#ifdef CONFIG_GTP_TYPE_B_PROTOCOL + input_mt_slot(pen_dev, id); + input_report_abs(pen_dev, ABS_MT_TRACKING_ID, -1); +#else + input_report_key(pen_dev, BTN_TOUCH, 0); + input_mt_sync(pen_dev); +#endif +} +#endif + +/** + * Proximity Module + */ +#ifdef CONFIG_GTP_PROXIMITY +#define GTP_PS_DEV_NAME "goodix_proximity" +#define GTP_REG_PROXIMITY_ENABLE 0x8049 +#define PS_FARAWAY 1 +#define PS_NEAR 0 +struct gt1x_ps_device{ + int enabled; // module enabled/disabled + int state; // Faraway or Near +#ifdef PLATFORM_MTK + struct hwmsen_object obj_ps; +#else + struct input_dev *input_dev; + struct kobject *kobj; +#endif +}; +static struct gt1x_ps_device *gt1x_ps_dev; + +void gt1x_ps_report(int state) +{ +#ifdef PLATFORM_MTK + s32 ret = -1; + + struct hwm_sensor_data sensor_data; + //map and store data to hwm_sensor_data + sensor_data.values[0] = !!state; + sensor_data.value_divide = 1; + sensor_data.status = SENSOR_STATUS_ACCURACY_MEDIUM; + //report to the up-layer + ret = hwmsen_get_interrupt_data(ID_PROXIMITY, &sensor_data); + if (ret) { + GTP_ERROR("Call hwmsen_get_interrupt_data fail = %d\n", ret); + } +#else + input_report_abs(gt1x_ps_dev->input_dev, ABS_DISTANCE, !!state); + input_sync(gt1x_ps_dev->input_dev); +#endif /* End PLATFROM_MTK */ + + GTP_INFO("Report proximity state: %s", + state == PS_FARAWAY? "FARAWAY":"NEAR"); +} + +static s32 gt1x_ps_enable(s32 enable) +{ + u8 state; + s32 ret = -1; + + GTP_INFO("Proximity function to be %s.", enable ? "on" : "off"); + state = enable ? 1 : 0; + if (gt1x_chip_type == CHIP_TYPE_GT1X) + ret = gt1x_i2c_write(GTP_REG_PROXIMITY_ENABLE, &state, 1); + else if (gt1x_chip_type == CHIP_TYPE_GT2X) + ret = gt1x_send_cmd(state ? 0x12 : 0x13, 0); + if (ret) { + GTP_ERROR("GTP %s proximity cmd failed.", + state ? "enable" : "disable"); + } + + if (!ret && enable) { + gt1x_ps_dev->enabled = 1; + } else { + gt1x_ps_dev->enabled = 0; + } + gt1x_ps_dev->state = PS_FARAWAY; + GTP_INFO("Proximity function %s %s.", + state ? "enable" : "disable", ret ? "fail" : "success"); + return ret; +} + +int gt1x_prox_event_handler(u8 * data) +{ + u8 ps = 0; + + if (gt1x_ps_dev && gt1x_ps_dev->enabled) { + ps = (data[0] & 0x60) ? 0 : 1; + if (ps != gt1x_ps_dev->state) { + gt1x_ps_report(ps); + gt1x_ps_dev->state = ps; + GTP_DEBUG("REG INDEX[0x814E]:0x%02X\n", data[0]); + } + + return (ps == PS_NEAR? 1 : 0); + } + return -1; +} + +#ifdef PLATFORM_MTK +static inline s32 gt1x_get_ps_value(void) +{ + return gt1x_ps_dev->state; +} + +static s32 gt1x_ps_operate(void *self, u32 command, void *buff_in, + s32 size_in, void *buff_out, s32 size_out, s32 * actualout) +{ + s32 err = 0; + s32 value; + struct hwm_sensor_data *sensor_data; + struct hwm_sensor_data sensor_size; + + GTP_INFO("psensor operator cmd:%d", command); + switch (command) { + case SENSOR_DELAY: + if ((buff_in == NULL) || (size_in < sizeof(int))) { + GTP_ERROR("Set delay parameter error!"); + err = -EINVAL; + } + // Do nothing + break; + + case SENSOR_ENABLE: + if ((buff_in == NULL) || (size_in < sizeof(int))) { + GTP_ERROR("Enable sensor parameter error!"); + err = -EINVAL; + } else { + value = *(int *)buff_in; + err = gt1x_ps_enable(value); + } + + break; + + case SENSOR_GET_DATA: + if ((buff_out == NULL) || (size_out < sizeof(sensor_size))) { + GTP_ERROR("Get sensor data parameter error!"); + err = -EINVAL; + } else { + sensor_data = (struct hwm_sensor_data *) buff_out; + sensor_data->values[0] = gt1x_get_ps_value(); + sensor_data->value_divide = 1; + sensor_data->status = SENSOR_STATUS_ACCURACY_MEDIUM; + } + + break; + + default: + GTP_ERROR("proxmy sensor operate function no this parameter %d!\n", + command); + err = -1; + break; + } + + return err; +} +#endif + +#ifndef PLATFORM_MTK +static ssize_t gt1x_ps_enable_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d", + gt1x_ps_dev->enabled); +} + +static ssize_t gt1x_ps_enable_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + if(sscanf(buf, "%u", &input) != 1) { + return -EINVAL; + } + if(input == 1) { + gt1x_ps_enable(1); + gt1x_ps_report(PS_FARAWAY); + } else if(input == 0) { + gt1x_ps_report(PS_FARAWAY); + gt1x_ps_enable(0); + } else { + return -EINVAL; + } + return count; +} + +static ssize_t gt1x_ps_state_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d", gt1x_ps_dev->state); +} + +static ssize_t gt1x_ps_state_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + if(sscanf(buf, "%u", &input) != 1) { + return -EINVAL; + } + + if (!gt1x_ps_dev->enabled) { + return -EINVAL; + } + + if(input == 1) { + gt1x_ps_dev->state = PS_FARAWAY; + } else if(input == 0) { + gt1x_ps_dev->state = PS_NEAR; + } else { + return -EINVAL; + } + + gt1x_ps_report(gt1x_ps_dev->state); + return count; +} + +static struct kobj_attribute ps_attrs[] = { + __ATTR(enable, S_IWUSR | S_IWGRP | S_IRUSR | S_IRGRP, + gt1x_ps_enable_show, gt1x_ps_enable_store), + __ATTR(state, S_IWUSR | S_IWGRP | S_IRUSR | S_IRGRP, + gt1x_ps_state_show, gt1x_ps_state_store) +}; + +#endif /* End PLATFORM_MTK */ + +static int gt1x_ps_init(void) +{ + int err; + + gt1x_ps_dev = kzalloc(sizeof(struct gt1x_ps_device), GFP_KERNEL); + if (!gt1x_ps_dev) { + return -ENOMEM; + } + + gt1x_ps_dev->state = PS_FARAWAY; + +#ifdef PLATFORM_MTK + gt1x_ps_dev->obj_ps.polling = 0; + gt1x_ps_dev->obj_ps.sensor_operate = gt1x_ps_operate; + + if ((err = hwmsen_attach(ID_PROXIMITY, >1x_ps_dev->obj_ps))) { + GTP_ERROR("hwmsen attach fail, return:%d.", err); + goto err_exit; + } + + GTP_INFO("hwmsen attach OK."); + return 0; +#else + gt1x_ps_dev->input_dev = input_allocate_device(); + if(!gt1x_ps_dev->input_dev) { + GTP_ERROR("Failed to alloc inpput device for proximity!"); + err = -ENOMEM; + goto err_exit; + } + + gt1x_ps_dev->input_dev->name = GTP_PS_DEV_NAME; + gt1x_ps_dev->input_dev->phys = "goodix/proximity"; + gt1x_ps_dev->input_dev->id.bustype = BUS_I2C; + gt1x_ps_dev->input_dev->id.vendor = 0xDEED; + gt1x_ps_dev->input_dev->id.product = 0xBEEF; + gt1x_ps_dev->input_dev->id.version = 1; + set_bit(EV_ABS, gt1x_ps_dev->input_dev->evbit); + input_set_abs_params(gt1x_ps_dev->input_dev, ABS_DISTANCE, 0, 1, 0, 0); + + err = input_register_device(gt1x_ps_dev->input_dev); + if(err) { + GTP_ERROR("Failed to register proximity input device: %s!", + gt1x_ps_dev->input_dev->name); + goto err_register_dev; + } + /* register sysfs interface */ + if (!sysfs_rootdir) { + sysfs_rootdir = kobject_create_and_add("goodix", NULL); + if(!sysfs_rootdir){ + GTP_ERROR("Failed to create and add sysfs interface: goodix."); + err = -ENOMEM; + goto err_register_dev; + } + } + + gt1x_ps_dev->kobj = kobject_create_and_add("proximity", sysfs_rootdir); + if(!gt1x_ps_dev->kobj){ + GTP_ERROR("Failed to create and add sysfs interface: proximity."); + err = -ENOMEM; + goto err_register_dev; + } + // create sysfs files + { + int i; + for(i = 0; i < sizeof(ps_attrs)/sizeof(ps_attrs[0]); i++) { + if((err = sysfs_create_file(gt1x_ps_dev->kobj, &ps_attrs[i].attr))) { + goto err_create_file; + } + } + } + + GTP_INFO("Proximity sensor init OK."); + return 0; +err_create_file: + kobject_put(gt1x_ps_dev->kobj); +err_register_dev: + input_free_device(gt1x_ps_dev->input_dev); +#endif /* End PLATFROM_MTK */ + +err_exit: + kfree(gt1x_ps_dev); + gt1x_ps_dev = NULL; + return err; +} + +static void gt1x_ps_deinit(void) +{ + if(gt1x_ps_dev) { +#ifndef PLATFORM_MTK + int i = 0; + for(; i < sizeof(ps_attrs) / sizeof(ps_attrs[0]); i++) { + sysfs_remove_file(gt1x_ps_dev->kobj, &ps_attrs[i].attr); + } + kobject_del(gt1x_ps_dev->kobj); + input_free_device(gt1x_ps_dev->input_dev); +#endif + kfree(gt1x_ps_dev); + } +} + +#endif /*GTP_PROXIMITY */ + +/** + * ESD Protect Module + */ +#ifdef CONFIG_GTP_ESD_PROTECT +static int esd_work_cycle = 200; +static struct delayed_work esd_check_work; +static int esd_running = 0; +static struct mutex esd_lock; +static void gt1x_esd_check_func(struct work_struct *); + +void gt1x_init_esd_protect(void) +{ + esd_work_cycle = 2 * HZ; // HZ: clock ticks in 1 second generated by system + GTP_DEBUG("Clock ticks for an esd cycle: %d", esd_work_cycle); + INIT_DELAYED_WORK(&esd_check_work, gt1x_esd_check_func); + mutex_init(&esd_lock); +} + +static void gt1x_deinit_esd_protect(void) +{ + gt1x_esd_switch(SWITCH_OFF); +} + +void gt1x_esd_switch(s32 on) +{ + mutex_lock(&esd_lock); + if (SWITCH_ON == on) { /* switch on esd check */ + if (!esd_running) { + esd_running = 1; + GTP_INFO("Esd protector started!"); + queue_delayed_work(gt1x_workqueue, + &esd_check_work, esd_work_cycle); + } + } else { /* switch off esd check */ + if (esd_running) { + esd_running = 0; + GTP_INFO("Esd protector stoped!"); + cancel_delayed_work(&esd_check_work); + } + } + mutex_unlock(&esd_lock); +} + +static void gt1x_esd_check_func(struct work_struct *work) +{ + s32 i = 0; + s32 ret = -1; + u8 esd_buf[4] = { 0 }; + + if (!esd_running) { + GTP_INFO("Esd protector suspended!"); + return; + } + + for (i = 0; i < 3; i++) { + ret = gt1x_i2c_read(GTP_REG_CMD, esd_buf, 4); + GTP_DEBUG("[Esd]0x8040 = 0x%02X, 0x8043 = 0x%02X", + esd_buf[0], esd_buf[3]); + if (!ret && esd_buf[0] != 0xAA && esd_buf[3] == 0xAA) { + break; + } + msleep(50); + } + + if (likely(i < 3)) { + /* IC works normally, Write 0x8040 0xAA, feed the watchdog */ + gt1x_send_cmd(GTP_CMD_ESD, 0); + } else { + if (esd_running) { + GTP_ERROR("IC works abnormally! Process reset guitar."); + memset(esd_buf, 0x01, sizeof(esd_buf)); + gt1x_i2c_write(0x4226, esd_buf, sizeof(esd_buf)); + msleep(50); + + gt1x_power_reset(); + } else { + GTP_INFO("Esd protector suspended, no need reset!"); + } + } + + mutex_lock(&esd_lock); + if (esd_running) { + queue_delayed_work(gt1x_workqueue, &esd_check_work, esd_work_cycle); + } else { + GTP_INFO("Esd protector suspended!"); + } + mutex_unlock(&esd_lock); +} +#endif + +/** + * Smart Cover Module + */ +#ifdef CONFIG_GTP_SMART_COVER +struct smart_cover_device{ + int enabled; + int state; // 0:cover faraway 1:near + int suspended; // suspended or woring + struct kobject *kobj; + u8 config[GTP_CONFIG_ORG_LENGTH + GTP_CONFIG_EXT_LENGTH]; + int cfg_len; +}; +static struct smart_cover_device *gt1x_sc_dev; + +/** + * gt1x_smart_cover_update_state - update smart cover config + */ +static int gt1x_smart_cover_update_state(void) +{ + int ret = 0; + struct smart_cover_device *dev = gt1x_sc_dev; + + if (!dev) { + return -ENODEV; + } + + if(!dev->suspended) { + if(dev->state) { /* near */ + ret = gt1x_send_cfg(dev->config, dev->cfg_len); + } else { + #ifdef CONFIG_GTP_CHARGER_SWITCH + gt1x_charger_config(1); // charger detector module check and + // send a config + #else + ret = gt1x_send_cfg(gt1x_config, gt1x_cfg_length); + #endif + } + GTP_DEBUG("Update cover state %s.", dev->state ? "Nearby" : "Far away"); + } else { + GTP_DEBUG("TP is suspended, do nothing."); + } + return ret; +} + +static ssize_t smart_cover_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct smart_cover_device *dev = gt1x_sc_dev; + + if (!dev) { + return -ENODEV; + } + + return scnprintf(buf, PAGE_SIZE, "%d", dev->state); +} + +static ssize_t smart_cover_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + struct smart_cover_device *dev = gt1x_sc_dev; + int s = (buf[0] - '0'); + + if (!dev || !dev->enabled || s > 1 || s == dev->state) { + return count; + } + + dev->state = s; + gt1x_smart_cover_update_state(); + + return count; +} + +/** + * gt1x_parse_sc_cfg - parse smart cover config + * @sensor_id: sensor id of the hardware + */ +int gt1x_parse_sc_cfg(int sensor_id) +{ +#ifdef CONFIG_MTK_PLATFORM + struct device *dev = tpd->tpd_dev; +#else + struct device *dev = >1x_i2c_client->dev; +#endif + u8 *cfg; + int size; + + if (!gt1x_sc_dev) + return -ENODEV; + cfg = gt1x_sc_dev->config; + + size = gt1x_find_tp_config(dev, "smartcover-config", + sensor_id, cfg); + if (size < 0) { + GTP_ERROR("Failed to obtain smartcover config"); + return size; + } + + cfg[0] &= 0x7F; + set_reg_bit(cfg[TRIGGER_LOC], 0, gt1x_int_type); + set_reg_bit(cfg[MODULE_SWITCH3_LOC], 5, !gt1x_wakeup_level); + gt1x_sc_dev->cfg_len = size; + return 0; +} + +static struct kobj_attribute sc_attr = + __ATTR(state, S_IWUSR | S_IWGRP | S_IRUSR | S_IRGRP, + smart_cover_show, smart_cover_store); + +static int gt1x_smart_cover_init(void) +{ + int err = 0; + + gt1x_sc_dev = kzalloc(sizeof(struct smart_cover_device), GFP_KERNEL); + if (!gt1x_sc_dev) { + GTP_ERROR("SmartCover init failed in step: 1."); + return -ENOMEM; + } + + gt1x_sc_dev->enabled = 1; + gt1x_parse_sc_cfg(gt1x_version.sensor_id); + + if (!sysfs_rootdir) { + // this kobject is shared between modules, do not free it when error occur + sysfs_rootdir = kobject_create_and_add(GOODIX_SYSFS_DIR, NULL); + if (!sysfs_rootdir) { + err = -2; + goto exit_free_mem; + } + } + + if (!gt1x_sc_dev->kobj) + gt1x_sc_dev->kobj = kobject_create_and_add("smartcover", + sysfs_rootdir); + if (!gt1x_sc_dev->kobj) { + err = -3; + goto exit_free_mem; + } + + if(sysfs_create_file(gt1x_sc_dev->kobj, &sc_attr.attr)) { + err = -4; + goto exit_put_kobj; + } + + GTP_INFO("SmartCover module init OK."); + return 0; + +exit_put_kobj: + kobject_put(gt1x_sc_dev->kobj); +exit_free_mem: + kfree(gt1x_sc_dev); + gt1x_sc_dev = NULL; + GTP_ERROR("SmartCover init failed in step:%d", -err); + return err; +} + +static void gt1x_smart_cover_deinit(void) +{ + if (!gt1x_sc_dev) { + return; + } + + kobject_del(gt1x_sc_dev->kobj); + kfree(gt1x_sc_dev); + gt1x_sc_dev = NULL; +} +#endif + +/** + * Charger Detect & Switch Module + */ +#ifdef CONFIG_GTP_CHARGER_SWITCH +static u8 gt1x_config_charger[GTP_CONFIG_ORG_LENGTH + + GTP_CONFIG_EXT_LENGTH] = { 0 }; +static struct delayed_work charger_switch_work; +static int charger_work_cycle = 200; +static spinlock_t charger_lock; +static int charger_running = 0; +static void gt1x_charger_work_func(struct work_struct *); + +/** + * gt1x_parse_chr_cfg - parse charger config + * @sensor_id: sensor id of the hardware + * Return: 0: succeed, <0 error + */ +int gt1x_parse_chr_cfg(int sensor_id) +{ +#ifdef CONFIG_MTK_PLATFORM + struct device *dev = tpd->tpd_dev; +#else + struct device *dev = >1x_i2c_client->dev; +#endif + u8 *cfg = gt1x_config_charger; + int len; + + len = gt1x_find_tp_config(dev, "charger-config", + sensor_id, cfg); + if (len < 0) { + GTP_ERROR("Failed to obtain charger config"); + return len; + } + + cfg[0] &= 0x7F; + cfg[RESOLUTION_LOC] = (u8) gt1x_abs_x_max; + cfg[RESOLUTION_LOC + 1] = (u8) (gt1x_abs_x_max >> 8); + cfg[RESOLUTION_LOC + 2] = (u8) gt1x_abs_y_max; + cfg[RESOLUTION_LOC + 3] = (u8) (gt1x_abs_y_max >> 8); + set_reg_bit(cfg[TRIGGER_LOC], 0, gt1x_int_type); + set_reg_bit(cfg[MODULE_SWITCH3_LOC], 5, !gt1x_wakeup_level); + return 0; +} + + +static void gt1x_init_charger(void) +{ + charger_work_cycle = 2 * HZ; // HZ: clock ticks in 1 second generated by system + GTP_DEBUG("Clock ticks for an charger cycle: %d", charger_work_cycle); + INIT_DELAYED_WORK(&charger_switch_work, gt1x_charger_work_func); + spin_lock_init(&charger_lock); + + if (gt1x_parse_chr_cfg(gt1x_version.sensor_id) < 0) { + GTP_ERROR("Error occured when parse charger config."); + } +} + +/** + * gt1x_charger_switch - switch states of charging work thread + * + * @on: SWITCH_ON - start work thread, SWITCH_OFF: stop . + * + */ +void gt1x_charger_switch(s32 on) +{ + spin_lock(&charger_lock); + if (SWITCH_ON == on) { + if (!charger_running) { + charger_running = 1; + spin_unlock(&charger_lock); + GTP_INFO("Charger checker started!"); + queue_delayed_work(gt1x_workqueue, + &charger_switch_work, charger_work_cycle); + } else { + spin_unlock(&charger_lock); + } + } else { + if (charger_running) { + charger_running = 0; + spin_unlock(&charger_lock); + cancel_delayed_work(&charger_switch_work); + GTP_INFO("Charger checker stoped!"); + } else { + spin_unlock(&charger_lock); + } + } +} + +/** + * gt1x_charger_config - check and update charging status configuration + * @dir_update + * 0: check before send charging status configuration + * 1: directly send charging status configuration + * + */ +void gt1x_charger_config(s32 dir_update) +{ + static u8 chr_pluggedin = 0; + +#ifdef CONFIG_GTP_SMART_COVER + if (gt1x_sc_dev && gt1x_sc_dev->enabled + && gt1x_sc_dev->state) { + return; + } +#endif + + if (gt1x_get_charger_status()) { + if (!chr_pluggedin || dir_update) { + GTP_INFO("Charger Plugin."); + if (gt1x_send_cfg(gt1x_config_charger, gt1x_cfg_length)) { + GTP_ERROR("Send config for Charger Plugin failed!"); + } + if (gt1x_send_cmd(GTP_CMD_CHARGER_ON, 0)) { + GTP_ERROR("Update status for Charger Plugin failed!"); + } + chr_pluggedin = 1; + } + } else { + if (chr_pluggedin || dir_update) { + GTP_INFO("Charger Plugout."); + if (gt1x_send_cfg(gt1x_config, gt1x_cfg_length)) { + GTP_ERROR("Send config for Charger Plugout failed!"); + } + if (gt1x_send_cmd(GTP_CMD_CHARGER_OFF, 0)) { + GTP_ERROR("Update status for Charger Plugout failed!"); + } + chr_pluggedin = 0; + } + } +} + +static void gt1x_charger_work_func(struct work_struct *work) +{ + if (!charger_running) { + GTP_INFO("Charger checker suspended!"); + return; + } + + gt1x_charger_config(0); + + GTP_DEBUG("Charger check done!"); + if (charger_running) { + queue_delayed_work(gt1x_workqueue, + &charger_switch_work, charger_work_cycle); + } +} +#endif + +int gt1x_suspend(void) +{ + s32 ret = -1; +#if defined(CONFIG_GTP_HOTKNOT) && !defined(CONFIG_HOTKNOT_BLOCK_RW) + u8 buf[1] = { 0 }; +#endif + + if (update_info.status) { + return 0; + } +#ifdef CONFIG_GTP_SMART_COVER + if (gt1x_sc_dev) { + gt1x_sc_dev->suspended = 1; + } +#endif + GTP_INFO("Suspend start..."); +#ifdef CONFIG_GTP_PROXIMITY + if (gt1x_ps_dev && gt1x_ps_dev->enabled) { + GTP_INFO("proximity is detected!"); + return 0; + } +#endif + +#ifdef CONFIG_GTP_HOTKNOT + if (hotknot_enabled) { +#ifdef CONFIG_HOTKNOT_BLOCK_RW + if (hotknot_paired_flag) { + GTP_INFO("hotknot is paired!"); + return 0; + } +#else + ret = gt1x_i2c_read_dbl_check(GTP_REG_HN_PAIRED, buf, sizeof(buf)); + if ((!ret && buf[0] == 0x55) || hotknot_transfer_mode) { + GTP_DEBUG("0x81AA: 0x%02X", buf[0]); + GTP_INFO("hotknot is paired!"); + return 0; + } +#endif + } +#endif + + gt1x_halt = 1; +#ifdef CONFIG_GTP_ESD_PROTECT + gt1x_esd_switch(SWITCH_OFF); +#endif +#ifdef CONFIG_GTP_CHARGER_SWITCH + gt1x_charger_switch(SWITCH_OFF); +#endif + gt1x_irq_disable(); + +#ifdef CONFIG_GTP_GESTURE_WAKEUP + gesture_clear_wakeup_data(); + if (gesture_enabled) { + gesture_enter_doze(); + gt1x_irq_enable(); +#ifndef CONFIG_MTK_PLATFORM + enable_irq_wake(gt1x_i2c_client->irq); +#endif + gt1x_halt = 0; + } else +#endif + { + ret = gt1x_enter_sleep(); + if (ret < 0) { + GTP_ERROR("Suspend failed."); + } + } + + /* to avoid waking up while not sleeping + delay 48 + 10ms to ensure reliability */ + msleep(58); + GTP_INFO("Suspend end..."); + return 0; +} + +int gt1x_resume(void) +{ + s32 ret = -1; + + if (update_info.status) { + return 0; + } + +#ifdef CONFIG_GTP_SMART_COVER + if (gt1x_sc_dev) { + gt1x_sc_dev->suspended = 0; + } +#endif + GTP_INFO("Resume start..."); + +#ifdef CONFIG_GTP_PROXIMITY + if (gt1x_ps_dev && gt1x_ps_dev->enabled) { + GTP_INFO("Proximity is on!"); + return 0; + } +#endif + +#ifdef CONFIG_GTP_HOTKNOT + if (hotknot_enabled) { + #ifdef CONFIG_HOTKNOT_BLOCK_RW + if (hotknot_paired_flag) { + hotknot_paired_flag = 0; + hotknot_wakeup_block(); + GTP_INFO("Hotknot is paired!"); + return 0; + } + #endif + } +#endif + +#ifdef CONFIG_GTP_GESTURE_WAKEUP + /* just return 0 if IC does not suspend */ + if (!gesture_enabled && !gt1x_halt){ + return 0; + } +#else + if (!gt1x_halt){ + return 0; + } +#endif + + ret = gt1x_wakeup_sleep(); + if (ret < 0) { + GTP_ERROR("Resume failed."); + } +#ifdef CONFIG_GTP_HOTKNOT + if (!hotknot_enabled) { + gt1x_send_cmd(GTP_CMD_HN_EXIT_SLAVE, 0); + } +#endif + +#ifdef CONFIG_GTP_CHARGER_SWITCH + gt1x_charger_config(0); + gt1x_charger_switch(SWITCH_ON); +#endif + + gt1x_halt = 0; + gt1x_irq_enable(); + +#ifdef CONFIG_GTP_ESD_PROTECT + gt1x_esd_switch(SWITCH_ON); +#endif + + GTP_DEBUG("Resume end."); + return 0; +} + +s32 gt1x_init(void) +{ + s32 ret = -1; + s32 retry = 0; + u8 reg_val[1]; + + while (retry++ < GTP_RETRY_3) { + gt1x_init_failed = 0; + /* check main system firmware */ + ret = gt1x_i2c_read_dbl_check(GTP_REG_FW_CHK_MAINSYS, reg_val, 1); + if (ret != 0) { + gt1x_init_failed = 1; + gt1x_reset_guitar(); + continue; + } else if (reg_val[0] != 0xBE) { + GTP_ERROR("Check main system not pass[0x%2X].", reg_val[0]); + gt1x_init_failed = 1; + msleep(20); + } + +#ifndef CONFIG_GTP_AUTO_UPDATE + /* debug info */ + ret = gt1x_i2c_read_dbl_check(GTP_REG_FW_CHK_SUBSYS, reg_val, 1); + if (!ret && reg_val[0] == 0xAA) { + GTP_ERROR("Check subsystem not pass[0x%2X].", reg_val[0]); + } +#endif + break; + } + + /* if the initialization fails, set default setting */ + if (gt1x_init_failed) { + GTP_ERROR("Init failed, use default setting"); + gt1x_abs_x_max = GTP_MAX_WIDTH; + gt1x_abs_y_max = GTP_MAX_HEIGHT; + gt1x_int_type = GTP_INT_TRIGGER; + gt1x_wakeup_level = GTP_WAKEUP_LEVEL; + } + + /* get chip type */ + ret = gt1x_get_chip_type(); + if (ret != 0) + GTP_ERROR("Get chip type failed!"); + + /* read version information */ + ret = gt1x_read_version(>1x_version); + if (ret != 0) + GTP_ERROR("Get verision failed!"); + + /* init and send configs */ + ret = gt1x_init_panel(); + if (ret != 0) + GTP_ERROR("Init panel failed."); + + gt1x_workqueue = create_singlethread_workqueue("gt1x_workthread"); + if (gt1x_workqueue == NULL) + GTP_ERROR("Create workqueue failed!"); + + /* init auxiliary node and functions */ + gt1x_init_debug_node(); + +#ifdef CONFIG_GTP_CREATE_WR_NODE + gt1x_init_tool_node(); +#endif + +#if defined(CONFIG_GTP_GESTURE_WAKEUP) || defined(CONFIG_GTP_HOTKNOT) + gt1x_init_node(); +#endif + +#ifdef CONFIG_GTP_PROXIMITY + gt1x_ps_init(); +#endif + +#ifdef CONFIG_GTP_CHARGER_SWITCH + gt1x_init_charger(); + gt1x_charger_config(1); + gt1x_charger_switch(SWITCH_ON); +#endif + +#ifdef CONFIG_GTP_SMART_COVER + gt1x_smart_cover_init(); +#endif + +#ifdef CONFIG_GTP_WITH_STYLUS + gt1x_pen_init(); +#endif + + return ret; +} + +void gt1x_deinit(void) +{ + gt1x_deinit_debug_node(); + +#if defined(CONFIG_GTP_GESTURE_WAKEUP) || defined(CONFIG_GTP_HOTKNOT) + gt1x_deinit_node(); +#endif + +#ifdef CONFIG_GTP_CREATE_WR_NODE + gt1x_deinit_tool_node(); +#endif + +#ifdef CONFIG_GTP_ESD_PROTECT + gt1x_deinit_esd_protect(); +#endif + +#ifdef CONFIG_GTP_CHARGER_SWITCH + gt1x_charger_switch(SWITCH_OFF); +#endif + +#ifdef CONFIG_GTP_PROXIMITY + gt1x_ps_deinit(); +#endif + +#ifdef CONFIG_GTP_SMART_COVER + gt1x_smart_cover_deinit(); +#endif + + if (sysfs_rootdir) { + kobject_del(sysfs_rootdir); + sysfs_rootdir = NULL; + } + + if (gt1x_workqueue) { + destroy_workqueue(gt1x_workqueue); + } + +} + diff --git a/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_generic.h b/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_generic.h new file mode 100755 index 000000000000..d26404e437a5 --- /dev/null +++ b/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_generic.h @@ -0,0 +1,532 @@ +/* drivers/input/touchscreen/gt1x_generic.h + * + * 2010 - 2017 Goodix Technology. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + * Version: 1.6 + */ + +#ifndef _GT1X_GENERIC_H_ +#define _GT1X_GENERIC_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif +#ifdef CONFIG_OF +#include +#include +#endif +#if defined(CONFIG_DRM_PANEL) +#include +#elif defined(CONFIG_FB) +#include +#include +#endif +#include + + +#define GTP_DRIVER_VERSION "V1.6<2016/11/02>" +#define GTP_I2C_NAME "Goodix-TS" +#define GT1X_DEBUG_PROC_FILE "gt1x_debug" +#define GTP_POLL_TIME 10 +#define GTP_ADDR_LENGTH 2 +#define GTP_CONFIG_MIN_LENGTH 186 +#define GTP_CONFIG_MAX_LENGTH 240 +#define GTP_CONFIG_ORG_LENGTH 239 +#define GTP_CONFIG_EXT_LENGTH 128 +#define GTP_MAX_I2C_XFER_LEN 250 +#define SWITCH_OFF 0 +#define SWITCH_ON 1 + +/* buffer used to store ges track points coor. */ +//#define GES_BUFFER_ADDR 0xA2A0 // GT1151 +//#define GES_BUFFER_ADDR 0x8A40 // GT9L +//#define GES_BUFFER_ADDR 0x9734 // GT1152 +#define GES_BUFFER_ADDR 0xBDA8 // GT9286 +//#define GES_BUFFER_ADDR 0xBC74 // GT6286 +#ifndef GES_BUFFER_ADDR +#warning [GOODIX] need define GES_BUFFER_ADDR . +#endif + +#define GT_DEBUG 1 + +#define GT_I2C_NAME "gt-ts" +#if GT_DEBUG +#define GT_LOG(fmt, args...) pr_err("[%s] %s %d: " fmt, GT_I2C_NAME, __func__, __LINE__, ##args) +#else +#define GT_LOG(fmt, args...) pr_info("[%s] %s %d: " fmt, GT_I2C_NAME, __func__, __LINE__, ##args) +#endif +#define GT_ERR(fmt, args...) pr_err("[%s] %s %d: " fmt, GT_I2C_NAME, __func__, __LINE__, ##args) + +#define KEY_GES_REGULAR KEY_F2 // regular gesture-key +#define KEY_GES_CUSTOM KEY_F3 //customize gesture-key + +//#define CONFIG_GTP_DEBUG_ON + +#ifdef CONFIG_GTP_DEBUG_ON +#define GTP_DEBUG_ON 1 +#else +#define GTP_DEBUG_ON 0 +#endif + +#ifdef CONFIG_GTP_DEBUG_ARRAY_ON +#define GTP_DEBUG_ARRAY_ON 1 +#else +#define GTP_DEBUG_ARRAY_ON 0 +#endif + +#ifdef CONFIG_GTP_DEBUG_FUNC_ON +#define GTP_DEBUG_FUNC_ON 1 +#else +#define GTP_DEBUG_FUNC_ON 0 +#endif + +#ifdef CONFIG_GTP_CUSTOM_CFG +#define GTP_MAX_HEIGHT 1080 +#define GTP_MAX_WIDTH 2160 +#define GTP_INT_TRIGGER 1 //0:Rising 1:Falling +#define GTP_WAKEUP_LEVEL 1 +#else +#define GTP_MAX_HEIGHT 4096 +#define GTP_MAX_WIDTH 4096 +#define GTP_INT_TRIGGER 1 +#define GTP_WAKEUP_LEVEL 1 +#endif + +#define GTP_MAX_TOUCH 10 + +#ifdef CONFIG_GTP_WITH_STYLUS +#define GTP_STYLUS_KEY_TAB {BTN_STYLUS, BTN_STYLUS2} +#endif + +#ifdef CONFIG_GTP_HAVE_TOUCH_KEY +#define GTP_KEY_TAB {KEY_BACK, KEY_HOMEPAGE, KEY_MENU, KEY_SEARCH} +#define GTP_MAX_KEY_NUM 4 +#endif + +#define GTP_REG_MATRIX_DRVNUM 0x8069 +#define GTP_REG_MATRIX_SENNUM 0x806A +#define GTP_REG_RQST 0x8044 +#define GTP_REG_BAK_REF 0x90EC +#define GTP_REG_MAIN_CLK 0x8020 +#define GTP_REG_HAVE_KEY 0x8057 +#define GTP_REG_HN_STATE 0x8800 + +#define GTP_REG_WAKEUP_GESTURE 0x814C +#define GTP_REG_WAKEUP_GESTURE_DETAIL 0xA2A0 // need change +#define GTP_BAK_REF_PATH "/data/gt1x_ref.bin" +#define GTP_MAIN_CLK_PATH "/data/gt1x_clk.bin" + +/* request type */ +#define GTP_RQST_CONFIG 0x01 +#define GTP_RQST_BAK_REF 0x02 +#define GTP_RQST_RESET 0x03 +#define GTP_RQST_MAIN_CLOCK 0x04 +#define GTP_RQST_HOTKNOT_CODE 0x20 +#define GTP_RQST_RESPONDED 0x00 +#define GTP_RQST_IDLE 0xFF + +#define HN_DEVICE_PAIRED 0x80 +#define HN_MASTER_DEPARTED 0x40 +#define HN_SLAVE_DEPARTED 0x20 +#define HN_MASTER_SEND 0x10 +#define HN_SLAVE_RECEIVED 0x08 + +/*Register define */ +#define GTP_READ_COOR_ADDR 0x814E +#define GTP_REG_CMD 0x8040 +#define GTP_REG_SENSOR_ID 0x814A +#define GTP_REG_CONFIG_DATA 0x8050 +#define GTP_REG_CONFIG_RESOLUTION 0x8051 +#define GTP_REG_CONFIG_TRIGGER 0x8056 +#define GTP_REG_CONFIG_CHECKSUM 0x813C +#define GTP_REG_CONFIG_UPDATE 0x813E +#define GTP_REG_EXT_CFG_FLAG 0x805A +#define GTP_REG_EXT_CONFIG 0xBF7B +#define GTP_REG_VERSION 0x8140 +#define GTP_REG_HW_INFO 0x4220 +#define GTP_REG_REFRESH_RATE 0x8056 +#define GTP_REG_ESD_CHECK 0x8043 +#define GTP_REG_FLASH_PASSBY 0x8006 +#define GTP_REG_HN_PAIRED 0x81AA +#define GTP_REG_HN_MODE 0x81A8 +#define GTP_REG_MODULE_SWITCH3 0x8058 +#define GTP_REG_FW_CHK_MAINSYS 0x41E4 +#define GTP_REG_FW_CHK_SUBSYS 0x5095 + +#define set_reg_bit(reg,pos,val) ((reg)=((reg) & (~(1<<(pos))))|(!!(val)<<(pos))) + +/* cmd define */ +#define GTP_CMD_SLEEP 0x05 +#define GTP_CMD_CHARGER_ON 0x06 +#define GTP_CMD_CHARGER_OFF 0x07 +#define GTP_CMD_GESTURE_WAKEUP 0x08 +#define GTP_CMD_CLEAR_CFG 0x10 +#define GTP_CMD_ESD 0xAA +#define GTP_CMD_HN_TRANSFER 0x22 +#define GTP_CMD_HN_EXIT_SLAVE 0x28 + +/* define offset in the config*/ +#define RESOLUTION_LOC (GTP_REG_CONFIG_RESOLUTION - GTP_REG_CONFIG_DATA) +#define TRIGGER_LOC (GTP_REG_CONFIG_TRIGGER - GTP_REG_CONFIG_DATA) +#define MODULE_SWITCH3_LOC (GTP_REG_MODULE_SWITCH3 - GTP_REG_CONFIG_DATA) + +#ifdef CONFIG_GTP_WARP_X_ON +#define GTP_WARP_X(x_max, x) ( x_max - 1 - x ) +#else +#define GTP_WARP_X(x_max, x) x +#endif + +#ifdef CONFIG_GTP_WARP_Y_ON +#define GTP_WARP_Y(y_max, y) ( y_max - 1 - y ) +#else +#define GTP_WARP_Y(y_max, y) y +#endif + +#define IS_NUM_OR_CHAR(x) (((x) >= 'A' && (x) <= 'Z') || ((x) >= '0' && (x) <= '9')) + +//Log define +#define GTP_INFO(fmt,arg...) printk("<>[%s:%d] "fmt"\n", __func__, __LINE__, ##arg) +#define GTP_ERROR(fmt,arg...) printk("<>[%s:%d] "fmt"\n", __func__, __LINE__, ##arg) +#define GTP_DEBUG(fmt,arg...) do{\ + if(GTP_DEBUG_ON)\ + printk("<>[%s:%d]"fmt"\n",__func__, __LINE__, ##arg);\ + }while(0) +#define GTP_DEBUG_ARRAY(array, num) do{\ + s32 i;\ + u8* a = array;\ + if(GTP_DEBUG_ARRAY_ON)\ + {\ + printk("<>");\ + for (i = 0; i < (num); i++)\ + {\ + printk("%02x ", (a)[i]);\ + if ((i + 1 ) %10 == 0)\ + {\ + printk("\n<>");\ + }\ + }\ + printk("\n");\ + }\ + }while(0) +#define GTP_DEBUG_FUNC() do{\ + if(GTP_DEBUG_FUNC_ON)\ + printk("<> Func:%s@Line:%d\n",__func__,__LINE__);\ + }while(0) + +#define GTP_SWAP(x, y) do{\ + typeof(x) z = x;\ + x = y;\ + y = z;\ + }while (0) + +#pragma pack(1) +struct gt1x_version_info { + u8 product_id[5]; + u32 patch_id; + u32 mask_id; + u8 sensor_id; + u8 match_opt; +}; +#pragma pack() + +typedef enum { + DOZE_DISABLED = 0, + DOZE_ENABLED = 1, + DOZE_WAKEUP = 2, +} DOZE_T; + +typedef enum { + CHIP_TYPE_GT1X = 0, + CHIP_TYPE_GT2X = 1, + CHIP_TYPE_NONE = 0xFF +} gt1x_chip_type_t; + +#define _ERROR(e) ((0x01 << e) | (0x01 << (sizeof(s32) * 8 - 1))) +#define ERROR _ERROR(1) //for common use +//system relevant +#define ERROR_IIC _ERROR(2) //IIC communication error. +#define ERROR_MEM _ERROR(3) //memory error. + +//system irrelevant +#define ERROR_HN_VER _ERROR(10) //HotKnot version error. +#define ERROR_CHECK _ERROR(11) //Compare src and dst error. +#define ERROR_RETRY _ERROR(12) //Too many retries. +#define ERROR_PATH _ERROR(13) //Mount path error +#define ERROR_FW _ERROR(14) +#define ERROR_FILE _ERROR(15) +#define ERROR_VALUE _ERROR(16) //Illegal value of variables + +#define GTP_RETRY_3 3 +#define GTP_RETRY_5 5 + +/* bit operation */ +#define SET_BIT(data, flag) ((data) |= (flag)) +#define CLR_BIT(data, flag) ((data) &= ~(flag)) +#define CHK_BIT(data, flag) ((data) & (flag)) + +/* touch states */ +#define BIT_TOUCH 0x01 +#define BIT_TOUCH_KEY 0x02 +#define BIT_STYLUS 0x04 +#define BIT_STYLUS_KEY 0x08 +#define BIT_HOVER 0x10 + +#include +struct i2c_msg; + +/* Export global variables and functions */ + +/* Export from gt1x_extents.c and gt1x_firmware.h */ +#ifdef CONFIG_GTP_HOTKNOT +extern u8 hotknot_enabled; +extern u8 hotknot_transfer_mode; +extern u8 gt1x_patch_jump_fw[]; +extern u8 hotknot_auth_fw[]; +extern u8 hotknot_transfer_fw[]; +extern void hotknot_wakeup_block(void); +#ifdef CONFIG_HOTKNOT_BLOCK_RW +extern s32 hotknot_paired_flag; +extern s32 hotknot_event_handler(u8 * data); +#endif +#endif //GTP_HOTKNOT + +extern s32 gt1x_init_node(void); +extern void gt1x_deinit_node(void); + +#ifdef CONFIG_GTP_GESTURE_WAKEUP +extern DOZE_T gesture_doze_status; +extern int gesture_enabled; +extern void gt1x_gesture_debug(int on) ; +extern s32 gesture_event_handler(struct input_dev *dev); +extern s32 gesture_enter_doze(void); +extern void gesture_clear_wakeup_data(void); +#endif + +struct goodix_pinctrl { + struct pinctrl *ts_pinctrl; + struct pinctrl_state *pinctrl_wakeup; + struct pinctrl_state *pinctrl_normal; + struct pinctrl_state *pinctrl_poweroff; + struct pinctrl_state *pinctrl_sleep; +}; + +/* Export from gt1x.c */ +extern struct goodix_pinctrl *gt_pinctrl; +extern void gt1x_touch_down(s32 x, s32 y, s32 size, s32 id); +extern void gt1x_touch_up(s32 id); +extern int gt1x_power_switch(s32 state); +extern int gt1x_vcc_i2c_switch(s32 state); +extern void gt1x_irq_enable(void); +extern void gt1x_irq_disable(void); +extern int gt1x_debug_proc(u8 * buf, int count); + +struct fw_update_info { + int update_type; + int status; + int progress; + int max_progress; + int force_update; + struct fw_info *firmware_info; + u32 fw_length; + const struct firmware *fw; + + // file update + char *fw_name; + u8 *buffer; + mm_segment_t old_fs; + struct file *fw_file; + + // header update + u8 *fw_data; +}; + +/* Export form gt1x_update.c */ +extern struct fw_update_info update_info; + +extern u8 gt1x_default_FW[]; +extern int gt1x_hold_ss51_dsp(void); +extern int gt1x_auto_update_proc(void *data); +extern int gt1x_update_firmware(void *filename); + +extern void gt1x_enter_update_mode(void); +extern void gt1x_leave_update_mode(void); +extern int gt1x_hold_ss51_dsp_no_reset(void); +extern int gt1x_load_patch(u8 * patch, u32 patch_size, int offset, int bank_size); +extern int gt1x_startup_patch(void); + +/* Export from gt1x_tool.c */ +#ifdef CONFIG_GTP_CREATE_WR_NODE +extern int gt1x_init_tool_node(void); +extern void gt1x_deinit_tool_node(void); +#endif + +/* Export from gt1x_generic.c */ +extern struct i2c_client *gt1x_i2c_client; + +extern gt1x_chip_type_t gt1x_chip_type; +extern struct gt1x_version_info gt1x_version; + +extern s32 _do_i2c_read(struct i2c_msg *msgs, u16 addr, u8 * buffer, s32 len); +extern s32 _do_i2c_write(struct i2c_msg *msg, u16 addr, u8 * buffer, s32 len); +extern s32 gt1x_i2c_write(u16 addr, u8 * buffer, s32 len); +extern s32 gt1x_i2c_read(u16 addr, u8 * buffer, s32 len); +extern s32 gt1x_i2c_read_dbl_check(u16 addr, u8 * buffer, s32 len); + +extern u8 gt1x_int_type; +extern u32 gt1x_abs_x_max; +extern u32 gt1x_abs_y_max; +extern u8 gt1x_init_failed; +extern int gt1x_halt; +extern volatile int gt1x_rawdiff_mode; + +extern s32 gt1x_init(void); +extern void gt1x_deinit(void); +extern s32 gt1x_read_version(struct gt1x_version_info *ver_info); +extern s32 gt1x_init_panel(void); +extern s32 gt1x_get_chip_type(void); +extern s32 gt1x_request_event_handler(void); +extern int gt1x_send_cmd(u8 cmd, u8 data); +extern s32 gt1x_send_cfg(u8 * config, int cfg_len); +extern void gt1x_select_addr(void); +extern s32 gt1x_reset_guitar(void); +extern void gt1x_power_reset(void); +extern int gt1x_parse_config(char *filename, u8 * gt1x_config); +extern s32 gt1x_touch_event_handler(u8 * data, struct input_dev *dev, struct input_dev *pen_dev); +extern int gt1x_suspend(void); +extern int gt1x_resume(void); + +#ifdef CONFIG_GTP_HAVE_TOUCH_KEY +extern const u16 gt1x_touch_key_array[]; +#endif + +#ifdef CONFIG_GTP_WITH_STYLUS +extern struct input_dev *pen_dev; +extern void gt1x_pen_up(s32 id); +extern void gt1x_pen_down(s32 x, s32 y, s32 size, s32 id); +#endif + +#ifdef CONFIG_GTP_PROXIMITY +extern u8 gt1x_proximity_flag; +extern int gt1x_prox_event_handler(u8 * data); +#endif + +#ifdef CONFIG_GTP_SMART_COVER +extern int gt1x_parse_sc_cfg(int sensor_id); +#endif + +#ifdef CONFIG_GTP_ESD_PROTECT +extern void gt1x_init_esd_protect(void); +extern void gt1x_esd_switch(s32 on); +#endif + +#ifdef CONFIG_GTP_CHARGER_SWITCH +extern u32 gt1x_get_charger_status(void); +extern void gt1x_charger_switch(s32 on); +extern void gt1x_charger_config(s32 dir_update); +extern int gt1x_parse_chr_cfg(int sensor_id); +#endif + +#define IIC_MAX_TRANSFER_SIZE 250 + +#ifdef CONFIG_MTK_PLATFORM +/* MTK platform */ +#include +#ifdef CONFIG_MTK_BOOT +#include "mt_boot_common.h" +#endif +#include +#include +#ifndef MT6589 +#include +#endif +#include "tpd.h" +#include "upmu_common.h" + +#define GTP_GPIO_AS_INT(pin) tpd_gpio_as_int(pin) +#define GTP_GPIO_OUTPUT(pin, level) tpd_gpio_output(pin, level) + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0)) +#define GTP_MTK_LEGACY +#endif + +#define PLATFORM_MTK +#define GTP_I2C_ADDRESS 0x5D +#define TPD_I2C_NUMBER 1 + +#ifdef CONFIG_MTK_I2C_EXTENSION +#define TPD_SUPPORT_I2C_DMA 1 +#else +#define TPD_SUPPORT_I2C_DMA 0 +#endif + +#if defined(CONFIG_MTK_LEGACY) +#define TPD_POWER_SOURCE_CUSTOM MT6328_POWER_LDO_VGP1 +#endif + +#ifdef MT6589 +extern void mt65xx_eint_unmask(unsigned int line); +extern void mt65xx_eint_mask(unsigned int line); +#define mt_eint_mask mt65xx_eint_mask +#define mt_eint_unmask mt65xx_eint_unmask +#endif + +#define IIC_DMA_MAX_TRANSFER_SIZE 250 +#define I2C_MASTER_CLOCK 300 +#define TPD_HAVE_CALIBRATION +#define TPD_CALIBRATION_MATRIX {962,0,0,0,1600,0,0,0}; + +extern void tpd_on(void); +extern void tpd_off(void); + +#else +/* Generic Platform(Qcom or othter) */ +#ifdef CONFIG_OF +extern int gt1x_rst_gpio; +extern int gt1x_int_gpio; +#define GTP_RST_PORT gt1x_rst_gpio +#define GTP_INT_PORT gt1x_int_gpio +#else +#define GTP_RST_PORT 102 +#define GTP_INT_PORT 52 +#endif + +#define GTP_GPIO_AS_INPUT(pin) gpio_direction_input(pin) +#define GTP_GPIO_AS_INT(pin) GTP_GPIO_AS_INPUT(pin) +#define GTP_GPIO_OUTPUT(pin,level) gpio_direction_output(pin,level) +#define GTP_IRQ_TAB {IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING,\ + IRQ_TYPE_LEVEL_LOW, IRQ_TYPE_LEVEL_HIGH} + +#endif /* CONFIG_MTK_PLATFORM */ + +#endif // _GT1X_GENERIC_H_ + diff --git a/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_tools.c b/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_tools.c new file mode 100755 index 000000000000..cd19c0d22aa1 --- /dev/null +++ b/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_tools.c @@ -0,0 +1,468 @@ +/* drivers/input/touchscreen/goodix_tool.c + * + * 2010 - 2017 Goodix Technology. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + * Version: 1.6 + */ + +#include +#include +#include +#include +#include "gt1x_generic.h" + +static ssize_t gt1x_tool_read(struct file *filp, char __user * buffer, size_t count, loff_t * ppos); +static ssize_t gt1x_tool_write(struct file *filp, const char __user *buffer, size_t count, loff_t * ppos); + + +static int gt1x_tool_release(struct inode *inode, struct file *filp); +static int gt1x_tool_open(struct inode *inode,struct file *file); + +#pragma pack(1) +typedef struct { + u8 wr; //write read flag£¬0:R 1:W 2:PID 3: + u8 flag; //0:no need flag/int 1: need flag 2:need int + u8 flag_addr[2]; //flag address + u8 flag_val; //flag val + u8 flag_relation; //flag_val:flag 0:not equal 1:equal 2:> 3:< + u16 circle; //polling cycle + u8 times; //plling times + u8 retry; //I2C retry times + u16 delay; //delay befor read or after write + u16 data_len; //data length + u8 addr_len; //address length + u8 addr[2]; //address + u8 res[3]; //reserved + u8 *data; //data pointer +} st_cmd_head; +#pragma pack() +static st_cmd_head cmd_head; + +static s32 DATA_LENGTH = 0; +static s8 IC_TYPE[16] = "GT1X"; + +#define UPDATE_FUNCTIONS +#define DATA_LENGTH_UINT 512 +#define CMD_HEAD_LENGTH (sizeof(st_cmd_head) - sizeof(u8*)) + +static char procname[20] = { 0 }; + +static struct proc_dir_entry *gt1x_tool_proc_entry; +static struct file_operations gt1x_tool_fops = { + .read = gt1x_tool_read, + .write = gt1x_tool_write, + .open = gt1x_tool_open, + .release = gt1x_tool_release, + .owner = THIS_MODULE, +}; + +static void set_tool_node_name(char *procname) +{ + + int v0 = 0, v1 = 0, v2 = 0; + + sscanf(UTS_RELEASE, "%d.%d.%d", &v0, &v1, &v2); + sprintf(procname, "gmnode%02d%02d%02d", v0, v1, v2); +} + +int gt1x_init_tool_node(void) +{ + memset(&cmd_head, 0, sizeof(cmd_head)); + cmd_head.wr = 1; //if the first operation is read, will return fail. + cmd_head.data = kzalloc(DATA_LENGTH_UINT, GFP_KERNEL); + if (NULL == cmd_head.data) { + GTP_ERROR("Apply for memory failed."); + return -1; + } + DATA_LENGTH = DATA_LENGTH_UINT - GTP_ADDR_LENGTH; + + set_tool_node_name(procname); + + gt1x_tool_proc_entry = proc_create(procname, 0666, NULL, >1x_tool_fops); + if (gt1x_tool_proc_entry == NULL) { + GTP_ERROR("CAN't create proc entry /proc/%s.", procname); + return -1; + } else { + GTP_INFO("Created proc entry /proc/%s.", procname); + } + return 0; +} + +void gt1x_deinit_tool_node(void) +{ + remove_proc_entry(procname, NULL); + kfree(cmd_head.data); + cmd_head.data = NULL; +} + +static s32 tool_i2c_read(u8 * buf, u16 len) +{ + u16 addr = (buf[0] << 8) + buf[1]; + if (!gt1x_i2c_read(addr, &buf[2], len)) { + return 1; + } + return -1; +} + +static s32 tool_i2c_write(u8 * buf, u16 len) +{ + u16 addr = (buf[0] << 8) + buf[1]; + if (!gt1x_i2c_write(addr, &buf[2], len - 2)) { + return 1; + } + return -1; +} + +static u8 relation(u8 src, u8 dst, u8 rlt) +{ + u8 ret = 0; + + switch (rlt) { + case 0: + ret = (src != dst) ? true : false; + break; + + case 1: + ret = (src == dst) ? true : false; + GTP_DEBUG("equal:src:0x%02x dst:0x%02x ret:%d.", src, dst, (s32) ret); + break; + + case 2: + ret = (src > dst) ? true : false; + break; + + case 3: + ret = (src < dst) ? true : false; + break; + + case 4: + ret = (src & dst) ? true : false; + break; + + case 5: + ret = (!(src | dst)) ? true : false; + break; + + default: + ret = false; + break; + } + + return ret; +} + +/******************************************************* +Function: + Comfirm function. +Input: + None. +Output: + Return write length. +********************************************************/ +static u8 comfirm(void) +{ + s32 i = 0; + u8 buf[32]; + + memcpy(buf, cmd_head.flag_addr, cmd_head.addr_len); + + for (i = 0; i < cmd_head.times; i++) { + if (tool_i2c_read(buf, 1) <= 0) { + GTP_ERROR("Read flag data failed!"); + return -1; + } + + if (true == relation(buf[GTP_ADDR_LENGTH], cmd_head.flag_val, cmd_head.flag_relation)) { + GTP_DEBUG("value at flag addr:0x%02x.", buf[GTP_ADDR_LENGTH]); + GTP_DEBUG("flag value:0x%02x.", cmd_head.flag_val); + break; + } + + msleep(cmd_head.circle); + } + + if (i >= cmd_head.times) { + GTP_ERROR("Didn't get the flag to continue!"); + return -1; + } + + return 0; +} + +/******************************************************* +Function: + Goodix tool write function. +Input: + standard proc write function param. +Output: + Return write length. +********************************************************/ +static ssize_t gt1x_tool_write(struct file *filp, const char __user * buff, size_t len, loff_t * data) +{ + u64 ret = 0; + u8 temp_data = 0; + + GTP_DEBUG_FUNC(); + //GTP_DEBUG_ARRAY((u8 *) buff, len); + + ret = copy_from_user(&cmd_head, buff, CMD_HEAD_LENGTH); + if (ret) { + GTP_ERROR("copy_from_user failed."); + } + + ret = copy_from_user(&temp_data, &buff[CMD_HEAD_LENGTH], 1); + if (ret) { + GTP_ERROR("copy_from_user failed."); + } + + GTP_DEBUG("wr :0x%02x.", cmd_head.wr); + /* + GTP_DEBUG("flag:0x%02x.", cmd_head.flag); + GTP_DEBUG("flag addr:0x%02x%02x.", cmd_head.flag_addr[0], cmd_head.flag_addr[1]); + GTP_DEBUG("flag val:0x%02x.", cmd_head.flag_val); + GTP_DEBUG("flag rel:0x%02x.", cmd_head.flag_relation); + GTP_DEBUG("circle :%d.", (s32)cmd_head.circle); + GTP_DEBUG("times :%d.", (s32)cmd_head.times); + GTP_DEBUG("retry :%d.", (s32)cmd_head.retry); + GTP_DEBUG("delay :%d.", (s32)cmd_head.delay); + GTP_DEBUG("data len:%d.", (s32)cmd_head.data_len); + GTP_DEBUG("addr len:%d.", (s32)cmd_head.addr_len); + GTP_DEBUG("addr:0x%02x%02x.", cmd_head.addr[0], cmd_head.addr[1]); + GTP_DEBUG("len:%d.", (s32)len); + GTP_DEBUG("buf[20]:0x%02x.", temp_data); + */ + + if (1 == cmd_head.wr) { + u16 addr, data_len, pos; + + if (1 == cmd_head.flag) { + if (comfirm()) { + GTP_ERROR("[WRITE]Comfirm fail!"); + return -1; + } + } else if (2 == cmd_head.flag) { + //Need interrupt! + } + + addr = (cmd_head.addr[0] << 8) + cmd_head.addr[1]; + data_len = cmd_head.data_len; + pos = 0; + while (data_len > 0) { + len = data_len > DATA_LENGTH ? DATA_LENGTH : data_len; + ret = copy_from_user(&cmd_head.data[GTP_ADDR_LENGTH], &buff[CMD_HEAD_LENGTH + pos], len); + if (ret) { + GTP_ERROR("[WRITE]copy_from_user failed."); + return -1; + } + cmd_head.data[0] = ((addr >> 8) & 0xFF); + cmd_head.data[1] = (addr & 0xFF); + + GTP_DEBUG_ARRAY(cmd_head.data, len + GTP_ADDR_LENGTH); + + if (tool_i2c_write(cmd_head.data, len + GTP_ADDR_LENGTH) <= 0) { + GTP_ERROR("[WRITE]Write data failed!"); + return -1; + } + addr += len; + pos += len; + data_len -= len; + } + + if (cmd_head.delay) { + msleep(cmd_head.delay); + } + + return cmd_head.data_len + CMD_HEAD_LENGTH; + } else if (3 == cmd_head.wr) { //gt1x unused + + memcpy(IC_TYPE, cmd_head.data, cmd_head.data_len); + return cmd_head.data_len + CMD_HEAD_LENGTH; + } else if (5 == cmd_head.wr) { //? + + //memcpy(IC_TYPE, cmd_head.data, cmd_head.data_len); + return cmd_head.data_len + CMD_HEAD_LENGTH; + } else if (7 == cmd_head.wr) { //disable irq! + gt1x_irq_disable(); +#ifdef CONFIG_GTP_ESD_PROTECT + gt1x_esd_switch(SWITCH_OFF); +#endif + return CMD_HEAD_LENGTH; + } else if (9 == cmd_head.wr) { //enable irq! + gt1x_irq_enable(); +#ifdef CONFIG_GTP_ESD_PROTECT + gt1x_esd_switch(SWITCH_ON); +#endif + return CMD_HEAD_LENGTH; + } else if (17 == cmd_head.wr) { + ret = copy_from_user(&cmd_head.data[GTP_ADDR_LENGTH], &buff[CMD_HEAD_LENGTH], cmd_head.data_len); + if (ret) { + GTP_ERROR("copy_from_user failed."); + return -1; + } + + if (cmd_head.data[GTP_ADDR_LENGTH]) { + GTP_DEBUG("gtp enter rawdiff."); + gt1x_rawdiff_mode = true; + } else { + gt1x_rawdiff_mode = false; + GTP_DEBUG("gtp leave rawdiff."); + } + + return CMD_HEAD_LENGTH; + } else if (11 == cmd_head.wr) { + gt1x_enter_update_mode(); + } else if (13 == cmd_head.wr) { + gt1x_leave_update_mode(); + } else if (15 == cmd_head.wr) { + struct task_struct *thrd = NULL; + memset(cmd_head.data, 0, cmd_head.data_len + 1); + //memcpy(cmd_head.data, &buff[CMD_HEAD_LENGTH], cmd_head.data_len); + ret = copy_from_user(cmd_head.data, &buff[CMD_HEAD_LENGTH], cmd_head.data_len); + if (ret) { + GTP_ERROR("copy_from_user failed."); + } + GTP_DEBUG("update firmware, filename: %s", cmd_head.data); + thrd = kthread_run(gt1x_update_firmware, (void *)cmd_head.data, "GT1x FW Update"); + if (IS_ERR(thrd)) { + return PTR_ERR(thrd); + } + } + + return CMD_HEAD_LENGTH; +} + +static u8 devicecount = 0; +static int gt1x_tool_open(struct inode *inode,struct file *file) +{ + if (devicecount > 0) { + return -ERESTARTSYS; + GTP_ERROR("tools open failed!"); + } + + devicecount++; + return 0; +} + +static int gt1x_tool_release(struct inode *inode, struct file *filp) +{ + devicecount--; + return 0; +} +/******************************************************* +Function: + Goodix tool read function. +Input: + standard proc read function param. +Output: + Return read length. +********************************************************/ +static ssize_t gt1x_tool_read(struct file *filp, char __user * buffer, size_t count, loff_t * ppos) +{ + s32 ret = 0; + + GTP_DEBUG_FUNC(); + if(*ppos) { + GTP_DEBUG("[PARAM]size: %zd, *ppos: %d", count, (int)*ppos); + *ppos = 0; + return 0; + } + + if (cmd_head.wr % 2) { + GTP_ERROR("[READ] invaild operator fail!"); + return -1; + } else if (!cmd_head.wr) { + /* general i2c read */ + u16 addr, data_len, len, loc; + + if (1 == cmd_head.flag) { + if (comfirm()) { + GTP_ERROR("[READ]Comfirm fail!"); + return -1; + } + } else if (2 == cmd_head.flag) { + //Need interrupt! + } + + addr = (cmd_head.addr[0] << 8) + cmd_head.addr[1]; + data_len = cmd_head.data_len; + loc = 0; + + GTP_DEBUG("[READ] ADDR:0x%04X.", addr); + GTP_DEBUG("[READ] Length: %d", data_len); + + if (cmd_head.delay) { + msleep(cmd_head.delay); + } + + while (data_len > 0) { + len = data_len > DATA_LENGTH ? DATA_LENGTH : data_len; + cmd_head.data[0] = (addr >> 8) & 0xFF; + cmd_head.data[1] = (addr & 0xFF); + if (tool_i2c_read(cmd_head.data, len) <= 0) { + GTP_ERROR("[READ]Read data failed!"); + return -1; + } + //memcpy(&buffer[loc], &cmd_head.data[GTP_ADDR_LENGTH], len); + if (copy_to_user(&buffer[loc], &cmd_head.data[GTP_ADDR_LENGTH], len)) + { + GTP_ERROR("[Read]copy_to_user failed."); + return -1; + } + data_len -= len; + addr += len; + loc += len; + GTP_DEBUG_ARRAY(&cmd_head.data[GTP_ADDR_LENGTH], len); + } + *ppos += cmd_head.data_len; + return cmd_head.data_len; + } else if (2 == cmd_head.wr) { + ret = copy_to_user(buffer, IC_TYPE, sizeof(IC_TYPE)); + *ppos += sizeof(IC_TYPE); + GTP_DEBUG("Return ic type:%s len:%jd.", IC_TYPE, sizeof(IC_TYPE)); + return ret; + } else if (4 == cmd_head.wr) + { + u8 progress_buf[4]; + /* read fw update progress */ + progress_buf[0] = update_info.progress >> 8; + progress_buf[1] = update_info.progress & 0xff; + progress_buf[2] = update_info.max_progress >> 8; + progress_buf[3] = update_info.max_progress & 0xff; + if (copy_to_user(buffer, progress_buf, 4)) + { + GTP_ERROR("[Read]copy_to_user failed."); + return -1; + } + *ppos += 4; + return 4; + } else if (6 == cmd_head.wr) { + //Read error code! + return -1; + } else if (8 == cmd_head.wr) { + /* Read driver version */ + s32 tmp_len; + tmp_len = strlen(GTP_DRIVER_VERSION); + //memcpy(buffer, GTP_DRIVER_VERSION, tmp_len); + if (copy_to_user(buffer, GTP_DRIVER_VERSION, tmp_len) || copy_to_user(&buffer[tmp_len], "\n", 1)) + { + GTP_ERROR("[Read]copy_to_user failed."); + return -1; + } + //buffer[tmp_len] = 0; + *ppos += tmp_len + 1; + return (tmp_len + 1); + } + *ppos += cmd_head.data_len; + return cmd_head.data_len; +} diff --git a/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_update.c b/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_update.c new file mode 100755 index 000000000000..4ab7a9beaa68 --- /dev/null +++ b/drivers/input/touchscreen/gt1x_v1_6_revised/gt1x_update.c @@ -0,0 +1,1494 @@ +/* drivers/input/touchscreen/gt1x_update.c + * + * 2010 - 2017 Goodix Technology. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be a reference + * to you, when you are integrating the GOODiX's CTP IC into your system, + * 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. + * + * Version: 1.6 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gt1x_generic.h" + +#define GT1X_FW_NAME "gt1x_fw.bin" +#define UPDATE_FILE_PATH_1 "/data/_goodix_update_.bin" +#define UPDATE_FILE_PATH_2 "/sdcard/_goodix_update_.bin" + +#define CONFIG_FILE_PATH_1 "/data/_gt1x_config_.cfg" +#define CONFIG_FILE_PATH_2 "/sdcard/_gt1x_config_.cfg" + +#define FOUND_FW_PATH_1 0x01 +#define FOUND_FW_PATH_2 0x02 +#define FOUND_CFG_PATH_1 0x04 +#define FOUND_CFG_PATH_2 0x08 + +#define PACK_SIZE 256 + +// hardware register define +#define _bRW_MISCTL__SRAM_BANK 0x4048 +#define _bRW_MISCTL__MEM_CD_EN 0x4049 +#define _bRW_MISCTL__CACHE_EN 0x404B +#define _bRW_MISCTL__TMR0_EN 0x40B0 +#define _rRW_MISCTL__SWRST_B0_ 0x4180 +#define _bWO_MISCTL__CPU_SWRST_PULSE 0x4184 +#define _rRW_MISCTL__BOOTCTL_B0_ 0x4190 +#define _rRW_MISCTL__BOOT_OPT_B0_ 0x4218 +#define _rRW_MISCTL__BOOT_CTL_ 0x5094 +#define _bRW_MISCTL__DSP_MCU_PWR_ 0x4010 +#define _bRW_MISCTL__PATCH_AREA_EN_ 0x404D + +/* + 1. firmware structure + header: 128b + + offset size content + 0 4 firmware length + 4 2 checksum + 6 6 target MASK name + 12 3 target MASK version + 15 6 TP subsystem PID + 21 3 TP subsystem version + 24 1 subsystem count + 25 1 chip type 0x91: GT1X, 0x92: GT2X + 26 6 reserved + 32 8 subsystem info[0] + 32 8 subsystem info[1] + ..... + 120 8 subsystem info[11] + + body: followed header + + 128 N0 subsystem[0] + 128+N0 N1 subsystem[1] + .... + + 2. subsystem info structure + offset size content + 0 1 subsystem type + 1 2 subsystem length + 3 2 stored address in flash addr = value * 256 + 5 3 reserved + +*/ + +#define FW_HEAD_SIZE 128 +#define FW_HEAD_SUBSYSTEM_INFO_SIZE 8 +#define FW_HEAD_OFFSET_SUBSYSTEM_INFO_BASE 32 + +#define FW_SECTION_TYPE_SS51_ISP 0x01 +#define FW_SECTION_TYPE_SS51_PATCH 0x02 +#define FW_SECTION_TYPE_SS51_PATCH_OVERLAY 0x03 +#define FW_SECTION_TYPE_DSP 0x04 +#define FW_SECTION_TYPE_HOTKNOT 0x05 +#define FW_SECTION_TYPE_GESTURE 0x06 +#define FW_SECTION_TYPE_GESTURE_OVERLAY 0x07 +#define FW_SECTION_TYPE_FLASHLESS_FAST_POWER 0x08 + +#define UPDATE_TYPE_FILE 1 +#define UPDATE_TYPE_REQUEST 2 + +#define UPDATE_STATUS_IDLE 0 +#define UPDATE_STATUS_RUNNING 1 +#define UPDATE_STATUS_ABORT 2 + +struct fw_subsystem_info { + int type; + int length; + u32 address; + int offset; +}; + +#pragma pack(1) +struct fw_info { + u32 length; + u16 checksum; + u8 target_mask[6]; + u8 target_mask_version[3]; + u8 pid[6]; + u8 version[3]; + u8 subsystem_count; + u8 chip_type; + u8 reserved[6]; + struct fw_subsystem_info subsystem[12]; +}; +#pragma pack() + +struct fw_update_info update_info = { + .status = UPDATE_STATUS_IDLE, + .progress = 0, + .max_progress = 9, + .force_update = 0 +}; + +int gt1x_update_prepare(char *filename); +int gt1x_check_firmware(void); +u8 *gt1x_get_fw_data(u32 offset, int length); +int gt1x_update_judge(void); +int gt1x_run_ss51_isp(u8 * ss51_isp, int length); +int gt1x_burn_subsystem(struct fw_subsystem_info *subsystem); +u16 gt1x_calc_checksum(u8 * fw, u32 length); +int gt1x_recall_check(u8 * chk_src, u16 start_rd_addr, u16 chk_length); +void gt1x_update_cleanup(void); +int gt1x_check_subsystem_in_flash(struct fw_subsystem_info *subsystem); +int gt1x_read_flash(u32 addr, int length); +int gt1x_error_erase(void); +void dump_to_file(u16 addr, int length, char *filepath); +int gt1x_update_firmware(void *filename); +int gt1x_auto_update_proc(void *data); +static int gt1x_search_update_files(void); + +int gt1x_hold_ss51_dsp(void); +void gt1x_leave_update_mode(void); + +/** + * @return: return 0 if success, otherwise return a negative number + * which contains the error code. + */ +s32 gt1x_check_fs_mounted(char *path_name) +{ + struct path root_path; + struct path path; + s32 err; + + err = kern_path("/", LOOKUP_FOLLOW, &root_path); + if (err) + return ERROR_PATH; + + err = kern_path(path_name, LOOKUP_FOLLOW, &path); + if (err) { + err = ERROR_PATH; + goto check_fs_fail; + } + + if (path.mnt->mnt_sb == root_path.mnt->mnt_sb) { + // not mounted + err = ERROR_PATH; + } else { + err = 0; + } + + path_put(&path); +check_fs_fail: + path_put(&root_path); + return err; +} + +int gt1x_i2c_write_with_readback(u16 addr, u8 * buffer, int length) +{ + u8 buf[100]; + int ret = gt1x_i2c_write(addr, buffer, length); + if (ret) { + return ret; + } + ret = gt1x_i2c_read(addr, buf, length); + if (ret) { + return ret; + } + if (memcmp(buf, buffer, length)) { + return ERROR_CHECK; + } + return 0; +} + +#define getU32(a) ((u32)getUint((u8 *)(a), 4)) +#define getU16(a) ((u16)getUint((u8 *)(a), 2)) +u32 getUint(u8 * buffer, int len) +{ + u32 num = 0; + int i; + for (i = 0; i < len; i++) { + num <<= 8; + num += buffer[i]; + } + return num; +} + +int gt1x_auto_update_proc(void *data) +{ + int ret; + char *filename; + u8 config[GTP_CONFIG_ORG_LENGTH + GTP_CONFIG_EXT_LENGTH] = { 0 }; + + if (data == NULL) { + GTP_INFO("Start auto update thread from request..."); + gt1x_update_firmware(NULL); + return 0; + } + + GTP_INFO("Start auto update thread from file..."); + ret = gt1x_search_update_files(); + if (ret & (FOUND_FW_PATH_1 | FOUND_FW_PATH_2)) { + if (ret & FOUND_FW_PATH_1) { + filename = UPDATE_FILE_PATH_1; + } else { + filename = UPDATE_FILE_PATH_2; + } + gt1x_update_firmware(filename); + } + + if (ret & (FOUND_CFG_PATH_1 | FOUND_CFG_PATH_2)) { + if (ret & FOUND_CFG_PATH_1) { + filename = CONFIG_FILE_PATH_1; + } else { + filename = CONFIG_FILE_PATH_2; + } + + if (gt1x_parse_config(filename, config) > 0) { + ret = gt1x_i2c_write(GTP_REG_CONFIG_DATA, config, GTP_CONFIG_ORG_LENGTH); + if (ret < 0) { + GTP_ERROR("Update config failed!"); + return 0; + } + + /* extends config */ + if (config[0x805A - GTP_REG_CONFIG_DATA] & 0x40) { + ret = gt1x_i2c_write(GTP_REG_EXT_CONFIG, + &config[GTP_CONFIG_ORG_LENGTH], GTP_CONFIG_EXT_LENGTH); + + if (ret < 0) { + GTP_ERROR("Update ext config failed!"); + return 0; + } + } + + GTP_INFO("Update config successfully!"); + } + } + + return 0; +} + +static int gt1x_search_update_files(void) +{ + int retry = 20 * 2; //wait 10s(max) if fs is not ready + struct file *pfile = NULL; + mm_segment_t old_fs; + int found = 0; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + + GTP_INFO("Search firmware file..."); + while (retry-- > 0) { + msleep(500); + + // check if rootfs is ready + if (gt1x_check_fs_mounted("/data")) { + GTP_DEBUG("filesystem is not ready"); + continue; + } + // search firmware + pfile = filp_open(UPDATE_FILE_PATH_1, O_RDONLY, 0); + if (IS_ERR(pfile)) { + pfile = filp_open(UPDATE_FILE_PATH_2, O_RDONLY, 0); + if (!IS_ERR(pfile)) { + found |= FOUND_FW_PATH_2; + } + } else { + found |= FOUND_FW_PATH_1; + } + + if (!IS_ERR(pfile)) { + filp_close(pfile, NULL); + } + // search config file + pfile = filp_open(CONFIG_FILE_PATH_1, O_RDONLY, 0); + if (IS_ERR(pfile)) { + pfile = filp_open(CONFIG_FILE_PATH_2, O_RDONLY, 0); + if (!IS_ERR(pfile)) { + found |= FOUND_CFG_PATH_2; + } + } else { + found |= FOUND_CFG_PATH_1; + } + if (!IS_ERR(pfile)) { + filp_close(pfile, NULL); + } + + if (found) { + break; + } + + GTP_INFO("Not found firmware or config file, retry."); + } + set_fs(old_fs); + + return found; +} + +void gt1x_enter_update_mode(void) +{ + GTP_DEBUG("Enter FW update mode."); +#ifdef CONFIG_GTP_ESD_PROTECT + gt1x_esd_switch(SWITCH_OFF); +#endif +#ifdef CONFIG_GTP_CHARGER_SWITCH + gt1x_charger_switch(SWITCH_OFF); +#endif + gt1x_irq_disable(); +} + +int gt1x_update_firmware(void *filename) +{ + int i = 0; + int ret = 0; + u8 *p; + + if (update_info.status != UPDATE_STATUS_IDLE) { + GTP_ERROR("Update process is running!"); + return ERROR; + } + update_info.status = UPDATE_STATUS_RUNNING; + update_info.progress = 0; + + gt1x_enter_update_mode(); + + ret = gt1x_update_prepare(filename); + if (ret) { + update_info.status = UPDATE_STATUS_ABORT; + goto gt1x_update_exit; + } + + ret = gt1x_check_firmware(); + if (ret) { + update_info.status = UPDATE_STATUS_ABORT; + goto gt1x_update_exit; + } +#ifdef CONFIG_GTP_FW_UPDATE_VERIFY + update_info.max_progress = + 6 + update_info.firmware_info->subsystem_count; +#else + update_info.max_progress = + 3 + update_info.firmware_info->subsystem_count; +#endif + update_info.progress++; // 1 + + ret = gt1x_update_judge(); + if (ret) { + update_info.status = UPDATE_STATUS_ABORT; + goto gt1x_update_exit; + } + update_info.progress++; // 2 + + p = gt1x_get_fw_data(update_info.firmware_info->subsystem[0].offset, update_info.firmware_info->subsystem[0].length); + if (p == NULL) { + GTP_ERROR("get isp fail"); + ret = ERROR_FW; + update_info.status = UPDATE_STATUS_ABORT; + goto gt1x_update_exit; + } + update_info.progress++; // 3 + + ret = gt1x_run_ss51_isp(p, update_info.firmware_info->subsystem[0].length); + if (ret) { + GTP_ERROR("run isp fail"); + goto gt1x_update_exit; + } + update_info.progress++; // 4 + msleep(800); + + for (i = 1; i < update_info.firmware_info->subsystem_count; i++) { + GTP_INFO("subsystem: %d", update_info.firmware_info->subsystem[i].type); + GTP_INFO("Length: %d", update_info.firmware_info->subsystem[i].length); + GTP_INFO("Address: %d", update_info.firmware_info->subsystem[i].address); + + ret = gt1x_burn_subsystem(&(update_info.firmware_info->subsystem[i])); + if (ret) { + GTP_ERROR("burn subsystem fail!"); + goto gt1x_update_exit; + } + update_info.progress++; + } + +#ifdef CONFIG_GTP_FW_UPDATE_VERIFY + gt1x_reset_guitar(); + + p = gt1x_get_fw_data(update_info.firmware_info->subsystem[0].offset, update_info.firmware_info->subsystem[0].length); + if (p == NULL) { + GTP_ERROR("get isp fail"); + ret = ERROR_FW; + goto gt1x_update_exit; + } + update_info.progress++; + + ret = gt1x_run_ss51_isp(p, update_info.firmware_info->subsystem[0].length); + if (ret) { + GTP_ERROR("run isp fail"); + goto gt1x_update_exit; + } + update_info.progress++; + + GTP_INFO("Reset guitar & check firmware in flash."); + for (i = 1; i < update_info.firmware_info->subsystem_count; i++) { + GTP_INFO("subsystem: %d", update_info.firmware_info->subsystem[i].type); + GTP_INFO("Length: %d", update_info.firmware_info->subsystem[i].length); + GTP_INFO("Address: %d", update_info.firmware_info->subsystem[i].address); + + ret = gt1x_check_subsystem_in_flash(&(update_info.firmware_info->subsystem[i])); + if (ret) { + gt1x_error_erase(); + break; + } + } + update_info.progress++; +#endif + +gt1x_update_exit: + gt1x_update_cleanup(); + gt1x_leave_update_mode(); + gt1x_read_version(NULL); + if (ret) { + update_info.progress = 2 * update_info.max_progress; + GTP_ERROR("Update firmware failed!"); + return ret; + } else if (gt1x_init_failed) { + gt1x_read_version(>1x_version); + gt1x_init_panel(); + #ifdef CONFIG_GTP_CHARGER_SWITCH + gt1x_parse_chr_cfg(gt1x_version.sensor_id); + #endif + #ifdef CONFIG_GTP_SMART_COVER + gt1x_parse_sc_cfg(gt1x_version.sensor_id); + #endif + } + GTP_INFO("Update firmware succeefully!"); + + return ret; +} + +int gt1x_update_prepare(char *filename) +{ + int ret = 0; + int retry = 5; + + if (filename == NULL) { + update_info.fw_name = NULL; + update_info.fw = NULL; + ret = request_firmware(&update_info.fw, GT1X_FW_NAME, >1x_i2c_client->dev); + if (ret < 0) { + GTP_ERROR("Request firmware failed - %s (%d)", GT1X_FW_NAME, ret); + return ERROR_FW; + } + + update_info.update_type = UPDATE_TYPE_REQUEST; + update_info.fw_data = (u8*)update_info.fw->data; + update_info.fw_length = update_info.fw->size; + } else { + GTP_INFO("Firmware: %s", filename); + update_info.old_fs = get_fs(); + set_fs(KERNEL_DS); + update_info.fw_name = filename; + update_info.update_type = UPDATE_TYPE_FILE; + update_info.fw_file = filp_open(update_info.fw_name, O_RDONLY, 0); + if (IS_ERR(update_info.fw_file)) { + GTP_ERROR("Open update file(%s) error!", update_info.fw_name); + set_fs(update_info.old_fs); + return ERROR_FILE; + } + update_info.fw_file->f_op->llseek(update_info.fw_file, 0, SEEK_SET); + update_info.fw_length = update_info.fw_file->f_op->llseek(update_info.fw_file, 0, SEEK_END); + } + + while (retry > 0) { + retry--; + update_info.firmware_info = (struct fw_info *)kzalloc(sizeof(struct fw_info), GFP_KERNEL); + if (update_info.firmware_info == NULL) { + GTP_INFO("Alloc %zu bytes memory fail.", sizeof(struct fw_info)); + continue; + } else { + break; + } + } + if (retry <= 0) { + ret = ERROR_RETRY; + goto gt1x_update_pre_fail1; + } + + retry = 5; + while (retry > 0) { + update_info.buffer = (u8 *) kzalloc(1024 * 4, GFP_KERNEL); + if (update_info.buffer == NULL) { + GTP_ERROR("Alloc %d bytes memory fail.", 1024 * 4); + continue; + } else { + break; + } + } + if (retry <= 0) { + ret = ERROR_RETRY; + goto gt1x_update_pre_fail0; + } + + return 0; + +gt1x_update_pre_fail0: + kfree(update_info.firmware_info); +gt1x_update_pre_fail1: + if (update_info.update_type == UPDATE_TYPE_REQUEST) { + release_firmware(update_info.fw); + update_info.fw = NULL; + } else if (update_info.update_type == UPDATE_TYPE_FILE) { + filp_close(update_info.fw_file, NULL); + } + + return ret; +} + +void gt1x_update_cleanup(void) +{ + if (update_info.update_type == UPDATE_TYPE_FILE) { + if (update_info.fw_file != NULL) { + filp_close(update_info.fw_file, NULL); + update_info.fw_file = NULL; + } + set_fs(update_info.old_fs); + } else if (update_info.update_type == UPDATE_TYPE_REQUEST) { + if (update_info.fw) { + release_firmware(update_info.fw); + update_info.fw = NULL; + } + } + + if (update_info.buffer != NULL) { + kfree(update_info.buffer); + update_info.buffer = NULL; + } + if (update_info.firmware_info != NULL) { + kfree(update_info.firmware_info); + update_info.firmware_info = NULL; + } +} + +int gt1x_check_firmware(void) +{ + u16 checksum; + u16 checksum_in_header; + u8 *p; + struct fw_info *firmware_temp; + int i; + int offset; + + // compare file length with the length field in the firmware header + if (update_info.fw_length < FW_HEAD_SIZE) { + GTP_ERROR("Bad firmware!(file length: %d)", update_info.fw_length); + return ERROR_CHECK; + } + p = gt1x_get_fw_data(0, 6); + if (p == NULL) { + return ERROR_FW; + } + + if (getU32(p) + 6 != update_info.fw_length) { + GTP_ERROR("Bad firmware!(file length: %d, header define: %d)", update_info.fw_length, getU32(p)); + return ERROR_CHECK; + } + // check firmware's checksum + checksum_in_header = getU16(&p[4]); + checksum = 0; + for (i = 6; i < update_info.fw_length; i++) { + p = gt1x_get_fw_data(i, 1); + if (p == NULL) { + return ERROR_FW; + } + checksum += p[0]; + } + + if (checksum != checksum_in_header) { + GTP_ERROR("Bad firmware!(checksum: 0x%04X, header define: 0x%04X)", checksum, checksum_in_header); + return ERROR_CHECK; + } + // parse firmware + p = gt1x_get_fw_data(0, FW_HEAD_SIZE); + if (p == NULL) { + return ERROR_FW; + } + memcpy((u8 *) update_info.firmware_info, p, FW_HEAD_SIZE - 8 * 12); + update_info.firmware_info->pid[5] = 0; + + p = &p[FW_HEAD_OFFSET_SUBSYSTEM_INFO_BASE]; + firmware_temp = update_info.firmware_info; + offset = FW_HEAD_SIZE; + for (i = 0; i < firmware_temp->subsystem_count; i++) { + firmware_temp->subsystem[i].type = p[i * FW_HEAD_SUBSYSTEM_INFO_SIZE]; + firmware_temp->subsystem[i].length = getU16(&p[i * FW_HEAD_SUBSYSTEM_INFO_SIZE + 1]); + firmware_temp->subsystem[i].address = getU16(&p[i * FW_HEAD_SUBSYSTEM_INFO_SIZE + 3]) * 256; + firmware_temp->subsystem[i].offset = offset; + offset += firmware_temp->subsystem[i].length; + } + + // print update information + GTP_INFO("Update type: %s", update_info.update_type == UPDATE_TYPE_REQUEST ? "RequestFW" : "FileFW"); + GTP_INFO("Firmware length: %d", update_info.fw_length); + GTP_INFO("Firmware product: GT%s", update_info.firmware_info->pid); + GTP_INFO("Firmware patch: %02X%02X%02X", update_info.firmware_info->version[0], update_info.firmware_info->version[1], update_info.firmware_info->version[2]); + GTP_INFO("Firmware chip: 0x%02X", update_info.firmware_info->chip_type); + GTP_INFO("Subsystem count: %d", update_info.firmware_info->subsystem_count); + for (i = 0; i < update_info.firmware_info->subsystem_count; i++) { + GTP_DEBUG("------------------------------------------"); + GTP_DEBUG("Subsystem: %d", i); + GTP_DEBUG("Type: %d", update_info.firmware_info->subsystem[i].type); + GTP_DEBUG("Length: %d", update_info.firmware_info->subsystem[i].length); + GTP_DEBUG("Address: 0x%08X", update_info.firmware_info->subsystem[i].address); + GTP_DEBUG("Offset: %d", update_info.firmware_info->subsystem[i].offset); + } + + return 0; +} + +/** + * @return: return a pointer pointed at the content of firmware + * if success, otherwise return NULL. + */ +u8 *gt1x_get_fw_data(u32 offset, int length) +{ + int ret; + if (update_info.update_type == UPDATE_TYPE_FILE) { + update_info.fw_file->f_op->llseek(update_info.fw_file, offset, SEEK_SET); + ret = update_info.fw_file->f_op->read(update_info.fw_file, (char *)update_info.buffer, length, &update_info.fw_file->f_pos); + if (ret < 0) { + GTP_ERROR("Read data error!"); + return NULL; + } + return update_info.buffer; + } else { + return &update_info.fw_data[offset]; + } +} + +int gt1x_update_judge(void) +{ + int ret; + u8 reg_val[2] = {0}; + u8 retry = 2; + struct gt1x_version_info ver_info; + struct gt1x_version_info fw_ver_info; + + fw_ver_info.mask_id = (update_info.firmware_info->target_mask_version[0] << 16) + | (update_info.firmware_info->target_mask_version[1] << 8) + | (update_info.firmware_info->target_mask_version[2]); + fw_ver_info.patch_id = (update_info.firmware_info->version[0] << 16) + | (update_info.firmware_info->version[1] << 8) + | (update_info.firmware_info->version[2]); + memcpy(fw_ver_info.product_id, update_info.firmware_info->pid, 4); + fw_ver_info.product_id[4] = 0; + + /* check fw status reg */ + do { + ret = gt1x_i2c_read_dbl_check(GTP_REG_FW_CHK_MAINSYS, reg_val, 1); + if (ret < 0) { /* read reg failed */ + goto _reset; + } else if (ret > 0) { + continue; + } + + ret = gt1x_i2c_read_dbl_check(GTP_REG_FW_CHK_SUBSYS, ®_val[1], 1); + if (ret < 0) { + goto _reset; + } else if (ret > 0) { + continue; + } + + break; +_reset: + gt1x_reset_guitar(); + }while (--retry); + + if (!retry) { + GTP_INFO("Update abort because of i2c error."); + return ERROR_CHECK; + } + if (reg_val[0] != 0xBE || reg_val[1] == 0xAA) { + GTP_INFO("Check fw status reg not pass,reg[0x814E]=0x%2X,reg[0x5095]=0x%2X!", + reg_val[0], reg_val[1]); + return 0; + } + +#ifdef CONFIG_GTP_DEBUG_ON + if (update_info.force_update) { + GTP_DEBUG("Debug mode, force update fw."); + return 0; + } +#endif + + ret = gt1x_read_version(&ver_info); + if (ret < 0) { + GTP_INFO("Get IC's version info failed, force update!"); + return 0; + } + if (memcmp(fw_ver_info.product_id, ver_info.product_id, 4)) { + GTP_INFO("Product id is not match!"); + return ERROR_CHECK; + } + if ((fw_ver_info.mask_id & 0xFFFFFF00) != (ver_info.mask_id & 0xFFFFFF00)) { + GTP_INFO("Mask id is not match!"); + return ERROR_CHECK; + } + if ((fw_ver_info.patch_id & 0xFF0000) != (ver_info.patch_id & 0xFF0000)){ + GTP_INFO("CID is not equal, need update!"); + return 0; + } + + if ((fw_ver_info.patch_id & 0xFFFF) <= (ver_info.patch_id & 0xFFFF)) { + GTP_INFO("The version of the fw is not high than the IC's!"); + return ERROR_CHECK; + } + return 0; +} + +int __gt1x_hold_ss51_dsp_20(void) +{ + int ret = -1; + int retry = 0; + u8 buf[1]; + int hold_times = 0; + + while (retry++ < 30) { + + // Hold ss51 & dsp + buf[0] = 0x0C; + ret = gt1x_i2c_write(_rRW_MISCTL__SWRST_B0_, buf, 1); + if (ret) { + GTP_ERROR("Hold ss51 & dsp I2C error,retry:%d", retry); + continue; + } + // Confirm hold + buf[0] = 0x00; + ret = gt1x_i2c_read(_rRW_MISCTL__SWRST_B0_, buf, 1); + if (ret) { + GTP_ERROR("Hold ss51 & dsp I2C error,retry:%d", retry); + continue; + } + if (0x0C == buf[0]) { + if (hold_times++ < 20) { + continue; + } else { + break; + } + } + GTP_ERROR("Hold ss51 & dsp confirm 0x4180 failed,value:%d", buf[0]); + } + if (retry >= 30) { + GTP_ERROR("Hold ss51&dsp failed!"); + return ERROR_RETRY; + } + + GTP_INFO("Hold ss51&dsp successfully."); + return 0; +} + +int gt1x_hold_ss51_dsp(void) +{ + int ret = ERROR, retry = 5; + u8 buffer[2]; + + do { + gt1x_select_addr(); + usleep_range(20000, 20010); + ret = gt1x_i2c_read(0x4220, buffer, 1); + } while (retry-- && ret < 0); + + if (ret < 0) + return ERROR; + + //hold ss51_dsp + ret = __gt1x_hold_ss51_dsp_20(); + if (ret) { + return ret; + } + // enable dsp & mcu power + buffer[0] = 0x00; + ret = gt1x_i2c_write_with_readback(_bRW_MISCTL__DSP_MCU_PWR_, buffer, 1); + if (ret) { + GTP_ERROR("enabel dsp & mcu power fail!"); + return ret; + } + // disable watchdog + buffer[0] = 0x00; + ret = gt1x_i2c_write_with_readback(_bRW_MISCTL__TMR0_EN, buffer, 1); + if (ret) { + GTP_ERROR("disable wdt fail!"); + return ret; + } + // clear cache + buffer[0] = 0x00; + ret = gt1x_i2c_write_with_readback(_bRW_MISCTL__CACHE_EN, buffer, 1); + if (ret) { + GTP_ERROR("clear cache fail!"); + return ret; + } + // soft reset + buffer[0] = 0x01; + ret = gt1x_i2c_write(_bWO_MISCTL__CPU_SWRST_PULSE, buffer, 1); + if (ret) { + GTP_ERROR("software reset fail!"); + return ret; + } + // set scramble + buffer[0] = 0x00; + ret = gt1x_i2c_write_with_readback(_rRW_MISCTL__BOOT_OPT_B0_, buffer, 1); + if (ret) { + GTP_ERROR("set scramble fail!"); + return ret; + } + + return 0; +} + +int gt1x_run_ss51_isp(u8 * ss51_isp, int length) +{ + int ret; + u8 buffer[10]; + + ret = gt1x_hold_ss51_dsp(); + if (ret) { + return ret; + } + // select bank4 + buffer[0] = 0x04; + ret = gt1x_i2c_write_with_readback(_bRW_MISCTL__SRAM_BANK, buffer, 1); + if (ret) { + GTP_ERROR("select bank4 fail."); + return ret; + } + // enable patch area access + buffer[0] = 0x01; + ret = gt1x_i2c_write_with_readback(_bRW_MISCTL__PATCH_AREA_EN_, buffer, 1); + if (ret) { + GTP_ERROR("enable patch area access fail!"); + return ret; + } + + GTP_INFO("ss51_isp length: %d, checksum: 0x%04X", length, gt1x_calc_checksum(ss51_isp, length)); + // load ss51 isp + ret = gt1x_i2c_write(0xC000, ss51_isp, length); + if (ret) { + GTP_ERROR("load ss51 isp fail!"); + return ret; + } + // recall compare + ret = gt1x_recall_check(ss51_isp, 0xC000, length); + if (ret) { + GTP_ERROR("recall check ss51 isp fail!"); + return ret; + } + + memset(buffer, 0xAA, 10); + ret = gt1x_i2c_write_with_readback(0x8140, buffer, 10); + + // disable patch area access + buffer[0] = 0x00; + ret = gt1x_i2c_write_with_readback(_bRW_MISCTL__PATCH_AREA_EN_, buffer, 1); + if (ret) { + GTP_ERROR("disable patch area access fail!"); + return ret; + } + // set 0x8006 + memset(buffer, 0x55, 8); + ret = gt1x_i2c_write_with_readback(0x8006, buffer, 8); + if (ret) { + GTP_ERROR("set 0x8006[0~7] 0x55 fail!"); + return ret; + } + // release ss51 + buffer[0] = 0x08; + ret = gt1x_i2c_write_with_readback(_rRW_MISCTL__SWRST_B0_, buffer, 1); + if (ret) { + GTP_ERROR("release ss51 fail!"); + return ret; + } + + msleep(100); + // check run state + ret = gt1x_i2c_read(0x8006, buffer, 2); + if (ret) { + GTP_ERROR("read 0x8006 fail!"); + return ret; + } + if (!(buffer[0] == 0xAA && buffer[1] == 0xBB)) { + GTP_ERROR("ERROR: isp is not running! 0x8006: %02X %02X", buffer[0], buffer[1]); + return ERROR_CHECK; + } + + return 0; +} + +u16 gt1x_calc_checksum(u8 * fw, u32 length) +{ + u32 i = 0; + u32 checksum = 0; + + for (i = 0; i < length; i += 2) { + checksum += (((int)fw[i]) << 8); + checksum += fw[i + 1]; + } + return (checksum & 0xFFFF); +} + +int gt1x_recall_check(u8 * chk_src, u16 start_addr, u16 chk_length) +{ + u8 rd_buf[PACK_SIZE]; + s32 ret = 0; + u16 len = 0; + u32 compared_length = 0; + + while (chk_length > 0) { + len = (chk_length > PACK_SIZE ? PACK_SIZE : chk_length); + + ret = gt1x_i2c_read(start_addr + compared_length, rd_buf, len); + if (ret) { + GTP_ERROR("recall i2c error,exit!"); + return ret; + } + + if (memcmp(rd_buf, &chk_src[compared_length], len)) { + GTP_ERROR("Recall frame not equal(addr: 0x%04X)", start_addr + compared_length); + GTP_DEBUG("chk_src array:"); + GTP_DEBUG_ARRAY(&chk_src[compared_length], len); + GTP_DEBUG("recall array:"); + GTP_DEBUG_ARRAY(rd_buf, len); + return ERROR_CHECK; + } + + chk_length -= len; + compared_length += len; + } + + GTP_DEBUG("Recall check %d bytes(address: 0x%04X) success.", compared_length, start_addr); + return 0; +} + +int gt1x_burn_subsystem(struct fw_subsystem_info *subsystem) +{ + int block_len; + u16 checksum; + int burn_len = 0; + u16 cur_addr; + u32 length = subsystem->length; + u8 buffer[10]; + int ret; + int wait_time; + int burn_state; + int retry = 5; + u8 *fw; + + GTP_INFO("Subsystem: %d", subsystem->type); + GTP_INFO("Length: %d", subsystem->length); + GTP_INFO("Address: 0x%08X", subsystem->address); + + while (length > 0 && retry > 0) { + retry--; + + block_len = length > 1024 * 4 ? 1024 * 4 : length; + + GTP_INFO("Burn block ==> length: %d, address: 0x%08X", block_len, subsystem->address + burn_len); + fw = gt1x_get_fw_data(subsystem->offset + burn_len, block_len); + if (fw == NULL) { + return ERROR_FW; + } + + cur_addr = ((subsystem->address + burn_len) >> 8); + + checksum = 0; + checksum += block_len; + checksum += cur_addr; + checksum += gt1x_calc_checksum(fw, block_len); + checksum = (0 - checksum); + + buffer[0] = ((block_len >> 8) & 0xFF); + buffer[1] = (block_len & 0xFF); + buffer[2] = ((cur_addr >> 8) & 0xFF); + buffer[3] = (cur_addr & 0xFF); + + ret = gt1x_i2c_write_with_readback(0x8100, buffer, 4); + if (ret) { + GTP_ERROR("write length & address fail!"); + continue; + } + + ret = gt1x_i2c_write(0x8100 + 4, fw, block_len); + if (ret) { + GTP_ERROR("write fw data fail!"); + continue; + } + + buffer[0] = ((checksum >> 8) & 0xFF); + buffer[1] = (checksum & 0xFF); + ret = gt1x_i2c_write_with_readback(0x8100 + 4 + block_len, buffer, 2); + if (ret) { + GTP_ERROR("write checksum fail!"); + continue; + } + + buffer[0] = 0; + ret = gt1x_i2c_write_with_readback(0x8022, buffer, 1); + if (ret) { + GTP_ERROR("clear control flag fail!"); + continue; + } + + buffer[0] = subsystem->type; + buffer[1] = subsystem->type; + ret = gt1x_i2c_write_with_readback(0x8020, buffer, 2); + if (ret) { + GTP_ERROR("write subsystem type fail!"); + continue; + } + burn_state = ERROR; + wait_time = 200; + msleep(5); + + while (wait_time-- > 0) { + u8 confirm = 0x55; + + ret = gt1x_i2c_read(0x8022, buffer, 1); + if (ret < 0) { + continue; + } + msleep(5); + ret = gt1x_i2c_read(0x8022, &confirm, 1); + if (ret < 0) { + continue; + } + if (buffer[0] != confirm) { + continue; + } + + if (buffer[0] == 0xAA) { + GTP_DEBUG("burning....."); + continue; + } else if (buffer[0] == 0xDD) { + GTP_ERROR("checksum error!"); + break; + } else if (buffer[0] == 0xBB) { + GTP_INFO("burning success."); + burn_state = 0; + break; + } else if (buffer[0] == 0xCC) { + GTP_ERROR("burning failed!"); + break; + } else { + GTP_DEBUG("unknown state!(0x8022: 0x%02X)", buffer[0]); + } + } + + if (!burn_state) { + length -= block_len; + burn_len += block_len; + retry = 5; + } + } + if (length == 0) { + return 0; + } else { + return ERROR_RETRY; + } +} + +int gt1x_check_subsystem_in_flash(struct fw_subsystem_info *subsystem) +{ + int block_len; + int checked_len = 0; + u32 length = subsystem->length; + int ret; + int check_state = 0; + int retry = 5; + u8 *fw; + + GTP_INFO("Subsystem: %d", subsystem->type); + GTP_INFO("Length: %d", subsystem->length); + GTP_INFO("Address: 0x%08X", subsystem->address); + + while (length > 0) { + block_len = length > 1024 * 4 ? 1024 * 4 : length; + + GTP_INFO("Check block ==> length: %d, address: 0x%08X", block_len, subsystem->address + checked_len); + fw = gt1x_get_fw_data(subsystem->offset + checked_len, block_len); + if (fw == NULL) { + return ERROR_FW; + } + ret = gt1x_read_flash(subsystem->address + checked_len, block_len); + if (ret) { + check_state |= ret; + } + + ret = gt1x_recall_check(fw, 0x8100, block_len); + if (ret) { + GTP_ERROR("Block in flash is broken!"); + check_state |= ret; + } + + length -= block_len; + checked_len += block_len; + retry = 5; + } + if (check_state) { + GTP_ERROR("Subsystem in flash is broken!"); + } else { + GTP_INFO("Subsystem in flash is correct!"); + } + return check_state; +} + +int gt1x_read_flash(u32 addr, int length) +{ + int wait_time; + int ret = 0; + u8 buffer[4]; + u16 read_addr = (addr >> 8); + + GTP_INFO("Read flash: 0x%04X, length: %d", addr, length); + + buffer[0] = 0; + ret = gt1x_i2c_write_with_readback(0x8022, buffer, 1); + + buffer[0] = ((length >> 8) & 0xFF); + buffer[1] = (length & 0xFF); + buffer[2] = ((read_addr >> 8) & 0xFF); + buffer[3] = (read_addr & 0xFF); + ret |= gt1x_i2c_write_with_readback(0x8100, buffer, 4); + + buffer[0] = 0xAA; + buffer[1] = 0xAA; + ret |= gt1x_i2c_write(0x8020, buffer, 2); + if (ret) { + GTP_ERROR("Error occured."); //comment + return ret; + } + + wait_time = 200; + while (wait_time > 0) { + wait_time--; + msleep(5); + ret = gt1x_i2c_read_dbl_check(0x8022, buffer, 1); + if (ret) { + continue; + } + if (buffer[0] == 0xBB) { + GTP_INFO("Read success(addr: 0x%04X, length: %d)", addr, length); + break; + } + } + if (wait_time == 0) { + GTP_ERROR("Read Flash FAIL!"); + return ERROR_RETRY; + } + return 0; +} + + +int gt1x_error_erase(void) +{ + int block_len; + u16 checksum; + u16 erase_addr; + u8 buffer[10]; + int ret; + int wait_time; + int burn_state = ERROR; + int retry = 5; + u8 *fw = NULL; + + GTP_INFO("Erase flash area of ss51."); + + gt1x_reset_guitar(); + + fw = gt1x_get_fw_data(update_info.firmware_info->subsystem[0].offset, + update_info.firmware_info->subsystem[0].length); + if (fw == NULL) { + GTP_ERROR("get isp fail"); + return ERROR_FW; + } + ret = gt1x_run_ss51_isp(fw, update_info.firmware_info->subsystem[0].length); + if (ret) { + GTP_ERROR("run isp fail"); + return ERROR_PATH; + } + + fw = kmalloc(1024 * 4, GFP_KERNEL); + if (!fw) { + GTP_ERROR("error when alloc mem."); + return ERROR_MEM; + } + + memset(fw, 0xFF, 1024 * 4); + erase_addr = 0x00; + block_len = 1024 * 4; + + while (retry-- > 0) { + + checksum = 0; + checksum += block_len; + checksum += erase_addr; + checksum += gt1x_calc_checksum(fw, block_len); + checksum = (0 - checksum); + + buffer[0] = ((block_len >> 8) & 0xFF); + buffer[1] = (block_len & 0xFF); + buffer[2] = ((erase_addr >> 8) & 0xFF); + buffer[3] = (erase_addr & 0xFF); + + ret = gt1x_i2c_write_with_readback(0x8100, buffer, 4); + if (ret) { + GTP_ERROR("write length & address fail!"); + continue; + } + + ret = gt1x_i2c_write(0x8100 + 4, fw, block_len); + if (ret) { + GTP_ERROR("write fw data fail!"); + continue; + } + + ret = gt1x_recall_check(fw, 0x8100 + 4, block_len); + if (ret) { + continue; + } + + buffer[0] = ((checksum >> 8) & 0xFF); + buffer[1] = (checksum & 0xFF); + ret = gt1x_i2c_write_with_readback(0x8100 + 4 + block_len, buffer, 2); + if (ret) { + GTP_ERROR("write checksum fail!"); + continue; + } + + buffer[0] = 0; + ret = gt1x_i2c_write_with_readback(0x8022, buffer, 1); + if (ret) { + GTP_ERROR("clear control flag fail!"); + continue; + } + + buffer[0] = FW_SECTION_TYPE_SS51_PATCH; + buffer[1] = FW_SECTION_TYPE_SS51_PATCH; + ret = gt1x_i2c_write_with_readback(0x8020, buffer, 2); + if (ret) { + GTP_ERROR("write subsystem type fail!"); + continue; + } + burn_state = ERROR; + wait_time = 200; + while (wait_time > 0) { + wait_time--; + msleep(5); + ret = gt1x_i2c_read_dbl_check(0x8022, buffer, 1); + if (ret) { + continue; + } + + if (buffer[0] == 0xAA) { + GTP_DEBUG("burning....."); + continue; + } else if (buffer[0] == 0xDD) { + GTP_ERROR("checksum error!"); + break; + } else if (buffer[0] == 0xBB) { + GTP_INFO("burning success."); + burn_state = 0; + break; + } else if (buffer[0] == 0xCC) { + GTP_ERROR("burning failed!"); + break; + } else { + GTP_DEBUG("unknown state!(0x8022: 0x%02X)", buffer[0]); + } + } + } + + kfree(fw); + if (burn_state == 0) { + return 0; + } else { + return ERROR_RETRY; + } +} + +void gt1x_leave_update_mode(void) +{ + GTP_DEBUG("Leave FW update mode."); + if (update_info.status != UPDATE_STATUS_ABORT) + gt1x_reset_guitar(); + +#ifdef CONFIG_GTP_CHARGER_SWITCH + gt1x_charger_switch(SWITCH_ON); +#endif +#ifdef CONFIG_GTP_ESD_PROTECT + gt1x_esd_switch(SWITCH_ON); +#endif + + update_info.status = UPDATE_STATUS_IDLE; + gt1x_irq_enable(); +} + +void dump_to_file(u16 addr, int length, char *filepath) +{ + struct file *flp = NULL; + u8 buf[128]; + const int READ_BLOCK_SIZE = 128; + int read_length = 0; + int len = 0; + + GTP_INFO("Dump(0x%04X, %d bytes) to file: %s\n", addr, length, filepath); + flp = filp_open(filepath, O_RDWR | O_CREAT, 0666); + if (IS_ERR(flp)) { + GTP_ERROR("can not open file: %s\n", filepath); + return; + } + flp->f_op->llseek(flp, 0, SEEK_SET); + + while (length > 0) { + len = (length > READ_BLOCK_SIZE ? READ_BLOCK_SIZE : length); + memset(buf, 0x33, len); + if (gt1x_i2c_read(addr + read_length, buf, len)) { + memset(buf, 0x33, len); + } + flp->f_op->write(flp, (char *)buf, len, &flp->f_pos); + read_length += len; + length -= len; + } + filp_close(flp, NULL); +} + +int gt1x_hold_ss51_dsp_no_reset(void) +{ + int ret = ERROR; + u8 buffer[2]; + + //hold ss51_dsp + ret = __gt1x_hold_ss51_dsp_20(); + if (ret) { + return ret; + } + // enable dsp & mcu power + buffer[0] = 0x00; + ret = gt1x_i2c_write_with_readback(_bRW_MISCTL__DSP_MCU_PWR_, buffer, 1); + if (ret) { + GTP_ERROR("enabel dsp & mcu power fail!"); + return ret; + } + // disable watchdog + buffer[0] = 0x00; + ret = gt1x_i2c_write_with_readback(_bRW_MISCTL__TMR0_EN, buffer, 1); + if (ret) { + GTP_ERROR("disable wdt fail!"); + return ret; + } + // clear cache + buffer[0] = 0x00; + ret = gt1x_i2c_write_with_readback(_bRW_MISCTL__CACHE_EN, buffer, 1); + if (ret) { + GTP_ERROR("clear cache fail!"); + return ret; + } + // soft reset + buffer[0] = 0x01; + ret = gt1x_i2c_write(_bWO_MISCTL__CPU_SWRST_PULSE, buffer, 1); + if (ret) { + GTP_ERROR("software reset fail!"); + return ret; + } + // set scramble + buffer[0] = 0x00; + ret = gt1x_i2c_write_with_readback(_rRW_MISCTL__BOOT_OPT_B0_, buffer, 1); + if (ret) { + GTP_ERROR("set scramble fail!"); + return ret; + } + + return 0; +} + +#define GT1X_LOAD_PACKET_SIZE (1024 * 2) + +int gt1x_load_patch(u8 * patch, u32 patch_size, int offset, int bank_size) +{ + s32 loaded_length = 0; + s32 len = 0; + s32 ret = 0; + u8 bank = 0, tmp; + u16 address; + + GTP_INFO("Load patch code(size: %d, checksum: 0x%04X, position: 0x%04X, bank-size: %d", patch_size, gt1x_calc_checksum(patch, patch_size), 0xC000 + offset, bank_size); + while (loaded_length != patch_size) { + if (loaded_length == 0 || (loaded_length + offset) % bank_size == 0) { + // select bank + bank = 0x04 + (loaded_length + offset) / bank_size; + ret = gt1x_i2c_write(_bRW_MISCTL__SRAM_BANK, &bank, 1); + if (ret) { + GTP_ERROR("select bank%d fail!", bank); + return ret; + } + GTP_INFO("Select bank%d success.", bank); + // enable patch area access + tmp = 0x01; + ret = gt1x_i2c_write_with_readback(_bRW_MISCTL__PATCH_AREA_EN_ + bank - 4, &tmp, 1); + if (ret) { + GTP_ERROR("enable patch area access fail!"); + return ret; + } + } + + len = patch_size - loaded_length > GT1X_LOAD_PACKET_SIZE ? GT1X_LOAD_PACKET_SIZE : patch_size - loaded_length; + address = 0xC000 + (loaded_length + offset) % bank_size; + + ret = gt1x_i2c_write(address, &patch[loaded_length], len); + if (ret) { + GTP_ERROR("load 0x%04X, %dbytes fail!", address, len); + return ret; + } + ret = gt1x_recall_check(&patch[loaded_length], address, len); + if (ret) { + GTP_ERROR("Recall check 0x%04X, %dbytes fail!", address, len); + return ret; + } + GTP_INFO("load code 0x%04X, %dbytes success.", address, len); + + loaded_length += len; + } + + return 0; +} + +int gt1x_startup_patch(void) +{ + s32 ret = 0; + u8 buffer[8] = { 0x55 }; + buffer[0] = 0x00; + buffer[1] = 0x00; + ret |= gt1x_i2c_write(_bRW_MISCTL__PATCH_AREA_EN_, buffer, 2); + + memset(buffer, 0x55, 8); + ret |= gt1x_i2c_write(GTP_REG_FLASH_PASSBY, buffer, 8); + ret |= gt1x_i2c_write(GTP_REG_VERSION, buffer, 5); + + buffer[0] = 0xAA; + ret |= gt1x_i2c_write(GTP_REG_CMD, buffer, 1); + ret |= gt1x_i2c_write(GTP_REG_ESD_CHECK, buffer, 1); + + buffer[0] = 0x00; + ret |= gt1x_i2c_write(_rRW_MISCTL__SWRST_B0_, buffer, 1); + + msleep(200); + + return ret; +} + diff --git a/drivers/input/touchscreen/synaptics_tcm/Kconfig b/drivers/input/touchscreen/synaptics_tcm/Kconfig index b217f5b6580f..cb483de57cb8 100644 --- a/drivers/input/touchscreen/synaptics_tcm/Kconfig +++ b/drivers/input/touchscreen/synaptics_tcm/Kconfig @@ -3,7 +3,7 @@ # menuconfig TOUCHSCREEN_SYNAPTICS_TCM bool "Synaptics TCM touchscreen" - default y + default n help Say Y here if you have a Synaptics TCM touchscreen connected to your system. diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index beae681810e3..61ae03a0a563 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -273,6 +273,7 @@ struct arm_smmu_device { u32 num_context_banks; u32 num_s2_context_banks; DECLARE_BITMAP(context_map, ARM_SMMU_MAX_CBS); + DECLARE_BITMAP(secure_context_map, ARM_SMMU_MAX_CBS); struct arm_smmu_cb *cbs; atomic_t irptndx; @@ -2785,6 +2786,11 @@ static void arm_smmu_destroy_domain_context(struct iommu_domain *domain) arm_smmu_unassign_table(smmu_domain); arm_smmu_secure_domain_unlock(smmu_domain); __arm_smmu_free_bitmap(smmu->context_map, cfg->cbndx); + /* As the nonsecure context bank index is any way set to zero, + * so, directly clearing up the secure cb bitmap. + */ + if (arm_smmu_is_slave_side_secure(smmu_domain)) + __arm_smmu_free_bitmap(smmu->secure_context_map, cfg->cbndx); arm_smmu_power_off(smmu->pwr); arm_smmu_domain_reinit(smmu_domain); @@ -3916,6 +3922,46 @@ static size_t msm_secure_smmu_map_sg(struct iommu_domain *domain, return ret; } +void *get_smmu_from_addr(struct iommu_device *iommu, void __iomem *addr) +{ + struct arm_smmu_device *smmu = NULL; + unsigned long base, mask; + + smmu = arm_smmu_get_by_fwnode(iommu->fwnode); + if (!smmu) + return NULL; + + base = (unsigned long)smmu->base; + mask = ~(smmu->size - 1); + + if ((base & mask) == ((unsigned long)addr & mask)) + return (void *)smmu; + + return NULL; +} + +bool arm_smmu_skip_write(void __iomem *addr) +{ + struct arm_smmu_device *smmu; + int cb; + + smmu = arm_smmu_get_by_addr(addr); + + /* Skip write if smmu not available by now */ + if (!smmu) + return true; + + /* Do not write to global space */ + if (((unsigned long)addr & (smmu->size - 1)) < (smmu->size >> 1)) + return true; + + /* Finally skip writing to secure CB */ + cb = ((unsigned long)addr & ((smmu->size >> 1) - 1)) >> PAGE_SHIFT; + if (test_bit(cb, smmu->secure_context_map)) + return true; + + return false; +} #endif static int arm_smmu_add_device(struct device *dev) @@ -4956,9 +5002,13 @@ static int arm_smmu_alloc_cb(struct iommu_domain *domain, cb = smmu->s2crs[idx].cbndx; } - if (cb >= 0 && arm_smmu_is_static_cb(smmu)) + if (cb >= 0 && arm_smmu_is_static_cb(smmu)) { smmu_domain->slave_side_secure = true; + if (arm_smmu_is_slave_side_secure(smmu_domain)) + bitmap_set(smmu->secure_context_map, cb, 1); + } + if (cb < 0 && !arm_smmu_is_static_cb(smmu)) { mutex_unlock(&smmu->stream_map_mutex); return __arm_smmu_alloc_bitmap(smmu->context_map, @@ -5765,7 +5815,8 @@ static int arm_smmu_device_remove(struct platform_device *pdev) if (arm_smmu_power_on(smmu->pwr)) return -EINVAL; - if (!bitmap_empty(smmu->context_map, ARM_SMMU_MAX_CBS)) + if (!bitmap_empty(smmu->context_map, ARM_SMMU_MAX_CBS) || + !bitmap_empty(smmu->secure_context_map, ARM_SMMU_MAX_CBS)) dev_err(&pdev->dev, "removing device with active domains!\n"); idr_destroy(&smmu->asid_idr); diff --git a/drivers/iommu/io-pgtable-msm-secure.c b/drivers/iommu/io-pgtable-msm-secure.c index 98990a5d5ca4..0d5025842e35 100644 --- a/drivers/iommu/io-pgtable-msm-secure.c +++ b/drivers/iommu/io-pgtable-msm-secure.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2018,2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "io-pgtable-msm-secure: " fmt @@ -66,7 +66,6 @@ int msm_iommu_sec_pgtbl_init(void) /* Now allocate memory for the secure page tables */ attrs = DMA_ATTR_NO_KERNEL_MAPPING; dev.coherent_dma_mask = DMA_BIT_MASK(sizeof(dma_addr_t) * 8); - arch_setup_dma_ops(&dev, 0, 0, NULL, 1); cpu_addr = dma_alloc_attrs(&dev, psize[0], &paddr, GFP_KERNEL, attrs); if (!cpu_addr) { pr_err("%s: Failed to allocate %d bytes for PTBL\n", diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 8b6cafb5b9f4..c732f1f50b0b 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -34,6 +34,9 @@ #include #include #include +#ifdef CONFIG_MSM_TZ_SMMU +#include +#endif static struct kset *iommu_group_kset; static DEFINE_IDA(iommu_group_ida); @@ -133,6 +136,26 @@ void iommu_device_unregister(struct iommu_device *iommu) spin_unlock(&iommu_device_lock); } +#ifdef CONFIG_MSM_TZ_SMMU +void *arm_smmu_get_by_addr(void __iomem *addr) +{ + struct iommu_device *iommu; + unsigned long flags; + void *smmu = NULL; + + spin_lock_irqsave(&iommu_device_lock, flags); + list_for_each_entry(iommu, &iommu_device_list, list) { + smmu = get_smmu_from_addr(iommu, addr); + if (!smmu) + continue; + break; + } + spin_unlock_irqrestore(&iommu_device_lock, flags); + + return smmu; +} +#endif + static struct iommu_domain *__iommu_domain_alloc(struct bus_type *bus, unsigned type); static int __iommu_attach_device(struct iommu_domain *domain, diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 3a01526f41ae..2c28f3a072b9 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -88,5 +88,5 @@ obj-$(CONFIG_MESON_IRQ_GPIO) += irq-meson-gpio.o obj-$(CONFIG_GOLDFISH_PIC) += irq-goldfish-pic.o obj-$(CONFIG_NDS32) += irq-ativic32.o obj-$(CONFIG_QCOM_PDC) += qcom-pdc.o -obj-$(CONFIG_QCOM_MPM) += qcom-mpm.o qcom-mpm-bengal.o qcom-mpm-scuba.o qcom-mpm-sdm660.o +obj-$(CONFIG_QCOM_MPM) += qcom-mpm.o qcom-mpm-bengal.o qcom-mpm-scuba.o qcom-mpm-sdm660.o qcom-mpm-msm8937.o qcom-mpm-msm8953.o obj-$(CONFIG_SIFIVE_PLIC) += irq-sifive-plic.o diff --git a/drivers/irqchip/qcom-mpm-msm8937.c b/drivers/irqchip/qcom-mpm-msm8937.c new file mode 100644 index 000000000000..72efbba3af8b --- /dev/null +++ b/drivers/irqchip/qcom-mpm-msm8937.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#include + +const struct mpm_pin mpm_msm8937_gic_chip_data[] = { + {2, 216}, + {49, 172}, + {53, 104}, + {58, 166}, + {62, 222}, + {-1}, +}; diff --git a/drivers/irqchip/qcom-mpm-msm8953.c b/drivers/irqchip/qcom-mpm-msm8953.c new file mode 100644 index 000000000000..d8ed5698cfb1 --- /dev/null +++ b/drivers/irqchip/qcom-mpm-msm8953.c @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#include + +const struct mpm_pin mpm_msm8953_gic_chip_data[] = { + {2, 216}, /* tsens_upper_lower_int */ + {37, 252}, /* qmp_usb3_lfps_rxterm_irq -> ss_phy_irq */ + {49, 168}, /* qusb2phy_dpse_hv -> hs_phy_irq*/ + {53, 104}, /* mdss_irq */ + {58, 168}, /* qusb2phy_dmse_hv -> hs_phy_irq*/ + {88, 222}, /* ee0_krait_hlos_spmi_periph_irq */ + {-1}, +}; diff --git a/drivers/irqchip/qcom-mpm.c b/drivers/irqchip/qcom-mpm.c index ab8a3b35f26b..7540a4600b16 100644 --- a/drivers/irqchip/qcom-mpm.c +++ b/drivers/irqchip/qcom-mpm.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2010-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2010-2021, The Linux Foundation. All rights reserved. */ #include @@ -596,6 +596,14 @@ static const struct of_device_id mpm_gic_chip_data_table[] = { .compatible = "qcom,mpm-gic-sdm660", .data = mpm_sdm660_gic_chip_data, }, + { + .compatible = "qcom,mpm-gic-msm8937", + .data = mpm_msm8937_gic_chip_data, + }, + { + .compatible = "qcom,mpm-gic-msm8953", + .data = mpm_msm8953_gic_chip_data, + }, {} }; MODULE_DEVICE_TABLE(of, mpm_gic_chip_data_table); diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 0dce3bf63b8c..a92e8970ceb4 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -799,6 +799,16 @@ config LEDS_QPNP_VIBRATOR_LDO peripheral found on Qualcomm Technologies, Inc. QPNP PMICs. The vibrator-ldo peripheral is capable of driving ERM vibrators. +config LEDS_QPNP_VIBRATOR + tristate "Vibrator support for QPNP PMIC" + depends on LEDS_CLASS && MFD_SPMI_PMIC + help + This option enables device driver support for the vibrator + on the Qualcomm technologies Inc's QPNP PMICs. The vibrator + is connected on the VIB_DRV_N line and can be controlled + manually or by the DTEST lines.It uses the android timed-output + framework. + comment "LED Triggers" source "drivers/leds/trigger/Kconfig" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index d3900c4046e5..43af291b9e1c 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -82,6 +82,7 @@ obj-$(CONFIG_LEDS_LM3601X) += leds-lm3601x.o obj-$(CONFIG_LEDS_QTI_TRI_LED) += leds-qti-tri-led.o obj-$(CONFIG_LEDS_QPNP_FLASH_V2) += leds-qpnp-flash-v2.o leds-qpnp-flash-common.o obj-$(CONFIG_LEDS_QPNP_VIBRATOR_LDO) += leds-qpnp-vibrator-ldo.o +obj-$(CONFIG_LEDS_QPNP_VIBRATOR) += leds-qpnp-vibrator.o # LED SPI Drivers obj-$(CONFIG_LEDS_CR0014114) += leds-cr0014114.o diff --git a/drivers/leds/leds-qpnp-vibrator.c b/drivers/leds/leds-qpnp-vibrator.c new file mode 100644 index 000000000000..493ff551816e --- /dev/null +++ b/drivers/leds/leds-qpnp-vibrator.c @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2013-2015, 2018-2019, 2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define QPNP_VIB_VTG_CTL(base) ((base) + 0x41) +#define QPNP_VIB_EN_CTL(base) ((base) + 0x46) + +#define QPNP_VIB_MAX_LEVEL 31 +#define QPNP_VIB_MIN_LEVEL 12 + +#define QPNP_VIB_DEFAULT_TIMEOUT 15000 +#define QPNP_VIB_DEFAULT_VTG_LVL 3100 + +#define QPNP_VIB_EN BIT(7) +#define QPNP_VIB_VTG_SET_MASK 0x1F +#define QPNP_VIB_LOGIC_SHIFT 4 + +enum qpnp_vib_mode { + QPNP_VIB_MANUAL, + QPNP_VIB_DTEST1, + QPNP_VIB_DTEST2, + QPNP_VIB_DTEST3, +}; + +struct qpnp_pwm_info { + struct pwm_device *pwm_dev; + u32 pwm_channel; + u32 duty_us; + u32 period_us; +}; + +struct qpnp_vib { + struct platform_device *pdev; + struct regmap *regmap; + struct hrtimer vib_timer; + struct led_classdev cdev; + struct work_struct work; + struct qpnp_pwm_info pwm_info; + enum qpnp_vib_mode mode; + u8 reg_vtg_ctl; + u8 reg_en_ctl; + u8 active_low; + u16 base; + int state; + int vtg_level; + int timeout; + u32 vib_play_ms; + struct mutex lock; +}; + +static int qpnp_vib_read_u8(struct qpnp_vib *vib, u8 *data, u16 reg) +{ + int rc; + + rc = regmap_read(vib->regmap, reg, (unsigned int *)data); + if (rc < 0) + dev_err(&vib->pdev->dev, + "Error reading address: %X - ret %X\n", reg, rc); + + return rc; +} + +static int qpnp_vib_write_u8(struct qpnp_vib *vib, u8 *data, u16 reg) +{ + int rc; + + rc = regmap_write(vib->regmap, reg, (unsigned int)*data); + if (rc < 0) + dev_err(&vib->pdev->dev, + "Error writing address: %X - ret %X\n", reg, rc); + + return rc; +} + +static int qpnp_vibrator_config(struct qpnp_vib *vib) +{ + u8 reg = 0; + int rc; + + /* Configure the VTG CTL regiser */ + rc = qpnp_vib_read_u8(vib, ®, QPNP_VIB_VTG_CTL(vib->base)); + if (rc < 0) + return rc; + + reg &= ~QPNP_VIB_VTG_SET_MASK; + reg |= (vib->vtg_level & QPNP_VIB_VTG_SET_MASK); + rc = qpnp_vib_write_u8(vib, ®, QPNP_VIB_VTG_CTL(vib->base)); + if (rc) + return rc; + + vib->reg_vtg_ctl = reg; + + /* Configure the VIB ENABLE regiser */ + rc = qpnp_vib_read_u8(vib, ®, QPNP_VIB_EN_CTL(vib->base)); + if (rc < 0) + return rc; + + reg |= (!!vib->active_low) << QPNP_VIB_LOGIC_SHIFT; + if (vib->mode != QPNP_VIB_MANUAL) { + vib->pwm_info.pwm_dev = pwm_request(vib->pwm_info.pwm_channel, + "qpnp-vib"); + if (IS_ERR_OR_NULL(vib->pwm_info.pwm_dev)) { + dev_err(&vib->pdev->dev, "vib pwm request failed\n"); + return -ENODEV; + } + + rc = pwm_config(vib->pwm_info.pwm_dev, vib->pwm_info.duty_us, + vib->pwm_info.period_us); + if (rc < 0) { + dev_err(&vib->pdev->dev, "vib pwm config failed\n"); + pwm_free(vib->pwm_info.pwm_dev); + return -ENODEV; + } + + reg |= BIT(vib->mode - 1); + } + + rc = qpnp_vib_write_u8(vib, ®, QPNP_VIB_EN_CTL(vib->base)); + if (rc < 0) + return rc; + + vib->reg_en_ctl = reg; + + return rc; +} + +static int qpnp_vib_set(struct qpnp_vib *vib, int on) +{ + int rc; + u8 val; + + if (on) { + if (vib->mode != QPNP_VIB_MANUAL) { + pwm_enable(vib->pwm_info.pwm_dev); + } else { + val = vib->reg_en_ctl; + val |= QPNP_VIB_EN; + rc = qpnp_vib_write_u8(vib, &val, + QPNP_VIB_EN_CTL(vib->base)); + if (rc < 0) + return rc; + vib->reg_en_ctl = val; + } + } else { + if (vib->mode != QPNP_VIB_MANUAL) { + pwm_disable(vib->pwm_info.pwm_dev); + } else { + val = vib->reg_en_ctl; + val &= ~QPNP_VIB_EN; + rc = qpnp_vib_write_u8(vib, &val, + QPNP_VIB_EN_CTL(vib->base)); + if (rc < 0) + return rc; + vib->reg_en_ctl = val; + } + } + + return 0; +} + +static void qpnp_vib_update(struct work_struct *work) +{ + struct qpnp_vib *vib = container_of(work, struct qpnp_vib, + work); + qpnp_vib_set(vib, vib->state); +} + +static enum hrtimer_restart qpnp_vib_timer_func(struct hrtimer *timer) +{ + struct qpnp_vib *vib = container_of(timer, struct qpnp_vib, + vib_timer); + + vib->state = 0; + schedule_work(&vib->work); + + return HRTIMER_NORESTART; +} + +#ifdef CONFIG_PM +static int qpnp_vibrator_suspend(struct device *dev) +{ + struct qpnp_vib *vib = dev_get_drvdata(dev); + + hrtimer_cancel(&vib->vib_timer); + cancel_work_sync(&vib->work); + /* turn-off vibrator */ + qpnp_vib_set(vib, 0); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(qpnp_vibrator_pm_ops, qpnp_vibrator_suspend, NULL); + +static int qpnp_vib_parse_dt(struct qpnp_vib *vib) +{ + struct platform_device *pdev = vib->pdev; + struct device_node *node = pdev->dev.of_node; + const char *mode; + u32 temp_val; + int rc; + + vib->timeout = QPNP_VIB_DEFAULT_TIMEOUT; + + rc = of_property_read_u32(node, "reg", &temp_val); + if (rc < 0) { + dev_err(&pdev->dev, + "Couldn't find reg in node = %s rc = %d\n", + node->full_name, rc); + return rc; + } + vib->base = temp_val; + + rc = of_property_read_u32(node, + "qcom,vib-timeout-ms", &temp_val); + if (!rc) { + vib->timeout = temp_val; + } else if (rc != -EINVAL) { + dev_err(&pdev->dev, "Unable to read vib timeout\n"); + return rc; + } + + vib->vtg_level = QPNP_VIB_DEFAULT_VTG_LVL; + rc = of_property_read_u32(node, + "qcom,vib-vtg-level-mV", &temp_val); + if (!rc) { + vib->vtg_level = temp_val; + } else if (rc != -EINVAL) { + dev_err(&pdev->dev, "Unable to read vtg level\n"); + return rc; + } + + vib->vtg_level /= 100; + if (vib->vtg_level < QPNP_VIB_MIN_LEVEL) + vib->vtg_level = QPNP_VIB_MIN_LEVEL; + else if (vib->vtg_level > QPNP_VIB_MAX_LEVEL) + vib->vtg_level = QPNP_VIB_MAX_LEVEL; + + vib->mode = QPNP_VIB_MANUAL; + rc = of_property_read_string(node, "qcom,mode", &mode); + if (!rc) { + if (strcmp(mode, "manual") == 0) { + vib->mode = QPNP_VIB_MANUAL; + } else if (strcmp(mode, "dtest1") == 0) { + vib->mode = QPNP_VIB_DTEST1; + } else if (strcmp(mode, "dtest2") == 0) { + vib->mode = QPNP_VIB_DTEST2; + } else if (strcmp(mode, "dtest3") == 0) { + vib->mode = QPNP_VIB_DTEST3; + } else { + dev_err(&pdev->dev, "Invalid mode\n"); + return -EINVAL; + } + } else if (rc != -EINVAL) { + dev_err(&pdev->dev, "Unable to read mode\n"); + return rc; + } + + if (vib->mode != QPNP_VIB_MANUAL) { + rc = of_property_read_u32(node, + "qcom,pwm-channel", &temp_val); + if (!rc) + vib->pwm_info.pwm_channel = temp_val; + else + return rc; + + rc = of_property_read_u32(node, + "qcom,period-us", &temp_val); + if (!rc) + vib->pwm_info.period_us = temp_val; + else + return rc; + + rc = of_property_read_u32(node, + "qcom,duty-us", &temp_val); + if (!rc) + vib->pwm_info.duty_us = temp_val; + else + return rc; + } + + vib->active_low = of_property_read_bool(node, + "qcom,active-low"); + + return 0; +} + +static ssize_t qpnp_vib_get_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct qpnp_vib *chip = container_of(cdev, struct qpnp_vib, cdev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", !!chip->reg_en_ctl); +} + +static ssize_t qpnp_vib_set_state(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + /* At present, nothing to do with setting state */ + return count; +} + +static ssize_t qpnp_vib_get_duration(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct qpnp_vib *vib = container_of(cdev, struct qpnp_vib, cdev); + ktime_t time_rem; + s64 time_us = 0; + + if (hrtimer_active(&vib->vib_timer)) { + time_rem = hrtimer_get_remaining(&vib->vib_timer); + time_us = ktime_to_us(time_rem); + } + + return scnprintf(buf, PAGE_SIZE, "%lld\n", div_s64(time_us, 1000)); +} + +static ssize_t qpnp_vib_set_duration(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct qpnp_vib *vib = container_of(cdev, struct qpnp_vib, cdev); + u32 value; + int rc; + + rc = kstrtouint(buf, 0, &value); + if (rc < 0) + return rc; + + /* setting 0 on duration is NOP for now */ + if (value <= 0) + return count; + + if (value > vib->timeout) + value = vib->timeout; + + mutex_lock(&vib->lock); + vib->vib_play_ms = value; + mutex_unlock(&vib->lock); + return count; +} + +static ssize_t qpnp_vib_get_activate(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct qpnp_vib *vib = container_of(cdev, struct qpnp_vib, cdev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", vib->state); +} + +static ssize_t qpnp_vib_set_activate(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct qpnp_vib *vib = container_of(cdev, struct qpnp_vib, cdev); + u32 value; + int rc; + + rc = kstrtouint(buf, 0, &value); + if (rc < 0) + return rc; + + if (value != 0 && value != 1) + return count; + + vib->state = value; + pr_debug("state = %d, time = %ums\n", vib->state, + vib->vib_play_ms); + mutex_lock(&vib->lock); + if (vib->state) { + hrtimer_cancel(&vib->vib_timer); + hrtimer_start(&vib->vib_timer, + ktime_set(vib->vib_play_ms / 1000, + (vib->vib_play_ms % 1000) * 1000000), + HRTIMER_MODE_REL); + } + mutex_unlock(&vib->lock); + schedule_work(&vib->work); + + return count; +} + +static struct device_attribute qpnp_vib_attrs[] = { + __ATTR(state, 0664, qpnp_vib_get_state, qpnp_vib_set_state), + __ATTR(duration, 0664, qpnp_vib_get_duration, qpnp_vib_set_duration), + __ATTR(activate, 0664, qpnp_vib_get_activate, qpnp_vib_set_activate), +}; + +/* Dummy functions for brightness */ +static +enum led_brightness qpnp_brightness_get(struct led_classdev *cdev) +{ + return 0; +} + +static void qpnp_brightness_set(struct led_classdev *cdev, + enum led_brightness level) +{ + +} + +static int qpnp_vibrator_probe(struct platform_device *pdev) +{ + struct qpnp_vib *vib; + int rc; + int i; + + vib = devm_kzalloc(&pdev->dev, sizeof(*vib), GFP_KERNEL); + if (!vib) + return -ENOMEM; + + vib->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!vib->regmap) { + dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); + return -EINVAL; + } + + vib->pdev = pdev; + + rc = qpnp_vib_parse_dt(vib); + if (rc) { + dev_err(&pdev->dev, "DT parsing failed\n"); + return rc; + } + + rc = qpnp_vibrator_config(vib); + if (rc) { + dev_err(&pdev->dev, "vib config failed\n"); + return rc; + } + + mutex_init(&vib->lock); + INIT_WORK(&vib->work, qpnp_vib_update); + + hrtimer_init(&vib->vib_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + vib->vib_timer.function = qpnp_vib_timer_func; + + vib->cdev.name = "vibrator"; + vib->cdev.brightness_get = qpnp_brightness_get; + vib->cdev.brightness_set = qpnp_brightness_set; + vib->cdev.max_brightness = 100; + rc = devm_led_classdev_register(&pdev->dev, &vib->cdev); + if (rc < 0) { + dev_err(&pdev->dev, "Error in registering led class device, rc=%d\n", + rc); + goto led_reg_fail; + } + + /* Enabling sysfs entries */ + for (i = 0; i < ARRAY_SIZE(qpnp_vib_attrs); i++) { + rc = sysfs_create_file(&vib->cdev.dev->kobj, + &qpnp_vib_attrs[i].attr); + if (rc < 0) { + dev_err(&pdev->dev, "Error in creating sysfs file, rc=%d\n", + rc); + goto sysfs_fail; + } + } + + dev_set_drvdata(&pdev->dev, vib); + return rc; + +sysfs_fail: + dev_set_drvdata(&pdev->dev, NULL); +led_reg_fail: + hrtimer_cancel(&vib->vib_timer); + cancel_work_sync(&vib->work); + mutex_destroy(&vib->lock); + return -EINVAL; +} + +static int qpnp_vibrator_remove(struct platform_device *pdev) +{ + struct qpnp_vib *vib = dev_get_drvdata(&pdev->dev); + int i; + + /* Removing sysfs entries */ + for (i = 0; i < ARRAY_SIZE(qpnp_vib_attrs); i++) + sysfs_remove_file(&vib->cdev.dev->kobj, + &qpnp_vib_attrs[i].attr); + + dev_set_drvdata(&pdev->dev, NULL); + hrtimer_cancel(&vib->vib_timer); + cancel_work_sync(&vib->work); + mutex_destroy(&vib->lock); + return 0; +} + +static const struct of_device_id spmi_match_table[] = { + { .compatible = "qcom,qpnp-vibrator", + }, + {} +}; + +static struct platform_driver qpnp_vibrator_driver = { + .driver = { + .name = "qcom,qpnp-vibrator", + .of_match_table = spmi_match_table, + .pm = &qpnp_vibrator_pm_ops, + }, + .probe = qpnp_vibrator_probe, + .remove = qpnp_vibrator_remove, +}; + +module_platform_driver(qpnp_vibrator_driver); + +MODULE_DESCRIPTION("qpnp vibrator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/md/dm-verity-target.c b/drivers/md/dm-verity-target.c index b1124acd03dc..47d756cd1f16 100644 --- a/drivers/md/dm-verity-target.c +++ b/drivers/md/dm-verity-target.c @@ -62,6 +62,14 @@ struct dm_verity_prefetch_work { struct buffer_aux { int hash_verified; }; +/* + * While system shutdown, skip verity work for I/O error. + */ +static inline bool verity_is_system_shutting_down(void) +{ + return system_state == SYSTEM_HALT || system_state == SYSTEM_POWER_OFF + || system_state == SYSTEM_RESTART; +} /* * Initialize struct buffer_aux for a freshly created buffer. @@ -564,7 +572,8 @@ static void verity_end_io(struct bio *bio) { struct dm_verity_io *io = bio->bi_private; - if (bio->bi_status && !verity_fec_is_enabled(io->v)) { + if (bio->bi_status && + (!verity_fec_is_enabled(io->v) || verity_is_system_shutting_down())) { verity_finish_io(io, bio->bi_status); return; } diff --git a/drivers/media/platform/msm/dvb/demux/mpq_dmx_plugin_common.c b/drivers/media/platform/msm/dvb/demux/mpq_dmx_plugin_common.c index c0d4a50c6049..7d32e7c86ea6 100644 --- a/drivers/media/platform/msm/dvb/demux/mpq_dmx_plugin_common.c +++ b/drivers/media/platform/msm/dvb/demux/mpq_dmx_plugin_common.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. */ #include @@ -273,12 +273,32 @@ static int mpq_dmx_dmabuf_map(int ion_fd, struct sg_table **sgt, return ret; } +/* + * CR-2864017: Deregister dma buffers from shmbridge here, because + * mpq_sdmx_destroy_shm_bridge_callback will not be called if the OMX + * application does't release buffers but reuse them even after switching + * PES filters. + */ static void mpq_dmx_dmabuf_unmap(struct sg_table *sgt, - struct dma_buf_attachment *attach, - struct dma_buf *dmabuf) + struct dma_buf_attachment *attach, + struct dma_buf *dmabuf) { + int ret = 0; + uint64_t handle = 0; + dma_buf_unmap_attachment(attach, sgt, DMA_BIDIRECTIONAL); dma_buf_detach(dmabuf, attach); + + handle = (uint64_t)dmabuf->dtor_data; + MPQ_DVB_DBG_PRINT("%s: to destroy shm bridge %lld\n", + __func__, handle); + ret = qtee_shmbridge_deregister(handle); + if (ret) { + MPQ_DVB_ERR_PRINT("%s: failed to destroy shm bridge %lld\n", + __func__, handle); + } + + dma_buf_set_destructor(dmabuf, NULL, NULL); dma_buf_put(dmabuf); } diff --git a/drivers/media/platform/msm/npu/npu_host_ipc.c b/drivers/media/platform/msm/npu/npu_host_ipc.c index 5ea693b1d3c5..64d91d9d87e4 100644 --- a/drivers/media/platform/msm/npu/npu_host_ipc.c +++ b/drivers/media/platform/msm/npu/npu_host_ipc.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2018-2021, The Linux Foundation. All rights reserved. */ /* ------------------------------------------------------------------------- @@ -295,7 +295,7 @@ static int ipc_queue_write(struct npu_device *npu_dev, uint32_t packet_size, new_write_idx; uint32_t empty_space; void *write_ptr; - uint32_t read_idx; + uint32_t read_idx, write_idx; size_t offset = (size_t)IPC_ADDR + sizeof(struct hfi_queue_tbl_header) + @@ -384,6 +384,23 @@ static int ipc_queue_write(struct npu_device *npu_dev, sizeof(queue.qhdr_rx_req)); *is_rx_req_set = (queue.qhdr_rx_req == 1) ? 1 : 0; + /* check if queue is empty (consumed by fw) */ + if (*is_rx_req_set) { + MEMR(npu_dev, (void *)((size_t)(offset + (uint32_t)( + (size_t)&(queue.qhdr_write_idx) - (size_t)&queue))), + (uint8_t *)&write_idx, + sizeof(queue.qhdr_write_idx)); + + MEMR(npu_dev, (void *)((size_t)(offset + (uint32_t)( + (size_t)&(queue.qhdr_read_idx) - (size_t)&queue))), + (uint8_t *)&read_idx, + sizeof(queue.qhdr_read_idx)); + + /* cmd has been consumed by fw, no need to trigger irq */ + if (read_idx == write_idx) + *is_rx_req_set = 0; + } + return status; } diff --git a/drivers/media/platform/msm/npu/npu_mgr.c b/drivers/media/platform/msm/npu/npu_mgr.c index dea1349f63e1..32adf1babe58 100644 --- a/drivers/media/platform/msm/npu/npu_mgr.c +++ b/drivers/media/platform/msm/npu/npu_mgr.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2018-2021, The Linux Foundation. All rights reserved. */ /* ------------------------------------------------------------------------- @@ -1661,6 +1661,7 @@ static int app_msg_proc(struct npu_host_ctx *host_ctx, uint32_t *msg) struct npu_misc_cmd *misc_cmd = NULL; int need_ctx_switch = 0; + memset(&kevt, 0, sizeof(kevt)); msg_id = msg[1]; switch (msg_id) { case NPU_IPC_MSG_EXECUTE_DONE: diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_base.c b/drivers/media/platform/msm/sde/rotator/sde_rotator_base.c index 9227c90e3657..e358d8b99ecc 100644 --- a/drivers/media/platform/msm/sde/rotator/sde_rotator_base.c +++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_base.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2012, 2015-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2012, 2015-2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "%s: " fmt, __func__ @@ -136,18 +136,32 @@ static bool force_on_xin_clk(u32 bit_off, u32 clk_ctl_reg_off, bool enable) void vbif_lock(struct platform_device *parent_pdev) { +#ifdef CONFIG_FB_MSM_MDSS + struct sde_rot_data_type *mdata = sde_rot_get_mdata(); + + if (mdata && mdata->vbif_reg_lock) + mdata->vbif_reg_lock(); +#else if (!parent_pdev) return; -// mdp_vbif_lock(parent_pdev, true); + mdp_vbif_lock(parent_pdev, true); +#endif } void vbif_unlock(struct platform_device *parent_pdev) { +#ifdef CONFIG_FB_MSM_MDSS + struct sde_rot_data_type *mdata = sde_rot_get_mdata(); + + if (mdata && mdata->vbif_reg_unlock) + mdata->vbif_reg_unlock(); +#else if (!parent_pdev) return; -// mdp_vbif_lock(parent_pdev, false); + mdp_vbif_lock(parent_pdev, false); +#endif } void sde_mdp_halt_vbif_xin(struct sde_mdp_vbif_halt_params *params) @@ -242,6 +256,7 @@ u32 sde_mdp_get_ot_limit(u32 width, u32 height, u32 pixfmt, u32 fps, u32 is_rd) */ switch (mdata->mdss_version) { case SDE_MDP_HW_REV_540: + case SDE_MDP_HW_REV_320: if (is_yuv) { if (res <= (RES_1080p * 30)) ot_lim = 2; @@ -254,7 +269,6 @@ u32 sde_mdp_get_ot_limit(u32 width, u32 height, u32 pixfmt, u32 fps, u32 is_rd) } else if (fmt->bpp == 4 && res <= (RES_WQXGA * 60)) { ot_lim = 16; } - break; default: if (res <= (RES_1080p * 30)) diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_base.h b/drivers/media/platform/msm/sde/rotator/sde_rotator_base.h index 729e9e0a8b84..66a60c48a25c 100644 --- a/drivers/media/platform/msm/sde/rotator/sde_rotator_base.h +++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_base.h @@ -277,6 +277,8 @@ struct sde_rot_data_type { int (*iommu_ctrl)(int enable); int (*secure_session_ctrl)(int enable); int (*wait_for_transition)(int state, int request); + void (*vbif_reg_lock)(void); + void (*vbif_reg_unlock)(void); struct sde_rot_vbif_debug_bus *nrt_vbif_dbg_bus; u32 nrt_vbif_dbg_bus_size; struct sde_rot_debug_bus *rot_dbg_bus; diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_hwio.h b/drivers/media/platform/msm/sde/rotator/sde_rotator_hwio.h index d45ea1c582b3..c0061d3731df 100644 --- a/drivers/media/platform/msm/sde/rotator/sde_rotator_hwio.h +++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_hwio.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. */ #ifndef SDE_ROTATOR_HWIO_H @@ -59,8 +59,11 @@ #define MMSS_VBIF_NRT_VBIF_OUT_WR_LIM_CONF0 0x00D4 #define MMSS_VBIF_NRT_VBIF_OUT_AXI_AMEMTYPE_CONF0 0x0160 #define MMSS_VBIF_NRT_VBIF_QOS_RP_REMAP_000 0x0550 +#ifdef CONFIG_FB_MSM_MDSS +#define MMSS_VBIF_NRT_VBIF_QOS_LVL_REMAP_000 0x0570 +#else #define MMSS_VBIF_NRT_VBIF_QOS_LVL_REMAP_000 0x0590 - +#endif #define SDE_MDP_REG_TRAFFIC_SHAPER_EN BIT(31) #define SDE_MDP_REG_TRAFFIC_SHAPER_RD_CLIENT(num) (0x030 + (num * 4)) #define SDE_MDP_REG_TRAFFIC_SHAPER_WR_CLIENT(num) (0x060 + (num * 4)) diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_smmu.c b/drivers/media/platform/msm/sde/rotator/sde_rotator_smmu.c index 46c78e3ac653..4436740d5b0d 100644 --- a/drivers/media/platform/msm/sde/rotator/sde_rotator_smmu.c +++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_smmu.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "%s: " fmt, __func__ @@ -380,6 +380,8 @@ static void sde_smmu_callback(struct mdss_smmu_intf *smmu) /* Copy mmu device info into sde private structure */ mdata->iommu_ctrl = smmu->iommu_ctrl; + mdata->vbif_reg_lock = smmu->reg_lock; + mdata->vbif_reg_unlock = smmu->reg_unlock; mdata->wait_for_transition = smmu->wait_for_transition; mdata->secure_session_ctrl = smmu->secure_session_ctrl; if (smmu->is_secure) { diff --git a/drivers/media/platform/msm/vidc_3x/msm_v4l2_vidc.c b/drivers/media/platform/msm/vidc_3x/msm_v4l2_vidc.c index 9131337a8d47..00a35099babd 100644 --- a/drivers/media/platform/msm/vidc_3x/msm_v4l2_vidc.c +++ b/drivers/media/platform/msm/vidc_3x/msm_v4l2_vidc.c @@ -808,8 +808,8 @@ static int __init msm_vidc_init(void) mutex_init(&vidc_driver->lock); vidc_driver->debugfs_root = msm_vidc_debugfs_init_drv(); if (!vidc_driver->debugfs_root) -// dprintk(VIDC_ERR, -// "Failed to create debugfs for msm_vidc\n"); + dprintk(VIDC_ERR, + "Failed to create debugfs for msm_vidc\n"); rc = platform_driver_register(&msm_vidc_driver); if (rc) { diff --git a/drivers/media/platform/msm/vidc_3x/msm_vidc_debug.c b/drivers/media/platform/msm/vidc_3x/msm_vidc_debug.c index 74bbcb5c9d89..807ed21d788e 100644 --- a/drivers/media/platform/msm/vidc_3x/msm_vidc_debug.c +++ b/drivers/media/platform/msm/vidc_3x/msm_vidc_debug.c @@ -228,7 +228,7 @@ struct dentry *msm_vidc_debugfs_init_core(struct msm_vidc_core *core, snprintf(debugfs_name, MAX_DEBUGFS_NAME, "core%d", core->id); dir = debugfs_create_dir(debugfs_name, parent); if (!dir) { -// dprintk(VIDC_ERR, "Failed to create debugfs for msm_vidc\n"); + dprintk(VIDC_ERR, "Failed to create debugfs for msm_vidc\n"); goto failed_create_dir; } @@ -447,7 +447,7 @@ struct dentry *msm_vidc_debugfs_init_inst(struct msm_vidc_inst *inst, dir = debugfs_create_dir(debugfs_name, parent); if (!dir) { -// dprintk(VIDC_ERR, "Failed to create debugfs for msm_vidc\n"); + dprintk(VIDC_ERR, "Failed to create debugfs for msm_vidc\n"); goto failed_create_dir; } diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index cc6d6fcf2c33..aefd9809b954 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -520,3 +520,24 @@ config I2C_RTC6226_QCA module will be called radio-i2c-RTC6226_QCA. endif # RADIO_ADAPTERS + +config RADIO_IRIS + tristate "QTI IRIS FM support" + depends on VIDEO_V4L2 + help + Say Y here if you want to use the QTI FM chip (IRIS). + This FM chip uses SMD interface + + To compile this driver as a module, choose M here: the + module will be called radio-iris. + + +config RADIO_IRIS_TRANSPORT + tristate "QTI IRIS Transport" + depends on RADIO_IRIS + help + Say Y here if you want to use the QTI FM chip (IRIS). + with SMD as transport. + + To compile this driver as a module, choose M here: the + module will be called radio-iris-transport. diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index a1167b9e0aab..ac0e3c58c216 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -35,6 +35,8 @@ obj-$(CONFIG_RADIO_WL128X) += wl128x/ obj-$(CONFIG_RADIO_TEA575X) += tea575x.o obj-$(CONFIG_USB_RAREMONO) += radio-raremono.o obj-$(CONFIG_I2C_RTC6226_QCA) += rtc6226/ +obj-$(CONFIG_RADIO_IRIS) += radio-iris.o +obj-$(CONFIG_RADIO_IRIS_TRANSPORT) += radio-iris-transport.o shark2-objs := radio-shark2.o radio-tea5777.o diff --git a/drivers/media/radio/radio-iris-transport.c b/drivers/media/radio/radio-iris-transport.c new file mode 100644 index 000000000000..db429e0b9ca4 --- /dev/null +++ b/drivers/media/radio/radio-iris-transport.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2000-2001, 2011-2012, 2014-2015 The Linux Foundation. + * All rights reserved. + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2004-2006 Marcel Holtmann + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct radio_data { + struct radio_hci_dev *hdev; + struct tasklet_struct rx_task; + struct smd_channel *fm_channel; +}; +struct radio_data hs; +DEFINE_MUTEX(fm_smd_enable); +static int fmsmd_set; +static bool chan_opened; +static int hcismd_fm_set_enable(const char *val, const struct kernel_param *kp); +module_param_call(fmsmd_set, hcismd_fm_set_enable, NULL, &fmsmd_set, 0644); +static struct work_struct *reset_worker; +static void radio_hci_smd_deregister(void); +static void radio_hci_smd_exit(void); + +static void radio_hci_smd_destruct(struct radio_hci_dev *hdev) +{ + radio_hci_unregister_dev(); +} + + +static void radio_hci_smd_recv_event(unsigned long temp) +{ + int len; + int rc; + struct sk_buff *skb; + unsigned char *buf; + struct radio_data *hsmd = &hs; + + len = smd_read_avail(hsmd->fm_channel); + + while (len) { + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) { + FMDERR("Memory not allocated for the socket\n"); + return; + } + + buf = kmalloc(len, GFP_ATOMIC); + if (!buf) { + kfree_skb(skb); + return; + } + + rc = smd_read(hsmd->fm_channel, (void *)buf, len); + + memcpy(skb_put(skb, len), buf, len); + + skb_orphan(skb); + skb->dev = (struct net_device *)hs.hdev; + + rc = radio_hci_recv_frame(skb); + + kfree(buf); + len = smd_read_avail(hsmd->fm_channel); + } +} + +static int radio_hci_smd_send_frame(struct sk_buff *skb) +{ + int len = 0; + + FMDBG("skb %pK\n", skb); + + len = smd_write(hs.fm_channel, skb->data, skb->len); + if (len < skb->len) { + FMDERR("Failed to write Data %d\n", len); + kfree_skb(skb); + return -ENODEV; + } + kfree_skb(skb); + return 0; +} + + +static void send_disable_event(struct work_struct *worker) +{ + struct sk_buff *skb; + unsigned char buf[6] = { 0x0f, 0x04, 0x01, 0x02, 0x4c, 0x00 }; + int len = sizeof(buf); + + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) { + FMDERR("Memory not allocated for the socket\n"); + kfree(worker); + return; + } + + FMDBG("FM INSERT DISABLE Rx Event\n"); + + memcpy(skb_put(skb, len), buf, len); + + skb_orphan(skb); + skb->dev = (struct net_device *)hs.hdev; + + radio_hci_recv_frame(skb); + kfree(worker); +} + +static void radio_hci_smd_notify_cmd(void *data, unsigned int event) +{ + struct radio_hci_dev *hdev = (struct radio_hci_dev *)data; + + FMDBG("data %p event %u\n", data, event); + + if (!hdev) { + FMDERR("Frame for unknown HCI device (hdev=NULL)\n"); + return; + } + + switch (event) { + case SMD_EVENT_DATA: + tasklet_schedule(&hs.rx_task); + break; + case SMD_EVENT_OPEN: + break; + case SMD_EVENT_CLOSE: + reset_worker = kzalloc(sizeof(*reset_worker), GFP_ATOMIC); + if (reset_worker) { + INIT_WORK(reset_worker, send_disable_event); + schedule_work(reset_worker); + } + break; + default: + break; + } +} + +static int radio_hci_smd_register_dev(struct radio_data *hsmd) +{ + struct radio_hci_dev *hdev; + int rc; + + FMDBG("hsmd: %pK\n", hsmd); + + if (hsmd == NULL) + return -ENODEV; + + hdev = kmalloc(sizeof(struct radio_hci_dev), GFP_KERNEL); + if (hdev == NULL) + return -ENODEV; + + tasklet_init(&hsmd->rx_task, radio_hci_smd_recv_event, + (unsigned long) hsmd); + hdev->send = radio_hci_smd_send_frame; + hdev->destruct = radio_hci_smd_destruct; + hdev->close_smd = radio_hci_smd_exit; + + /* Open the SMD Channel and device and register the callback function */ + rc = smd_named_open_on_edge("APPS_FM", SMD_APPS_WCNSS, + &hsmd->fm_channel, hdev, radio_hci_smd_notify_cmd); + + if (rc < 0) { + FMDERR("Cannot open the command channel\n"); + hsmd->hdev = NULL; + kfree(hdev); + return -ENODEV; + } + + smd_disable_read_intr(hsmd->fm_channel); + + if (radio_hci_register_dev(hdev) < 0) { + FMDERR("Can't register HCI device\n"); + smd_close(hsmd->fm_channel); + hsmd->hdev = NULL; + kfree(hdev); + return -ENODEV; + } + + hsmd->hdev = hdev; + return 0; +} + +static void radio_hci_smd_deregister(void) +{ + radio_hci_unregister_dev(); + kfree(hs.hdev); + hs.hdev = NULL; + + smd_close(hs.fm_channel); + hs.fm_channel = 0; + fmsmd_set = 0; +} + +static int radio_hci_smd_init(void) +{ + int ret; + + if (chan_opened) { + FMDBG("Channel is already opened\n"); + return 0; + } + + /* this should be called with fm_smd_enable lock held */ + ret = radio_hci_smd_register_dev(&hs); + if (ret < 0) { + FMDERR("Failed to register smd device\n"); + chan_opened = false; + return ret; + } + chan_opened = true; + return ret; +} + +static void radio_hci_smd_exit(void) +{ + if (!chan_opened) { + FMDBG("Channel already closed\n"); + return; + } + + /* this should be called with fm_smd_enable lock held */ + radio_hci_smd_deregister(); + chan_opened = false; +} + +static int hcismd_fm_set_enable(const char *val, const struct kernel_param *kp) +{ + int ret = 0; + + mutex_lock(&fm_smd_enable); + ret = param_set_int(val, kp); + if (ret) + goto done; + switch (fmsmd_set) { + + case 1: + radio_hci_smd_init(); + break; + case 0: + radio_hci_smd_exit(); + break; + default: + ret = -EFAULT; + } +done: + mutex_unlock(&fm_smd_enable); + return ret; +} +MODULE_DESCRIPTION("FM SMD driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/radio/radio-iris.c b/drivers/media/radio/radio-iris.c new file mode 100644 index 000000000000..682e4c276768 --- /dev/null +++ b/drivers/media/radio/radio-iris.c @@ -0,0 +1,5694 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved + */ + +#define DRIVER_NAME "radio-iris" +#define DRIVER_CARD "QTI FM Radio Transceiver" +#define DRIVER_DESC "Driver for QTI FM Radio Transceiver " + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static unsigned int rds_buf = 100; +static int oda_agt; +static int grp_mask; +static int rt_plus_carrier = -1; +static int ert_carrier = -1; +static unsigned char ert_buf[256]; +static unsigned char ert_len; +static unsigned char c_byt_pair_index; +static char utf_8_flag; +static char rt_ert_flag; +static char formatting_dir; +static unsigned char sig_blend = CTRL_ON; +static DEFINE_MUTEX(iris_fm); + +module_param(rds_buf, uint, 0000); +MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*"); + +module_param(sig_blend, byte, 0200 | 0400 | 0040 | 0004); +MODULE_PARM_DESC(sig_blend, "signal blending switch: 0:OFF 1:ON"); + +static void radio_hci_cmd_task(unsigned long arg); +static void radio_hci_rx_task(unsigned long arg); +static struct video_device *video_get_dev(void); +static DEFINE_RWLOCK(hci_task_lock); + +typedef int (*radio_hci_request_func)(struct radio_hci_dev *hdev, + int (*req)(struct radio_hci_dev *hdev, unsigned long param), + unsigned long param, unsigned long timeout_msecs); + +struct iris_device { + struct device *dev; + struct kfifo data_buf[IRIS_BUF_MAX]; + + int pending_xfrs[IRIS_XFR_MAX]; + int xfr_bytes_left; + int xfr_in_progress; + struct completion sync_xfr_start; + int tune_req; + unsigned int mode; + bool is_fm_closing; + + __u16 pi; + __u8 pty; + __u8 ps_repeatcount; + __u8 prev_trans_rds; + __u8 af_jump_bit; + struct video_device *videodev; + struct v4l2_device v4l2_dev; + + struct mutex lock; + spinlock_t buf_lock[IRIS_BUF_MAX]; + wait_queue_head_t event_queue; + wait_queue_head_t read_queue; + + struct radio_hci_dev *fm_hdev; + + struct v4l2_capability g_cap; + struct v4l2_control *g_ctl; + + struct hci_fm_mute_mode_req mute_mode; + struct hci_fm_stereo_mode_req stereo_mode; + struct hci_fm_station_rsp fm_st_rsp; + struct hci_fm_search_station_req srch_st; + struct hci_fm_search_rds_station_req srch_rds; + struct hci_fm_search_station_list_req srch_st_list; + struct hci_fm_recv_conf_req recv_conf; + struct hci_fm_trans_conf_req_struct trans_conf; + struct hci_fm_rds_grp_req rds_grp; + unsigned char g_search_mode; + unsigned char power_mode; + int search_on; + unsigned int tone_freq; + unsigned char spur_table_size; + unsigned char g_scan_time; + unsigned int g_antenna; + unsigned int g_rds_grp_proc_ps; + unsigned char event_mask; + enum iris_region_t region; + struct hci_fm_dbg_param_rsp st_dbg_param; + struct hci_ev_srch_list_compl srch_st_result; + struct hci_fm_riva_poke riva_data_req; + struct hci_fm_ssbi_req ssbi_data_accs; + struct hci_fm_ssbi_peek ssbi_peek_reg; + struct hci_fm_sig_threshold_rsp sig_th; + struct hci_fm_ch_det_threshold ch_det_threshold; + struct hci_fm_data_rd_rsp default_data; + struct hci_fm_spur_data spur_data; + unsigned char is_station_valid; + struct hci_fm_blend_table blend_tbl; +}; + +static struct video_device *priv_videodev; +static int iris_do_calibration(struct iris_device *radio); +static void hci_buff_ert(struct iris_device *radio, + struct rds_grp_data *rds_buf); +static void hci_ev_rt_plus(struct iris_device *radio, + struct rds_grp_data rds_buf); +static void hci_ev_ert(struct iris_device *radio); +static int update_spur_table(struct iris_device *radio); +static int initialise_recv(struct iris_device *radio); +static int initialise_trans(struct iris_device *radio); +static int is_enable_rx_possible(struct iris_device *radio); +static int is_enable_tx_possible(struct iris_device *radio); + +static struct v4l2_queryctrl iris_v4l2_queryctrl[] = { + { + .id = V4L2_CID_AUDIO_VOLUME, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Volume", + .minimum = 0, + .maximum = 15, + .step = 1, + .default_value = 15, + }, + { + .id = V4L2_CID_AUDIO_BALANCE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_BASS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_TREBLE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_MUTE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_AUDIO_LOUDNESS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SRCHMODE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Search mode", + .minimum = 0, + .maximum = 7, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SCANDWELL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Search dwell time", + .minimum = 0, + .maximum = 7, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SRCHON, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Search on/off", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + + }, + { + .id = V4L2_CID_PRIVATE_IRIS_STATE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "radio 0ff/rx/tx/reset", + .minimum = 0, + .maximum = 3, + .step = 1, + .default_value = 1, + + }, + { + .id = V4L2_CID_PRIVATE_IRIS_REGION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "radio standard", + .minimum = 0, + .maximum = 2, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SIGNAL_TH, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Signal Threshold", + .minimum = 0x80, + .maximum = 0x7F, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SRCH_PTY, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Search PTY", + .minimum = 0, + .maximum = 31, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SRCH_PI, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Search PI", + .minimum = 0, + .maximum = 0xFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SRCH_CNT, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Preset num", + .minimum = 0, + .maximum = 12, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_EMPHASIS, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Emphasis", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RDS_STD, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "RDS standard", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SPACING, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Channel spacing", + .minimum = 0, + .maximum = 2, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RDSON, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "RDS on/off", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RDSGROUP_MASK, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RDS group mask", + .minimum = 0, + .maximum = 0xFFFFFFFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RDSGROUP_PROC, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RDS processing", + .minimum = 0, + .maximum = 0xFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RDSD_BUF, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RDS data groups to buffer", + .minimum = 1, + .maximum = 21, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_PSALL, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "pass all ps strings", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_LP_MODE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Low power mode", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_ANTENNA, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "headset/internal", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_TX_SETPSREPEATCOUNT, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Set PS REPEATCOUNT", + .minimum = 0, + .maximum = 15, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_STOP_RDS_TX_PS_NAME, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Stop PS NAME", + .minimum = 0, + .maximum = 1, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_STOP_RDS_TX_RT, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Stop RT", + .minimum = 0, + .maximum = 1, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SOFT_MUTE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Soft Mute", + .minimum = 0, + .maximum = 1, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RIVA_ACCS_ADDR, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Riva addr", + .minimum = 0x3180000, + .maximum = 0x31E0004, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RIVA_ACCS_LEN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Data len", + .minimum = 0, + .maximum = 0xFF, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RIVA_PEEK, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Riva peek", + .minimum = 0, + .maximum = 1, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RIVA_POKE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Riva poke", + .minimum = 0x3180000, + .maximum = 0x31E0004, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SSBI_ACCS_ADDR, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Ssbi addr", + .minimum = 0x280, + .maximum = 0x37F, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SSBI_PEEK, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Ssbi peek", + .minimum = 0, + .maximum = 0x37F, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SSBI_POKE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "ssbi poke", + .minimum = 0x01, + .maximum = 0xFF, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_HLSI, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "set hlsi", + .minimum = 0, + .maximum = 2, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RDS_GRP_COUNTERS, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "RDS grp", + .minimum = 0, + .maximum = 1, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SET_NOTCH_FILTER, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Notch filter", + .minimum = 0, + .maximum = 2, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_READ_DEFAULT, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Read default", + }, + { + .id = V4L2_CID_PRIVATE_IRIS_WRITE_DEFAULT, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Write default", + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SET_CALIBRATION, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "SET Calibration", + .minimum = 0, + .maximum = 1, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_DO_CALIBRATION, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "SET Calibration", + .minimum = 0, + .maximum = 1, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_GET_SINR, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "GET SINR", + .minimum = -128, + .maximum = 127, + }, + { + .id = V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Intf High Threshold", + .minimum = 0, + .maximum = 0xFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Intf low Threshold", + .minimum = 0, + .maximum = 0xFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SINR_THRESHOLD, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "SINR Threshold", + .minimum = -128, + .maximum = 127, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SINR_SAMPLES, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "SINR samples", + .minimum = 1, + .maximum = 0xFF, + .default_value = 0, + }, +}; + +static void iris_q_event(struct iris_device *radio, + enum iris_evt_t event) +{ + struct kfifo *data_b; + unsigned char evt = event; + + FMDBG("radio %pK event %d", radio, event); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + + data_b = &radio->data_buf[IRIS_BUF_EVENTS]; + if (kfifo_in_locked(data_b, &evt, 1, &radio->buf_lock[IRIS_BUF_EVENTS])) + wake_up_interruptible(&radio->event_queue); +} + +static int hci_send_frame(struct sk_buff *skb) +{ + struct radio_hci_dev *hdev; + + FMDBG("skb %pK", skb); + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return -EINVAL; + } + hdev = (struct radio_hci_dev *) skb->dev; + if (unlikely(!hdev)) { + kfree_skb(skb); + return -ENODEV; + } + + __net_timestamp(skb); + + skb_orphan(skb); + return hdev->send(skb); +} + +static void radio_hci_cmd_task(unsigned long arg) +{ + struct radio_hci_dev *hdev = (struct radio_hci_dev *) arg; + struct sk_buff *skb; + + FMDBG("hdev %pK", hdev); + + if (unlikely(hdev == NULL)) { + FMDERR("%s, HCI Device is null\n", __func__); + return; + } + if (!(atomic_read(&hdev->cmd_cnt)) + && time_after(jiffies, hdev->cmd_last_tx + HZ)) { + FMDERR("%s command tx timeout\n", hdev->name); + atomic_set(&hdev->cmd_cnt, 1); + } + + skb = skb_dequeue(&hdev->cmd_q); + if (atomic_read(&hdev->cmd_cnt) && skb) { + kfree_skb(hdev->sent_cmd); + hdev->sent_cmd = skb_clone(skb, GFP_ATOMIC); + if (hdev->sent_cmd) { + atomic_dec(&hdev->cmd_cnt); + hci_send_frame(skb); + hdev->cmd_last_tx = jiffies; + } else { + skb_queue_head(&hdev->cmd_q, skb); + tasklet_schedule(&hdev->cmd_task); + } + } + +} + +static void radio_hci_rx_task(unsigned long arg) +{ + struct radio_hci_dev *hdev = (struct radio_hci_dev *) arg; + struct sk_buff *skb; + + FMDBG("hdev %pK", hdev); + + if (unlikely(hdev == NULL)) { + FMDERR("%s, HCI Device is null\n", __func__); + return; + } + read_lock(&hci_task_lock); + + skb = skb_dequeue(&hdev->rx_q); + radio_hci_event_packet(hdev, skb); + + read_unlock(&hci_task_lock); +} + +int radio_hci_register_dev(struct radio_hci_dev *hdev) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + FMDBG("radio %pK", radio); + + if (!radio) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + + if (!hdev) { + FMDERR("hdev is null\n"); + return -EINVAL; + } + + hdev->flags = 0; + + tasklet_init(&hdev->cmd_task, radio_hci_cmd_task, (unsigned long) + hdev); + tasklet_init(&hdev->rx_task, radio_hci_rx_task, (unsigned long) + hdev); + + init_waitqueue_head(&hdev->req_wait_q); + + skb_queue_head_init(&hdev->rx_q); + skb_queue_head_init(&hdev->cmd_q); + skb_queue_head_init(&hdev->raw_q); + + + radio->fm_hdev = hdev; + + return 0; +} +EXPORT_SYMBOL(radio_hci_register_dev); + +int radio_hci_unregister_dev(void) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + struct radio_hci_dev *hdev = NULL; + + if (!radio) { + FMDERR("radio is null\n"); + return -EINVAL; + } + hdev = radio->fm_hdev; + if (!hdev) { + FMDERR("hdev is null\n"); + return -EINVAL; + } + + tasklet_kill(&hdev->rx_task); + tasklet_kill(&hdev->cmd_task); + skb_queue_purge(&hdev->rx_q); + skb_queue_purge(&hdev->cmd_q); + skb_queue_purge(&hdev->raw_q); + + radio->fm_hdev = NULL; + return 0; +} +EXPORT_SYMBOL(radio_hci_unregister_dev); + +int radio_hci_recv_frame(struct sk_buff *skb) +{ + struct radio_hci_dev *hdev; + + FMDBG("hdev %pK", skb); + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return -EINVAL; + } + hdev = (struct radio_hci_dev *) skb->dev; + if (unlikely(!hdev)) { + FMDERR("%s hdev is null while receiving frame\n", hdev->name); + kfree_skb(skb); + return -ENXIO; + } + + __net_timestamp(skb); + + radio_hci_event_packet(hdev, skb); + kfree_skb(skb); + return 0; +} +EXPORT_SYMBOL(radio_hci_recv_frame); + +int radio_hci_send_cmd(struct radio_hci_dev *hdev, __u16 opcode, __u32 plen, + void *param) +{ + int len = RADIO_HCI_COMMAND_HDR_SIZE + plen; + struct radio_hci_command_hdr *hdr; + struct sk_buff *skb; + int ret = 0; + + FMDBG("hdev %pK opcode %u len %u", hdev, opcode, plen); + + if (unlikely(hdev == NULL)) { + FMDERR("%s, hci device is null\n", __func__); + return -EINVAL; + } + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + hdr = (struct radio_hci_command_hdr *) skb_put(skb, + RADIO_HCI_COMMAND_HDR_SIZE); + hdr->opcode = cpu_to_le16(opcode); + hdr->plen = plen; + + if (plen) + memcpy(skb_put(skb, plen), param, plen); + + skb->dev = (void *) hdev; + + ret = hci_send_frame(skb); + + return ret; +} +EXPORT_SYMBOL(radio_hci_send_cmd); + +static int hci_fm_enable_recv_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_ENABLE_RECV_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_tone_generator(struct radio_hci_dev *hdev, + unsigned long param) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u16 opcode = 0; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_DIAGNOSTIC_CMD_REQ, + HCI_FM_SET_INTERNAL_TONE_GENRATOR); + return radio_hci_send_cmd(hdev, opcode, + sizeof(radio->tone_freq), &radio->tone_freq); +} + +static int hci_fm_enable_trans_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_TRANS_CTRL_CMD_REQ, + HCI_OCF_FM_ENABLE_TRANS_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_disable_recv_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_DISABLE_RECV_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_disable_trans_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_TRANS_CTRL_CMD_REQ, + HCI_OCF_FM_DISABLE_TRANS_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_get_fm_recv_conf_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_GET_RECV_CONF_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_get_fm_trans_conf_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_TRANS_CTRL_CMD_REQ, + HCI_OCF_FM_GET_TRANS_CONF_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} +static int hci_set_fm_recv_conf_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + struct hci_fm_recv_conf_req *recv_conf_req = + (struct hci_fm_recv_conf_req *) param; + + if (recv_conf_req == NULL) { + FMDERR("%s, recv conf is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SET_RECV_CONF_REQ); + return radio_hci_send_cmd(hdev, opcode, sizeof((*recv_conf_req)), + recv_conf_req); +} + +static int hci_set_fm_trans_conf_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + struct hci_fm_trans_conf_req_struct *trans_conf_req = + (struct hci_fm_trans_conf_req_struct *) param; + + if (trans_conf_req == NULL) { + FMDERR("%s, tx conf is null\n", __func__); + return -EINVAL; + } + + opcode = hci_opcode_pack(HCI_OGF_FM_TRANS_CTRL_CMD_REQ, + HCI_OCF_FM_SET_TRANS_CONF_REQ); + return radio_hci_send_cmd(hdev, opcode, sizeof((*trans_conf_req)), + trans_conf_req); +} + +static int hci_fm_get_station_param_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_GET_STATION_PARAM_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_set_fm_mute_mode_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_mute_mode_req *mute_mode_req = + (struct hci_fm_mute_mode_req *) param; + + if (mute_mode_req == NULL) { + FMDERR("%s, mute mode is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SET_MUTE_MODE_REQ); + return radio_hci_send_cmd(hdev, opcode, sizeof((*mute_mode_req)), + mute_mode_req); +} + + +static int hci_trans_ps_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_tx_ps *tx_ps_req = + (struct hci_fm_tx_ps *) param; + + if (tx_ps_req == NULL) { + FMDERR("%s, tx ps req is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_TRANS_CTRL_CMD_REQ, + HCI_OCF_FM_RDS_PS_REQ); + + return radio_hci_send_cmd(hdev, opcode, sizeof((*tx_ps_req)), + tx_ps_req); +} + +static int hci_trans_rt_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_tx_rt *tx_rt_req = + (struct hci_fm_tx_rt *) param; + + if (tx_rt_req == NULL) { + FMDERR("%s, tx rt req is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_TRANS_CTRL_CMD_REQ, + HCI_OCF_FM_RDS_RT_REQ); + + return radio_hci_send_cmd(hdev, opcode, sizeof((*tx_rt_req)), + tx_rt_req); +} + +static int hci_set_fm_stereo_mode_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_stereo_mode_req *stereo_mode_req = + (struct hci_fm_stereo_mode_req *) param; + + if (stereo_mode_req == NULL) { + FMDERR("%s, stere mode req is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SET_STEREO_MODE_REQ); + return radio_hci_send_cmd(hdev, opcode, sizeof((*stereo_mode_req)), + stereo_mode_req); +} + +static int hci_fm_set_antenna_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + __u8 antenna = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SET_ANTENNA); + return radio_hci_send_cmd(hdev, opcode, sizeof(antenna), &antenna); +} + +static int hci_fm_set_sig_threshold_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + __u8 sig_threshold = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SET_SIGNAL_THRESHOLD); + return radio_hci_send_cmd(hdev, opcode, sizeof(sig_threshold), + &sig_threshold); +} + +static int hci_fm_set_event_mask(struct radio_hci_dev *hdev, + unsigned long param) +{ + u16 opcode = 0; + u8 event_mask = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SET_EVENT_MASK); + return radio_hci_send_cmd(hdev, opcode, sizeof(event_mask), + &event_mask); +} +static int hci_fm_get_sig_threshold_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_GET_SIGNAL_THRESHOLD); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_get_program_service_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_GET_PROGRAM_SERVICE_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_get_radio_text_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_GET_RADIO_TEXT_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_get_af_list_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_GET_AF_LIST_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_search_stations_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_search_station_req *srch_stations = + (struct hci_fm_search_station_req *) param; + + if (srch_stations == NULL) { + FMDERR("%s, search station param is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SEARCH_STATIONS); + return radio_hci_send_cmd(hdev, opcode, sizeof((*srch_stations)), + srch_stations); +} + +static int hci_fm_srch_rds_stations_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_search_rds_station_req *srch_stations = + (struct hci_fm_search_rds_station_req *) param; + + if (srch_stations == NULL) { + FMDERR("%s, rds stations param is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SEARCH_RDS_STATIONS); + return radio_hci_send_cmd(hdev, opcode, sizeof((*srch_stations)), + srch_stations); +} + +static int hci_fm_srch_station_list_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_search_station_list_req *srch_list = + (struct hci_fm_search_station_list_req *) param; + + if (srch_list == NULL) { + FMDERR("%s, search list param is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SEARCH_STATIONS_LIST); + return radio_hci_send_cmd(hdev, opcode, sizeof((*srch_list)), + srch_list); +} + +static int hci_fm_cancel_search_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_CANCEL_SEARCH); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_rds_grp_mask_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + struct hci_fm_rds_grp_req *fm_grp_mask = + (struct hci_fm_rds_grp_req *)param; + + if (fm_grp_mask == NULL) { + FMDERR("%s, grp mask param is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_RDS_GRP); + return radio_hci_send_cmd(hdev, opcode, sizeof(*fm_grp_mask), + fm_grp_mask); +} + +static int hci_fm_rds_grp_process_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + __u32 fm_grps_process = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_RDS_GRP_PROCESS); + return radio_hci_send_cmd(hdev, opcode, sizeof(fm_grps_process), + &fm_grps_process); +} + +static int hci_fm_tune_station_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + __u32 tune_freq = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_TUNE_STATION_REQ); + return radio_hci_send_cmd(hdev, opcode, sizeof(tune_freq), &tune_freq); +} + +static int hci_def_data_read_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_def_data_rd_req *def_data_rd = + (struct hci_fm_def_data_rd_req *) param; + + if (def_data_rd == NULL) { + FMDERR("%s, def data read param is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_DEFAULT_DATA_READ); + return radio_hci_send_cmd(hdev, opcode, sizeof((*def_data_rd)), + def_data_rd); +} + +static int hci_def_data_write_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_def_data_wr_req *def_data_wr = + (struct hci_fm_def_data_wr_req *) param; + + if (def_data_wr == NULL) { + FMDERR("%s, def data write param is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_DEFAULT_DATA_WRITE); + + return radio_hci_send_cmd(hdev, opcode, (def_data_wr->length+2), + def_data_wr); +} + +static int hci_set_notch_filter_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + __u8 notch_filter_val = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_EN_NOTCH_CTRL); + return radio_hci_send_cmd(hdev, opcode, sizeof(notch_filter_val), + ¬ch_filter_val); +} + +static int hci_fm_reset_req(struct radio_hci_dev *hdev, unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_RESET); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_get_feature_lists_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_GET_FEATURE_LIST); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_do_calibration_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + __u8 mode = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_DO_CALIBRATION); + return radio_hci_send_cmd(hdev, opcode, sizeof(mode), &mode); +} + +static int hci_read_grp_counters_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + __u8 reset_counters = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_STATUS_PARAMETERS_CMD_REQ, + HCI_OCF_FM_READ_GRP_COUNTERS); + return radio_hci_send_cmd(hdev, opcode, sizeof(reset_counters), + &reset_counters); +} + +static int hci_peek_data_req(struct radio_hci_dev *hdev, unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_riva_data *peek_data = (struct hci_fm_riva_data *)param; + + if (peek_data == NULL) { + FMDERR("%s, peek data param is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_DIAGNOSTIC_CMD_REQ, + HCI_OCF_FM_PEEK_DATA); + return radio_hci_send_cmd(hdev, opcode, sizeof((*peek_data)), + peek_data); +} + +static int hci_poke_data_req(struct radio_hci_dev *hdev, unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_riva_poke *poke_data = (struct hci_fm_riva_poke *) param; + + if (poke_data == NULL) { + FMDERR("%s, poke data param is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_DIAGNOSTIC_CMD_REQ, + HCI_OCF_FM_POKE_DATA); + return radio_hci_send_cmd(hdev, opcode, sizeof((*poke_data)), + poke_data); +} + +static int hci_ssbi_peek_reg_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_ssbi_peek *ssbi_peek = (struct hci_fm_ssbi_peek *) param; + + if (ssbi_peek == NULL) { + FMDERR("%s, ssbi peek param is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_DIAGNOSTIC_CMD_REQ, + HCI_OCF_FM_SSBI_PEEK_REG); + return radio_hci_send_cmd(hdev, opcode, sizeof((*ssbi_peek)), + ssbi_peek); +} + +static int hci_ssbi_poke_reg_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_ssbi_req *ssbi_poke = (struct hci_fm_ssbi_req *) param; + + if (ssbi_poke == NULL) { + FMDERR("%s, ssbi poke param is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_DIAGNOSTIC_CMD_REQ, + HCI_OCF_FM_SSBI_POKE_REG); + return radio_hci_send_cmd(hdev, opcode, sizeof((*ssbi_poke)), + ssbi_poke); +} + +static int hci_fm_get_station_dbg_param_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_DIAGNOSTIC_CMD_REQ, + HCI_OCF_FM_STATION_DBG_PARAM); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_set_ch_det_th(struct radio_hci_dev *hdev, + unsigned long param) +{ + struct hci_fm_ch_det_threshold *ch_det_th = + (struct hci_fm_ch_det_threshold *) param; + u16 opcode; + + if (ch_det_th == NULL) { + FMDERR("%s, channel det thrshld is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SET_CH_DET_THRESHOLD); + return radio_hci_send_cmd(hdev, opcode, sizeof((*ch_det_th)), + ch_det_th); +} + +static int hci_fm_get_ch_det_th(struct radio_hci_dev *hdev, + unsigned long param) +{ + u16 opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_GET_CH_DET_THRESHOLD); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_get_blend_tbl(struct radio_hci_dev *hdev, + unsigned long param) +{ + u16 opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_GET_BLND_TBL); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_set_blend_tbl(struct radio_hci_dev *hdev, + unsigned long param) +{ + struct hci_fm_blend_table *blnd_tbl = + (struct hci_fm_blend_table *) param; + u16 opcode; + + if (blnd_tbl == NULL) { + FMDERR("%s, blend tbl is null\n", __func__); + return -EINVAL; + } + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SET_BLND_TBL); + return radio_hci_send_cmd(hdev, opcode, + sizeof(struct hci_fm_blend_table), blnd_tbl); +} + +static int radio_hci_err(__u32 code) +{ + switch (code) { + case 0: + return 0; + case 0x01: + return -EBADRQC; + case 0x02: + return -ENOTCONN; + case 0x03: + return -EIO; + case 0x07: + return -ENOMEM; + case 0x0c: + return -EBUSY; + case 0x11: + return -EOPNOTSUPP; + case 0x12: + return -EINVAL; + default: + return -EINVAL; + } +} + +static int __radio_hci_request(struct radio_hci_dev *hdev, + int (*req)(struct radio_hci_dev *hdev, + unsigned long param), + unsigned long param, unsigned long timeout_msecs, + bool interruptible) +{ + int err = 0; + DECLARE_WAITQUEUE(wait, current); + + if (unlikely(hdev == NULL)) { + FMDERR("%s, hci dev is null\n", __func__); + return -EINVAL; + } + + mutex_lock(&iris_fm); + hdev->req_status = HCI_REQ_PEND; + + add_wait_queue(&hdev->req_wait_q, &wait); + if (interruptible) + set_current_state(TASK_INTERRUPTIBLE); + else + set_current_state(TASK_UNINTERRUPTIBLE); + + err = req(hdev, param); + + schedule_timeout(msecs_to_jiffies(timeout_msecs)); + + remove_wait_queue(&hdev->req_wait_q, &wait); + + if (interruptible && signal_pending(current)) { + mutex_unlock(&iris_fm); + return -EINTR; + } + + switch (hdev->req_status) { + case HCI_REQ_DONE: + case HCI_REQ_STATUS: + err = radio_hci_err(hdev->req_result); + break; + default: + err = -ETIMEDOUT; + break; + } + + hdev->req_status = hdev->req_result = 0; + mutex_unlock(&iris_fm); + + return err; +} + +static inline int radio_hci_request_interruptible(struct radio_hci_dev *hdev, + int (*req)(struct + radio_hci_dev * hdev, unsigned long param), + unsigned long param, unsigned long timeout_msecs) +{ + int ret = 0; + + ret = __radio_hci_request(hdev, req, param, timeout_msecs, true); + + return ret; +} + +static inline int radio_hci_request_uninterruptible(struct radio_hci_dev *hdev, + int (*req)(struct + radio_hci_dev * hdev, unsigned long param), + unsigned long param, unsigned long timeout_msecs) +{ + int ret = 0; + + ret = __radio_hci_request(hdev, req, param, timeout_msecs, false); + + return ret; +} + +static inline int radio_hci_request(struct radio_hci_dev *hdev, + int (*req)(struct + radio_hci_dev * hdev, unsigned long param), + unsigned long param, unsigned long timeout_msecs) +{ + return radio_hci_request_interruptible(hdev, req, param, timeout_msecs); +} + +static inline int hci_conf_event_mask(__u8 *arg, + struct radio_hci_dev *hdev) +{ + u8 event_mask; + + if (arg == NULL) { + FMDERR("%s, arg is null\n", __func__); + return -EINVAL; + } + event_mask = *arg; + return radio_hci_request(hdev, hci_fm_set_event_mask, + event_mask, RADIO_HCI_TIMEOUT); +} +static int hci_set_fm_recv_conf(struct hci_fm_recv_conf_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_recv_conf_req *set_recv_conf = arg; + + ret = radio_hci_request(hdev, hci_set_fm_recv_conf_req, (unsigned + long)set_recv_conf, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_set_fm_trans_conf(struct hci_fm_trans_conf_req_struct *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_trans_conf_req_struct *set_trans_conf = arg; + + ret = radio_hci_request(hdev, hci_set_fm_trans_conf_req, (unsigned + long)set_trans_conf, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_tune_station(__u32 *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + __u32 tune_freq; + + if (arg == NULL) { + FMDERR("%s, arg is null\n", __func__); + return -EINVAL; + } + tune_freq = *arg; + ret = radio_hci_request(hdev, hci_fm_tune_station_req, tune_freq, + RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_set_fm_mute_mode(struct hci_fm_mute_mode_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_mute_mode_req *set_mute_conf = arg; + + ret = radio_hci_request(hdev, hci_set_fm_mute_mode_req, (unsigned + long)set_mute_conf, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_set_fm_stereo_mode(struct hci_fm_stereo_mode_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_stereo_mode_req *set_stereo_conf = arg; + + ret = radio_hci_request(hdev, hci_set_fm_stereo_mode_req, (unsigned + long)set_stereo_conf, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_set_antenna(__u8 *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + __u8 antenna; + + if (arg == NULL) { + FMDERR("%s, arg is null\n", __func__); + return -EINVAL; + } + antenna = *arg; + ret = radio_hci_request(hdev, hci_fm_set_antenna_req, antenna, + RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_set_signal_threshold(__u8 *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + __u8 sig_threshold; + + if (arg == NULL) { + FMDERR("%s, arg is null\n", __func__); + return -EINVAL; + } + sig_threshold = *arg; + ret = radio_hci_request(hdev, hci_fm_set_sig_threshold_req, + sig_threshold, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_search_stations(struct hci_fm_search_station_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_search_station_req *srch_stations = arg; + + ret = radio_hci_request(hdev, hci_fm_search_stations_req, (unsigned + long)srch_stations, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_search_rds_stations(struct hci_fm_search_rds_station_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_search_rds_station_req *srch_stations = arg; + + ret = radio_hci_request(hdev, hci_fm_srch_rds_stations_req, (unsigned + long)srch_stations, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_search_station_list + (struct hci_fm_search_station_list_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_search_station_list_req *srch_list = arg; + + ret = radio_hci_request(hdev, hci_fm_srch_station_list_req, (unsigned + long)srch_list, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_rds_grp(struct hci_fm_rds_grp_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_rds_grp_req *fm_grp_mask = arg; + + ret = radio_hci_request(hdev, hci_fm_rds_grp_mask_req, (unsigned + long)fm_grp_mask, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_rds_grps_process(__u32 *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + __u32 fm_grps_process; + + if (arg == NULL) { + FMDERR("%s, arg is null\n", __func__); + return -EINVAL; + } + fm_grps_process = *arg; + ret = radio_hci_request(hdev, hci_fm_rds_grp_process_req, + fm_grps_process, RADIO_HCI_TIMEOUT); + + return ret; +} + +int hci_def_data_read(struct hci_fm_def_data_rd_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_def_data_rd_req *def_data_rd = arg; + + ret = radio_hci_request(hdev, hci_def_data_read_req, (unsigned + long)def_data_rd, RADIO_HCI_TIMEOUT); + + return ret; +} + +int hci_def_data_write(struct hci_fm_def_data_wr_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_def_data_wr_req *def_data_wr = arg; + + ret = radio_hci_request(hdev, hci_def_data_write_req, (unsigned + long)def_data_wr, RADIO_HCI_TIMEOUT); + + return ret; +} + +int hci_fm_do_calibration(__u8 *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + __u8 mode; + + if (arg == NULL) { + FMDERR("%s, arg is null\n", __func__); + return -EINVAL; + } + mode = *arg; + ret = radio_hci_request(hdev, hci_fm_do_calibration_req, mode, + RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_read_grp_counters(__u8 *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + __u8 reset_counters; + + if (arg == NULL) { + FMDERR("%s, arg is null\n", __func__); + return -EINVAL; + } + reset_counters = *arg; + ret = radio_hci_request(hdev, hci_read_grp_counters_req, + reset_counters, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_set_notch_filter(__u8 *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + __u8 notch_filter; + + if (arg == NULL) { + FMDERR("%s, arg is null\n", __func__); + return -EINVAL; + } + + notch_filter = *arg; + ret = radio_hci_request(hdev, hci_set_notch_filter_req, + notch_filter, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_peek_data(struct hci_fm_riva_data *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_riva_data *peek_data = arg; + + ret = radio_hci_request(hdev, hci_peek_data_req, (unsigned + long)peek_data, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_poke_data(struct hci_fm_riva_poke *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_riva_poke *poke_data = arg; + + ret = radio_hci_request(hdev, hci_poke_data_req, (unsigned + long)poke_data, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_ssbi_peek_reg(struct hci_fm_ssbi_peek *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_ssbi_peek *ssbi_peek_reg = arg; + + ret = radio_hci_request(hdev, hci_ssbi_peek_reg_req, (unsigned + long)ssbi_peek_reg, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_ssbi_poke_reg(struct hci_fm_ssbi_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_ssbi_req *ssbi_poke_reg = arg; + + ret = radio_hci_request(hdev, hci_ssbi_poke_reg_req, (unsigned + long)ssbi_poke_reg, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_set_ch_det_thresholds_req(struct hci_fm_ch_det_threshold *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_ch_det_threshold *ch_det_threshold = arg; + + ret = radio_hci_request(hdev, hci_fm_set_ch_det_th, + (unsigned long)ch_det_threshold, RADIO_HCI_TIMEOUT); + return ret; +} + +static int hci_fm_set_cal_req_proc(struct radio_hci_dev *hdev, + unsigned long param) +{ + u16 opcode = 0; + struct hci_fm_set_cal_req_proc *cal_req = + (struct hci_fm_set_cal_req_proc *)param; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_SET_CALIBRATION); + return radio_hci_send_cmd(hdev, opcode, + sizeof(struct hci_fm_set_cal_req_proc), cal_req); +} + +static int hci_fm_do_cal_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + u16 opcode = 0; + u8 cal_mode = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_DO_CALIBRATION); + return radio_hci_send_cmd(hdev, opcode, sizeof(cal_mode), + &cal_mode); + +} + +static int hci_fm_set_spur_tbl_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + u16 opcode = 0, len = 0; + struct hci_fm_set_spur_table_req *spur_req = + (struct hci_fm_set_spur_table_req *)param; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_SET_SPUR_TABLE); + if (spur_req->no_of_freqs_entries > ENTRIES_EACH_CMD) + len = (ENTRIES_EACH_CMD * SPUR_DATA_LEN) + + SPUR_DATA_INDEX; + else + len = (spur_req->no_of_freqs_entries * SPUR_DATA_LEN) + + SPUR_DATA_INDEX; + + return radio_hci_send_cmd(hdev, opcode, len, spur_req); +} + +static int hci_fm_get_spur_tbl_data(struct radio_hci_dev *hdev, + unsigned long param) +{ + u16 opcode = 0; + unsigned int spur_freq = (unsigned int)param; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_GET_SPUR_TABLE); + return radio_hci_send_cmd(hdev, opcode, sizeof(int), &spur_freq); +} + +static int hci_set_blend_tbl_req(struct hci_fm_blend_table *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_blend_table *blend_tbl = arg; + + ret = radio_hci_request(hdev, hci_fm_set_blend_tbl, + (unsigned long)blend_tbl, RADIO_HCI_TIMEOUT); + return ret; +} + +static int hci_cmd_internal(unsigned int cmd, struct radio_hci_dev *hdev, + bool interruptible) +{ + int ret = 0; + unsigned long arg = 0; + radio_hci_request_func radio_hci_request; + + FMDBG("hdev %pK cmd 0x%x", hdev, cmd); + + if (!hdev) + return -ENODEV; + + radio_hci_request = interruptible ? radio_hci_request_interruptible : + radio_hci_request_uninterruptible; + + switch (cmd) { + case HCI_FM_ENABLE_RECV_CMD: + ret = radio_hci_request(hdev, hci_fm_enable_recv_req, arg, + RADIO_HCI_TIMEOUT); + break; + + case HCI_FM_DISABLE_RECV_CMD: + ret = radio_hci_request(hdev, hci_fm_disable_recv_req, arg, + RADIO_HCI_TIMEOUT); + break; + + case HCI_FM_GET_RECV_CONF_CMD: + ret = radio_hci_request(hdev, hci_get_fm_recv_conf_req, arg, + RADIO_HCI_TIMEOUT); + break; + + case HCI_FM_GET_STATION_PARAM_CMD: + ret = radio_hci_request(hdev, + hci_fm_get_station_param_req, arg, + RADIO_HCI_TIMEOUT); + break; + + case HCI_FM_GET_SIGNAL_TH_CMD: + ret = radio_hci_request(hdev, + hci_fm_get_sig_threshold_req, arg, + RADIO_HCI_TIMEOUT); + break; + + case HCI_FM_GET_PROGRAM_SERVICE_CMD: + ret = radio_hci_request(hdev, + hci_fm_get_program_service_req, arg, + RADIO_HCI_TIMEOUT); + break; + + case HCI_FM_GET_RADIO_TEXT_CMD: + ret = radio_hci_request(hdev, hci_fm_get_radio_text_req, arg, + RADIO_HCI_TIMEOUT); + break; + + case HCI_FM_GET_AF_LIST_CMD: + ret = radio_hci_request(hdev, hci_fm_get_af_list_req, arg, + RADIO_HCI_TIMEOUT); + break; + + case HCI_FM_CANCEL_SEARCH_CMD: + ret = radio_hci_request(hdev, hci_fm_cancel_search_req, arg, + RADIO_HCI_TIMEOUT); + break; + + case HCI_FM_RESET_CMD: + ret = radio_hci_request(hdev, hci_fm_reset_req, arg, + RADIO_HCI_TIMEOUT); + break; + + case HCI_FM_GET_FEATURES_CMD: + ret = radio_hci_request(hdev, + hci_fm_get_feature_lists_req, arg, + RADIO_HCI_TIMEOUT); + break; + + case HCI_FM_STATION_DBG_PARAM_CMD: + ret = radio_hci_request(hdev, + hci_fm_get_station_dbg_param_req, arg, + RADIO_HCI_TIMEOUT); + break; + + case HCI_FM_ENABLE_TRANS_CMD: + ret = radio_hci_request(hdev, hci_fm_enable_trans_req, arg, + RADIO_HCI_TIMEOUT); + break; + + case HCI_FM_DISABLE_TRANS_CMD: + ret = radio_hci_request(hdev, hci_fm_disable_trans_req, arg, + RADIO_HCI_TIMEOUT); + break; + + case HCI_FM_GET_TX_CONFIG: + ret = radio_hci_request(hdev, hci_get_fm_trans_conf_req, arg, + RADIO_HCI_TIMEOUT); + break; + case HCI_FM_GET_DET_CH_TH_CMD: + ret = radio_hci_request(hdev, hci_fm_get_ch_det_th, arg, + RADIO_HCI_TIMEOUT); + break; + case HCI_FM_GET_BLND_TBL_CMD: + ret = radio_hci_request(hdev, hci_fm_get_blend_tbl, arg, + RADIO_HCI_TIMEOUT); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + + +static int hci_cmd(unsigned int cmd, struct radio_hci_dev *hdev) +{ + return hci_cmd_internal(cmd, hdev, true); +} + +static int hci_cmd_uninterruptible(unsigned int cmd, struct radio_hci_dev *hdev) +{ + return hci_cmd_internal(cmd, hdev, false); +} + +static void radio_hci_req_complete(struct radio_hci_dev *hdev, int result) +{ + + if (unlikely(hdev == NULL)) { + FMDERR("%s, hci device is null\n", __func__); + return; + } + hdev->req_result = result; + hdev->req_status = HCI_REQ_DONE; + wake_up(&hdev->req_wait_q); +} + +static void radio_hci_status_complete(struct radio_hci_dev *hdev, int result) +{ + if (unlikely(hdev == NULL)) { + FMDERR("%s, hci device is null\n", __func__); + return; + } + hdev->req_result = result; + hdev->req_status = HCI_REQ_STATUS; + wake_up(&hdev->req_wait_q); +} + +static void hci_cc_rsp(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + __u8 status; + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + status = *((__u8 *) skb->data); + + radio_hci_req_complete(hdev, status); +} + +static void hci_cc_fm_disable_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + __u8 status; + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + FMDBG("hdev %pK skb %p", hdev, skb); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null"); + return; + } + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + + status = *((__u8 *) skb->data); + if ((radio->mode == FM_TURNING_OFF) && (status == 0)) { + if (!radio->is_fm_closing) + iris_q_event(radio, IRIS_EVT_RADIO_DISABLED); + radio_hci_req_complete(hdev, status); + radio->mode = FM_OFF; + } else if (radio->mode == FM_CALIB) { + radio_hci_req_complete(hdev, status); + } else if ((radio->mode == FM_RECV) || (radio->mode == FM_TRANS)) { + iris_q_event(radio, IRIS_EVT_RADIO_DISABLED); + radio->mode = FM_OFF; + } else if ((radio->mode == FM_TURNING_OFF) && (status != 0)) { + radio_hci_req_complete(hdev, status); + } +} + +static void hci_cc_conf_rsp(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + struct hci_fm_conf_rsp *rsp; + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null"); + return; + } + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + rsp = (struct hci_fm_conf_rsp *)skb->data; + if (!rsp->status) + radio->recv_conf = rsp->recv_conf_rsp; + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_fm_trans_get_conf_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_fm_get_trans_conf_rsp *rsp; + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + + rsp = (struct hci_fm_get_trans_conf_rsp *)skb->data; + if (!rsp->status) + memcpy((void *)&radio->trans_conf, + (void *)&rsp->trans_conf_rsp, + sizeof(rsp->trans_conf_rsp)); + + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_fm_enable_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_fm_conf_rsp *rsp; + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + FMDBG("hdev %pK skb %p", hdev, skb); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + + rsp = (struct hci_fm_conf_rsp *)skb->data; + if (rsp->status) { + radio_hci_req_complete(hdev, rsp->status); + return; + } + + radio_hci_req_complete(hdev, rsp->status); +} + + +static void hci_cc_fm_trans_set_conf_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_fm_conf_rsp *rsp; + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + rsp = (struct hci_fm_conf_rsp *)skb->data; + if (!rsp->status) + iris_q_event(radio, HCI_EV_CMD_COMPLETE); + + radio_hci_req_complete(hdev, rsp->status); +} + + +static void hci_cc_sig_threshold_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_fm_sig_threshold_rsp *rsp; + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + + rsp = (struct hci_fm_sig_threshold_rsp *)skb->data; + if (!rsp->status) + memcpy(&radio->sig_th, rsp, + sizeof(struct hci_fm_sig_threshold_rsp)); + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_station_rsp(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + struct hci_fm_station_rsp *rsp; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + + rsp = (struct hci_fm_station_rsp *)skb->data; + radio->fm_st_rsp = *(rsp); + + /* Tune is always successful */ + radio_hci_req_complete(hdev, 0); +} + +static void hci_cc_prg_srv_rsp(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + struct hci_fm_prgm_srv_rsp *rsp; + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + + rsp = (struct hci_fm_prgm_srv_rsp *)skb->data; + + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_rd_txt_rsp(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + struct hci_fm_radio_txt_rsp *rsp; + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + + rsp = (struct hci_fm_radio_txt_rsp *)skb->data; + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_af_list_rsp(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + struct hci_fm_af_list_rsp *rsp; + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + + rsp = (struct hci_fm_af_list_rsp *)skb->data; + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_feature_list_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct v4l2_capability *v4l_cap; + struct hci_fm_feature_list_rsp *rsp; + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + + rsp = (struct hci_fm_feature_list_rsp *)skb->data; + v4l_cap = &radio->g_cap; + + if (!rsp->status) + v4l_cap->capabilities = (rsp->feature_mask & 0x000002) | + (rsp->feature_mask & 0x000001); + + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_dbg_param_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + struct hci_fm_dbg_param_rsp *rsp; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + + rsp = (struct hci_fm_dbg_param_rsp *)skb->data; + radio->st_dbg_param = *(rsp); + radio_hci_req_complete(hdev, radio->st_dbg_param.status); +} + +static void iris_q_evt_data(struct iris_device *radio, + char *data, int len, int event) +{ + struct kfifo *data_b; + + FMDBG("radio %pK data %p len %d event %d", radio, data, len, event); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + data_b = &radio->data_buf[event]; + if (kfifo_in_locked(data_b, data, len, &radio->buf_lock[event])) + wake_up_interruptible(&radio->event_queue); +} + +static void hci_cc_riva_peek_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u8 status; + int len; + char *data; + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + status = *((__u8 *) skb->data); + if (!status) { + len = skb->data[RIVA_PEEK_LEN_OFSET] + RIVA_PEEK_PARAM; + data = kmalloc(len, GFP_ATOMIC); + + if (data != NULL) { + memcpy(data, &skb->data[PEEK_DATA_OFSET], len); + iris_q_evt_data(radio, data, len, IRIS_BUF_PEEK); + kfree(data); + } + } + + radio_hci_req_complete(hdev, status); +} + +static void hci_cc_riva_read_default_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u8 status; + __u8 len; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + status = *((__u8 *) skb->data); + if (!status) { + len = skb->data[1]; + memset(&radio->default_data, 0, + sizeof(struct hci_fm_data_rd_rsp)); + memcpy(&radio->default_data, &skb->data[0], len+2); + iris_q_evt_data(radio, &skb->data[0], len+2, + IRIS_BUF_RD_DEFAULT); + } + radio_hci_req_complete(hdev, status); +} + +static void hci_cc_get_spur_tbl(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u8 status; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + status = *((__u8 *) skb->data); + if (!status) { + iris_q_evt_data(radio, &skb->data[1], SPUR_DATA_LEN, + IRIS_BUF_SPUR); + iris_q_event(radio, IRIS_EVT_SPUR_TBL); + } + radio_hci_req_complete(hdev, status); +} + +static void hci_cc_ssbi_peek_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u8 status; + char *data; + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + status = *((__u8 *) skb->data); + if (!status) { + data = kmalloc(SSBI_PEEK_LEN, GFP_ATOMIC); + if (data != NULL) { + data[0] = skb->data[PEEK_DATA_OFSET]; + iris_q_evt_data(radio, data, SSBI_PEEK_LEN, + IRIS_BUF_SSBI_PEEK); + kfree(data); + } + } + + radio_hci_req_complete(hdev, status); +} + +static void hci_cc_rds_grp_cntrs_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u8 status; + char *data; + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + status = *((__u8 *) skb->data); + if (!status) { + data = kmalloc(RDS_GRP_CNTR_LEN, GFP_ATOMIC); + if (data != NULL) { + memcpy(data, &skb->data[1], RDS_GRP_CNTR_LEN); + iris_q_evt_data(radio, data, RDS_GRP_CNTR_LEN, + IRIS_BUF_RDS_CNTRS); + kfree(data); + } + } + radio_hci_req_complete(hdev, status); +} + +static void hci_cc_do_calibration_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + static struct hci_cc_do_calibration_rsp rsp; + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + + rsp.status = skb->data[0]; + rsp.mode = skb->data[CALIB_MODE_OFSET]; + + if (!rsp.status) { + if (rsp.mode == PROCS_CALIB_MODE) { + memcpy(&rsp.data[0], &skb->data[CALIB_DATA_OFSET], + PROCS_CALIB_SIZE); + iris_q_evt_data(radio, rsp.data, PROCS_CALIB_SIZE, + IRIS_BUF_CAL_DATA); + } + } + radio_hci_req_complete(hdev, rsp.status); +} + +static void hci_cc_get_ch_det_threshold_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + u8 status; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + status = skb->data[0]; + if (!status) + memcpy(&radio->ch_det_threshold, &skb->data[1], + sizeof(struct hci_fm_ch_det_threshold)); + + radio_hci_req_complete(hdev, status); +} + +static void hci_cc_get_blend_tbl_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + u8 status; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + status = skb->data[0]; + if (!status) + memcpy(&radio->blend_tbl, &skb->data[1], + sizeof(struct hci_fm_blend_table)); + + radio_hci_req_complete(hdev, status); +} + +static inline void hci_cmd_complete_event(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_ev_cmd_complete *cmd_compl_ev; + __u16 opcode; + + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + + cmd_compl_ev = (struct hci_ev_cmd_complete *)skb->data; + skb_pull(skb, sizeof(*cmd_compl_ev)); + + opcode = __le16_to_cpu(cmd_compl_ev->cmd_opcode); + + FMDBG("opcode 0x%x", opcode); + switch (opcode) { + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_ENABLE_RECV_REQ): + case hci_trans_ctrl_cmd_op_pack(HCI_OCF_FM_ENABLE_TRANS_REQ): + hci_cc_fm_enable_rsp(hdev, skb); + break; + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_RECV_CONF_REQ): + hci_cc_conf_rsp(hdev, skb); + break; + + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_DISABLE_RECV_REQ): + case hci_trans_ctrl_cmd_op_pack(HCI_OCF_FM_DISABLE_TRANS_REQ): + hci_cc_fm_disable_rsp(hdev, skb); + break; + + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_RECV_CONF_REQ): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_MUTE_MODE_REQ): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_STEREO_MODE_REQ): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_ANTENNA): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_SIGNAL_THRESHOLD): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_CANCEL_SEARCH): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_RDS_GRP): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_RDS_GRP_PROCESS): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_EN_WAN_AVD_CTRL): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_EN_NOTCH_CTRL): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_CH_DET_THRESHOLD): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_BLND_TBL): + case hci_trans_ctrl_cmd_op_pack(HCI_OCF_FM_RDS_RT_REQ): + case hci_trans_ctrl_cmd_op_pack(HCI_OCF_FM_RDS_PS_REQ): + case hci_common_cmd_op_pack(HCI_OCF_FM_DEFAULT_DATA_WRITE): + hci_cc_rsp(hdev, skb); + break; + case hci_common_cmd_op_pack(HCI_OCF_FM_RESET): + case hci_diagnostic_cmd_op_pack(HCI_OCF_FM_SSBI_POKE_REG): + case hci_diagnostic_cmd_op_pack(HCI_OCF_FM_POKE_DATA): + case hci_diagnostic_cmd_op_pack(HCI_FM_SET_INTERNAL_TONE_GENRATOR): + case hci_common_cmd_op_pack(HCI_OCF_FM_SET_CALIBRATION): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_EVENT_MASK): + case hci_common_cmd_op_pack(HCI_OCF_FM_SET_SPUR_TABLE): + hci_cc_rsp(hdev, skb); + break; + case hci_common_cmd_op_pack(HCI_OCF_FM_GET_SPUR_TABLE): + hci_cc_get_spur_tbl(hdev, skb); + break; + case hci_diagnostic_cmd_op_pack(HCI_OCF_FM_SSBI_PEEK_REG): + hci_cc_ssbi_peek_rsp(hdev, skb); + break; + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_SIGNAL_THRESHOLD): + hci_cc_sig_threshold_rsp(hdev, skb); + break; + + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_STATION_PARAM_REQ): + hci_cc_station_rsp(hdev, skb); + break; + + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_PROGRAM_SERVICE_REQ): + hci_cc_prg_srv_rsp(hdev, skb); + break; + + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_RADIO_TEXT_REQ): + hci_cc_rd_txt_rsp(hdev, skb); + break; + + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_AF_LIST_REQ): + hci_cc_af_list_rsp(hdev, skb); + break; + + case hci_common_cmd_op_pack(HCI_OCF_FM_DEFAULT_DATA_READ): + hci_cc_riva_read_default_rsp(hdev, skb); + break; + + case hci_diagnostic_cmd_op_pack(HCI_OCF_FM_PEEK_DATA): + hci_cc_riva_peek_rsp(hdev, skb); + break; + + case hci_common_cmd_op_pack(HCI_OCF_FM_GET_FEATURE_LIST): + hci_cc_feature_list_rsp(hdev, skb); + break; + + case hci_diagnostic_cmd_op_pack(HCI_OCF_FM_STATION_DBG_PARAM): + hci_cc_dbg_param_rsp(hdev, skb); + break; + case hci_trans_ctrl_cmd_op_pack(HCI_OCF_FM_SET_TRANS_CONF_REQ): + hci_cc_fm_trans_set_conf_rsp(hdev, skb); + break; + + case hci_status_param_op_pack(HCI_OCF_FM_READ_GRP_COUNTERS): + hci_cc_rds_grp_cntrs_rsp(hdev, skb); + break; + case hci_common_cmd_op_pack(HCI_OCF_FM_DO_CALIBRATION): + hci_cc_do_calibration_rsp(hdev, skb); + break; + + case hci_trans_ctrl_cmd_op_pack(HCI_OCF_FM_GET_TRANS_CONF_REQ): + hci_cc_fm_trans_get_conf_rsp(hdev, skb); + break; + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_CH_DET_THRESHOLD): + hci_cc_get_ch_det_threshold_rsp(hdev, skb); + break; + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_BLND_TBL): + hci_cc_get_blend_tbl_rsp(hdev, skb); + break; + default: + FMDERR("%s opcode 0x%x\n", hdev->name, opcode); + break; + } + +} + +static inline void hci_cmd_status_event(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_ev_cmd_status *ev = (void *) skb->data; + + radio_hci_status_complete(hdev, ev->status); +} + +static inline void hci_ev_tune_status(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + int i; + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + memcpy(&radio->fm_st_rsp.station_rsp, &skb->data[0], + sizeof(struct hci_ev_tune_status)); + iris_q_event(radio, IRIS_EVT_TUNE_SUCC); + + for (i = 0; i < IRIS_BUF_MAX; i++) { + if (i >= IRIS_BUF_RT_RDS) + kfifo_reset(&radio->data_buf[i]); + } + if (radio->fm_st_rsp.station_rsp.serv_avble) + iris_q_event(radio, IRIS_EVT_ABOVE_TH); + else + iris_q_event(radio, IRIS_EVT_BELOW_TH); + + if (radio->fm_st_rsp.station_rsp.stereo_prg) + iris_q_event(radio, IRIS_EVT_STEREO); + else if (radio->fm_st_rsp.station_rsp.stereo_prg == 0) + iris_q_event(radio, IRIS_EVT_MONO); + + if (radio->fm_st_rsp.station_rsp.rds_sync_status) + iris_q_event(radio, IRIS_EVT_RDS_AVAIL); + else + iris_q_event(radio, IRIS_EVT_RDS_NOT_AVAIL); +} + +static inline void hci_ev_search_compl(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + radio->search_on = 0; + iris_q_event(radio, IRIS_EVT_SEEK_COMPLETE); +} + +static inline void hci_ev_srch_st_list_compl(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + struct hci_ev_srch_list_compl *ev; + int cnt; + int stn_num; + int rel_freq; + int abs_freq; + int len; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + ev = kmalloc(sizeof(*ev), GFP_ATOMIC); + if (!ev) + return; + + ev->num_stations_found = skb->data[STN_NUM_OFFSET]; + len = ev->num_stations_found * PARAMS_PER_STATION + STN_FREQ_OFFSET; + + for (cnt = STN_FREQ_OFFSET, stn_num = 0; + (cnt < len) && (stn_num < ev->num_stations_found) + && (stn_num < ARRAY_SIZE(ev->rel_freq)); + cnt += PARAMS_PER_STATION, stn_num++) { + abs_freq = *((int *)&skb->data[cnt]); + rel_freq = abs_freq - radio->recv_conf.band_low_limit; + rel_freq = (rel_freq * 20) / KHZ_TO_MHZ; + + ev->rel_freq[stn_num].rel_freq_lsb = GET_LSB(rel_freq); + ev->rel_freq[stn_num].rel_freq_msb = GET_MSB(rel_freq); + } + + len = ev->num_stations_found * 2 + sizeof(ev->num_stations_found); + iris_q_event(radio, IRIS_EVT_NEW_SRCH_LIST); + iris_q_evt_data(radio, (char *)ev, len, IRIS_BUF_SRCH_LIST); + kfree(ev); +} + +static inline void hci_ev_search_next(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + iris_q_event(radio, IRIS_EVT_SCAN_NEXT); +} + +static inline void hci_ev_stereo_status(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u8 st_status; + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + st_status = *((__u8 *) skb->data); + if (st_status) + iris_q_event(radio, IRIS_EVT_STEREO); + else + iris_q_event(radio, IRIS_EVT_MONO); +} + +static void hci_ev_raw_rds_group_data(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio; + unsigned char blocknum, index; + struct rds_grp_data temp; + unsigned int mask_bit; + unsigned short int aid, agt, gtc; + unsigned short int carrier; + + radio = video_get_drvdata(video_get_dev()); + index = RDSGRP_DATA_OFFSET; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return; + } + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + + for (blocknum = 0; blocknum < RDS_BLOCKS_NUM; blocknum++) { + temp.rdsBlk[blocknum].rdsLsb = + (skb->data[index]); + temp.rdsBlk[blocknum].rdsMsb = + (skb->data[index+1]); + index = index + 2; + } + + aid = AID(temp.rdsBlk[3].rdsLsb, temp.rdsBlk[3].rdsMsb); + gtc = GTC(temp.rdsBlk[1].rdsMsb); + agt = AGT(temp.rdsBlk[1].rdsLsb); + + if (gtc == GRP_3A) { + switch (aid) { + case ERT_AID: + /* calculate the grp mask for RDS grp + * which will contain actual eRT text + * + * Bit Pos 0 1 2 3 4 5 6 7 + * Grp Type 0A 0B 1A 1B 2A 2B 3A 3B + * + * similarly for rest grps + */ + mask_bit = (((agt >> 1) << 1) + (agt & 1)); + oda_agt = (1 << mask_bit); + utf_8_flag = (temp.rdsBlk[2].rdsLsb & 1); + formatting_dir = EXTRACT_BIT(temp.rdsBlk[2].rdsLsb, + ERT_FORMAT_DIR_BIT); + if (ert_carrier != agt) + iris_q_event(radio, IRIS_EVT_NEW_ODA); + ert_carrier = agt; + break; + case RT_PLUS_AID: + /* calculate the grp mask for RDS grp + * which will contain actual eRT text + * + * Bit Pos 0 1 2 3 4 5 6 7 + * Grp Type 0A 0B 1A 1B 2A 2B 3A 3B + * + * similarly for rest grps + */ + mask_bit = (((agt >> 1) << 1) + (agt & 1)); + oda_agt = (1 << mask_bit); + /*Extract 5th bit of MSB (b7b6b5b4b3b2b1b0)*/ + rt_ert_flag = EXTRACT_BIT(temp.rdsBlk[2].rdsMsb, + RT_ERT_FLAG_BIT); + if (rt_plus_carrier != agt) + iris_q_event(radio, IRIS_EVT_NEW_ODA); + rt_plus_carrier = agt; + break; + default: + oda_agt = 0; + break; + } + } else { + carrier = gtc; + if (carrier == rt_plus_carrier) + hci_ev_rt_plus(radio, temp); + else if (carrier == ert_carrier) + hci_buff_ert(radio, &temp); + } +} + +static void hci_buff_ert(struct iris_device *radio, + struct rds_grp_data *rds_buf) +{ + int i; + unsigned short int info_byte = 0; + unsigned short int byte_pair_index; + + if (rds_buf == NULL) { + FMDERR("%s, rds buffer is null\n", __func__); + return; + } + byte_pair_index = AGT(rds_buf->rdsBlk[1].rdsLsb); + if (byte_pair_index == 0) { + c_byt_pair_index = 0; + ert_len = 0; + } + if (c_byt_pair_index == byte_pair_index) { + c_byt_pair_index++; + for (i = 2; i <= 3; i++) { + info_byte = rds_buf->rdsBlk[i].rdsLsb; + info_byte |= (rds_buf->rdsBlk[i].rdsMsb << 8); + ert_buf[ert_len++] = rds_buf->rdsBlk[i].rdsMsb; + ert_buf[ert_len++] = rds_buf->rdsBlk[i].rdsLsb; + if ((utf_8_flag == 0) + && (info_byte == CARRIAGE_RETURN)) { + ert_len -= 2; + break; + } else if ((utf_8_flag == 1) + && + (rds_buf->rdsBlk[i].rdsMsb + == CARRIAGE_RETURN)) { + info_byte = CARRIAGE_RETURN; + ert_len -= 2; + break; + } else if ((utf_8_flag == 1) + && + (rds_buf->rdsBlk[i].rdsLsb + == CARRIAGE_RETURN)) { + info_byte = CARRIAGE_RETURN; + ert_len--; + break; + } + } + if ((byte_pair_index == MAX_ERT_SEGMENT) || + (info_byte == CARRIAGE_RETURN)) { + hci_ev_ert(radio); + c_byt_pair_index = 0; + ert_len = 0; + } + } else { + ert_len = 0; + c_byt_pair_index = 0; + } +} +static void hci_ev_ert(struct iris_device *radio) + +{ + char *data = NULL; + + if (ert_len <= 0) + return; + data = kmalloc((ert_len + 3), GFP_ATOMIC); + if (data != NULL) { + data[0] = ert_len; + data[1] = utf_8_flag; + data[2] = formatting_dir; + memcpy((data + 3), ert_buf, ert_len); + iris_q_evt_data(radio, data, (ert_len + 3), IRIS_BUF_ERT); + iris_q_event(radio, IRIS_EVT_NEW_ERT); + kfree(data); + } +} + +static void hci_ev_rt_plus(struct iris_device *radio, + struct rds_grp_data rds_buf) +{ + char tag_type1, tag_type2; + char *data = NULL; + int len = 0; + unsigned short int agt; + + agt = AGT(rds_buf.rdsBlk[1].rdsLsb); + /*right most 3 bits of Lsb of block 2 + * and left most 3 bits of Msb of block 3 + */ + tag_type1 = (((agt & TAG1_MSB_MASK) << TAG1_MSB_OFFSET) | + (rds_buf.rdsBlk[2].rdsMsb >> TAG1_LSB_OFFSET)); + + /*right most 1 bit of lsb of 3rd block + * and left most 5 bits of Msb of 4th block + */ + tag_type2 = (((rds_buf.rdsBlk[2].rdsLsb & TAG2_MSB_MASK) + << TAG2_MSB_OFFSET) | + (rds_buf.rdsBlk[3].rdsMsb >> TAG2_LSB_OFFSET)); + + if (tag_type1 != DUMMY_CLASS) + len += RT_PLUS_LEN_1_TAG; + if (tag_type2 != DUMMY_CLASS) + len += RT_PLUS_LEN_1_TAG; + + if (len != 0) { + len += 2; + data = kmalloc(len, GFP_ATOMIC); + } else { + FMDERR("Len is zero\n"); + return; + } + if (data != NULL) { + data[0] = len; + len = 1; + data[len++] = rt_ert_flag; + if (tag_type1 != DUMMY_CLASS) { + data[len++] = tag_type1; + /*start position of tag1 + *right most 5 bits of msb of 3rd block + *and left most bit of lsb of 3rd block + */ + data[len++] = (((rds_buf.rdsBlk[2].rdsMsb & + TAG1_POS_MSB_MASK) + << TAG1_POS_MSB_OFFSET) + | + (rds_buf.rdsBlk[2].rdsLsb >> + TAG1_POS_LSB_OFFSET)); + /*length of tag1 + *left most 6 bits of lsb of 3rd block + */ + data[len++] = ((rds_buf.rdsBlk[2].rdsLsb + >> TAG1_LEN_OFFSET) + & + TAG1_LEN_MASK) + 1; + } + if (tag_type2 != DUMMY_CLASS) { + data[len++] = tag_type2; + /*start position of tag2 + *right most 3 bit of msb of 4th block + *and left most 3 bits of lsb of 4th block + */ + data[len++] = (((rds_buf.rdsBlk[3].rdsMsb + & TAG2_POS_MSB_MASK) + << TAG2_POS_MSB_OFFSET) + | + (rds_buf.rdsBlk[3].rdsLsb + >> TAG2_POS_LSB_OFFSET)); + /*length of tag2 + *right most 5 bits of lsb of 4th block + */ + data[len++] = (rds_buf.rdsBlk[3].rdsLsb + & TAG2_LEN_MASK) + 1; + } + iris_q_evt_data(radio, data, len, IRIS_BUF_RT_PLUS); + iris_q_event(radio, IRIS_EVT_NEW_RT_PLUS); + kfree(data); + } +} + +static inline void hci_ev_program_service(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + int len; + char *data; + + len = (skb->data[RDS_PS_LENGTH_OFFSET] * RDS_STRING) + RDS_OFFSET; + iris_q_event(radio, IRIS_EVT_NEW_PS_RDS); + data = kmalloc(len, GFP_ATOMIC); + if (!data) + return; + + data[0] = skb->data[RDS_PS_LENGTH_OFFSET]; + data[1] = skb->data[RDS_PTYPE]; + data[2] = skb->data[RDS_PID_LOWER]; + data[3] = skb->data[RDS_PID_HIGHER]; + data[4] = 0; + + memcpy(data+RDS_OFFSET, &skb->data[RDS_PS_DATA_OFFSET], len-RDS_OFFSET); + + iris_q_evt_data(radio, data, len, IRIS_BUF_PS_RDS); + + kfree(data); +} + + +static inline void hci_ev_radio_text(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + int len = 0; + char *data; + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + iris_q_event(radio, IRIS_EVT_NEW_RT_RDS); + + while ((skb->data[len+RDS_OFFSET] != 0x0d) && (len < MAX_RT_LENGTH)) + len++; + data = kmalloc(len+RDS_OFFSET, GFP_ATOMIC); + if (!data) + return; + + data[0] = len; + data[1] = skb->data[RDS_PTYPE]; + data[2] = skb->data[RDS_PID_LOWER]; + data[3] = skb->data[RDS_PID_HIGHER]; + data[4] = skb->data[RT_A_B_FLAG_OFFSET]; + + memcpy(data+RDS_OFFSET, &skb->data[RDS_OFFSET], len); + data[len+RDS_OFFSET] = 0x00; + + iris_q_evt_data(radio, data, len+RDS_OFFSET, IRIS_BUF_RT_RDS); + + kfree(data); +} + +static void hci_ev_af_list(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + struct hci_ev_af_list ev; + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + ev.tune_freq = *((int *) &skb->data[0]); + ev.pi_code = *((__le16 *) &skb->data[PI_CODE_OFFSET]); + ev.af_size = skb->data[AF_SIZE_OFFSET]; + if (ev.af_size > AF_LIST_MAX) { + FMDERR("AF list size received more than available size\n"); + return; + } + memcpy(&ev.af_list[0], &skb->data[AF_LIST_OFFSET], + ev.af_size * sizeof(int)); + iris_q_event(radio, IRIS_EVT_NEW_AF_LIST); + iris_q_evt_data(radio, (char *)&ev, (7 + ev.af_size * sizeof(int)), + IRIS_BUF_AF_LIST); +} + +static void hci_ev_rds_lock_status(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u8 rds_status; + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + + rds_status = skb->data[0]; + + if (rds_status) + iris_q_event(radio, IRIS_EVT_RDS_AVAIL); + else + iris_q_event(radio, IRIS_EVT_RDS_NOT_AVAIL); +} + +static void hci_ev_service_available(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + u8 serv_avble; + + if (unlikely(skb == NULL)) { + FMDERR("%s, socket buffer is null\n", __func__); + return; + } + serv_avble = skb->data[0]; + if (serv_avble) + iris_q_event(radio, IRIS_EVT_ABOVE_TH); + else + iris_q_event(radio, IRIS_EVT_BELOW_TH); +} + +static void hci_ev_rds_grp_complete(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + iris_q_event(radio, IRIS_EVT_TXRDSDONE); +} + +void radio_hci_event_packet(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + struct radio_hci_event_hdr *hdr; + u8 event; + + if (skb == NULL) { + FMDERR("Socket buffer is NULL\n"); + return; + } + + hdr = (void *) skb->data; + event = hdr->evt; + + skb_pull(skb, RADIO_HCI_EVENT_HDR_SIZE); + + FMDBG("event 0x%x", event); + switch (event) { + case HCI_EV_TUNE_STATUS: + hci_ev_tune_status(hdev, skb); + break; + case HCI_EV_SEARCH_PROGRESS: + case HCI_EV_SEARCH_RDS_PROGRESS: + case HCI_EV_SEARCH_LIST_PROGRESS: + hci_ev_search_next(hdev, skb); + break; + case HCI_EV_STEREO_STATUS: + hci_ev_stereo_status(hdev, skb); + break; + case HCI_EV_RDS_LOCK_STATUS: + hci_ev_rds_lock_status(hdev, skb); + break; + case HCI_EV_SERVICE_AVAILABLE: + hci_ev_service_available(hdev, skb); + break; + case HCI_EV_RDS_RX_DATA: + hci_ev_raw_rds_group_data(hdev, skb); + break; + case HCI_EV_PROGRAM_SERVICE: + hci_ev_program_service(hdev, skb); + break; + case HCI_EV_RADIO_TEXT: + hci_ev_radio_text(hdev, skb); + break; + case HCI_EV_FM_AF_LIST: + hci_ev_af_list(hdev, skb); + break; + case HCI_EV_TX_RDS_GRP_COMPL: + hci_ev_rds_grp_complete(hdev, skb); + break; + case HCI_EV_TX_RDS_CONT_GRP_COMPL: + break; + + case HCI_EV_CMD_COMPLETE: + hci_cmd_complete_event(hdev, skb); + break; + + case HCI_EV_CMD_STATUS: + hci_cmd_status_event(hdev, skb); + break; + + case HCI_EV_SEARCH_COMPLETE: + case HCI_EV_SEARCH_RDS_COMPLETE: + hci_ev_search_compl(hdev, skb); + break; + + case HCI_EV_SEARCH_LIST_COMPLETE: + hci_ev_srch_st_list_compl(hdev, skb); + break; + + default: + break; + } +} + +/* + * fops/IOCTL helper functions + */ + +static int iris_search(struct iris_device *radio, int on, int dir) +{ + int retval = 0; + enum search_t srch; + int saved_val; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + + srch = radio->g_search_mode & SRCH_MODE; + saved_val = radio->search_on; + radio->search_on = on; + if (on) { + switch (srch) { + case SCAN_FOR_STRONG: + case SCAN_FOR_WEAK: + radio->srch_st_list.srch_list_dir = dir; + radio->srch_st_list.srch_list_mode = srch; + retval = hci_fm_search_station_list( + &radio->srch_st_list, radio->fm_hdev); + break; + case RDS_SEEK_PTY: + case RDS_SCAN_PTY: + case RDS_SEEK_PI: + srch = srch - SEARCH_RDS_STNS_MODE_OFFSET; + radio->srch_rds.srch_station.srch_mode = srch; + radio->srch_rds.srch_station.srch_dir = dir; + radio->srch_rds.srch_station.scan_time = + radio->g_scan_time; + retval = hci_fm_search_rds_stations(&radio->srch_rds, + radio->fm_hdev); + break; + default: + radio->srch_st.srch_mode = srch; + radio->srch_st.scan_time = radio->g_scan_time; + radio->srch_st.srch_dir = dir; + retval = hci_fm_search_stations( + &radio->srch_st, radio->fm_hdev); + break; + } + + } else { + retval = hci_cmd(HCI_FM_CANCEL_SEARCH_CMD, radio->fm_hdev); + } + + if (retval < 0) + radio->search_on = saved_val; + return retval; +} + +static int set_low_power_mode(struct iris_device *radio, int power_mode) +{ + + int rds_grps_proc = 0x00; + int retval = 0; + struct hci_fm_rds_grp_req grp_3a; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + + FMDBG("power mode old 0x%x new 0x%x", radio->power_mode, power_mode); + if (radio->power_mode != power_mode) { + + if (power_mode) { + memcpy(&grp_3a, &radio->rds_grp, + sizeof(struct hci_fm_rds_grp_req)); + /* Disable 3A group */ + grp_3a.rds_grp_enable_mask &= ~FM_RDS_3A_GRP; + retval = hci_fm_rds_grp(&grp_3a, radio->fm_hdev); + if (retval < 0) + FMDERR("error in disable 3A group mask\n"); + radio->event_mask = 0x00; + if (radio->af_jump_bit) + rds_grps_proc = 0x00 | AF_JUMP_ENABLE; + else + rds_grps_proc = 0x00; + retval = hci_fm_rds_grps_process( + &rds_grps_proc, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Disable RDS failed\n"); + return retval; + } + retval = hci_conf_event_mask(&radio->event_mask, + radio->fm_hdev); + } else { + /* Enable RDS group to normal */ + retval = hci_fm_rds_grp(&radio->rds_grp, + radio->fm_hdev); + if (retval < 0) + FMDERR("error in enable 3A group mask\n"); + radio->event_mask = SIG_LEVEL_INTR | + RDS_SYNC_INTR | AUDIO_CTRL_INTR; + retval = hci_conf_event_mask(&radio->event_mask, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Enable Async events failed\n"); + return retval; + } + retval = hci_fm_rds_grps_process( + &radio->g_rds_grp_proc_ps, + radio->fm_hdev); + } + radio->power_mode = power_mode; + } + return retval; +} +static int iris_recv_set_region(struct iris_device *radio, int req_region) +{ + int retval; + int saved_val; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + saved_val = radio->region; + radio->region = req_region; + + retval = hci_set_fm_recv_conf( + &radio->recv_conf, + radio->fm_hdev); + + if (retval < 0) + radio->region = saved_val; + + return retval; +} + + +static int iris_trans_set_region(struct iris_device *radio, int req_region) +{ + int retval; + int saved_val; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + + saved_val = radio->region; + radio->region = req_region; + + retval = hci_set_fm_trans_conf( + &radio->trans_conf, + radio->fm_hdev); + + if (retval < 0) + radio->region = saved_val; + return retval; +} + + +static int iris_set_freq(struct iris_device *radio, unsigned int freq) +{ + + int retval; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + retval = hci_fm_tune_station(&freq, radio->fm_hdev); + if (retval < 0) + FMDERR("Error while setting the frequency : %d\n", retval); + return retval; +} + + +static int iris_vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + unsigned char i; + int retval = -EINVAL; + + if (qc == NULL) { + FMDERR("%s, query ctrl is null\n", __func__); + return retval; + } + for (i = 0; i < ARRAY_SIZE(iris_v4l2_queryctrl); i++) { + if (qc->id && qc->id == iris_v4l2_queryctrl[i].id) { + memcpy(qc, &(iris_v4l2_queryctrl[i]), sizeof(*qc)); + retval = 0; + break; + } + } + + return retval; +} + +static int iris_do_calibration(struct iris_device *radio) +{ + char cal_mode = 0x00; + int retval = 0x00; + + FMDBG("radio %pK", radio); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + + cal_mode = PROCS_CALIB_MODE; + radio->mode = FM_CALIB; + retval = hci_cmd(HCI_FM_ENABLE_RECV_CMD, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Enable failed before calibration %x\n", retval); + radio->mode = FM_OFF; + return retval; + } + retval = radio_hci_request(radio->fm_hdev, hci_fm_do_cal_req, + (unsigned long)cal_mode, RADIO_HCI_TIMEOUT); + if (retval < 0) { + FMDERR("Do Process calibration failed %x\n", retval); + radio->mode = FM_RECV; + return retval; + } + retval = hci_cmd(HCI_FM_DISABLE_RECV_CMD, + radio->fm_hdev); + if (retval < 0) + FMDERR("Disable Failed after calibration %d\n", retval); + + return retval; +} +static int iris_vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + int retval = 0; + int cf0; + struct hci_fm_def_data_rd_req rd; + int lsb, msb; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + retval = -EINVAL; + goto end; + } + + if (ctrl == NULL) { + FMDERR("%s, v4l2 ctrl is null\n", __func__); + retval = -EINVAL; + goto end; + } + + FMDBG("id 0x%x", ctrl->id); + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: + break; + case V4L2_CID_AUDIO_MUTE: + if (is_valid_hard_mute(radio->mute_mode.hard_mute)) + ctrl->value = radio->mute_mode.hard_mute; + else + retval = -EINVAL; + break; + case V4L2_CID_PRIVATE_IRIS_SRCHMODE: + if (is_valid_srch_mode(radio->g_search_mode)) + ctrl->value = radio->g_search_mode; + else + retval = -EINVAL; + break; + case V4L2_CID_PRIVATE_IRIS_SCANDWELL: + if (is_valid_scan_dwell_prd(radio->g_scan_time)) + ctrl->value = radio->g_scan_time; + else + retval = -EINVAL; + break; + case V4L2_CID_PRIVATE_IRIS_SRCHON: + ctrl->value = radio->search_on; + break; + case V4L2_CID_PRIVATE_IRIS_STATE: + if (is_valid_fm_state(radio->mode)) + ctrl->value = radio->mode; + else + retval = -EINVAL; + break; + case V4L2_CID_PRIVATE_IRIS_IOVERC: + retval = hci_cmd(HCI_FM_STATION_DBG_PARAM_CMD, radio->fm_hdev); + if (retval < 0) + return retval; + ctrl->value = radio->st_dbg_param.io_verc; + break; + case V4L2_CID_PRIVATE_IRIS_INTDET: + retval = hci_cmd(HCI_FM_STATION_DBG_PARAM_CMD, radio->fm_hdev); + if (retval == 0) + ctrl->value = radio->st_dbg_param.in_det_out; + else + retval = -EINVAL; + break; + case V4L2_CID_PRIVATE_IRIS_REGION: + ctrl->value = radio->region; + break; + case V4L2_CID_PRIVATE_IRIS_SIGNAL_TH: + retval = hci_cmd(HCI_FM_GET_SIGNAL_TH_CMD, radio->fm_hdev); + if ((retval == 0) && + is_valid_sig_th(radio->sig_th.sig_threshold)) + ctrl->value = radio->sig_th.sig_threshold; + else + retval = -EINVAL; + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_PTY: + if (is_valid_pty(radio->srch_rds.srch_pty)) + ctrl->value = radio->srch_rds.srch_pty; + else + retval = -EINVAL; + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_PI: + if (is_valid_pi(radio->srch_rds.srch_pi)) + ctrl->value = radio->srch_rds.srch_pi; + else + retval = -EINVAL; + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_CNT: + if (is_valid_srch_station_cnt( + radio->srch_st_result.num_stations_found)) + ctrl->value = radio->srch_st_result.num_stations_found; + else + retval = -EINVAL; + break; + case V4L2_CID_PRIVATE_IRIS_EMPHASIS: + if (radio->mode == FM_RECV) { + retval = hci_cmd(HCI_FM_GET_RECV_CONF_CMD, + radio->fm_hdev); + if ((retval == 0) && + is_valid_emphasis(radio->recv_conf.emphasis)) + ctrl->value = radio->recv_conf.emphasis; + else + retval = -EINVAL; + } else if (radio->mode == FM_TRANS) { + retval = hci_cmd(HCI_FM_GET_TX_CONFIG, + radio->fm_hdev); + if ((retval == 0) && + is_valid_emphasis(radio->trans_conf.emphasis)) + ctrl->value = radio->trans_conf.emphasis; + else + retval = -EINVAL; + } else { + retval = -EINVAL; + FMDERR("Error in radio mode %d\n", retval); + } + break; + case V4L2_CID_PRIVATE_IRIS_RDS_STD: + if (radio->mode == FM_RECV) { + retval = hci_cmd(HCI_FM_GET_RECV_CONF_CMD, + radio->fm_hdev); + if ((retval == 0) && + is_valid_rds_std(radio->recv_conf.rds_std)) + ctrl->value = radio->recv_conf.rds_std; + else + retval = -EINVAL; + } else if (radio->mode == FM_TRANS) { + retval = hci_cmd(HCI_FM_GET_TX_CONFIG, + radio->fm_hdev); + if ((retval == 0) && + is_valid_rds_std(radio->trans_conf.rds_std)) + ctrl->value = radio->trans_conf.rds_std; + else + retval = -EINVAL; + } else { + retval = -EINVAL; + FMDERR("Error in radio mode %d\n", retval); + } + break; + case V4L2_CID_PRIVATE_IRIS_SPACING: + if (radio->mode == FM_RECV) { + retval = hci_cmd(HCI_FM_GET_RECV_CONF_CMD, + radio->fm_hdev); + if ((retval == 0) && + is_valid_chan_spacing( + radio->recv_conf.ch_spacing)) + ctrl->value = radio->recv_conf.ch_spacing; + else + retval = -EINVAL; + } else { + retval = -EINVAL; + FMDERR("Error in radio mode %d\n", retval); + } + break; + case V4L2_CID_PRIVATE_IRIS_RDSON: + if (radio->mode == FM_RECV) { + retval = hci_cmd(HCI_FM_GET_RECV_CONF_CMD, + radio->fm_hdev); + if ((retval == 0) && + is_valid_rds_std(radio->recv_conf.rds_std)) + ctrl->value = radio->recv_conf.rds_std; + else + retval = -EINVAL; + } else { + retval = -EINVAL; + FMDERR("Error in radio mode %d\n", retval); + } + break; + case V4L2_CID_PRIVATE_IRIS_RDSGROUP_MASK: + ctrl->value = radio->rds_grp.rds_grp_enable_mask; + break; + case V4L2_CID_PRIVATE_IRIS_RDSGROUP_PROC: + case V4L2_CID_PRIVATE_IRIS_PSALL: + ctrl->value = radio->g_rds_grp_proc_ps; + break; + case V4L2_CID_PRIVATE_IRIS_RDSD_BUF: + ctrl->value = radio->rds_grp.rds_buf_size; + break; + case V4L2_CID_PRIVATE_IRIS_LP_MODE: + ctrl->value = radio->power_mode; + break; + case V4L2_CID_PRIVATE_IRIS_ANTENNA: + ctrl->value = radio->g_antenna; + break; + case V4L2_CID_PRIVATE_IRIS_SOFT_MUTE: + retval = hci_cmd(HCI_FM_STATION_DBG_PARAM_CMD, radio->fm_hdev); + if ((retval == 0) && + is_valid_soft_mute(radio->mute_mode.soft_mute)) + ctrl->value = radio->mute_mode.soft_mute; + else + retval = -EINVAL; + break; + case V4L2_CID_PRIVATE_IRIS_DO_CALIBRATION: + retval = iris_do_calibration(radio); + break; + case V4L2_CID_PRIVATE_IRIS_GET_SINR: + if (radio->mode == FM_RECV) { + retval = hci_cmd(HCI_FM_GET_STATION_PARAM_CMD, + radio->fm_hdev); + if (retval == 0) + ctrl->value = radio->fm_st_rsp.station_rsp.sinr; + } else + retval = -EINVAL; + break; + case V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD: + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval == 0) + ctrl->value = radio->ch_det_threshold.high_th; + break; + case V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD: + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval == 0) + ctrl->value = radio->ch_det_threshold.low_th; + break; + case V4L2_CID_PRIVATE_SINR_THRESHOLD: + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval == 0) + ctrl->value = radio->ch_det_threshold.sinr; + break; + case V4L2_CID_PRIVATE_SINR_SAMPLES: + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval == 0) + ctrl->value = radio->ch_det_threshold.sinr_samples; + break; + case V4L2_CID_PRIVATE_VALID_CHANNEL: + ctrl->value = radio->is_station_valid; + break; + case V4L2_CID_PRIVATE_AF_RMSSI_TH: + rd.mode = FM_RDS_CNFG_MODE; + rd.length = FM_RDS_CNFG_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval == 0) { + lsb = radio->default_data.data[AF_RMSSI_TH_LSB_OFFSET]; + msb = radio->default_data.data[AF_RMSSI_TH_MSB_OFFSET]; + ctrl->value = ((msb << 8) | lsb); + } + break; + case V4L2_CID_PRIVATE_AF_RMSSI_SAMPLES: + rd.mode = FM_RDS_CNFG_MODE; + rd.length = FM_RDS_CNFG_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval == 0) + ctrl->value = + radio->default_data.data[AF_RMSSI_SAMPLES_OFFSET]; + break; + case V4L2_CID_PRIVATE_GOOD_CH_RMSSI_TH: + rd.mode = FM_RX_CONFG_MODE; + rd.length = FM_RX_CNFG_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval == 0) { + ctrl->value = + radio->default_data.data[GD_CH_RMSSI_TH_OFFSET]; + if (ctrl->value > MAX_GD_CH_RMSSI_TH) + ctrl->value -= 256; + } + break; + case V4L2_CID_PRIVATE_SRCHALGOTYPE: + rd.mode = FM_RX_CONFG_MODE; + rd.length = FM_RX_CNFG_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval == 0) + ctrl->value = + radio->default_data.data[SRCH_ALGO_TYPE_OFFSET]; + break; + case V4L2_CID_PRIVATE_SINRFIRSTSTAGE: + rd.mode = FM_RX_CONFG_MODE; + rd.length = FM_RX_CNFG_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval == 0) { + ctrl->value = + radio->default_data.data[SINRFIRSTSTAGE_OFFSET]; + if (ctrl->value > MAX_SINR_FIRSTSTAGE) + ctrl->value -= 256; + } + break; + case V4L2_CID_PRIVATE_RMSSIFIRSTSTAGE: + rd.mode = FM_RX_CONFG_MODE; + rd.length = FM_RX_CNFG_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval == 0) { + ctrl->value = + radio->default_data.data[RMSSIFIRSTSTAGE_OFFSET]; + if (ctrl->value > MAX_RMSSI_FIRSTSTAGE) + ctrl->value -= 256; + } + break; + case V4L2_CID_PRIVATE_CF0TH12: + rd.mode = FM_RX_CONFG_MODE; + rd.length = FM_RX_CNFG_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval == 0) { + ctrl->value = + radio->default_data.data[CF0TH12_BYTE1_OFFSET]; + cf0 = radio->default_data.data[CF0TH12_BYTE2_OFFSET]; + ctrl->value |= (cf0 << 8); + cf0 = radio->default_data.data[CF0TH12_BYTE3_OFFSET]; + ctrl->value |= (cf0 << 16); + cf0 = radio->default_data.data[CF0TH12_BYTE4_OFFSET]; + if (cf0 > 127) + cf0 -= 256; + ctrl->value |= (cf0 << 24); + } + break; + case V4L2_CID_PRIVATE_BLEND_SINRHI: + retval = hci_cmd(HCI_FM_GET_BLND_TBL_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to get blend table %d", retval); + goto end; + } + ctrl->value = radio->blend_tbl.scBlendSinrHi; + break; + case V4L2_CID_PRIVATE_BLEND_RMSSIHI: + retval = hci_cmd(HCI_FM_GET_BLND_TBL_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to get blend table %d", retval); + goto end; + } + ctrl->value = radio->blend_tbl.scBlendRmssiHi; + break; + default: + retval = -EINVAL; + break; + } + +end: + if (retval > 0) + retval = -EINVAL; + if (ctrl != NULL && retval < 0) + FMDERR("get control failed: %d, ret: %d\n", ctrl->id, retval); + + return retval; +} + +static int iris_vidioc_g_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *ctrl) +{ + int retval = 0; + char *data = NULL; + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + struct hci_fm_def_data_rd_req default_data_rd; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + retval = -EINVAL; + goto end; + } + + if ((ctrl == NULL) || (ctrl->count == 0) + || (ctrl->controls == NULL)) { + FMDERR("%s, invalid v4l2 ctrl\n", __func__); + retval = -EINVAL; + goto end; + } + + FMDBG("0x%x", ctrl->controls[0].id); + switch ((ctrl->controls[0]).id) { + case V4L2_CID_PRIVATE_IRIS_READ_DEFAULT: + data = (ctrl->controls[0]).string; + memset(&default_data_rd, 0, sizeof(default_data_rd)); + if (copy_from_user(&default_data_rd.mode, data, + sizeof(default_data_rd))) { + retval = -EFAULT; + goto end; + } + retval = hci_def_data_read(&default_data_rd, radio->fm_hdev); + break; + default: + retval = -EINVAL; + break; + } + +end: + if (retval > 0) + retval = -EINVAL; + + return retval; +} + +static int iris_vidioc_s_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *ctrl) +{ + int retval = 0; + size_t bytes_to_copy; + struct hci_fm_tx_ps tx_ps; + struct hci_fm_tx_rt tx_rt; + struct hci_fm_def_data_wr_req default_data; + struct hci_fm_set_cal_req_proc proc_cal_req; + struct hci_fm_set_spur_table_req spur_tbl_req; + char *spur_data; + char tmp_buf[2]; + + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + char *data = NULL; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null"); + retval = -EINVAL; + goto end; + } + + if ((ctrl == NULL) || (ctrl->count == 0) + || (ctrl->controls == NULL)) { + FMDERR("%s, invalid v4l2 ctrl\n", __func__); + retval = -EINVAL; + goto end; + } + + FMDBG("0x%x\n", ctrl->controls[0].id); + switch ((ctrl->controls[0]).id) { + case V4L2_CID_RDS_TX_PS_NAME: + FMDBG("In V4L2_CID_RDS_TX_PS_NAME\n"); + /*Pass a sample PS string */ + + memset(tx_ps.ps_data, 0, MAX_PS_LENGTH); + bytes_to_copy = min_t(size_t, ctrl->controls[0].size, + MAX_PS_LENGTH); + data = (ctrl->controls[0]).string; + + if (copy_from_user(tx_ps.ps_data, + data, bytes_to_copy)) { + FMDERR("%s: copy from user for tx ps name failed\n", + __func__); + retval = -EFAULT; + goto end; + } else { + tx_ps.ps_control = 0x01; + tx_ps.pi = radio->pi; + tx_ps.pty = radio->pty; + tx_ps.ps_repeatcount = radio->ps_repeatcount; + tx_ps.ps_num = (bytes_to_copy / PS_STRING_LEN); + + retval = radio_hci_request(radio->fm_hdev, + hci_trans_ps_req, + (unsigned long)&tx_ps, + RADIO_HCI_TIMEOUT); + } + break; + case V4L2_CID_RDS_TX_RADIO_TEXT: + bytes_to_copy = + min_t(size_t, (ctrl->controls[0]).size, MAX_RT_LENGTH); + data = (ctrl->controls[0]).string; + + memset(tx_rt.rt_data, 0, MAX_RT_LENGTH); + + if (copy_from_user(tx_rt.rt_data, + data, bytes_to_copy)) { + FMDERR("%s: copy from user for tx rt failed\n", + __func__); + retval = -EFAULT; + goto end; + } else { + tx_rt.rt_control = 0x01; + tx_rt.pi = radio->pi; + tx_rt.pty = radio->pty; + tx_rt.rt_len = bytes_to_copy; + + retval = radio_hci_request(radio->fm_hdev, + hci_trans_rt_req, + (unsigned long)&tx_rt, + RADIO_HCI_TIMEOUT); + } + break; + case V4L2_CID_PRIVATE_IRIS_WRITE_DEFAULT: + data = (ctrl->controls[0]).string; + memset(&default_data, 0, sizeof(default_data)); + /* + * Check if length of the 'FM Default Data' to be sent + * is within the maximum 'FM Default Data' packet limit. + * Max. 'FM Default Data' packet length is 251 bytes: + * 1 byte - XFR Mode + * 1 byte - length of the default data + * 249 bytes - actual data to be configured + */ + if (ctrl->controls[0].size > (DEFAULT_DATA_SIZE + 2)) { + pr_err("%s: Default data buffer overflow\n", __func__); + retval = -EINVAL; + goto end; + } + + /* copy only 'size' bytes of data as requested by user */ + retval = copy_from_user(&default_data, data, + ctrl->controls[0].size); + if (retval > 0) { + FMDERR("Failed to copy %d bytes data\n", retval); + retval = -EFAULT; + goto end; + } + FMDBG("XFR Mode\t: 0x%x", default_data.mode); + FMDBG("XFR Data Length\t: %d\n", default_data.length); + /* + * Check if the 'length' of the actual XFR data to be configured + * is valid or not. Length of actual XFR data should be always + * 2 bytes less than the total length of the 'FM Default Data'. + * Length of 'FM Default Data' DEF_DATA_LEN: (1+1+XFR Data Size) + * Length of 'Actual XFR Data' XFR_DATA_LEN: (DEF_DATA_LEN - 2) + */ + if (default_data.length != (ctrl->controls[0].size - 2)) { + FMDERR("Invalid 'length' parameter\n"); + retval = -EINVAL; + goto end; + } + retval = hci_def_data_write(&default_data, radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_SET_CALIBRATION: + data = (ctrl->controls[0]).string; + bytes_to_copy = (ctrl->controls[0]).size; + if (bytes_to_copy < PROCS_CALIB_SIZE) { + FMDERR("data is less than required size\n"); + retval = -EFAULT; + goto end; + } + memset(proc_cal_req.data, 0, PROCS_CALIB_SIZE); + proc_cal_req.mode = PROCS_CALIB_MODE; + if (copy_from_user(&proc_cal_req.data[0], + data, sizeof(proc_cal_req.data))) { + retval = -EFAULT; + goto end; + } + retval = radio_hci_request(radio->fm_hdev, + hci_fm_set_cal_req_proc, + (unsigned long)&proc_cal_req, + RADIO_HCI_TIMEOUT); + break; + case V4L2_CID_PRIVATE_IRIS_SET_SPURTABLE: + memset(&spur_tbl_req, 0, sizeof(spur_tbl_req)); + data = (ctrl->controls[0]).string; + if (copy_from_user(&bytes_to_copy, &((ctrl->controls[0]).size), + sizeof(bytes_to_copy))) { + retval = -EFAULT; + goto end; + } + if (copy_from_user(&tmp_buf[0], &data[0], + sizeof(tmp_buf))) { + retval = -EFAULT; + goto end; + } + spur_tbl_req.mode = tmp_buf[0]; + spur_tbl_req.no_of_freqs_entries = tmp_buf[1]; + + if (((spur_tbl_req.no_of_freqs_entries * SPUR_DATA_LEN) != + bytes_to_copy - 2) || + ((spur_tbl_req.no_of_freqs_entries * SPUR_DATA_LEN) > + 2 * FM_SPUR_TBL_SIZE)) { + FMDERR("Invalid data len: data[1] = %d, bytes = %zu\n", + spur_tbl_req.no_of_freqs_entries, + bytes_to_copy); + retval = -EINVAL; + goto end; + } + spur_data = + kmalloc((spur_tbl_req.no_of_freqs_entries * SPUR_DATA_LEN) + + 2, GFP_ATOMIC); + if (!spur_data) { + retval = -EFAULT; + goto end; + } + if (copy_from_user(spur_data, + &data[2], (bytes_to_copy - 2))) { + kfree(spur_data); + retval = -EFAULT; + goto end; + } + + if (spur_tbl_req.no_of_freqs_entries <= ENTRIES_EACH_CMD) { + memcpy(&spur_tbl_req.spur_data[0], spur_data, + (spur_tbl_req.no_of_freqs_entries * + SPUR_DATA_LEN)); + retval = radio_hci_request(radio->fm_hdev, + hci_fm_set_spur_tbl_req, + (unsigned long)&spur_tbl_req, + RADIO_HCI_TIMEOUT); + } else { + memcpy(&spur_tbl_req.spur_data[0], spur_data, + (ENTRIES_EACH_CMD * SPUR_DATA_LEN)); + retval = radio_hci_request(radio->fm_hdev, + hci_fm_set_spur_tbl_req, + (unsigned long)&spur_tbl_req, + RADIO_HCI_TIMEOUT); + if (retval < 0) { + FMDERR("Spur command failed to execute\n"); + kfree(spur_data); + goto end; + } + spur_tbl_req.mode = 0x02;/* 02-Continue mode */ + spur_tbl_req.no_of_freqs_entries = + spur_tbl_req.no_of_freqs_entries + - ENTRIES_EACH_CMD; + memcpy(&spur_tbl_req.spur_data[0], + &spur_data[ENTRIES_EACH_CMD * SPUR_DATA_LEN], + (spur_tbl_req.no_of_freqs_entries * SPUR_DATA_LEN)); + retval = radio_hci_request(radio->fm_hdev, + hci_fm_set_spur_tbl_req, + (unsigned long)&spur_tbl_req, + RADIO_HCI_TIMEOUT); + } + kfree(spur_data); + break; + default: + FMDBG("Shouldn't reach here\n"); + retval = -EINVAL; + goto end; + } + +end: + if (retval > 0) + retval = -EINVAL; + + return retval; +} + +static int iris_vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + int retval = 0; + unsigned int rds_grps_proc = 0; + __u8 temp_val = 0; + int saved_val; + unsigned long arg = 0; + struct hci_fm_tx_ps tx_ps = {0}; + struct hci_fm_tx_rt tx_rt = {0}; + struct hci_fm_def_data_rd_req rd; + struct hci_fm_def_data_wr_req wrd; + char sinr_th, sinr; + __u8 intf_det_low_th, intf_det_high_th, intf_det_out; + unsigned int spur_freq; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + retval = -EINVAL; + goto end; + } + + if (ctrl == NULL) { + FMDERR("%s, v4l2 ctrl is null\n", __func__); + retval = -EINVAL; + goto end; + } + + FMDBG("id 0x%x", ctrl->id); + switch (ctrl->id) { + case V4L2_CID_PRIVATE_IRIS_TX_TONE: + if (!is_valid_tone(ctrl->value)) { + retval = -EINVAL; + FMDERR("%s: tone value is not valid\n", __func__); + goto end; + } + saved_val = radio->tone_freq; + radio->tone_freq = ctrl->value; + retval = radio_hci_request(radio->fm_hdev, + hci_fm_tone_generator, arg, + RADIO_HCI_TIMEOUT); + if (retval < 0) { + FMDERR("Error while setting the tone %d\n", retval); + radio->tone_freq = saved_val; + } + break; + case V4L2_CID_AUDIO_VOLUME: + break; + case V4L2_CID_AUDIO_MUTE: + if (!is_valid_hard_mute(ctrl->value)) { + retval = -EINVAL; + FMDERR("%s: hard mute value is not valid\n", __func__); + goto end; + } + saved_val = radio->mute_mode.hard_mute; + radio->mute_mode.hard_mute = ctrl->value; + retval = hci_set_fm_mute_mode( + &radio->mute_mode, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Error while set FM hard mute %d\n", + retval); + radio->mute_mode.hard_mute = saved_val; + } + break; + case V4L2_CID_PRIVATE_IRIS_SRCHMODE: + if (is_valid_srch_mode(ctrl->value)) { + radio->g_search_mode = ctrl->value; + } else { + FMDERR("%s: srch mode is not valid\n", __func__); + retval = -EINVAL; + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_SCANDWELL: + if (is_valid_scan_dwell_prd(ctrl->value)) { + radio->g_scan_time = ctrl->value; + } else { + FMDERR("%s: scandwell period is not valid\n", __func__); + retval = -EINVAL; + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_SRCHON: + iris_search(radio, ctrl->value, SRCH_DIR_UP); + break; + case V4L2_CID_PRIVATE_IRIS_STATE: + switch (ctrl->value) { + case FM_RECV: + if (radio->mode != FM_OFF) { + FMDERR("FM mode is not off %d\n", radio->mode); + retval = -EINVAL; + goto end; + } + if (is_enable_rx_possible(radio) != 0) { + FMDERR("%s: fm is not in proper state\n", + __func__); + retval = -EINVAL; + goto end; + } + radio->mode = FM_RECV_TURNING_ON; + retval = hci_cmd(HCI_FM_ENABLE_RECV_CMD, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Enabling RECV FM fail %d\n", retval); + radio->mode = FM_OFF; + goto end; + } else { + retval = initialise_recv(radio); + if (retval < 0) { + FMDERR("Error while initialising\n"); + FMDERR("radio %d\n", retval); + hci_cmd(HCI_FM_DISABLE_RECV_CMD, + radio->fm_hdev); + radio->mode = FM_OFF; + goto end; + } + } + if (radio->mode == FM_RECV_TURNING_ON) { + radio->mode = FM_RECV; + iris_q_event(radio, IRIS_EVT_RADIO_READY); + } + break; + case FM_TRANS: + if (is_enable_tx_possible(radio) != 0) { + retval = -EINVAL; + goto end; + } + radio->mode = FM_TRANS_TURNING_ON; + retval = hci_cmd(HCI_FM_ENABLE_TRANS_CMD, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Enabling TRANS FM fail %d\n", retval); + radio->mode = FM_OFF; + goto end; + } else { + retval = initialise_trans(radio); + if (retval < 0) { + FMDERR("Error while initialising\n"); + FMDERR("radio %d\n", retval); + hci_cmd(HCI_FM_DISABLE_TRANS_CMD, + radio->fm_hdev); + radio->mode = FM_OFF; + goto end; + } + } + if (radio->mode == FM_TRANS_TURNING_ON) { + radio->mode = FM_TRANS; + iris_q_event(radio, IRIS_EVT_RADIO_READY); + } + break; + case FM_OFF: + radio->spur_table_size = 0; + switch (radio->mode) { + case FM_RECV: + radio->mode = FM_TURNING_OFF; + retval = hci_cmd(HCI_FM_DISABLE_RECV_CMD, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Err on disable recv FM\n"); + FMDERR("%d\n", retval); + radio->mode = FM_RECV; + goto end; + } + break; + case FM_TRANS: + radio->mode = FM_TURNING_OFF; + retval = hci_cmd(HCI_FM_DISABLE_TRANS_CMD, + radio->fm_hdev); + + if (retval < 0) { + FMDERR("Err disabling trans FM\n"); + FMDERR("%d\n", retval); + radio->mode = FM_TRANS; + goto end; + } + break; + default: + retval = -EINVAL; + } + break; + default: + retval = -EINVAL; + } + break; + case V4L2_CID_PRIVATE_IRIS_REGION: + if (radio->mode == FM_RECV) { + retval = iris_recv_set_region(radio, ctrl->value); + } else { + if (radio->mode == FM_TRANS) { + retval = iris_trans_set_region(radio, + ctrl->value); + } else { + FMDERR("%s: fm is not in proper state\n", + __func__); + retval = -EINVAL; + goto end; + } + } + break; + case V4L2_CID_PRIVATE_IRIS_SIGNAL_TH: + if (!is_valid_sig_th(ctrl->value)) { + retval = -EINVAL; + FMDERR("%s: sig threshold is not valid\n", __func__); + goto end; + } + temp_val = ctrl->value; + retval = hci_fm_set_signal_threshold( + &temp_val, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Error while setting signal threshold\n"); + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_PTY: + if (is_valid_pty(ctrl->value)) { + radio->srch_rds.srch_pty = ctrl->value; + radio->srch_st_list.srch_pty = ctrl->value; + } else { + FMDERR("%s: pty is not valid\n", __func__); + retval = -EINVAL; + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_PI: + if (is_valid_pi(ctrl->value)) { + radio->srch_rds.srch_pi = ctrl->value; + } else { + retval = -EINVAL; + FMDERR("%s: Pi is not valid\n", __func__); + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_CNT: + if (is_valid_srch_station_cnt(ctrl->value)) { + radio->srch_st_list.srch_list_max = ctrl->value; + } else { + retval = -EINVAL; + FMDERR("%s: srch station count is not valid\n", + __func__); + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_SPACING: + if (!is_valid_chan_spacing(ctrl->value)) { + retval = -EINVAL; + FMDERR("%s: channel spacing is not valid\n", __func__); + goto end; + } + if (radio->mode == FM_RECV) { + saved_val = radio->recv_conf.ch_spacing; + radio->recv_conf.ch_spacing = ctrl->value; + retval = hci_set_fm_recv_conf( + &radio->recv_conf, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Error in setting channel spacing\n"); + radio->recv_conf.ch_spacing = saved_val; + goto end; + } + } + break; + case V4L2_CID_PRIVATE_IRIS_EMPHASIS: + if (!is_valid_emphasis(ctrl->value)) { + retval = -EINVAL; + FMDERR("%s, emphasis is not valid\n", __func__); + goto end; + } + switch (radio->mode) { + case FM_RECV: + saved_val = radio->recv_conf.emphasis; + radio->recv_conf.emphasis = ctrl->value; + retval = hci_set_fm_recv_conf( + &radio->recv_conf, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Error in setting emphasis\n"); + radio->recv_conf.emphasis = saved_val; + goto end; + } + break; + case FM_TRANS: + saved_val = radio->trans_conf.emphasis; + radio->trans_conf.emphasis = ctrl->value; + retval = hci_set_fm_trans_conf( + &radio->trans_conf, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Error in setting emphasis\n"); + radio->trans_conf.emphasis = saved_val; + goto end; + } + break; + default: + retval = -EINVAL; + FMDERR("FM mode is unknown %d\n", radio->mode); + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_RDS_STD: + if (!is_valid_rds_std(ctrl->value)) { + retval = -EINVAL; + FMDERR("%s: rds std is not valid\n", __func__); + goto end; + } + switch (radio->mode) { + case FM_RECV: + saved_val = radio->recv_conf.rds_std; + radio->recv_conf.rds_std = ctrl->value; + retval = hci_set_fm_recv_conf( + &radio->recv_conf, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Error in rds_std\n"); + radio->recv_conf.rds_std = saved_val; + goto end; + } + break; + case FM_TRANS: + saved_val = radio->trans_conf.rds_std; + radio->trans_conf.rds_std = ctrl->value; + retval = hci_set_fm_trans_conf( + &radio->trans_conf, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Error in rds_Std\n"); + radio->trans_conf.rds_std = saved_val; + goto end; + } + break; + default: + retval = -EINVAL; + FMDERR("%s: fm is not in proper state\n", __func__); + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_RDSON: + if (!is_valid_rds_std(ctrl->value)) { + retval = -EINVAL; + FMDERR("%s: rds std is not valid\n", __func__); + goto end; + } + switch (radio->mode) { + case FM_RECV: + saved_val = radio->recv_conf.rds_std; + radio->recv_conf.rds_std = ctrl->value; + retval = hci_set_fm_recv_conf( + &radio->recv_conf, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Error in rds_std\n"); + radio->recv_conf.rds_std = saved_val; + goto end; + } + break; + case FM_TRANS: + saved_val = radio->trans_conf.rds_std; + radio->trans_conf.rds_std = ctrl->value; + retval = hci_set_fm_trans_conf( + &radio->trans_conf, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Error in rds_Std\n"); + radio->trans_conf.rds_std = saved_val; + goto end; + } + break; + default: + retval = -EINVAL; + FMDERR("%s: fm is not in proper state\n", __func__); + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_RDSGROUP_MASK: + saved_val = radio->rds_grp.rds_grp_enable_mask; + grp_mask = (grp_mask | oda_agt | ctrl->value); + radio->rds_grp.rds_grp_enable_mask = grp_mask; + radio->rds_grp.rds_buf_size = 1; + radio->rds_grp.en_rds_change_filter = 0; + retval = hci_fm_rds_grp(&radio->rds_grp, radio->fm_hdev); + if (retval < 0) { + FMDERR("error in setting group mask\n"); + radio->rds_grp.rds_grp_enable_mask = saved_val; + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_RDSGROUP_PROC: + saved_val = radio->g_rds_grp_proc_ps; + radio->g_rds_grp_proc_ps |= ctrl->value; + retval = hci_fm_rds_grps_process( + &radio->g_rds_grp_proc_ps, + radio->fm_hdev); + if (retval < 0) { + radio->g_rds_grp_proc_ps = saved_val; + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_RDSD_BUF: + radio->rds_grp.rds_buf_size = ctrl->value; + break; + case V4L2_CID_PRIVATE_IRIS_PSALL: + saved_val = radio->g_rds_grp_proc_ps; + rds_grps_proc = (ctrl->value << RDS_PS_SIMPLE_OFFSET); + radio->g_rds_grp_proc_ps |= rds_grps_proc; + retval = hci_fm_rds_grps_process( + &radio->g_rds_grp_proc_ps, + radio->fm_hdev); + if (retval < 0) { + radio->g_rds_grp_proc_ps = saved_val; + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_AF_JUMP: + saved_val = radio->g_rds_grp_proc_ps; + /*Clear the current AF jump settings*/ + radio->g_rds_grp_proc_ps &= ~(1 << RDS_AF_JUMP_OFFSET); + radio->af_jump_bit = ctrl->value; + rds_grps_proc = 0x00; + rds_grps_proc = (ctrl->value << RDS_AF_JUMP_OFFSET); + radio->g_rds_grp_proc_ps |= rds_grps_proc; + retval = hci_fm_rds_grps_process( + &radio->g_rds_grp_proc_ps, + radio->fm_hdev); + if (retval < 0) { + radio->g_rds_grp_proc_ps = saved_val; + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_LP_MODE: + set_low_power_mode(radio, ctrl->value); + break; + case V4L2_CID_PRIVATE_IRIS_ANTENNA: + if (!is_valid_antenna(ctrl->value)) { + retval = -EINVAL; + FMDERR("%s: antenna type is not valid\n", __func__); + goto end; + } + temp_val = ctrl->value; + retval = hci_fm_set_antenna(&temp_val, radio->fm_hdev); + if (retval < 0) { + FMDERR("Set Antenna failed retval = %x\n", retval); + goto end; + } + radio->g_antenna = ctrl->value; + break; + case V4L2_CID_RDS_TX_PTY: + if (is_valid_pty(ctrl->value)) { + radio->pty = ctrl->value; + } else { + retval = -EINVAL; + FMDERR("%s: pty is not valid\n", __func__); + goto end; + } + break; + case V4L2_CID_RDS_TX_PI: + if (is_valid_pi(ctrl->value)) { + radio->pi = ctrl->value; + } else { + retval = -EINVAL; + FMDERR("%s: pi is not valid\n", __func__); + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_STOP_RDS_TX_PS_NAME: + tx_ps.ps_control = 0x00; + retval = radio_hci_request(radio->fm_hdev, hci_trans_ps_req, + (unsigned long)&tx_ps, RADIO_HCI_TIMEOUT); + break; + case V4L2_CID_PRIVATE_IRIS_STOP_RDS_TX_RT: + tx_rt.rt_control = 0x00; + retval = radio_hci_request(radio->fm_hdev, hci_trans_rt_req, + (unsigned long)&tx_rt, RADIO_HCI_TIMEOUT); + break; + case V4L2_CID_PRIVATE_IRIS_TX_SETPSREPEATCOUNT: + if (is_valid_ps_repeat_cnt(ctrl->value)) { + radio->ps_repeatcount = ctrl->value; + } else { + retval = -EINVAL; + FMDERR("%s: ps repeat count is not valid\n", __func__); + goto end; + } + break; + case V4L2_CID_TUNE_POWER_LEVEL: + if (ctrl->value > FM_TX_PWR_LVL_MAX) + ctrl->value = FM_TX_PWR_LVL_MAX; + if (ctrl->value < FM_TX_PWR_LVL_0) + ctrl->value = FM_TX_PWR_LVL_0; + rd.mode = FM_TX_PHY_CFG_MODE; + rd.length = FM_TX_PHY_CFG_LEN; + rd.param_len = 0x00; + rd.param = 0x00; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval < 0) { + FMDERR("Default data read failed for PHY_CFG %d\n", + retval); + goto end; + } + memset(&wrd, 0, sizeof(wrd)); + wrd.mode = FM_TX_PHY_CFG_MODE; + wrd.length = FM_TX_PHY_CFG_LEN; + memcpy(&wrd.data, &radio->default_data.data, + radio->default_data.ret_data_len); + wrd.data[FM_TX_PWR_GAIN_OFFSET] = + (ctrl->value) * FM_TX_PWR_LVL_STEP_SIZE; + retval = hci_def_data_write(&wrd, radio->fm_hdev); + if (retval < 0) + FMDERR("Default write failed for PHY_TXGAIN %d\n", + retval); + break; + case V4L2_CID_PRIVATE_IRIS_SOFT_MUTE: + if (!is_valid_soft_mute(ctrl->value)) { + retval = -EINVAL; + FMDERR("%s: soft mute is not valid\n", __func__); + goto end; + } + saved_val = radio->mute_mode.soft_mute; + radio->mute_mode.soft_mute = ctrl->value; + retval = hci_set_fm_mute_mode( + &radio->mute_mode, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Error while setting FM soft mute %d\n", + retval); + radio->mute_mode.soft_mute = saved_val; + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_RIVA_ACCS_ADDR: + radio->riva_data_req.cmd_params.start_addr = ctrl->value; + break; + case V4L2_CID_PRIVATE_IRIS_RIVA_ACCS_LEN: + if (is_valid_peek_len(ctrl->value)) { + radio->riva_data_req.cmd_params.length = ctrl->value; + } else { + retval = -EINVAL; + FMDERR("%s: riva access len is not valid\n", __func__); + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_RIVA_POKE: + if (radio->riva_data_req.cmd_params.length <= + MAX_RIVA_PEEK_RSP_SIZE) { +#ifdef CONFIG_COMPAT + retval = copy_from_user( + radio->riva_data_req.data, + (void *)(__s64)ctrl->value, + radio->riva_data_req.cmd_params.length); +#else + retval = copy_from_user( + radio->riva_data_req.data, + (void *)ctrl->value, + radio->riva_data_req.cmd_params.length); +#endif + if (retval != 0) { + retval = -retval; + goto end; + } + radio->riva_data_req.cmd_params.subopcode = + RIVA_POKE_OPCODE; + retval = hci_poke_data( + &radio->riva_data_req, + radio->fm_hdev); + } else { + FMDERR("Can not copy into driver buffer\n"); + retval = -EINVAL; + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_SSBI_ACCS_ADDR: + radio->ssbi_data_accs.start_addr = ctrl->value; + break; + case V4L2_CID_PRIVATE_IRIS_SSBI_POKE: + radio->ssbi_data_accs.data = ctrl->value; + retval = hci_ssbi_poke_reg(&radio->ssbi_data_accs, + radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_RIVA_PEEK: + radio->riva_data_req.cmd_params.subopcode = RIVA_PEEK_OPCODE; + ctrl->value = hci_peek_data(&radio->riva_data_req.cmd_params, + radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_SSBI_PEEK: + radio->ssbi_peek_reg.start_address = ctrl->value; + hci_ssbi_peek_reg(&radio->ssbi_peek_reg, radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_RDS_GRP_COUNTERS: + if (is_valid_reset_cntr(ctrl->value)) { + temp_val = ctrl->value; + hci_read_grp_counters(&temp_val, radio->fm_hdev); + } else { + FMDERR("%s: reset counter value is not valid\n", + __func__); + retval = -EINVAL; + goto end; + } + break; + case V4L2_CID_PRIVATE_IRIS_HLSI: + if (!is_valid_hlsi(ctrl->value)) { + FMDERR("%s: hlsi value is not valid\n", __func__); + retval = -EINVAL; + goto end; + } + retval = hci_cmd(HCI_FM_GET_RECV_CONF_CMD, + radio->fm_hdev); + if (retval) + goto end; + saved_val = radio->recv_conf.hlsi; + radio->recv_conf.hlsi = ctrl->value; + retval = hci_set_fm_recv_conf( + &radio->recv_conf, + radio->fm_hdev); + if (retval < 0) + radio->recv_conf.hlsi = saved_val; + break; + case V4L2_CID_PRIVATE_IRIS_SET_NOTCH_FILTER: + if (is_valid_notch_filter(ctrl->value)) { + temp_val = ctrl->value; + retval = hci_set_notch_filter(&temp_val, + radio->fm_hdev); + } else { + FMDERR("%s: notch filter is not valid\n", __func__); + retval = -EINVAL; + goto end; + } + break; + case V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD: + if (!is_valid_intf_det_hgh_th(ctrl->value)) { + FMDERR("%s: intf high threshold is not valid\n", + __func__); + retval = -EINVAL; + goto end; + } + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Fail to get chnl det thresholds %d\n", retval); + goto end; + } + saved_val = radio->ch_det_threshold.high_th; + radio->ch_det_threshold.high_th = ctrl->value; + retval = hci_set_ch_det_thresholds_req(&radio->ch_det_threshold, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to set High det threshold %d\n", retval); + radio->ch_det_threshold.high_th = saved_val; + goto end; + } + break; + + case V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD: + if (!is_valid_intf_det_low_th(ctrl->value)) { + FMDERR("%s: intf det low threshold is not valid\n", + __func__); + retval = -EINVAL; + goto end; + } + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Fail to get chnl det thresholds %d\n", retval); + goto end; + } + saved_val = radio->ch_det_threshold.low_th; + radio->ch_det_threshold.low_th = ctrl->value; + retval = hci_set_ch_det_thresholds_req(&radio->ch_det_threshold, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Fail to Set Low det threshold %d\n", retval); + radio->ch_det_threshold.low_th = saved_val; + goto end; + } + break; + + case V4L2_CID_PRIVATE_SINR_THRESHOLD: + if (!is_valid_sinr_th(ctrl->value)) { + FMDERR("%s: sinr threshold is not valid\n", __func__); + retval = -EINVAL; + goto end; + } + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Fail to get chnl det thresholds %d\n", retval); + goto end; + } + saved_val = radio->ch_det_threshold.sinr; + radio->ch_det_threshold.sinr = ctrl->value; + retval = hci_set_ch_det_thresholds_req(&radio->ch_det_threshold, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Fail to set SINR threshold %d\n", retval); + radio->ch_det_threshold.sinr = saved_val; + goto end; + } + break; + + case V4L2_CID_PRIVATE_SINR_SAMPLES: + if (!is_valid_sinr_samples(ctrl->value)) { + FMDERR("%s: sinr samples count is not valid\n", + __func__); + retval = -EINVAL; + goto end; + } + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Fail to get chnl det thresholds %d\n", retval); + goto end; + } + saved_val = radio->ch_det_threshold.sinr_samples; + radio->ch_det_threshold.sinr_samples = ctrl->value; + retval = hci_set_ch_det_thresholds_req(&radio->ch_det_threshold, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Fail to set SINR samples %d\n", retval); + radio->ch_det_threshold.sinr_samples = saved_val; + goto end; + } + break; + + case V4L2_CID_PRIVATE_IRIS_SRCH_ALGORITHM: + case V4L2_CID_PRIVATE_IRIS_SET_AUDIO_PATH: + /* + * These private controls are place holders to keep the + * driver compatible with changes done in the frameworks + * which are specific to TAVARUA. + */ + retval = 0; + break; + case V4L2_CID_PRIVATE_SPUR_FREQ: + if (radio->spur_table_size >= MAX_SPUR_FREQ_LIMIT) { + FMDERR("%s: Spur Table Full\n", __func__); + retval = -1; + } else + radio->spur_data.freq[radio->spur_table_size] = + ctrl->value; + break; + case V4L2_CID_PRIVATE_SPUR_FREQ_RMSSI: + if (radio->spur_table_size >= MAX_SPUR_FREQ_LIMIT) { + FMDERR("%s: Spur Table Full\n", __func__); + retval = -1; + } else + radio->spur_data.rmssi[radio->spur_table_size] = + ctrl->value; + break; + case V4L2_CID_PRIVATE_SPUR_SELECTION: + if (radio->spur_table_size >= MAX_SPUR_FREQ_LIMIT) { + FMDERR("%s: Spur Table Full\n", __func__); + retval = -1; + } else { + radio->spur_data.enable[radio->spur_table_size] = + ctrl->value; + radio->spur_table_size++; + } + break; + case V4L2_CID_PRIVATE_UPDATE_SPUR_TABLE: + update_spur_table(radio); + break; + case V4L2_CID_PRIVATE_VALID_CHANNEL: + retval = hci_cmd(HCI_FM_GET_DET_CH_TH_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("%s: Failed to determine channel validity\n", + __func__); + goto end; + } else { + sinr_th = radio->ch_det_threshold.sinr; + intf_det_low_th = radio->ch_det_threshold.low_th; + intf_det_high_th = radio->ch_det_threshold.high_th; + } + if (!is_valid_sinr_th(sinr_th) || + !is_valid_intf_det_low_th(intf_det_low_th) || + !is_valid_intf_det_hgh_th(intf_det_high_th)) { + retval = -EINVAL; + goto end; + } + retval = hci_cmd(HCI_FM_GET_STATION_PARAM_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("%s: Failed to determine channel validity\n", + __func__); + goto end; + } else + sinr = radio->fm_st_rsp.station_rsp.sinr; + + retval = hci_cmd(HCI_FM_STATION_DBG_PARAM_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("%s: Failed to determine channel validity\n", + __func__); + goto end; + } else + intf_det_out = radio->st_dbg_param.in_det_out; + + if ((sinr >= sinr_th) && (intf_det_out >= intf_det_low_th) && + (intf_det_out <= intf_det_high_th)) + radio->is_station_valid = VALID_CHANNEL; + else + radio->is_station_valid = INVALID_CHANNEL; + break; + case V4L2_CID_PRIVATE_AF_RMSSI_TH: + rd.mode = FM_RDS_CNFG_MODE; + rd.length = FM_RDS_CNFG_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval < 0) { + FMDERR("default data read failed %x\n", retval); + goto end; + } + wrd.mode = FM_RDS_CNFG_MODE; + wrd.length = FM_RDS_CNFG_LEN; + memcpy(&wrd.data, &radio->default_data.data, + radio->default_data.ret_data_len); + wrd.data[AF_RMSSI_TH_LSB_OFFSET] = ((ctrl->value) & 255); + wrd.data[AF_RMSSI_TH_MSB_OFFSET] = ((ctrl->value) >> 8); + retval = hci_def_data_write(&wrd, radio->fm_hdev); + if (retval < 0) + FMDERR("set AF jump RMSSI threshold failed\n"); + break; + case V4L2_CID_PRIVATE_AF_RMSSI_SAMPLES: + rd.mode = FM_RDS_CNFG_MODE; + rd.length = FM_RDS_CNFG_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval < 0) { + FMDERR("default data read failed %x\n", retval); + goto end; + } + wrd.mode = FM_RDS_CNFG_MODE; + wrd.length = FM_RDS_CNFG_LEN; + memcpy(&wrd.data, &radio->default_data.data, + radio->default_data.ret_data_len); + wrd.data[AF_RMSSI_SAMPLES_OFFSET] = ctrl->value; + retval = hci_def_data_write(&wrd, radio->fm_hdev); + if (retval < 0) + FMDERR("set AF jump RMSSI Samples failed\n"); + break; + case V4L2_CID_PRIVATE_GOOD_CH_RMSSI_TH: + rd.mode = FM_RX_CONFG_MODE; + rd.length = FM_RX_CNFG_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval < 0) { + FMDERR("default data read failed %x\n", retval); + goto end; + } + wrd.mode = FM_RX_CONFG_MODE; + wrd.length = FM_RX_CNFG_LEN; + memcpy(&wrd.data, &radio->default_data.data, + radio->default_data.ret_data_len); + wrd.data[GD_CH_RMSSI_TH_OFFSET] = ctrl->value; + retval = hci_def_data_write(&wrd, radio->fm_hdev); + if (retval < 0) + FMDERR("set good channel RMSSI th failed\n"); + break; + case V4L2_CID_PRIVATE_SRCHALGOTYPE: + rd.mode = FM_RX_CONFG_MODE; + rd.length = FM_RX_CNFG_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval < 0) { + FMDERR("default data read failed %x\n", retval); + goto end; + } + wrd.mode = FM_RX_CONFG_MODE; + wrd.length = FM_RX_CNFG_LEN; + memcpy(&wrd.data, &radio->default_data.data, + radio->default_data.ret_data_len); + wrd.data[SRCH_ALGO_TYPE_OFFSET] = ctrl->value; + retval = hci_def_data_write(&wrd, radio->fm_hdev); + if (retval < 0) + FMDERR("set Search Algo Type failed\n"); + break; + case V4L2_CID_PRIVATE_SINRFIRSTSTAGE: + rd.mode = FM_RX_CONFG_MODE; + rd.length = FM_RX_CNFG_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval < 0) { + FMDERR("default data read failed %x\n", retval); + goto end; + } + wrd.mode = FM_RX_CONFG_MODE; + wrd.length = FM_RX_CNFG_LEN; + memcpy(&wrd.data, &radio->default_data.data, + radio->default_data.ret_data_len); + wrd.data[SINRFIRSTSTAGE_OFFSET] = ctrl->value; + retval = hci_def_data_write(&wrd, radio->fm_hdev); + if (retval < 0) + FMDERR("set SINR First Stage failed\n"); + break; + case V4L2_CID_PRIVATE_RMSSIFIRSTSTAGE: + rd.mode = FM_RX_CONFG_MODE; + rd.length = FM_RX_CNFG_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval < 0) { + FMDERR("default data read failed %x\n", retval); + goto end; + } + wrd.mode = FM_RX_CONFG_MODE; + wrd.length = FM_RX_CNFG_LEN; + memcpy(&wrd.data, &radio->default_data.data, + radio->default_data.ret_data_len); + wrd.data[RMSSIFIRSTSTAGE_OFFSET] = ctrl->value; + retval = hci_def_data_write(&wrd, radio->fm_hdev); + if (retval < 0) + FMDERR("set RMSSI First Stage failed\n"); + break; + case V4L2_CID_PRIVATE_CF0TH12: + rd.mode = FM_RX_CONFG_MODE; + rd.length = FM_RX_CNFG_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval < 0) { + FMDERR("default data read failed %x\n", retval); + goto end; + } + wrd.mode = FM_RX_CONFG_MODE; + wrd.length = FM_RX_CNFG_LEN; + memcpy(&wrd.data, &radio->default_data.data, + radio->default_data.ret_data_len); + wrd.data[CF0TH12_BYTE1_OFFSET] = (ctrl->value & 255); + wrd.data[CF0TH12_BYTE2_OFFSET] = ((ctrl->value >> 8) & 255); + wrd.data[CF0TH12_BYTE3_OFFSET] = ((ctrl->value >> 16) & 255); + wrd.data[CF0TH12_BYTE4_OFFSET] = ((ctrl->value >> 24) & 255); + retval = hci_def_data_write(&wrd, radio->fm_hdev); + if (retval < 0) + FMDERR("set CF0 Threshold failed\n"); + break; + case V4L2_CID_PRIVATE_RXREPEATCOUNT: + rd.mode = RDS_PS0_XFR_MODE; + rd.length = RDS_PS0_LEN; + rd.param_len = 0; + rd.param = 0; + + retval = hci_def_data_read(&rd, radio->fm_hdev); + if (retval < 0) { + FMDERR("default data read failed for PS0 %x\n", retval); + goto end; + } + wrd.mode = RDS_PS0_XFR_MODE; + wrd.length = RDS_PS0_LEN; + memcpy(&wrd.data, &radio->default_data.data, + radio->default_data.ret_data_len); + wrd.data[RX_REPEATE_BYTE_OFFSET] = ctrl->value; + + retval = hci_def_data_write(&wrd, radio->fm_hdev); + if (retval < 0) + FMDERR("set RxRePeat count failed\n"); + break; + case V4L2_CID_PRIVATE_IRIS_GET_SPUR_TBL: + spur_freq = ctrl->value; + retval = radio_hci_request(radio->fm_hdev, + hci_fm_get_spur_tbl_data, + (unsigned long)spur_freq, + RADIO_HCI_TIMEOUT); + if (retval < 0) + FMDERR("get Spur data failed\n"); + break; + case V4L2_CID_PRIVATE_BLEND_SINRHI: + if (!is_valid_blend_value(ctrl->value)) { + FMDERR("%s: blend sinr count is not valid\n", + __func__); + retval = -EINVAL; + goto end; + } + retval = hci_cmd(HCI_FM_GET_BLND_TBL_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to get blend table %d\n", retval); + goto end; + } + radio->blend_tbl.scBlendSinrHi = ctrl->value; + retval = hci_set_blend_tbl_req(&radio->blend_tbl, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to set blend table %d\n", retval); + goto end; + } + break; + case V4L2_CID_PRIVATE_BLEND_RMSSIHI: + if (!is_valid_blend_value(ctrl->value)) { + FMDERR("%s: blend rmssi count is not valid\n", + __func__); + retval = -EINVAL; + goto end; + } + retval = hci_cmd(HCI_FM_GET_BLND_TBL_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to get blend table %d\n", retval); + goto end; + } + radio->blend_tbl.scBlendRmssiHi = ctrl->value; + retval = hci_set_blend_tbl_req(&radio->blend_tbl, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to set blend table %d\n", retval); + goto end; + } + break; + default: + retval = -EINVAL; + break; + } + +end: + if (retval > 0) + retval = -EINVAL; + + return retval; +} + +static int update_spur_table(struct iris_device *radio) +{ + struct hci_fm_def_data_wr_req default_data; + int len = 0, index = 0, offset = 0, i = 0; + int retval = 0, temp = 0, cnt = 0; + + memset(&default_data, 0, sizeof(default_data)); + + /* Pass the mode of SPUR_CLK */ + default_data.mode = CKK_SPUR; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + temp = radio->spur_table_size; + for (cnt = 0; cnt < (temp / 5); cnt++) { + offset = 0; + /* + * Program the spur entries in spur table in following order: + * Spur index + * Length of the spur data + * Spur Data: + * MSB of the spur frequency + * LSB of the spur frequency + * Enable/Disable the spur frequency + * RMSSI value of the spur frequency + */ + default_data.data[offset++] = ENTRY_0 + cnt; + for (i = 0; i < SPUR_ENTRIES_PER_ID; i++) { + default_data.data[offset++] = GET_FREQ(COMPUTE_SPUR( + radio->spur_data.freq[index]), 0); + default_data.data[offset++] = GET_FREQ(COMPUTE_SPUR( + radio->spur_data.freq[index]), 1); + default_data.data[offset++] = + radio->spur_data.enable[index]; + default_data.data[offset++] = + radio->spur_data.rmssi[index]; + index++; + } + len = (SPUR_ENTRIES_PER_ID * SPUR_DATA_SIZE); + default_data.length = (len + 1); + retval = hci_def_data_write(&default_data, radio->fm_hdev); + if (retval < 0) { + FMDBG("%s: Failed to configure entries for ID : %d\n", + __func__, default_data.data[0]); + return retval; + } + } + + /* Compute balance SPUR frequencies to be programmed */ + temp %= SPUR_ENTRIES_PER_ID; + if (temp > 0) { + offset = 0; + default_data.data[offset++] = (radio->spur_table_size / 5); + for (i = 0; i < temp; i++) { + default_data.data[offset++] = GET_FREQ(COMPUTE_SPUR( + radio->spur_data.freq[index]), 0); + default_data.data[offset++] = GET_FREQ(COMPUTE_SPUR( + radio->spur_data.freq[index]), 1); + default_data.data[offset++] = + radio->spur_data.enable[index]; + default_data.data[offset++] = + radio->spur_data.rmssi[index]; + index++; + } + len = (temp * SPUR_DATA_SIZE); + default_data.length = (len + 1); + retval = hci_def_data_write(&default_data, radio->fm_hdev); + if (retval < 0) { + FMDERR("%s: Failed to configure entries for ID : %d\n", + __func__, default_data.data[0]); + return retval; + } + } + + return retval; +} + +static int iris_vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + int retval; + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + if (tuner == NULL) { + FMDERR("%s, tuner is null\n", __func__); + return -EINVAL; + } + if (tuner->index > 0) { + FMDERR("Invalid Tuner Index\n"); + return -EINVAL; + } + if (radio->mode == FM_RECV) { + retval = hci_cmd(HCI_FM_GET_STATION_PARAM_CMD, radio->fm_hdev); + if (retval < 0) { + FMDERR("Failed to Get station params\n"); + return retval; + } + tuner->type = V4L2_TUNER_RADIO; + tuner->rangelow = + radio->recv_conf.band_low_limit * TUNE_PARAM; + tuner->rangehigh = + radio->recv_conf.band_high_limit * TUNE_PARAM; + tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + tuner->capability = V4L2_TUNER_CAP_LOW; + tuner->signal = radio->fm_st_rsp.station_rsp.rssi; + tuner->audmode = radio->fm_st_rsp.station_rsp.stereo_prg; + tuner->afc = 0; + } else if (radio->mode == FM_TRANS) { + retval = hci_cmd(HCI_FM_GET_TX_CONFIG, radio->fm_hdev); + if (retval < 0) { + FMDERR("get Tx config failed %d\n", retval); + return retval; + } + tuner->type = V4L2_TUNER_RADIO; + tuner->rangelow = + radio->trans_conf.band_low_limit * TUNE_PARAM; + tuner->rangehigh = + radio->trans_conf.band_high_limit * TUNE_PARAM; + } else + return -EINVAL; + return 0; +} + +static int iris_vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *tuner) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + int retval = 0; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + + if (tuner == NULL) { + FMDERR("%s, tuner is null\n", __func__); + return -EINVAL; + } + + if (tuner->index > 0) + return -EINVAL; + + if (radio->mode == FM_RECV) { + radio->recv_conf.band_low_limit = tuner->rangelow / TUNE_PARAM; + radio->recv_conf.band_high_limit = + tuner->rangehigh / TUNE_PARAM; + if (tuner->audmode == V4L2_TUNER_MODE_MONO) { + radio->stereo_mode.stereo_mode = 0x01; + retval = hci_set_fm_stereo_mode( + &radio->stereo_mode, + radio->fm_hdev); + } else { + radio->stereo_mode.stereo_mode = 0x00; + retval = hci_set_fm_stereo_mode( + &radio->stereo_mode, + radio->fm_hdev); + } + if (retval < 0) + FMDERR(": set tuner failed with %d\n", retval); + return retval; + } else if (radio->mode == FM_TRANS) { + radio->trans_conf.band_low_limit = tuner->rangelow / TUNE_PARAM; + radio->trans_conf.band_high_limit = + tuner->rangehigh / TUNE_PARAM; + } else + return -EINVAL; + + return retval; +} + +static int iris_vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *freq) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + + if ((freq != NULL) && (radio != NULL)) { + freq->frequency = + radio->fm_st_rsp.station_rsp.station_freq * TUNE_PARAM; + } else + return -EINVAL; + return 0; +} + +static int iris_vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *freq) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + int retval = -1; + u32 f; + + if (freq == NULL) { + FMDERR("%s, v4l2 freq is null\n", __func__); + return -EINVAL; + } + f = (freq->frequency / TUNE_PARAM); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + if (freq->type != V4L2_TUNER_RADIO) + return -EINVAL; + + /* We turn off RDS prior to tuning to a new station. + * because of a bug in SoC which prevents tuning + * during RDS transmission. + */ + if (radio->mode == FM_TRANS + && (radio->trans_conf.rds_std == 0 || + radio->trans_conf.rds_std == 1)) { + radio->prev_trans_rds = radio->trans_conf.rds_std; + radio->trans_conf.rds_std = 2; + hci_set_fm_trans_conf(&radio->trans_conf, + radio->fm_hdev); + } + + retval = iris_set_freq(radio, f); + + if (radio->mode == FM_TRANS + && radio->trans_conf.rds_std == 2 + && (radio->prev_trans_rds == 1 + || radio->prev_trans_rds == 0)) { + radio->trans_conf.rds_std = radio->prev_trans_rds; + hci_set_fm_trans_conf(&radio->trans_conf, + radio->fm_hdev); + } + + if (retval < 0) + FMDERR(" set frequency failed with %d\n", retval); + return retval; +} + +static int iris_fops_release(struct file *file) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + int retval = 0; + + FMDBG("radio %pK", radio); + + if (radio == NULL) + return -EINVAL; + + FMDBG("state %d", radio->mode); + mutex_lock(&radio->lock); + + if (radio->mode == FM_OFF) + goto end; + + if (radio->mode == FM_RECV) { + radio->is_fm_closing = true; + radio->mode = FM_TURNING_OFF; + retval = hci_cmd_uninterruptible(HCI_FM_DISABLE_RECV_CMD, + radio->fm_hdev); + radio->mode = FM_OFF; + radio->is_fm_closing = false; + } else if (radio->mode == FM_TRANS) { + radio->is_fm_closing = true; + radio->mode = FM_TURNING_OFF; + retval = hci_cmd_uninterruptible(HCI_FM_DISABLE_TRANS_CMD, + radio->fm_hdev); + radio->mode = FM_OFF; + radio->is_fm_closing = false; + } else if (radio->mode == FM_CALIB) { + radio->mode = FM_OFF; + mutex_unlock(&radio->lock); + return retval; + } +end: + FMDBG("mode %d", radio->mode); + mutex_lock(&fm_smd_enable); + if (radio->fm_hdev != NULL) + radio->fm_hdev->close_smd(); + mutex_unlock(&fm_smd_enable); + + mutex_unlock(&radio->lock); + + if (retval < 0) + FMDERR("Err on disable FM %d\n", retval); + + FMDBG("ret %d", retval); + return retval; +} + +/************************************************************************** + * File Operations Interface + ************************************************************************** + * + * iris_fops_read - read event data + */ +static ssize_t iris_fops_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + enum iris_buf_t buf_type = -1; + unsigned char buf_fifo[STD_BUF_SIZE] = {0}; + struct kfifo *data_fifo = NULL; + unsigned int len = 0, retval = -1; + u32 bytesused = 0; + + FMDBG("buffer %pK", buffer); + + if ((radio == NULL) || (buffer == NULL)) { + FMDERR("radio/buffer is NULL\n"); + return -ENXIO; + } + buf_type = count; + len = STD_BUF_SIZE; + + if ((buf_type < IRIS_BUF_MAX) && (buf_type >= 0)) { + data_fifo = &radio->data_buf[buf_type]; + if (buf_type == IRIS_BUF_EVENTS) + if (wait_event_interruptible(radio->event_queue, + kfifo_len(data_fifo)) < 0) + return -EINTR; + } else { + FMDERR("invalid buffer type\n"); + return -EINVAL; + } + if (len <= STD_BUF_SIZE) { + bytesused = kfifo_out_locked(data_fifo, &buf_fifo[0], + len, &radio->buf_lock[buf_type]); + } else { + FMDERR("kfifo_out_locked can not use len more than 128\n"); + return -EINVAL; + } + retval = copy_to_user(buffer, &buf_fifo[0], bytesused); + if (retval > 0) { + FMDERR("Failed to copy %d bytes of data\n", retval); + return -EAGAIN; + } + retval = bytesused; + return retval; +} + +static int iris_vidioc_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buffer) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + enum iris_buf_t buf_type = -1; + unsigned char buf_fifo[STD_BUF_SIZE] = {0}; + struct kfifo *data_fifo = NULL; + unsigned char *buf = NULL; + unsigned int len = 0, retval = -1; + + FMDBG("buffer %pK", buffer); + + if ((radio == NULL) || (buffer == NULL)) { + FMDERR("radio/buffer is NULL\n"); + return -ENXIO; + } + buf_type = buffer->index; + buf = (unsigned char *)buffer->m.userptr; + len = buffer->length; + + if ((buf_type < IRIS_BUF_MAX) && (buf_type >= 0)) { + data_fifo = &radio->data_buf[buf_type]; + if (buf_type == IRIS_BUF_EVENTS) + if (wait_event_interruptible(radio->event_queue, + kfifo_len(data_fifo)) < 0) + return -EINTR; + } else { + FMDERR("invalid buffer type\n"); + return -EINVAL; + } + if (len <= STD_BUF_SIZE) { + buffer->bytesused = kfifo_out_locked(data_fifo, &buf_fifo[0], + len, &radio->buf_lock[buf_type]); + } else { + FMDERR("kfifo_out_locked can not use len more than 128\n"); + return -EINVAL; + } + retval = copy_to_user(buf, &buf_fifo[0], buffer->bytesused); + if (retval > 0) { + FMDERR("Failed to copy %d bytes of data\n", retval); + return -EAGAIN; + } + + return retval; +} + +static int iris_vidioc_g_fmt_type_private(struct file *file, void *priv, + struct v4l2_format *f) +{ + return 0; + +} + +static int iris_vidioc_s_hw_freq_seek(struct file *file, void *priv, + const struct v4l2_hw_freq_seek *seek) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + int dir; + + if (seek == NULL) { + FMDERR("%s, v4l2_hw_freq_seek is null\n", __func__); + return -EINVAL; + } + if (seek->seek_upward) + dir = SRCH_DIR_UP; + else + dir = SRCH_DIR_DOWN; + return iris_search(radio, CTRL_ON, dir); +} + +static int iris_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *capability) +{ + struct iris_device *radio; + + FMDBG("caps %pK", capability); + + radio = video_get_drvdata(video_devdata(file)); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + if (capability == NULL) { + FMDERR("%s, capability struct is null\n", __func__); + return -EINVAL; + } + strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver)); + strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); + + strlcpy(radio->g_cap.driver, DRIVER_NAME, sizeof(radio->g_cap.driver)); + strlcpy(radio->g_cap.card, DRIVER_CARD, sizeof(radio->g_cap.card)); + + radio->g_cap.capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + capability->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + capability->capabilities = capability->device_caps | + V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int initialise_recv(struct iris_device *radio) +{ + int retval; + + FMDBG("radio %pK", radio); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + + radio->mute_mode.soft_mute = CTRL_ON; + retval = hci_set_fm_mute_mode(&radio->mute_mode, + radio->fm_hdev); + + if (retval < 0) { + FMDERR("Failed to enable Smute\n"); + return retval; + } + + radio->stereo_mode.stereo_mode = CTRL_OFF; + radio->stereo_mode.sig_blend = sig_blend; + radio->stereo_mode.intf_blend = CTRL_ON; + radio->stereo_mode.most_switch = CTRL_ON; + retval = hci_set_fm_stereo_mode(&radio->stereo_mode, + radio->fm_hdev); + + if (retval < 0) { + FMDERR("Failed to set stereo mode\n"); + return retval; + } + + radio->event_mask = SIG_LEVEL_INTR | RDS_SYNC_INTR | AUDIO_CTRL_INTR; + retval = hci_conf_event_mask(&radio->event_mask, radio->fm_hdev); + if (retval < 0) { + FMDERR("Enable Async events failed\n"); + return retval; + } + + retval = hci_cmd(HCI_FM_GET_RECV_CONF_CMD, radio->fm_hdev); + if (retval < 0) + FMDERR("Failed to get the Recv Config\n"); + return retval; +} + +static int initialise_trans(struct iris_device *radio) +{ + + int retval; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null"); + return -EINVAL; + } + + retval = hci_cmd(HCI_FM_GET_TX_CONFIG, radio->fm_hdev); + if (retval < 0) + FMDERR("get frequency failed %d\n", retval); + + return retval; +} + +static int is_enable_rx_possible(struct iris_device *radio) +{ + int retval = 1; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + + if (radio->mode == FM_OFF || radio->mode == FM_RECV) + retval = 0; + + return retval; +} + +static int is_enable_tx_possible(struct iris_device *radio) +{ + int retval = 1; + + if (radio->mode == FM_OFF || radio->mode == FM_TRANS) + retval = 0; + + return retval; +} + +static const struct v4l2_ioctl_ops iris_ioctl_ops = { + .vidioc_querycap = iris_vidioc_querycap, + .vidioc_queryctrl = iris_vidioc_queryctrl, + .vidioc_g_ctrl = iris_vidioc_g_ctrl, + .vidioc_s_ctrl = iris_vidioc_s_ctrl, + .vidioc_g_tuner = iris_vidioc_g_tuner, + .vidioc_s_tuner = iris_vidioc_s_tuner, + .vidioc_g_frequency = iris_vidioc_g_frequency, + .vidioc_s_frequency = iris_vidioc_s_frequency, + .vidioc_s_hw_freq_seek = iris_vidioc_s_hw_freq_seek, + .vidioc_dqbuf = iris_vidioc_dqbuf, + .vidioc_g_fmt_type_private = iris_vidioc_g_fmt_type_private, + .vidioc_s_ext_ctrls = iris_vidioc_s_ext_ctrls, + .vidioc_g_ext_ctrls = iris_vidioc_g_ext_ctrls, +}; + +static const struct v4l2_file_operations iris_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .read = iris_fops_read, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = v4l2_compat_ioctl32, +#endif + .release = iris_fops_release, +}; + +static struct video_device iris_viddev_template = { + .fops = &iris_fops, + .ioctl_ops = &iris_ioctl_ops, + .name = DRIVER_NAME, + .release = video_device_release, +}; + +static struct video_device *video_get_dev(void) +{ + return priv_videodev; +} + +static int iris_probe(struct platform_device *pdev) +{ + struct iris_device *radio; + int retval; + int radio_nr = -1; + int i; + + FMDBG("pdev %pK", pdev); + + if (!pdev) { + FMDERR(": pdev is null\n"); + return -ENOMEM; + } + + radio = kzalloc(sizeof(struct iris_device), GFP_KERNEL); + if (!radio) + return -ENOMEM; + + radio->dev = &pdev->dev; + platform_set_drvdata(pdev, radio); + + radio->videodev = video_device_alloc(); + if (!radio->videodev) { + kfree(radio); + return -ENOMEM; + } + + memcpy(radio->videodev, &iris_viddev_template, + sizeof(iris_viddev_template)); + strlcpy(radio->v4l2_dev.name, DRIVER_NAME, + sizeof(radio->v4l2_dev.name)); + radio->videodev->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + retval = v4l2_device_register(NULL, &radio->v4l2_dev); + if (retval) + return -EINVAL; + radio->videodev->v4l2_dev = &radio->v4l2_dev; + + for (i = 0; i < IRIS_BUF_MAX; i++) { + int kfifo_alloc_rc = 0; + + spin_lock_init(&radio->buf_lock[i]); + + if ((i == IRIS_BUF_RAW_RDS) || (i == IRIS_BUF_PEEK)) + kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i], + rds_buf*3, GFP_KERNEL); + else if ((i == IRIS_BUF_CAL_DATA) || (i == IRIS_BUF_RT_RDS)) + kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i], + STD_BUF_SIZE*2, GFP_KERNEL); + else + kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i], + STD_BUF_SIZE, GFP_KERNEL); + + if (kfifo_alloc_rc != 0) { + FMDERR("failed allocating buffers %d\n", + kfifo_alloc_rc); + for (; i > -1; i--) + kfifo_free(&radio->data_buf[i]); + video_device_release(radio->videodev); + kfree(radio); + return -ENOMEM; + } + } + + mutex_init(&radio->lock); + init_completion(&radio->sync_xfr_start); + radio->tune_req = 0; + radio->prev_trans_rds = 2; + radio->is_fm_closing = false; + init_waitqueue_head(&radio->event_queue); + init_waitqueue_head(&radio->read_queue); + + video_set_drvdata(radio->videodev, radio); + + if (video_get_drvdata(radio->videodev) == NULL) + FMDERR(": video_get_drvdata failed\n"); + + retval = video_register_device(radio->videodev, VFL_TYPE_RADIO, + radio_nr); + if (retval) { + FMDERR(": Could not register video device\n"); + video_device_release(radio->videodev); + for (; i > -1; i--) + kfifo_free(&radio->data_buf[i]); + kfree(radio); + return retval; + } + priv_videodev = kzalloc(sizeof(struct video_device), + GFP_KERNEL); + if (priv_videodev != NULL) { + memcpy(priv_videodev, radio->videodev, + sizeof(struct video_device)); + } else { + video_unregister_device(radio->videodev); + video_device_release(radio->videodev); + for (; i > -1; i--) + kfifo_free(&radio->data_buf[i]); + kfree(radio); + } + return 0; +} + + +static int iris_remove(struct platform_device *pdev) +{ + int i; + struct iris_device *radio = platform_get_drvdata(pdev); + + if (radio == NULL) { + FMDERR(":radio is null\n"); + return -EINVAL; + } + video_unregister_device(radio->videodev); + + for (i = 0; i < IRIS_BUF_MAX; i++) + kfifo_free(&radio->data_buf[i]); + + kfree(radio); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id iris_fm_match[] = { + {.compatible = "qcom,iris_fm"}, + {} +}; + +static struct platform_driver iris_driver = { + .probe = iris_probe, + .driver = { + //.owner = THIS_MODULE, + .name = "iris_fm", + .of_match_table = iris_fm_match, + }, + .remove = iris_remove, +}; + +static int __init iris_radio_init(void) +{ + return platform_driver_register(&iris_driver); +} +module_init(iris_radio_init); + +static void __exit iris_radio_exit(void) +{ + platform_driver_unregister(&iris_driver); +} +module_exit(iris_radio_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/media/rc/Kconfig b/drivers/media/rc/Kconfig index f37dc3504f60..1021c08a9ba4 100644 --- a/drivers/media/rc/Kconfig +++ b/drivers/media/rc/Kconfig @@ -42,15 +42,6 @@ menuconfig RC_DECODERS bool "Remote controller decoders" depends on RC_CORE -config IR_SPI - tristate "SPI connected IR LED" - depends on SPI && LIRC - ---help--- - Say Y if you want to use an IR LED connected through SPI bus. - - To compile this driver as a module, choose M here: the module will be - called ir-spi. - if RC_DECODERS config IR_NEC_DECODER tristate "Enable IR raw decoder for the NEC protocol" diff --git a/drivers/media/rc/ir-spi.c b/drivers/media/rc/ir-spi.c index 5ac8f4ccbf5a..c58f2d38a458 100644 --- a/drivers/media/rc/ir-spi.c +++ b/drivers/media/rc/ir-spi.c @@ -2,305 +2,166 @@ // SPI driven IR LED device driver // // Copyright (c) 2016 Samsung Electronics Co., Ltd. -// Copyright (C) 2020 XiaoMi, Inc. // Copyright (c) Andi Shyti +#include #include #include #include #include #include #include -#include "media/lirc_dev.h" +#include #define IR_SPI_DRIVER_NAME "ir-spi" -#define IR_SPI_DEFAULT_FREQUENCY 1920000 -#define IR_SPI_BIT_PER_WORD 32 -#define IR_SPI_DATA_BUFFER 150000 +#define IR_SPI_DEFAULT_FREQUENCY 38000 +#define IR_SPI_MAX_BUFSIZE 4096 struct ir_spi_data { - u16 nusers; - int power_gpio; - int buffer_size; + u32 freq; + bool negated; - u8 *buffer; - struct lirc_driver lirc_driver; + u16 tx_buf[IR_SPI_MAX_BUFSIZE]; + u16 pulse; + u16 space; + + struct rc_dev *rc; struct spi_device *spi; - struct spi_transfer xfer; - struct mutex mutex; struct regulator *regulator; }; -static ssize_t ir_spi_chardev_write(struct file *file, - const char __user *buffer, - size_t length, loff_t *offset) +static int ir_spi_tx(struct rc_dev *dev, + unsigned int *buffer, unsigned int count) { - struct ir_spi_data *idata = file->private_data; - bool please_free = false; - int ret = 0; + int i; + int ret; + unsigned int len = 0; + struct ir_spi_data *idata = dev->priv; + struct spi_transfer xfer; - if (idata->xfer.len && (idata->xfer.len != length)) - return -EINVAL; + /* convert the pulse/space signal to raw binary signal */ + for (i = 0; i < count; i++) { + unsigned int periods; + int j; + u16 val; - mutex_lock(&idata->mutex); + periods = DIV_ROUND_CLOSEST(buffer[i] * idata->freq, 1000000); - if (!idata->xfer.len) { - idata->buffer = kmalloc(length, GFP_DMA); + if (len + periods >= IR_SPI_MAX_BUFSIZE) + return -EINVAL; - if (!idata->buffer) { - ret = -ENOMEM; - goto out_unlock; - } - - idata->xfer.len = length; - please_free = true; + /* + * the first value in buffer is a pulse, so that 0, 2, 4, ... + * contain a pulse duration. On the contrary, 1, 3, 5, ... + * contain a space duration. + */ + val = (i % 2) ? idata->space : idata->pulse; + for (j = 0; j < periods; j++) + idata->tx_buf[len++] = val; } - if (copy_from_user(idata->buffer, buffer, length)) { - ret = -EFAULT; - goto out_free; - } -#if 0 + + memset(&xfer, 0, sizeof(xfer)); + + xfer.speed_hz = idata->freq * 16; + xfer.len = len * sizeof(*idata->tx_buf); + xfer.tx_buf = idata->tx_buf; + ret = regulator_enable(idata->regulator); - if (ret) { - dev_err(&idata->spi->dev, "failed to power on the LED\n"); - goto out_free; - } + if (ret) + return ret; -#endif - idata->xfer.tx_buf = idata->buffer; - dev_warn(&idata->spi->dev, "xfer.len%d buffer_size %d\n", - (int)idata->xfer.len, idata->buffer_size); - ret = spi_sync_transfer(idata->spi, &idata->xfer, 1); + ret = spi_sync_transfer(idata->spi, &xfer, 1); if (ret) dev_err(&idata->spi->dev, "unable to deliver the signal\n"); -#if 0 + regulator_disable(idata->regulator); -#endif -out_free: - if (please_free) { - kfree(idata->buffer); - idata->xfer.len = 0; - idata->buffer = NULL; - } - -out_unlock: - mutex_unlock(&idata->mutex); - gpio_set_value(1168, 0); - return ret ? ret : length; + return ret ? ret : count; } -static int ir_spi_chardev_open(struct inode *inode, struct file *file) +static int ir_spi_set_tx_carrier(struct rc_dev *dev, u32 carrier) { - struct ir_spi_data *idata = lirc_get_pdata(file); + struct ir_spi_data *idata = dev->priv; - if (unlikely(idata->nusers >= SHRT_MAX)) { - dev_err(&idata->spi->dev, "device busy\n"); - return -EBUSY; - } + if (!carrier) + return -EINVAL; - file->private_data = idata; - - mutex_lock(&idata->mutex); - idata->nusers++; - mutex_unlock(&idata->mutex); + idata->freq = carrier; return 0; } -static int ir_spi_chardev_close(struct inode *inode, struct file *file) +static int ir_spi_set_duty_cycle(struct rc_dev *dev, u32 duty_cycle) { - struct ir_spi_data *idata = lirc_get_pdata(file); + struct ir_spi_data *idata = dev->priv; + int bits = (duty_cycle * 15) / 100; - mutex_lock(&idata->mutex); - idata->nusers--; + idata->pulse = GENMASK(bits, 0); - /* - * check if someone else is using the driver, - * if not, then: - * - * - reset length and frequency values to default - * - shut down the LED - * - free the buffer (NULL or ZERO_SIZE_PTR are noop) - */ - if (!idata->nusers) { - idata->xfer.len = 0; - idata->xfer.speed_hz = IR_SPI_DEFAULT_FREQUENCY; + if (idata->negated) { + idata->pulse = ~idata->pulse; + idata->space = 0xffff; + } else { + idata->space = 0; } - mutex_unlock(&idata->mutex); - return 0; } -static long ir_spi_chardev_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - __u32 p; - s32 ret; - struct ir_spi_data *idata = file->private_data; - - switch (cmd) { - case LIRC_GET_FEATURES: - return put_user(idata->lirc_driver.features, - (__u32 __user *) arg); - - case LIRC_GET_LENGTH: - return put_user(idata->xfer.len, (__u32 __user *) arg); - - case LIRC_SET_SEND_MODE: { - void *new; - - ret = get_user(p, (__u32 __user *) arg); - if (ret) - return ret; - - /* - * the user is trying to set the same - * length of the current value - */ - if (idata->xfer.len == p) - return 0; - - /* - * multiple users should use the driver with the - * length, otherwise return EPERM same data - */ - if (idata->nusers > 1) - return -EPERM; - - /* - * if the buffer is already allocated, reallocate it with the - * desired value. If the desired value is 0, then the buffer is - * freed from krealloc() - */ - if (idata->xfer.len) { - new = krealloc(idata->buffer, p, GFP_DMA); - } else { - if ((p > idata->buffer_size) || (idata->buffer == NULL)) { - printk ("IR new malloc %d", (int)idata->xfer.len); - if (idata->buffer != NULL) { - kfree (idata->buffer); - idata->buffer = NULL; - } - new = kmalloc(p, GFP_DMA); - if (!new) - return -ENOMEM; - idata->buffer = new; - idata->buffer_size = p; - } - } - - mutex_lock(&idata->mutex); - idata->xfer.len = p; - mutex_unlock(&idata->mutex); - - return 0; - } - - case LIRC_SET_SEND_CARRIER: - return put_user(idata->xfer.speed_hz, (__u32 __user *) arg); - - case LIRC_SET_REC_CARRIER: - ret = get_user(p, (__u32 __user *) arg); - if (ret) - return ret; - - /* - * The frequency cannot be obviously set to '0', - * while, as in the case of the data length, - * multiple users should use the driver with the same - * frequency value, otherwise return EPERM - */ - if (!p || ((idata->nusers > 1) && p != idata->xfer.speed_hz)) - return -EPERM; - - mutex_lock(&idata->mutex); - idata->xfer.speed_hz = p; - mutex_unlock(&idata->mutex); - return 0; - } - - return -EINVAL; -} - -static const struct file_operations ir_spi_fops = { - .owner = THIS_MODULE, - .read = lirc_dev_fop_read, - .write = ir_spi_chardev_write, - .poll = lirc_dev_fop_poll, - .open = ir_spi_chardev_open, - .release = ir_spi_chardev_close, - .llseek = noop_llseek, - .unlocked_ioctl = ir_spi_chardev_ioctl, - .compat_ioctl = ir_spi_chardev_ioctl, -}; - static int ir_spi_probe(struct spi_device *spi) { + int ret; + u8 dc; struct ir_spi_data *idata; - u8 *buffer = NULL; + idata = devm_kzalloc(&spi->dev, sizeof(*idata), GFP_KERNEL); if (!idata) return -ENOMEM; - printk("liping led!"); - gpio_request(1168, "gpio_ir_led"); - gpio_direction_output(1168, 1); - gpio_set_value(1168, 0); - //gpio_free(gpio_99); -#if 0 - idata->regulator = devm_regulator_get(&spi->dev, "vdd"); + + idata->regulator = devm_regulator_get(&spi->dev, "irda_regulator"); if (IS_ERR(idata->regulator)) - return PTR_ERR(idata->regulator); -#endif - snprintf(idata->lirc_driver.name, sizeof(idata->lirc_driver.name), - IR_SPI_DRIVER_NAME); - idata->lirc_driver.features = LIRC_CAN_SEND_RAW; - idata->lirc_driver.code_length = 1; - idata->lirc_driver.fops = &ir_spi_fops; - idata->lirc_driver.dev = &spi->dev; - idata->lirc_driver.data = idata; - idata->lirc_driver.owner = THIS_MODULE; - idata->lirc_driver.minor = -1; + return PTR_ERR(idata->regulator); - idata->lirc_driver.minor = lirc_register_driver(&idata->lirc_driver); - if (idata->lirc_driver.minor < 0) { - dev_err(&spi->dev, "unable to generate character device\n"); - return idata->lirc_driver.minor; - } - - mutex_init(&idata->mutex); - - idata->spi = spi; - - idata->xfer.bits_per_word = IR_SPI_BIT_PER_WORD; - idata->xfer.speed_hz = IR_SPI_DEFAULT_FREQUENCY; - buffer = kmalloc(IR_SPI_DATA_BUFFER, GFP_DMA); - if (!buffer) { + idata->rc = devm_rc_allocate_device(&spi->dev, RC_DRIVER_IR_RAW_TX); + if (!idata->rc) return -ENOMEM; - } - idata->buffer = buffer; - idata->buffer_size = IR_SPI_DATA_BUFFER; - return 0; + + idata->rc->tx_ir = ir_spi_tx; + idata->rc->s_tx_carrier = ir_spi_set_tx_carrier; + idata->rc->s_tx_duty_cycle = ir_spi_set_duty_cycle; + idata->rc->device_name = "IR SPI"; + idata->rc->driver_name = IR_SPI_DRIVER_NAME; + idata->rc->priv = idata; + idata->spi = spi; + + idata->negated = of_property_read_bool(spi->dev.of_node, + "led-active-low"); + ret = of_property_read_u8(spi->dev.of_node, "duty-cycle", &dc); + if (ret) + dc = 50; + + /* ir_spi_set_duty_cycle cannot fail, + * it returns int to be compatible with the + * rc->s_tx_duty_cycle function + */ + ir_spi_set_duty_cycle(idata->rc, dc); + + idata->freq = IR_SPI_DEFAULT_FREQUENCY; + + return devm_rc_register_device(&spi->dev, idata->rc); } static int ir_spi_remove(struct spi_device *spi) { - struct ir_spi_data *idata = spi_get_drvdata(spi); - if (idata->buffer != NULL) { - kfree(idata->buffer); - idata->buffer = NULL; - } - lirc_unregister_driver(idata->lirc_driver.minor); - return 0; } static const struct of_device_id ir_spi_of_match[] = { - { .compatible = "ir-spi" }, + { .compatible = "ir-spi-led" }, {}, }; +MODULE_DEVICE_TABLE(of, ir_spi_of_match); static struct spi_driver ir_spi_driver = { .probe = ir_spi_probe, @@ -313,7 +174,6 @@ static struct spi_driver ir_spi_driver = { module_spi_driver(ir_spi_driver); - MODULE_AUTHOR("Andi Shyti "); MODULE_DESCRIPTION("SPI IR LED"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/rc/lirc_dev.c b/drivers/media/rc/lirc_dev.c index a5884294c0bf..d6f5f5b3f75f 100644 --- a/drivers/media/rc/lirc_dev.c +++ b/drivers/media/rc/lirc_dev.c @@ -18,571 +18,807 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include -#include -#include -#include #include #include -#include +#include +#include +#include +#include +#include -#include -#include -#include +#include "rc-core-priv.h" +#include #define LIRCBUF_SIZE 1024 -#define NOPLUG -1 -#define LOGHEAD "lirc_dev (%s[%d]): " static dev_t lirc_base_dev; -struct irctl { - struct lirc_driver d; - int attached; - int open; - - struct mutex irctl_lock; - struct lirc_buffer *buf; - bool buf_internal; - unsigned int chunk_size; - - struct device dev; - struct cdev cdev; -}; - -static DEFINE_MUTEX(lirc_dev_lock); - -static struct irctl *irctls[MAX_IRCTL_DEVICES]; +/* Used to keep track of allocated lirc devices */ +static DEFINE_IDA(lirc_ida); /* Only used for sysfs but defined to void otherwise */ static struct class *lirc_class; - -static void lirc_release(struct device *ld) +/** + * ir_lirc_raw_event() - Send raw IR data to lirc to be relayed to userspace + * + * @dev: the struct rc_dev descriptor of the device + * @ev: the struct ir_raw_event descriptor of the pulse/space + */ +void ir_lirc_raw_event(struct rc_dev *dev, struct ir_raw_event ev) { - struct irctl *ir = container_of(ld, struct irctl, dev); + unsigned long flags; + struct lirc_fh *fh; + int sample; - put_device(ir->dev.parent); + /* Packet start */ + if (ev.reset) { + /* + * Userspace expects a long space event before the start of + * the signal to use as a sync. This may be done with repeat + * packets and normal samples. But if a reset has been sent + * then we assume that a long time has passed, so we send a + * space with the maximum time value. + */ + sample = LIRC_SPACE(LIRC_VALUE_MASK); + dev_dbg(&dev->dev, "delivering reset sync space to lirc_dev\n"); - if (ir->buf_internal) { - lirc_buffer_free(ir->buf); - kfree(ir->buf); - } + /* Carrier reports */ + } else if (ev.carrier_report) { + sample = LIRC_FREQUENCY(ev.carrier); + dev_dbg(&dev->dev, "carrier report (freq: %d)\n", sample); - mutex_lock(&lirc_dev_lock); - irctls[ir->d.minor] = NULL; - mutex_unlock(&lirc_dev_lock); - kfree(ir); -} + /* Packet end */ + } else if (ev.timeout) { + if (dev->gap) + return; -static int lirc_allocate_buffer(struct irctl *ir) -{ - int err = 0; - int bytes_in_key; - unsigned int chunk_size; - unsigned int buffer_size; - struct lirc_driver *d = &ir->d; + dev->gap_start = ktime_get(); + dev->gap = true; + dev->gap_duration = ev.duration; - bytes_in_key = BITS_TO_LONGS(d->code_length) + - (d->code_length % 8 ? 1 : 0); - buffer_size = d->buffer_size ? d->buffer_size : BUFLEN / bytes_in_key; - chunk_size = d->chunk_size ? d->chunk_size : bytes_in_key; + sample = LIRC_TIMEOUT(ev.duration / 1000); + dev_dbg(&dev->dev, "timeout report (duration: %d)\n", sample); - if (d->rbuf) { - ir->buf = d->rbuf; - ir->buf_internal = false; + /* Normal sample */ } else { - ir->buf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL); - if (!ir->buf) { - err = -ENOMEM; - goto out; + if (dev->gap) { + dev->gap_duration += ktime_to_ns(ktime_sub(ktime_get(), + dev->gap_start)); + + /* Convert to ms and cap by LIRC_VALUE_MASK */ + do_div(dev->gap_duration, 1000); + dev->gap_duration = min_t(u64, dev->gap_duration, + LIRC_VALUE_MASK); + + spin_lock_irqsave(&dev->lirc_fh_lock, flags); + list_for_each_entry(fh, &dev->lirc_fh, list) + kfifo_put(&fh->rawir, + LIRC_SPACE(dev->gap_duration)); + spin_unlock_irqrestore(&dev->lirc_fh_lock, flags); + dev->gap = false; } - err = lirc_buffer_init(ir->buf, chunk_size, buffer_size); - if (err) { - kfree(ir->buf); - ir->buf = NULL; - goto out; - } - - ir->buf_internal = true; - d->rbuf = ir->buf; + sample = ev.pulse ? LIRC_PULSE(ev.duration / 1000) : + LIRC_SPACE(ev.duration / 1000); + dev_dbg(&dev->dev, "delivering %uus %s to lirc_dev\n", + TO_US(ev.duration), TO_STR(ev.pulse)); } - ir->chunk_size = ir->buf->chunk_size; -out: - return err; + /* + * bpf does not care about the gap generated above; that exists + * for backwards compatibility + */ + lirc_bpf_run(dev, sample); + + spin_lock_irqsave(&dev->lirc_fh_lock, flags); + list_for_each_entry(fh, &dev->lirc_fh, list) { + if (LIRC_IS_TIMEOUT(sample) && !fh->send_timeout_reports) + continue; + if (kfifo_put(&fh->rawir, sample)) + wake_up_poll(&fh->wait_poll, EPOLLIN | EPOLLRDNORM); + } + spin_unlock_irqrestore(&dev->lirc_fh_lock, flags); } -int lirc_register_driver(struct lirc_driver *d) +/** + * ir_lirc_scancode_event() - Send scancode data to lirc to be relayed to + * userspace. This can be called in atomic context. + * @dev: the struct rc_dev descriptor of the device + * @lsc: the struct lirc_scancode describing the decoded scancode + */ +void ir_lirc_scancode_event(struct rc_dev *dev, struct lirc_scancode *lsc) { - struct irctl *ir; - int minor; - int err; + unsigned long flags; + struct lirc_fh *fh; - if (!d) { - pr_err("driver pointer must be not NULL!\n"); - return -EBADRQC; + lsc->timestamp = ktime_get_ns(); + + spin_lock_irqsave(&dev->lirc_fh_lock, flags); + list_for_each_entry(fh, &dev->lirc_fh, list) { + if (kfifo_put(&fh->scancodes, *lsc)) + wake_up_poll(&fh->wait_poll, EPOLLIN | EPOLLRDNORM); } - - if (!d->dev) { - pr_err("dev pointer not filled in!\n"); - return -EINVAL; - } - - if (!d->fops) { - pr_err("fops pointer not filled in!\n"); - return -EINVAL; - } - - if (d->minor >= MAX_IRCTL_DEVICES) { - dev_err(d->dev, "minor must be between 0 and %d!\n", - MAX_IRCTL_DEVICES - 1); - return -EBADRQC; - } - - if (d->code_length < 1 || d->code_length > (BUFLEN * 8)) { - dev_err(d->dev, "code length must be less than %d bits\n", - BUFLEN * 8); - return -EBADRQC; - } - - if (!d->rbuf && !(d->fops && d->fops->read && - d->fops->poll && d->fops->unlocked_ioctl)) { - dev_err(d->dev, "undefined read, poll, ioctl\n"); - return -EBADRQC; - } - - mutex_lock(&lirc_dev_lock); - - minor = d->minor; - - if (minor < 0) { - /* find first free slot for driver */ - for (minor = 0; minor < MAX_IRCTL_DEVICES; minor++) - if (!irctls[minor]) - break; - if (minor == MAX_IRCTL_DEVICES) { - dev_err(d->dev, "no free slots for drivers!\n"); - err = -ENOMEM; - goto out_lock; - } - } else if (irctls[minor]) { - dev_err(d->dev, "minor (%d) just registered!\n", minor); - err = -EBUSY; - goto out_lock; - } - - ir = kzalloc(sizeof(struct irctl), GFP_KERNEL); - if (!ir) { - err = -ENOMEM; - goto out_lock; - } - - mutex_init(&ir->irctl_lock); - irctls[minor] = ir; - d->minor = minor; - - /* some safety check 8-) */ - d->name[sizeof(d->name)-1] = '\0'; - - if (d->features == 0) - d->features = LIRC_CAN_REC_LIRCCODE; - - ir->d = *d; - - if (LIRC_CAN_REC(d->features)) { - err = lirc_allocate_buffer(irctls[minor]); - if (err) { - kfree(ir); - goto out_lock; - } - d->rbuf = ir->buf; - } - - device_initialize(&ir->dev); - ir->dev.devt = MKDEV(MAJOR(lirc_base_dev), ir->d.minor); - ir->dev.class = lirc_class; - ir->dev.parent = d->dev; - ir->dev.release = lirc_release; - dev_set_name(&ir->dev, "lirc%d", ir->d.minor); - - cdev_init(&ir->cdev, d->fops); - ir->cdev.owner = ir->d.owner; - ir->cdev.kobj.parent = &ir->dev.kobj; - - err = cdev_add(&ir->cdev, ir->dev.devt, 1); - if (err) - goto out_free_dev; - - ir->attached = 1; - - err = device_add(&ir->dev); - if (err) - goto out_cdev; - - mutex_unlock(&lirc_dev_lock); - - get_device(ir->dev.parent); - - dev_info(ir->d.dev, "lirc_dev: driver %s registered at minor = %d\n", - ir->d.name, ir->d.minor); - - return minor; - -out_cdev: - cdev_del(&ir->cdev); -out_free_dev: - put_device(&ir->dev); -out_lock: - mutex_unlock(&lirc_dev_lock); - - return err; + spin_unlock_irqrestore(&dev->lirc_fh_lock, flags); } -EXPORT_SYMBOL(lirc_register_driver); +EXPORT_SYMBOL_GPL(ir_lirc_scancode_event); -int lirc_unregister_driver(int minor) +static int ir_lirc_open(struct inode *inode, struct file *file) { - struct irctl *ir; + struct rc_dev *dev = container_of(inode->i_cdev, struct rc_dev, + lirc_cdev); + struct lirc_fh *fh = kzalloc(sizeof(*fh), GFP_KERNEL); + unsigned long flags; + int retval; - if (minor < 0 || minor >= MAX_IRCTL_DEVICES) { - pr_err("minor (%d) must be between 0 and %d!\n", - minor, MAX_IRCTL_DEVICES - 1); - return -EBADRQC; + if (!fh) + return -ENOMEM; + + get_device(&dev->dev); + + if (!dev->registered) { + retval = -ENODEV; + goto out_fh; } - ir = irctls[minor]; - if (!ir) { - pr_err("failed to get irctl\n"); - return -ENOENT; + if (dev->driver_type == RC_DRIVER_IR_RAW) { + if (kfifo_alloc(&fh->rawir, MAX_IR_EVENT_SIZE, GFP_KERNEL)) { + retval = -ENOMEM; + goto out_fh; + } } - mutex_lock(&lirc_dev_lock); - - if (ir->d.minor != minor) { - dev_err(ir->d.dev, "lirc_dev: minor %d device not registered\n", - minor); - mutex_unlock(&lirc_dev_lock); - return -ENOENT; + if (dev->driver_type != RC_DRIVER_IR_RAW_TX) { + if (kfifo_alloc(&fh->scancodes, 32, GFP_KERNEL)) { + retval = -ENOMEM; + goto out_rawir; + } } - dev_dbg(ir->d.dev, "lirc_dev: driver %s unregistered from minor = %d\n", - ir->d.name, ir->d.minor); + fh->send_mode = LIRC_MODE_PULSE; + fh->rc = dev; + fh->send_timeout_reports = true; - ir->attached = 0; - if (ir->open) { - dev_dbg(ir->d.dev, LOGHEAD "releasing opened driver\n", - ir->d.name, ir->d.minor); - wake_up_interruptible(&ir->buf->wait_poll); - } + if (dev->driver_type == RC_DRIVER_SCANCODE) + fh->rec_mode = LIRC_MODE_SCANCODE; + else + fh->rec_mode = LIRC_MODE_MODE2; - mutex_unlock(&lirc_dev_lock); + retval = rc_open(dev); + if (retval) + goto out_kfifo; - device_del(&ir->dev); - cdev_del(&ir->cdev); - put_device(&ir->dev); + init_waitqueue_head(&fh->wait_poll); + + file->private_data = fh; + spin_lock_irqsave(&dev->lirc_fh_lock, flags); + list_add(&fh->list, &dev->lirc_fh); + spin_unlock_irqrestore(&dev->lirc_fh_lock, flags); + + nonseekable_open(inode, file); return 0; -} -EXPORT_SYMBOL(lirc_unregister_driver); - -int lirc_dev_fop_open(struct inode *inode, struct file *file) -{ - struct irctl *ir; - int retval = 0; - - if (iminor(inode) >= MAX_IRCTL_DEVICES) { - pr_err("open result for %d is -ENODEV\n", iminor(inode)); - return -ENODEV; - } - - if (mutex_lock_interruptible(&lirc_dev_lock)) - return -ERESTARTSYS; - - ir = irctls[iminor(inode)]; - mutex_unlock(&lirc_dev_lock); - - if (!ir) { - retval = -ENODEV; - goto error; - } - - dev_dbg(ir->d.dev, LOGHEAD "open called\n", ir->d.name, ir->d.minor); - - if (ir->d.minor == NOPLUG) { - retval = -ENODEV; - goto error; - } - - if (ir->open) { - retval = -EBUSY; - goto error; - } - - if (ir->d.rdev) { - retval = rc_open(ir->d.rdev); - if (retval) - goto error; - } - - if (ir->buf) - lirc_buffer_clear(ir->buf); - - ir->open++; - -error: - nonseekable_open(inode, file); +out_kfifo: + if (dev->driver_type != RC_DRIVER_IR_RAW_TX) + kfifo_free(&fh->scancodes); +out_rawir: + if (dev->driver_type == RC_DRIVER_IR_RAW) + kfifo_free(&fh->rawir); +out_fh: + kfree(fh); + put_device(&dev->dev); return retval; } -EXPORT_SYMBOL(lirc_dev_fop_open); -int lirc_dev_fop_close(struct inode *inode, struct file *file) +static int ir_lirc_close(struct inode *inode, struct file *file) { - struct irctl *ir = irctls[iminor(inode)]; - int ret; + struct lirc_fh *fh = file->private_data; + struct rc_dev *dev = fh->rc; + unsigned long flags; - if (!ir) { - pr_err("called with invalid irctl\n"); - return -EINVAL; - } + spin_lock_irqsave(&dev->lirc_fh_lock, flags); + list_del(&fh->list); + spin_unlock_irqrestore(&dev->lirc_fh_lock, flags); - ret = mutex_lock_killable(&lirc_dev_lock); - WARN_ON(ret); + if (dev->driver_type == RC_DRIVER_IR_RAW) + kfifo_free(&fh->rawir); + if (dev->driver_type != RC_DRIVER_IR_RAW_TX) + kfifo_free(&fh->scancodes); + kfree(fh); - rc_close(ir->d.rdev); - - ir->open--; - if (!ret) - mutex_unlock(&lirc_dev_lock); + rc_close(dev); + put_device(&dev->dev); return 0; } -EXPORT_SYMBOL(lirc_dev_fop_close); -unsigned int lirc_dev_fop_poll(struct file *file, poll_table *wait) +static ssize_t ir_lirc_transmit_ir(struct file *file, const char __user *buf, + size_t n, loff_t *ppos) { - struct irctl *ir = irctls[iminor(file_inode(file))]; - unsigned int ret; + struct lirc_fh *fh = file->private_data; + struct rc_dev *dev = fh->rc; + unsigned int *txbuf; + struct ir_raw_event *raw = NULL; + ssize_t ret; + size_t count; + ktime_t start; + s64 towait; + unsigned int duration = 0; /* signal duration in us */ + int i; - if (!ir) { - pr_err("called with invalid irctl\n"); - return POLLERR; + ret = mutex_lock_interruptible(&dev->lock); + if (ret) + return ret; + + if (!dev->registered) { + ret = -ENODEV; + goto out_unlock; } - if (!ir->attached) - return POLLHUP | POLLERR; + if (!dev->tx_ir) { + ret = -EINVAL; + goto out_unlock; + } - if (ir->buf) { - poll_wait(file, &ir->buf->wait_poll, wait); + if (fh->send_mode == LIRC_MODE_SCANCODE) { + struct lirc_scancode scan; - if (lirc_buffer_empty(ir->buf)) - ret = 0; - else - ret = POLLIN | POLLRDNORM; - } else - ret = POLLERR; + if (n != sizeof(scan)) { + ret = -EINVAL; + goto out_unlock; + } - dev_dbg(ir->d.dev, LOGHEAD "poll result = %d\n", - ir->d.name, ir->d.minor, ret); + if (copy_from_user(&scan, buf, sizeof(scan))) { + ret = -EFAULT; + goto out_unlock; + } + if (scan.flags || scan.keycode || scan.timestamp) { + ret = -EINVAL; + goto out_unlock; + } + + /* + * The scancode field in lirc_scancode is 64-bit simply + * to future-proof it, since there are IR protocols encode + * use more than 32 bits. For now only 32-bit protocols + * are supported. + */ + if (scan.scancode > U32_MAX || + !rc_validate_scancode(scan.rc_proto, scan.scancode)) { + ret = -EINVAL; + goto out_unlock; + } + + raw = kmalloc_array(LIRCBUF_SIZE, sizeof(*raw), GFP_KERNEL); + if (!raw) { + ret = -ENOMEM; + goto out_unlock; + } + + ret = ir_raw_encode_scancode(scan.rc_proto, scan.scancode, + raw, LIRCBUF_SIZE); + if (ret < 0) + goto out_kfree_raw; + + count = ret; + + txbuf = kmalloc_array(count, sizeof(unsigned int), GFP_KERNEL); + if (!txbuf) { + ret = -ENOMEM; + goto out_kfree_raw; + } + + for (i = 0; i < count; i++) + /* Convert from NS to US */ + txbuf[i] = DIV_ROUND_UP(raw[i].duration, 1000); + + if (dev->s_tx_carrier) { + int carrier = ir_raw_encode_carrier(scan.rc_proto); + + if (carrier > 0) + dev->s_tx_carrier(dev, carrier); + } + } else { + if (n < sizeof(unsigned int) || n % sizeof(unsigned int)) { + ret = -EINVAL; + goto out_unlock; + } + + count = n / sizeof(unsigned int); + if (count > LIRCBUF_SIZE || count % 2 == 0) { + ret = -EINVAL; + goto out_unlock; + } + + txbuf = memdup_user(buf, n); + if (IS_ERR(txbuf)) { + ret = PTR_ERR(txbuf); + goto out_unlock; + } + } + + for (i = 0; i < count; i++) { + if (txbuf[i] > IR_MAX_DURATION / 1000 - duration || !txbuf[i]) { + ret = -EINVAL; + goto out_kfree; + } + + duration += txbuf[i]; + } + + start = ktime_get(); + + ret = dev->tx_ir(dev, txbuf, count); + if (ret < 0) + goto out_kfree; + + kfree(txbuf); + kfree(raw); + mutex_unlock(&dev->lock); + + /* + * The lircd gap calculation expects the write function to + * wait for the actual IR signal to be transmitted before + * returning. + */ + towait = ktime_us_delta(ktime_add_us(start, duration), + ktime_get()); + if (towait > 0) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(usecs_to_jiffies(towait)); + } + + return n; +out_kfree: + kfree(txbuf); +out_kfree_raw: + kfree(raw); +out_unlock: + mutex_unlock(&dev->lock); return ret; } -EXPORT_SYMBOL(lirc_dev_fop_poll); -long lirc_dev_fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +static long ir_lirc_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { - __u32 mode; - int result = 0; - struct irctl *ir = irctls[iminor(file_inode(file))]; + struct lirc_fh *fh = file->private_data; + struct rc_dev *dev = fh->rc; + u32 __user *argp = (u32 __user *)(arg); + u32 val = 0; + int ret; - if (!ir) { - pr_err("no irctl found!\n"); - return -ENODEV; + if (_IOC_DIR(cmd) & _IOC_WRITE) { + ret = get_user(val, argp); + if (ret) + return ret; } - dev_dbg(ir->d.dev, LOGHEAD "ioctl called (0x%x)\n", - ir->d.name, ir->d.minor, cmd); + ret = mutex_lock_interruptible(&dev->lock); + if (ret) + return ret; - if (ir->d.minor == NOPLUG || !ir->attached) { - dev_err(ir->d.dev, LOGHEAD "ioctl result = -ENODEV\n", - ir->d.name, ir->d.minor); - return -ENODEV; + if (!dev->registered) { + ret = -ENODEV; + goto out; } - mutex_lock(&ir->irctl_lock); - switch (cmd) { case LIRC_GET_FEATURES: - result = put_user(ir->d.features, (__u32 __user *)arg); + if (dev->driver_type == RC_DRIVER_SCANCODE) + val |= LIRC_CAN_REC_SCANCODE; + + if (dev->driver_type == RC_DRIVER_IR_RAW) { + val |= LIRC_CAN_REC_MODE2; + if (dev->rx_resolution) + val |= LIRC_CAN_GET_REC_RESOLUTION; + } + + if (dev->tx_ir) { + val |= LIRC_CAN_SEND_PULSE; + if (dev->s_tx_mask) + val |= LIRC_CAN_SET_TRANSMITTER_MASK; + if (dev->s_tx_carrier) + val |= LIRC_CAN_SET_SEND_CARRIER; + if (dev->s_tx_duty_cycle) + val |= LIRC_CAN_SET_SEND_DUTY_CYCLE; + } + + if (dev->s_rx_carrier_range) + val |= LIRC_CAN_SET_REC_CARRIER | + LIRC_CAN_SET_REC_CARRIER_RANGE; + + if (dev->s_learning_mode) + val |= LIRC_CAN_USE_WIDEBAND_RECEIVER; + + if (dev->s_carrier_report) + val |= LIRC_CAN_MEASURE_CARRIER; + + if (dev->max_timeout) + val |= LIRC_CAN_SET_REC_TIMEOUT; + break; + + /* mode support */ case LIRC_GET_REC_MODE: - if (!LIRC_CAN_REC(ir->d.features)) { - result = -ENOTTY; - break; - } - - result = put_user(LIRC_REC2MODE - (ir->d.features & LIRC_CAN_REC_MASK), - (__u32 __user *)arg); + if (dev->driver_type == RC_DRIVER_IR_RAW_TX) + ret = -ENOTTY; + else + val = fh->rec_mode; break; + case LIRC_SET_REC_MODE: - if (!LIRC_CAN_REC(ir->d.features)) { - result = -ENOTTY; + switch (dev->driver_type) { + case RC_DRIVER_IR_RAW_TX: + ret = -ENOTTY; + break; + case RC_DRIVER_SCANCODE: + if (val != LIRC_MODE_SCANCODE) + ret = -EINVAL; + break; + case RC_DRIVER_IR_RAW: + if (!(val == LIRC_MODE_MODE2 || + val == LIRC_MODE_SCANCODE)) + ret = -EINVAL; break; } - result = get_user(mode, (__u32 __user *)arg); - if (!result && !(LIRC_MODE2REC(mode) & ir->d.features)) - result = -EINVAL; - /* - * FIXME: We should actually set the mode somehow but - * for now, lirc_serial doesn't support mode changing either - */ + if (!ret) + fh->rec_mode = val; break; - case LIRC_GET_LENGTH: - result = put_user(ir->d.code_length, (__u32 __user *)arg); + + case LIRC_GET_SEND_MODE: + if (!dev->tx_ir) + ret = -ENOTTY; + else + val = fh->send_mode; break; + + case LIRC_SET_SEND_MODE: + if (!dev->tx_ir) + ret = -ENOTTY; + else if (!(val == LIRC_MODE_PULSE || val == LIRC_MODE_SCANCODE)) + ret = -EINVAL; + else + fh->send_mode = val; + break; + + /* TX settings */ + case LIRC_SET_TRANSMITTER_MASK: + if (!dev->s_tx_mask) + ret = -ENOTTY; + else + ret = dev->s_tx_mask(dev, val); + break; + + case LIRC_SET_SEND_CARRIER: + if (!dev->s_tx_carrier) + ret = -ENOTTY; + else + ret = dev->s_tx_carrier(dev, val); + break; + + case LIRC_SET_SEND_DUTY_CYCLE: + if (!dev->s_tx_duty_cycle) + ret = -ENOTTY; + else if (val <= 0 || val >= 100) + ret = -EINVAL; + else + ret = dev->s_tx_duty_cycle(dev, val); + break; + + /* RX settings */ + case LIRC_SET_REC_CARRIER: + if (!dev->s_rx_carrier_range) + ret = -ENOTTY; + else if (val <= 0) + ret = -EINVAL; + else + ret = dev->s_rx_carrier_range(dev, fh->carrier_low, + val); + break; + + case LIRC_SET_REC_CARRIER_RANGE: + if (!dev->s_rx_carrier_range) + ret = -ENOTTY; + else if (val <= 0) + ret = -EINVAL; + else + fh->carrier_low = val; + break; + + case LIRC_GET_REC_RESOLUTION: + if (!dev->rx_resolution) + ret = -ENOTTY; + else + val = dev->rx_resolution / 1000; + break; + + case LIRC_SET_WIDEBAND_RECEIVER: + if (!dev->s_learning_mode) + ret = -ENOTTY; + else + ret = dev->s_learning_mode(dev, !!val); + break; + + case LIRC_SET_MEASURE_CARRIER_MODE: + if (!dev->s_carrier_report) + ret = -ENOTTY; + else + ret = dev->s_carrier_report(dev, !!val); + break; + + /* Generic timeout support */ case LIRC_GET_MIN_TIMEOUT: - if (!(ir->d.features & LIRC_CAN_SET_REC_TIMEOUT) || - ir->d.min_timeout == 0) { - result = -ENOTTY; - break; - } - - result = put_user(ir->d.min_timeout, (__u32 __user *)arg); + if (!dev->max_timeout) + ret = -ENOTTY; + else + val = DIV_ROUND_UP(dev->min_timeout, 1000); break; + case LIRC_GET_MAX_TIMEOUT: - if (!(ir->d.features & LIRC_CAN_SET_REC_TIMEOUT) || - ir->d.max_timeout == 0) { - result = -ENOTTY; - break; - } - - result = put_user(ir->d.max_timeout, (__u32 __user *)arg); + if (!dev->max_timeout) + ret = -ENOTTY; + else + val = dev->max_timeout / 1000; break; + + case LIRC_SET_REC_TIMEOUT: + if (!dev->max_timeout) { + ret = -ENOTTY; + } else if (val > U32_MAX / 1000) { + /* Check for multiply overflow */ + ret = -EINVAL; + } else { + u32 tmp = val * 1000; + + if (tmp < dev->min_timeout || tmp > dev->max_timeout) + ret = -EINVAL; + else if (dev->s_timeout) + ret = dev->s_timeout(dev, tmp); + else + dev->timeout = tmp; + } + break; + + case LIRC_GET_REC_TIMEOUT: + if (!dev->timeout) + ret = -ENOTTY; + else + val = DIV_ROUND_UP(dev->timeout, 1000); + break; + + case LIRC_SET_REC_TIMEOUT_REPORTS: + if (dev->driver_type != RC_DRIVER_IR_RAW) + ret = -ENOTTY; + else + fh->send_timeout_reports = !!val; + break; + default: - result = -ENOTTY; + ret = -ENOTTY; } - mutex_unlock(&ir->irctl_lock); + if (!ret && _IOC_DIR(cmd) & _IOC_READ) + ret = put_user(val, argp); - return result; +out: + mutex_unlock(&dev->lock); + return ret; } -EXPORT_SYMBOL(lirc_dev_fop_ioctl); -ssize_t lirc_dev_fop_read(struct file *file, - char __user *buffer, - size_t length, - loff_t *ppos) +static __poll_t ir_lirc_poll(struct file *file, struct poll_table_struct *wait) { - struct irctl *ir = irctls[iminor(file_inode(file))]; - unsigned char *buf; - int ret = 0, written = 0; - DECLARE_WAITQUEUE(wait, current); + struct lirc_fh *fh = file->private_data; + struct rc_dev *rcdev = fh->rc; + __poll_t events = 0; - if (!ir) { - pr_err("called with invalid irctl\n"); - return -ENODEV; + poll_wait(file, &fh->wait_poll, wait); + + if (!rcdev->registered) { + events = EPOLLHUP | EPOLLERR; + } else if (rcdev->driver_type != RC_DRIVER_IR_RAW_TX) { + if (fh->rec_mode == LIRC_MODE_SCANCODE && + !kfifo_is_empty(&fh->scancodes)) + events = EPOLLIN | EPOLLRDNORM; + + if (fh->rec_mode == LIRC_MODE_MODE2 && + !kfifo_is_empty(&fh->rawir)) + events = EPOLLIN | EPOLLRDNORM; } - if (!LIRC_CAN_REC(ir->d.features)) + return events; +} + +static ssize_t ir_lirc_read_mode2(struct file *file, char __user *buffer, + size_t length) +{ + struct lirc_fh *fh = file->private_data; + struct rc_dev *rcdev = fh->rc; + unsigned int copied; + int ret; + + if (length < sizeof(unsigned int) || length % sizeof(unsigned int)) return -EINVAL; - dev_dbg(ir->d.dev, LOGHEAD "read called\n", ir->d.name, ir->d.minor); + do { + if (kfifo_is_empty(&fh->rawir)) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; - buf = kzalloc(ir->chunk_size, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - if (mutex_lock_interruptible(&ir->irctl_lock)) { - ret = -ERESTARTSYS; - goto out_unlocked; - } - if (!ir->attached) { - ret = -ENODEV; - goto out_locked; - } - - if (length % ir->chunk_size) { - ret = -EINVAL; - goto out_locked; - } - - /* - * we add ourselves to the task queue before buffer check - * to avoid losing scan code (in case when queue is awaken somewhere - * between while condition checking and scheduling) - */ - add_wait_queue(&ir->buf->wait_poll, &wait); - - /* - * while we didn't provide 'length' bytes, device is opened in blocking - * mode and 'copy_to_user' is happy, wait for data. - */ - while (written < length && ret == 0) { - if (lirc_buffer_empty(ir->buf)) { - /* According to the read(2) man page, 'written' can be - * returned as less than 'length', instead of blocking - * again, returning -EWOULDBLOCK, or returning - * -ERESTARTSYS - */ - if (written) - break; - if (file->f_flags & O_NONBLOCK) { - ret = -EWOULDBLOCK; - break; - } - if (signal_pending(current)) { - ret = -ERESTARTSYS; - break; - } - - mutex_unlock(&ir->irctl_lock); - set_current_state(TASK_INTERRUPTIBLE); - schedule(); - set_current_state(TASK_RUNNING); - - if (mutex_lock_interruptible(&ir->irctl_lock)) { - ret = -ERESTARTSYS; - remove_wait_queue(&ir->buf->wait_poll, &wait); - goto out_unlocked; - } - - if (!ir->attached) { - ret = -ENODEV; - goto out_locked; - } - } else { - lirc_buffer_read(ir->buf, buf); - ret = copy_to_user((void __user *)buffer+written, buf, - ir->buf->chunk_size); - if (!ret) - written += ir->buf->chunk_size; - else - ret = -EFAULT; + ret = wait_event_interruptible(fh->wait_poll, + !kfifo_is_empty(&fh->rawir) || + !rcdev->registered); + if (ret) + return ret; } + + if (!rcdev->registered) + return -ENODEV; + + ret = mutex_lock_interruptible(&rcdev->lock); + if (ret) + return ret; + ret = kfifo_to_user(&fh->rawir, buffer, length, &copied); + mutex_unlock(&rcdev->lock); + if (ret) + return ret; + } while (copied == 0); + + return copied; +} + +static ssize_t ir_lirc_read_scancode(struct file *file, char __user *buffer, + size_t length) +{ + struct lirc_fh *fh = file->private_data; + struct rc_dev *rcdev = fh->rc; + unsigned int copied; + int ret; + + if (length < sizeof(struct lirc_scancode) || + length % sizeof(struct lirc_scancode)) + return -EINVAL; + + do { + if (kfifo_is_empty(&fh->scancodes)) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible(fh->wait_poll, + !kfifo_is_empty(&fh->scancodes) || + !rcdev->registered); + if (ret) + return ret; + } + + if (!rcdev->registered) + return -ENODEV; + + ret = mutex_lock_interruptible(&rcdev->lock); + if (ret) + return ret; + ret = kfifo_to_user(&fh->scancodes, buffer, length, &copied); + mutex_unlock(&rcdev->lock); + if (ret) + return ret; + } while (copied == 0); + + return copied; +} + +static ssize_t ir_lirc_read(struct file *file, char __user *buffer, + size_t length, loff_t *ppos) +{ + struct lirc_fh *fh = file->private_data; + struct rc_dev *rcdev = fh->rc; + + if (rcdev->driver_type == RC_DRIVER_IR_RAW_TX) + return -EINVAL; + + if (!rcdev->registered) + return -ENODEV; + + if (fh->rec_mode == LIRC_MODE_MODE2) + return ir_lirc_read_mode2(file, buffer, length); + else /* LIRC_MODE_SCANCODE */ + return ir_lirc_read_scancode(file, buffer, length); +} + +static const struct file_operations lirc_fops = { + .owner = THIS_MODULE, + .write = ir_lirc_transmit_ir, + .unlocked_ioctl = ir_lirc_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ir_lirc_ioctl, +#endif + .read = ir_lirc_read, + .poll = ir_lirc_poll, + .open = ir_lirc_open, + .release = ir_lirc_close, + .llseek = no_llseek, +}; + +static void lirc_release_device(struct device *ld) +{ + struct rc_dev *rcdev = container_of(ld, struct rc_dev, lirc_dev); + + put_device(&rcdev->dev); +} + +int ir_lirc_register(struct rc_dev *dev) +{ + const char *rx_type, *tx_type; + int err, minor; + + minor = ida_simple_get(&lirc_ida, 0, RC_DEV_MAX, GFP_KERNEL); + if (minor < 0) + return minor; + + device_initialize(&dev->lirc_dev); + dev->lirc_dev.class = lirc_class; + dev->lirc_dev.parent = &dev->dev; + dev->lirc_dev.release = lirc_release_device; + dev->lirc_dev.devt = MKDEV(MAJOR(lirc_base_dev), minor); + dev_set_name(&dev->lirc_dev, "lirc%d", minor); + + INIT_LIST_HEAD(&dev->lirc_fh); + spin_lock_init(&dev->lirc_fh_lock); + + cdev_init(&dev->lirc_cdev, &lirc_fops); + + err = cdev_device_add(&dev->lirc_cdev, &dev->lirc_dev); + if (err) + goto out_ida; + + get_device(&dev->dev); + + switch (dev->driver_type) { + case RC_DRIVER_SCANCODE: + rx_type = "scancode"; + break; + case RC_DRIVER_IR_RAW: + rx_type = "raw IR"; + break; + default: + rx_type = "no"; + break; } - remove_wait_queue(&ir->buf->wait_poll, &wait); + if (dev->tx_ir) + tx_type = "raw IR"; + else + tx_type = "no"; -out_locked: - mutex_unlock(&ir->irctl_lock); + dev_info(&dev->dev, "lirc_dev: driver %s registered at minor = %d, %s receiver, %s transmitter", + dev->driver_name, minor, rx_type, tx_type); -out_unlocked: - kfree(buf); + return 0; - return ret ? ret : written; +out_ida: + ida_simple_remove(&lirc_ida, minor); + return err; } -EXPORT_SYMBOL(lirc_dev_fop_read); -void *lirc_get_pdata(struct file *file) +void ir_lirc_unregister(struct rc_dev *dev) { - return irctls[iminor(file_inode(file))]->d.data; + unsigned long flags; + struct lirc_fh *fh; + + dev_dbg(&dev->dev, "lirc_dev: driver %s unregistered from minor = %d\n", + dev->driver_name, MINOR(dev->lirc_dev.devt)); + + spin_lock_irqsave(&dev->lirc_fh_lock, flags); + list_for_each_entry(fh, &dev->lirc_fh, list) + wake_up_poll(&fh->wait_poll, EPOLLHUP | EPOLLERR); + spin_unlock_irqrestore(&dev->lirc_fh_lock, flags); + + cdev_device_del(&dev->lirc_cdev, &dev->lirc_dev); + ida_simple_remove(&lirc_ida, MINOR(dev->lirc_dev.devt)); } -EXPORT_SYMBOL(lirc_get_pdata); - -static int __init lirc_dev_init(void) +int __init lirc_dev_init(void) { int retval; @@ -592,7 +828,7 @@ static int __init lirc_dev_init(void) return PTR_ERR(lirc_class); } - retval = alloc_chrdev_region(&lirc_base_dev, 0, MAX_IRCTL_DEVICES, + retval = alloc_chrdev_region(&lirc_base_dev, 0, RC_DEV_MAX, "BaseRemoteCtl"); if (retval) { class_destroy(lirc_class); @@ -600,22 +836,39 @@ static int __init lirc_dev_init(void) return retval; } - pr_info("IR Remote Control driver registered, major %d\n", - MAJOR(lirc_base_dev)); + pr_debug("IR Remote Control driver registered, major %d\n", + MAJOR(lirc_base_dev)); return 0; } -static void __exit lirc_dev_exit(void) +void __exit lirc_dev_exit(void) { class_destroy(lirc_class); - unregister_chrdev_region(lirc_base_dev, MAX_IRCTL_DEVICES); - pr_info("module unloaded\n"); + unregister_chrdev_region(lirc_base_dev, RC_DEV_MAX); } -module_init(lirc_dev_init); -module_exit(lirc_dev_exit); +struct rc_dev *rc_dev_get_from_fd(int fd) +{ + struct fd f = fdget(fd); + struct lirc_fh *fh; + struct rc_dev *dev; -MODULE_DESCRIPTION("LIRC base driver module"); -MODULE_AUTHOR("Artur Lipowski"); -MODULE_LICENSE("GPL"); + if (!f.file) + return ERR_PTR(-EBADF); + + if (f.file->f_op != &lirc_fops) { + fdput(f); + return ERR_PTR(-EINVAL); + } + + fh = f.file->private_data; + dev = fh->rc; + + get_device(&dev->dev); + fdput(f); + + return dev; +} + +MODULE_ALIAS("lirc_dev"); diff --git a/drivers/media/rc/rc-core-priv.h b/drivers/media/rc/rc-core-priv.h index 8cdf59ecaae9..e847bdad5c51 100644 --- a/drivers/media/rc/rc-core-priv.h +++ b/drivers/media/rc/rc-core-priv.h @@ -3,7 +3,6 @@ * Remote Controller core raw events header * * Copyright (C) 2010 by Mauro Carvalho Chehab - * Copyright (C) 2020 XiaoMi, Inc. */ #ifndef _RC_CORE_PRIV @@ -298,7 +297,6 @@ void ir_raw_init(void); * lirc interface */ #ifdef CONFIG_LIRC -/** int lirc_dev_init(void); void lirc_dev_exit(void); void ir_lirc_raw_event(struct rc_dev *dev, struct ir_raw_event ev); @@ -307,7 +305,6 @@ int ir_lirc_register(struct rc_dev *dev); void ir_lirc_unregister(struct rc_dev *dev); struct rc_dev *rc_dev_get_from_fd(int fd); #else -**/ static inline int lirc_dev_init(void) { return 0; } static inline void lirc_dev_exit(void) {} static inline void ir_lirc_raw_event(struct rc_dev *dev, diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c index eb429089dffc..cf3df733d960 100644 --- a/drivers/media/rc/rc-main.c +++ b/drivers/media/rc/rc-main.c @@ -2,7 +2,6 @@ // rc-main.c - Remote Controller core module // // Copyright (C) 2009-2010 by Mauro Carvalho Chehab -// Copyright (C) 2020 XiaoMi, Inc. #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -947,7 +946,6 @@ int rc_open(struct rc_dev *rdev) return rval; } -EXPORT_SYMBOL_GPL(rc_open); static int ir_open(struct input_dev *idev) { @@ -967,7 +965,6 @@ void rc_close(struct rc_dev *rdev) mutex_unlock(&rdev->lock); } } -EXPORT_SYMBOL_GPL(rc_close); static void ir_close(struct input_dev *idev) { diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index c6fd78ee166a..3bbf362a4626 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -577,18 +577,6 @@ config OKL4_RINGBUF Say Y here if you want to test communication between OKL4 guests using virtual interrupts and shared memory. -config SIMTRAY_STATUS - tristate "Xiaomi SIM tray status" - default n - help - Say 'y' here to support SIM tray GPIO detection - -config DPDT_STATUS - tristate "Xiaomi dpdt status" - default n - help - Say 'y' here to support dpdt GPIO detection - config TEST_IRQ_REQUESTER tristate "Receive interrupt notifications in user-space" depends on DEBUG_FS @@ -649,11 +637,7 @@ source "drivers/misc/cxl/Kconfig" source "drivers/misc/ocxl/Kconfig" source "drivers/misc/cardreader/Kconfig" source "drivers/misc/fpr_FingerprintCard/Kconfig" -source "drivers/misc/goodix/Kconfig" -source "drivers/misc/focaltech/Kconfig" -source "drivers/misc/cdfingerfp/Kconfig" -source "drivers/misc/xiaomi_fs/Kconfig" -source "drivers/misc/hqsysfs/Kconfig" +source "drivers/misc/qrc/Kconfig" endmenu config OKL4_USER_VIPC @@ -674,3 +658,5 @@ config OKL4_LINK_SHBUF transport. This driver presents the link to Linux as a character device which can be written to or read from to access the shared memory. An ioctl on the device is used to send a virtual interrupt to the partner cell. + +source "drivers/misc/aw862xx_haptic/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 570d5814a027..1a25bf1dfe1b 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -68,10 +68,6 @@ obj-$(CONFIG_QPNP_MISC) += qpnp-misc.o obj-$(CONFIG_OKL4_USER_VIRQ) += okl4-virq.o obj-$(CONFIG_OKL4_RINGBUF) += okl4-ringbuf.o obj-$(CONFIG_TEST_IRQ_REQUESTER) += irq_requester.o -obj-$(CONFIG_SIMTRAY_STATUS) += simtray.o -obj-$(CONFIG_DPDT_STATUS) += dpdt.o -obj-$(CONFIG_MI_FS) +=xiaomi_fs/ -obj-$(CONFIG_HQ_SYSFS_SUPPORT) += hqsysfs/ obj-$(CONFIG_OKL4_USER_VIPC) += okl4-vipc.o obj-$(CONFIG_OKL4_GUEST) += okl4-panic.o @@ -80,6 +76,5 @@ obj-$(CONFIG_WIGIG_SENSING_SPI) += wigig_sensing.o obj-$(CONFIG_QTI_MAXIM_FAN_CONTROLLER) += max31760.o obj-$(CONFIG_QTI_XR_SMRTVWR_MISC) += qxr-stdalonevwr.o obj-$(CONFIG_FPR_FPC) += fpr_FingerprintCard/ -obj-y += goodix/ -obj-y += focaltech/ -obj-y += cdfingerfp/ +obj-y += qrc/ +obj-$(CONFIG_AW862XX_HAPTIC) += aw862xx_haptic/ diff --git a/drivers/misc/aw862xx_haptic/Kconfig b/drivers/misc/aw862xx_haptic/Kconfig new file mode 100644 index 000000000000..3ab4ef52f193 --- /dev/null +++ b/drivers/misc/aw862xx_haptic/Kconfig @@ -0,0 +1,16 @@ +# +# Mediatek AW862XX Driver +# + +config AW862XX_HAPTIC + tristate "Awinic AW862XX haptic driver" + depends on I2C + help + Say Y here if you have Awinic AW862XX haptic controller + chip in your system. + If unsure, say N. + To compile this driver as a module, choose M here: the + module will be called mpr121_touchkey. + + + diff --git a/drivers/misc/aw862xx_haptic/Makefile b/drivers/misc/aw862xx_haptic/Makefile new file mode 100644 index 000000000000..c940789adcbc --- /dev/null +++ b/drivers/misc/aw862xx_haptic/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_AW862XX_HAPTIC) += aw8624.o aw8622x.o haptic.o diff --git a/drivers/misc/aw862xx_haptic/aw8622x.c b/drivers/misc/aw862xx_haptic/aw8622x.c new file mode 100644 index 000000000000..01994d5b409f --- /dev/null +++ b/drivers/misc/aw862xx_haptic/aw8622x.c @@ -0,0 +1,3977 @@ +/* + * aw8622x.c + * + * Copyright (c) 2020 AWINIC Technology CO., LTD + * + * vun Author: Ray + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "aw8622x_reg.h" +#include "aw8622x.h" +#include "haptic.h" +/****************************************************** + * + * Value + * + ******************************************************/ +static char *aw8622x_ram_name = "aw8622x_haptic.bin"; +static char aw8622x_rtp_name[][AW8622X_RTP_NAME_MAX] = { + {"aw8622x_osc_rtp_12K_10s.bin"}, + {"aw8622x_rtp.bin"}, + {"aw8622x_rtp_lighthouse.bin"}, + {"aw8622x_rtp_silk.bin"}, +}; + +struct pm_qos_request aw8622x_pm_qos_req_vb; + +/****************************************************** +* +* functions +* +******************************************************/ + +static int aw8622x_analyse_duration_range(struct aw8622x *aw8622x); + + /****************************************************** + * + * aw8622x i2c write/read + * + ******************************************************/ +static int aw8622x_i2c_write(struct aw8622x *aw8622x, + unsigned char reg_addr, unsigned char reg_data) +{ + int ret = -1; + unsigned char cnt = 0; + + while (cnt < AW8622X_I2C_RETRIES) { + ret = + i2c_smbus_write_byte_data(aw8622x->i2c, reg_addr, reg_data); + if (ret < 0) { + aw_dev_err(aw8622x->dev, "%s: i2c_write addr=0x%02X, data=0x%02X, cnt=%d, error=%d\n", + __func__, reg_addr, reg_data, cnt, ret); + } else { + break; + } + cnt++; + usleep_range(AW8622X_I2C_RETRY_DELAY * 1000, + AW8622X_I2C_RETRY_DELAY * 1000 + 500); + } + return ret; +} + +int aw8622x_i2c_read(struct aw8622x *aw8622x, + unsigned char reg_addr, unsigned char *reg_data) +{ + int ret = -1; + unsigned char cnt = 0; + + while (cnt < AW8622X_I2C_RETRIES) { + ret = i2c_smbus_read_byte_data(aw8622x->i2c, reg_addr); + if (ret < 0) { + aw_dev_err(aw8622x->dev, + "%s: i2c_read addr=0x%02X, cnt=%d error=%d\n", + __func__, reg_addr, cnt, ret); + } else { + *reg_data = ret; + break; + } + cnt++; + usleep_range(AW8622X_I2C_RETRY_DELAY * 1000, + AW8622X_I2C_RETRY_DELAY * 1000 + 500); + } + return ret; +} + +int aw8622x_i2c_writes(struct aw8622x *aw8622x, + unsigned char reg_addr, unsigned char *buf, + unsigned int len) +{ + int ret = -1; + unsigned char *data = NULL; + + data = kmalloc(len + 1, GFP_KERNEL); + if (data == NULL) { + aw_dev_err(aw8622x->dev, + "%s: can not allocate memory\n", __func__); + return -ENOMEM; + } + data[0] = reg_addr; + memcpy(&data[1], buf, len); + ret = i2c_master_send(aw8622x->i2c, data, len + 1); + if (ret < 0) + aw_dev_err(aw8622x->dev, + "%s: i2c master send error\n", __func__); + kfree(data); + return ret; +} + +static int aw8622x_i2c_write_bits(struct aw8622x *aw8622x, + unsigned char reg_addr, unsigned int mask, + unsigned char reg_data) +{ + int ret = -1; + unsigned char reg_val = 0; + + ret = aw8622x_i2c_read(aw8622x, reg_addr, ®_val); + if (ret < 0) { + aw_dev_err(aw8622x->dev, + "%s: i2c read error, ret=%d\n", __func__, ret); + return ret; + } + reg_val &= mask; + reg_val |= reg_data; + ret = aw8622x_i2c_write(aw8622x, reg_addr, reg_val); + if (ret < 0) { + aw_dev_err(aw8622x->dev, + "%s: i2c write error, ret=%d\n", __func__, ret); + return ret; + } + return 0; +} + +unsigned char aw8622x_haptic_rtp_get_fifo_afs(struct aw8622x *aw8622x) +{ + unsigned char ret = 0; + unsigned char reg_val = 0; + + aw8622x_i2c_read(aw8622x, AW8622X_REG_SYSST, ®_val); + reg_val &= AW8622X_BIT_SYSST_FF_AFS; + ret = reg_val >> 3; + return ret; +} + +/***************************************************** + * + * rtp + * + *****************************************************/ +void aw8622x_haptic_set_rtp_aei(struct aw8622x *aw8622x, bool flag) +{ + if (flag) { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSINTM, + AW8622X_BIT_SYSINTM_FF_AEM_MASK, + AW8622X_BIT_SYSINTM_FF_AEM_ON); + } else { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSINTM, + AW8622X_BIT_SYSINTM_FF_AEM_MASK, + AW8622X_BIT_SYSINTM_FF_AEM_OFF); + } +} + +static int aw8622x_analyse_duration_range(struct aw8622x *aw8622x) +{ + int i = 0; + int ret = 0; + int len = 0; + int *duration_time = NULL; + + len = ARRAY_SIZE(aw8622x->dts_info.duration_time); + duration_time = aw8622x->dts_info.duration_time; + if (len < 2) { + aw_dev_err(aw8622x->dev, "%s: duration time range error\n", + __func__); + return -ERANGE; + } + for (i = (len - 1); i > 0; i--) { + if (duration_time[i] > duration_time[i-1]) + continue; + else + break; + + } + if (i > 0) { + aw_dev_err(aw8622x->dev, "%s: duration time range error\n", + __func__); + ret = -ERANGE; + } + return ret; +} + +static int +aw8622x_analyse_duration_array_size(struct aw8622x *aw8622x, struct device_node *np) +{ + int ret = 0; + + ret = of_property_count_elems_of_size(np, "aw8622x_vib_duration_time", 4); + if (ret < 0) { + aw8622x->duration_time_flag = -1; + aw_dev_info(aw8622x->dev, + "%s vib_duration_time not found\n", __func__); + return ret; + } + aw8622x->duration_time_size = ret; + if (aw8622x->duration_time_size > 3) { + aw8622x->duration_time_flag = -1; + aw_dev_info(aw8622x->dev, + "%s vib_duration_time error, array size = %d\n", + __func__, aw8622x->duration_time_size); + return -ERANGE; + } + return 0; +} + +/***************************************************** + * + * device tree + * + *****************************************************/ +int aw8622x_parse_dt(struct aw8622x *aw8622x, struct device *dev, + struct device_node *np) +{ + unsigned int val = 0; + unsigned int prctmode_temp[3]; + unsigned int sine_array_temp[4]; + unsigned int trig_config_temp[21]; + unsigned int duration_time[3]; + int ret = 0; + + val = of_property_read_u32(np, + "aw8622x_vib_mode", + &aw8622x->dts_info.mode); + if (val != 0) + aw_dev_info(aw8622x->dev, + "%s aw8622x_vib_mode not found\n", + __func__); + val = of_property_read_u32(np, + "aw8622x_vib_f0_pre", + &aw8622x->dts_info.f0_ref); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_f0_ref not found\n", + __func__); + val = + of_property_read_u32(np, "aw8622x_vib_f0_cali_percen", + &aw8622x->dts_info.f0_cali_percent); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_f0_cali_percent not found\n", + __func__); + + val = of_property_read_u32(np, "aw8622x_vib_cont_drv1_lvl", + &aw8622x->dts_info.cont_drv1_lvl_dt); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_cont_drv1_lvl not found\n", + __func__); + val = + of_property_read_u32(np, "aw8622x_vib_cont_drv2_lvl", + &aw8622x->dts_info.cont_drv2_lvl_dt); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_cont_drv2_lvl not found\n", + __func__); + val = + of_property_read_u32(np, "aw8622x_vib_cont_drv1_time", + &aw8622x->dts_info.cont_drv1_time_dt); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_cont_drv1_time not found\n", + __func__); + val = + of_property_read_u32(np, "aw8622x_vib_cont_drv2_time", + &aw8622x->dts_info.cont_drv2_time_dt); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_cont_drv2_time not found\n", + __func__); + val = + of_property_read_u32(np, "aw8622x_vib_cont_drv_width", + &aw8622x->dts_info.cont_drv_width); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_cont_drv_width not found\n", + __func__); + val = + of_property_read_u32(np, "aw8622x_vib_cont_wait_num", + &aw8622x->dts_info.cont_wait_num_dt); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_cont_wait_num not found\n", + __func__); + val = + of_property_read_u32(np, "aw8622x_vib_cont_brk_gain", + &aw8622x->dts_info.cont_brk_gain); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_cont_brk_gain not found\n", + __func__); + val = + of_property_read_u32(np, "aw8622x_vib_cont_tset", + &aw8622x->dts_info.cont_tset); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_cont_tset not found\n", + __func__); + val = + of_property_read_u32(np, "aw8622x_vib_cont_bemf_set", + &aw8622x->dts_info.cont_bemf_set); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_cont_bemf_set not found\n", + __func__); + val = + of_property_read_u32(np, "aw8622x_vib_d2s_gain", + &aw8622x->dts_info.d2s_gain); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_d2s_gain not found\n", + __func__); + val = + of_property_read_u32(np, "aw8622x_vib_cont_brk_time", + &aw8622x->dts_info.cont_brk_time_dt); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_cont_brk_time not found\n", + __func__); + val = + of_property_read_u32(np, "aw8622x_vib_cont_track_margin", + &aw8622x->dts_info.cont_track_margin); + if (val != 0) + aw_dev_info(aw8622x->dev, + "%s vib_cont_track_margin not found\n", __func__); + + val = of_property_read_u32_array(np, "aw8622x_vib_prctmode", + prctmode_temp, + ARRAY_SIZE(prctmode_temp)); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_prctmode not found\n", + __func__); + memcpy(aw8622x->dts_info.prctmode, prctmode_temp, + sizeof(prctmode_temp)); + val = of_property_read_u32_array(np, + "aw8622x_vib_sine_array", + sine_array_temp, + ARRAY_SIZE(sine_array_temp)); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_sine_array not found\n", + __func__); + memcpy(aw8622x->dts_info.sine_array, sine_array_temp, + sizeof(sine_array_temp)); + val = + of_property_read_u32_array(np, + "aw8622x_vib_trig_config", + trig_config_temp, + ARRAY_SIZE(trig_config_temp)); + if (val != 0) + aw_dev_info(aw8622x->dev, "%s vib_trig_config not found\n", + __func__); + memcpy(aw8622x->dts_info.trig_config, trig_config_temp, + sizeof(trig_config_temp)); + val = of_property_read_u32_array(np, "aw8622x_vib_duration_time", + duration_time, ARRAY_SIZE(duration_time)); + if (val != 0) + aw_dev_info(aw8622x->dev, + "%s vib_duration_time not found\n", __func__); + ret = aw8622x_analyse_duration_array_size(aw8622x, np); + if (!ret) + memcpy(aw8622x->dts_info.duration_time, + duration_time, sizeof(duration_time)); + aw8622x->dts_info.is_enabled_auto_bst = + of_property_read_bool(np, + "aw8622x_vib_is_enabled_auto_bst"); + aw_dev_info(aw8622x->dev, + "%s aw8622x->info.is_enabled_auto_bst = %d\n", __func__, + aw8622x->dts_info.is_enabled_auto_bst); + + return 0; +} + +static void aw8622x_haptic_upload_lra(struct aw8622x *aw8622x, + unsigned int flag) +{ + switch (flag) { + case WRITE_ZERO: + aw_dev_info(aw8622x->dev, "%s write zero to trim_lra!\n", + __func__); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_TRIMCFG3, + AW8622X_BIT_TRIMCFG3_TRIM_LRA_MASK, + 0x00); + break; + case F0_CALI: + aw_dev_info(aw8622x->dev, "%s write f0_cali_data to trim_lra = 0x%02X\n", + __func__, aw8622x->f0_cali_data); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_TRIMCFG3, + AW8622X_BIT_TRIMCFG3_TRIM_LRA_MASK, + (char)aw8622x->f0_cali_data); + break; + case OSC_CALI: + aw_dev_info(aw8622x->dev, "%s write osc_cali_data to trim_lra = 0x%02X\n", + __func__, aw8622x->osc_cali_data); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_TRIMCFG3, + AW8622X_BIT_TRIMCFG3_TRIM_LRA_MASK, + (char)aw8622x->osc_cali_data); + break; + default: + break; + } +} + + + +/***************************************************** + * + * sram size, normally 3k(2k fifo, 1k ram) + * + *****************************************************/ +static int aw8622x_sram_size(struct aw8622x *aw8622x, int size_flag) +{ + if (size_flag == AW8622X_HAPTIC_SRAM_2K) { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_RTPCFG1, + AW8622X_BIT_RTPCFG1_SRAM_SIZE_2K_MASK, + AW8622X_BIT_RTPCFG1_SRAM_SIZE_2K_EN); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_RTPCFG1, + AW8622X_BIT_RTPCFG1_SRAM_SIZE_1K_MASK, + AW8622X_BIT_RTPCFG1_SRAM_SIZE_1K_DIS); + } else if (size_flag == AW8622X_HAPTIC_SRAM_1K) { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_RTPCFG1, + AW8622X_BIT_RTPCFG1_SRAM_SIZE_2K_MASK, + AW8622X_BIT_RTPCFG1_SRAM_SIZE_2K_DIS); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_RTPCFG1, + AW8622X_BIT_RTPCFG1_SRAM_SIZE_1K_MASK, + AW8622X_BIT_RTPCFG1_SRAM_SIZE_1K_EN); + } else if (size_flag == AW8622X_HAPTIC_SRAM_3K) { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_RTPCFG1, + AW8622X_BIT_RTPCFG1_SRAM_SIZE_1K_MASK, + AW8622X_BIT_RTPCFG1_SRAM_SIZE_1K_EN); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_RTPCFG1, + AW8622X_BIT_RTPCFG1_SRAM_SIZE_2K_MASK, + AW8622X_BIT_RTPCFG1_SRAM_SIZE_2K_EN); + } + return 0; +} + +static int aw8622x_haptic_stop(struct aw8622x *aw8622x) +{ + unsigned char cnt = 40; + unsigned char reg_val = 0; + bool force_flag = true; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + aw8622x->play_mode = AW8622X_HAPTIC_STANDBY_MODE; + aw8622x_i2c_write(aw8622x, AW8622X_REG_PLAYCFG4, 0x02); + while (cnt) { + aw8622x_i2c_read(aw8622x, AW8622X_REG_GLBRD5, ®_val); + if ((reg_val & 0x0f) == 0x00 + || (reg_val & 0x0f) == 0x0A) { + cnt = 0; + force_flag = false; + aw_dev_info(aw8622x->dev, "%s entered standby! glb_state=0x%02X\n", + __func__, reg_val); + } else { + cnt--; + aw_dev_dbg(aw8622x->dev, "%s wait for standby, glb_state=0x%02X\n", + __func__, reg_val); + } + usleep_range(2000, 2500); + } + + if (force_flag) { + aw_dev_err(aw8622x->dev, "%s force to enter standby mode!\n", + __func__); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL2, + AW8622X_BIT_SYSCTRL2_STANDBY_MASK, + AW8622X_BIT_SYSCTRL2_STANDBY_ON); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL2, + AW8622X_BIT_SYSCTRL2_STANDBY_MASK, + AW8622X_BIT_SYSCTRL2_STANDBY_OFF); + } + return 0; +} + +static void aw8622x_haptic_raminit(struct aw8622x *aw8622x, bool flag) +{ + if (flag) { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL1, + AW8622X_BIT_SYSCTRL1_RAMINIT_MASK, + AW8622X_BIT_SYSCTRL1_RAMINIT_ON); + } else { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL1, + AW8622X_BIT_SYSCTRL1_RAMINIT_MASK, + AW8622X_BIT_SYSCTRL1_RAMINIT_OFF); + } +} + +static int aw8622x_haptic_get_vbat(struct aw8622x *aw8622x) +{ + unsigned char reg_val = 0; + unsigned int vbat_code = 0; + /*unsigned int cont = 2000;*/ + + aw8622x_haptic_stop(aw8622x); + aw8622x_haptic_raminit(aw8622x, true); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_DETCFG2, + AW8622X_BIT_DETCFG2_VBAT_GO_MASK, + AW8622X_BIT_DETCFG2_VABT_GO_ON); + usleep_range(20000, 25000); + aw8622x_i2c_read(aw8622x, AW8622X_REG_DET_VBAT, ®_val); + vbat_code = (vbat_code | reg_val) << 2; + aw8622x_i2c_read(aw8622x, AW8622X_REG_DET_LO, ®_val); + vbat_code = vbat_code | ((reg_val & 0x30) >> 4); + aw8622x->vbat = 6100 * vbat_code / 1024; + if (aw8622x->vbat > AW8622X_VBAT_MAX) { + aw8622x->vbat = AW8622X_VBAT_MAX; + aw_dev_info(aw8622x->dev, "%s vbat max limit = %dmV\n", + __func__, aw8622x->vbat); + } + if (aw8622x->vbat < AW8622X_VBAT_MIN) { + aw8622x->vbat = AW8622X_VBAT_MIN; + aw_dev_info(aw8622x->dev, "%s vbat min limit = %dmV\n", + __func__, aw8622x->vbat); + } + aw_dev_info(aw8622x->dev, "%s aw8622x->vbat=%dmV, vbat_code=0x%02X\n", + __func__, aw8622x->vbat, vbat_code); + aw8622x_haptic_raminit(aw8622x, false); + return 0; +} + +/***************************************************** + * + * rtp brk + * + *****************************************************/ +/* +*static int aw8622x_rtp_brake_set(struct aw8622x *aw8622x) { +* aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_CONTCFG1, +* AW8622X_BIT_CONTCFG1_MBRK_MASK, +* AW8622X_BIT_CONTCFG1_MBRK_ENABLE); +* +* aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL7, +* AW8622X_BIT_SYSCTRL7_D2S_GAIN_MASK, +* 0x05); +* return 0; +*} +*/ + +static void aw8622x_interrupt_clear(struct aw8622x *aw8622x) +{ + unsigned char reg_val = 0; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + aw8622x_i2c_read(aw8622x, AW8622X_REG_SYSINT, ®_val); + aw_dev_dbg(aw8622x->dev, "%s: reg SYSINT=0x%02X\n", __func__, reg_val); +} + +static int aw8622x_haptic_set_gain(struct aw8622x *aw8622x, unsigned char gain) +{ + aw_dev_info(aw8622x->dev, "%s aw8622x->gain=0x%x gain=0x%x\n",__func__, aw8622x->gain,gain); + aw8622x_i2c_write(aw8622x, AW8622X_REG_PLAYCFG2, gain); + return 0; +} + +static int aw8622x_haptic_ram_vbat_compensate(struct aw8622x *aw8622x, + bool flag) +{ + int temp_gain = 0; + + if (flag) { + if (aw8622x->ram_vbat_compensate == + AW8622X_HAPTIC_RAM_VBAT_COMP_ENABLE) { + aw8622x_haptic_get_vbat(aw8622x); + temp_gain = + aw8622x->gain * AW8622X_VBAT_REFER / aw8622x->vbat; + if (temp_gain > + (128 * AW8622X_VBAT_REFER / AW8622X_VBAT_MIN)) { + temp_gain = + 128 * AW8622X_VBAT_REFER / AW8622X_VBAT_MIN; + aw_dev_dbg(aw8622x->dev, "%s gain limit=%d\n", + __func__, temp_gain); + } + aw8622x_haptic_set_gain(aw8622x, temp_gain); + } else { + aw8622x_haptic_set_gain(aw8622x, aw8622x->gain); + } + } else { + aw8622x_haptic_set_gain(aw8622x, aw8622x->gain); + } + return 0; +} + +static int aw8622x_haptic_play_mode(struct aw8622x *aw8622x, + unsigned char play_mode) +{ + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + + switch (play_mode) { + case AW8622X_HAPTIC_STANDBY_MODE: + aw_dev_info(aw8622x->dev, "%s: enter standby mode\n", __func__); + aw8622x->play_mode = AW8622X_HAPTIC_STANDBY_MODE; + aw8622x_haptic_stop(aw8622x); + break; + case AW8622X_HAPTIC_RAM_MODE: + aw_dev_info(aw8622x->dev, "%s: enter ram mode\n", __func__); + aw8622x->play_mode = AW8622X_HAPTIC_RAM_MODE; + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PLAYCFG3, + AW8622X_BIT_PLAYCFG3_PLAY_MODE_MASK, + AW8622X_BIT_PLAYCFG3_PLAY_MODE_RAM); + break; + case AW8622X_HAPTIC_RAM_LOOP_MODE: + aw_dev_info(aw8622x->dev, "%s: enter ram loop mode\n", + __func__); + aw8622x->play_mode = AW8622X_HAPTIC_RAM_LOOP_MODE; + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PLAYCFG3, + AW8622X_BIT_PLAYCFG3_PLAY_MODE_MASK, + AW8622X_BIT_PLAYCFG3_PLAY_MODE_RAM); + break; + case AW8622X_HAPTIC_RTP_MODE: + aw_dev_info(aw8622x->dev, "%s: enter rtp mode\n", __func__); + aw8622x->play_mode = AW8622X_HAPTIC_RTP_MODE; + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PLAYCFG3, + AW8622X_BIT_PLAYCFG3_PLAY_MODE_MASK, + AW8622X_BIT_PLAYCFG3_PLAY_MODE_RTP); + break; + case AW8622X_HAPTIC_TRIG_MODE: + aw_dev_info(aw8622x->dev, "%s: enter trig mode\n", __func__); + aw8622x->play_mode = AW8622X_HAPTIC_TRIG_MODE; + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PLAYCFG3, + AW8622X_BIT_PLAYCFG3_PLAY_MODE_MASK, + AW8622X_BIT_PLAYCFG3_PLAY_MODE_RAM); + break; + case AW8622X_HAPTIC_CONT_MODE: + aw_dev_info(aw8622x->dev, "%s: enter cont mode\n", __func__); + aw8622x->play_mode = AW8622X_HAPTIC_CONT_MODE; + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PLAYCFG3, + AW8622X_BIT_PLAYCFG3_PLAY_MODE_MASK, + AW8622X_BIT_PLAYCFG3_PLAY_MODE_CONT); + break; + default: + aw_dev_err(aw8622x->dev, "%s: play mode %d error", + __func__, play_mode); + break; + } + return 0; +} + +static int aw8622x_haptic_play_go(struct aw8622x *aw8622x, bool flag) +{ + unsigned char reg_val = 0; + + aw_dev_dbg(aw8622x->dev, "%s enter\n", __func__); + if (flag == true) { + aw8622x_i2c_write(aw8622x, AW8622X_REG_PLAYCFG4, 0x01); + mdelay(2); + } else { + aw8622x_i2c_write(aw8622x, AW8622X_REG_PLAYCFG4, 0x02); + } + + aw8622x_i2c_read(aw8622x, AW8622X_REG_GLBRD5, ®_val); + return 0; +} + +static int aw8622x_haptic_set_wav_seq(struct aw8622x *aw8622x, + unsigned char wav, unsigned char seq) +{ + aw8622x_i2c_write(aw8622x, AW8622X_REG_WAVCFG1 + wav, seq); + return 0; +} + +static int aw8622x_haptic_set_wav_loop(struct aw8622x *aw8622x, + unsigned char wav, unsigned char loop) +{ + unsigned char tmp = 0; + + if (wav % 2) { + tmp = loop << 0; + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_WAVCFG9 + (wav / 2), + AW8622X_BIT_WAVLOOP_SEQ_EVEN_MASK, tmp); + } else { + tmp = loop << 4; + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_WAVCFG9 + (wav / 2), + AW8622X_BIT_WAVLOOP_SEQ_ODD_MASK, tmp); + } + return 0; +} + +/***************************************************** + * + * haptic f0 cali + * + *****************************************************/ +static int aw8622x_haptic_read_lra_f0(struct aw8622x *aw8622x) +{ + int ret = 0; + unsigned char reg_val = 0; + unsigned int f0_reg = 0; + unsigned long f0_tmp = 0; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + /* F_LRA_F0_H */ + ret = aw8622x_i2c_read(aw8622x, AW8622X_REG_CONTRD14, ®_val); + f0_reg = (f0_reg | reg_val) << 8; + /* F_LRA_F0_L */ + ret = aw8622x_i2c_read(aw8622x, AW8622X_REG_CONTRD15, ®_val); + f0_reg |= (reg_val << 0); + if (!f0_reg) { + aw_dev_err(aw8622x->dev, "%s didn't get lra f0 because f0_reg value is 0!\n", + __func__); + aw8622x->f0 = aw8622x->dts_info.f0_ref; + return -ERANGE; + } else { + f0_tmp = 384000 * 10 / f0_reg; + aw8622x->f0 = (unsigned int)f0_tmp; + aw_dev_info(aw8622x->dev, "%s lra_f0=%d\n", __func__, + aw8622x->f0); + } + + return 0; +} + +static int aw8622x_haptic_read_cont_f0(struct aw8622x *aw8622x) +{ + int ret = 0; + unsigned char reg_val = 0; + unsigned int f0_reg = 0; + unsigned long f0_tmp = 0; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + ret = aw8622x_i2c_read(aw8622x, AW8622X_REG_CONTRD16, ®_val); + f0_reg = (f0_reg | reg_val) << 8; + ret = aw8622x_i2c_read(aw8622x, AW8622X_REG_CONTRD17, ®_val); + f0_reg |= (reg_val << 0); + if (!f0_reg) { + aw_dev_err(aw8622x->dev, "%s didn't get cont f0 because f0_reg value is 0!\n", + __func__); + aw8622x->cont_f0 = aw8622x->dts_info.f0_ref; + return -ERANGE; + } else { + f0_tmp = 384000 * 10 / f0_reg; + aw8622x->cont_f0 = (unsigned int)f0_tmp; + aw_dev_info(aw8622x->dev, "%s cont_f0=%d\n", __func__, + aw8622x->cont_f0); + } + return 0; +} + +static int aw8622x_haptic_cont_get_f0(struct aw8622x *aw8622x) +{ + int ret = 0; + unsigned char reg_val = 0; + unsigned int cnt = 200; + bool get_f0_flag = false; + unsigned char brk_en_temp = 0; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + aw8622x->f0 = aw8622x->dts_info.f0_ref; + /* enter standby mode */ + aw8622x_haptic_stop(aw8622x); + /* f0 calibrate work mode */ + aw8622x_haptic_play_mode(aw8622x, AW8622X_HAPTIC_CONT_MODE); + /* enable f0 detect */ + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_CONTCFG1, + AW8622X_BIT_CONTCFG1_EN_F0_DET_MASK, + AW8622X_BIT_CONTCFG1_F0_DET_ENABLE); + /* cont config */ + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_CONTCFG6, + AW8622X_BIT_CONTCFG6_TRACK_EN_MASK, + AW8622X_BIT_CONTCFG6_TRACK_ENABLE); + /* enable auto brake */ + aw8622x_i2c_read(aw8622x, AW8622X_REG_PLAYCFG3, ®_val); + brk_en_temp = 0x04 & reg_val; + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PLAYCFG3, + AW8622X_BIT_PLAYCFG3_BRK_EN_MASK, + AW8622X_BIT_PLAYCFG3_BRK_ENABLE); + /* f0 driver level */ + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_CONTCFG6, + AW8622X_BIT_CONTCFG6_DRV1_LVL_MASK, + aw8622x->dts_info.cont_drv1_lvl_dt); + aw8622x_i2c_write(aw8622x, AW8622X_REG_CONTCFG7, + aw8622x->dts_info.cont_drv2_lvl_dt); + /* DRV1_TIME */ + aw8622x_i2c_write(aw8622x, AW8622X_REG_CONTCFG8, + aw8622x->dts_info.cont_drv1_time_dt); + /* DRV2_TIME */ + aw8622x_i2c_write(aw8622x, AW8622X_REG_CONTCFG9, + aw8622x->dts_info.cont_drv2_time_dt); + /* TRACK_MARGIN */ + if (!aw8622x->dts_info.cont_track_margin) { + aw_dev_err(aw8622x->dev, "%s aw8622x->dts_info.cont_track_margin = 0!\n", + __func__); + } else { + aw8622x_i2c_write(aw8622x, AW8622X_REG_CONTCFG11, + (unsigned char)aw8622x->dts_info. + cont_track_margin); + } + /* DRV_WIDTH */ + /* + * aw8622x_i2c_write(aw8622x, AW8622X_REG_CONTCFG3, + * aw8622x->dts_info.cont_drv_width); + */ + /* cont play go */ + aw8622x_haptic_play_go(aw8622x, true); + /* 300ms */ + while (cnt) { + aw8622x_i2c_read(aw8622x, AW8622X_REG_GLBRD5, ®_val); + if ((reg_val & 0x0f) == 0x00) { + cnt = 0; + get_f0_flag = true; + aw_dev_info(aw8622x->dev, "%s entered standby mode! glb_state=0x%02X\n", + __func__, reg_val); + } else { + cnt--; + aw_dev_info(aw8622x->dev, "%s waitting for standby, glb_state=0x%02X\n", + __func__, reg_val); + } + usleep_range(10000, 10500); + } + if (get_f0_flag) { + ret = aw8622x_haptic_read_lra_f0(aw8622x); + aw8622x_haptic_read_cont_f0(aw8622x); + } else { + aw_dev_err(aw8622x->dev, "%s enter standby mode failed, stop reading f0!\n", + __func__); + } + /* restore default config */ + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_CONTCFG1, + AW8622X_BIT_CONTCFG1_EN_F0_DET_MASK, + AW8622X_BIT_CONTCFG1_F0_DET_DISABLE); + /* recover auto break config */ + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PLAYCFG3, + AW8622X_BIT_PLAYCFG3_BRK_EN_MASK, + brk_en_temp); + return ret; +} + +static int aw8622x_haptic_rtp_init(struct aw8622x *aw8622x) +{ + unsigned int buf_len = 0; + unsigned char glb_state_val = 0; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + pm_qos_add_request(&aw8622x_pm_qos_req_vb, PM_QOS_CPU_DMA_LATENCY, + AW8622X_PM_QOS_VALUE_VB); + aw8622x->rtp_cnt = 0; + mutex_lock(&aw8622x->rtp_lock); + while ((!aw8622x_haptic_rtp_get_fifo_afs(aw8622x)) + && (aw8622x->play_mode == AW8622X_HAPTIC_RTP_MODE)) { + aw_dev_info(aw8622x->dev, "%s rtp cnt = %d\n", __func__, + aw8622x->rtp_cnt); + if (!aw8622x->rtp_container) { + aw_dev_info(aw8622x->dev, "%s:aw8622x->rtp_container is null, break!\n", + __func__); + break; + } + if (aw8622x->rtp_cnt < (aw8622x->ram.base_addr)) { + if ((aw8622x->rtp_container->len - aw8622x->rtp_cnt) < + (aw8622x->ram.base_addr)) { + buf_len = aw8622x->rtp_container->len - aw8622x->rtp_cnt; + } else { + buf_len = aw8622x->ram.base_addr; + } + } else if ((aw8622x->rtp_container->len - aw8622x->rtp_cnt) < + (aw8622x->ram.base_addr >> 2)) { + buf_len = aw8622x->rtp_container->len - aw8622x->rtp_cnt; + } else { + buf_len = aw8622x->ram.base_addr >> 2; + } + aw_dev_info(aw8622x->dev, "%s buf_len = %d\n", __func__, + buf_len); + aw8622x_i2c_writes(aw8622x, AW8622X_REG_RTPDATA, + &aw8622x->rtp_container->data[aw8622x->rtp_cnt], + buf_len); + aw8622x->rtp_cnt += buf_len; + aw8622x_i2c_read(aw8622x, AW8622X_REG_GLBRD5, &glb_state_val); + if ((aw8622x->rtp_cnt == aw8622x->rtp_container->len) + || ((glb_state_val & 0x0f) == 0x00)) { + if (aw8622x->rtp_cnt == aw8622x->rtp_container->len) + aw_dev_info(aw8622x->dev, + "%s: rtp load completely! glb_state_val=%02x aw8622x->rtp_cnt=%02x\n", + __func__, glb_state_val, + aw8622x->rtp_cnt); + else + aw_dev_err(aw8622x->dev, + "%s rtp load failed!! glb_state_val=%02x aw8622x->rtp_cnt=%02x\n", + __func__, glb_state_val, + aw8622x->rtp_cnt); + aw8622x->rtp_cnt = 0; + pm_qos_remove_request(&aw8622x_pm_qos_req_vb); + mutex_unlock(&aw8622x->rtp_lock); + return 0; + } + } + + if (aw8622x->play_mode == AW8622X_HAPTIC_RTP_MODE) + aw8622x_haptic_set_rtp_aei(aw8622x, true); + + aw_dev_info(aw8622x->dev, "%s exit\n", __func__); + mutex_unlock(&aw8622x->rtp_lock); + pm_qos_remove_request(&aw8622x_pm_qos_req_vb); + return 0; +} + +static int aw8622x_haptic_ram_config(struct aw8622x *aw8622x, int duration) +{ + unsigned char wavseq = 0; + unsigned char wavloop = 0; + int ret = 0; + + if (aw8622x->duration_time_flag < 0) { + aw_dev_err(aw8622x->dev, + "%s: duration time error, array size = %d\n", + __func__, aw8622x->duration_time_size); + return -ERANGE; + } + ret = aw8622x_analyse_duration_range(aw8622x); + if (ret < 0) + return ret; + + aw_dev_info(aw8622x->dev, "%s: duration:[%d] time_list = [%d %d %d]\n", __func__, duration,aw8622x->dts_info.duration_time[0],aw8622x->dts_info.duration_time[1],aw8622x->dts_info.duration_time[2]); + + if ((duration > 0) && (duration < + aw8622x->dts_info.duration_time[0])) { + wavseq = 3; /*3*/ + wavloop = 0; + } else if ((duration >= aw8622x->dts_info.duration_time[0]) && + (duration < aw8622x->dts_info.duration_time[1])) { + wavseq = 2; /*2*/ + wavloop = 0; + } else if ((duration >= aw8622x->dts_info.duration_time[1]) && + (duration < aw8622x->dts_info.duration_time[2])) { + wavseq = 2; /*1*/ + wavloop = 0; + } else if(duration >= aw8622x->dts_info.duration_time[2]) { + wavseq = 4; /*4*/ + wavloop = 15; /*long vibration*/ + } else { + wavseq = 0; + wavloop = 0; + } + + aw8622x_haptic_set_wav_seq(aw8622x, 0, wavseq); + aw8622x_haptic_set_wav_loop(aw8622x, 0, wavloop); + aw8622x_haptic_set_wav_seq(aw8622x, 1, 0); + aw8622x_haptic_set_wav_loop(aw8622x, 1, 0); + + return 0; +} + +static unsigned char aw8622x_haptic_osc_read_status(struct aw8622x *aw8622x) +{ + unsigned char reg_val = 0; + + aw8622x_i2c_read(aw8622x, AW8622X_REG_SYSST2, ®_val); + return reg_val; +} + +static int aw8622x_haptic_set_repeat_wav_seq(struct aw8622x *aw8622x, + unsigned char seq) +{ + aw8622x_haptic_set_wav_seq(aw8622x, 0x00, seq); + aw8622x_haptic_set_wav_loop(aw8622x, 0x00, + AW8622X_BIT_WAVLOOP_INIFINITELY); + return 0; +} + +static void aw8622x_rtp_work_routine(struct work_struct *work) +{ + const struct firmware *rtp_file; + int ret = -1; + unsigned int cnt = 200; + unsigned char reg_val = 0; + bool rtp_work_flag = false; + struct aw8622x *aw8622x = container_of(work, struct aw8622x, rtp_work); + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + mutex_lock(&aw8622x->rtp_lock); + /* fw loaded */ + ret = request_firmware(&rtp_file, + aw8622x_rtp_name[aw8622x->rtp_file_num], + aw8622x->dev); + if (ret < 0) { + aw_dev_err(aw8622x->dev, "%s: failed to read %s\n", __func__, + aw8622x_rtp_name[aw8622x->rtp_file_num]); + mutex_unlock(&aw8622x->rtp_lock); + return; + } + aw8622x->rtp_init = 0; + vfree(aw8622x->rtp_container); + aw8622x->rtp_container = vmalloc(rtp_file->size + sizeof(int)); + if (!aw8622x->rtp_container) { + release_firmware(rtp_file); + aw_dev_err(aw8622x->dev, "%s: error allocating memory\n", + __func__); + mutex_unlock(&aw8622x->rtp_lock); + return; + } + aw8622x->rtp_container->len = rtp_file->size; + aw_dev_info(aw8622x->dev, "%s: rtp file:[%s] size = %dbytes\n", + __func__, aw8622x_rtp_name[aw8622x->rtp_file_num], + aw8622x->rtp_container->len); + memcpy(aw8622x->rtp_container->data, rtp_file->data, rtp_file->size); + mutex_unlock(&aw8622x->rtp_lock); + release_firmware(rtp_file); + mutex_lock(&aw8622x->lock); + aw8622x->rtp_init = 1; + aw8622x_haptic_upload_lra(aw8622x, OSC_CALI); + /* gain */ + aw8622x_haptic_ram_vbat_compensate(aw8622x, false); + /* rtp mode config */ + aw8622x_haptic_play_mode(aw8622x, AW8622X_HAPTIC_RTP_MODE); + /* haptic go */ + aw8622x_haptic_play_go(aw8622x, true); + mutex_unlock(&aw8622x->lock); + usleep_range(2000, 2500); + while (cnt) { + aw8622x_i2c_read(aw8622x, AW8622X_REG_GLBRD5, ®_val); + if ((reg_val & 0x0f) == 0x08) { + cnt = 0; + rtp_work_flag = true; + aw_dev_info(aw8622x->dev, "%s RTP_GO! glb_state=0x08\n", + __func__); + } else { + cnt--; + aw_dev_dbg(aw8622x->dev, "%s wait for RTP_GO, glb_state=0x%02X\n", + __func__, reg_val); + } + usleep_range(2000, 2500); + } + if (rtp_work_flag) { + aw8622x_haptic_rtp_init(aw8622x); + } else { + /* enter standby mode */ + aw8622x_haptic_stop(aw8622x); + aw_dev_err(aw8622x->dev, "%s failed to enter RTP_GO status!\n", + __func__); + } +} + +static int aw8622x_rtp_osc_calibration(struct aw8622x *aw8622x) +{ + const struct firmware *rtp_file; + int ret = -1; + unsigned int buf_len = 0; + unsigned char osc_int_state = 0; + + aw8622x->rtp_cnt = 0; + aw8622x->timeval_flags = 1; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + /* fw loaded */ + ret = request_firmware(&rtp_file, aw8622x_rtp_name[0], aw8622x->dev); + if (ret < 0) { + aw_dev_err(aw8622x->dev, "%s: failed to read %s\n", __func__, + aw8622x_rtp_name[0]); + return ret; + } + /*awinic add stop,for irq interrupt during calibrate */ + aw8622x_haptic_stop(aw8622x); + aw8622x->rtp_init = 0; + mutex_lock(&aw8622x->rtp_lock); + vfree(aw8622x->rtp_container); + aw8622x->rtp_container = vmalloc(rtp_file->size + sizeof(int)); + if (!aw8622x->rtp_container) { + release_firmware(rtp_file); + mutex_unlock(&aw8622x->rtp_lock); + aw_dev_err(aw8622x->dev, "%s: error allocating memory\n", + __func__); + return -ENOMEM; + } + aw8622x->rtp_container->len = rtp_file->size; + aw8622x->rtp_len = rtp_file->size; + aw_dev_info(aw8622x->dev, "%s: rtp file:[%s] size = %dbytes\n", + __func__, aw8622x_rtp_name[0], aw8622x->rtp_container->len); + + memcpy(aw8622x->rtp_container->data, rtp_file->data, rtp_file->size); + release_firmware(rtp_file); + mutex_unlock(&aw8622x->rtp_lock); + /* gain */ + aw8622x_haptic_ram_vbat_compensate(aw8622x, false); + /* rtp mode config */ + aw8622x_haptic_play_mode(aw8622x, AW8622X_HAPTIC_RTP_MODE); + + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL7, + AW8622X_BIT_SYSCTRL7_INT_MODE_MASK, + AW8622X_BIT_SYSCTRL7_INT_MODE_EDGE); + disable_irq(gpio_to_irq(aw8622x->irq_gpio)); + /* haptic go */ + aw8622x_haptic_play_go(aw8622x, true); + /* require latency of CPU & DMA not more then PM_QOS_VALUE_VB us */ + pm_qos_add_request(&aw8622x_pm_qos_req_vb, PM_QOS_CPU_DMA_LATENCY, + AW8622X_PM_QOS_VALUE_VB); + while (1) { + if (!aw8622x_haptic_rtp_get_fifo_afs(aw8622x)) { + mutex_lock(&aw8622x->rtp_lock); + if ((aw8622x->rtp_container->len - aw8622x->rtp_cnt) < + (aw8622x->ram.base_addr >> 2)) + buf_len = aw8622x->rtp_container->len - aw8622x->rtp_cnt; + else + buf_len = (aw8622x->ram.base_addr >> 2); + + if (aw8622x->rtp_cnt != aw8622x->rtp_container->len) { + if (aw8622x->timeval_flags == 1) { +#ifdef KERNEL_VERSION_49 + do_gettimeofday(&aw8622x->start); +#else + aw8622x->kstart = ktime_get(); +#endif + aw8622x->timeval_flags = 0; + } + aw8622x->rtp_update_flag = + aw8622x_i2c_writes(aw8622x, + AW8622X_REG_RTPDATA, + &aw8622x->rtp_container->data + [aw8622x->rtp_cnt], + buf_len); + aw8622x->rtp_cnt += buf_len; + } + mutex_unlock(&aw8622x->rtp_lock); + } + osc_int_state = aw8622x_haptic_osc_read_status(aw8622x); + if (osc_int_state & AW8622X_BIT_SYSST2_FF_EMPTY) { +#ifdef KERNEL_VERSION_49 + do_gettimeofday(&aw8622x->end); +#else + aw8622x->kend = ktime_get(); +#endif + pr_info + ("%s osc trim playback done aw8622x->rtp_cnt= %d\n", + __func__, aw8622x->rtp_cnt); + break; + } +#ifdef KERNEL_VERSION_49 + do_gettimeofday(&aw8622x->end); + aw8622x->microsecond = + (aw8622x->end.tv_sec - aw8622x->start.tv_sec) * 1000000 + + (aw8622x->end.tv_usec - aw8622x->start.tv_usec); +#else + aw8622x->kend = ktime_get(); + aw8622x->microsecond = ktime_to_us(ktime_sub(aw8622x->kend, + aw8622x->kstart)); +#endif + + + if (aw8622x->microsecond > AW8622X_OSC_CALI_MAX_LENGTH) { + aw_dev_info(aw8622x->dev, + "%s osc trim time out! aw8622x->rtp_cnt %d osc_int_state %02x\n", + __func__, aw8622x->rtp_cnt, osc_int_state); + break; + } + } + pm_qos_remove_request(&aw8622x_pm_qos_req_vb); + enable_irq(gpio_to_irq(aw8622x->irq_gpio)); +#ifdef KERNEL_VERSION_49 + aw8622x->microsecond = + (aw8622x->end.tv_sec - aw8622x->start.tv_sec)*1000000 + + (aw8622x->end.tv_usec - aw8622x->start.tv_usec); + +#else + aw8622x->microsecond = ktime_to_us(ktime_sub(aw8622x->kend, + aw8622x->kstart)); +#endif + /*calibration osc */ + aw_dev_info(aw8622x->dev, "%s awinic_microsecond: %ld\n", __func__, + aw8622x->microsecond); + aw_dev_info(aw8622x->dev, "%s exit\n", __func__); + return 0; +} + +static int aw8622x_osc_trim_calculation(unsigned long int theory_time, + unsigned long int real_time) +{ + unsigned int real_code = 0; + unsigned int lra_code = 0; + unsigned int DFT_LRA_TRIM_CODE = 0; + /*0.1 percent below no need to calibrate */ + unsigned int osc_cali_threshold = 10; + + aw_dev_info(g_aw8622x->dev, "%s enter\n", __func__); + if (theory_time == real_time) { + aw_dev_info(g_aw8622x->dev, + "%s theory_time == real_time: %ld, no need to calibrate!\n", + __func__, real_time); + return 0; + } else if (theory_time < real_time) { + if ((real_time - theory_time) > (theory_time / 50)) { + aw_dev_info(g_aw8622x->dev, + "%s (real_time - theory_time) > (theory_time/50), can't calibrate!\n", + __func__); + return DFT_LRA_TRIM_CODE; + } + + if ((real_time - theory_time) < + (osc_cali_threshold * theory_time / 10000)) { + aw_dev_info(g_aw8622x->dev, + "%s real_time: %ld, theory_time: %ld, no need to calibrate!\n", + __func__, + real_time, theory_time); + return DFT_LRA_TRIM_CODE; + } + + real_code = ((real_time - theory_time) * 4000) / theory_time; + real_code = ((real_code % 10 < 5) ? 0 : 1) + real_code / 10; + real_code = 32 + real_code; + } else if (theory_time > real_time) { + if ((theory_time - real_time) > (theory_time / 50)) { + aw_dev_info(g_aw8622x->dev, + "%s (theory_time - real_time) > (theory_time / 50), can't calibrate!\n", + __func__); + return DFT_LRA_TRIM_CODE; + } + if ((theory_time - real_time) < + (osc_cali_threshold * theory_time / 10000)) { + aw_dev_info(g_aw8622x->dev, + "%s real_time: %ld, theory_time: %ld, no need to calibrate!\n", + __func__, + real_time, theory_time); + return DFT_LRA_TRIM_CODE; + } + real_code = ((theory_time - real_time) * 4000) / theory_time; + real_code = ((real_code % 10 < 5) ? 0 : 1) + real_code / 10; + real_code = 32 - real_code; + } + if (real_code > 31) + lra_code = real_code - 32; + else + lra_code = real_code + 32; + aw_dev_info(g_aw8622x->dev, + "%s real_time: %ld, theory_time: %ld\n", __func__, real_time, + theory_time); + aw_dev_info(g_aw8622x->dev, + "%s real_code: %02X, trim_lra: 0x%02X\n", __func__, real_code, + lra_code); + return lra_code; +} + +static int aw8622x_haptic_get_lra_resistance(struct aw8622x *aw8622x) +{ + unsigned char reg_val = 0; + unsigned char d2s_gain_temp = 0; + unsigned int lra_code = 0; + unsigned int lra = 0; + + mutex_lock(&aw8622x->lock); + aw8622x_haptic_stop(aw8622x); + aw8622x_i2c_read(aw8622x, AW8622X_REG_SYSCTRL7, ®_val); + d2s_gain_temp = 0x07 & reg_val; + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL7, + AW8622X_BIT_SYSCTRL7_D2S_GAIN_MASK, + aw8622x->dts_info.d2s_gain); + aw8622x_haptic_raminit(aw8622x, true); + /* enter standby mode */ + aw8622x_haptic_stop(aw8622x); + usleep_range(2000, 2500); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL2, + AW8622X_BIT_SYSCTRL2_STANDBY_MASK, + AW8622X_BIT_SYSCTRL2_STANDBY_OFF); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_DETCFG1, + AW8622X_BIT_DETCFG1_RL_OS_MASK, + AW8622X_BIT_DETCFG1_RL); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_DETCFG2, + AW8622X_BIT_DETCFG2_DIAG_GO_MASK, + AW8622X_BIT_DETCFG2_DIAG_GO_ON); + usleep_range(30000, 35000); + aw8622x_i2c_read(aw8622x, AW8622X_REG_DET_RL, ®_val); + lra_code = (lra_code | reg_val) << 2; + aw8622x_i2c_read(aw8622x, AW8622X_REG_DET_LO, ®_val); + lra_code = lra_code | (reg_val & 0x03); + /* 2num */ + lra = (lra_code * 678 * 100) / (1024 * 10); + /* Keep up with aw8624 driver */ + aw8622x->lra = lra * 10; + aw8622x_haptic_raminit(aw8622x, false); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL7, + AW8622X_BIT_SYSCTRL7_D2S_GAIN_MASK, + d2s_gain_temp); + mutex_unlock(&aw8622x->lock); + return 0; +} + +static int aw8622x_haptic_juge_RTP_is_going_on(struct aw8622x *aw8622x) +{ + unsigned char rtp_state = 0; + unsigned char mode = 0; + unsigned char glb_st = 0; + + aw8622x_i2c_read(aw8622x, AW8622X_REG_PLAYCFG3, &mode); + aw8622x_i2c_read(aw8622x, AW8622X_REG_GLBRD5, &glb_st); + if ((mode & AW8622X_BIT_PLAYCFG3_PLAY_MODE_RTP) && + (glb_st == AW8622X_BIT_GLBRD5_STATE_RTP_GO)) { + rtp_state = 1; + } + return rtp_state; +} + +static int aw8622x_container_update(struct aw8622x *aw8622x, + struct aw8622x_container *aw8622x_cont) +{ + unsigned char reg_val = 0; + unsigned int shift = 0; + unsigned int temp = 0; + int i = 0; + int ret = 0; +#ifdef AW_CHECK_RAM_DATA + unsigned short check_sum = 0; +#endif + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + mutex_lock(&aw8622x->lock); + aw8622x->ram.baseaddr_shift = 2; + aw8622x->ram.ram_shift = 4; + /* RAMINIT Enable */ + aw8622x_haptic_raminit(aw8622x, true); + /* Enter standby mode */ + aw8622x_haptic_stop(aw8622x); + /* base addr */ + shift = aw8622x->ram.baseaddr_shift; + aw8622x->ram.base_addr = + (unsigned int)((aw8622x_cont->data[0 + shift] << 8) | + (aw8622x_cont->data[1 + shift])); + + /* default 3k SRAM */ + aw8622x_sram_size(aw8622x, AW8622X_HAPTIC_SRAM_3K); + + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_RTPCFG1, /*ADDRH*/ + AW8622X_BIT_RTPCFG1_ADDRH_MASK, + aw8622x_cont->data[0 + shift]); + + aw8622x_i2c_write(aw8622x, AW8622X_REG_RTPCFG2, /*ADDRL*/ + aw8622x_cont->data[1 + shift]); + + /* FIFO_AEH */ + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_RTPCFG3, + AW8622X_BIT_RTPCFG3_FIFO_AEH_MASK, + (unsigned char) + (((aw8622x->ram.base_addr >> 1) >> 4) & 0xF0)); + /* FIFO AEL */ + aw8622x_i2c_write(aw8622x, AW8622X_REG_RTPCFG4, + (unsigned char) + (((aw8622x->ram.base_addr >> 1) & 0x00FF))); + /* FIFO_AFH */ + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_RTPCFG3, + AW8622X_BIT_RTPCFG3_FIFO_AFH_MASK, + (unsigned char)(((aw8622x->ram.base_addr - + (aw8622x->ram.base_addr >> 2)) >> 8) & 0x0F)); + /* FIFO_AFL */ + aw8622x_i2c_write(aw8622x, AW8622X_REG_RTPCFG5, + (unsigned char)(((aw8622x->ram.base_addr - + (aw8622x->ram.base_addr >> 2)) & 0x00FF))); +/* +* unsigned int temp +* HIGHLOW +* |_ _ _ _AF-12BIT_ _ _ _AE-12BIT| +*/ + aw8622x_i2c_read(aw8622x, AW8622X_REG_RTPCFG3, ®_val); + temp = ((reg_val & 0x0f) << 24) | ((reg_val & 0xf0) << 4); + aw8622x_i2c_read(aw8622x, AW8622X_REG_RTPCFG4, ®_val); + temp = temp | reg_val; + aw_dev_info(aw8622x->dev, "%s: almost_empty_threshold = %d\n", __func__, + (unsigned short)temp); + aw8622x_i2c_read(aw8622x, AW8622X_REG_RTPCFG5, ®_val); + temp = temp | (reg_val << 16); + aw_dev_info(aw8622x->dev, "%s: almost_full_threshold = %d\n", __func__, + temp >> 16); + /* ram */ + shift = aw8622x->ram.baseaddr_shift; + + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_RAMADDRH, + AW8622X_BIT_RAMADDRH_MASK, + aw8622x_cont->data[0 + shift]); + aw8622x_i2c_write(aw8622x, AW8622X_REG_RAMADDRL, + aw8622x_cont->data[1 + shift]); + shift = aw8622x->ram.ram_shift; + aw_dev_info(aw8622x->dev, "%s: ram_len = %d\n", __func__, + aw8622x_cont->len - shift); + for (i = shift; i < aw8622x_cont->len; i++) { + aw8622x->ram_update_flag = aw8622x_i2c_write(aw8622x, + AW8622X_REG_RAMDATA, + aw8622x_cont->data + [i]); + } +#ifdef AW_CHECK_RAM_DATA + shift = aw8622x->ram.baseaddr_shift; + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_RAMADDRH, + AW8622X_BIT_RAMADDRH_MASK, + aw8622x_cont->data[0 + shift]); + aw8622x_i2c_write(aw8622x, AW8622X_REG_RAMADDRL, + aw8622x_cont->data[1 + shift]); + shift = aw8622x->ram.ram_shift; + for (i = shift; i < aw8622x_cont->len; i++) { + aw8622x_i2c_read(aw8622x, AW8622X_REG_RAMDATA, ®_val); + /* + * aw_dev_info(aw8622x->dev, + * "%s aw8622x_cont->data=0x%02X, ramdata=0x%02X\n", + * __func__,aw8622x_cont->data[i],reg_val); + */ + if (reg_val != aw8622x_cont->data[i]) { + aw_dev_err(aw8622x->dev, + "%s: ram check error addr=0x%04x, file_data=0x%02X, ram_data=0x%02X\n", + __func__, i, aw8622x_cont->data[i], reg_val); + ret = -ERANGE; + break; + } + check_sum += reg_val; + } + if (!ret) { + aw8622x_i2c_read(aw8622x, AW8622X_REG_RTPCFG1, ®_val); + check_sum += reg_val & 0x0f; + aw8622x_i2c_read(aw8622x, AW8622X_REG_RTPCFG2, ®_val); + check_sum += reg_val; + + if (check_sum != aw8622x->ram.check_sum) { + aw_dev_err(aw8622x->dev, "%s: ram data check sum error, check_sum=0x%04x\n", + __func__, check_sum); + ret = -ERANGE; + } else { + aw_dev_info(aw8622x->dev, "%s: ram data check sum pass, check_sum=0x%04x\n", + __func__, check_sum); + } + } + +#endif + /* RAMINIT Disable */ + aw8622x_haptic_raminit(aw8622x, false); + mutex_unlock(&aw8622x->lock); + aw_dev_info(aw8622x->dev, "%s exit\n", __func__); + return ret; +} + +static int aw8622x_haptic_get_ram_number(struct aw8622x *aw8622x) +{ + unsigned char i = 0; + unsigned char reg_val = 0; + unsigned char ram_data[3]; + unsigned int first_wave_addr = 0; + + aw_dev_info(aw8622x->dev, "%s enter!\n", __func__); + if (!aw8622x->ram_init) { + aw_dev_err(aw8622x->dev, + "%s: ram init faild, ram_num = 0!\n", + __func__); + return -EPERM; + } + + mutex_lock(&aw8622x->lock); + /* RAMINIT Enable */ + aw8622x_haptic_raminit(aw8622x, true); + aw8622x_haptic_stop(aw8622x); + aw8622x_i2c_write(aw8622x, AW8622X_REG_RAMADDRH, + (unsigned char)(aw8622x->ram.base_addr >> 8)); + aw8622x_i2c_write(aw8622x, AW8622X_REG_RAMADDRL, + (unsigned char)(aw8622x->ram.base_addr & 0x00ff)); + for (i = 0; i < 3; i++) { + aw8622x_i2c_read(aw8622x, AW8622X_REG_RAMDATA, ®_val); + ram_data[i] = reg_val; + } + first_wave_addr = (ram_data[1] << 8 | ram_data[2]); + aw8622x->ram.ram_num = + (first_wave_addr - aw8622x->ram.base_addr - 1) / 4; + aw_dev_info(aw8622x->dev, + "%s: ram_version = 0x%02x\n", __func__, ram_data[0]); + aw_dev_info(aw8622x->dev, + "%s: first waveform addr = 0x%04x\n", + __func__, first_wave_addr); + aw_dev_info(aw8622x->dev, + "%s: ram_num = %d\n", __func__, aw8622x->ram.ram_num); + /* RAMINIT Disable */ + aw8622x_haptic_raminit(aw8622x, false); + mutex_unlock(&aw8622x->lock); + + return 0; +} + +static void aw8622x_ram_loaded(const struct firmware *cont, void *context) +{ + struct aw8622x *aw8622x = context; + struct aw8622x_container *aw8622x_fw; + unsigned short check_sum = 0; + int i = 0; + int ret = 0; +#ifdef AW_READ_BIN_FLEXBALLY + static unsigned char load_cont; + int ram_timer_val = 1000; + + load_cont++; +#endif + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + if (!cont) { + aw_dev_err(aw8622x->dev, "%s: failed to read %s\n", __func__, + aw8622x_ram_name); + release_firmware(cont); +#ifdef AW_READ_BIN_FLEXBALLY + if (load_cont <= 20) { + schedule_delayed_work(&aw8622x->ram_work, + msecs_to_jiffies(ram_timer_val)); + aw_dev_info(aw8622x->dev, "%s:start hrtimer: load_cont=%d\n", + __func__, load_cont); + } +#endif + return; + } + aw_dev_info(aw8622x->dev, "%s: loaded %s - size: %zu bytes\n", __func__, + aw8622x_ram_name, cont ? cont->size : 0); +/* +* for(i=0; i < cont->size; i++) { +* aw_dev_info(aw8622x->dev, "%s: addr: 0x%04x, data: 0x%02X\n", +* __func__, i, *(cont->data+i)); +* } +*/ + /* check sum */ + for (i = 2; i < cont->size; i++) + check_sum += cont->data[i]; + if (check_sum != + (unsigned short)((cont->data[0] << 8) | (cont->data[1]))) { + aw_dev_err(aw8622x->dev, + "%s: check sum err: check_sum=0x%04x\n", __func__, + check_sum); + return; + } else { + aw_dev_info(aw8622x->dev, "%s: check sum pass: 0x%04x\n", + __func__, check_sum); + aw8622x->ram.check_sum = check_sum; + } + + /* aw8622x ram update less then 128kB */ + aw8622x_fw = kzalloc(cont->size + sizeof(int), GFP_KERNEL); + if (!aw8622x_fw) { + release_firmware(cont); + aw_dev_err(aw8622x->dev, "%s: Error allocating memory\n", + __func__); + return; + } + aw8622x_fw->len = cont->size; + memcpy(aw8622x_fw->data, cont->data, cont->size); + release_firmware(cont); + ret = aw8622x_container_update(aw8622x, aw8622x_fw); + if (ret) { + kfree(aw8622x_fw); + aw8622x->ram.len = 0; + aw_dev_err(aw8622x->dev, "%s: ram firmware update failed!\n", + __func__); + } else { + aw8622x->ram_init = 1; + aw8622x->ram.len = aw8622x_fw->len; + kfree(aw8622x_fw); + aw_dev_info(aw8622x->dev, "%s: ram firmware update complete!\n", + __func__); + } + aw8622x_haptic_get_ram_number(aw8622x); + +} + +static int aw8622x_ram_update(struct aw8622x *aw8622x) +{ + aw8622x->ram_init = 0; + aw8622x->rtp_init = 0; + + return request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + aw8622x_ram_name, aw8622x->dev, + GFP_KERNEL, aw8622x, aw8622x_ram_loaded); +} + +static int aw8622x_rtp_trim_lra_calibration(struct aw8622x *aw8622x) +{ + unsigned char reg_val = 0; + unsigned int fre_val = 0; + unsigned int theory_time = 0; + unsigned int lra_trim_code = 0; + + aw8622x_i2c_read(aw8622x, AW8622X_REG_SYSCTRL2, ®_val); + fre_val = (reg_val & 0x03) >> 0; + + if (fre_val == 2 || fre_val == 3) + theory_time = (aw8622x->rtp_len / 12000) * 1000000; /*12K */ + if (fre_val == 0) + theory_time = (aw8622x->rtp_len / 24000) * 1000000; /*24K */ + if (fre_val == 1) + theory_time = (aw8622x->rtp_len / 48000) * 1000000; /*48K */ + + aw_dev_info(aw8622x->dev, "%s microsecond:%ld theory_time = %d\n", + __func__, aw8622x->microsecond, theory_time); + + lra_trim_code = aw8622x_osc_trim_calculation(theory_time, + aw8622x->microsecond); + if (lra_trim_code >= 0) { + aw8622x->osc_cali_data = lra_trim_code; + aw8622x_haptic_upload_lra(aw8622x, OSC_CALI); + } + return 0; +} + +static enum hrtimer_restart aw8622x_vibrator_timer_func(struct hrtimer *timer) +{ + struct aw8622x *aw8622x = container_of(timer, struct aw8622x, timer); + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + aw8622x->state = 0; + schedule_work(&aw8622x->long_vibrate_work); + + return HRTIMER_NORESTART; +} + +static int aw8622x_haptic_play_repeat_seq(struct aw8622x *aw8622x, + unsigned char flag) +{ + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + + if (flag) { + aw8622x_haptic_play_mode(aw8622x, AW8622X_HAPTIC_RAM_LOOP_MODE); + aw8622x_haptic_play_go(aw8622x, true); + } + return 0; +} + +static int aw8622x_haptic_trig_param_init(struct aw8622x *aw8622x) +{ + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + + if ((aw8622x->name == AW86224_5) && (aw8622x->isUsedIntn)) + return 0; + /* trig1 date */ + aw8622x->trig[0].trig_level = aw8622x->dts_info.trig_config[0]; + aw8622x->trig[0].trig_polar = aw8622x->dts_info.trig_config[1]; + aw8622x->trig[0].pos_enable = aw8622x->dts_info.trig_config[2]; + aw8622x->trig[0].pos_sequence = aw8622x->dts_info.trig_config[3]; + aw8622x->trig[0].neg_enable = aw8622x->dts_info.trig_config[4]; + aw8622x->trig[0].neg_sequence = aw8622x->dts_info.trig_config[5]; + aw8622x->trig[0].trig_brk = aw8622x->dts_info.trig_config[6]; + aw_dev_info(aw8622x->dev, "%s: trig1 date init ok!\n", __func__); + if ((aw8622x->name == AW86224_5) && (!aw8622x->isUsedIntn)) { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL2, + AW8622X_BIT_SYSCTRL2_INTN_PIN_MASK, + AW8622X_BIT_SYSCTRL2_TRIG1); + return 0; + } + + /* trig2 date */ + aw8622x->trig[1].trig_level = aw8622x->dts_info.trig_config[7 + 0]; + aw8622x->trig[1].trig_polar = aw8622x->dts_info.trig_config[7 + 1]; + aw8622x->trig[1].pos_enable = aw8622x->dts_info.trig_config[7 + 2]; + aw8622x->trig[1].pos_sequence = aw8622x->dts_info.trig_config[7 + 3]; + aw8622x->trig[1].neg_enable = aw8622x->dts_info.trig_config[7 + 4]; + aw8622x->trig[1].neg_sequence = aw8622x->dts_info.trig_config[7 + 5]; + aw8622x->trig[1].trig_brk = aw8622x->dts_info.trig_config[7 + 6]; + aw_dev_info(aw8622x->dev, "%s: trig2 date init ok!\n", __func__); + + /* trig3 date */ + aw8622x->trig[2].trig_level = aw8622x->dts_info.trig_config[14 + 0]; + aw8622x->trig[2].trig_polar = aw8622x->dts_info.trig_config[14 + 1]; + aw8622x->trig[2].pos_enable = aw8622x->dts_info.trig_config[14 + 2]; + aw8622x->trig[2].pos_sequence = aw8622x->dts_info.trig_config[14 + 3]; + aw8622x->trig[2].neg_enable = aw8622x->dts_info.trig_config[14 + 4]; + aw8622x->trig[2].neg_sequence = aw8622x->dts_info.trig_config[14 + 5]; + aw8622x->trig[2].trig_brk = aw8622x->dts_info.trig_config[14 + 6]; + aw_dev_info(aw8622x->dev, "%s: trig3 date init ok!\n", __func__); + + return 0; +} + +static int aw8622x_haptic_trig_param_config(struct aw8622x *aw8622x) +{ + unsigned char trig1_polar_lev_brk = 0x00; + unsigned char trig2_polar_lev_brk = 0x00; + unsigned char trig3_polar_lev_brk = 0x00; + unsigned char trig1_pos_seq = 0x00; + unsigned char trig2_pos_seq = 0x00; + unsigned char trig3_pos_seq = 0x00; + unsigned char trig1_neg_seq = 0x00; + unsigned char trig2_neg_seq = 0x00; + unsigned char trig3_neg_seq = 0x00; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + + if ((aw8622x->name == AW86224_5) && (aw8622x->isUsedIntn)) + return 0; + /* trig1 config */ + trig1_polar_lev_brk = aw8622x->trig[0].trig_polar << 2 | + aw8622x->trig[0].trig_level << 1 | + aw8622x->trig[0].trig_brk; + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_TRGCFG7, + AW8622X_BIT_TRGCFG7_TRG1_POR_LEV_BRK_MASK, + trig1_polar_lev_brk << 5); + + trig1_pos_seq = aw8622x->trig[0].pos_enable << 7 | + aw8622x->trig[0].pos_sequence; + aw8622x_i2c_write(aw8622x, AW8622X_REG_TRGCFG1, trig1_pos_seq); + + trig1_neg_seq = aw8622x->trig[0].neg_enable << 7 | + aw8622x->trig[0].neg_sequence; + aw8622x_i2c_write(aw8622x, AW8622X_REG_TRGCFG4, trig1_neg_seq); + + aw_dev_info(aw8622x->dev, "%s: trig1 date config ok!\n", __func__); + + if ((aw8622x->name == AW86224_5) && (!aw8622x->isUsedIntn)) { + aw_dev_info(aw8622x->dev, "%s: intn pin is trig.\n", __func__); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL2, + AW8622X_BIT_SYSCTRL2_INTN_PIN_MASK, + AW8622X_BIT_SYSCTRL2_TRIG1); + return 0; + } + /* trig2 config */ + trig2_polar_lev_brk = aw8622x->trig[1].trig_polar << 2 | + aw8622x->trig[1].trig_level << 1 | + aw8622x->trig[1].trig_brk; + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_TRGCFG7, + AW8622X_BIT_TRGCFG7_TRG2_POR_LEV_BRK_MASK, + trig2_polar_lev_brk << 1); + trig2_pos_seq = aw8622x->trig[1].pos_enable << 7 | + aw8622x->trig[1].pos_sequence; + aw8622x_i2c_write(aw8622x, AW8622X_REG_TRGCFG2, trig2_pos_seq); + trig2_neg_seq = aw8622x->trig[1].neg_enable << 7 | + aw8622x->trig[1].neg_sequence; + aw8622x_i2c_write(aw8622x, AW8622X_REG_TRGCFG5, trig2_neg_seq); + aw_dev_info(aw8622x->dev, "%s: trig2 date config ok!\n", __func__); + + /* trig3 config */ + trig3_polar_lev_brk = aw8622x->trig[2].trig_polar << 2 | + aw8622x->trig[2].trig_level << 1 | + aw8622x->trig[2].trig_brk; + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_TRGCFG8, + AW8622X_BIT_TRGCFG8_TRG3_POR_LEV_BRK_MASK, + trig3_polar_lev_brk << 5); + trig3_pos_seq = aw8622x->trig[2].pos_enable << 7 | + aw8622x->trig[2].pos_sequence; + aw8622x_i2c_write(aw8622x, AW8622X_REG_TRGCFG3, trig3_pos_seq); + trig3_neg_seq = aw8622x->trig[2].neg_enable << 7 | + aw8622x->trig[2].neg_sequence; + aw8622x_i2c_write(aw8622x, AW8622X_REG_TRGCFG6, trig3_neg_seq); + aw_dev_info(aw8622x->dev, "%s: trig3 date config ok!\n", __func__); + + return 0; +} + +/***************************************************** + * + * motor protect + * + *****************************************************/ +static int aw8622x_haptic_swicth_motor_protect_config(struct aw8622x *aw8622x, + unsigned char addr, + unsigned char val) +{ + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + if (addr == 1) { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_DETCFG1, + AW8622X_BIT_DETCFG1_PRCT_MODE_MASK, + AW8622X_BIT_DETCFG1_PRCT_MODE_VALID); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PWMCFG1, + AW8622X_BIT_PWMCFG1_PRC_EN_MASK, + AW8622X_BIT_PWMCFG1_PRC_ENABLE); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PWMCFG3, + AW8622X_BIT_PWMCFG3_PR_EN_MASK, + AW8622X_BIT_PWMCFG3_PR_ENABLE); + } else if (addr == 0) { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_DETCFG1, + AW8622X_BIT_DETCFG1_PRCT_MODE_MASK, + AW8622X_BIT_DETCFG1_PRCT_MODE_INVALID); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PWMCFG1, + AW8622X_BIT_PWMCFG1_PRC_EN_MASK, + AW8622X_BIT_PWMCFG1_PRC_DISABLE); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PWMCFG3, + AW8622X_BIT_PWMCFG3_PR_EN_MASK, + AW8622X_BIT_PWMCFG3_PR_DISABLE); + } else if (addr == 0x2d) { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PWMCFG1, + AW8622X_BIT_PWMCFG1_PRCTIME_MASK, val); + } else if (addr == 0x3e) { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PWMCFG3, + AW8622X_BIT_PWMCFG3_PRLVL_MASK, val); + } else if (addr == 0x3f) { + aw8622x_i2c_write(aw8622x, AW8622X_REG_PWMCFG4, val); + } + return 0; +} + +static int aw8622x_haptic_f0_calibration(struct aw8622x *aw8622x) +{ + int ret = 0; + unsigned char reg_val = 0; + unsigned int f0_limit = 0; + char f0_cali_lra = 0; + int f0_cali_step = 0; + unsigned int f0_cali_min = aw8622x->dts_info.f0_ref * + (100 - aw8622x->dts_info.f0_cali_percent) / 100; + unsigned int f0_cali_max = aw8622x->dts_info.f0_ref * + (100 + aw8622x->dts_info.f0_cali_percent) / 100; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + /* + * aw8622x_haptic_upload_lra(aw8622x, WRITE_ZERO); + */ + if (aw8622x_haptic_cont_get_f0(aw8622x)) { + aw_dev_err(aw8622x->dev, "%s get f0 error, user defafult f0\n", + __func__); + return -ERANGE; + } else { + /* max and min limit */ + f0_limit = aw8622x->f0; + aw_dev_info(aw8622x->dev, "%s f0_ref = %d, f0_cali_min = %d, f0_cali_max = %d, f0 = %d\n", + __func__, aw8622x->dts_info.f0_ref, + f0_cali_min, f0_cali_max, aw8622x->f0); + + if ((aw8622x->f0 < f0_cali_min) || aw8622x->f0 > f0_cali_max) { + aw_dev_err(aw8622x->dev, "%s f0 calibration out of range = %d!\n", + __func__, aw8622x->f0); + f0_limit = aw8622x->dts_info.f0_ref; + return -ERANGE; + } + aw_dev_info(aw8622x->dev, "%s f0_limit = %d\n", __func__, + (int)f0_limit); + /* calculate cali step */ + f0_cali_step = 100000 * ((int)f0_limit - + (int)aw8622x->dts_info.f0_ref) / + ((int)f0_limit * 24); + aw_dev_info(aw8622x->dev, "%s f0_cali_step = %d\n", __func__, + f0_cali_step); + if (f0_cali_step >= 0) { /*f0_cali_step >= 0 */ + if (f0_cali_step % 10 >= 5) + f0_cali_step = 32 + (f0_cali_step / 10 + 1); + else + f0_cali_step = 32 + f0_cali_step / 10; + } else { /* f0_cali_step < 0 */ + if (f0_cali_step % 10 <= -5) + f0_cali_step = 32 + (f0_cali_step / 10 - 1); + else + f0_cali_step = 32 + f0_cali_step / 10; + } + if (f0_cali_step > 31) + f0_cali_lra = (char)f0_cali_step - 32; + else + f0_cali_lra = (char)f0_cali_step + 32; + /* update cali step */ + aw8622x->f0_cali_data = (int)f0_cali_lra; + aw8622x_haptic_upload_lra(aw8622x, F0_CALI); + + aw8622x_i2c_read(aw8622x, AW8622X_REG_TRIMCFG3, ®_val); + + aw_dev_info(aw8622x->dev, "%s final trim_lra=0x%02x\n", + __func__, reg_val); + } + /* restore standby work mode */ + aw8622x_haptic_stop(aw8622x); + return ret; +} + +/***************************************************** + * + * haptic cont + * + *****************************************************/ +static int aw8622x_haptic_cont_config(struct aw8622x *aw8622x) +{ + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + + /* work mode */ + aw8622x_haptic_play_mode(aw8622x, AW8622X_HAPTIC_CONT_MODE); + /* cont config */ + /* aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_CONTCFG1, + ** AW8622X_BIT_CONTCFG1_EN_F0_DET_MASK, + ** AW8622X_BIT_CONTCFG1_F0_DET_ENABLE); + */ + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_CONTCFG6, + AW8622X_BIT_CONTCFG6_TRACK_EN_MASK, + AW8622X_BIT_CONTCFG6_TRACK_ENABLE); + /* f0 driver level */ + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_CONTCFG6, + AW8622X_BIT_CONTCFG6_DRV1_LVL_MASK, + aw8622x->cont_drv1_lvl); + aw8622x_i2c_write(aw8622x, AW8622X_REG_CONTCFG7, + aw8622x->cont_drv2_lvl); + /* DRV1_TIME */ + /* aw8622x_i2c_write(aw8622x, AW8622X_REG_CONTCFG8, 0xFF); */ + /* DRV2_TIME */ + aw8622x_i2c_write(aw8622x, AW8622X_REG_CONTCFG9, 0xFF); + /* cont play go */ + aw8622x_haptic_play_go(aw8622x, true); + return 0; +} + +static int aw8622x_haptic_play_wav_seq(struct aw8622x *aw8622x, + unsigned char flag) +{ + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + if (flag) { + aw8622x_haptic_play_mode(aw8622x, AW8622X_HAPTIC_RAM_MODE); + aw8622x_haptic_play_go(aw8622x, true); + } + return 0; +} + +#ifdef TIMED_OUTPUT +static int aw8622x_vibrator_get_time(struct timed_output_dev *dev) +{ + struct aw8622x *aw8622x = container_of(dev, struct aw8622x, vib_dev); + + if (hrtimer_active(&aw8622x->timer)) { + ktime_t r = hrtimer_get_remaining(&aw8622x->timer); + + return ktime_to_ms(r); + } + return 0; +} + +static void aw8622x_vibrator_enable(struct timed_output_dev *dev, int value) +{ + struct aw8622x *aw8622x = container_of(dev, struct aw8622x, vib_dev); + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + if (!aw8622x->ram_init) { + aw_dev_err(aw8622x->dev, + "%s: ram init failed, not allow to play!\n", + __func__); + return; + } + mutex_lock(&aw8622x->lock); + aw8622x_haptic_stop(aw8622x); + if (value > 0) { + aw8622x_haptic_ram_vbat_compensate(aw8622x, false); + aw8622x_haptic_play_wav_seq(aw8622x, value); + } + mutex_unlock(&aw8622x->lock); + aw_dev_info(aw8622x->dev, "%s exit\n", __func__); +} +#else +static enum led_brightness aw8622x_haptic_brightness_get(struct led_classdev + *cdev) +{ + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + + return aw8622x->amplitude; +} + +static void aw8622x_haptic_brightness_set(struct led_classdev *cdev, + enum led_brightness level) +{ + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + if (!aw8622x->ram_init) { + aw_dev_err(aw8622x->dev, "%s: ram init failed, not allow to play!\n", + __func__); + return; + } + if (aw8622x->ram_update_flag < 0) + return; + aw8622x->amplitude = level; + mutex_lock(&aw8622x->lock); + aw8622x_haptic_stop(aw8622x); + if (aw8622x->amplitude > 0) { + aw8622x_haptic_upload_lra(aw8622x, F0_CALI); + aw8622x_haptic_ram_vbat_compensate(aw8622x, false); + aw8622x_haptic_play_wav_seq(aw8622x, aw8622x->amplitude); + } + mutex_unlock(&aw8622x->lock); +} +#endif + +static int +aw8622x_haptic_audio_ctr_list_insert(struct haptic_audio *haptic_audio, + struct haptic_ctr *haptic_ctr, + struct device *dev) +{ + struct haptic_ctr *p_new = NULL; + + p_new = (struct haptic_ctr *)kzalloc( + sizeof(struct haptic_ctr), GFP_KERNEL); + if (p_new == NULL) { + aw_dev_err(dev, "%s: kzalloc memory fail\n", __func__); + return -ENOMEM; + } + /* update new list info */ + p_new->cnt = haptic_ctr->cnt; + p_new->cmd = haptic_ctr->cmd; + p_new->play = haptic_ctr->play; + p_new->wavseq = haptic_ctr->wavseq; + p_new->loop = haptic_ctr->loop; + p_new->gain = haptic_ctr->gain; + + INIT_LIST_HEAD(&(p_new->list)); + list_add(&(p_new->list), &(haptic_audio->ctr_list)); + return 0; +} + +static int +aw8622x_haptic_audio_ctr_list_clear(struct haptic_audio *haptic_audio) +{ + struct haptic_ctr *p_ctr = NULL; + struct haptic_ctr *p_ctr_bak = NULL; + + list_for_each_entry_safe_reverse(p_ctr, p_ctr_bak, + &(haptic_audio->ctr_list), + list) { + list_del(&p_ctr->list); + kfree(p_ctr); + } + + return 0; +} + +static int aw8622x_haptic_audio_off(struct aw8622x *aw8622x) +{ + aw_dev_dbg(aw8622x->dev, "%s: enter\n", __func__); + mutex_lock(&aw8622x->lock); + aw8622x_haptic_set_gain(aw8622x, 0x80); + aw8622x_haptic_stop(aw8622x); + aw8622x->gun_type = 0xff; + aw8622x->bullet_nr = 0; + aw8622x_haptic_audio_ctr_list_clear(&aw8622x->haptic_audio); + mutex_unlock(&aw8622x->lock); + return 0; +} + +static int aw8622x_haptic_audio_init(struct aw8622x *aw8622x) +{ + + aw_dev_dbg(aw8622x->dev, "%s enter\n", __func__); + aw8622x_haptic_set_wav_seq(aw8622x, 0x01, 0x00); + + return 0; +} + +static int aw8622x_haptic_activate(struct aw8622x *aw8622x) +{ + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL2, + AW8622X_BIT_SYSCTRL2_STANDBY_MASK, + AW8622X_BIT_SYSCTRL2_STANDBY_OFF); + aw8622x_interrupt_clear(aw8622x); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSINTM, + AW8622X_BIT_SYSINTM_UVLM_MASK, + AW8622X_BIT_SYSINTM_UVLM_ON); + return 0; +} + +static int aw8622x_haptic_start(struct aw8622x *aw8622x) +{ + aw8622x_haptic_activate(aw8622x); + aw8622x_haptic_play_go(aw8622x, true); + return 0; +} + + +static void aw8622x_haptic_audio_work_routine(struct work_struct *work) +{ + struct aw8622x *aw8622x = container_of(work, + struct aw8622x, + haptic_audio.work); + struct haptic_audio *haptic_audio = NULL; + struct haptic_ctr *p_ctr = NULL; + struct haptic_ctr *p_ctr_bak = NULL; + unsigned int ctr_list_flag = 0; + unsigned int ctr_list_input_cnt = 0; + unsigned int ctr_list_output_cnt = 0; + unsigned int ctr_list_diff_cnt = 0; + unsigned int ctr_list_del_cnt = 0; + int rtp_is_going_on = 0; + + aw_dev_dbg(aw8622x->dev, "%s enter\n", __func__); + + haptic_audio = &(aw8622x->haptic_audio); + mutex_lock(&aw8622x->haptic_audio.lock); + memset(&aw8622x->haptic_audio.ctr, 0, + sizeof(struct haptic_ctr)); + ctr_list_flag = 0; + list_for_each_entry_safe_reverse(p_ctr, p_ctr_bak, + &(haptic_audio->ctr_list), list) { + ctr_list_flag = 1; + break; + } + if (ctr_list_flag == 0) + aw_dev_info(aw8622x->dev, "%s: ctr list empty\n", __func__); + + if (ctr_list_flag == 1) { + list_for_each_entry_safe(p_ctr, p_ctr_bak, + &(haptic_audio->ctr_list), list) { + ctr_list_input_cnt = p_ctr->cnt; + break; + } + list_for_each_entry_safe_reverse(p_ctr, p_ctr_bak, + &(haptic_audio->ctr_list), list) { + ctr_list_output_cnt = p_ctr->cnt; + break; + } + if (ctr_list_input_cnt > ctr_list_output_cnt) + ctr_list_diff_cnt = ctr_list_input_cnt - ctr_list_output_cnt; + + if (ctr_list_input_cnt < ctr_list_output_cnt) + ctr_list_diff_cnt = 32 + ctr_list_input_cnt - ctr_list_output_cnt; + + if (ctr_list_diff_cnt > 2) { + list_for_each_entry_safe_reverse(p_ctr, p_ctr_bak, + &(haptic_audio->ctr_list), list) { + if ((p_ctr->play == 0) && + (AW8622X_HAPTIC_CMD_ENABLE == + (AW8622X_HAPTIC_CMD_HAPTIC & p_ctr->cmd))) { + list_del(&p_ctr->list); + kfree(p_ctr); + ctr_list_del_cnt++; + } + if (ctr_list_del_cnt == ctr_list_diff_cnt) + break; + } + } + } + + /* get the last data from list */ + list_for_each_entry_safe_reverse(p_ctr, p_ctr_bak, + &(haptic_audio->ctr_list), list) { + aw8622x->haptic_audio.ctr.cnt = p_ctr->cnt; + aw8622x->haptic_audio.ctr.cmd = p_ctr->cmd; + aw8622x->haptic_audio.ctr.play = p_ctr->play; + aw8622x->haptic_audio.ctr.wavseq = p_ctr->wavseq; + aw8622x->haptic_audio.ctr.loop = p_ctr->loop; + aw8622x->haptic_audio.ctr.gain = p_ctr->gain; + list_del(&p_ctr->list); + kfree(p_ctr); + break; + } + + if (aw8622x->haptic_audio.ctr.play) { + aw_dev_info(aw8622x->dev, "%s: cnt=%d, cmd=%d, play=%d, wavseq=%d, loop=%d, gain=%d\n", + __func__, + aw8622x->haptic_audio.ctr.cnt, + aw8622x->haptic_audio.ctr.cmd, + aw8622x->haptic_audio.ctr.play, + aw8622x->haptic_audio.ctr.wavseq, + aw8622x->haptic_audio.ctr.loop, + aw8622x->haptic_audio.ctr.gain); + } + + /* rtp mode jump */ + rtp_is_going_on = aw8622x_haptic_juge_RTP_is_going_on(aw8622x); + if (rtp_is_going_on) { + mutex_unlock(&aw8622x->haptic_audio.lock); + return; + } + mutex_unlock(&aw8622x->haptic_audio.lock); + + /*haptic play control*/ + if (AW8622X_HAPTIC_CMD_ENABLE == + (AW8622X_HAPTIC_CMD_HAPTIC & aw8622x->haptic_audio.ctr.cmd)) { + if (aw8622x->haptic_audio.ctr.play == + AW8622X_HAPTIC_PLAY_ENABLE) { + aw_dev_info(aw8622x->dev, + "%s: haptic_audio_play_start\n", __func__); + aw_dev_info(aw8622x->dev, + "%s: normal haptic start\n", __func__); + mutex_lock(&aw8622x->lock); + aw8622x_haptic_stop(aw8622x); + aw8622x_haptic_play_mode(aw8622x, + AW8622X_HAPTIC_RAM_MODE); + aw8622x_haptic_set_wav_seq(aw8622x, 0x00, + aw8622x->haptic_audio.ctr.wavseq); + aw8622x_haptic_set_wav_loop(aw8622x, 0x00, + aw8622x->haptic_audio.ctr.loop); + aw8622x_haptic_set_gain(aw8622x, + aw8622x->haptic_audio.ctr.gain); + aw8622x_haptic_start(aw8622x); + mutex_unlock(&aw8622x->lock); + } else if (AW8622X_HAPTIC_PLAY_STOP == + aw8622x->haptic_audio.ctr.play) { + mutex_lock(&aw8622x->lock); + aw8622x_haptic_stop(aw8622x); + mutex_unlock(&aw8622x->lock); + } else if (AW8622X_HAPTIC_PLAY_GAIN == + aw8622x->haptic_audio.ctr.play) { + mutex_lock(&aw8622x->lock); + aw8622x_haptic_set_gain(aw8622x, + aw8622x->haptic_audio.ctr.gain); + mutex_unlock(&aw8622x->lock); + } + } + + +} + +static ssize_t aw8622x_cont_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + + aw8622x_haptic_read_cont_f0(aw8622x); + len += snprintf(buf + len, PAGE_SIZE - len, + "%d\n", aw8622x->cont_f0); + return len; +} + +static ssize_t aw8622x_cont_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + aw8622x_haptic_stop(aw8622x); + if (val) + aw8622x_haptic_cont_config(aw8622x); + return count; +} + +static ssize_t aw8622x_f0_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + unsigned char reg = 0; + + aw_dev_info(aw8622x->dev, "%s: enter\n", __func__); + mutex_lock(&aw8622x->lock); + + /* set d2s_gain to max to get better performance when cat f0 .*/ + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL7, + AW8622X_BIT_SYSCTRL7_D2S_GAIN_MASK, + AW8622X_BIT_SYSCTRL7_D2S_GAIN_40); + aw8622x_i2c_read(aw8622x, AW8622X_REG_TRIMCFG3, ®); + aw8622x_haptic_upload_lra(aw8622x, WRITE_ZERO); + aw8622x_haptic_cont_get_f0(aw8622x); + aw8622x_i2c_write(aw8622x, AW8622X_REG_TRIMCFG3, reg); + /* set d2s_gain to default when cat f0 is finished.*/ + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL7, + AW8622X_BIT_SYSCTRL7_D2S_GAIN_MASK, + aw8622x->dts_info.d2s_gain); + mutex_unlock(&aw8622x->lock); + len += snprintf(buf + len, PAGE_SIZE - len, + "%d\n", aw8622x->f0); + return len; +} + +static ssize_t aw8622x_f0_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + return count; +} + +static ssize_t aw8622x_reg_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + unsigned char i = 0; + unsigned char reg_val = 0; + + for (i = 0; i < AW8622X_REG_MAX; i++) { + if (!(aw8622x_reg_access[i] & REG_RD_ACCESS)) + continue; + aw8622x_i2c_read(aw8622x, i, ®_val); + len += snprintf(buf + len, PAGE_SIZE - len, + "reg:0x%02X=0x%02X\n", i, reg_val); + } + return len; +} + +static ssize_t aw8622x_reg_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int databuf[2] = { 0, 0 }; + + if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) { + aw8622x_i2c_write(aw8622x, (unsigned char)databuf[0], + (unsigned char)databuf[1]); + } + + return count; +} + +static ssize_t aw8622x_duration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ktime_t time_rem; + s64 time_ms = 0; + + if (hrtimer_active(&aw8622x->timer)) { + time_rem = hrtimer_get_remaining(&aw8622x->timer); + time_ms = ktime_to_ms(time_rem); + } + return snprintf(buf, PAGE_SIZE, "%lld\n", time_ms); +} + +static ssize_t aw8622x_duration_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + /* setting 0 on duration is NOP for now */ + if (val <= 0) + return count; + rc = aw8622x_haptic_ram_config(aw8622x, val); + if (rc < 0) + return rc; + aw8622x->duration = val; + return count; +} + +static ssize_t aw8622x_activate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + + /* For now nothing to show */ + return snprintf(buf, PAGE_SIZE, "activate = %d\n", aw8622x->state); +} + +static ssize_t aw8622x_activate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + if (!aw8622x->ram_init) { + aw_dev_err(aw8622x->dev, "%s: ram init failed, not allow to play!\n", + __func__); + return count; + } + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + if (val != 0 && val != 1) + return count; + aw_dev_info(aw8622x->dev, "%s: value=%d\n", __func__, val); + mutex_lock(&aw8622x->lock); + hrtimer_cancel(&aw8622x->timer); + aw8622x->state = val; + mutex_unlock(&aw8622x->lock); + schedule_work(&aw8622x->long_vibrate_work); + return count; +} + +static ssize_t aw8622x_seq_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + size_t count = 0; + unsigned char i = 0; + unsigned char reg_val = 0; + + for (i = 0; i < AW8622X_SEQUENCER_SIZE; i++) { + aw8622x_i2c_read(aw8622x, AW8622X_REG_WAVCFG1 + i, ®_val); + count += snprintf(buf + count, PAGE_SIZE - count, + "seq%d: 0x%02x\n", i + 1, reg_val); + aw8622x->seq[i] |= reg_val; + } + return count; +} + +static ssize_t aw8622x_seq_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int databuf[2] = { 0, 0 }; + + if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) { + if (databuf[0] > AW8622X_SEQUENCER_SIZE || + databuf[1] > aw8622x->ram.ram_num) { + aw_dev_err(aw8622x->dev, "%s input value out of range\n", + __func__); + return count; + } + aw_dev_info(aw8622x->dev, "%s: seq%d=0x%02X\n", __func__, + databuf[0], databuf[1]); + mutex_lock(&aw8622x->lock); + aw8622x->seq[databuf[0]] = (unsigned char)databuf[1]; + aw8622x_haptic_set_wav_seq(aw8622x, (unsigned char)databuf[0], + aw8622x->seq[databuf[0]]); + mutex_unlock(&aw8622x->lock); + } + return count; +} + +static ssize_t aw8622x_loop_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + size_t count = 0; + unsigned char i = 0; + unsigned char reg_val = 0; + + for (i = 0; i < AW8622X_SEQUENCER_LOOP_SIZE; i++) { + aw8622x_i2c_read(aw8622x, AW8622X_REG_WAVCFG9 + i, ®_val); + aw8622x->loop[i * 2 + 0] = (reg_val >> 4) & 0x0F; + aw8622x->loop[i * 2 + 1] = (reg_val >> 0) & 0x0F; + + count += snprintf(buf + count, PAGE_SIZE - count, + "seq%d_loop = 0x%02x\n", i * 2 + 1, + aw8622x->loop[i * 2 + 0]); + count += snprintf(buf + count, PAGE_SIZE - count, + "seq%d_loop = 0x%02x\n", i * 2 + 2, + aw8622x->loop[i * 2 + 1]); + } + return count; +} + +static ssize_t aw8622x_loop_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int databuf[2] = { 0, 0 }; + + if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) { + aw_dev_info(aw8622x->dev, "%s: seq%d loop=0x%02X\n", __func__, + databuf[0], databuf[1]); + mutex_lock(&aw8622x->lock); + aw8622x->loop[databuf[0]] = (unsigned char)databuf[1]; + aw8622x_haptic_set_wav_loop(aw8622x, (unsigned char)databuf[0], + aw8622x->loop[databuf[0]]); + mutex_unlock(&aw8622x->lock); + } + return count; +} + +static ssize_t aw8622x_rtp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + + len += snprintf(buf + len, PAGE_SIZE - len, + "rtp_cnt = %d\n", + aw8622x->rtp_cnt); + return len; +} + +static ssize_t aw8622x_rtp_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, + struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) { + aw_dev_info(aw8622x->dev, + "%s: kstrtouint fail\n", __func__); + return rc; + } + mutex_lock(&aw8622x->lock); + aw8622x_haptic_stop(aw8622x); + aw8622x_haptic_set_rtp_aei(aw8622x, false); + aw8622x_interrupt_clear(aw8622x); + /* aw8622x_rtp_brake_set(aw8622x); */ + if (val < (sizeof(aw8622x_rtp_name) / AW8622X_RTP_NAME_MAX)) { + aw8622x->rtp_file_num = val; + if (val) { + aw_dev_info(aw8622x->dev, + "%s: aw8622x_rtp_name[%d]: %s\n", __func__, + val, aw8622x_rtp_name[val]); + + schedule_work(&aw8622x->rtp_work); + } else { + aw_dev_err(aw8622x->dev, + "%s: rtp_file_num 0x%02X over max value\n", + __func__, aw8622x->rtp_file_num); + } + } + mutex_unlock(&aw8622x->lock); + return count; +} + +static ssize_t aw8622x_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", aw8622x->state); +} + +static ssize_t aw8622x_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return count; +} + +static ssize_t aw8622x_activate_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", + aw8622x->activate_mode); +} + +static ssize_t aw8622x_activate_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + mutex_lock(&aw8622x->lock); + aw8622x->activate_mode = val; + mutex_unlock(&aw8622x->lock); + return count; +} + +static ssize_t aw8622x_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned char reg_val = 0; + + aw8622x_i2c_read(aw8622x, AW8622X_REG_WAVCFG1, ®_val); + aw8622x->index = reg_val; + return snprintf(buf, PAGE_SIZE, "index = %d\n", aw8622x->index); +} + +static ssize_t aw8622x_index_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + if (val > aw8622x->ram.ram_num) { + aw_dev_err(aw8622x->dev, + "%s: input value out of range!\n", __func__); + return count; + } + aw_dev_info(aw8622x->dev, "%s: value=%d\n", __func__, val); + mutex_lock(&aw8622x->lock); + aw8622x->index = val; + aw8622x_haptic_set_repeat_wav_seq(aw8622x, aw8622x->index); + mutex_unlock(&aw8622x->lock); + return count; +} + +static ssize_t aw8622x_sram_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned char reg_val = 0; + + aw8622x_i2c_read(aw8622x, AW8622X_REG_RTPCFG1, ®_val); + if ((reg_val & 0x30) == 0x20) + return snprintf(buf, PAGE_SIZE, "sram_size = 2K\n"); + else if ((reg_val & 0x30) == 0x10) + return snprintf(buf, PAGE_SIZE, "sram_size = 1K\n"); + else if ((reg_val & 0x30) == 0x30) + return snprintf(buf, PAGE_SIZE, "sram_size = 3K\n"); + return snprintf(buf, PAGE_SIZE, + "sram_size = 0x%02x error, plz check reg.\n", + reg_val & 0x30); +} + +static ssize_t aw8622x_sram_size_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + aw_dev_info(aw8622x->dev, "%s: value=%d\n", __func__, val); + mutex_lock(&aw8622x->lock); + if (val == AW8622X_HAPTIC_SRAM_2K) + aw8622x_sram_size(aw8622x, AW8622X_HAPTIC_SRAM_2K); + else if (val == AW8622X_HAPTIC_SRAM_1K) + aw8622x_sram_size(aw8622x, AW8622X_HAPTIC_SRAM_1K); + else if (val == AW8622X_HAPTIC_SRAM_3K) + aw8622x_sram_size(aw8622x, AW8622X_HAPTIC_SRAM_3K); + mutex_unlock(&aw8622x->lock); + return count; +} + +static ssize_t aw8622x_osc_cali_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + + len += snprintf(buf + len, PAGE_SIZE - len, "osc_cali_data = 0x%02X\n", + aw8622x->osc_cali_data); + + return len; +} + +static ssize_t aw8622x_osc_cali_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + mutex_lock(&aw8622x->lock); + if (val == 1) { + aw8622x_haptic_upload_lra(aw8622x, WRITE_ZERO); + aw8622x_rtp_osc_calibration(aw8622x); + aw8622x_rtp_trim_lra_calibration(aw8622x); + } else if (val == 2) { + aw8622x_haptic_upload_lra(aw8622x, OSC_CALI); + aw8622x_rtp_osc_calibration(aw8622x); + } else { + aw_dev_err(aw8622x->dev, "%s input value out of range\n", __func__); + } + /* osc calibration flag end, other behaviors are permitted */ + mutex_unlock(&aw8622x->lock); + + return count; +} + +static ssize_t aw8622x_gain_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char reg = 0; + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + + aw8622x_i2c_read(aw8622x, AW8622X_REG_PLAYCFG2, ®); + + return snprintf(buf, PAGE_SIZE, "0x%02X\n", reg); +} + +static ssize_t aw8622x_gain_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + aw_dev_info(aw8622x->dev, "%s: value=%d\n", __func__, val); + //if (val >= 0x80) + // val = 0x80; + mutex_lock(&aw8622x->lock); + aw8622x->gain = val; + aw8622x_haptic_set_gain(aw8622x, aw8622x->gain); + mutex_unlock(&aw8622x->lock); + return count; +} + +static ssize_t aw8622x_ram_update_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + unsigned int i = 0; + unsigned char reg_val = 0; + + /* RAMINIT Enable */ + aw8622x_haptic_raminit(aw8622x, true); + aw8622x_haptic_stop(aw8622x); + aw8622x_i2c_write(aw8622x, AW8622X_REG_RAMADDRH, + (unsigned char)(aw8622x->ram.base_addr >> 8)); + aw8622x_i2c_write(aw8622x, AW8622X_REG_RAMADDRL, + (unsigned char)(aw8622x->ram.base_addr & 0x00ff)); + len += snprintf(buf + len, PAGE_SIZE - len, + "haptic_ram len = %d\n", aw8622x->ram.len); + for (i = 0; i < aw8622x->ram.len; i++) { + aw8622x_i2c_read(aw8622x, AW8622X_REG_RAMDATA, ®_val); + if (i % 5 == 0) + len += snprintf(buf + len, + PAGE_SIZE - len, "0x%02X\n", reg_val); + else + len += snprintf(buf + len, + PAGE_SIZE - len, "0x%02X,", reg_val); + } + len += snprintf(buf + len, PAGE_SIZE - len, "\n"); + /* RAMINIT Disable */ + aw8622x_haptic_raminit(aw8622x, false); + return len; +} + +static ssize_t aw8622x_ram_update_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + if (val) + aw8622x_ram_update(aw8622x); + return count; +} + +static ssize_t aw8622x_f0_save_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + + len += snprintf(buf + len, PAGE_SIZE - len, "f0_cali_data = 0x%02X\n", + aw8622x->f0_cali_data); + + return len; +} + +static ssize_t aw8622x_f0_save_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + aw8622x->f0_cali_data = val; + return count; +} + +static ssize_t aw8622x_osc_save_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + + len += snprintf(buf + len, PAGE_SIZE - len, "osc_cali_data = 0x%02X\n", + aw8622x->osc_cali_data); + + return len; +} + +static ssize_t aw8622x_osc_save_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + aw8622x->osc_cali_data = val; + return count; +} + +static ssize_t aw8622x_trig_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + unsigned char i = 0; + unsigned char trig_num = 3; + + if (aw8622x->name == AW86224_5) + trig_num = 1; + + for (i = 0; i < trig_num; i++) { + len += snprintf(buf + len, PAGE_SIZE - len, + "trig%d: trig_level=%d, trig_polar=%d, pos_enable=%d, pos_sequence=%d, neg_enable=%d, neg_sequence=%d trig_brk=%d\n", + i + 1, + aw8622x->trig[i].trig_level, + aw8622x->trig[i].trig_polar, + aw8622x->trig[i].pos_enable, + aw8622x->trig[i].pos_sequence, + aw8622x->trig[i].neg_enable, + aw8622x->trig[i].neg_sequence, + aw8622x->trig[i].trig_brk); + } + + return len; +} + + + +static ssize_t aw8622x_trig_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int databuf[9] = { 0 }; + + if (sscanf(buf, "%d %d %d %d %d %d %d %d", + &databuf[0], &databuf[1], &databuf[2], + &databuf[3], &databuf[4], &databuf[5], + &databuf[6], &databuf[7]) == 8) { + aw_dev_info(aw8622x->dev, + "%s: %d, %d, %d, %d, %d, %d, %d, %d\n", + __func__, databuf[0], databuf[1], databuf[2], + databuf[3], databuf[4], databuf[5], databuf[6], + databuf[7]); + if ((aw8622x->name == AW86224_5) && (databuf[0])) { + aw_dev_err(aw8622x->dev, + "%s: input seq value out of range!\n", + __func__); + return count; + } + if (databuf[0] < 0 || databuf[0] > 2) { + aw_dev_err(aw8622x->dev, + "%s: input seq value out of range!\n", + __func__); + return count; + } + if (!aw8622x->ram_init) { + aw_dev_err(aw8622x->dev, + "%s: ram init failed, not allow to play!\n", + __func__); + return count; + } + if (databuf[4] > aw8622x->ram.ram_num || + databuf[6] > aw8622x->ram.ram_num) { + aw_dev_err(aw8622x->dev, + "%s: input seq value out of range!\n", + __func__); + return count; + } + aw8622x->trig[databuf[0]].trig_level = databuf[1]; + aw8622x->trig[databuf[0]].trig_polar = databuf[2]; + aw8622x->trig[databuf[0]].pos_enable = databuf[3]; + aw8622x->trig[databuf[0]].pos_sequence = databuf[4]; + aw8622x->trig[databuf[0]].neg_enable = databuf[5]; + aw8622x->trig[databuf[0]].neg_sequence = databuf[6]; + aw8622x->trig[databuf[0]].trig_brk = databuf[7]; + mutex_lock(&aw8622x->lock); + aw8622x_haptic_trig_param_config(aw8622x); + mutex_unlock(&aw8622x->lock); + } else + aw_dev_err(aw8622x->dev, + "%s: please input eight parameters\n", + __func__); + return count; +} + +static ssize_t aw8622x_ram_vbat_compensate_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + + len += + snprintf(buf + len, PAGE_SIZE - len, "ram_vbat_comp = %d\n", + aw8622x->ram_vbat_compensate); + + return len; +} + +static ssize_t aw8622x_ram_vbat_compensate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + mutex_lock(&aw8622x->lock); + if (val) + aw8622x->ram_vbat_compensate = + AW8622X_HAPTIC_RAM_VBAT_COMP_ENABLE; + else + aw8622x->ram_vbat_compensate = + AW8622X_HAPTIC_RAM_VBAT_COMP_DISABLE; + mutex_unlock(&aw8622x->lock); + + return count; +} + +static ssize_t aw8622x_cali_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + unsigned char reg = 0; + + aw_dev_info(aw8622x->dev, "%s: enter\n", __func__); + + mutex_lock(&aw8622x->lock); + aw8622x_i2c_read(aw8622x, AW8622X_REG_TRIMCFG3, ®); + aw8622x_haptic_upload_lra(aw8622x, F0_CALI); + aw8622x_haptic_cont_get_f0(aw8622x); + aw8622x_i2c_write(aw8622x, AW8622X_REG_TRIMCFG3, reg); + mutex_unlock(&aw8622x->lock); + len += snprintf(buf + len, PAGE_SIZE - len, "%d\n", aw8622x->f0); + return len; +} + +static ssize_t aw8622x_cali_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + if (val) { + mutex_lock(&aw8622x->lock); + aw8622x_haptic_upload_lra(aw8622x, WRITE_ZERO); + rc = aw8622x_haptic_f0_calibration(aw8622x); + if(rc < 0) + { + count = rc; + } + mutex_unlock(&aw8622x->lock); + } + return count; +} + +static ssize_t aw8622x_cont_wait_num_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + + len += snprintf(buf + len, PAGE_SIZE - len, + "cont_wait_num = 0x%02X\n", aw8622x->cont_wait_num); + return len; +} + +static ssize_t aw8622x_cont_wait_num_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int databuf[1] = { 0 }; + + if (sscanf(buf, "%x", &databuf[0]) == 1) { + aw8622x->cont_wait_num = databuf[0]; + aw8622x_i2c_write(aw8622x, AW8622X_REG_CONTCFG4, databuf[0]); + } + return count; +} + +static ssize_t aw8622x_cont_drv_lvl_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + + len += snprintf(buf + len, PAGE_SIZE - len, + "cont_drv1_lvl = 0x%02X\n", + aw8622x->cont_drv1_lvl); + len += snprintf(buf + len, PAGE_SIZE - len, + "cont_drv2_lvl = 0x%02X\n", + aw8622x->cont_drv2_lvl); + return len; +} + +static ssize_t aw8622x_cont_drv_lvl_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int databuf[2] = { 0, 0 }; + + if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) { + aw8622x->cont_drv1_lvl = databuf[0]; + aw8622x->cont_drv2_lvl = databuf[1]; + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_CONTCFG6, + AW8622X_BIT_CONTCFG6_DRV1_LVL_MASK, + aw8622x->cont_drv1_lvl); + aw8622x_i2c_write(aw8622x, AW8622X_REG_CONTCFG7, + aw8622x->cont_drv2_lvl); + } + return count; +} + +static ssize_t aw8622x_cont_drv_time_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + + len += snprintf(buf + len, PAGE_SIZE - len, + "cont_drv1_time = 0x%02X\n", + aw8622x->cont_drv1_time); + len += snprintf(buf + len, PAGE_SIZE - len, + "cont_drv2_time = 0x%02X\n", + aw8622x->cont_drv2_time); + return len; +} + +static ssize_t aw8622x_cont_drv_time_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int databuf[2] = { 0, 0 }; + + if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) { + aw8622x->cont_drv1_time = databuf[0]; + aw8622x->cont_drv2_time = databuf[1]; + aw8622x_i2c_write(aw8622x, AW8622X_REG_CONTCFG8, + aw8622x->cont_drv1_time); + aw8622x_i2c_write(aw8622x, AW8622X_REG_CONTCFG9, + aw8622x->cont_drv2_time); + } + return count; +} + +static ssize_t aw8622x_cont_brk_time_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + + len += snprintf(buf + len, PAGE_SIZE - len, "cont_brk_time = 0x%02X\n", + aw8622x->cont_brk_time); + return len; +} + +static ssize_t aw8622x_cont_brk_time_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int databuf[1] = { 0 }; + + if (sscanf(buf, "%x", &databuf[0]) == 1) { + aw8622x->cont_brk_time = databuf[0]; + aw8622x_i2c_write(aw8622x, AW8622X_REG_CONTCFG10, + aw8622x->cont_brk_time); + } + return count; +} + +static ssize_t aw8622x_vbat_monitor_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + + mutex_lock(&aw8622x->lock); + aw8622x_haptic_get_vbat(aw8622x); + len += snprintf(buf + len, PAGE_SIZE - len, "vbat_monitor = %d\n", + aw8622x->vbat); + mutex_unlock(&aw8622x->lock); + + return len; +} + +static ssize_t aw8622x_vbat_monitor_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return count; +} + +static ssize_t aw8622x_lra_resistance_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + + aw8622x_haptic_get_lra_resistance(aw8622x); + len += snprintf(buf + len, PAGE_SIZE - len, "lra_resistance = %d\n", + aw8622x->lra); + return len; +} + +static ssize_t aw8622x_lra_resistance_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return count; +} + +static ssize_t aw8622x_prctmode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + unsigned char reg_val = 0; + + aw8622x_i2c_read(aw8622x, AW8622X_REG_DETCFG1, ®_val); + + len += snprintf(buf + len, PAGE_SIZE - len, "prctmode = %d\n", + reg_val & 0x08); + return len; +} + +static ssize_t aw8622x_prctmode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int databuf[2] = { 0, 0 }; + unsigned int addr = 0; + unsigned int val = 0; + + if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) { + addr = databuf[0]; + val = databuf[1]; + mutex_lock(&aw8622x->lock); + aw8622x_haptic_swicth_motor_protect_config(aw8622x, addr, val); + mutex_unlock(&aw8622x->lock); + } + return count; +} + +static ssize_t aw8622x_gun_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + + return snprintf(buf, PAGE_SIZE, "0x%02x\n", aw8622x->gun_type); + +} + +static ssize_t aw8622x_gun_type_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + aw_dev_info(aw8622x->dev, "%s: value=%d\n", __func__, val); + mutex_lock(&aw8622x->lock); + aw8622x->gun_type = val; + mutex_unlock(&aw8622x->lock); + return count; +} + +static ssize_t aw8622x_bullet_nr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + + return snprintf(buf, PAGE_SIZE, "0x%02x\n", aw8622x->bullet_nr); +} + +static ssize_t aw8622x_bullet_nr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + aw_dev_info(aw8622x->dev, "%s: value=%d\n", __func__, val); + mutex_lock(&aw8622x->lock); + aw8622x->bullet_nr = val; + mutex_unlock(&aw8622x->lock); + return count; +} + +static ssize_t aw8622x_haptic_audio_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + + len += snprintf(buf+len, PAGE_SIZE-len, + "%d\n", aw8622x->haptic_audio.ctr.cnt); + return len; +} + +static ssize_t aw8622x_haptic_audio_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + unsigned int databuf[6] = {0}; + int rtp_is_going_on = 0; + struct haptic_ctr *hap_ctr = NULL; + + rtp_is_going_on = aw8622x_haptic_juge_RTP_is_going_on(aw8622x); + if (rtp_is_going_on) { + aw_dev_info(aw8622x->dev, + "%s: RTP is runing, stop audio haptic\n", __func__); + return count; + } + if (!aw8622x->ram_init) + return count; + + if (sscanf(buf, "%d %d %d %d %d %d", + &databuf[0], &databuf[1], &databuf[2], + &databuf[3], &databuf[4], &databuf[5]) == 6) { + if (databuf[2]) { + aw_dev_info(aw8622x->dev, "%s: cnt=%d, cmd=%d, play=%d, wavseq=%d, loop=%d, gain=%d\n", + __func__, + databuf[0], databuf[1], databuf[2], + databuf[3], databuf[4], databuf[5]); + hap_ctr = (struct haptic_ctr *)kzalloc(sizeof(struct haptic_ctr), + GFP_KERNEL); + if (hap_ctr == NULL) { + aw_dev_err(aw8622x->dev, "%s: kzalloc memory fail\n", + __func__); + return count; + } + mutex_lock(&aw8622x->haptic_audio.lock); + hap_ctr->cnt = (unsigned char)databuf[0]; + hap_ctr->cmd = (unsigned char)databuf[1]; + hap_ctr->play = (unsigned char)databuf[2]; + hap_ctr->wavseq = (unsigned char)databuf[3]; + hap_ctr->loop = (unsigned char)databuf[4]; + hap_ctr->gain = (unsigned char)databuf[5]; + aw8622x_haptic_audio_ctr_list_insert(&aw8622x->haptic_audio, + hap_ctr, aw8622x->dev); + if (hap_ctr->cmd == 0xff) { + aw_dev_info(aw8622x->dev, + "%s: haptic_audio stop\n", __func__); + if (hrtimer_active(&aw8622x->haptic_audio.timer)) { + aw_dev_info(aw8622x->dev, "%s: cancel haptic_audio_timer\n", + __func__); + hrtimer_cancel(&aw8622x->haptic_audio.timer); + aw8622x->haptic_audio.ctr.cnt = 0; + aw8622x_haptic_audio_off(aw8622x); + } + } else { + if (hrtimer_active(&aw8622x->haptic_audio.timer)) { + } else { + aw_dev_info(aw8622x->dev, "%s: start haptic_audio_timer\n", + __func__); + aw8622x_haptic_audio_init(aw8622x); + hrtimer_start(&aw8622x->haptic_audio.timer, + ktime_set(aw8622x->haptic_audio.delay_val/1000000, + (aw8622x->haptic_audio.delay_val%1000000)*1000), + HRTIMER_MODE_REL); + } + } + + } + mutex_unlock(&aw8622x->haptic_audio.lock); + kfree(hap_ctr); + } + return count; +} + +static ssize_t aw8622x_ram_num_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cdev_t *cdev = dev_get_drvdata(dev); + struct aw8622x *aw8622x = container_of(cdev, struct aw8622x, vib_dev); + ssize_t len = 0; + + aw8622x_haptic_get_ram_number(aw8622x); + len += snprintf(buf+len, PAGE_SIZE-len, + "ram_num = %d\n", aw8622x->ram.ram_num); + return len; +} + +static DEVICE_ATTR(f0, 0644, aw8622x_f0_show, aw8622x_f0_store); +static DEVICE_ATTR(cont, 0644, aw8622x_cont_show, aw8622x_cont_store); +static DEVICE_ATTR(register, 0644, aw8622x_reg_show, aw8622x_reg_store); +static DEVICE_ATTR(duration, 0644, aw8622x_duration_show, + aw8622x_duration_store); +static DEVICE_ATTR(index, 0644, aw8622x_index_show, aw8622x_index_store); +static DEVICE_ATTR(activate, 0644, aw8622x_activate_show, + aw8622x_activate_store); +static DEVICE_ATTR(activate_mode, 0644, aw8622x_activate_mode_show, + aw8622x_activate_mode_store); +static DEVICE_ATTR(seq, 0644, aw8622x_seq_show, aw8622x_seq_store); +static DEVICE_ATTR(loop, 0644, aw8622x_loop_show, aw8622x_loop_store); +static DEVICE_ATTR(rtp, 0644, aw8622x_rtp_show, aw8622x_rtp_store); +static DEVICE_ATTR(state, 0644, aw8622x_state_show, aw8622x_state_store); +static DEVICE_ATTR(sram_size, 0644, aw8622x_sram_size_show, + aw8622x_sram_size_store); +static DEVICE_ATTR(osc_cali, 0644, aw8622x_osc_cali_show, + aw8622x_osc_cali_store); +static DEVICE_ATTR(gain, 0644, aw8622x_gain_show, aw8622x_gain_store); +static DEVICE_ATTR(ram_update, 0644, aw8622x_ram_update_show, + aw8622x_ram_update_store); +static DEVICE_ATTR(f0_save, 0644, aw8622x_f0_save_show, aw8622x_f0_save_store); +static DEVICE_ATTR(osc_save, 0644, aw8622x_osc_save_show, + aw8622x_osc_save_store); +static DEVICE_ATTR(trig, 0644, aw8622x_trig_show, aw8622x_trig_store); +static DEVICE_ATTR(ram_vbat_comp, 0644, aw8622x_ram_vbat_compensate_show, + aw8622x_ram_vbat_compensate_store); +static DEVICE_ATTR(cali, 0644, aw8622x_cali_show, aw8622x_cali_store); +static DEVICE_ATTR(cont_wait_num, 0644, aw8622x_cont_wait_num_show, + aw8622x_cont_wait_num_store); +static DEVICE_ATTR(cont_drv_lvl, 0644, aw8622x_cont_drv_lvl_show, + aw8622x_cont_drv_lvl_store); +static DEVICE_ATTR(cont_drv_time, 0644, aw8622x_cont_drv_time_show, + aw8622x_cont_drv_time_store); +static DEVICE_ATTR(cont_brk_time, 0644, aw8622x_cont_brk_time_show, + aw8622x_cont_brk_time_store); +static DEVICE_ATTR(vbat_monitor, 0644, aw8622x_vbat_monitor_show, + aw8622x_vbat_monitor_store); +static DEVICE_ATTR(lra_resistance, 0644, aw8622x_lra_resistance_show, + aw8622x_lra_resistance_store); +static DEVICE_ATTR(prctmode, 0644, aw8622x_prctmode_show, + aw8622x_prctmode_store); +static DEVICE_ATTR(gun_type, 0644, aw8622x_gun_type_show, + aw8622x_gun_type_store); +static DEVICE_ATTR(bullet_nr, 0644, aw8622x_bullet_nr_show, + aw8622x_bullet_nr_store); +static DEVICE_ATTR(haptic_audio, 0644, aw8622x_haptic_audio_show, + aw8622x_haptic_audio_store); +static DEVICE_ATTR(ram_num, 0644, aw8622x_ram_num_show, NULL); +static struct attribute *aw8622x_vibrator_attributes[] = { + &dev_attr_state.attr, + &dev_attr_duration.attr, + &dev_attr_activate.attr, + &dev_attr_activate_mode.attr, + &dev_attr_index.attr, + &dev_attr_gain.attr, + &dev_attr_seq.attr, + &dev_attr_loop.attr, + &dev_attr_register.attr, + &dev_attr_rtp.attr, + &dev_attr_ram_update.attr, + &dev_attr_f0.attr, + &dev_attr_cali.attr, + &dev_attr_f0_save.attr, + &dev_attr_osc_save.attr, + &dev_attr_cont.attr, + &dev_attr_cont_wait_num.attr, + &dev_attr_cont_drv_lvl.attr, + &dev_attr_cont_drv_time.attr, + &dev_attr_cont_brk_time.attr, + &dev_attr_vbat_monitor.attr, + &dev_attr_lra_resistance.attr, + &dev_attr_prctmode.attr, + &dev_attr_sram_size.attr, + &dev_attr_ram_vbat_comp.attr, + &dev_attr_osc_cali.attr, + &dev_attr_gun_type.attr, + &dev_attr_bullet_nr.attr, + &dev_attr_haptic_audio.attr, + &dev_attr_trig.attr, + &dev_attr_ram_num.attr, + NULL +}; + +struct attribute_group aw8622x_vibrator_attribute_group = { + .attrs = aw8622x_vibrator_attributes +}; + +static void aw8622x_long_vibrate_work_routine(struct work_struct *work) +{ + struct aw8622x *aw8622x = container_of(work, struct aw8622x, + long_vibrate_work); + + aw_dev_info(aw8622x->dev, "%s enter aw8622x->state=%d aw8622x->activate_mode=%d\n", __func__,aw8622x->state,aw8622x->activate_mode); + + mutex_lock(&aw8622x->lock); + /* Enter standby mode */ + aw8622x_haptic_stop(aw8622x); + aw8622x_haptic_upload_lra(aw8622x, F0_CALI); + if (aw8622x->state) { + if (aw8622x->activate_mode == + AW8622X_HAPTIC_ACTIVATE_RAM_MODE) { + aw8622x_haptic_ram_vbat_compensate(aw8622x, true); + aw8622x_haptic_play_repeat_seq(aw8622x, true); + aw_dev_info(aw8622x->dev, "%s mode:%s\n", __func__, + "AW8622X_HAPTIC_ACTIVATE_RAM_MODE"); + } else if (aw8622x->activate_mode == + AW8622X_HAPTIC_ACTIVATE_CONT_MODE) { + aw_dev_info(aw8622x->dev, "%s mode:%s\n", __func__, + "AW8622X_HAPTIC_ACTIVATE_CONT_MODE"); + aw8622x_haptic_cont_config(aw8622x); + } else { + aw_dev_err(aw8622x->dev, "%s: activate_mode error\n", + __func__); + } + /* run ms timer */ + aw_dev_info(aw8622x->dev, "%s set timer :%d s : %dms\n", __func__,aw8622x->duration / 1000,(aw8622x->duration % 1000) * 1000000); + hrtimer_start(&aw8622x->timer, + ktime_set(aw8622x->duration / 1000, + (aw8622x->duration % 1000) * 1000000), + HRTIMER_MODE_REL); + } + mutex_unlock(&aw8622x->lock); +} + +int aw8622x_vibrator_init(struct aw8622x *aw8622x) +{ + int ret = 0; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + +#ifdef TIMED_OUTPUT + aw_dev_info(aw8622x->dev, "%s: TIMED_OUT FRAMEWORK!\n", __func__); + aw8622x->vib_dev.name = "awinic_vibrator"; + aw8622x->vib_dev.get_time = aw8622x_vibrator_get_time; + aw8622x->vib_dev.enable = aw8622x_vibrator_enable; + + ret = timed_output_dev_register(&(aw8622x->vib_dev)); + if (ret < 0) { + aw_dev_err(aw8622x->dev, + "%s: fail to create timed output dev\n", __func__); + return ret; + } + ret = sysfs_create_group(&aw8622x->vib_dev.dev->kobj, + &aw8622x_vibrator_attribute_group); + if (ret < 0) { + aw_dev_err(aw8622x->dev, "%s error creating sysfs attr files\n", + __func__); + return ret; + } +#else + aw_dev_info(aw8622x->dev, "%s: loaded in leds_cdev framework!\n", + __func__); + aw8622x->vib_dev.name = "vibrator"; + aw8622x->vib_dev.brightness_get = aw8622x_haptic_brightness_get; + aw8622x->vib_dev.brightness_set = aw8622x_haptic_brightness_set; + + ret = devm_led_classdev_register(&aw8622x->i2c->dev, &aw8622x->vib_dev); + if (ret < 0) { + aw_dev_err(aw8622x->dev, "%s: fail to create led dev\n", + __func__); + return ret; + } + ret = sysfs_create_group(&aw8622x->vib_dev.dev->kobj, + &aw8622x_vibrator_attribute_group); + if (ret < 0) { + aw_dev_err(aw8622x->dev, "%s error creating sysfs attr files\n", + __func__); + return ret; + } +#endif + hrtimer_init(&aw8622x->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + aw8622x->timer.function = aw8622x_vibrator_timer_func; + INIT_WORK(&aw8622x->long_vibrate_work, + aw8622x_long_vibrate_work_routine); + INIT_WORK(&aw8622x->rtp_work, aw8622x_rtp_work_routine); + mutex_init(&aw8622x->lock); + mutex_init(&aw8622x->rtp_lock); + + return 0; +} + +static int aw8622x_haptic_set_pwm(struct aw8622x *aw8622x, unsigned char mode) +{ + switch (mode) { + case AW8622X_PWM_48K: + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL2, + AW8622X_BIT_SYSCTRL2_WAVDAT_MODE_MASK, + AW8622X_BIT_SYSCTRL2_RATE_48K); + break; + case AW8622X_PWM_24K: + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL2, + AW8622X_BIT_SYSCTRL2_WAVDAT_MODE_MASK, + AW8622X_BIT_SYSCTRL2_RATE_24K); + break; + case AW8622X_PWM_12K: + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL2, + AW8622X_BIT_SYSCTRL2_WAVDAT_MODE_MASK, + AW8622X_BIT_SYSCTRL2_RATE_12K); + break; + default: + break; + } + return 0; +} + +static void aw8622x_haptic_misc_para_init(struct aw8622x *aw8622x) +{ + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + aw8622x->cont_drv1_lvl = aw8622x->dts_info.cont_drv1_lvl_dt; + aw8622x->cont_drv2_lvl = aw8622x->dts_info.cont_drv2_lvl_dt; + aw8622x->cont_drv1_time = aw8622x->dts_info.cont_drv1_time_dt; + aw8622x->cont_drv2_time = aw8622x->dts_info.cont_drv2_time_dt; + aw8622x->cont_brk_time = aw8622x->dts_info.cont_brk_time_dt; + aw8622x->cont_wait_num = aw8622x->dts_info.cont_wait_num_dt; + /* SIN_H */ + aw8622x_i2c_write(aw8622x, AW8622X_REG_SYSCTRL3, + aw8622x->dts_info.sine_array[0]); + /* SIN_L */ + aw8622x_i2c_write(aw8622x, AW8622X_REG_SYSCTRL4, + aw8622x->dts_info.sine_array[1]); + /* COS_H */ + aw8622x_i2c_write(aw8622x, AW8622X_REG_SYSCTRL5, + aw8622x->dts_info.sine_array[2]); + /* COS_L */ + aw8622x_i2c_write(aw8622x, AW8622X_REG_SYSCTRL6, + aw8622x->dts_info.sine_array[3]); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_TRGCFG8, + AW8622X_BIT_TRGCFG8_TRG_TRIG1_MODE_MASK, + AW8622X_BIT_TRGCFG8_TRIG1); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_ANACFG8, + AW8622X_BIT_ANACFG8_TRTF_CTRL_HDRV_MASK, + AW8622X_BIT_ANACFG8_TRTF_CTRL_HDRV); + + /* d2s_gain */ + if (!aw8622x->dts_info.d2s_gain) { + aw_dev_err(aw8622x->dev, "%s aw8622x->dts_info.d2s_gain = 0!\n", + __func__); + } else { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL7, + AW8622X_BIT_SYSCTRL7_D2S_GAIN_MASK, + aw8622x->dts_info.d2s_gain); + } + + /* cont_tset */ + if (!aw8622x->dts_info.cont_tset) { + aw_dev_err(aw8622x->dev, + "%s aw8622x->dts_info.cont_tset = 0!\n", __func__); + } else { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_CONTCFG13, + AW8622X_BIT_CONTCFG13_TSET_MASK, + aw8622x->dts_info.cont_tset << 4); + } + + /* cont_bemf_set */ + if (!aw8622x->dts_info.cont_bemf_set) { + aw_dev_err(aw8622x->dev, "%s aw8622x->dts_info.cont_bemf_set = 0!\n", + __func__); + } else { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_CONTCFG13, + AW8622X_BIT_CONTCFG13_BEME_SET_MASK, + aw8622x->dts_info.cont_bemf_set); + } + + /* cont_brk_time */ + if (!aw8622x->cont_brk_time) { + aw_dev_err(aw8622x->dev, "%s aw8622x->cont_brk_time = 0!\n", + __func__); + } else { + aw8622x_i2c_write(aw8622x, AW8622X_REG_CONTCFG10, + aw8622x->cont_brk_time); + } + + /* cont_bst_brk_gain */ + /* + ** if (!aw8622x->dts_info.cont_bst_brk_gain) { + ** aw_dev_err(aw8622x->dev, + ** "%s aw8622x->dts_info.cont_bst_brk_gain = 0!\n", + ** __func__); + ** } else { + ** aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_CONTCFG5, + ** AW8622X_BIT_CONTCFG5_BST_BRK_GAIN_MASK, + ** aw8622x->dts_info.cont_bst_brk_gain); + ** } + */ + + /* cont_brk_gain */ + if (!aw8622x->dts_info.cont_brk_gain) { + aw_dev_err(aw8622x->dev, "%s aw8622x->dts_info.cont_brk_gain = 0!\n", + __func__); + } else { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_CONTCFG5, + AW8622X_BIT_CONTCFG5_BRK_GAIN_MASK, + aw8622x->dts_info.cont_brk_gain); + } +} + +/***************************************************** + * + * offset calibration + * + *****************************************************/ +static int aw8622x_haptic_offset_calibration(struct aw8622x *aw8622x) +{ + unsigned int cont = 2000; + unsigned char reg_val = 0; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + + aw8622x_haptic_raminit(aw8622x, true); + + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_DETCFG2, + AW8622X_BIT_DETCFG2_DIAG_GO_MASK, + AW8622X_BIT_DETCFG2_DIAG_GO_ON); + while (1) { + aw8622x_i2c_read(aw8622x, AW8622X_REG_DETCFG2, ®_val); + if ((reg_val & 0x01) == 0 || cont == 0) + break; + cont--; + } + if (cont == 0) + aw_dev_err(aw8622x->dev, "%s calibration offset failed!\n", + __func__); + aw8622x_haptic_raminit(aw8622x, false); + return 0; +} + +/***************************************************** + * + * vbat mode + * + *****************************************************/ +static int aw8622x_haptic_vbat_mode_config(struct aw8622x *aw8622x, + unsigned char flag) +{ + if (flag == AW8622X_HAPTIC_CONT_VBAT_HW_ADJUST_MODE) { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL1, + AW8622X_BIT_SYSCTRL1_VBAT_MODE_MASK, + AW8622X_BIT_SYSCTRL1_VBAT_MODE_HW); + } else { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL1, + AW8622X_BIT_SYSCTRL1_VBAT_MODE_MASK, + AW8622X_BIT_SYSCTRL1_VBAT_MODE_SW); + } + return 0; +} + +static void aw8622x_ram_work_routine(struct work_struct *work) +{ + struct aw8622x *aw8622x = container_of(work, struct aw8622x, + ram_work.work); + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + aw8622x_ram_update(aw8622x); +} + +int aw8622x_ram_work_init(struct aw8622x *aw8622x) +{ + int ram_timer_val = 8000; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + INIT_DELAYED_WORK(&aw8622x->ram_work, aw8622x_ram_work_routine); + schedule_delayed_work(&aw8622x->ram_work, + msecs_to_jiffies(ram_timer_val)); + return 0; +} +static enum hrtimer_restart +aw8622x_haptic_audio_timer_func(struct hrtimer *timer) +{ + struct aw8622x *aw8622x = container_of(timer, + struct aw8622x, haptic_audio.timer); + + aw_dev_dbg(aw8622x->dev, "%s enter\n", __func__); + schedule_work(&aw8622x->haptic_audio.work); + + hrtimer_start(&aw8622x->haptic_audio.timer, + ktime_set(aw8622x->haptic_audio.timer_val/1000000, + (aw8622x->haptic_audio.timer_val%1000000)*1000), + HRTIMER_MODE_REL); + return HRTIMER_NORESTART; +} + +static void +aw8622x_haptic_auto_bst_enable(struct aw8622x *aw8622x, unsigned char flag) +{ + if (flag) { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PLAYCFG3, + AW8622X_BIT_PLAYCFG3_BRK_EN_MASK, + AW8622X_BIT_PLAYCFG3_BRK_ENABLE); + } else { + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_PLAYCFG3, + AW8622X_BIT_PLAYCFG3_BRK_EN_MASK, + AW8622X_BIT_PLAYCFG3_BRK_DISABLE); + } +} +int aw8622x_haptic_init(struct aw8622x *aw8622x) +{ + int ret = 0; + unsigned char i = 0; + unsigned char reg_val = 0; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + /* haptic audio */ + aw8622x->haptic_audio.delay_val = 1; + aw8622x->haptic_audio.timer_val = 21318; + INIT_LIST_HEAD(&(aw8622x->haptic_audio.ctr_list)); + hrtimer_init(&aw8622x->haptic_audio.timer, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); + aw8622x->haptic_audio.timer.function = aw8622x_haptic_audio_timer_func; + INIT_WORK(&aw8622x->haptic_audio.work, + aw8622x_haptic_audio_work_routine); + mutex_init(&aw8622x->haptic_audio.lock); + aw8622x->gun_type = 0xff; + aw8622x->bullet_nr = 0x00; + + mutex_lock(&aw8622x->lock); + /* haptic init */ + aw8622x->ram_state = 0; + aw8622x->activate_mode = aw8622x->dts_info.mode; + ret = aw8622x_i2c_read(aw8622x, AW8622X_REG_WAVCFG1, ®_val); + aw8622x->index = reg_val & 0x7F; + ret = aw8622x_i2c_read(aw8622x, AW8622X_REG_PLAYCFG2, ®_val); + aw8622x->gain = reg_val & 0xFF; + aw_dev_info(aw8622x->dev, "%s aw8622x->gain =0x%02X\n", __func__, + aw8622x->gain); + for (i = 0; i < AW8622X_SEQUENCER_SIZE; i++) { + ret = aw8622x_i2c_read(aw8622x, AW8622X_REG_WAVCFG1 + i, + ®_val); + aw8622x->seq[i] = reg_val; + } + aw8622x_haptic_play_mode(aw8622x, AW8622X_HAPTIC_STANDBY_MODE); + aw8622x_haptic_set_pwm(aw8622x, AW8622X_PWM_12K); + /* misc value init */ + aw8622x_haptic_misc_para_init(aw8622x); + /* set motor protect */ + aw8622x_haptic_swicth_motor_protect_config(aw8622x, 0x00, 0x00); + aw8622x_haptic_trig_param_init(aw8622x); + aw8622x_haptic_trig_param_config(aw8622x); + aw8622x_haptic_offset_calibration(aw8622x); + /*config auto_brake*/ + aw8622x_haptic_auto_bst_enable(aw8622x, + aw8622x->dts_info.is_enabled_auto_bst); + /* vbat compensation */ + aw8622x_haptic_vbat_mode_config(aw8622x, + AW8622X_HAPTIC_CONT_VBAT_HW_ADJUST_MODE); + aw8622x->ram_vbat_compensate = AW8622X_HAPTIC_RAM_VBAT_COMP_ENABLE; + + /* f0 calibration */ + /*LRA trim source select register*/ + aw8622x_i2c_write_bits(aw8622x, + AW8622X_REG_TRIMCFG1, + AW8622X_BIT_TRIMCFG1_RL_TRIM_SRC_MASK, + AW8622X_BIT_TRIMCFG1_RL_TRIM_SRC_REG); + aw8622x_haptic_upload_lra(aw8622x, WRITE_ZERO); + aw8622x_haptic_f0_calibration(aw8622x); + mutex_unlock(&aw8622x->lock); + return ret; +} + +void aw8622x_interrupt_setup(struct aw8622x *aw8622x) +{ + unsigned char reg_val = 0; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + + aw8622x_i2c_read(aw8622x, AW8622X_REG_SYSINT, ®_val); + + aw_dev_info(aw8622x->dev, "%s: reg SYSINT=0x%02X\n", __func__, reg_val); + + /* edge int mode */ + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL7, + AW8622X_BIT_SYSCTRL7_INT_MODE_MASK, + AW8622X_BIT_SYSCTRL7_INT_MODE_EDGE); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSCTRL7, + AW8622X_BIT_SYSCTRL7_INT_EDGE_MODE_MASK, + AW8622X_BIT_SYSCTRL7_INT_EDGE_MODE_POS); + /* int enable */ + /* + *aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSINTM, + * AW8622X_BIT_SYSINTM_BST_SCPM_MASK, + * AW8622X_BIT_SYSINTM_BST_SCPM_OFF); + *aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSINTM, + * AW8622X_BIT_SYSINTM_BST_OVPM_MASK, + * AW8622X_BIT_SYSINTM_BST_OVPM_ON); + */ + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSINTM, + AW8622X_BIT_SYSINTM_UVLM_MASK, + AW8622X_BIT_SYSINTM_UVLM_ON); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSINTM, + AW8622X_BIT_SYSINTM_OCDM_MASK, + AW8622X_BIT_SYSINTM_OCDM_ON); + aw8622x_i2c_write_bits(aw8622x, AW8622X_REG_SYSINTM, + AW8622X_BIT_SYSINTM_OTM_MASK, + AW8622X_BIT_SYSINTM_OTM_ON); +} + +irqreturn_t aw8622x_irq(int irq, void *data) +{ + struct aw8622x *aw8622x = data; + unsigned char reg_val = 0; + unsigned int buf_len = 0; + unsigned char glb_state_val = 0; + + aw_dev_info(aw8622x->dev, "%s enter\n", __func__); + aw8622x_i2c_read(aw8622x, AW8622X_REG_SYSINT, ®_val); + aw_dev_info(aw8622x->dev, "%s: reg SYSINT=0x%02X\n", __func__, reg_val); + if (reg_val & AW8622X_BIT_SYSINT_UVLI) + aw_dev_err(aw8622x->dev, "%s chip uvlo int error\n", __func__); + if (reg_val & AW8622X_BIT_SYSINT_OCDI) + aw_dev_err(aw8622x->dev, "%s chip over current int error\n", + __func__); + if (reg_val & AW8622X_BIT_SYSINT_OTI) + aw_dev_err(aw8622x->dev, "%s chip over temperature int error\n", + __func__); + if (reg_val & AW8622X_BIT_SYSINT_DONEI) + aw_dev_info(aw8622x->dev, "%s chip playback done\n", __func__); + + if (reg_val & AW8622X_BIT_SYSINT_FF_AEI) { + aw_dev_info(aw8622x->dev, "%s: aw8622x rtp fifo almost empty\n", + __func__); + if (aw8622x->rtp_init) { + while ((!aw8622x_haptic_rtp_get_fifo_afs(aw8622x)) && + (aw8622x->play_mode == + AW8622X_HAPTIC_RTP_MODE)) { + mutex_lock(&aw8622x->rtp_lock); + if (!aw8622x->rtp_cnt) { + aw_dev_info(aw8622x->dev, "%s:aw8622x->rtp_cnt is 0!\n", + __func__); + mutex_unlock(&aw8622x->rtp_lock); + break; + } +#ifdef AW_ENABLE_RTP_PRINT_LOG + aw_dev_info(aw8622x->dev, + "%s: aw8622x rtp mode fifo update, cnt=%d\n", + __func__, aw8622x->rtp_cnt); +#endif + if (!aw8622x->rtp_container) { + aw_dev_info(aw8622x->dev, + "%s:aw8622x->rtp_container is null, break!\n", + __func__); + mutex_unlock(&aw8622x->rtp_lock); + break; + } + if ((aw8622x->rtp_container->len - aw8622x->rtp_cnt) < + (aw8622x->ram.base_addr >> 2)) { + buf_len = + aw8622x->rtp_container->len - aw8622x->rtp_cnt; + } else { + buf_len = (aw8622x->ram.base_addr >> 2); + } + aw8622x->rtp_update_flag = + aw8622x_i2c_writes(aw8622x, + AW8622X_REG_RTPDATA, + &aw8622x->rtp_container->data + [aw8622x->rtp_cnt], + buf_len); + aw8622x->rtp_cnt += buf_len; + aw8622x_i2c_read(aw8622x, AW8622X_REG_GLBRD5, + &glb_state_val); + if ((aw8622x->rtp_cnt == aw8622x->rtp_container->len) + || ((glb_state_val & 0x0f) == 0)) { + if (aw8622x->rtp_cnt == + aw8622x->rtp_container->len) + aw_dev_info(aw8622x->dev, + "%s: rtp load completely! glb_state_val=%02x aw8622x->rtp_cnt=%d\n", + __func__, glb_state_val, + aw8622x->rtp_cnt); + else + aw_dev_err(aw8622x->dev, + "%s rtp load failed!! glb_state_val=%02x aw8622x->rtp_cnt=%d\n", + __func__, glb_state_val, + aw8622x->rtp_cnt); + + aw8622x_haptic_set_rtp_aei(aw8622x, + false); + aw8622x->rtp_cnt = 0; + aw8622x->rtp_init = 0; + mutex_unlock(&aw8622x->rtp_lock); + break; + } + mutex_unlock(&aw8622x->rtp_lock); + } + } else { + aw_dev_info(aw8622x->dev, "%s: aw8622x rtp init = %d, init error\n", + __func__, aw8622x->rtp_init); + } + } + + if (reg_val & AW8622X_BIT_SYSINT_FF_AFI) + aw_dev_info(aw8622x->dev, "%s: aw8622x rtp mode fifo almost full!\n", + __func__); + + if (aw8622x->play_mode != AW8622X_HAPTIC_RTP_MODE) + aw8622x_haptic_set_rtp_aei(aw8622x, false); + + aw_dev_info(aw8622x->dev, "%s exit\n", __func__); + + return IRQ_HANDLED; +} + +char aw8622x_check_qualify(struct aw8622x *aw8622x) +{ + unsigned char reg = 0; + int ret = 0; + + ret = aw8622x_i2c_read(aw8622x, AW8622X_REG_EFRD9, ®); + if (ret < 0) { + aw_dev_err(aw8622x->dev, "%s: failed to read register 0x64: %d\n", + __func__, ret); + return ret; + } + if ((reg & 0x80) == 0x80) + return 1; + aw_dev_err(aw8622x->dev, "%s: register 0x64 error: 0x%02x\n", + __func__, reg); + return 0; +} diff --git a/drivers/misc/aw862xx_haptic/aw8622x.h b/drivers/misc/aw862xx_haptic/aw8622x.h new file mode 100644 index 000000000000..6bf2daf64311 --- /dev/null +++ b/drivers/misc/aw862xx_haptic/aw8622x.h @@ -0,0 +1,304 @@ +/********************************************************* + * + * aw8622x.h + * + ********************************************************/ +#ifndef _AW8622X_H_ +#define _AW8622X_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "haptic.h" +/********************************************************* + * + * Marco + * + ********************************************************/ +#if LINUX_VERSION_CODE <= KERNEL_VERSION(4, 4, 1) +#define TIMED_OUTPUT +#endif + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(4, 9, 1) +#define KERNEL_VERSION_49 +#endif + +#ifdef TIMED_OUTPUT +#include <../../../drivers/staging/android/timed_output.h> +typedef struct timed_output_dev cdev_t; +#else +typedef struct led_classdev cdev_t; +#endif + +#define AW8622X_I2C_RETRIES (5) +#define AW8622X_RTP_NAME_MAX (64) +#define AW8622X_SEQUENCER_SIZE (8) +#define AW8622X_SEQUENCER_LOOP_SIZE (4) +#define AW8622X_OSC_CALI_MAX_LENGTH (11000000) +#define AW8622X_PM_QOS_VALUE_VB (400) +#define AW8622X_VBAT_REFER (4200) +#define AW8622X_VBAT_MIN (3000) +#define AW8622X_VBAT_MAX (5500) +#define AW8622X_TRIG_NUM (3) +#define AW8622X_I2C_RETRY_DELAY (2) + + + +enum aw8622x_flags { + AW8622X_FLAG_NONR = 0, + AW8622X_FLAG_SKIP_INTERRUPTS = 1, +}; + +enum aw8622x_haptic_work_mode { + AW8622X_HAPTIC_STANDBY_MODE = 0, + AW8622X_HAPTIC_RAM_MODE = 1, + AW8622X_HAPTIC_RTP_MODE = 2, + AW8622X_HAPTIC_TRIG_MODE = 3, + AW8622X_HAPTIC_CONT_MODE = 4, + AW8622X_HAPTIC_RAM_LOOP_MODE = 5, +}; + +enum aw8622x_haptic_activate_mode { + AW8622X_HAPTIC_ACTIVATE_RAM_MODE = 0, + AW8622X_HAPTIC_ACTIVATE_CONT_MODE = 1, +}; + +enum aw8622x_haptic_cont_vbat_comp_mode { + AW8622X_HAPTIC_CONT_VBAT_SW_ADJUST_MODE = 0, + AW8622X_HAPTIC_CONT_VBAT_HW_ADJUST_MODE = 1, +}; + +enum aw8622x_haptic_ram_vbat_compensate_mode { + AW8622X_HAPTIC_RAM_VBAT_COMP_DISABLE = 0, + AW8622X_HAPTIC_RAM_VBAT_COMP_ENABLE = 1, +}; + +enum aw8622x_haptic_f0_flag { + AW8622X_HAPTIC_LRA_F0 = 0, + AW8622X_HAPTIC_CALI_F0 = 1, +}; + +enum aw8622x_sram_size_flag { + AW8622X_HAPTIC_SRAM_1K = 0, + AW8622X_HAPTIC_SRAM_2K = 1, + AW8622X_HAPTIC_SRAM_3K = 2, +}; + +enum aw8622x_haptic_pwm_mode { + AW8622X_PWM_48K = 0, + AW8622X_PWM_24K = 1, + AW8622X_PWM_12K = 2, +}; + +enum aw8622x_haptic_play { + AW8622X_HAPTIC_PLAY_NULL = 0, + AW8622X_HAPTIC_PLAY_ENABLE = 1, + AW8622X_HAPTIC_PLAY_STOP = 2, + AW8622X_HAPTIC_PLAY_GAIN = 8, +}; + +enum aw8622x_haptic_cmd { + AW8622X_HAPTIC_CMD_NULL = 0, + AW8622X_HAPTIC_CMD_ENABLE = 1, + AW8622X_HAPTIC_CMD_HAPTIC = 0x0f, + AW8622X_HAPTIC_CMD_TP = 0x10, + AW8622X_HAPTIC_CMD_SYS = 0xf0, + AW8622X_HAPTIC_CMD_STOP = 255, +}; + +enum aw8622x_haptic_cali_lra { + WRITE_ZERO = 0, + F0_CALI = 1, + OSC_CALI = 2, +}; + +enum aw8622x_haptic_rtp_mode { + AW8622X_RTP_SHORT = 4, + AW8622X_RTP_LONG = 5, + AW8622X_RTP_SEGMENT = 6, +}; + +enum aw8622x_ef_id { + AW86223_EF_ID = 0x01, + AW86224_5_EF_ID = 0x00, +}; + + + +/********************************************************* + * + * Struct Define + * + ********************************************************/ + + +/* trig_config + * trig default high level + * ___________ ___________ + * | | + * | | + * |___________| + * first edge + * second edge + * + * trig default low level + * ___________ + * | | + * | | + * __________| |__________ + * first edge + * second edge + ******************** vib_trig_config ********************* + * level polar pos_en pos_seq neg_en neg_seq brk bst + trig1* 1 0 1 1 1 2 0 0 + trig2* 1 0 0 1 0 2 0 0 + trig3* 1 0 0 1 0 2 0 0 +*/ +struct aw862xx_trig { + unsigned char trig_level; + unsigned char trig_polar; + unsigned char pos_enable; + unsigned char pos_sequence; + unsigned char neg_enable; + unsigned char neg_sequence; + unsigned char trig_brk; +}; + +struct aw8622x_dts_info { + unsigned int mode; + unsigned int f0_ref; + unsigned int f0_cali_percent; + unsigned int cont_drv1_lvl_dt; + unsigned int cont_drv2_lvl_dt; + unsigned int cont_drv1_time_dt; + unsigned int cont_drv2_time_dt; + unsigned int cont_wait_num_dt; + unsigned int cont_brk_time_dt; + unsigned int cont_track_margin; + unsigned int cont_tset; + unsigned int cont_drv_width; + unsigned int cont_bemf_set; + unsigned int cont_brk_gain; + unsigned int d2s_gain; + unsigned int prctmode[3]; + unsigned int sine_array[4]; + unsigned int trig_config[24]; + unsigned int duration_time[3]; + bool is_enabled_powerup_f0_cali; + bool is_enabled_auto_bst; +}; + +struct aw8622x { + struct regmap *regmap; + struct i2c_client *i2c; + /*struct snd_soc_codec *codec; */ + struct device *dev; + struct input_dev *input; + struct mutex lock; + struct mutex rtp_lock; + struct hrtimer timer; + struct work_struct long_vibrate_work; + struct work_struct rtp_work; + struct delayed_work ram_work; + struct aw862xx_trig trig[AW8622X_TRIG_NUM]; + struct aw8622x_dts_info dts_info; + struct ram ram; + struct aw8622x_container *rtp_container; +#ifdef KERNEL_VERSION_49 + struct timeval start, end; +#else + ktime_t kstart, kend; +#endif + cdev_t vib_dev; + + bool haptic_ready; + + unsigned char seq[AW8622X_SEQUENCER_SIZE]; + unsigned char loop[AW8622X_SEQUENCER_SIZE]; + unsigned char rtp_init; + unsigned char ram_init; + unsigned char rtp_routine_on; + unsigned char max_pos_beme; + unsigned char max_neg_beme; + unsigned char f0_cali_flag; + unsigned char ram_vbat_compensate; + unsigned char hwen_flag; + unsigned char flags; + unsigned char chipid; + unsigned char play_mode; + unsigned char activate_mode; + unsigned char ram_state; + unsigned char duration_time_size; + + char duration_time_flag; + + bool isUsedIntn; + + int name; + int reset_gpio; + int irq_gpio; + int state; + int duration; + int amplitude; + int index; + int vmax; + int gain; + int sysclk; + int rate; + int width; + int pstream; + int cstream; + + unsigned int gun_type; + unsigned int bullet_nr; + unsigned int rtp_cnt; + unsigned int rtp_file_num; + unsigned int f0; + unsigned int cont_f0; + unsigned int cont_drv1_lvl; + unsigned int cont_drv2_lvl; + unsigned int cont_brk_time; + unsigned int cont_wait_num; + unsigned int cont_drv1_time; + unsigned int cont_drv2_time; + unsigned int theory_time; + unsigned int vbat; + unsigned int lra; + unsigned int ram_update_flag; + unsigned int rtp_update_flag; + unsigned int osc_cali_data; + unsigned int f0_cali_data; + unsigned int timeval_flags; + unsigned int osc_cali_flag; + unsigned int sys_frequency; + unsigned int rtp_len; + unsigned long int microsecond; + struct haptic_audio haptic_audio; + /* ram monitor */ +#ifdef AW_RAM_STATE_OUTPUT + struct delayed_work ram_monitor_work; +#endif +}; + +struct aw8622x_container { + int len; + unsigned char data[]; +}; +extern char aw8622x_check_qualify(struct aw8622x *aw8622x); + +extern int aw8622x_parse_dt(struct aw8622x *aw8622x, struct device *dev, + struct device_node *np); +extern void aw8622x_interrupt_setup(struct aw8622x *aw8622x); +extern int aw8622x_vibrator_init(struct aw8622x *aw8622x); +extern int aw8622x_haptic_init(struct aw8622x *aw8622x); +extern int aw8622x_ram_work_init(struct aw8622x *aw8622x); +extern irqreturn_t aw8622x_irq(int irq, void *data); + +extern struct attribute_group aw8622x_vibrator_attribute_group; +#endif diff --git a/drivers/misc/aw862xx_haptic/aw8622x_reg.h b/drivers/misc/aw862xx_haptic/aw8622x_reg.h new file mode 100644 index 000000000000..092e24c67d61 --- /dev/null +++ b/drivers/misc/aw862xx_haptic/aw8622x_reg.h @@ -0,0 +1,856 @@ +/********************************************************* + * + * aw8622x_reg.h + * + ********************************************************/ +#ifndef _AW8622X_REG_H_ +#define _AW8622X_REG_H_ + +/******************************************** + * Register List + *******************************************/ +#define AW8622X_REG_ID (0x00) +#define AW8622X_REG_SYSST (0x01) +#define AW8622X_REG_SYSINT (0x02) +#define AW8622X_REG_SYSINTM (0x03) +#define AW8622X_REG_SYSST2 (0x04) +#define AW8622X_REG_SYSER (0x05) +#define AW8622X_REG_PLAYCFG2 (0x07) +#define AW8622X_REG_PLAYCFG3 (0x08) +#define AW8622X_REG_PLAYCFG4 (0x09) +#define AW8622X_REG_WAVCFG1 (0x0A) +#define AW8622X_REG_WAVCFG2 (0x0B) +#define AW8622X_REG_WAVCFG3 (0x0C) +#define AW8622X_REG_WAVCFG4 (0x0D) +#define AW8622X_REG_WAVCFG5 (0x0E) +#define AW8622X_REG_WAVCFG6 (0x0F) +#define AW8622X_REG_WAVCFG7 (0x10) +#define AW8622X_REG_WAVCFG8 (0x11) +#define AW8622X_REG_WAVCFG9 (0x12) +#define AW8622X_REG_WAVCFG10 (0x13) +#define AW8622X_REG_WAVCFG11 (0x14) +#define AW8622X_REG_WAVCFG12 (0x15) +#define AW8622X_REG_WAVCFG13 (0x16) +#define AW8622X_REG_WAVCFG14 (0x17) +#define AW8622X_REG_CONTCFG1 (0x18) +#define AW8622X_REG_CONTCFG2 (0x19) +#define AW8622X_REG_CONTCFG3 (0x1A) +#define AW8622X_REG_CONTCFG4 (0x1B) +#define AW8622X_REG_CONTCFG5 (0x1C) +#define AW8622X_REG_CONTCFG6 (0x1D) +#define AW8622X_REG_CONTCFG7 (0x1E) +#define AW8622X_REG_CONTCFG8 (0x1F) +#define AW8622X_REG_CONTCFG9 (0x20) +#define AW8622X_REG_CONTCFG10 (0x21) +#define AW8622X_REG_CONTCFG11 (0x22) +#define AW8622X_REG_CONTCFG12 (0x23) +#define AW8622X_REG_CONTCFG13 (0x24) +#define AW8622X_REG_CONTRD14 (0x25) +#define AW8622X_REG_CONTRD15 (0x26) +#define AW8622X_REG_CONTRD16 (0x27) +#define AW8622X_REG_CONTRD17 (0x28) +#define AW8622X_REG_CONTRD18 (0x29) +#define AW8622X_REG_CONTRD19 (0x2A) +#define AW8622X_REG_CONTRD20 (0x2B) +#define AW8622X_REG_CONTRD21 (0x2C) +#define AW8622X_REG_RTPCFG1 (0x2D) +#define AW8622X_REG_RTPCFG2 (0x2E) +#define AW8622X_REG_RTPCFG3 (0x2F) +#define AW8622X_REG_RTPCFG4 (0x30) +#define AW8622X_REG_RTPCFG5 (0x31) +#define AW8622X_REG_RTPDATA (0x32) +#define AW8622X_REG_TRGCFG1 (0x33) +#define AW8622X_REG_TRGCFG2 (0x34) +#define AW8622X_REG_TRGCFG3 (0x35) +#define AW8622X_REG_TRGCFG4 (0x36) +#define AW8622X_REG_TRGCFG5 (0x37) +#define AW8622X_REG_TRGCFG6 (0x38) +#define AW8622X_REG_TRGCFG7 (0x39) +#define AW8622X_REG_TRGCFG8 (0x3A) +#define AW8622X_REG_GLBCFG1 (0x3B) +#define AW8622X_REG_GLBCFG2 (0x3C) +#define AW8622X_REG_GLBCFG3 (0x3D) +#define AW8622X_REG_GLBCFG4 (0x3E) +#define AW8622X_REG_GLBRD5 (0x3F) +#define AW8622X_REG_RAMADDRH (0x40) +#define AW8622X_REG_RAMADDRL (0x41) +#define AW8622X_REG_RAMDATA (0x42) +#define AW8622X_REG_SYSCTRL1 (0x43) +#define AW8622X_REG_SYSCTRL2 (0x44) +#define AW8622X_REG_SYSCTRL3 (0x45) +#define AW8622X_REG_SYSCTRL4 (0x46) +#define AW8622X_REG_SYSCTRL5 (0x47) +#define AW8622X_REG_SYSCTRL6 (0x48) +#define AW8622X_REG_SYSCTRL7 (0x49) +#define AW8622X_REG_PWMCFG1 (0x4C) +#define AW8622X_REG_PWMCFG2 (0x4D) +#define AW8622X_REG_PWMCFG3 (0x4E) +#define AW8622X_REG_PWMCFG4 (0x4F) +#define AW8622X_REG_TMCFG (0x50) +#define AW8622X_REG_DETCFG1 (0x51) +#define AW8622X_REG_DETCFG2 (0x52) +#define AW8622X_REG_DET_RL (0x53) +#define AW8622X_REG_DET_OS (0x54) +#define AW8622X_REG_DET_VBAT (0x55) +#define AW8622X_REG_DET_TEST (0x56) +#define AW8622X_REG_DET_LO (0x57) +#define AW8622X_REG_TRIMCFG1 (0x58) +#define AW8622X_REG_TRIMCFG3 (0x5A) +#define AW8622X_REG_TRIMCFG4 (0x5B) +#define AW8622X_REG_EFCFG1 (0x5C) +#define AW8622X_REG_EFCFG2 (0x5D) +#define AW8622X_REG_EFCFG3 (0x5E) +#define AW8622X_REG_EFCFG4 (0x5F) +#define AW8622X_REG_EFCFG5 (0x60) +#define AW8622X_REG_EFCFG6 (0x61) +#define AW8622X_REG_EFRD9 (0x64) +#define AW8622X_REG_EFRD10 (0x65) +#define AW8622X_REG_ANACFG1 (0x6A) +#define AW8622X_REG_ANACFG2 (0x6B) +#define AW8622X_REG_ANACFG3 (0x6C) +#define AW8622X_REG_ANACFG4 (0x73) +#define AW8622X_REG_ANACFG5 (0x74) +#define AW8622X_REG_ANACFG6 (0x75) +#define AW8622X_REG_ANACFG7 (0x76) +#define AW8622X_REG_ANACFG8 (0x77) +#define AW8622X_REG_SPACE (0x78) +#define AW8622X_REG_MBIST (0x79) + +/******************************************** + * Register Access + *******************************************/ +#define REG_NONE_ACCESS (0) +#define REG_RD_ACCESS (1 << 0) +#define REG_WR_ACCESS (1 << 1) +#define AW8622X_REG_MAX 0xff +const unsigned char aw8622x_reg_access[AW8622X_REG_MAX] = { + [AW8622X_REG_ID] = REG_RD_ACCESS, + [AW8622X_REG_SYSST] = REG_RD_ACCESS, + [AW8622X_REG_SYSINT] = REG_RD_ACCESS, + [AW8622X_REG_SYSINTM] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_SYSST2] = REG_RD_ACCESS, + [AW8622X_REG_SYSER] = REG_RD_ACCESS, + [AW8622X_REG_PLAYCFG2] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_PLAYCFG3] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_PLAYCFG4] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_WAVCFG1] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_WAVCFG2] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_WAVCFG3] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_WAVCFG4] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_WAVCFG5] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_WAVCFG6] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_WAVCFG7] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_WAVCFG8] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_WAVCFG9] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_WAVCFG10] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_WAVCFG11] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_WAVCFG12] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_WAVCFG13] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_WAVCFG14] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_CONTCFG1] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_CONTCFG2] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_CONTCFG3] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_CONTCFG4] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_CONTCFG5] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_CONTCFG6] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_CONTCFG7] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_CONTCFG8] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_CONTCFG9] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_CONTCFG10] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_CONTCFG11] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_CONTCFG12] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_CONTCFG13] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_CONTRD14] = REG_RD_ACCESS, + [AW8622X_REG_CONTRD15] = REG_RD_ACCESS, + [AW8622X_REG_CONTRD16] = REG_RD_ACCESS, + [AW8622X_REG_CONTRD17] = REG_RD_ACCESS, + [AW8622X_REG_CONTRD18] = REG_RD_ACCESS, + [AW8622X_REG_CONTRD19] = REG_RD_ACCESS, + [AW8622X_REG_CONTRD20] = REG_RD_ACCESS, + [AW8622X_REG_CONTRD21] = REG_RD_ACCESS, + [AW8622X_REG_RTPCFG1] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_RTPCFG2] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_RTPCFG3] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_RTPCFG4] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_RTPCFG5] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_RTPDATA] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_TRGCFG1] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_TRGCFG2] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_TRGCFG3] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_TRGCFG4] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_TRGCFG5] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_TRGCFG6] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_TRGCFG7] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_TRGCFG8] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_GLBCFG1] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_GLBCFG2] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_GLBCFG3] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_GLBCFG4] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_GLBRD5] = REG_RD_ACCESS, + [AW8622X_REG_RAMADDRH] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_RAMADDRL] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_RAMDATA] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_SYSCTRL1] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_SYSCTRL2] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_SYSCTRL3] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_SYSCTRL4] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_SYSCTRL5] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_SYSCTRL6] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_SYSCTRL7] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_PWMCFG1] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_PWMCFG2] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_PWMCFG3] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_PWMCFG4] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_DETCFG1] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_DETCFG2] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_DET_RL] = REG_RD_ACCESS, + [AW8622X_REG_DET_OS] = REG_RD_ACCESS, + [AW8622X_REG_DET_VBAT] = REG_RD_ACCESS, + [AW8622X_REG_DET_TEST] = REG_RD_ACCESS, + [AW8622X_REG_DET_LO] = REG_RD_ACCESS, + [AW8622X_REG_TRIMCFG1] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_TRIMCFG3] = REG_RD_ACCESS | REG_WR_ACCESS, + [AW8622X_REG_TRIMCFG4] = REG_RD_ACCESS | REG_WR_ACCESS, +}; + +/****************************************************** + * Register Detail + *****************************************************/ +/* SYSST: reg 0x01 RO */ +#define AW8622X_BIT_SYSST_UVLS (1<<5) +#define AW8622X_BIT_SYSST_FF_AES (1<<4) +#define AW8622X_BIT_SYSST_FF_AFS (1<<3) +#define AW8622X_BIT_SYSST_OCDS (1<<2) +#define AW8622X_BIT_SYSST_OTS (1<<1) +#define AW8622X_BIT_SYSST_DONES (1<<0) + +/* SYSINT: reg 0x02 RC */ +#define AW8622X_BIT_SYSINT_UVLI (1<<5) +#define AW8622X_BIT_SYSINT_FF_AEI (1<<4) +#define AW8622X_BIT_SYSINT_FF_AFI (1<<3) +#define AW8622X_BIT_SYSINT_OCDI (1<<2) +#define AW8622X_BIT_SYSINT_OTI (1<<1) +#define AW8622X_BIT_SYSINT_DONEI (1<<0) + +/* SYSINTM: reg 0x03 RW */ +#define AW8622X_BIT_SYSINTM_UVLM_MASK (~(1<<5)) +#define AW8622X_BIT_SYSINTM_UVLM_OFF (1<<5) +#define AW8622X_BIT_SYSINTM_UVLM_ON (0<<5) +#define AW8622X_BIT_SYSINTM_FF_AEM_MASK (~(1<<4)) +#define AW8622X_BIT_SYSINTM_FF_AEM_OFF (1<<4) +#define AW8622X_BIT_SYSINTM_FF_AEM_ON (0<<4) +#define AW8622X_BIT_SYSINTM_FF_AFM_MASK (~(1<<3)) +#define AW8622X_BIT_SYSINTM_FF_AFM_OFF (1<<3) +#define AW8622X_BIT_SYSINTM_FF_AFM_ON (0<<3) +#define AW8622X_BIT_SYSINTM_OCDM_MASK (~(1<<2)) +#define AW8622X_BIT_SYSINTM_OCDM_OFF (1<<2) +#define AW8622X_BIT_SYSINTM_OCDM_ON (0<<2) +#define AW8622X_BIT_SYSINTM_OTM_MASK (~(1<<1)) +#define AW8622X_BIT_SYSINTM_OTM_OFF (1<<1) +#define AW8622X_BIT_SYSINTM_OTM_ON (0<<1) +#define AW8622X_BIT_SYSINTM_DONEM_MASK (~(1<<0)) +#define AW8622X_BIT_SYSINTM_DONEM_OFF (1<<0) +#define AW8622X_BIT_SYSINTM_DONEM_ON (0<<0) + +/* SYSST2: reg 0x04 RO */ +#define AW8622X_BIT_SYSST2_RAM_ADDR_ER (1<<7) +#define AW8622X_BIT_SYSST2_TRG_ADDR_ER (1<<6) +#define AW8622X_BIT_SYSST2_VBG_OK (1<<3) +#define AW8622X_BIT_SYSST2_LDO_OK (1<<2) +#define AW8622X_BIT_SYSST2_FF_FULL (1<<1) +#define AW8622X_BIT_SYSST2_FF_EMPTY (1<<0) + +/* SYSER: reg 0x05 RC */ +#define AW8622X_BIT_SYSER_I2S_ERR (1<<7) +#define AW8622X_BIT_SYSER_TRIG1_EVENT (1<<6) +#define AW8622X_BIT_SYSER_TRIG2_EVENT (1<<5) +#define AW8622X_BIT_SYSER_TRIG3_EVENT (1<<4) +#define AW8622X_BIT_SYSER_OV (1<<3) +#define AW8622X_BIT_SYSER_ADDR_ER (1<<2) +#define AW8622X_BIT_SYSER_FF_ER (1<<1) +#define AW8622X_BIT_SYSER_PLL_REF_ER (1<<0) + + +/* PLAYCFG2: reg 0x07 RW */ +/* GAIN */ + +/* PLAYCFG3: reg 0x08 RW */ +#define AW8622X_BIT_PLAYCFG3_STOP_MODE_MASK (~(1<<5)) +#define AW8622X_BIT_PLAYCFG3_STOP_MODE_NOW (1<<5) +#define AW8622X_BIT_PLAYCFG3_STOP_MODE_LATER (0<<5) +#define AW8622X_BIT_PLAYCFG3_BRK_EN_MASK (~(1<<2)) +#define AW8622X_BIT_PLAYCFG3_BRK_ENABLE (1<<2) +#define AW8622X_BIT_PLAYCFG3_BRK_DISABLE (0<<2) +#define AW8622X_BIT_PLAYCFG3_PLAY_MODE_MASK (~(3<<0)) +#define AW8622X_BIT_PLAYCFG3_PLAY_MODE_STOP (3<<0) +#define AW8622X_BIT_PLAYCFG3_PLAY_MODE_CONT (2<<0) +#define AW8622X_BIT_PLAYCFG3_PLAY_MODE_RTP (1<<0) +#define AW8622X_BIT_PLAYCFG3_PLAY_MODE_RAM (0<<0) + +/* PLAYCFG4: reg 0x09 RW */ +#define AW8622X_BIT_PLAYCFG4_STOP_MASK (~(1<<1)) +#define AW8622X_BIT_PLAYCFG4_STOP_ON (1<<1) +#define AW8622X_BIT_PLAYCFG4_STOP_OFF (0<<1) +#define AW8622X_BIT_PLAYCFG4_GO_MASK (~(1<<0)) +#define AW8622X_BIT_PLAYCFG4_GO_ON (1<<0) +#define AW8622X_BIT_PLAYCFG4_GO_OFF (0<<0) + +/* WAVCFG1-8: reg 0x0A - reg 0x11 RW */ +#define AW8622X_BIT_WAVCFG_SEQWAIT_MASK (~(1<<7)) +#define AW8622X_BIT_WAVCFG_SEQWAIT_TIME (1<<7) +#define AW8622X_BIT_WAVCFG_SEQWAIT_NUMBER (0<<7) + +/* WAVCFG9-12: reg 0x12 - reg 0x15 RW */ +#define AW8622X_BIT_WAVLOOP_SEQ_ODD_MASK (~(0x0F<<4)) +#define AW8622X_BIT_WAVLOOP_SEQ_ODD_INIFINITELY (0x0F<<4) +#define AW8622X_BIT_WAVLOOP_SEQ_EVEN_MASK (~(0x0F<<0)) +#define AW8622X_BIT_WAVLOOP_SEQ_EVEN_INIFINITELY (0x0F<<0) +#define AW8622X_BIT_WAVLOOP_INIFINITELY (0x0F<<0) + +/* WAVCFG9: reg 0x12 RW */ +#define AW8622X_BIT_WAVCFG9_SEQ1LOOP_MASK (~(0x0F<<4)) +#define AW8622X_BIT_WAVCFG9_SEQ1LOOP_INIFINITELY (0x0F<<4) +#define AW8622X_BIT_WAVCFG9_SEQ2LOOP_MASK (~(0x0F<<0)) +#define AW8622X_BIT_WAVCFG9_SEQ2LOOP_INIFINITELY (0x0F<<0) + +/* WAVCFG10: reg 0x13 RW */ +#define AW8622X_BIT_WAVCFG10_SEQ3LOOP_MASK (~(0x0F<<4)) +#define AW8622X_BIT_WAVCFG10_SEQ3LOOP_INIFINITELY (0x0F<<4) +#define AW8622X_BIT_WAVCFG10_SEQ4LOOP_MASK (~(0x0F<<0)) +#define AW8622X_BIT_WAVCFG10_SEQ4LOOP_INIFINITELY (0x0F<<0) + +/* WAVCFG11: reg 0x14 RW */ +#define AW8622X_BIT_WAVCFG11_SEQ5LOOP_MASK (~(0x0F<<4)) +#define AW8622X_BIT_WAVCFG11_SEQ5LOOP_INIFINITELY (0x0F<<4) +#define AW8622X_BIT_WAVCFG11_SEQ6LOOP_MASK (~(0x0F<<0)) +#define AW8622X_BIT_WAVCFG11_SEQ6LOOP_INIFINITELY (0x0F<<0) + +/* WAVCFG12: reg 0x15 RW */ +#define AW8622X_BIT_WAVCFG12_SEQ7LOOP_MASK (~(0x0F<<4)) +#define AW8622X_BIT_WAVCFG12_SEQ7LOOP_INIFINITELY (0x0F<<4) +#define AW8622X_BIT_WAVCFG12_SEQ8LOOP_MASK (~(0x0F<<0)) +#define AW8622X_BIT_WAVCFG12_SEQ8LOOP_INIFINITELY (0x0F<<0) + +/* WAVCFG13: reg 0x16 RW */ +#define AW8622X_BIT_WAVCFG13_WAITSLOT_MASK (~(3<<5)) +#define AW8622X_BIT_WAVCFG13_WAITSLOT_DIV_1 (0<<5) +#define AW8622X_BIT_WAVCFG13_WAITSLOT_DIV_8 (1<<5) +#define AW8622X_BIT_WAVCFG13_WAITSLOT_DIV_64 (2<<5) +#define AW8622X_BIT_WAVCFG13_WAITSLOT_DIV_512 (3<<5) +#define AW8622X_BIT_WAVCFG13_AUTO_MD_MASK (~(1<<4)) +#define AW8622X_BIT_WAVCFG13_AUTO_MD_CONT_MODE (1<<4) +#define AW8622X_BIT_WAVCFG13_AUTO_MD_SIN_WAV (0<<4) +#define AW8622X_BIT_WAVCFG13_MAINLOOP_MASK (~(0x0F<<0)) +#define AW8622X_BIT_WAVCFG13_MAINLOOP_INIFINITELY (0x0F<<0) + +/* WAVCFG14: reg 0x17 RW */ +/* AUTO_WAV */ + +/***************** CONT *****************/ +/* CONTCFG1: reg 0x18 RW */ +#define AW8622X_BIT_CONTCFG1_EDGE_FRE_MASK (~(0x0F<<4)) +#define AW8622X_BIT_CONTCFG1_EN_F0_DET_MASK (~(1<<3)) +#define AW8622X_BIT_CONTCFG1_F0_DET_ENABLE (1<<3) +#define AW8622X_BIT_CONTCFG1_F0_DET_DISABLE (0<<3) +#define AW8622X_BIT_CONTCFG1_SIN_MODE_MASK (~(1<<0)) +#define AW8622X_BIT_CONTCFG1_SIN_MODE_COS (1<<0) +#define AW8622X_BIT_CONTCFG1_SIN_MODE_SINE (0<<0) + +/* CONTCFG2: reg 0x19 RW */ +/* F_PRE */ + +/* CONTCFG3: reg 0x1A RW */ +/* DRV_WIDTH */ + +/* CONTCFG4: reg 0x1B RW */ +/* WAIT_NUM */ + +/* CONTCFG5: reg 0x1C RW */ +#define AW8622X_BIT_CONTCFG5_BRK_GAIN_MASK (~(0x0F<<0)) + +/* CONTCFG6: reg 0x1D RW */ +#define AW8622X_BIT_CONTCFG6_TRACK_EN_MASK (~(1<<7)) +#define AW8622X_BIT_CONTCFG6_TRACK_ENABLE (1<<7) +#define AW8622X_BIT_CONTCFG6_TRACK_DISABLE (0<<7) +#define AW8622X_BIT_CONTCFG6_DRV1_LVL_MASK (~(0x7F<<0)) + +/* CONTCFG7: reg 0x1E RW */ +#define AW8622X_BIT_CONTCFG7_DRV2_LVL_MASK (~(0x7F<<0)) + +/* CONTCFG8: reg 0x1F RW */ +/* DRV1_TIME */ + +/* CONTCFG9: reg 0x20 RW */ +/* DRV2_TIME */ + +/* CONTCFG10: reg 0x21 RW */ +/* BRK_TIME */ + +/* CONTCFG11: reg 0x22 RW */ +/* TRACK_MARGIN */ + +/* CONTCFG12: reg 0x23 RW */ +/* MBRK_TH */ + +/* CONTCFG13: reg 0x24 RW */ +#define AW8622X_BIT_CONTCFG13_TSET_MASK (~(0x0F<<4)) +#define AW8622X_BIT_CONTCFG13_BEME_SET_MASK (~(0x0F<<0)) + +/* CONTRD14: reg 0x25 RO */ +/* F_LRA_F0_H */ + +/* CONTRD15: reg 0x26 RO */ +/* F_LRA_F0_L */ + +/* CONTRD16: reg 0x27 RO */ +/* CONT_F0_H */ + +/* CONTRD17: reg 0x28 RO */ +/* CONT_F0_L */ + +/* CONTRD18: reg 0x29 RO */ +/* ACCELERATION */ + +/* CONTRD19: reg 0x2A RO */ +/* BEMF_PEAK1 */ + +/* CONTRD20: reg 0x2B RO */ +/* BEMF_PEAK2 */ + +/* CONTRD21: reg 0x2C RO */ +/* BEMF_PEAK3 */ + +/***************** RTP *****************/ +/* RTPCFG1: reg 0x2D RW */ +#define AW8622X_BIT_RTPCFG1_ADDRH_MASK (~(0x0F<<0)) + +#define AW8622X_BIT_RTPCFG1_SRAM_SIZE_2K_MASK (~(1<<5)) +#define AW8622X_BIT_RTPCFG1_SRAM_SIZE_2K_EN (1<<5) +#define AW8622X_BIT_RTPCFG1_SRAM_SIZE_2K_DIS (0<<5) + +#define AW8622X_BIT_RTPCFG1_SRAM_SIZE_1K_MASK (~(1<<4)) +#define AW8622X_BIT_RTPCFG1_SRAM_SIZE_1K_EN (1<<4) +#define AW8622X_BIT_RTPCFG1_SRAM_SIZE_1K_DIS (0<<4) +/* BASE_ADDR_H */ + +/* RTPCFG2: reg 0x2E RW */ +/* BASE_ADDR_L */ + +/* RTPCFG3: reg 0x2F RW */ +#define AW8622X_BIT_RTPCFG3_FIFO_AEH_MASK (~(0x0F<<4)) +#define AW8622X_BIT_RTPCFG3_FIFO_AFH_MASK (~(0x0F<<0)) + +/* RTPCFG4: reg 0x30 RW */ +/* FIFO_AEL */ + +/* RTPCFG5: reg 0x31 RW */ +/* FIFO_AFL */ + +/* RTPDATA: reg 0x32 RW */ +/* FIFO_AFL */ + +/***************** TRIGGER *****************/ +#define AW8622X_BIT_TRG_ENABLE_MASK (~(1<<7)) +#define AW8622X_BIT_TRG_ENABLE (1<<7) +#define AW8622X_BIT_TRG_DISABLE (0<<7) +#define AW8622X_BIT_TRG_SEQ_MASK (~(0x7F<<0)) + +/* TRGCFG1: reg 0x33 RW */ +#define AW8622X_BIT_TRGCFG1_TRG1_POS_MASK (~(1<<7)) +#define AW8622X_BIT_TRGCFG1_TRG1_POS_ENABLE (1<<7) +#define AW8622X_BIT_TRGCFG1_TRG1_POS_DISABLE (0<<7) +#define AW8622X_BIT_TRGCFG1_TRG1SEQ_P_MASK (~(0x7F<<0)) + +/* TRGCFG2: reg 0x34 RW */ +#define AW8622X_BIT_TRGCFG2_TRG2_POS_MASK (~(1<<7)) +#define AW8622X_BIT_TRGCFG2_TRG2_POS_ENABLE (1<<7) +#define AW8622X_BIT_TRGCFG2_TRG2_POS_DISABLE (0<<7) +#define AW8622X_BIT_TRGCFG2_TRG2SEQ_P_MASK (~(0x7F<<0)) + +/* TRGCFG3: reg 0x35 RW */ +#define AW8622X_BIT_TRGCFG3_TRG3_POS_MASK (~(1<<7)) +#define AW8622X_BIT_TRGCFG3_TRG3_POS_ENABLE (1<<7) +#define AW8622X_BIT_TRGCFG3_TRG3_POS_DISABLE (0<<7) +#define AW8622X_BIT_TRGCFG3_TRG3SEQ_P_MASK (~(0x7F<<0)) + +/* TRGCFG4: reg 0x36 RW */ +#define AW8622X_BIT_TRGCFG4_TRG1_NEG_MASK (~(1<<7)) +#define AW8622X_BIT_TRGCFG4_TRG1_NEG_ENABLE (1<<7) +#define AW8622X_BIT_TRGCFG4_TRG1_NEG_DISABLE (0<<7) +#define AW8622X_BIT_TRGCFG4_TRG1SEQ_N_MASK (~(0x7F<<0)) + +/* TRGCFG5: reg 0x37 RW */ +#define AW8622X_BIT_TRGCFG5_TRG2_NEG_MASK (~(1<<7)) +#define AW8622X_BIT_TRGCFG5_TRG2_NEG_ENABLE (1<<7) +#define AW8622X_BIT_TRGCFG5_TRG2_NEG_DISABLE (0<<7) +#define AW8622X_BIT_TRGCFG5_TRG2SEQ_N_MASK (~(0x7F<<0)) + +/* TRGCFG6: reg 0x38 RW */ +#define AW8622X_BIT_TRGCFG6_TRG3_NEG_MASK (~(1<<7)) +#define AW8622X_BIT_TRGCFG6_TRG3_NEG_ENABLE (1<<7) +#define AW8622X_BIT_TRGCFG6_TRG3_NEG_DISABLE (0<<7) +#define AW8622X_BIT_TRGCFG6_TRG3SEQ_N_MASK (~(0x7F<<0)) + +/* TRGCFG7: reg 0x39 RW */ +#define AW8622X_BIT_TRGCFG7_TRG1_POR_LEV_BRK_MASK (~(7<<5)) +#define AW8622X_BIT_TRGCFG7_TRG2_POR_LEV_BRK_MASK (~(7<<1)) +#define AW8622X_BIT_TRGCFG7_TRG1_POLAR_MASK (~(1<<7)) +#define AW8622X_BIT_TRGCFG7_TRG1_POLAR_NEG (1<<7) +#define AW8622X_BIT_TRGCFG7_TRG1_POLAR_POS (0<<7) +#define AW8622X_BIT_TRGCFG7_TRG1_MODE_MASK (~(1<<6)) +#define AW8622X_BIT_TRGCFG7_TRG1_MODE_LEVEL (1<<6) +#define AW8622X_BIT_TRGCFG7_TRG1_MODE_EDGE (0<<6) +#define AW8622X_BIT_TRGCFG7_TRG1_AUTO_BRK_MASK (~(1<<5)) +#define AW8622X_BIT_TRGCFG7_TRG1_AUTO_BRK_ENABLE (1<<5) +#define AW8622X_BIT_TRGCFG7_TRG1_AUTO_BRK_DISABLE (0<<5) +#define AW8622X_BIT_TRGCFG7_TRG2_POLAR_MASK (~(1<<3)) +#define AW8622X_BIT_TRGCFG7_TRG2_POLAR_NEG (1<<3) +#define AW8622X_BIT_TRGCFG7_TRG2_POLAR_POS (0<<3) +#define AW8622X_BIT_TRGCFG7_TRG2_MODE_MASK (~(1<<2)) +#define AW8622X_BIT_TRGCFG7_TRG2_MODE_LEVEL (1<<2) +#define AW8622X_BIT_TRGCFG7_TRG2_MODE_EDGE (0<<2) +#define AW8622X_BIT_TRGCFG7_TRG2_AUTO_BRK_MASK (~(1<<1)) +#define AW8622X_BIT_TRGCFG7_TRG2_AUTO_BRK_ENABLE (1<<1) +#define AW8622X_BIT_TRGCFG7_TRG2_AUTO_BRK_DISABLE (0<<1) + +/* TRGCFG8: reg 0x3A RW */ +#define AW8622X_BIT_TRGCFG8_TRG3_POR_LEV_BRK_MASK (~(7<<5)) +#define AW8622X_BIT_TRGCFG8_TRG3_POLAR_MASK (~(1<<7)) +#define AW8622X_BIT_TRGCFG8_TRG3_POLAR_NEG (1<<7) +#define AW8622X_BIT_TRGCFG8_TRG3_POLAR_POS (0<<7) +#define AW8622X_BIT_TRGCFG8_TRG3_MODE_MASK (~(1<<6)) +#define AW8622X_BIT_TRGCFG8_TRG3_MODE_LEVEL (1<<6) +#define AW8622X_BIT_TRGCFG8_TRG3_MODE_EDGE (0<<6) +#define AW8622X_BIT_TRGCFG8_TRG3_AUTO_BRK_MASK (~(1<<5)) +#define AW8622X_BIT_TRGCFG8_TRG3_AUTO_BRK_ENABLE (1<<5) +#define AW8622X_BIT_TRGCFG8_TRG3_AUTO_BRK_DISABLE (0<<5) +#define AW8622X_BIT_TRGCFG8_TRG_TRIG1_MODE_MASK (~(3<<3)) +#define AW8622X_BIT_TRGCFG8_PWM_LRA (0<<3) +#define AW8622X_BIT_TRGCFG8_PWM_ERA (1<<3) +#define AW8622X_BIT_TRGCFG8_TRIG1 (2<<3) +#define AW8622X_BIT_TRGCFG8_DISABLE (3<<3) +#define AW8622X_BIT_TRGCFG8_TRG1_STOP_MASK (~(1<<2)) +#define AW8622X_BIT_TRGCFG8_TRG1_STOP (1<<2) +#define AW8622X_BIT_TRGCFG8_TRG2_STOP_MASK (~(1<<1)) +#define AW8622X_BIT_TRGCFG8_TRG2_STOP (1<<1) +#define AW8622X_BIT_TRGCFG8_TRG3_STOP_MASK (~(1<<0)) +#define AW8622X_BIT_TRGCFG8_TRG3_STOP (1<<0) + +/* GLBCFG1: reg 0x3B RW */ +/* WAKE_DLY */ + +/* GLBCFG2: reg 0x3C RW */ +/* START_DLY */ + +/* GLBCFG3: reg 0x3D RW */ +/* END_DLY */ + +/* GLBCFG4: reg 0x3E RW */ +#define AW8622X_BIT_GLBCFG4_GO_PRIO_MASK (~(3<<6)) +#define AW8622X_BIT_GLBCFG4_TRG3_PRIO_MASK (~(3<<4)) +#define AW8622X_BIT_GLBCFG4_TRG2_PRIO_MASK (~(3<<2)) +#define AW8622X_BIT_GLBCFG4_TRG1_PRIO_MASK (~(3<<0)) + +/* GLBRD5: reg 0x3F R0 */ +/* GLB_STATE */ +#define AW8622X_BIT_GLBRD5_STATE_MASK (~(15<<0)) +#define AW8622X_BIT_GLBRD5_STATE_STANDBY (0<<0) +#define AW8622X_BIT_GLBRD5_STATE_WAKEUP (1<<0) +#define AW8622X_BIT_GLBRD5_STATE_STARTUP (2<<0) +#define AW8622X_BIT_GLBRD5_STATE_WAIT (3<<0) +#define AW8622X_BIT_GLBRD5_STATE_CONT_GO (6<<0) +#define AW8622X_BIT_GLBRD5_STATE_RAM_GO (7<<0) +#define AW8622X_BIT_GLBRD5_STATE_RTP_GO (8<<0) +#define AW8622X_BIT_GLBRD5_STATE_TRIG_GO (9<<0) +#define AW8622X_BIT_GLBRD5_STATE_I2S_GO (10<<0) +#define AW8622X_BIT_GLBRD5_STATE_BRAKE (11<<0) +#define AW8622X_BIT_GLBRD5_STATE_END (12<<0) +/* RAMADDRH: reg 0x40 RWS */ +#define AW8622X_BIT_RAMADDRH_MASK (~(63<<0)) + +/* RAMADDRL: reg 0x41 RWS */ +/* RAMADDRL */ + +/* RAMDATA: reg 0x42 RWS */ +/* RAMDATA */ + +/***************** SYSCTRL *****************/ +/* SYSCTRL1: reg 0x43 RW */ +#define AW8622X_BIT_SYSCTRL1_VBAT_MODE_MASK (~(1<<7)) +#define AW8622X_BIT_SYSCTRL1_VBAT_MODE_HW (1<<7) +#define AW8622X_BIT_SYSCTRL1_VBAT_MODE_SW (0<<7) +#define AW8622X_BIT_SYSCTRL1_PERP_MASK (~(1<<6)) +#define AW8622X_BIT_SYSCTRL1_PERP_ON (1<<6) +#define AW8622X_BIT_SYSCTRL1_PERP_OFF (0<<6) +#define AW8622X_BIT_SYSCTRL1_CLK_SEL_MASK (~(3<<4)) +#define AW8622X_BIT_SYSCTRL1_CLK_SEL_OSC (1<<4) +#define AW8622X_BIT_SYSCTRL1_CLK_SEL_AUTO (0<<4) +#define AW8622X_BIT_SYSCTRL1_RAMINIT_MASK (~(1<<3)) +#define AW8622X_BIT_SYSCTRL1_RAMINIT_ON (1<<3) +#define AW8622X_BIT_SYSCTRL1_RAMINIT_OFF (0<<3) +#define AW8622X_BIT_SYSCTRL1_EN_FIR_MASK (~(1<<2)) +#define AW8622X_BIT_SYSCTRL1_FIR_ENABLE (0<<2) +#define AW8622X_BIT_SYSCTRL1_WAKE_MODE_MASK (~(1<<1)) +#define AW8622X_BIT_SYSCTRL1_WAKE_MODE_WAKEUP (1<<1) +#define AW8622X_BIT_SYSCTRL1_WAKE_MODE_BST (0<<1) +#define AW8622X_BIT_SYSCTRL1_RTP_CLK_MASK (~(1<<0)) +#define AW8622X_BIT_SYSCTRL1_RTP_PLL (1<<0) +#define AW8622X_BIT_SYSCTRL1_RTP_OSC (0<<0) + +/* SYSCTRL2: reg 0x44 RW */ +#define AW8622X_BIT_SYSCTRL2_WAKE_MASK (~(1<<7)) +#define AW8622X_BIT_SYSCTRL2_WAKE_ON (1<<7) +#define AW8622X_BIT_SYSCTRL2_WAKE_OFF (0<<7) +#define AW8622X_BIT_SYSCTRL2_STANDBY_MASK (~(1<<6)) +#define AW8622X_BIT_SYSCTRL2_STANDBY_ON (1<<6) +#define AW8622X_BIT_SYSCTRL2_STANDBY_OFF (0<<6) +#define AW8622X_BIT_SYSCTRL2_RTP_DLY_MASK (~(3<<4)) +#define AW8622X_BIT_SYSCTRL2_INTN_PIN_MASK (~(1<<3)) +#define AW8622X_BIT_SYSCTRL2_INTN (1<<3) +#define AW8622X_BIT_SYSCTRL2_TRIG1 (0<<3) +#define AW8622X_BIT_SYSCTRL2_WCK_PIN_MASK (~(1<<2)) +#define AW8622X_BIT_SYSCTRL2_ENABLE_TRIG2 (1<<2) +#define AW8622X_BIT_SYSCTRL2_DISENABLE_TRIG2 (0<<2) +#define AW8622X_BIT_SYSCTRL2_WAVDAT_MODE_MASK (~(3<<0)) +#define AW8622X_BIT_SYSCTRL2_RATE_12K (2<<0) +#define AW8622X_BIT_SYSCTRL2_RATE_24K (0<<0) +#define AW8622X_BIT_SYSCTRL2_RATE_48K (1<<0) + +/* SYSCTRL3: reg 0x45 RW */ +/* SIN_H */ + +/* SYSCTRL4: reg 0x46 RW */ +/* SIN_L */ + +/* SYSCTRL5: reg 0x47 RW */ +/* COS_H */ + +/* SYSCTRL6: reg 0x48 RW */ +/* COS_L */ + +/* SYSCTRL7: reg 0x49 RW */ +#define AW8622X_BIT_SYSCTRL7_GAIN_BYPASS_MASK (~(1<<6)) +#define AW8622X_BIT_SYSCTRL7_GAIN_CHANGEABLE (1<<6) +#define AW8622X_BIT_SYSCTRL7_GAIN_FIXED (0<<6) + +#define AW8622X_BIT_SYSCTRL7_INT_EDGE_MODE_MASK (~(1<<5)) +#define AW8622X_BIT_SYSCTRL7_INT_EDGE_MODE_POS (0<<5) +#define AW8622X_BIT_SYSCTRL7_INT_EDGE_MODE_BOTH (1<<5) +#define AW8622X_BIT_SYSCTRL7_INT_MODE_MASK (~(1<<4)) +#define AW8622X_BIT_SYSCTRL7_INT_MODE_EDGE (1<<4) +#define AW8622X_BIT_SYSCTRL7_INT_MODE_LEVEL (0<<4) + +#define AW8622X_BIT_SYSCTRL7_INTP_MASK (~(1<<3)) +#define AW8622X_BIT_SYSCTRL7_INTP_HIGH (1<<3) +#define AW8622X_BIT_SYSCTRL7_INTP_LOW (0<<3) +#define AW8622X_BIT_SYSCTRL7_D2S_GAIN_MASK (~(7<<0)) +#define AW8622X_BIT_SYSCTRL7_D2S_GAIN_1 (0<<0) +#define AW8622X_BIT_SYSCTRL7_D2S_GAIN_2 (1<<0) +#define AW8622X_BIT_SYSCTRL7_D2S_GAIN_4 (2<<0) +#define AW8622X_BIT_SYSCTRL7_D2S_GAIN_5 (3<<0) +#define AW8622X_BIT_SYSCTRL7_D2S_GAIN_8 (4<<0) +#define AW8622X_BIT_SYSCTRL7_D2S_GAIN_10 (5<<0) +#define AW8622X_BIT_SYSCTRL7_D2S_GAIN_20 (6<<0) +#define AW8622X_BIT_SYSCTRL7_D2S_GAIN_40 (7<<0) + +/***************** I2S *****************/ +/* I2SCFG1: reg 0x4A RW */ +#define AW8622X_BIT_I2SCFG1_I2SMD_MASK (~(3<<6)) +#define AW8622X_BIT_I2SCFG1_I2SFS_MASK (~(3<<4)) +#define AW8622X_BIT_I2SCFG1_I2SFS_16BIT (0<<4) +#define AW8622X_BIT_I2SCFG1_I2SFS_20BIT (1<<4) +#define AW8622X_BIT_I2SCFG1_I2SFS_24BIT (2<<4) +#define AW8622X_BIT_I2SCFG1_I2SFS_32BIT (3<<4) +#define AW8622X_BIT_I2SCFG1_I2SBCK_MASK (~(3<<2)) +#define AW8622X_BIT_I2SCFG1_I2SBCK_32FS (0<<2) +#define AW8622X_BIT_I2SCFG1_I2SBCK_48FS (1<<2) +#define AW8622X_BIT_I2SCFG1_I2SBCK_64FS (2<<2) +#define AW8622X_BIT_I2SCFG1_RX_THRS_MASK (~(3<<0)) + +/* I2SCFG2: reg 0x4B RW */ +#define AW8622X_BIT_I2SCFG2_WSINV_MASK (~(1<<4)) +#define AW8622X_BIT_I2SCFG2_WSINV_SWITCH (1<<4) +#define AW8622X_BIT_I2SCFG2_WSINV_NO_SWITCH (0<<4) +#define AW8622X_BIT_I2SCFG2_BCKINV_MASK (~(1<<3)) +#define AW8622X_BIT_I2SCFG2_BCKINV_INVERT (1<<3) +#define AW8622X_BIT_I2SCFG2_BCKINV_NOTINVT (0<<3) +#define AW8622X_BIT_I2SCFG2_CHSEL_MASK (~(1<<2)) +#define AW8622X_BIT_I2SCFG2_CHSEL_LEFT (1<<2) +#define AW8622X_BIT_I2SCFG2_CHSEL_RIGHT (0<<2) +#define AW8622X_BIT_I2SCFG2_I2S_INT_MASK (~(1<<1)) +#define AW8622X_BIT_I2SCFG2_I2S_INT_ON (1<<1) +#define AW8622X_BIT_I2SCFG2_I2S_INT_OFF (0<<1) +#define AW8622X_BIT_I2SCFG2_I2S_EN_MASK (~(1<<0)) +#define AW8622X_BIT_I2SCFG2_I2S_ENABLE (1<<0) +#define AW8622X_BIT_I2SCFG2_I2S_DISABLE (0<<0) + +/* PWMCFG1: reg 0x4C RW */ +#define AW8622X_BIT_PWMCFG1_PRC_EN_MASK (~(1<<7)) +#define AW8622X_BIT_PWMCFG1_PRC_ENABLE (1<<7) +#define AW8622X_BIT_PWMCFG1_PRC_DISABLE (0<<7) +#define AW8622X_BIT_PWMCFG1_PRCTIME_MASK (~(0x7F<<0)) + +/* PWMCFG2: reg 0x4D RW */ +#define AW8622X_BIT_PWMCFG2_REF_SEL_MASK (~(1<<5)) +#define AW8622X_BIT_PWMCFG2_REF_SEL_TRIANGLE (1<<5) +#define AW8622X_BIT_PWMCFG2_REF_SEL_SAWTOOTH (0<<5) +#define AW8622X_BIT_PWMCFG2_PD_HWM_MASK (~(1<<4)) +#define AW8622X_BIT_PWMCFG2_PD_HWM_ON (1<<4) +#define AW8622X_BIT_PWMCFG2_PWMOE_MASK (~(1<<3)) +#define AW8622X_BIT_PWMCFG2_PWMOE_ON (1<<3) +#define AW8622X_BIT_PWMCFG2_PWMFRC_MASK (~(7<<0)) + +/* PWMCFG3: reg 0x4E RW */ +#define AW8622X_BIT_PWMCFG3_PR_EN_MASK (~(1<<7)) +#define AW8622X_BIT_PWMCFG3_PR_ENABLE (1<<7) +#define AW8622X_BIT_PWMCFG3_PR_DISABLE (0<<7) +#define AW8622X_BIT_PWMCFG3_PRLVL_MASK (~(0x7F<<0)) + +/* PWMCFG4: reg 0x4F RW */ +/* PRTIME */ + +/* TMCFG: reg 0x50 RW */ +/* TM */ + +/* DETCFG1: reg 0x51 RW */ +#define AW8622X_BIT_DETCFG1_FTS_GO_MASK (~(1<<7)) +#define AW8622X_BIT_DETCFG1_FTS_GO_ENABLE (1<<7) +#define AW8622X_BIT_DETCFG1_TEST_GO_MASK (~(1<<6)) +#define AW8622X_BIT_DETCFG1_TEST_GO_ENABLE (1<<6) +#define AW8622X_BIT_DETCFG1_ADO_SLOT_MODE_MASK (~(1<<5)) +#define AW8622X_BIT_DETCFG1_ADO_SLOT_ADC_32 (1<<5) +#define AW8622X_BIT_DETCFG1_ADO_SLOT_ADC_256 (0<<5) +#define AW8622X_BIT_DETCFG1_RL_OS_MASK (~(1<<4)) +#define AW8622X_BIT_DETCFG1_RL (1<<4) +#define AW8622X_BIT_DETCFG1_OS (0<<4) +#define AW8622X_BIT_DETCFG1_PRCT_MODE_MASK (~(1<<3)) +#define AW8622X_BIT_DETCFG1_PRCT_MODE_INVALID (1<<3) +#define AW8622X_BIT_DETCFG1_PRCT_MODE_VALID (0<<3) +#define AW8622X_BIT_DETCFG1_CLK_ADC_MASK (~(7<<0)) +#define AW8622X_BIT_DETCFG1_CLK_ADC_12M (0<<0) +#define AW8622X_BIT_DETCFG1_CLK_ADC_6M (1<<0) +#define AW8622X_BIT_DETCFG1_CLK_ADC_3M (2<<0) +#define AW8622X_BIT_DETCFG1_CLK_ADC_1M5 (3<<0) +#define AW8622X_BIT_DETCFG1_CLK_ADC_M75 (4<<0) +#define AW8622X_BIT_DETCFG1_CLK_ADC_M37 (5<<0) +#define AW8622X_BIT_DETCFG1_CLK_ADC_M18 (6<<0) +#define AW8622X_BIT_DETCFG1_CLK_ADC_M09 (7<<0) + +/* DETCFG2: reg 0x52 RW */ +#define AW8622X_BIT_DETCFG2_VBAT_GO_MASK (~(1<<1)) +#define AW8622X_BIT_DETCFG2_VABT_GO_ON (1<<1) +#define AW8622X_BIT_DETCFG2_DIAG_GO_MASK (~(1<<0)) +#define AW8622X_BIT_DETCFG2_DIAG_GO_ON (1<<0) + +/* DET_RL: reg 0x53 RW */ +/* RL */ + +/* DET_OS: reg 0x54 RW */ +/* OS */ + +/* DET_VBAT: reg 0x55 RW */ +/* VBAT */ + +/* DET_TEST: reg 0x56 RW */ +/* TEST */ + +/* DET_LO: reg 0x57 RW */ +#define AW8622X_BIT_DET_LO_TEST_MASK (~(3<<6)) +#define AW8622X_BIT_DET_LO_VBAT_MASK (~(3<<4)) +#define AW8622X_BIT_DET_LO_OS_MASK (~(3<<2)) +#define AW8622X_BIT_DET_LO_RL_MASK (~(3<<0)) + +/* TRIMCFG1: reg:0x58 RW */ +#define AW8622X_BIT_TRIMCFG1_RL_TRIM_SRC_MASK (~(1<<6)) +#define AW8622X_BIT_TRIMCFG1_RL_TRIM_SRC_REG (1<<6) +#define AW8622X_BIT_TRIMCFG1_RL_TRIM_SRC_EFUSE (0<<6) +#define AW8622X_BIT_TRIMCFG1_TRIM_RL_MASK (~(63<<0)) + +/* TRIMCFG3: reg:0x5A RW */ +#define AW8622X_BIT_TRIMCFG3_OSC_TRIM_SRC_MASK (~(1<<7)) +#define AW8622X_BIT_TRIMCFG3_OSC_TRIM_SRC_REG (1<<7) +#define AW8622X_BIT_TRIMCFG3_OSC_TRIM_SRC_EFUSE (0<<7) +#define AW8622X_BIT_TRIMCFG3_TRIM_LRA_MASK (~(63<<0)) + +/* TRIMCFG4: reg:0x5B RW */ +/* TRIM_OSC */ + +/* PLLCFG1: reg:0x68 RW */ +#define AW8622X_BIT_PLLCFG1_PLL_TEST_EN_MASK (~(1<<6)) +#define AW8622X_BIT_PLLCFG1_PLL_TEST_ENABLE (1<<6) +#define AW8622X_BIT_PLLCFG1_PLL_TEST_DIV_MASK (~(3<<4)) +#define AW8622X_BIT_PLLCFG1_PLL_TEST_DIV_1 (0<<4) +#define AW8622X_BIT_PLLCFG1_PLL_TEST_DIV_2 (1<<4) +#define AW8622X_BIT_PLLCFG1_PLL_TEST_DIV_4 (2<<4) +#define AW8622X_BIT_PLLCFG1_PLL_TEST_DIV_8 (3<<4) +#define AW8622X_BIT_PLLCFG1_PLL_BIAS_CP1_IEN_MASK (~(1<<3)) +#define AW8622X_BIT_PLLCFG1_PLL_BIAS_CP1_IENABLE (1<<3) +#define AW8622X_BIT_PLLCFG1_PLL_VTI_CP1_IEN_MASK (~(1<<2)) +#define AW8622X_BIT_PLLCFG1_PLL_VTI_CP1_IENABLE (1<<2) +#define AW8622X_BIT_PLLCFG1_PLL_DELAY_SEL_MASK (~(1<<1)) +#define AW8622X_BIT_PLLCFG1_PLL_R1_SEL_MASK (~(1<<0)) + +/* PLLCFG2: reg:0x69 RW */ +#define AW8622X_BIT_PLLCFG2_PLL_CP1_SEL_MASK (~(0x0F<<4)) +#define AW8622X_BIT_PLLCFG2_PLL_CP1_40UA (4<<4) +#define AW8622X_BIT_PLLCFG2_PLL_CP1_50UA (5<<4) +#define AW8622X_BIT_PLLCFG2_PLL_CP1_80UA (8<<4) +#define AW8622X_BIT_PLLCFG2_PLL_CP1_100UA (10<<4) +#define AW8622X_BIT_PLLCFG2_PLL_CP2_SEL_MASK (~(0x0F<<0)) +#define AW8622X_BIT_PLLCFG2_PLL_CP2_40NA (1<<0) +#define AW8622X_BIT_PLLCFG2_PLL_CP2_600NA (8<<0) +#define AW8622X_BIT_PLLCFG2_PLL_CP2_800NA (10<<0) +#define AW8622X_BIT_PLLCFG2_PLL_CP2_1200NA (12<<0) + +/* HDRVCFG1: reg:0x6A RW */ +#define AW8622X_BIT_HDRVCFG1_EN_HD_LOW_MASK (~(1<<7)) +#define AW8622X_BIT_HDRVCFG1_EN_HD_HZ (0<<7) +#define AW8622X_BIT_HDRVCFG1_EN_HD_PD (1<<7) + +/* IOCFG1: reg:0x6B RW */ +#define AW8622X_BIT_IOCFG1_HSEN_MASK (~(1<<6)) +#define AW8622X_BIT_IOCFG1_HS_ENABLE (1<<6) +#define AW8622X_BIT_IOCFG1_HS_DISABLE (0<<6) +#define AW8622X_BIT_IOCFG1_IO_FAST_MASK (~(3<<4)) +#define AW8622X_BIT_IOCFG1_ALL_IO_FAST_ENABLE (3<<4) +#define AW8622X_BIT_IOCFG1_IIS_IO_FAST_ENABLE (2<<4) +#define AW8622X_BIT_IOCFG1_IIC_IO_FAST_ENABLE (1<<4) +#define AW8622X_BIT_IOCFG1_IO_FAST_DISABLE (0<<4) + +/* OCCFG1: reg:0x74 RW */ +#define AW8622X_BIT_OCCFG1_HS_IOC_MASK (~(3<<6)) +#define AW8622X_BIT_OCCFG1_HS_IOC_3A15 (0<<6) +#define AW8622X_BIT_OCCFG1_HS_IOC_3A65 (1<<6) +#define AW8622X_BIT_OCCFG1_HS_IOC_4A15 (2<<6) +#define AW8622X_BIT_OCCFG1_HS_IOC_4A65 (3<<6) +#define AW8622X_BIT_OCCFG1_LS_IOC_MASK (~(3<<4)) +#define AW8622X_BIT_OCCFG1_LS_IOC_3A15 (0<<4) +#define AW8622X_BIT_OCCFG1_LS_IOC_3A65 (1<<4) +#define AW8622X_BIT_OCCFG1_LS_IOC_4A15 (2<<4) +#define AW8622X_BIT_OCCFG1_LS_IOC_4A65 (3<<4) +#define AW8622X_BIT_OCCFG1_OCDT_MASK (~(3<<2)) + +#define AW8622X_BIT_OCCLK_MODE_MASK (~(3<<0)) + +/* ADCCFG1: reg:0x75 RW */ +#define AW8622X_BIT_ADCCFG1_EN_OS_DET_MASK (~(1<<7)) +#define AW8622X_BIT_ADCCFG1_EN_RL_DET_MASK (~(1<<6)) +#define AW8622X_BIT_ADCCFG1_D2S_SEL_HDRV_MASK (~(1<<5)) +#define AW8622X_BIT_ADCCFG1_AD_SEL_HDP_MASK (~(1<<4)) +#define AW8622X_BIT_ADCCFG1_AD_SEL_HDN_MASK (~(1<<3)) +#define AW8622X_BIT_ADCCFG1_AD_SEL_VBAT_MASK (~(1<<2)) +#define AW8622X_BIT_ADCCFG1_AD_SEL_TEST_MASK (~(1<<1)) +#define AW8622X_BIT_ADCCFG1_AD_SEL_D2S_MASK (~(1<<0)) + +/* D2SCFG1: reg:0x76 RW */ +#define AW8622X_BIT_D2SCFG1_CLK_TRIM_MODE_MASK (~(7<<0)) +#define AW8622X_BIT_D2SCFG1_CLK_TRIM_MODE_48K (0<<0) +#define AW8622X_BIT_D2SCFG1_CLK_TRIM_MODE_24K (1<<0) +#define AW8622X_BIT_D2SCFG1_CLK_TRIM_MODE_12K (2<<0) +#define AW8622X_BIT_D2SCFG1_CLK_TRIM_MODE_6K (3<<0) +#define AW8622X_BIT_D2SCFG1_CLK_TRIM_MODE_12M (4<<0) + +/* ANACFG8: reg:0x77 RW */ +#define AW8622X_BIT_ANACFG8_TRTF_CTRL_HDRV_MASK (~(1<<6)) +#define AW8622X_BIT_ANACFG8_TRTF_CTRL_HDRV (3<<6) +#endif diff --git a/drivers/misc/aw862xx_haptic/aw8624.c b/drivers/misc/aw862xx_haptic/aw8624.c new file mode 100644 index 000000000000..823b1074bff6 --- /dev/null +++ b/drivers/misc/aw862xx_haptic/aw8624.c @@ -0,0 +1,4438 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_PM_WAKELOCKS +#include +#else +#include +#endif +#include +#include +#include "aw8624.h" +#include "aw8624_reg.h" +#include "haptic.h" +/****************************************************** + * + * Value + * + ******************************************************/ +static char *aw8624_ram_name = "aw8624_haptic.bin"; +static char aw8624_rtp_name[][AW8624_RTP_NAME_MAX] = { + {"aw8624_osc_rtp_24K_5s.bin"}, + {"aw8624_rtp.bin"}, + {"aw8624_rtp_lighthouse.bin"}, + {"aw8624_rtp_silk.bin"}, +}; +struct aw8624_dts_info aw8624_dts_data; +struct pm_qos_request aw8624_pm_qos_req_vb; + +/****************************************************** +* +* functions +* +******************************************************/ +static void aw8624_interrupt_clear(struct aw8624 *aw8624); +static void aw8624_haptic_upload_lra(struct aw8624 *aw8624, unsigned int flag); +static int aw8624_haptic_stop(struct aw8624 *aw8624); +static int aw8624_analyse_duration_range(struct aw8624 *aw8624); + +/****************************************************** +* +* aw8624 i2c write/read +* +******************************************************/ +static int aw8624_i2c_write(struct aw8624 *aw8624, + unsigned char reg_addr, unsigned char reg_data) +{ + int ret = -1; + unsigned char cnt = 0; + + while (cnt < AW8624_I2C_RETRIES) { + ret = + i2c_smbus_write_byte_data(aw8624->i2c, reg_addr, reg_data); + if (ret < 0) { + aw_dev_err(aw8624->dev, "%s: i2c_write cnt=%d error=%d\n", + __func__, cnt, ret); + } else { + break; + } + cnt++; + usleep_range(2000, 3000); + } + + return ret; +} + +int aw8624_i2c_read(struct aw8624 *aw8624, + unsigned char reg_addr, unsigned char *reg_data) +{ + int ret = -1; + unsigned char cnt = 0; + + while (cnt < AW8624_I2C_RETRIES) { + ret = i2c_smbus_read_byte_data(aw8624->i2c, reg_addr); + if (ret < 0) { + aw_dev_err(aw8624->dev, "%s: i2c_read cnt=%d error=%d\n", + __func__, cnt, ret); + } else { + *reg_data = ret; + break; + } + cnt++; + usleep_range(2000, 3000); + } + + return ret; +} + +int aw8624_i2c_write_bits(struct aw8624 *aw8624, + unsigned char reg_addr, unsigned int mask, unsigned char reg_data) +{ + unsigned char reg_val = 0; + + aw8624_i2c_read(aw8624, reg_addr, ®_val); + reg_val &= mask; + reg_val |= reg_data; + aw8624_i2c_write(aw8624, reg_addr, reg_val); + + return 0; +} + +int aw8624_i2c_writes(struct aw8624 *aw8624, + unsigned char reg_addr, unsigned char *buf, unsigned int len) +{ + int ret = -1; + unsigned char *data; + + data = kmalloc(len+1, GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + data[0] = reg_addr; + memcpy(&data[1], buf, len); + + ret = i2c_master_send(aw8624->i2c, data, len+1); + if (ret < 0) + aw_dev_err(aw8624->dev, + "%s: i2c master send error\n", __func__); + + kfree(data); + + return ret; +} + +static void aw8624_interrupt_clear(struct aw8624 *aw8624) +{ + unsigned char reg_val = 0; + + aw8624_i2c_read(aw8624, AW8624_REG_SYSINT, ®_val); + aw_dev_info(aw8624->dev, "%s: reg SYSINT=0x%x\n", __func__, reg_val); +} + +/***************************************************** + * + * ram update + * + *****************************************************/ +static void aw8624_rtp_loaded(const struct firmware *cont, void *context) +{ + struct aw8624 *aw8624 = context; + + if (!cont) { + aw_dev_err(aw8624->dev, "%s: failed to read %s\n", + __func__, aw8624_rtp_name[aw8624->rtp_file_num]); + release_firmware(cont); + return; + } + + aw_dev_info(aw8624->dev, "%s: loaded %s - size: %zu\n", __func__, + aw8624_rtp_name[aw8624->rtp_file_num], cont ? cont->size : 0); + + /* aw8624 rtp update */ + aw8624->rtp_container = vmalloc(cont->size+sizeof(int)); + if (!aw8624->rtp_container) { + release_firmware(cont); + aw_dev_err(aw8624->dev, + "%s: Error allocating memory\n", __func__); + return; + } + aw8624->rtp_container->len = cont->size; + aw_dev_info(aw8624->dev, + "%s: rtp size = %d\n", __func__, aw8624->rtp_container->len); + memcpy(aw8624->rtp_container->data, cont->data, cont->size); + release_firmware(cont); + + aw8624->rtp_init = 1; + aw_dev_info(aw8624->dev, "%s: rtp update complete\n", __func__); +} + +static int aw8624_rtp_update(struct aw8624 *aw8624) +{ + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + + return request_firmware_nowait(THIS_MODULE, + FW_ACTION_HOTPLUG, + aw8624_rtp_name[aw8624->rtp_file_num], + aw8624->dev, + GFP_KERNEL, + aw8624, + aw8624_rtp_loaded); +} + +static int aw8624_haptic_juge_RTP_is_going_on(struct aw8624 *aw8624) +{ + unsigned char rtp_state = 0; + unsigned char mode = 0; + unsigned char glb_st = 0; + + aw8624_i2c_read(aw8624, AW8624_REG_SYSCTRL, &mode); + aw8624_i2c_read(aw8624, AW8624_REG_GLB_STATE, &glb_st); + if ((mode & AW8624_BIT_SYSCTRL_PLAY_MODE_RTP) && + (glb_st == AW8624_BIT_GLBRD5_STATE_RTP_GO)) { + rtp_state = 1; + } + return rtp_state; +} + +static void aw8624_haptic_raminit(struct aw8624 *aw8624, bool flag) +{ + if (flag) { + aw8624_i2c_write_bits(aw8624, AW8624_REG_SYSCTRL, + AW8624_BIT_SYSCTRL_RAMINIT_MASK, + AW8624_BIT_SYSCTRL_RAMINIT_EN); + } else { + aw8624_i2c_write_bits(aw8624, AW8624_REG_SYSCTRL, + AW8624_BIT_SYSCTRL_RAMINIT_MASK, + AW8624_BIT_SYSCTRL_RAMINIT_OFF); + } +} + +static int aw8624_container_update(struct aw8624 *aw8624, + struct aw8624_container *aw8624_cont) +{ + unsigned int shift = 0; + int i = 0; + int ret = 0; +#ifdef AW_CHECK_RAM_DATA + unsigned short check_sum = 0; + unsigned char reg_val = 0; +#endif + mutex_lock(&aw8624->lock); + + aw8624->ram.baseaddr_shift = 2; + aw8624->ram.ram_shift = 4; + + /* RAMINIT Enable */ + aw8624_haptic_raminit(aw8624, true); + + /* base addr */ + shift = aw8624->ram.baseaddr_shift; + aw8624->ram.base_addr = (unsigned int)((aw8624_cont->data[0+shift]<<8) | + (aw8624_cont->data[1+shift])); + aw_dev_info(aw8624->dev, + "%s: base_addr=0x%4x\n", __func__, + aw8624->ram.base_addr); + + aw8624_i2c_write(aw8624, + AW8624_REG_BASE_ADDRH, + aw8624_cont->data[0+shift]); + aw8624_i2c_write(aw8624, + AW8624_REG_BASE_ADDRL, + aw8624_cont->data[1+shift]); + + aw8624_i2c_write(aw8624, + AW8624_REG_FIFO_AEH, + (unsigned char)((aw8624->ram.base_addr>>2)>>8)); + aw8624_i2c_write(aw8624, + AW8624_REG_FIFO_AEL, + (unsigned char)((aw8624->ram.base_addr>>2)&0x00FF)); + aw8624_i2c_write(aw8624, + AW8624_REG_FIFO_AFH, + (unsigned char)((aw8624->ram.base_addr + - (aw8624->ram.base_addr>>2))>>8)); + aw8624_i2c_write(aw8624, + AW8624_REG_FIFO_AFL, + (unsigned char)((aw8624->ram.base_addr + -(aw8624->ram.base_addr>>2))&0x00FF)); + + /* ram */ + shift = aw8624->ram.baseaddr_shift; + aw8624_i2c_write(aw8624, + AW8624_REG_RAMADDRH, aw8624_cont->data[0+shift]); + aw8624_i2c_write(aw8624, + AW8624_REG_RAMADDRL, aw8624_cont->data[1+shift]); + shift = aw8624->ram.ram_shift; + for (i = shift; i < aw8624_cont->len; i++) { + aw8624_i2c_write(aw8624, + AW8624_REG_RAMDATA, aw8624_cont->data[i]); + } + +#ifdef AW_CHECK_RAM_DATA + shift = aw8624->ram.baseaddr_shift; + aw8624_i2c_write_bits(aw8624, AW8624_REG_RAMADDRH, + AW8624_BIT_RAMADDRH_MASK, + aw8624_cont->data[0 + shift]); + aw8624_i2c_write(aw8624, AW8624_REG_RAMADDRL, + aw8624_cont->data[1 + shift]); + shift = aw8624->ram.ram_shift; + for (i = shift; i < aw8624_cont->len; i++) { + aw8624_i2c_read(aw8624, AW8624_REG_RAMDATA, ®_val); + /* + *aw_dev_info(aw8624->dev, + * "%s aw8624_cont->data=0x%02X, ramdata=0x%02X\n", + * __func__, aw8624_cont->data[i], reg_val); + */ + if (reg_val != aw8624_cont->data[i]) { + aw_dev_err(aw8624->dev, + "%s: ram check error addr=0x%04x, file_data=0x%02X, ram_data=0x%02X\n", + __func__, i, aw8624_cont->data[i], reg_val); + ret = -ERANGE; + break; + } + check_sum += reg_val; + } + if (!ret) { + aw8624_i2c_read(aw8624, AW8624_REG_BASE_ADDRH, ®_val); + check_sum += reg_val; + aw8624_i2c_read(aw8624, AW8624_REG_BASE_ADDRL, ®_val); + check_sum += reg_val; + + if (check_sum != aw8624->ram.check_sum) { + aw_dev_err(aw8624->dev, "%s: ram data check sum error, check_sum=0x%04x\n", + __func__, check_sum); + ret = -ERANGE; + } else { + aw_dev_info(aw8624->dev, "%s: ram data check sum pass, check_sum=0x%04x\n", + __func__, check_sum); + } + } + +#endif + /* RAMINIT Disable */ + aw8624_haptic_raminit(aw8624, false); + + mutex_unlock(&aw8624->lock); + aw_dev_info(aw8624->dev, "%s exit\n", __func__); + return ret; +} + +static int aw8624_haptic_get_ram_number(struct aw8624 *aw8624) +{ + unsigned char i = 0; + unsigned char reg_val = 0; + unsigned char ram_data[3]; + unsigned int first_wave_addr = 0; + + aw_dev_info(aw8624->dev, "%s enter!\n", __func__); + if (!aw8624->ram_init) { + aw_dev_err(aw8624->dev, + "%s: ram init faild, ram_num = 0!\n", + __func__); + return -EPERM; + } + + mutex_lock(&aw8624->lock); + /* RAMINIT Enable */ + aw8624_haptic_raminit(aw8624, true); + aw8624_haptic_stop(aw8624); + aw8624_i2c_write(aw8624, AW8624_REG_RAMADDRH, + (unsigned char)(aw8624->ram.base_addr >> 8)); + aw8624_i2c_write(aw8624, AW8624_REG_RAMADDRL, + (unsigned char)(aw8624->ram.base_addr & 0x00ff)); + for (i = 0; i < 3; i++) { + aw8624_i2c_read(aw8624, AW8624_REG_RAMDATA, ®_val); + ram_data[i] = reg_val; + } + first_wave_addr = (ram_data[1] << 8 | ram_data[2]); + aw8624->ram.ram_num = + (first_wave_addr - aw8624->ram.base_addr - 1) / 4; + aw_dev_info(aw8624->dev, + "%s: ram_version = 0x%02x\n", __func__, ram_data[0]); + aw_dev_info(aw8624->dev, + "%s: first waveform addr = 0x%04x\n", + __func__, first_wave_addr); + aw_dev_info(aw8624->dev, + "%s: ram_num = %d\n", __func__, aw8624->ram.ram_num); + /* RAMINIT Disable */ + aw8624_haptic_raminit(aw8624, false); + mutex_unlock(&aw8624->lock); + + return 0; +} + +static void aw8624_ram_loaded(const struct firmware *cont, void *context) +{ + struct aw8624 *aw8624 = context; + struct aw8624_container *aw8624_fw; + unsigned short check_sum = 0; + int i = 0; + int ret = 0; +#ifdef AW_READ_BIN_FLEXBALLY + static unsigned char load_cont; + int ram_timer_val = 1000; + + load_cont++; +#endif + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + if (!cont) { + aw_dev_err(aw8624->dev, + "%s: failed to read %s\n", + __func__, aw8624_ram_name); + release_firmware(cont); +#ifdef AW_READ_BIN_FLEXBALLY + if (load_cont <= 20) { + schedule_delayed_work(&aw8624->ram_work, + msecs_to_jiffies(ram_timer_val)); + aw_dev_info(aw8624->dev, "%s:start hrtimer: load_cont=%d\n", + __func__, load_cont); + } +#endif + return; + } + + aw_dev_info(aw8624->dev, + "%s: loaded %s - size: %zu\n", __func__, aw8624_ram_name, + cont ? cont->size : 0); + + /* check sum */ + for (i = 2; i < cont->size; i++) + check_sum += cont->data[i]; + + if (check_sum == (unsigned short)((cont->data[0]<<8)|(cont->data[1]))) { + aw_dev_info(aw8624->dev, + "%s: check sum pass : 0x%04x\n", __func__, check_sum); + aw8624->ram.check_sum = check_sum; + } else { + aw_dev_err(aw8624->dev, "%s: check sum err: check_sum=0x%04x\n", + __func__, check_sum); + return; + } + + /* aw8624 ram update */ + aw8624_fw = kzalloc(cont->size+sizeof(int), GFP_KERNEL); + if (!aw8624_fw) { + release_firmware(cont); + aw_dev_err(aw8624->dev, + "%s: Error allocating memory\n", __func__); + return; + } + aw8624_fw->len = cont->size; + memcpy(aw8624_fw->data, cont->data, cont->size); + release_firmware(cont); + + ret = aw8624_container_update(aw8624, aw8624_fw); + if (ret) { + kfree(aw8624_fw); + aw8624->ram.len = 0; + aw_dev_err(aw8624->dev, "%s: ram firmware update failed!\n", + __func__); + } else { + aw8624->ram_init = 1; + aw8624->ram.len = aw8624_fw->len; + kfree(aw8624_fw); + aw_dev_info(aw8624->dev, + "%s: ram firmware update complete\n", __func__); + } + aw8624_haptic_get_ram_number(aw8624); + if (aw8624->IsUsedIRQ) + aw8624_rtp_update(aw8624); + +} + +static int aw8624_ram_update(struct aw8624 *aw8624) +{ + aw8624->ram_init = 0; + aw8624->rtp_init = 0; + return request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + aw8624_ram_name, aw8624->dev, GFP_KERNEL, + aw8624, aw8624_ram_loaded); +} + +static void aw8624_ram_work_routine(struct work_struct *work) +{ + struct aw8624 *aw8624 = + container_of(work, struct aw8624, ram_work.work); + + aw8624_ram_update(aw8624); + +} + +int aw8624_ram_init(struct aw8624 *aw8624) +{ + int ram_timer_val = 8000; + + INIT_DELAYED_WORK(&aw8624->ram_work, aw8624_ram_work_routine); + schedule_delayed_work(&aw8624->ram_work, + msecs_to_jiffies(ram_timer_val)); + + return 0; +} + +/***************************************************** + * + * haptic control + * + *****************************************************/ + +static int aw8624_haptic_play_init(struct aw8624 *aw8624) +{ + if (aw8624->play_mode == AW8624_HAPTIC_CONT_MODE) { + aw8624_i2c_write(aw8624, + AW8624_REG_SW_BRAKE, + (unsigned char)(aw8624_dts_data.aw8624_sw_brake[0])); + } else { + aw8624_i2c_write(aw8624, + AW8624_REG_SW_BRAKE, + (unsigned char)(aw8624_dts_data.aw8624_sw_brake[1])); + } + return 0; +} + +static int aw8624_haptic_active(struct aw8624 *aw8624) +{ + aw8624_haptic_play_init(aw8624); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_SYSCTRL, + AW8624_BIT_SYSCTRL_WORK_MODE_MASK, + AW8624_BIT_SYSCTRL_ACTIVE); + aw8624_interrupt_clear(aw8624); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_SYSINTM, + AW8624_BIT_SYSINTM_UVLO_MASK, + AW8624_BIT_SYSINTM_UVLO_EN); + return 0; +} + +static int aw8624_haptic_play_mode(struct aw8624 *aw8624, + unsigned char play_mode) +{ + switch (play_mode) { + case AW8624_HAPTIC_STANDBY_MODE: + aw_dev_info(aw8624->dev, "%s: enter standby mode\n", __func__); + aw8624->play_mode = AW8624_HAPTIC_STANDBY_MODE; + aw8624_i2c_write_bits(aw8624, AW8624_REG_SYSINTM, + AW8624_BIT_SYSINTM_UVLO_MASK, + AW8624_BIT_SYSINTM_UVLO_OFF); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_SYSCTRL, + AW8624_BIT_SYSCTRL_WORK_MODE_MASK, + AW8624_BIT_SYSCTRL_STANDBY); + break; + case AW8624_HAPTIC_RAM_MODE: + aw_dev_info(aw8624->dev, "%s: enter ram mode\n", __func__); + aw8624->play_mode = AW8624_HAPTIC_RAM_MODE; + aw8624_i2c_write_bits(aw8624, + AW8624_REG_SYSCTRL, + AW8624_BIT_SYSCTRL_PLAY_MODE_MASK, + AW8624_BIT_SYSCTRL_PLAY_MODE_RAM); + aw8624_haptic_active(aw8624); + break; + case AW8624_HAPTIC_RAM_LOOP_MODE: + aw_dev_info(aw8624->dev, "%s: enter ram loop mode\n", __func__); + aw8624->play_mode = AW8624_HAPTIC_RAM_LOOP_MODE; + aw8624_i2c_write_bits(aw8624, + AW8624_REG_SYSCTRL, + AW8624_BIT_SYSCTRL_PLAY_MODE_MASK, + AW8624_BIT_SYSCTRL_PLAY_MODE_RAM); + aw8624_haptic_active(aw8624); + break; + case AW8624_HAPTIC_RTP_MODE: + aw_dev_info(aw8624->dev, "%s: enter rtp mode\n", __func__); + aw8624->play_mode = AW8624_HAPTIC_RTP_MODE; + aw8624_i2c_write_bits(aw8624, + AW8624_REG_SYSCTRL, + AW8624_BIT_SYSCTRL_PLAY_MODE_MASK, + AW8624_BIT_SYSCTRL_PLAY_MODE_RTP); + aw8624_haptic_active(aw8624); + break; + case AW8624_HAPTIC_TRIG_MODE: + aw_dev_info(aw8624->dev, "%s: enter trig mode\n", __func__); + aw8624->play_mode = AW8624_HAPTIC_TRIG_MODE; + aw8624_i2c_write_bits(aw8624, + AW8624_REG_SYSCTRL, + AW8624_BIT_SYSCTRL_PLAY_MODE_MASK, + AW8624_BIT_SYSCTRL_PLAY_MODE_RAM); + aw8624_haptic_active(aw8624); + break; + case AW8624_HAPTIC_CONT_MODE: + aw_dev_info(aw8624->dev, "%s: enter cont mode\n", __func__); + aw8624->play_mode = AW8624_HAPTIC_CONT_MODE; + aw8624_i2c_write_bits(aw8624, + AW8624_REG_SYSCTRL, + AW8624_BIT_SYSCTRL_PLAY_MODE_MASK, + AW8624_BIT_SYSCTRL_PLAY_MODE_CONT); + aw8624_haptic_active(aw8624); + break; + default: + dev_err(aw8624->dev, "%s: play mode %d err", + __func__, play_mode); + break; + } + return 0; +} + +static int aw8624_haptic_play_go(struct aw8624 *aw8624, bool flag) +{ + aw_dev_dbg(aw8624->dev, "%s enter, flag = %d\n", __func__, flag); + if (!flag) { +#ifdef KERNEL_VERSION_49 + do_gettimeofday(&aw8624->current_time); + aw8624->interval_us = (aw8624->current_time.tv_sec - + aw8624->pre_enter_time.tv_sec) * 1000000 + + (aw8624->current_time.tv_usec-aw8624->pre_enter_time.tv_usec); +#else + + aw8624->current_time = ktime_get(); + aw8624->interval_us = ktime_to_us(ktime_sub(aw8624->current_time, + aw8624->pre_enter_time)); +#endif + + + if (aw8624->interval_us < 2000) { + aw_dev_info(aw8624->dev, "%s:aw8624->interval_us=%d\n", + __func__, aw8624->interval_us); + mdelay(2); + } + } + if (flag == true) { + aw8624_i2c_write_bits(aw8624, AW8624_REG_GO, + AW8624_BIT_GO_MASK, AW8624_BIT_GO_ENABLE); +#ifdef KERNEL_VERSION_49 + do_gettimeofday(&aw8624->pre_enter_time); +#else + aw8624->pre_enter_time = ktime_get(); +#endif + + } else { + aw8624_i2c_write_bits(aw8624, AW8624_REG_GO, + AW8624_BIT_GO_MASK, AW8624_BIT_GO_DISABLE); + } + return 0; +} + +static int aw8624_haptic_stop_delay(struct aw8624 *aw8624) +{ + unsigned char reg_val = 0; + unsigned int cnt = 100; + + while (cnt--) { + aw8624_i2c_read(aw8624, AW8624_REG_GLB_STATE, ®_val); + if ((reg_val&0x0f) == 0x00) { + aw_dev_info(aw8624->dev, + "%s enter standby, reg glb_state=0x%02x\n", + __func__, reg_val); + return 0; + } + mdelay(2); + + aw_dev_info(aw8624->dev, + "%s wait for standby, reg glb_state=0x%02x\n", + __func__, reg_val); + } + aw_dev_err(aw8624->dev, + "%s do not enter standby automatically\n", __func__); + return 0; +} + +static int aw8624_haptic_stop(struct aw8624 *aw8624) +{ + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + aw8624_haptic_play_go(aw8624, false); + aw8624_haptic_stop_delay(aw8624); + aw8624_haptic_play_mode(aw8624, AW8624_HAPTIC_STANDBY_MODE); + + return 0; +} + +static int aw8624_haptic_start(struct aw8624 *aw8624) +{ + aw8624_haptic_active(aw8624); + aw8624_haptic_play_go(aw8624, true); + + return 0; +} + +static int aw8624_haptic_set_wav_seq(struct aw8624 *aw8624, + unsigned char wav, unsigned char seq) +{ + aw8624_i2c_write(aw8624, AW8624_REG_WAVSEQ1+wav, seq); + return 0; +} + +static int aw8624_haptic_set_wav_loop(struct aw8624 *aw8624, + unsigned char wav, unsigned char loop) +{ + unsigned char tmp = 0; + + if (wav%2) { + tmp = loop<<0; + aw8624_i2c_write_bits(aw8624, AW8624_REG_WAVLOOP1+(wav/2), + AW8624_BIT_WAVLOOP_SEQNP1_MASK, tmp); + } else { + tmp = loop<<4; + aw8624_i2c_write_bits(aw8624, AW8624_REG_WAVLOOP1+(wav/2), + AW8624_BIT_WAVLOOP_SEQN_MASK, tmp); + } + + return 0; +} + +static int +aw8624_haptic_set_repeat_wav_seq(struct aw8624 *aw8624, unsigned char seq) +{ + aw8624_haptic_set_wav_seq(aw8624, 0x00, seq); + aw8624_haptic_set_wav_loop(aw8624, + 0x00, + AW8624_BIT_WAVLOOP_INIFINITELY); + + return 0; +} + +static int aw8624_haptic_set_gain(struct aw8624 *aw8624, unsigned char gain) +{ + aw8624_i2c_write(aw8624, AW8624_REG_DATDBG, gain); + return 0; +} + +static int aw8624_haptic_set_pwm(struct aw8624 *aw8624, unsigned char mode) +{ + switch (mode) { + case AW8624_PWM_48K: + aw8624_i2c_write_bits(aw8624, + AW8624_REG_PWMDBG, + AW8624_BIT_PWMDBG_PWM_MODE_MASK, + AW8624_BIT_PWMDBG_PWM_48K); + break; + case AW8624_PWM_24K: + aw8624_i2c_write_bits(aw8624, + AW8624_REG_PWMDBG, + AW8624_BIT_PWMDBG_PWM_MODE_MASK, + AW8624_BIT_PWMDBG_PWM_24K); + break; + case AW8624_PWM_12K: + aw8624_i2c_write_bits(aw8624, + AW8624_REG_PWMDBG, + AW8624_BIT_PWMDBG_PWM_MODE_MASK, + AW8624_BIT_PWMDBG_PWM_12K); + break; + default: + break; + } + return 0; +} + +static int +aw8624_haptic_play_repeat_seq(struct aw8624 *aw8624, unsigned char flag) +{ + if (flag) { + aw8624_haptic_play_mode(aw8624, AW8624_HAPTIC_RAM_LOOP_MODE); + aw8624_haptic_start(aw8624); + } + + return 0; +} + +static int aw8624_haptic_play_wav_seq(struct aw8624 *aw8624, + unsigned char flag) +{ + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + if (flag) { + aw8624_haptic_play_mode(aw8624, AW8624_HAPTIC_RAM_MODE); + aw8624_haptic_start(aw8624); + } + return 0; +} + + +static int aw8624_haptic_swicth_motorprotect_config(struct aw8624 *aw8624, + unsigned char addr, unsigned char val) +{ + if (addr == 1) { + aw8624_i2c_write_bits(aw8624, + AW8624_REG_DETCTRL, + AW8624_BIT_DETCTRL_PROTECT_MASK, + AW8624_BIT_DETCTRL_PROTECT_SHUTDOWN); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_PWMPRC, + AW8624_BIT_PWMPRC_PRC_EN_MASK, + AW8624_BIT_PWMPRC_PRC_ENABLE); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_PRLVL, + AW8624_BIT_PRLVL_PR_EN_MASK, + AW8624_BIT_PRLVL_PR_ENABLE); + } else if (addr == 0) { + aw8624_i2c_write_bits(aw8624, + AW8624_REG_DETCTRL, + AW8624_BIT_DETCTRL_PROTECT_MASK, + AW8624_BIT_DETCTRL_PROTECT_NO_ACTION); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_PWMPRC, + AW8624_BIT_PWMPRC_PRC_EN_MASK, + AW8624_BIT_PWMPRC_PRC_DISABLE); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_PRLVL, + AW8624_BIT_PRLVL_PR_EN_MASK, + AW8624_BIT_PRLVL_PR_DISABLE); + } else if (addr == 0x2d) { + aw8624_i2c_write_bits(aw8624, AW8624_REG_PWMPRC, + AW8624_BIT_PWMPRC_PRCTIME_MASK, val); + } else if (addr == 0x3e) { + aw8624_i2c_write_bits(aw8624, AW8624_REG_PRLVL, + AW8624_BIT_PRLVL_PRLVL_MASK, val); + } else if (addr == 0x3f) { + aw8624_i2c_write_bits(aw8624, AW8624_REG_PRTIME, + AW8624_BIT_PRTIME_PRTIME_MASK, val); + } else { + /*nothing to do;*/ + } + + return 0; +} + +static int aw8624_haptic_ram_config(struct aw8624 *aw8624, int duration) +{ + unsigned char wavseq = 0; + unsigned char wavloop = 0; + int ret = 0; + + if (aw8624->duration_time_flag < 0) { + aw_dev_err(aw8624->dev, + "%s: duration time error, array size = %d\n", + __func__, aw8624->duration_time_size); + return -ERANGE; + } + ret = aw8624_analyse_duration_range(aw8624); + if (ret < 0) + return ret; + if ((duration > 0) && (duration < + aw8624_dts_data.aw8624_duration_time[0])) { + wavseq = 3; /*3*/ + wavloop = 0; + } else if ((duration >= aw8624_dts_data.aw8624_duration_time[0]) && + (duration < aw8624_dts_data.aw8624_duration_time[1])) { + wavseq = 2; /*2*/ + wavloop = 0; + } else if ((duration >= aw8624_dts_data.aw8624_duration_time[1]) && + (duration < aw8624_dts_data.aw8624_duration_time[2])) { + wavseq = 1; /*1*/ + wavloop = 0; + } else if (duration >= aw8624_dts_data.aw8624_duration_time[2]) { + wavseq = 4; /*4*/ + wavloop = 15; /*long vibration*/ + } else { + wavseq = 0; + wavloop = 0; + } + + aw8624_haptic_set_wav_seq(aw8624, 0, wavseq); + aw8624_haptic_set_wav_loop(aw8624, 0, wavloop); + aw8624_haptic_set_wav_seq(aw8624, 1, 0); + aw8624_haptic_set_wav_loop(aw8624, 1, 0); + + return 0; +} + +static int aw8624_haptic_select_pin(struct aw8624 *aw8624, unsigned char pin) +{ + if (pin == TRIG1) { + aw8624_i2c_write_bits(aw8624, + AW8624_REG_DBGCTRL, + AW8624_BIT_DBGCTRL_INTN_TRG_SEL_MASK, + AW8624_BIT_DBGCTRL_TRG_SEL_ENABLE); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_TRG_CFG2, + AW8624_BIT_TRGCFG2_TRG1_ENABLE_MASK, + AW8624_BIT_TRGCFG2_TRG1_ENABLE); + aw_dev_info(aw8624->dev, "%s: select TRIG1 pin\n", __func__); + } else if (pin == IRQ) { + aw8624_i2c_write_bits(aw8624, + AW8624_REG_DBGCTRL, + AW8624_BIT_DBGCTRL_INTN_TRG_SEL_MASK, + AW8624_BIT_DBGCTRL_INTN_SEL_ENABLE); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_TRG_CFG2, + AW8624_BIT_TRGCFG2_TRG1_ENABLE_MASK, + AW8624_BIT_TRGCFG2_TRG1_DISABLE); + aw_dev_info(aw8624->dev, "%s: select INIT pin\n", __func__); + } else + aw_dev_err(aw8624->dev, "%s: There is no such option\n", + __func__); + return 0; +} +static int aw8624_haptic_trig1_param_init(struct aw8624 *aw8624) +{ + if (aw8624->IsUsedIRQ) { + aw8624_haptic_select_pin(aw8624, IRQ); + return 0; + } + aw8624->trig.trig_enable = aw8624_dts_data.trig_config[0]; + aw8624->trig.trig_edge = aw8624_dts_data.trig_config[1]; + aw8624->trig.trig_polar = aw8624_dts_data.trig_config[2]; + aw8624->trig.pos_sequence = aw8624_dts_data.trig_config[3]; + aw8624->trig.neg_sequence = aw8624_dts_data.trig_config[4]; + aw_dev_info(aw8624->dev, "%s: trig1 date init ok!\n", __func__); + return 0; +} +static int aw8624_haptic_tirg1_param_config(struct aw8624 *aw8624) +{ + if (aw8624->IsUsedIRQ) { + aw8624_haptic_select_pin(aw8624, IRQ); + return 0; + } + if (aw8624->trig.trig_enable) + aw8624_haptic_select_pin(aw8624, TRIG1); + else + aw8624_haptic_select_pin(aw8624, IRQ); + + aw8624_i2c_write_bits(aw8624, AW8624_REG_TRG_CFG1, + AW8624_BIT_TRGCFG1_TRG1_EDGE_MASK, + aw8624->trig.trig_edge); + aw8624_i2c_write_bits(aw8624, AW8624_REG_TRG_CFG1, + AW8624_BIT_TRGCFG1_TRG1_POLAR_MASK, + aw8624->trig.trig_polar << 1); + aw8624_i2c_write(aw8624, AW8624_REG_TRG1_SEQP, + aw8624->trig.pos_sequence); + aw8624_i2c_write(aw8624, AW8624_REG_TRG1_SEQN, + aw8624->trig.neg_sequence); + return 0; +} +static int aw8624_haptic_vbat_mode(struct aw8624 *aw8624, unsigned char flag) +{ + if (flag == AW8624_HAPTIC_VBAT_HW_COMP_MODE) { + aw8624_i2c_write_bits(aw8624, + AW8624_REG_ADCTEST, + AW8624_BIT_DETCTRL_VBAT_MODE_MASK, + AW8624_BIT_DETCTRL_VBAT_HW_COMP); + } else { + aw8624_i2c_write_bits(aw8624, + AW8624_REG_ADCTEST, + AW8624_BIT_DETCTRL_VBAT_MODE_MASK, + AW8624_BIT_DETCTRL_VBAT_SW_COMP); + } + return 0; +} + +static int aw8624_haptic_set_f0_preset(struct aw8624 *aw8624) +{ + unsigned int f0_reg = 0; + + f0_reg = 1000000000/(aw8624->f0_pre*aw8624_dts_data.aw8624_f0_coeff); + aw8624_i2c_write(aw8624, + AW8624_REG_F_PRE_H, + (unsigned char)((f0_reg>>8)&0xff)); + aw8624_i2c_write(aw8624, + AW8624_REG_F_PRE_L, + (unsigned char)((f0_reg>>0)&0xff)); + + return 0; +} + +static int aw8624_haptic_read_f0(struct aw8624 *aw8624) +{ + int ret = 0; + unsigned char reg_val = 0; + unsigned int f0_reg = 0; + unsigned long f0_tmp = 0; + + ret = aw8624_i2c_read(aw8624, AW8624_REG_F_LRA_F0_H, ®_val); + f0_reg = (reg_val<<8); + ret = aw8624_i2c_read(aw8624, AW8624_REG_F_LRA_F0_L, ®_val); + f0_reg |= (reg_val<<0); + if (!f0_reg || !aw8624_dts_data.aw8624_f0_coeff) { + aw8624->f0 = 0; + aw_dev_info(aw8624->dev, "%s : get f0 failed with the value becoming 0!\n", + __func__); + return -EPERM; + } + + f0_tmp = 1000000000 / (f0_reg * aw8624_dts_data.aw8624_f0_coeff); + aw8624->f0 = (unsigned int)f0_tmp; + aw_dev_info(aw8624->dev, "%s f0=%d\n", __func__, aw8624->f0); + return 0; +} + +static int aw8624_haptic_read_cont_f0(struct aw8624 *aw8624) +{ + int ret = 0; + unsigned char reg_val = 0; + unsigned int f0_reg = 0; + unsigned long f0_tmp = 0; + + ret = aw8624_i2c_read(aw8624, AW8624_REG_F_LRA_CONT_H, ®_val); + f0_reg = (reg_val<<8); + ret = aw8624_i2c_read(aw8624, AW8624_REG_F_LRA_CONT_L, ®_val); + f0_reg |= (reg_val<<0); + if (!f0_reg) { + aw8624->cont_f0 = 0; + aw_dev_info(aw8624->dev, + "%s: failed to reading cont f0 with 0\n", __func__); + return 0; + } + + f0_tmp = 1000000000/(f0_reg*aw8624_dts_data.aw8624_f0_coeff); + aw8624->cont_f0 = (unsigned int)f0_tmp; + aw_dev_info(aw8624->dev, "%s cont_f0=%d\n", __func__, aw8624->cont_f0); + return 0; +} + +static int aw8624_haptic_read_beme(struct aw8624 *aw8624) +{ + int ret = 0; + unsigned char reg_val = 0; + + ret = aw8624_i2c_read(aw8624, AW8624_REG_WAIT_VOL_MP, ®_val); + aw8624->max_pos_beme = (reg_val<<0); + ret = aw8624_i2c_read(aw8624, AW8624_REG_WAIT_VOL_MN, ®_val); + aw8624->max_neg_beme = (reg_val<<0); + + aw_dev_info(aw8624->dev, + "%s max_pos_beme=%d\n", __func__, aw8624->max_pos_beme); + aw_dev_info(aw8624->dev, + "%s max_neg_beme=%d\n", __func__, aw8624->max_neg_beme); + + return 0; +} + +static int aw8624_vbat_monitor_detector(struct aw8624 *aw8624) +{ + unsigned char reg_val = 0; + unsigned int vbat = 0; + + aw8624_haptic_stop(aw8624); + /*step 1:EN_RAMINIT*/ + aw8624_haptic_raminit(aw8624, true); + + /*step 2 :launch power supply testing */ + aw8624_i2c_write_bits(aw8624, + AW8624_REG_DETCTRL, + AW8624_BIT_DETCTRL_VBAT_GO_MASK, + AW8624_BIT_DETCTRL_VABT_GO_ENABLE); + usleep_range(2000, 2500); + + aw8624_i2c_read(aw8624, AW8624_REG_VBATDET, ®_val); + vbat = 6100 * reg_val / 256; + aw_dev_info(aw8624->dev, "%s get_vbat=%dmV\n", + __func__, vbat); + /*step 3: return val*/ + aw8624_haptic_raminit(aw8624, false); + + return vbat; +} + +static int aw8624_lra_resistance_detector(struct aw8624 *aw8624) +{ + unsigned char reg_val = 0; + unsigned char reg_val_anactrl = 0; + unsigned char reg_val_d2scfg = 0; + unsigned int r_lra = 0; + + mutex_lock(&aw8624->lock); + aw8624_i2c_read(aw8624, AW8624_REG_ANACTRL, ®_val_anactrl); + aw8624_i2c_read(aw8624, AW8624_REG_D2SCFG, ®_val_d2scfg); + aw8624_haptic_stop(aw8624); + aw8624_haptic_raminit(aw8624, true); + + + aw8624_i2c_write_bits(aw8624, + AW8624_REG_ANACTRL, + AW8624_BIT_ANACTRL_EN_IO_PD1_MASK, + AW8624_BIT_ANACTRL_EN_IO_PD1_HIGH); + + aw8624_i2c_write_bits(aw8624, + AW8624_REG_D2SCFG, + AW8624_BIT_D2SCFG_CLK_ADC_MASK, + AW8624_BIT_D2SCFG_CLK_ASC_1P5MHZ); + + aw8624_i2c_write_bits(aw8624, + AW8624_REG_DETCTRL, + AW8624_BIT_DETCTRL_RL_OS_MASK, + AW8624_BIT_DETCTRL_RL_DETECT); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_DETCTRL, + AW8624_BIT_DETCTRL_DIAG_GO_MASK, + AW8624_BIT_DETCTRL_DIAG_GO_ENABLE); + usleep_range(3000, 3500); + aw8624_i2c_read(aw8624, AW8624_REG_RLDET, ®_val); + r_lra = 298 * reg_val; + /*len += snprintf(buf+len, PAGE_SIZE-len, "r_lra=%dmohm\n", r_lra);*/ + + aw8624_i2c_write(aw8624, AW8624_REG_D2SCFG, reg_val_d2scfg); + aw8624_i2c_write(aw8624, AW8624_REG_ANACTRL, reg_val_anactrl); + aw8624_haptic_raminit(aw8624, false); + mutex_unlock(&aw8624->lock); + + return r_lra; +} + +static int aw8624_haptic_ram_vbat_comp(struct aw8624 *aw8624, bool flag) +{ + int temp_gain = 0; + int vbat = 0; + + if (flag) { + if (aw8624->ram_vbat_comp == + AW8624_HAPTIC_RAM_VBAT_COMP_ENABLE) { + vbat = aw8624_vbat_monitor_detector(aw8624); + temp_gain = aw8624->gain * AW8624_VBAT_REFER / vbat; + if + (temp_gain > (128*AW8624_VBAT_REFER/AW8624_VBAT_MIN)) { + temp_gain = + 128*AW8624_VBAT_REFER/AW8624_VBAT_MIN; + aw_dev_dbg(aw8624->dev, "%s gain limit=%d\n", + __func__, temp_gain); + } + aw8624_haptic_set_gain(aw8624, temp_gain); + } else { + aw8624_haptic_set_gain(aw8624, aw8624->gain); + } + } else { + aw8624_haptic_set_gain(aw8624, aw8624->gain); + } + + return 0; +} + +void aw8624_haptic_set_rtp_aei(struct aw8624 *aw8624, bool flag) +{ + if (flag) { + aw8624_i2c_write_bits(aw8624, + AW8624_REG_SYSINTM, + AW8624_BIT_SYSINTM_FF_AE_MASK, + AW8624_BIT_SYSINTM_FF_AE_EN); + } else { + aw8624_i2c_write_bits(aw8624, + AW8624_REG_SYSINTM, + AW8624_BIT_SYSINTM_FF_AE_MASK, + AW8624_BIT_SYSINTM_FF_AE_OFF); + } +} + +static unsigned char aw8624_haptic_rtp_get_fifo_afi(struct aw8624 *aw8624) +{ + unsigned char ret = 0; + unsigned char reg_val = 0; + + if (aw8624->osc_cali_flag == 1) { + aw8624_i2c_read(aw8624, AW8624_REG_SYSST, ®_val); + reg_val &= AW8624_BIT_SYSST_FF_AFS; + ret = reg_val >> 3; + } else { + aw8624_i2c_read(aw8624, AW8624_REG_SYSINT, ®_val); + reg_val &= AW8624_BIT_SYSINT_FF_AFI; + ret = reg_val >> 3; + } + return ret; +} + +unsigned char aw8624_haptic_rtp_get_fifo_afs(struct aw8624 *aw8624) +{ + unsigned char ret = 0; + unsigned char reg_val = 0; + + aw8624_i2c_read(aw8624, AW8624_REG_SYSST, ®_val); + reg_val &= AW8624_BIT_SYSST_FF_AFS; + ret = reg_val >> 3; + + return ret; +} + +static int aw8624_haptic_rtp_init(struct aw8624 *aw8624) +{ + unsigned int buf_len = 0; + unsigned char glb_st = 0; + + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + aw8624->rtp_cnt = 0; + mutex_lock(&aw8624->rtp_lock); + while ((!aw8624_haptic_rtp_get_fifo_afs(aw8624)) && + (aw8624->play_mode == AW8624_HAPTIC_RTP_MODE)) { + aw_dev_info(aw8624->dev, + "%s rtp cnt = %d\n", __func__, aw8624->rtp_cnt); + if (!aw8624->rtp_container) { + aw_dev_info(aw8624->dev, + "%s:aw8624_rtp is null break\n", __func__); + break; + } + + if (aw8624->rtp_cnt < aw8624->ram.base_addr) { + if ((aw8624->rtp_container->len-aw8624->rtp_cnt) < + (aw8624->ram.base_addr)) { + buf_len = aw8624->rtp_container->len-aw8624->rtp_cnt; + } else { + buf_len = (aw8624->ram.base_addr); + } + } else if ((aw8624->rtp_container->len - aw8624->rtp_cnt) < + (aw8624->ram.base_addr >> 2)) { + buf_len = aw8624->rtp_container->len - aw8624->rtp_cnt; + } else { + buf_len = (aw8624->ram.base_addr >> 2); + } + aw_dev_info(aw8624->dev, + "%s buf_len = %d\n", __func__, buf_len); + aw8624_i2c_writes(aw8624, AW8624_REG_RTP_DATA, + &aw8624->rtp_container->data[aw8624->rtp_cnt], + buf_len); + + aw8624->rtp_cnt += buf_len; + aw8624_i2c_read(aw8624, AW8624_REG_GLB_STATE, &glb_st); + if (aw8624->rtp_cnt == aw8624->rtp_container->len || + ((glb_st & 0x0f) == 0x00)) { + if (aw8624->rtp_cnt == aw8624->rtp_container->len) + aw_dev_info(aw8624->dev, + "%s: rtp load completely! glb_st=%02x aw8624->rtp_cnt=%02x\n", + __func__, glb_st, aw8624->rtp_cnt); + else + aw_dev_err(aw8624->dev, + "%s rtp load failed!! glb_st=%02x aw8624->rtp_cnt=%02x\n", + __func__, glb_st, aw8624->rtp_cnt); + aw8624->rtp_cnt = 0; + mutex_unlock(&aw8624->rtp_lock); + return 0; + } + } + + if (aw8624->play_mode == AW8624_HAPTIC_RTP_MODE) + aw8624_haptic_set_rtp_aei(aw8624, true); + + aw_dev_info(aw8624->dev, "%s exit\n", __func__); + mutex_unlock(&aw8624->rtp_lock); + return 0; +} + +static void aw8624_rtp_work_routine(struct work_struct *work) +{ + const struct firmware *rtp_file; + int ret = -1; + unsigned char reg_val = 0; + bool rtp_work_flag = false; + unsigned int cnt = 200; + + struct aw8624 *aw8624 = container_of(work, struct aw8624, rtp_work); + + /* fw loaded */ + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + mutex_lock(&aw8624->rtp_lock); + ret = request_firmware(&rtp_file, + aw8624_rtp_name[aw8624->rtp_file_num], aw8624->dev); + if (ret < 0) { + aw_dev_err(aw8624->dev, "%s: failed to read %s\n", __func__, + aw8624_rtp_name[aw8624->rtp_file_num]); + mutex_unlock(&aw8624->rtp_lock); + return; + } + aw8624->rtp_init = 0; + vfree(aw8624->rtp_container); + aw8624->rtp_container = vmalloc(rtp_file->size+sizeof(int)); + if (!aw8624->rtp_container) { + release_firmware(rtp_file); + aw_dev_err(aw8624->dev, + "%s: error allocating memory\n", __func__); + mutex_unlock(&aw8624->rtp_lock); + return; + } + aw8624->rtp_container->len = rtp_file->size; + aw_dev_info(aw8624->dev, "%s: rtp file [%s] size = %d\n", __func__, + aw8624_rtp_name[aw8624->rtp_file_num], aw8624->rtp_container->len); + memcpy(aw8624->rtp_container->data, rtp_file->data, rtp_file->size); + mutex_unlock(&aw8624->rtp_lock); + release_firmware(rtp_file); + mutex_lock(&aw8624->lock); + aw8624->rtp_init = 1; + if (aw8624->IsUsedIRQ) + aw8624_haptic_select_pin(aw8624, IRQ); + aw8624_haptic_upload_lra(aw8624, AW8624_HAPTIC_RTP_CALI_LRA); + /* rtp mode config */ + aw8624_haptic_play_mode(aw8624, AW8624_HAPTIC_RTP_MODE); + + /* haptic start */ + aw8624_haptic_start(aw8624); + mutex_unlock(&aw8624->lock); + usleep_range(2000, 2500); + while (cnt) { + aw8624_i2c_read(aw8624, AW8624_REG_GLB_STATE, ®_val); + if ((reg_val & 0x0f) == 0x08) { + cnt = 0; + rtp_work_flag = true; + aw_dev_info(aw8624->dev, "%s RTP_GO! glb_state=0x08\n", + __func__); + } else { + cnt--; + aw_dev_dbg(aw8624->dev, "%s wait for RTP_GO, glb_state=0x%02X\n", + __func__, reg_val); + } + usleep_range(2000, 2500); + } + if (rtp_work_flag) { + aw8624_haptic_rtp_init(aw8624); + } else { + /* enter standby mode */ + aw8624_haptic_stop(aw8624); + aw_dev_err(aw8624->dev, "%s failed to enter RTP_GO status!\n", + __func__); + } + +} + +/***************************************************** + * + * haptic - audio + * + *****************************************************/ +static enum hrtimer_restart +aw8624_haptic_audio_timer_func(struct hrtimer *timer) +{ + struct aw8624 *aw8624 = + container_of(timer, struct aw8624, haptic_audio.timer); + + schedule_work(&aw8624->haptic_audio.work); + hrtimer_start(&aw8624->haptic_audio.timer, + ktime_set(aw8624->haptic_audio.timer_val/1000, + (aw8624->haptic_audio.timer_val%1000)*1000000), + HRTIMER_MODE_REL); + return HRTIMER_NORESTART; +} + +static void aw8624_haptic_audio_work_routine(struct work_struct *work) +{ + struct aw8624 *aw8624 = + container_of(work, struct aw8624, haptic_audio.work); + struct haptic_audio *haptic_audio = NULL; + struct haptic_ctr *p_ctr = NULL; + struct haptic_ctr *p_ctr_bak = NULL; + unsigned int ctr_list_flag = 0; + unsigned int ctr_list_input_cnt = 0; + unsigned int ctr_list_output_cnt = 0; + unsigned int ctr_list_diff_cnt = 0; + unsigned int ctr_list_del_cnt = 0; + int rtp_is_going_on = 0; + + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + haptic_audio = &(aw8624->haptic_audio); + mutex_lock(&aw8624->haptic_audio.lock); + memset(&aw8624->haptic_audio.ctr, 0, sizeof(struct haptic_ctr)); + ctr_list_flag = 0; + list_for_each_entry_safe_reverse(p_ctr, p_ctr_bak, + &(haptic_audio->ctr_list), list) { + ctr_list_flag = 1; + break; + } + if (ctr_list_flag == 0) + aw_dev_info(aw8624->dev, "%s: ctr list empty\n", __func__); + if (ctr_list_flag == 1) { + list_for_each_entry_safe(p_ctr, p_ctr_bak, + &(haptic_audio->ctr_list), list) { + ctr_list_input_cnt = p_ctr->cnt; + break; + } + list_for_each_entry_safe_reverse(p_ctr, p_ctr_bak, + &(haptic_audio->ctr_list), list) { + ctr_list_output_cnt = p_ctr->cnt; + break; + } + if (ctr_list_input_cnt > ctr_list_output_cnt) + ctr_list_diff_cnt = ctr_list_input_cnt - ctr_list_output_cnt; + + if (ctr_list_input_cnt < ctr_list_output_cnt) + ctr_list_diff_cnt = 32 + ctr_list_input_cnt - ctr_list_output_cnt; + + if (ctr_list_diff_cnt > 2) { + list_for_each_entry_safe_reverse(p_ctr, p_ctr_bak, + &(haptic_audio->ctr_list), list) { + if ((p_ctr->play == 0) && + (AW8624_HAPTIC_CMD_ENABLE == + (AW8624_HAPTIC_CMD_HAPTIC & p_ctr->cmd))) { + list_del(&p_ctr->list); + kfree(p_ctr); + ctr_list_del_cnt++; + } + if (ctr_list_del_cnt == ctr_list_diff_cnt) + break; + } + } + } + + /* get the last data from list */ + list_for_each_entry_safe_reverse(p_ctr, p_ctr_bak, + &(haptic_audio->ctr_list), list) { + aw8624->haptic_audio.ctr.cnt = p_ctr->cnt; + aw8624->haptic_audio.ctr.cmd = p_ctr->cmd; + aw8624->haptic_audio.ctr.play = p_ctr->play; + aw8624->haptic_audio.ctr.wavseq = p_ctr->wavseq; + aw8624->haptic_audio.ctr.loop = p_ctr->loop; + aw8624->haptic_audio.ctr.gain = p_ctr->gain; + list_del(&p_ctr->list); + kfree(p_ctr); + break; + } + + if (aw8624->haptic_audio.ctr.play) { + aw_dev_info(aw8624->dev, "%s: cnt=%d, cmd=%d, play=%d, wavseq=%d, loop=%d, gain=%d\n", + __func__, + aw8624->haptic_audio.ctr.cnt, + aw8624->haptic_audio.ctr.cmd, + aw8624->haptic_audio.ctr.play, + aw8624->haptic_audio.ctr.wavseq, + aw8624->haptic_audio.ctr.loop, + aw8624->haptic_audio.ctr.gain); + } + + /* rtp mode jump */ + rtp_is_going_on = aw8624_haptic_juge_RTP_is_going_on(aw8624); + if (rtp_is_going_on) { + mutex_unlock(&aw8624->haptic_audio.lock); + return; + } + mutex_unlock(&aw8624->haptic_audio.lock); + + if ((AW8624_HAPTIC_CMD_HAPTIC & aw8624->haptic_audio.ctr.cmd) == + AW8624_HAPTIC_CMD_ENABLE) { + if (aw8624->haptic_audio.ctr.play == + AW8624_HAPTIC_PLAY_ENABLE) { + aw_dev_info(aw8624->dev, + "%s: haptic audio play start\n", __func__); + mutex_lock(&aw8624->lock); + aw8624_haptic_stop(aw8624); + + aw8624_haptic_play_mode(aw8624, AW8624_HAPTIC_RAM_MODE); + + aw8624_haptic_set_wav_seq(aw8624, 0x00, + aw8624->haptic_audio.ctr.wavseq); + + aw8624_haptic_set_wav_loop(aw8624, 0x00, + aw8624->haptic_audio.ctr.loop); + + aw8624_haptic_set_gain(aw8624, + aw8624->haptic_audio.ctr.gain); + + aw8624_haptic_start(aw8624); + mutex_unlock(&aw8624->lock); + } else if (AW8624_HAPTIC_PLAY_STOP == + aw8624->haptic_audio.ctr.play) { + mutex_lock(&aw8624->lock); + aw8624_haptic_stop(aw8624); + mutex_unlock(&aw8624->lock); + + } else if (AW8624_HAPTIC_PLAY_GAIN == + aw8624->haptic_audio.ctr.play) { + mutex_lock(&aw8624->lock); + aw8624_haptic_set_gain(aw8624, + aw8624->haptic_audio.ctr.gain); + mutex_unlock(&aw8624->lock); + } + } +} + +/***************************************************** + * + * haptic cont + * + *****************************************************/ +static int aw8624_haptic_cont(struct aw8624 *aw8624) +{ + unsigned char brake0_level = 0; + unsigned char en_brake1 = 0; + unsigned char brake1_level = 0; + unsigned char en_brake2 = 0; + unsigned char brake2_level = 0; + unsigned char brake2_p_num = 0; + unsigned char brake1_p_num = 0; + unsigned char brake0_p_num = 0; + + aw_dev_info(aw8624->dev, "%s enter\n.", __func__); + /* work mode */ + aw8624_haptic_active(aw8624); + aw8624_haptic_play_mode(aw8624, AW8624_HAPTIC_CONT_MODE); + + /* preset f0 */ + aw8624->f0_pre = aw8624->f0; + aw8624_haptic_set_f0_preset(aw8624); + + /* lpf */ + aw8624_i2c_write_bits(aw8624, + AW8624_REG_DATCTRL, + AW8624_BIT_DATCTRL_FC_MASK, + AW8624_BIT_DATCTRL_FC_1000HZ); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_DATCTRL, + AW8624_BIT_DATCTRL_LPF_ENABLE_MASK, + AW8624_BIT_DATCTRL_LPF_ENABLE); + + /* brake */ + en_brake1 = aw8624_dts_data.aw8624_cont_brake[0][0]; + en_brake2 = aw8624_dts_data.aw8624_cont_brake[0][1]; + brake0_level = aw8624_dts_data.aw8624_cont_brake[0][2]; + brake1_level = aw8624_dts_data.aw8624_cont_brake[0][3]; + brake2_level = aw8624_dts_data.aw8624_cont_brake[0][4]; + brake0_p_num = aw8624_dts_data.aw8624_cont_brake[0][5]; + brake1_p_num = aw8624_dts_data.aw8624_cont_brake[0][6]; + brake2_p_num = aw8624_dts_data.aw8624_cont_brake[0][7]; + + aw8624_i2c_write(aw8624, + AW8624_REG_BRAKE0_CTRL, + (brake0_level << 0)); + aw8624_i2c_write(aw8624, + AW8624_REG_BRAKE1_CTRL, + (en_brake1 << 7)|(brake1_level << 0)); + aw8624_i2c_write(aw8624, + AW8624_REG_BRAKE2_CTRL, + (en_brake2 << 7)|(brake2_level << 0)); + aw8624_i2c_write(aw8624, + AW8624_REG_BRAKE_NUM, + ((brake2_p_num << 6)|(brake1_p_num << 3) | + (brake0_p_num << 0))); + + /* cont config */ + aw8624_i2c_write_bits(aw8624, + AW8624_REG_CONT_CTRL, + AW8624_BIT_CONT_CTRL_ZC_DETEC_MASK, + AW8624_BIT_CONT_CTRL_ZC_DETEC_ENABLE); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_CONT_CTRL, + AW8624_BIT_CONT_CTRL_WAIT_PERIOD_MASK, + AW8624_BIT_CONT_CTRL_WAIT_1PERIOD); + + aw8624_i2c_write_bits(aw8624, + AW8624_REG_CONT_CTRL, + AW8624_BIT_CONT_CTRL_MODE_MASK, + AW8624_BIT_CONT_CTRL_BY_GO_SIGNAL); + + aw8624_i2c_write_bits(aw8624, + AW8624_REG_CONT_CTRL, + AW8624_BIT_CONT_CTRL_EN_CLOSE_MASK, + AW8624_BIT_CONT_CTRL_CLOSE_PLAYBACK); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_CONT_CTRL, + AW8624_BIT_CONT_CTRL_F0_DETECT_MASK, + AW8624_BIT_CONT_CTRL_F0_DETECT_DISABLE); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_CONT_CTRL, + AW8624_BIT_CONT_CTRL_O2C_MASK, + AW8624_BIT_CONT_CTRL_O2C_DISABLE); + + /* TD time */ + aw8624_i2c_write(aw8624, + AW8624_REG_TD_H, + (unsigned char)(aw8624->cont_td>>8)); + aw8624_i2c_write(aw8624, + AW8624_REG_TD_L, + (unsigned char)(aw8624->cont_td>>0)); + + + aw8624_i2c_write_bits(aw8624, + AW8624_REG_BEMF_NUM, + AW8624_BIT_BEMF_NUM_BRK_MASK, + aw8624->cont_num_brk); + aw8624_i2c_write(aw8624, + AW8624_REG_TIME_NZC, + 0x1f); + + /* f0 driver level */ + aw8624_i2c_write(aw8624, + AW8624_REG_DRV_LVL, + aw8624->cont_drv_lvl); + aw8624_i2c_write(aw8624, + AW8624_REG_DRV_LVL_OV, + aw8624->cont_drv_lvl_ov); + + /* cont play go */ + aw8624_haptic_play_go(aw8624, true); + + return 0; +} + +/***************************************************** + * + * haptic f0 cali + * + *****************************************************/ +static void aw8624_haptic_upload_lra(struct aw8624 *aw8624, unsigned int flag) +{ + switch (flag) { + case AW8624_HAPTIC_F0_CALI_LRA: + aw_dev_info(aw8624->dev, "%s f0_cali_lra=%d\n", + __func__, aw8624->f0_calib_data); + aw8624_i2c_write(aw8624, AW8624_REG_TRIM_LRA, + (char)aw8624->f0_calib_data); + break; + case AW8624_HAPTIC_RTP_CALI_LRA: + aw_dev_info(aw8624->dev, "%s rtp_cali_lra=%d\n", + __func__, aw8624->lra_calib_data); + aw8624_i2c_write(aw8624, AW8624_REG_TRIM_LRA, + (char)aw8624->lra_calib_data); + break; + case AW8624_HAPTIC_ZERO: + aw_dev_info(aw8624->dev, + "%s write zero to trim_lra!\n", __func__); + aw8624_i2c_write(aw8624, AW8624_REG_TRIM_LRA, 0); + break; + default: + break; + } +} +static int aw8624_haptic_get_f0(struct aw8624 *aw8624) +{ + int ret = 0; + unsigned char reg_val = 0; + unsigned char f0_pre_num = 0; + unsigned char f0_wait_num = 0; + unsigned char f0_repeat_num = 0; + unsigned char f0_trace_num = 0; + unsigned int t_f0_ms = 0; + unsigned int t_f0_trace_ms = 0; + unsigned char i = 0; + unsigned int f0_cali_cnt = 50; + + + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + + aw8624->f0 = aw8624->f0_pre; + /* f0 calibrate work mode */ + aw8624_haptic_stop(aw8624); + aw8624_haptic_play_mode(aw8624, AW8624_HAPTIC_CONT_MODE); + + + aw8624_i2c_write_bits(aw8624, + AW8624_REG_CONT_CTRL, + AW8624_BIT_CONT_CTRL_EN_CLOSE_MASK, + AW8624_BIT_CONT_CTRL_OPEN_PLAYBACK); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_CONT_CTRL, + AW8624_BIT_CONT_CTRL_F0_DETECT_MASK, + AW8624_BIT_CONT_CTRL_F0_DETECT_ENABLE); + + /* LPF */ + aw8624_i2c_write_bits(aw8624, + AW8624_REG_DATCTRL, + AW8624_BIT_DATCTRL_FC_MASK, + AW8624_BIT_DATCTRL_FC_1000HZ); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_DATCTRL, + AW8624_BIT_DATCTRL_LPF_ENABLE_MASK, + AW8624_BIT_DATCTRL_LPF_ENABLE); + + /* preset f0 */ + aw8624_haptic_set_f0_preset(aw8624); + /* f0 driver level */ + aw8624_i2c_write(aw8624, AW8624_REG_DRV_LVL, aw8624->cont_drv_lvl); + /* f0 trace parameter */ + if (!aw8624->f0_pre) { + aw_dev_info(aw8624->dev, "%s:fail to get t_f0_ms\n", __func__); + return 0; + } + + f0_pre_num = aw8624_dts_data.aw8624_f0_trace_parameter[0]; + f0_wait_num = aw8624_dts_data.aw8624_f0_trace_parameter[1]; + f0_repeat_num = aw8624_dts_data.aw8624_f0_trace_parameter[2]; + f0_trace_num = aw8624_dts_data.aw8624_f0_trace_parameter[3]; + aw8624_i2c_write(aw8624, + AW8624_REG_NUM_F0_1, + (f0_pre_num<<4)|(f0_wait_num<<0)); + aw8624_i2c_write(aw8624, + AW8624_REG_NUM_F0_2, + (f0_repeat_num<<0)); + aw8624_i2c_write(aw8624, + AW8624_REG_NUM_F0_3, + (f0_trace_num<<0)); + + /* clear aw8624 interrupt */ + ret = aw8624_i2c_read(aw8624, AW8624_REG_SYSINT, ®_val); + + /* play go and start f0 calibration */ + aw8624_haptic_play_go(aw8624, true); + + /* f0 trace time */ + t_f0_ms = 1000*10 / aw8624->f0_pre; + t_f0_trace_ms = + t_f0_ms * (f0_pre_num + f0_wait_num + + (f0_trace_num + f0_wait_num) * (f0_repeat_num - 1)); + aw_dev_info(aw8624->dev, "%s: t_f0_trace_ms = %dms\n", + __func__, t_f0_trace_ms); + usleep_range(t_f0_trace_ms * 1000, t_f0_trace_ms * 1000 + 500); + + for (i = 0; i < f0_cali_cnt; i++) { + ret = aw8624_i2c_read(aw8624, AW8624_REG_GLB_STATE, ®_val); + /* f0 calibrate done */ + if ((reg_val & 0x0f) == 0x00) { + aw8624_haptic_read_f0(aw8624); + aw8624_haptic_read_beme(aw8624); + break; + } + usleep_range(10000, 10500); + aw_dev_info(aw8624->dev, "%s f0 cali sleep 10ms,glb_state=0x%x\n", + __func__, reg_val); + } + + if (i == f0_cali_cnt) + ret = -ERANGE; + else + ret = 0; + + aw8624_i2c_write_bits(aw8624, + AW8624_REG_CONT_CTRL, + AW8624_BIT_CONT_CTRL_EN_CLOSE_MASK, + AW8624_BIT_CONT_CTRL_CLOSE_PLAYBACK); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_CONT_CTRL, + AW8624_BIT_CONT_CTRL_F0_DETECT_MASK, + AW8624_BIT_CONT_CTRL_F0_DETECT_DISABLE); + + return ret; +} + +static int aw8624_haptic_f0_calibration(struct aw8624 *aw8624) +{ + int ret = 0; + unsigned char reg_val = 0; + unsigned int f0_limit = 0; + char f0_cali_lra = 0; + int f0_cali_step = 0; + /*int f0_dft_step = 0;*/ + + if (aw8624_haptic_get_f0(aw8624)) { + aw_dev_err(aw8624->dev, + "%s get f0 error, user defafult f0\n", __func__); + } else { + /* max and min limit */ + f0_limit = aw8624->f0; + if (aw8624->f0*100 < aw8624_dts_data.aw8624_f0_pre * + (100-aw8624_dts_data.aw8624_f0_cali_percen)) { + f0_limit = aw8624_dts_data.aw8624_f0_pre; + } + if (aw8624->f0*100 > aw8624_dts_data.aw8624_f0_pre * + (100+aw8624_dts_data.aw8624_f0_cali_percen)) { + f0_limit = aw8624_dts_data.aw8624_f0_pre; + } + /* calculate cali step */ + f0_cali_step = + 100000*((int)f0_limit-(int)aw8624->f0_pre)/((int)f0_limit*25); + + if (f0_cali_step >= 0) { /*f0_cali_step >= 0*/ + if (f0_cali_step % 10 >= 5) { + f0_cali_step = f0_cali_step/10 + 1 + + (aw8624->chipid_flag == 1 ? 32 : 16); + } else { + f0_cali_step = f0_cali_step/10 + + (aw8624->chipid_flag == 1 ? 32 : 16); + } + } else { /*f0_cali_step < 0*/ + if (f0_cali_step % 10 <= -5) { + f0_cali_step = + (aw8624->chipid_flag == 1 ? 32 : 16) + + (f0_cali_step/10 - 1); + } else { + f0_cali_step = + (aw8624->chipid_flag == 1 ? 32 : 16) + + f0_cali_step/10; + } + } + + if (aw8624->chipid_flag == 1) { + if (f0_cali_step > 31) + f0_cali_lra = (char)f0_cali_step - 32; + else + f0_cali_lra = (char)f0_cali_step + 32; + } else { + if (f0_cali_step < 16 || + (f0_cali_step > 31 && f0_cali_step < 48)) { + f0_cali_lra = (char)f0_cali_step + 16; + } else { + f0_cali_lra = (char)f0_cali_step - 16; + } + } + + aw8624->f0_calib_data = (int)f0_cali_lra; + /* update cali step */ + aw8624_haptic_upload_lra(aw8624, + AW8624_HAPTIC_F0_CALI_LRA); + aw8624_i2c_read(aw8624, + AW8624_REG_TRIM_LRA, + ®_val); + aw_dev_info(aw8624->dev, + "%s final trim_lra=0x%02x\n", __func__, reg_val); + } + + /* if (aw8624_haptic_get_f0(aw8624)) { */ + /* aw_dev_err(aw8624->dev,*/ + /* "%s get f0 error, user defafult f0\n", __func__); */ + /* } */ + + aw8624_haptic_play_mode(aw8624, AW8624_HAPTIC_STANDBY_MODE); + aw8624_haptic_stop(aw8624); + + return ret; +} + +/***************************************************** + * + * haptic fops + * + *****************************************************/ +static int aw8624_file_open(struct inode *inode, struct file *file) +{ + if (!try_module_get(THIS_MODULE)) + return -ENODEV; + + file->private_data = (void *)g_aw8624; + + return 0; +} + +static int aw8624_file_release(struct inode *inode, struct file *file) +{ + file->private_data = (void *)NULL; + + module_put(THIS_MODULE); + + return 0; +} + +static long aw8624_file_unlocked_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct aw8624 *aw8624 = (struct aw8624 *)file->private_data; + int ret = 0; + + dev_info(aw8624->dev, "%s: cmd=0x%x, arg=0x%lx\n", + __func__, cmd, arg); + + mutex_lock(&aw8624->lock); + + if (_IOC_TYPE(cmd) != AW8624_HAPTIC_IOCTL_MAGIC) { + dev_err(aw8624->dev, "%s: cmd magic err\n", + __func__); + mutex_unlock(&aw8624->lock); + return -EINVAL; + } + + switch (cmd) { + default: + dev_err(aw8624->dev, "%s, unknown cmd\n", __func__); + break; + } + + mutex_unlock(&aw8624->lock); + + return ret; +} + +static ssize_t aw8624_file_read(struct file *filp, + char *buff, size_t len, loff_t *offset) +{ + struct aw8624 *aw8624 = (struct aw8624 *)filp->private_data; + int ret = 0; + int i = 0; + unsigned char reg_val = 0; + unsigned char *pbuff = NULL; + + mutex_lock(&aw8624->lock); + + dev_info(aw8624->dev, "%s: len=%zu\n", __func__, len); + + switch (aw8624->fileops.cmd) { + case AW8624_HAPTIC_CMD_READ_REG: + pbuff = kzalloc(len, GFP_KERNEL); + if (pbuff != NULL) { + for (i = 0; i < len; i++) { + aw8624_i2c_read(aw8624, + aw8624->fileops.reg+i, + ®_val); + pbuff[i] = reg_val; + } + ret = copy_to_user(buff, pbuff, len); + if (ret) { + dev_err + (aw8624->dev, "%s: copy to user fail\n", + __func__); + } + kfree(pbuff); + } else { + dev_err(aw8624->dev, "%s: alloc memory fail\n", + __func__); + } + break; + default: + dev_err(aw8624->dev, "%s, unknown cmd %d\n", + __func__, aw8624->fileops.cmd); + break; + } + + mutex_unlock(&aw8624->lock); + + for (i = 0; i < len; i++) { + dev_info(aw8624->dev, "%s: buff[%d]=0x%02x\n", + __func__, i, buff[i]); + } + + return len; +} + +static ssize_t aw8624_file_write(struct file *filp, + const char *buff, size_t len, loff_t *off) +{ + struct aw8624 *aw8624 = (struct aw8624 *)filp->private_data; + int i = 0; + int ret = 0; + unsigned char *pbuff = NULL; + + + mutex_lock(&aw8624->lock); + + aw8624->fileops.cmd = buff[0]; + + switch (aw8624->fileops.cmd) { + case AW8624_HAPTIC_CMD_READ_REG: + if (len == 2) { + aw8624->fileops.reg = buff[1]; + } else { + dev_err(aw8624->dev, + "%s: read cmd len %zu err\n", + __func__, len); + } + break; + case AW8624_HAPTIC_CMD_WRITE_REG: + if (len > 2) { + pbuff = kzalloc(len, GFP_KERNEL); + if (pbuff != NULL) { + ret = copy_from_user(pbuff, buff, len); + if (ret) { + dev_err(aw8624->dev, + "%s: copy from user fail\n", + __func__); + } else { + for (i = 0; i < len - 2; i++) { + dev_info + (aw8624->dev, + "%s: write reg0x%x = 0x%x\n", + __func__, + pbuff[1]+i, pbuff[i+2]); + aw8624_i2c_write(aw8624, + pbuff[1]+i, + pbuff[2+i]); + } + } + kfree(pbuff); + } else { + dev_err(aw8624->dev, + "%s: alloc memory fail\n", + __func__); + } + } else { + dev_err(aw8624->dev, + "%s: write cmd len %zu err\n", + __func__, len); + } + break; + default: + dev_err(aw8624->dev, + "%s, unknown cmd %d\n", + __func__, aw8624->fileops.cmd); + break; + } + + mutex_unlock(&aw8624->lock); + + return len; +} + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .read = aw8624_file_read, + .write = aw8624_file_write, + .unlocked_ioctl = aw8624_file_unlocked_ioctl, + .open = aw8624_file_open, + .release = aw8624_file_release, +}; + +struct miscdevice aw8624_haptic_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = AW8624_HAPTIC_NAME, + .fops = &fops, +}; + +static int +aw8624_haptic_audio_ctr_list_insert(struct haptic_audio *haptic_audio, + struct haptic_ctr *haptic_ctr, + struct device *dev) +{ + struct haptic_ctr *p_new = NULL; + + p_new = (struct haptic_ctr *)kzalloc( + sizeof(struct haptic_ctr), GFP_KERNEL); + if (p_new == NULL) { + aw_dev_err(dev, "%s: kzalloc memory fail\n", __func__); + return -ERANGE; + } + /* update new list info */ + p_new->cnt = haptic_ctr->cnt; + p_new->cmd = haptic_ctr->cmd; + p_new->play = haptic_ctr->play; + p_new->wavseq = haptic_ctr->wavseq; + p_new->loop = haptic_ctr->loop; + p_new->gain = haptic_ctr->gain; + + INIT_LIST_HEAD(&(p_new->list)); + list_add(&(p_new->list), &(haptic_audio->ctr_list)); + return 0; +} + +static int aw8624_haptic_audio_ctr_list_clear(struct haptic_audio *haptic_audio) +{ + struct haptic_ctr *p_ctr = NULL; + struct haptic_ctr *p_ctr_bak = NULL; + + list_for_each_entry_safe_reverse(p_ctr, p_ctr_bak, + &(haptic_audio->ctr_list), list) { + list_del(&p_ctr->list); + kfree(p_ctr); + } + + return 0; +} + +static int aw8624_haptic_audio_off(struct aw8624 *aw8624) +{ + aw_dev_info(aw8624->dev, "%s: enter\n", __func__); + mutex_lock(&aw8624->lock); + aw8624_haptic_set_gain(aw8624, 0x80); + aw8624_haptic_stop(aw8624); + aw8624->gun_type = 0xff; + aw8624->bullet_nr = 0; + aw8624_haptic_audio_ctr_list_clear(&aw8624->haptic_audio); + mutex_unlock(&aw8624->lock); + return 0; +} + +static int aw8624_haptic_audio_init(struct aw8624 *aw8624) +{ + + aw_dev_info(aw8624->dev, "%s: enter\n", __func__); + aw8624_haptic_set_wav_seq(aw8624, 0x01, 0x00); + + return 0; +} + +static int aw8624_haptic_offset_calibration(struct aw8624 *aw8624) +{ + unsigned int cont = 2000; + unsigned char reg_val = 0; + unsigned char reg_val_sysctrl = 0; + + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + aw8624_i2c_read(aw8624, AW8624_REG_SYSCTRL, ®_val_sysctrl); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_SYSCTRL, + AW8624_BIT_SYSCTRL_RAMINIT_MASK, + AW8624_BIT_SYSCTRL_RAMINIT_EN); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_DETCTRL, + AW8624_BIT_DETCTRL_DIAG_GO_MASK, + AW8624_BIT_DETCTRL_DIAG_GO_ENABLE); + while (1) { + aw8624_i2c_read(aw8624, AW8624_REG_DETCTRL, ®_val); + if ((reg_val & 0x01) == 0 || cont == 0) + break; + cont--; + } + if (cont == 0) + aw_dev_err(aw8624->dev, "%s calibration offset failed!\n", + __func__); + + aw8624_i2c_write(aw8624, AW8624_REG_SYSCTRL, reg_val_sysctrl); + return 0; + +} + +int aw8624_haptic_init(struct aw8624 *aw8624) +{ + int ret = 0; + unsigned char i = 0; + unsigned char reg_val = 0, reg_flag = 0; + unsigned char bemf_config = 0; + + ret = misc_register(&aw8624_haptic_misc); + if (ret) { + dev_err(aw8624->dev, "%s: misc fail: %d\n", __func__, ret); + return ret; + } + + /* haptic audio */ + aw8624->haptic_audio.delay_val = 23; + aw8624->haptic_audio.timer_val = 23; + INIT_LIST_HEAD(&(aw8624->haptic_audio.ctr_list)); + hrtimer_init(&aw8624->haptic_audio.timer, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); + aw8624->haptic_audio.timer.function = aw8624_haptic_audio_timer_func; + INIT_WORK(&aw8624->haptic_audio.work, aw8624_haptic_audio_work_routine); + aw8624->gun_type = 0xff; + aw8624->bullet_nr = 0x00; + mutex_init(&aw8624->haptic_audio.lock); + /* haptic init */ + mutex_lock(&aw8624->lock); + ret = aw8624_i2c_read(aw8624, AW8624_REG_EF_RDATAH, ®_flag); + if ((ret >= 0) && ((reg_flag & 0x1) == 1)) { + aw8624->chipid_flag = 1; + } else { + dev_err(aw8624->dev, + "%s: to read register AW8624_REG_EF_RDATAH: %d\n", + __func__, ret); + } + + aw8624->activate_mode = aw8624_dts_data.aw8624_mode; + ret = aw8624_i2c_read(aw8624, AW8624_REG_WAVSEQ1, ®_val); + aw8624->index = reg_val & 0x7F; + ret = aw8624_i2c_read(aw8624, AW8624_REG_DATDBG, ®_val); + aw8624->gain = reg_val & 0xFF; + for (i = 0; i < AW8624_SEQUENCER_SIZE; i++) { + ret = aw8624_i2c_read(aw8624, AW8624_REG_WAVSEQ1+i, ®_val); + aw8624->seq[i] = reg_val; + } + + aw8624_haptic_play_mode(aw8624, AW8624_HAPTIC_STANDBY_MODE); + aw8624_haptic_set_pwm(aw8624, AW8624_PWM_24K); + + aw8624_haptic_swicth_motorprotect_config(aw8624, 0x0, 0x0); + /*trig config*/ + aw8624_haptic_trig1_param_init(aw8624); + aw8624_haptic_tirg1_param_config(aw8624); + aw8624_haptic_offset_calibration(aw8624); + aw8624_haptic_vbat_mode(aw8624, AW8624_HAPTIC_VBAT_HW_COMP_MODE); + mutex_unlock(&aw8624->lock); + + /* f0 calibration */ + aw8624->f0_pre = aw8624_dts_data.aw8624_f0_pre; + aw8624->cont_drv_lvl = aw8624_dts_data.aw8624_cont_drv_lvl; + aw8624->cont_drv_lvl_ov = aw8624_dts_data.aw8624_cont_drv_lvl_ov; + aw8624->cont_td = aw8624_dts_data.aw8624_cont_td; + aw8624->cont_zc_thr = aw8624_dts_data.aw8624_cont_zc_thr; + aw8624->cont_num_brk = aw8624_dts_data.aw8624_cont_num_brk; + aw8624->ram_vbat_comp = AW8624_HAPTIC_RAM_VBAT_COMP_ENABLE; + mutex_lock(&aw8624->lock); + aw8624_i2c_write_bits(aw8624, AW8624_REG_R_SPARE, + AW8624_BIT_R_SPARE_MASK, AW8624_BIT_R_SPARE_ENABLE); + /*LRA trim source select register*/ + aw8624_i2c_write_bits(aw8624, + AW8624_REG_ANACTRL, + AW8624_BIT_ANACTRL_LRA_SRC_MASK, + AW8624_BIT_ANACTRL_LRA_SRC_REG); + aw8624_haptic_upload_lra(aw8624, AW8624_HAPTIC_ZERO); + aw8624_haptic_f0_calibration(aw8624); + mutex_unlock(&aw8624->lock); + + /*brake*/ + mutex_lock(&aw8624->lock); + aw8624_i2c_write(aw8624, + AW8624_REG_SW_BRAKE, + (unsigned char)(aw8624_dts_data.aw8624_sw_brake[0])); + aw8624_i2c_write(aw8624, AW8624_REG_THRS_BRA_END, 0x00); + aw8624_i2c_write_bits(aw8624, + AW8624_REG_WAVECTRL, + AW8624_BIT_WAVECTRL_NUM_OV_DRIVER_MASK, + AW8624_BIT_WAVECTRL_NUM_OV_DRIVER); + aw8624->f0_value = 20000 / aw8624_dts_data.aw8624_f0_pre + 1; + /* zero cross */ + aw8624_i2c_write(aw8624, + AW8624_REG_ZC_THRSH_H, + (unsigned char)(aw8624->cont_zc_thr>>8)); + aw8624_i2c_write(aw8624, + AW8624_REG_ZC_THRSH_L, + (unsigned char)(aw8624->cont_zc_thr>>0)); + aw8624_i2c_write(aw8624, + AW8624_REG_TSET, + aw8624_dts_data.aw8624_tset); + + /* bemf */ + bemf_config = aw8624_dts_data.aw8624_bemf_config[0]; + aw8624_i2c_write(aw8624, AW8624_REG_BEMF_VTHH_H, bemf_config); + bemf_config = aw8624_dts_data.aw8624_bemf_config[1]; + aw8624_i2c_write(aw8624, AW8624_REG_BEMF_VTHH_L, bemf_config); + bemf_config = aw8624_dts_data.aw8624_bemf_config[2]; + aw8624_i2c_write(aw8624, AW8624_REG_BEMF_VTHL_H, bemf_config); + bemf_config = aw8624_dts_data.aw8624_bemf_config[3]; + aw8624_i2c_write(aw8624, AW8624_REG_BEMF_VTHL_L, bemf_config); + mutex_unlock(&aw8624->lock); + + return ret; +} + +/***************************************************** + * + * vibrator + * + *****************************************************/ +#ifdef TIMED_OUTPUT +static int aw8624_vibrator_get_time(struct timed_output_dev *to_dev) +{ + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); + + if (hrtimer_active(&aw8624->timer)) { + ktime_t r = hrtimer_get_remaining(&aw8624->timer); + + return ktime_to_ms(r); + } + + return 0; +} + +static void aw8624_vibrator_enable(struct timed_output_dev *to_dev, int value) +{ + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); + + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + if (!aw8624->ram_init) { + aw_dev_err(aw8624->dev, "%s: ram init failed, not allow to play!\n", + __func__); + return; + } + mutex_lock(&aw8624->lock); + aw8624_haptic_stop(aw8624); + if (value > 0) { + aw8624_haptic_upload_lra(aw8624, AW8624_HAPTIC_F0_CALI_LRA); + aw8624_haptic_ram_vbat_comp(aw8624, false); + aw8624_haptic_play_wav_seq(aw8624, value); + } + + mutex_unlock(&aw8624->lock); +} + +#else +static void +aw8624_vibrator_enable(struct led_classdev *dev, enum led_brightness value) +{ + struct aw8624 *aw8624 = container_of(dev, struct aw8624, cdev); + + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + if (!aw8624->ram_init) { + aw_dev_err(aw8624->dev, "%s: ram init failed, not allow to play!\n", + __func__); + return; + } + mutex_lock(&aw8624->lock); + aw8624_haptic_stop(aw8624); + if (value > 0) { + aw8624_haptic_upload_lra(aw8624, AW8624_HAPTIC_F0_CALI_LRA); + aw8624_haptic_ram_vbat_comp(aw8624, false); + aw8624_haptic_play_wav_seq(aw8624, value); + } + + mutex_unlock(&aw8624->lock); + + +} + +#endif + +static ssize_t aw8624_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + + return snprintf(buf, PAGE_SIZE, "%d\n", aw8624->state); +} + +static ssize_t aw8624_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return count; +} + +static ssize_t aw8624_duration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + ktime_t time_rem; + s64 time_ms = 0; + + if (hrtimer_active(&aw8624->timer)) { + time_rem = hrtimer_get_remaining(&aw8624->timer); + time_ms = ktime_to_ms(time_rem); + } + + return snprintf(buf, PAGE_SIZE, "%lld\n", time_ms); +} + +static ssize_t aw8624_duration_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + /* setting 0 on duration is NOP for now */ + if (val <= 0) + return count; + + if (val <= aw8624->f0_value) + val = aw8624->f0_value; + rc = aw8624_haptic_ram_config(aw8624, val); + if (rc < 0) + return rc; + aw8624->duration = val; + return count; +} + +static ssize_t aw8624_activate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + /* For now nothing to show */ + return snprintf(buf, PAGE_SIZE, "%d\n", aw8624->state); +} + +static ssize_t aw8624_activate_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + unsigned int val = 0; + int rc = 0; + + if (!aw8624->ram_init) { + aw_dev_err(aw8624->dev, "%s: ram init failed, not allow to play!\n", + __func__); + return count; + } + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + if (val != 0 && val != 1) + return count; + + aw_dev_info(aw8624->dev, "%s: value=%d\n", __func__, val); + + mutex_lock(&aw8624->lock); + hrtimer_cancel(&aw8624->timer); + + aw8624->state = val; + + /*aw8624_haptic_stop(aw8624);*/ + + mutex_unlock(&aw8624->lock); + schedule_work(&aw8624->vibrator_work); + + return count; +} + +static ssize_t aw8624_activate_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + return snprintf(buf, PAGE_SIZE, "%d\n", + aw8624->activate_mode); +} + +static ssize_t aw8624_activate_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + mutex_lock(&aw8624->lock); + aw8624->activate_mode = val; + mutex_unlock(&aw8624->lock); + return count; +} + + +static ssize_t aw8624_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + unsigned char reg_val = 0; + + aw8624_i2c_read(aw8624, AW8624_REG_WAVSEQ1, ®_val); + aw8624->index = reg_val; + + return snprintf(buf, PAGE_SIZE, "%d\n", aw8624->index); +} + +static ssize_t aw8624_index_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + if (val > aw8624->ram.ram_num) { + aw_dev_err(aw8624->dev, + "%s: input value out of range!\n", __func__); + return count; + } + aw_dev_info(aw8624->dev, "%s: value=%d\n", __func__, val); + + mutex_lock(&aw8624->lock); + aw8624->index = val; + aw8624_haptic_set_repeat_wav_seq(aw8624, aw8624->index); + mutex_unlock(&aw8624->lock); + return count; +} + +static ssize_t aw8624_gain_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + #ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + return snprintf(buf, PAGE_SIZE, "0x%02x\n", aw8624->gain); +} + +static ssize_t aw8624_gain_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + aw_dev_info(aw8624->dev, "%s: value=%d\n", __func__, val); + + mutex_lock(&aw8624->lock); + aw8624->gain = val; + aw8624_haptic_set_gain(aw8624, aw8624->gain); + mutex_unlock(&aw8624->lock); + return count; +} + +static ssize_t aw8624_seq_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + size_t count = 0; + unsigned char i = 0; + unsigned char reg_val = 0; + + for (i = 0; i < AW8624_SEQUENCER_SIZE; i++) { + aw8624_i2c_read(aw8624, AW8624_REG_WAVSEQ1+i, ®_val); + count += snprintf(buf+count, PAGE_SIZE-count, + "seq%d: 0x%02x\n", i+1, reg_val); + aw8624->seq[i] |= reg_val; + } + return count; +} + +static ssize_t aw8624_seq_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + unsigned int databuf[2] = {0, 0}; + + if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) { + if (databuf[0] > AW8624_SEQUENCER_SIZE || + databuf[1] > aw8624->ram.ram_num) { + aw_dev_err(aw8624->dev, "%s input value out of range\n", + __func__); + return count; + } + aw_dev_info(aw8624->dev, "%s: seq%d=0x%x\n", + __func__, databuf[0], databuf[1]); + mutex_lock(&aw8624->lock); + aw8624->seq[databuf[0]] = (unsigned char)databuf[1]; + aw8624_haptic_set_wav_seq(aw8624, (unsigned char)databuf[0], + aw8624->seq[databuf[0]]); + mutex_unlock(&aw8624->lock); + } + return count; +} + +static ssize_t aw8624_loop_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + size_t count = 0; + unsigned char i = 0; + unsigned char reg_val = 0; + + for (i = 0; i < AW8624_SEQUENCER_LOOP_SIZE; i++) { + aw8624_i2c_read(aw8624, AW8624_REG_WAVLOOP1+i, ®_val); + aw8624->loop[i*2+0] = (reg_val>>4)&0x0F; + aw8624->loop[i*2+1] = (reg_val>>0)&0x0F; + + count += snprintf(buf+count, PAGE_SIZE-count, + "seq%d_loop: 0x%02x\n", i*2+1, aw8624->loop[i*2+0]); + count += snprintf(buf+count, PAGE_SIZE-count, + "seq%d_loop: 0x%02x\n", i*2+2, aw8624->loop[i*2+1]); + } + return count; +} + +static ssize_t aw8624_loop_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + unsigned int databuf[2] = {0, 0}; + + if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) { + aw_dev_info(aw8624->dev, "%s: seq%d loop=0x%x\n", + __func__, databuf[0], databuf[1]); + mutex_lock(&aw8624->lock); + aw8624->loop[databuf[0]] = (unsigned char)databuf[1]; + aw8624_haptic_set_wav_loop(aw8624, (unsigned char)databuf[0], + aw8624->loop[databuf[0]]); + mutex_unlock(&aw8624->lock); + } + + return count; +} + +static ssize_t aw8624_reg_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + ssize_t len = 0; + unsigned char i = 0; + unsigned char reg_val = 0; + + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + for (i = 0; i < AW8624_REG_MAX; i++) { + if (!(aw8624_reg_access[i]®_RD_ACCESS)) + continue; + aw8624_i2c_read(aw8624, i, ®_val); + len += snprintf(buf+len, PAGE_SIZE-len, "reg:0x%02x=0x%02x\n", + i, reg_val); + } + return len; +} + +static ssize_t aw8624_reg_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + unsigned int databuf[2] = {0, 0}; + + if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) { + aw8624_i2c_write(aw8624, + (unsigned char)databuf[0], + (unsigned char)databuf[1]); + } + + return count; +} + +static ssize_t aw8624_rtp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + ssize_t len = 0; + + len += snprintf(buf+len, PAGE_SIZE-len, "rtp_cnt = %d\n", + aw8624->rtp_cnt); + + return len; +} + +static ssize_t aw8624_rtp_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + + unsigned int val = 0; + int rc = 0; + + if (!(aw8624->IsUsedIRQ)) + return rc; + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + aw8624_haptic_stop(aw8624); + aw8624_haptic_set_rtp_aei(aw8624, false); + aw8624_interrupt_clear(aw8624); + if (val < (sizeof(aw8624_rtp_name)/AW8624_RTP_NAME_MAX)) { + aw8624->rtp_file_num = val; + if (val) { + aw_dev_info(aw8624->dev, + "%s: aw8624_rtp_name[%d]: %s\n", __func__, + val, aw8624_rtp_name[val]); + schedule_work(&aw8624->rtp_work); + } else + aw_dev_err(aw8624->dev, + "%s: rtp_file_num 0x%02X over max value\n", + __func__, aw8624->rtp_file_num); + } else { + aw_dev_err(aw8624->dev, "%s: rtp_file_num 0x%02x over max value\n", + __func__, aw8624->rtp_file_num); + } + + return count; +} + + +static ssize_t aw8624_ram_update_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + #ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + ssize_t len = 0; + unsigned char reg_val_sysctrl = 0; + unsigned char reg_val = 0; + unsigned int i = 0; + + aw8624_i2c_read(aw8624, AW8624_REG_SYSCTRL, ®_val_sysctrl); + /* RAMINIT Enable */ + aw8624_i2c_write_bits(aw8624, + AW8624_REG_SYSCTRL, + AW8624_BIT_SYSCTRL_RAMINIT_MASK, + AW8624_BIT_SYSCTRL_RAMINIT_EN); + aw8624_i2c_write(aw8624, AW8624_REG_RAMADDRH, + (unsigned char)(aw8624->ram.base_addr >> 8)); + aw8624_i2c_write(aw8624, AW8624_REG_RAMADDRL, + (unsigned char)(aw8624->ram.base_addr & 0x00ff)); + len += snprintf(buf + len, PAGE_SIZE - len, + "haptic_ram len = %d\n", aw8624->ram.len); + for (i = 0; i < aw8624->ram.len; i++) { + aw8624_i2c_read(aw8624, AW8624_REG_RAMDATA, ®_val); + if (i % 5 == 0) + len += snprintf(buf + len, + PAGE_SIZE - len, + "0x%02X\n", reg_val); + else + len += snprintf(buf + len, + PAGE_SIZE - len, + "0x%02X,", reg_val); + } + len += snprintf(buf + len, PAGE_SIZE - len, "\n"); + /* RAMINIT Disable */ + aw8624_i2c_write(aw8624, AW8624_REG_SYSCTRL, reg_val_sysctrl); + return len; +} + +static ssize_t aw8624_ram_update_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + if (val) + aw8624_ram_update(aw8624); + + return count; +} + +static ssize_t aw8624_f0_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + + ssize_t len = 0; + unsigned char temp = 0; + + mutex_lock(&aw8624->lock); + aw8624_i2c_read(aw8624, AW8624_REG_TRIM_LRA, &temp); + aw8624_haptic_upload_lra(aw8624, AW8624_HAPTIC_ZERO); + aw8624_haptic_get_f0(aw8624); + aw8624_i2c_write(aw8624, AW8624_REG_TRIM_LRA, temp); + mutex_unlock(&aw8624->lock); + len += snprintf(buf+len, PAGE_SIZE-len, "%d\n", aw8624->f0); + aw_dev_info(aw8624->dev, "len = %zd, buf=%s", len, buf); + return len; +} + +static ssize_t aw8624_f0_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + return count; +} + + +static ssize_t aw8624_cali_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + ssize_t len = 0; + unsigned char temp = 0; + + mutex_lock(&aw8624->lock); + aw8624_i2c_read(aw8624, AW8624_REG_TRIM_LRA, &temp); + aw8624_haptic_upload_lra(aw8624, AW8624_HAPTIC_F0_CALI_LRA); + aw8624_haptic_get_f0(aw8624); + aw8624_i2c_write(aw8624, AW8624_REG_TRIM_LRA, temp); + mutex_unlock(&aw8624->lock); + + len += snprintf(buf+len, PAGE_SIZE-len, "%d\n", aw8624->f0); + return len; +} + +static ssize_t +aw8624_cali_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + if (val) { + mutex_lock(&aw8624->lock); + aw8624_haptic_upload_lra(aw8624, AW8624_HAPTIC_ZERO); + aw8624_haptic_f0_calibration(aw8624); + mutex_unlock(&aw8624->lock); + } + return count; +} + +static ssize_t +aw8624_cont_show(struct device *dev, struct device_attribute *attr, char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + ssize_t len = 0; + + aw8624_haptic_read_cont_f0(aw8624); + len += snprintf(buf+len, PAGE_SIZE-len, "%d\n", aw8624->cont_f0); + return len; +} + +static ssize_t +aw8624_cont_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + aw8624_haptic_stop(aw8624); + + if (val) + aw8624_haptic_cont(aw8624); + return count; +} + + +static ssize_t +aw8624_cont_td_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + ssize_t len = 0; + + len += snprintf(buf+len, PAGE_SIZE-len, + "cont_delay_time = 0x%04x\n", + aw8624->cont_td); + return len; +} + +static ssize_t +aw8624_cont_td_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + int err, val; + + err = kstrtoint(buf, 16, &val); + if (err != 0) { + aw_dev_err(aw8624->dev, "%s format not match!", __func__); + return count; + } + + aw8624->cont_td = val; + aw8624_i2c_write(aw8624, + AW8624_REG_TD_H, + (unsigned char)(val >> 8)); + aw8624_i2c_write(aw8624, + AW8624_REG_TD_L, + (unsigned char)(val >> 0)); + + return count; +} + +static ssize_t +aw8624_cont_drv_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + ssize_t len = 0; + + len += snprintf(buf+len, PAGE_SIZE-len, + "cont drv level = 0x%02x\n", + aw8624->cont_drv_lvl); + len += snprintf(buf+len, PAGE_SIZE-len, + "cont drv level overdrive= 0x%02x\n", + aw8624->cont_drv_lvl_ov); + return len; +} + +static ssize_t +aw8624_cont_drv_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + unsigned int databuf[2] = {0, 0}; + + if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) { + aw8624->cont_drv_lvl = databuf[0]; + aw8624_i2c_write(aw8624, + AW8624_REG_DRV_LVL, + aw8624->cont_drv_lvl); + aw8624->cont_drv_lvl_ov = databuf[1]; + aw8624_i2c_write(aw8624, + AW8624_REG_DRV_LVL_OV, + aw8624->cont_drv_lvl_ov); + } + return count; +} + +static ssize_t +aw8624_cont_num_brk_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + ssize_t len = 0; + + len += snprintf(buf+len, PAGE_SIZE-len, + "cont_brk_num = 0x%02x\n", + aw8624->cont_num_brk); + return len; +} + +static ssize_t +aw8624_cont_num_brk_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + int err, val; + + err = kstrtoint(buf, 16, &val); + if (err != 0) { + aw_dev_err(aw8624->dev, "%s format not match!", __func__); + return count; + } + + aw8624->cont_num_brk = val; + if (aw8624->cont_num_brk > 7) + aw8624->cont_num_brk = 7; + + aw8624_i2c_write_bits(aw8624, AW8624_REG_BEMF_NUM, + AW8624_BIT_BEMF_NUM_BRK_MASK, aw8624->cont_num_brk); + + return count; +} + +static ssize_t +aw8624_cont_zc_thr_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + ssize_t len = 0; + + len += snprintf(buf+len, PAGE_SIZE-len, + "cont_zero_cross_thr = 0x%04x\n", + aw8624->cont_zc_thr); + return len; +} + +static ssize_t +aw8624_cont_zc_thr_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + int err, val; + + err = kstrtoint(buf, 0, &val); + if (err != 0) { + aw_dev_err(aw8624->dev, "%s format not match!", __func__); + return count; + } + aw_dev_info(aw8624->dev, "%s: val=%d\n", __func__, val); + if (val == 1) { + aw8624_i2c_write(aw8624, + AW8624_REG_ZC_THRSH_H, + (unsigned char)(val >> 8)); + aw8624_i2c_write(aw8624, + AW8624_REG_ZC_THRSH_L, + (unsigned char)(val >> 0)); + } + return count; +} + +static ssize_t +aw8624_vbat_monitor_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + ssize_t len = 0; + unsigned int vbat = 0; + + mutex_lock(&aw8624->lock); + vbat = aw8624_vbat_monitor_detector(aw8624); + mutex_unlock(&aw8624->lock); + len += snprintf(buf+len, PAGE_SIZE-len, "vbat_monitor = %d\n", vbat); + + return len; +} + +static ssize_t +aw8624_vbat_monitor_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return count; +} + +static ssize_t +aw8624_lra_resistance_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + ssize_t len = 0; + unsigned int r_lra = 0; + + r_lra = aw8624_lra_resistance_detector(aw8624); + + len += snprintf(buf+len, PAGE_SIZE-len, "lra_resistance = %d\n", r_lra); + return len; +} + + +static ssize_t +aw8624_lra_resistance_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return count; +} + + + +static ssize_t +aw8624_prctmode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + ssize_t len = 0; + unsigned char reg_val = 0; + + aw8624_i2c_read(aw8624, AW8624_REG_RLDET, ®_val); + + len += snprintf(buf+len, PAGE_SIZE-len, + "prctmode = %d\n", reg_val&0x20); + return len; +} + + +static ssize_t +aw8624_prctmode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + unsigned int databuf[2] = {0, 0}; + unsigned int addr = 0; + unsigned int val = 0; + + if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) { + addr = databuf[0]; + val = databuf[1]; + mutex_lock(&aw8624->lock); + aw8624_haptic_swicth_motorprotect_config(aw8624, addr, val); + mutex_unlock(&aw8624->lock); + } + return count; +} + +static ssize_t +aw8624_ram_vbat_comp_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + ssize_t len = 0; + + len += snprintf(buf+len, PAGE_SIZE-len, + "ram_vbat_comp = %d\n", + aw8624->ram_vbat_comp); + + return len; +} + + +static ssize_t +aw8624_ram_vbat_comp_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + mutex_lock(&aw8624->lock); + if (val) + aw8624->ram_vbat_comp = AW8624_HAPTIC_RAM_VBAT_COMP_ENABLE; + else + aw8624->ram_vbat_comp = AW8624_HAPTIC_RAM_VBAT_COMP_DISABLE; + + mutex_unlock(&aw8624->lock); + + return count; +} + +static ssize_t +aw8624_haptic_audio_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + ssize_t len = 0; + + len += snprintf(buf+len, PAGE_SIZE-len, "%d\n", + aw8624->haptic_audio.ctr.cnt); + return len; +} + +static ssize_t +aw8624_haptic_audio_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + unsigned int databuf[6] = {0}; + int rtp_is_going_on = 0; + struct haptic_ctr *hap_ctr = NULL; + + rtp_is_going_on = aw8624_haptic_juge_RTP_is_going_on(aw8624); + if (rtp_is_going_on) { + aw_dev_info(aw8624->dev, + "%s: RTP is runing, stop audio haptic\n", __func__); + return count; + } + if (!aw8624->ram_init) { + aw_dev_err(aw8624->dev, + "%s: ram init failed, not allow to play!\n", + __func__); + return count; + } + + if (sscanf(buf, "%d %d %d %d %d %d", + &databuf[0], &databuf[1], &databuf[2], + &databuf[3], &databuf[4], &databuf[5]) == 6) { + if (databuf[2]) { + aw_dev_dbg(aw8624->dev, + "%s: cnt=%d, cmd=%d, play=%d, wavseq=%d, loop=%d, gain=%d\n", + __func__, + databuf[0], databuf[1], databuf[2], + databuf[3], databuf[4], databuf[5]); + hap_ctr = (struct haptic_ctr *)kzalloc(sizeof(struct haptic_ctr), + GFP_KERNEL); + if (hap_ctr == NULL) { + aw_dev_err(aw8624->dev, + "%s: kzalloc memory fail\n", __func__); + return count; + } + mutex_lock(&aw8624->haptic_audio.lock); + hap_ctr->cnt = (unsigned char)databuf[0]; + hap_ctr->cmd = (unsigned char)databuf[1]; + hap_ctr->play = (unsigned char)databuf[2]; + hap_ctr->wavseq = (unsigned char)databuf[3]; + hap_ctr->loop = (unsigned char)databuf[4]; + hap_ctr->gain = (unsigned char)databuf[5]; + aw8624_haptic_audio_ctr_list_insert(&aw8624->haptic_audio, + hap_ctr, aw8624->dev); + + if (hap_ctr->cmd == 0xff) { + aw_dev_info(aw8624->dev, + "%s: haptic_audio stop\n", __func__); + if (hrtimer_active(&aw8624->haptic_audio.timer)) { + aw_dev_info(aw8624->dev, + "%s: cancel haptic_audio_timer\n", + __func__); + hrtimer_cancel(&aw8624->haptic_audio.timer); + aw8624->haptic_audio.ctr.cnt = 0; + aw8624_haptic_audio_off(aw8624); + } + } else { + if (hrtimer_active(&aw8624->haptic_audio.timer)) { + } else { + aw_dev_info(aw8624->dev, "%s: start haptic_audio_timer\n", + __func__); + aw8624_haptic_audio_init(aw8624); + hrtimer_start(&aw8624->haptic_audio.timer, + ktime_set(aw8624->haptic_audio.delay_val/1000, + (aw8624->haptic_audio.delay_val%1000)*1000000), + HRTIMER_MODE_REL); + } + } + } + mutex_unlock(&aw8624->haptic_audio.lock); + kfree(hap_ctr); + + + } + return count; +} + +static ssize_t +aw8624_haptic_audio_time_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + ssize_t len = 0; + + len += snprintf(buf+len, PAGE_SIZE-len, + "haptic_audio.delay_val=%dus\n", + aw8624->haptic_audio.delay_val); + len += snprintf(buf+len, PAGE_SIZE-len, + "haptic_audio.timer_val=%dus\n", + aw8624->haptic_audio.timer_val); + return len; +} + +static ssize_t +aw8624_haptic_audio_time_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + unsigned int databuf[2] = {0}; + + if (sscanf(buf, "%d %d", &databuf[0], &databuf[1]) == 2) { + aw8624->haptic_audio.delay_val = databuf[0]; + aw8624->haptic_audio.timer_val = databuf[1]; + } + return count; +} + +static int aw8624_clock_OSC_trim_calibration + (unsigned long int theory_time, unsigned long int real_time) +{ + unsigned int real_code = 0; + unsigned int LRA_TRIM_CODE = 0; + unsigned int DFT_LRA_TRIM_CODE = 0; + unsigned int Not_need_cali_threshold = 10;/*0.1 percent not need cali*/ + + aw_dev_info(g_aw8624->dev, "%s enter\n", __func__); + if (theory_time == real_time) { + aw_dev_info(g_aw8624->dev, + "aw_osctheory_time == real_time:%ld,", real_time); + aw_dev_info(g_aw8624->dev, + "theory_time = %ld not need to cali\n", theory_time); + return 0; + } else if (theory_time < real_time) { + if ((real_time - theory_time) > (theory_time / 25)) { + aw_dev_info(g_aw8624->dev, + "%s: failed not to cali\n", __func__); + return DFT_LRA_TRIM_CODE; + } + + if ((real_time - theory_time) < + (Not_need_cali_threshold*theory_time/10000)) { + aw_dev_info(g_aw8624->dev, "aw_oscmicrosecond:%ld,theory_time = %ld\n", + real_time, theory_time); + return DFT_LRA_TRIM_CODE; + } + + real_code = ((real_time - theory_time) * 4000) / theory_time; + real_code = ((real_code%10 < 5) ? 0 : 1) + real_code/10; + real_code = 32 + real_code; + } else if (theory_time > real_time) { + if ((theory_time - real_time) > (theory_time / 25)) { + aw_dev_info(g_aw8624->dev, "failed not to cali\n"); + return DFT_LRA_TRIM_CODE; + } + if ((theory_time - real_time) < + (Not_need_cali_threshold * theory_time/10000)) { + aw_dev_info(g_aw8624->dev, "aw_oscmicrosecond:%ld,theory_time = %ld\n", + real_time, theory_time); + return DFT_LRA_TRIM_CODE; + } + real_code = ((theory_time - real_time) * 4000) / theory_time; + real_code = ((real_code%10 < 5) ? 0 : 1) + real_code/10; + real_code = 32 - real_code; + } + if (real_code > 31) + LRA_TRIM_CODE = real_code - 32; + else + LRA_TRIM_CODE = real_code + 32; + + aw_dev_info(g_aw8624->dev, "aw_oscmicrosecond:%ld,theory_time = %ld,real_code =0X%02X,", + real_time, theory_time, real_code); + aw_dev_info(g_aw8624->dev, "LRA_TRIM_CODE 0X%02X\n", LRA_TRIM_CODE); + + return LRA_TRIM_CODE; +} + +static int aw8624_rtp_trim_lra_calibration(struct aw8624 *aw8624) +{ + unsigned char reg_val = 0; + unsigned int fre_val = 0; + unsigned int theory_time = 0; + unsigned int lra_rtim_code = 0; + + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + aw8624_i2c_read(aw8624, AW8624_REG_PWMDBG, ®_val); + fre_val = (reg_val & 0x006f) >> 5; + + if (fre_val == 3) + theory_time = (aw8624->rtp_len / 12000) * 1000000; /*12K */ + if (fre_val == 2) + theory_time = (aw8624->rtp_len / 24000) * 1000000; /*24K */ + if (fre_val == 1 || fre_val == 0) + theory_time = (aw8624->rtp_len / 48000) * 1000000; /*48K */ + + aw_dev_info(aw8624->dev, "microsecond:%ld theory_time = %d\n", + aw8624->microsecond, theory_time); + + lra_rtim_code = aw8624_clock_OSC_trim_calibration(theory_time, + aw8624->microsecond); + if (lra_rtim_code >= 0) { + aw8624->lra_calib_data = lra_rtim_code; + aw8624_i2c_write(aw8624, AW8624_REG_TRIM_LRA, + (char)lra_rtim_code); + } + return 0; +} + +static unsigned char aw8624_haptic_osc_read_int(struct aw8624 *aw8624) +{ + unsigned char reg_val = 0; + + aw8624_i2c_read(aw8624, AW8624_REG_DBGSTAT, ®_val); + return reg_val; +} + +static int aw8624_rtp_osc_calibration(struct aw8624 *aw8624) +{ + const struct firmware *rtp_file; + int ret = -1; + unsigned int buf_len = 0; + unsigned char osc_int_state = 0; + + aw8624->rtp_cnt = 0; + aw8624->timeval_flags = 1; + aw8624->osc_cali_flag = 1; + + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + /* fw loaded */ + ret = request_firmware(&rtp_file, + aw8624_rtp_name[0],/*aw8624->rtp_file_num */ + aw8624->dev); + if (ret < 0) { + /*aw8624->rtp_file_num */ + aw_dev_err(aw8624->dev, + "%s: failed to read %s\n", + __func__, aw8624_rtp_name[0]); + return ret; + } + + /*awinic add stop,for irq interrupt during calibrate*/ + aw8624_haptic_stop(aw8624); + aw8624->rtp_init = 0; + mutex_lock(&aw8624->rtp_lock); + vfree(aw8624->rtp_container); + aw8624->rtp_container = vmalloc(rtp_file->size + sizeof(int)); + if (!aw8624->rtp_container) { + release_firmware(rtp_file); + mutex_unlock(&aw8624->rtp_lock); + aw_dev_err(aw8624->dev, + "%s: error allocating memory\n", __func__); + return -ENOMEM; + } + aw8624->rtp_container->len = rtp_file->size; + aw8624->rtp_len = rtp_file->size; + /*aw8624->rtp_file_num */ + aw_dev_info(aw8624->dev, "%s: rtp file [%s] size = %d\n", __func__, + aw8624_rtp_name[0], aw8624->rtp_container->len); + memcpy(aw8624->rtp_container->data, rtp_file->data, rtp_file->size); + release_firmware(rtp_file); + mutex_unlock(&aw8624->rtp_lock); + + /* gain */ + aw8624_haptic_ram_vbat_comp(aw8624, false); + + /* rtp mode config */ + aw8624_haptic_play_mode(aw8624, AW8624_HAPTIC_RTP_MODE); + + aw8624_i2c_write_bits(aw8624, AW8624_REG_DBGCTRL, + AW8624_BIT_DBGCTRL_INT_MODE_MASK, + AW8624_BIT_DBGCTRL_INT_MODE_EDGE); + disable_irq(gpio_to_irq(aw8624->irq_gpio)); + /* haptic start */ + aw8624_haptic_start(aw8624); + pm_qos_add_request(&aw8624_pm_qos_req_vb, PM_QOS_CPU_DMA_LATENCY, + AW8624_PM_QOS_VALUE_VB); + while (1) { + if (!aw8624_haptic_rtp_get_fifo_afi(aw8624)) { + aw_dev_info(aw8624->dev, + "%s: haptic_rtp_get_fifo_afi, rtp_cnt= %d\n", + __func__, aw8624->rtp_cnt); + mutex_lock(&aw8624->rtp_lock); + if ((aw8624->rtp_container->len - aw8624->rtp_cnt) < + (aw8624->ram.base_addr >> 2)) + buf_len = aw8624->rtp_container->len - aw8624->rtp_cnt; + else + buf_len = (aw8624->ram.base_addr >> 2); + + if (aw8624->rtp_cnt != aw8624->rtp_container->len) { + if (aw8624->timeval_flags == 1) { +#ifdef KERNEL_VERSION_49 + do_gettimeofday(&aw8624->start); +#else + aw8624->kstart = ktime_get(); +#endif + aw8624->timeval_flags = 0; + } + aw8624->rtpupdate_flag = + aw8624_i2c_writes(aw8624, + AW8624_REG_RTP_DATA, + &aw8624->rtp_container->data[aw8624-> + rtp_cnt], buf_len); + aw8624->rtp_cnt += buf_len; + } + mutex_unlock(&aw8624->rtp_lock); + } + + osc_int_state = aw8624_haptic_osc_read_int(aw8624); + if (osc_int_state&AW8624_BIT_SYSINT_DONEI) { +#ifdef KERNEL_VERSION_49 + do_gettimeofday(&aw8624->end); +#else + aw8624->kend = ktime_get(); +#endif + aw_dev_info(aw8624->dev, + "%s vincent playback aw8624->rtp_cnt= %d\n", + __func__, aw8624->rtp_cnt); + break; + } +#ifdef KERNEL_VERSION_49 + do_gettimeofday(&aw8624->end); + aw8624->microsecond = + (aw8624->end.tv_sec - aw8624->start.tv_sec)*1000000 + + (aw8624->end.tv_usec - aw8624->start.tv_usec); + +#else + aw8624->kend = ktime_get(); + aw8624->microsecond = ktime_to_us(ktime_sub(aw8624->kend, + aw8624->kstart)); + +#endif + if (aw8624->microsecond > AW8624_OSC_CALIBRATION_T_LENGTH) { + aw_dev_info(aw8624->dev, + "%s:vincent time out aw8624->rtp_cnt %d,", + __func__, aw8624->rtp_cnt); + aw_dev_info(aw8624->dev, + "osc_int_state %02x\n", osc_int_state); + break; + } + } + pm_qos_remove_request(&aw8624_pm_qos_req_vb); + enable_irq(gpio_to_irq(aw8624->irq_gpio)); + + aw8624->osc_cali_flag = 0; +#ifdef KERNEL_VERSION_49 + aw8624->microsecond = + (aw8624->end.tv_sec - aw8624->start.tv_sec)*1000000 + + (aw8624->end.tv_usec - aw8624->start.tv_usec); + +#else + aw8624->microsecond = ktime_to_us(ktime_sub(aw8624->kend, + aw8624->kstart)); +#endif + /*calibration osc*/ + aw_dev_info(aw8624->dev, + "%s awinic_microsecond:%ld\n", __func__, aw8624->microsecond); + aw_dev_info(aw8624->dev, "%s exit\n", __func__); + return 0; +} + +static ssize_t aw8624_osc_cali_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + ssize_t len = 0; + + len += + snprintf(buf + len, PAGE_SIZE - len, "lra_calib_data=%d\n", + aw8624->lra_calib_data); + + return len; +} + +static ssize_t aw8624_osc_cali_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + mutex_lock(&aw8624->lock); + /* osc calibration flag start,Other behaviors are forbidden */ + aw8624->osc_cali_run = 1; + if (val == 1) { + aw8624_haptic_upload_lra(aw8624, AW8624_HAPTIC_ZERO); + aw8624_rtp_osc_calibration(aw8624); + aw8624_rtp_trim_lra_calibration(aw8624); + } else if (val == 2) { + aw8624_haptic_upload_lra(aw8624, AW8624_HAPTIC_RTP_CALI_LRA); + aw8624_rtp_osc_calibration(aw8624); + } else { + aw_dev_err(aw8624->dev, + "%s input value out of range\n", __func__); + } + aw8624->osc_cali_run = 0; + /* osc calibration flag end,Other behaviors are permitted */ + mutex_unlock(&aw8624->lock); + + return count; +} + + + + +static enum hrtimer_restart aw8624_vibrator_timer_func(struct hrtimer *timer) +{ + struct aw8624 *aw8624 = container_of(timer, struct aw8624, timer); + + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + aw8624->state = 0; + schedule_work(&aw8624->vibrator_work); + + return HRTIMER_NORESTART; +} + + + +static void aw8624_vibrator_work_routine(struct work_struct *work) +{ + struct aw8624 *aw8624 = + container_of(work, struct aw8624, vibrator_work); + + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + + mutex_lock(&aw8624->lock); + + aw8624_haptic_stop(aw8624); + + aw8624_haptic_upload_lra(aw8624, AW8624_HAPTIC_F0_CALI_LRA); + + if (aw8624->state) { + if (aw8624->activate_mode == AW8624_HAPTIC_ACTIVATE_RAM_MODE) { + aw8624_haptic_ram_vbat_comp(aw8624, true); + aw8624_haptic_play_repeat_seq(aw8624, true); + } else if (aw8624->activate_mode == AW8624_HAPTIC_ACTIVATE_CONT_MODE) { + aw8624_haptic_cont(aw8624); + } else { + /*other mode*/ + } + /* run ms timer */ + hrtimer_start(&aw8624->timer, + ktime_set(aw8624->duration / 1000, + (aw8624->duration % 1000) * 1000000), + HRTIMER_MODE_REL); +#ifdef CONFIG_PM_WAKELOCKS + __pm_stay_awake(&aw8624->wk_lock); +#else + wake_lock(&aw8624->wk_lock); +#endif + aw8624->wk_lock_flag = 1; + } else { + if (aw8624->wk_lock_flag == 1) { +#ifdef CONFIG_PM_WAKELOCKS + __pm_relax(&aw8624->wk_lock); +#else + wake_unlock(&aw8624->wk_lock); +#endif + aw8624->wk_lock_flag = 0; + } + /*aw8624_haptic_stop(aw8624);*/ + } + + mutex_unlock(&aw8624->lock); +} + + + +/****************************************************** + * + * irq + * + ******************************************************/ +void aw8624_interrupt_setup(struct aw8624 *aw8624) +{ + unsigned char reg_val = 0; + + aw8624_i2c_read(aw8624, AW8624_REG_SYSINT, ®_val); + aw_dev_info(aw8624->dev, "%s: reg SYSINT=0x%x\n", __func__, reg_val); + + aw8624_i2c_write_bits(aw8624, AW8624_REG_DBGCTRL, + AW8624_BIT_DBGCTRL_INT_MODE_MASK, + AW8624_BIT_DBGCTRL_INT_MODE_EDGE); + + aw8624_i2c_write_bits(aw8624, AW8624_REG_SYSINTM, + AW8624_BIT_SYSINTM_UVLO_MASK, AW8624_BIT_SYSINTM_UVLO_EN); + aw8624_i2c_write_bits(aw8624, AW8624_REG_SYSINTM, + AW8624_BIT_SYSINTM_OCD_MASK, AW8624_BIT_SYSINTM_OCD_EN); + aw8624_i2c_write_bits(aw8624, AW8624_REG_SYSINTM, + AW8624_BIT_SYSINTM_OT_MASK, AW8624_BIT_SYSINTM_OT_EN); +} + +static ssize_t aw8624_gun_type_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + return snprintf(buf, PAGE_SIZE, "0x%02x\n", aw8624->gun_type); +} + +static ssize_t aw8624_gun_type_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + aw_dev_dbg(aw8624->dev, "%s: value=%d\n", __func__, val); + + mutex_lock(&aw8624->lock); + aw8624->gun_type = val; + mutex_unlock(&aw8624->lock); + return count; +} + +static ssize_t aw8624_bullet_nr_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + return snprintf(buf, PAGE_SIZE, "0x%02x\n", aw8624->bullet_nr); +} + +static ssize_t aw8624_bullet_nr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + + unsigned int val = 0; + int rc = 0; + + rc = kstrtouint(buf, 0, &val); + if (rc < 0) + return rc; + + aw_dev_dbg(aw8624->dev, "%s: value=%d\n", __func__, val); + + mutex_lock(&aw8624->lock); + aw8624->bullet_nr = val; + mutex_unlock(&aw8624->lock); + return count; +} + +static ssize_t aw8624_trig_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + ssize_t len = 0; + + len += snprintf(buf + len, PAGE_SIZE - len, + "trig: trig_enable=%d, trig_edge=%d, trig_polar=%d, pos_sequence=%d, neg_sequence=%d\n", + aw8624->trig.trig_enable, + aw8624->trig.trig_edge, + aw8624->trig.trig_polar, + aw8624->trig.pos_sequence, + aw8624->trig.neg_sequence); + return len; + +} + +static ssize_t aw8624_trig_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + unsigned int databuf[5] = { 0 }; + + if (!aw8624->ram_init) { + aw_dev_err(aw8624->dev, + "%s: ram init failed, not allow to play!\n", + __func__); + return count; + } + if (sscanf(buf, "%d %d %d %d %d", + &databuf[0], &databuf[1], + &databuf[2], &databuf[3], &databuf[4]) == 5) { + if (databuf[0] > 1) + databuf[0] = 1; + if (databuf[0] < 0) + databuf[0] = 0; + if (databuf[1] > 1) + databuf[0] = 1; + if (databuf[1] < 0) + databuf[0] = 0; + if (databuf[2] > 1) + databuf[0] = 1; + if (databuf[2] < 0) + databuf[0] = 0; + if (databuf[3] > aw8624->ram.ram_num || + databuf[4] > aw8624->ram.ram_num) { + aw_dev_err(aw8624->dev, + "%s: input seq value out of range!\n", + __func__); + return count; + } + aw8624->trig.trig_enable = databuf[0]; + aw8624->trig.trig_edge = databuf[1]; + aw8624->trig.trig_polar = databuf[2]; + aw8624->trig.pos_sequence = databuf[3]; + aw8624->trig.neg_sequence = databuf[4]; + mutex_lock(&aw8624->lock); + aw8624_haptic_tirg1_param_config(aw8624); + mutex_unlock(&aw8624->lock); + } else + aw_dev_err(aw8624->dev, + "%s: please input five parameters\n", + __func__); + return count; +} + +static ssize_t aw8624_ram_num_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ +#ifdef TIMED_OUTPUT + struct timed_output_dev *to_dev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(to_dev, struct aw8624, to_dev); +#else + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw8624 *aw8624 = container_of(cdev, struct aw8624, cdev); +#endif + ssize_t len = 0; + + aw8624_haptic_get_ram_number(aw8624); + + len += snprintf(buf + len, PAGE_SIZE - len, + "ram_num = %d\n", aw8624->ram.ram_num); + return len; + +} + +#if 0 +/* +* schedule_work is low priority. +* aw8624 rtp mode can`t be interrupted. +*/ +static irqreturn_t aw8624_irq(int irq, void *data) +{ + struct aw8624 *aw8624 = (struct aw8624 *)data; + + schedule_work(&aw8624->irq_work); + return IRQ_HANDLED; +} + +#endif +/***************************************************** + * + * device tree + * + *****************************************************/ +static DEVICE_ATTR(state, 0664, aw8624_state_show, aw8624_state_store); +static DEVICE_ATTR(duration, 0664, aw8624_duration_show, aw8624_duration_store); +static DEVICE_ATTR(activate, 0664, aw8624_activate_show, aw8624_activate_store); +static DEVICE_ATTR(activate_mode, 0664, + aw8624_activate_mode_show, aw8624_activate_mode_store); +static DEVICE_ATTR(index, 0664, aw8624_index_show, aw8624_index_store); +static DEVICE_ATTR(gain, 0664, aw8624_gain_show, aw8624_gain_store); +static DEVICE_ATTR(seq, 0664, aw8624_seq_show, aw8624_seq_store); +static DEVICE_ATTR(loop, 0664, aw8624_loop_show, aw8624_loop_store); +static DEVICE_ATTR(register, 0664, aw8624_reg_show, aw8624_reg_store); +static DEVICE_ATTR(ram_update, 0664, + aw8624_ram_update_show, aw8624_ram_update_store); +static DEVICE_ATTR(f0, 0664, aw8624_f0_show, aw8624_f0_store); +static DEVICE_ATTR(cali, 0664, aw8624_cali_show, aw8624_cali_store); +static DEVICE_ATTR(cont, 0664, aw8624_cont_show, aw8624_cont_store); +static DEVICE_ATTR(cont_td, 0664, aw8624_cont_td_show, aw8624_cont_td_store); +static DEVICE_ATTR(cont_drv, 0664, aw8624_cont_drv_show, aw8624_cont_drv_store); +static DEVICE_ATTR(cont_num_brk, 0664, + aw8624_cont_num_brk_show, aw8624_cont_num_brk_store); +static DEVICE_ATTR(cont_zc_thr, 0664, + aw8624_cont_zc_thr_show, aw8624_cont_zc_thr_store); +static DEVICE_ATTR(vbat_monitor, 0664, + aw8624_vbat_monitor_show, aw8624_vbat_monitor_store); +static DEVICE_ATTR(lra_resistance, 0664, + aw8624_lra_resistance_show, aw8624_lra_resistance_store); +static DEVICE_ATTR(prctmode, 0664, aw8624_prctmode_show, aw8624_prctmode_store); +static DEVICE_ATTR(haptic_audio, 0664, + aw8624_haptic_audio_show, aw8624_haptic_audio_store); +static DEVICE_ATTR(haptic_audio_time, 0664, + aw8624_haptic_audio_time_show, aw8624_haptic_audio_time_store); +static DEVICE_ATTR(ram_vbat_comp, 0664, + aw8624_ram_vbat_comp_show, aw8624_ram_vbat_comp_store); +static DEVICE_ATTR(rtp, 0664, aw8624_rtp_show, aw8624_rtp_store); +static DEVICE_ATTR(osc_cali, 0664, aw8624_osc_cali_show, aw8624_osc_cali_store); +static DEVICE_ATTR(gun_type, 0664, aw8624_gun_type_show, aw8624_gun_type_store); +static DEVICE_ATTR(bullet_nr, 0664, + aw8624_bullet_nr_show, aw8624_bullet_nr_store); +static DEVICE_ATTR(trig, 0664, + aw8624_trig_show, aw8624_trig_store); +static DEVICE_ATTR(ram_num, 0664, aw8624_ram_num_show, NULL); +static struct attribute *aw8624_vibrator_attributes[] = { + &dev_attr_state.attr, + &dev_attr_duration.attr, + &dev_attr_activate.attr, + &dev_attr_activate_mode.attr, + &dev_attr_index.attr, + &dev_attr_gain.attr, + &dev_attr_seq.attr, + &dev_attr_loop.attr, + &dev_attr_register.attr, + &dev_attr_ram_update.attr, + &dev_attr_f0.attr, + &dev_attr_cali.attr, + &dev_attr_cont.attr, + &dev_attr_cont_td.attr, + &dev_attr_cont_drv.attr, + &dev_attr_cont_num_brk.attr, + &dev_attr_cont_zc_thr.attr, + &dev_attr_vbat_monitor.attr, + &dev_attr_lra_resistance.attr, + &dev_attr_prctmode.attr, + &dev_attr_haptic_audio.attr, + &dev_attr_ram_vbat_comp.attr, + &dev_attr_haptic_audio_time.attr, + &dev_attr_rtp.attr, + &dev_attr_osc_cali.attr, + &dev_attr_gun_type.attr, + &dev_attr_bullet_nr.attr, + &dev_attr_trig.attr, + &dev_attr_ram_num.attr, + NULL +}; + +struct attribute_group aw8624_vibrator_attribute_group = { + .attrs = aw8624_vibrator_attributes +}; +int aw8624_vibrator_init(struct aw8624 *aw8624) +{ + int ret = 0; + + aw_dev_info(aw8624->dev, "%s enter\n", __func__); + +#ifdef TIMED_OUTPUT + aw8624->to_dev.name = "awinic_vibrator"; + aw8624->to_dev.get_time = aw8624_vibrator_get_time; + aw8624->to_dev.enable = aw8624_vibrator_enable; + + ret = timed_output_dev_register(&(aw8624->to_dev)); + if (ret < 0) { + dev_err(aw8624->dev, "%s: fail to create timed output dev\n", + __func__); + return ret; + } + ret = sysfs_create_group(&aw8624->to_dev.dev->kobj, + &aw8624_vibrator_attribute_group); + if (ret < 0) { + dev_err(aw8624->dev, + "%s error creating sysfs attr files\n", + __func__); + return ret; + } +#else + aw8624->cdev.name = "awinic_vibrator"; + aw8624->cdev.brightness_set = aw8624_vibrator_enable; + + + ret = devm_led_classdev_register(&aw8624->i2c->dev, &(aw8624->cdev)); + if (ret < 0) { + dev_err(aw8624->dev, "%s: fail to create leds dev\n", + __func__); + return ret; + } + + ret = sysfs_create_group(&aw8624->cdev.dev->kobj, + &aw8624_vibrator_attribute_group); + if (ret < 0) { + dev_err(aw8624->dev, "%s error creating sysfs attr files\n", + __func__); + return ret; + } +#endif + hrtimer_init(&aw8624->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + aw8624->timer.function = aw8624_vibrator_timer_func; + INIT_WORK(&aw8624->vibrator_work, aw8624_vibrator_work_routine); + + if (aw8624->IsUsedIRQ) + INIT_WORK(&aw8624->rtp_work, aw8624_rtp_work_routine); + + mutex_init(&aw8624->lock); + mutex_init(&aw8624->rtp_lock); + + return 0; +} + + +irqreturn_t aw8624_irq(int irq, void *data) +{ + struct aw8624 *aw8624 = data; + unsigned char reg_val = 0; + unsigned char glb_st = 0; + unsigned int buf_len = 0; + + aw8624_i2c_read(aw8624, AW8624_REG_SYSINT, ®_val); + if (reg_val & AW8624_BIT_SYSINT_UVLI) + aw_dev_err(aw8624->dev, "%s chip uvlo int error\n", __func__); + if (reg_val & AW8624_BIT_SYSINT_OCDI) + aw_dev_err(aw8624->dev, "%s chip over current int error\n", + __func__); + if (reg_val & AW8624_BIT_SYSINT_OTI) + aw_dev_err(aw8624->dev, "%s chip over temperature int error\n", + __func__); + if (reg_val & AW8624_BIT_SYSINT_DONEI) + aw_dev_info(aw8624->dev, "%s chip playback done\n", __func__); + + + if (reg_val & AW8624_BIT_SYSINT_UVLI) { + aw8624_i2c_read(aw8624, AW8624_REG_GLB_STATE, &glb_st); + if (glb_st == 0) { + aw8624_i2c_write_bits(aw8624, + AW8624_REG_SYSINTM, + AW8624_BIT_SYSINTM_UVLO_MASK, + AW8624_BIT_SYSINTM_UVLO_OFF); + } + } + + if (reg_val & AW8624_BIT_SYSINT_FF_AEI) { + aw_dev_info(aw8624->dev, "%s: aw8624 rtp fifo almost empty\n", + __func__); + if (aw8624->rtp_init) { + while ((!aw8624_haptic_rtp_get_fifo_afs(aw8624)) && + (aw8624->play_mode == AW8624_HAPTIC_RTP_MODE)) { + mutex_lock(&aw8624->rtp_lock); + if (!aw8624->rtp_cnt) { + aw_dev_info(aw8624->dev, "%s:aw8624->rtp_cnt is 0!\n", + __func__); + mutex_unlock(&aw8624->rtp_lock); + break; + } +#ifdef AW_ENABLE_RTP_PRINT_LOG + aw_dev_info(aw8624->dev, + "%s: aw8624 rtp mode fifo update, cnt=%d\n", + __func__, aw8624->rtp_cnt); +#endif + if (!aw8624->rtp_container) { + aw_dev_info(aw8624->dev, + "%s:aw8624->rtp_container is null, break!\n", + __func__); + mutex_unlock(&aw8624->rtp_lock); + break; + } + if ((aw8624->rtp_container->len-aw8624->rtp_cnt) < + (aw8624->ram.base_addr>>3)) { + buf_len = + aw8624->rtp_container->len-aw8624->rtp_cnt; + } else { + buf_len = (aw8624->ram.base_addr>>3); + } + aw8624_i2c_writes(aw8624, + AW8624_REG_RTP_DATA, + &aw8624->rtp_container->data[aw8624->rtp_cnt], + buf_len); + aw8624->rtp_cnt += buf_len; + aw8624_i2c_read(aw8624, AW8624_REG_GLB_STATE, + &glb_st); + if ((aw8624->rtp_cnt == aw8624->rtp_container->len) || + ((glb_st & 0x0f) == 0x00)) { + if (aw8624->rtp_cnt == + aw8624->rtp_container->len) + aw_dev_info(aw8624->dev, + "%s: rtp load completely! glb_st=%02x aw8624->rtp_cnt=%d\n", + __func__, glb_st, + aw8624->rtp_cnt); + else + aw_dev_err(aw8624->dev, + "%s rtp load failed!! glb_st=%02x aw8624->rtp_cnt=%d\n", + __func__, glb_st, + aw8624->rtp_cnt); + aw8624_haptic_set_rtp_aei(aw8624, + false); + aw8624->rtp_cnt = 0; + aw8624->rtp_init = 0; + mutex_unlock(&aw8624->rtp_lock); + break; + } + mutex_unlock(&aw8624->rtp_lock); + } + } else { + aw_dev_err(aw8624->dev, + "%s: aw8624 rtp init = %d, init error\n", + __func__, aw8624->rtp_init); + } + } + + if (reg_val & AW8624_BIT_SYSINT_FF_AFI) + aw_dev_info(aw8624->dev, + "%s: aw8624 rtp mode fifo full\n", __func__); + + if (aw8624->play_mode != AW8624_HAPTIC_RTP_MODE) + aw8624_haptic_set_rtp_aei(aw8624, false); + + /*aw8624_i2c_read(aw8624, AW8624_REG_SYSINT, ®_val);*/ + /*aw8624_i2c_read(aw8624, AW8624_REG_SYSST, ®_val);*/ + + return IRQ_HANDLED; +} + +static int aw8624_analyse_duration_range(struct aw8624 *aw8624) +{ + int i = 0; + int ret = 0; + int len = 0; + int *duration_time = NULL; + + len = ARRAY_SIZE(aw8624_dts_data.aw8624_duration_time); + duration_time = aw8624_dts_data.aw8624_duration_time; + if (len < 2) { + aw_dev_err(aw8624->dev, "%s: duration time range error\n", + __func__); + return -ERANGE; + } + for (i = (len - 1); i > 0; i--) { + if (duration_time[i] > duration_time[i-1]) + continue; + else + break; + + } + if (i > 0) { + aw_dev_err(aw8624->dev, "%s: duration time range error\n", + __func__); + ret = -ERANGE; + } + return ret; +} + +static int +aw8624_analyse_duration_array_size(struct aw8624 *aw8624, struct device_node *np) +{ + int ret = 0; + + ret = of_property_count_elems_of_size(np, + "aw8624_vib_duration_time", 4); + if (ret < 0) { + aw8624->duration_time_flag = -1; + aw_dev_info(aw8624->dev, + "%s vib_duration_time not found\n", __func__); + return ret; + } + aw8624->duration_time_size = ret; + if (aw8624->duration_time_size > 3) { + aw8624->duration_time_flag = -1; + aw_dev_info(aw8624->dev, + "%s vib_duration_time error, array size = %d\n", + __func__, aw8624->duration_time_size); + return -ERANGE; + } + return 0; +} + +int aw8624_parse_dt(struct aw8624 *aw8624, struct device *dev, + struct device_node *np) { + unsigned int val = 0; + /*unsigned int brake_ram_config[24];*/ + unsigned int brake_cont_config[24]; + unsigned int f0_trace_parameter[4]; + unsigned int bemf_config[4]; + unsigned int duration_time[3]; + unsigned int sw_brake[2]; + unsigned int trig_config_temp[5]; + int ret = 0; + + val = + of_property_read_u32(np, "aw8624_vib_mode", + &aw8624_dts_data.aw8624_mode); + if (val != 0) + aw_dev_info(aw8624->dev, "aw8624_vib_mode not found\n"); + val = + of_property_read_u32(np, "aw8624_vib_f0_pre", + &aw8624_dts_data.aw8624_f0_pre); + if (val != 0) + aw_dev_info(aw8624->dev, "aw8624_vib_f0_pre not found\n"); + val = + of_property_read_u32(np, "aw8624_vib_f0_cali_percen", + &aw8624_dts_data.aw8624_f0_cali_percen); + if (val != 0) + aw_dev_info(aw8624->dev, + "aw8624_vib_f0_cali_percen not found\n"); + val = + of_property_read_u32(np, "aw8624_vib_cont_drv_lev", + &aw8624_dts_data.aw8624_cont_drv_lvl); + if (val != 0) + aw_dev_info(aw8624->dev, + "aw8624_vib_cont_drv_lev not found\n"); + val = + of_property_read_u32(np, "aw8624_vib_cont_drv_lvl_ov", + &aw8624_dts_data.aw8624_cont_drv_lvl_ov); + if (val != 0) + aw_dev_info(aw8624->dev, + "aw8624_vib_cont_drv_lvl_ov not found\n"); + val = + of_property_read_u32(np, "aw8624_vib_cont_td", + &aw8624_dts_data.aw8624_cont_td); + if (val != 0) + aw_dev_info(aw8624->dev, "aw8624_vib_cont_td not found\n"); + val = + of_property_read_u32(np, "aw8624_vib_cont_zc_thr", + &aw8624_dts_data.aw8624_cont_zc_thr); + if (val != 0) + aw_dev_info(aw8624->dev, "aw8624_vib_cont_zc_thr not found\n"); + val = + of_property_read_u32(np, "aw8624_vib_cont_num_brk", + &aw8624_dts_data.aw8624_cont_num_brk); + if (val != 0) + aw_dev_info(aw8624->dev, + "aw8624_vib_cont_num_brk not found\n"); + val = + of_property_read_u32(np, "aw8624_vib_f0_coeff", + &aw8624_dts_data.aw8624_f0_coeff); + if (val != 0) + aw_dev_info(aw8624->dev, + "aw8624_vib_f0_coeff not found\n"); + val = of_property_read_u32_array(np, "aw8624_vib_brake_cont_config", + brake_cont_config, ARRAY_SIZE(brake_cont_config)); + if (val != 0) + aw_dev_info(aw8624->dev, + "%s vib_brake_cont_config not found\n", __func__); + memcpy(aw8624_dts_data.aw8624_cont_brake, + brake_cont_config, sizeof(brake_cont_config)); + + val = of_property_read_u32_array(np, "aw8624_vib_f0_trace_parameter", + f0_trace_parameter, ARRAY_SIZE(f0_trace_parameter)); + if (val != 0) + aw_dev_info(aw8624->dev, + "%s vib_f0_trace_parameter not found\n", __func__); + memcpy(aw8624_dts_data.aw8624_f0_trace_parameter, + f0_trace_parameter, sizeof(f0_trace_parameter)); + + val = of_property_read_u32_array(np, "aw8624_vib_bemf_config", + bemf_config, ARRAY_SIZE(bemf_config)); + if (val != 0) + aw_dev_info(aw8624->dev, + "%s vib_bemf_config not found\n", __func__); + memcpy(aw8624_dts_data.aw8624_bemf_config, + bemf_config, sizeof(bemf_config)); + + val = + of_property_read_u32_array(np, "aw8624_vib_sw_brake", + sw_brake, ARRAY_SIZE(sw_brake)); + if (val != 0) + aw_dev_info(aw8624->dev, + "%s vib_wavseq not found\n", __func__); + memcpy(aw8624_dts_data.aw8624_sw_brake, + sw_brake, sizeof(sw_brake)); + + val = of_property_read_u32(np, "aw8624_vib_tset", + &aw8624_dts_data.aw8624_tset); + if (val != 0) + aw_dev_info(aw8624->dev, "%s vib_tset not found\n", __func__); + val = of_property_read_u32_array(np, "aw8624_vib_duration_time", + duration_time, ARRAY_SIZE(duration_time)); + if (val != 0) + aw_dev_info(aw8624->dev, + "%s vib_duration_time not found\n", __func__); + ret = aw8624_analyse_duration_array_size(aw8624, np); + if (!ret) + memcpy(aw8624_dts_data.aw8624_duration_time, + duration_time, sizeof(duration_time)); + val = + of_property_read_u32_array(np, + "aw8624_vib_trig_config", + trig_config_temp, + ARRAY_SIZE(trig_config_temp)); + if (val != 0) + aw_dev_info(aw8624->dev, "%s vib_trig_config not found\n", + __func__); + memcpy(aw8624_dts_data.trig_config, trig_config_temp, + sizeof(trig_config_temp)); + return 0; +} diff --git a/drivers/misc/aw862xx_haptic/aw8624.h b/drivers/misc/aw862xx_haptic/aw8624.h new file mode 100644 index 000000000000..a2a47c74f02a --- /dev/null +++ b/drivers/misc/aw862xx_haptic/aw8624.h @@ -0,0 +1,345 @@ +#ifndef _AW8624_H_ +#define _AW8624_H_ + +/********************************************************* +* +* kernel version +* +********************************************************/ +#if LINUX_VERSION_CODE <= KERNEL_VERSION(4, 4, 1) +#define TIMED_OUTPUT +#endif + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(4, 14, 1) +#define KERNEL_VERSION_414 +#endif +#if LINUX_VERSION_CODE <= KERNEL_VERSION(4, 9, 1) +#define KERNEL_VERSION_49 +#endif + +/********************************************************* +* +* aw8624.h +* + ********************************************************/ +#include +#include +#include +#include +#include +#include +#include "haptic.h" +#ifdef TIMED_OUTPUT +#include <../../../drivers/staging/android/timed_output.h> +#else +#include +#endif + +/********************************************************* +* +* marco +* +********************************************************/ + +#define AW8624_I2C_RETRIES (2) +#define AW8624_RTP_NAME_MAX (64) +#define AW8624_SEQUENCER_SIZE (8) +#define AW8624_SEQUENCER_LOOP_SIZE (4) +#define AW8624_OSC_CALIBRATION_T_LENGTH (5100000) +#define AW8624_PM_QOS_VALUE_VB (400) +#define AW8624_VBAT_REFER (4200) +#define AW8624_VBAT_MIN (3000) +#define AW8624_VBAT_MAX (4500) +#define AW8624_HAPTIC_NAME ("aw8624_haptic") + + +/* +* aw8624 dts info +* +* aw8624_brake_local[3][8] +* [0][0-7] is en_brake1,en_brake2,brake0_level, +* brake1_level,brake2_level,brake0_p_num,brake1_p_num,brake0_level; +*/ + + + +enum aw8624_flags { + AW8624_FLAG_NONR = 0, + AW8624_FLAG_SKIP_INTERRUPTS = 1, +}; + +enum aw8624_chipids { + AW8624_ID = 1, +}; + +enum aw8624_haptic_read_write { + AW8624_HAPTIC_CMD_READ_REG = 0, + AW8624_HAPTIC_CMD_WRITE_REG = 1, +}; + +enum aw8624_haptic_play { + AW8624_HAPTIC_PLAY_NULL = 0, + AW8624_HAPTIC_PLAY_ENABLE = 1, + AW8624_HAPTIC_PLAY_STOP = 2, + AW8624_HAPTIC_PLAY_GAIN = 8, +}; + +enum aw8624_haptic_cmd { + AW8624_HAPTIC_CMD_NULL = 0, + AW8624_HAPTIC_CMD_ENABLE = 1, + AW8624_HAPTIC_CMD_HAPTIC = 0x0f, + AW8624_HAPTIC_CMD_TP = 0x10, + AW8624_HAPTIC_CMD_SYS = 0xf0, + AW8624_HAPTIC_CMD_STOP = 255, +}; +enum aw8624_haptic_work_mode { + AW8624_HAPTIC_STANDBY_MODE = 0, + AW8624_HAPTIC_RAM_MODE = 1, + AW8624_HAPTIC_RTP_MODE = 2, + AW8624_HAPTIC_TRIG_MODE = 3, + AW8624_HAPTIC_CONT_MODE = 4, + AW8624_HAPTIC_RAM_LOOP_MODE = 5, +}; + +enum aw8624_haptic_bst_mode { + AW8624_HAPTIC_BYPASS_MODE = 0, + AW8624_HAPTIC_BOOST_MODE = 1, +}; + +enum aw8624_haptic_activate_mode { + AW8624_HAPTIC_ACTIVATE_RAM_MODE = 0, + AW8624_HAPTIC_ACTIVATE_CONT_MODE = 1, +}; + +enum aw8624_haptic_vbat_comp_mode { + AW8624_HAPTIC_VBAT_SW_COMP_MODE = 0, + AW8624_HAPTIC_VBAT_HW_COMP_MODE = 1, +}; + +enum aw8624_haptic_ram_vbat_comp_mode { + AW8624_HAPTIC_RAM_VBAT_COMP_DISABLE = 0, + AW8624_HAPTIC_RAM_VBAT_COMP_ENABLE = 1, +}; + +enum aw8624_haptic_f0_flag { + AW8624_HAPTIC_LRA_F0 = 0, + AW8624_HAPTIC_CALI_F0 = 1, +}; + +enum aw8624_haptic_pwm_mode { + AW8624_PWM_48K = 0, + AW8624_PWM_24K = 1, + AW8624_PWM_12K = 2, +}; + +enum aw8624_haptic_cali_lra { + AW8624_HAPTIC_ZERO = 0, + AW8624_HAPTIC_F0_CALI_LRA = 1, + AW8624_HAPTIC_RTP_CALI_LRA = 2, +}; + +enum aw8624_haptic_pin { + TRIG1 = 0, + IRQ = 1, +}; + +/********************************************************* +* +* struct +* +********************************************************/ +struct fileops { + unsigned char cmd; + unsigned char reg; + unsigned char ram_addrh; + unsigned char ram_addrl; +}; + +struct aw8624_trig{ + unsigned char trig_enable; + unsigned char trig_edge; + unsigned char trig_polar; + unsigned char pos_sequence; + unsigned char neg_sequence; +}; + +struct aw8624_dts_info { + int aw8624_mode; + int aw8624_f0_pre; + int aw8624_f0_cali_percen; + int aw8624_cont_drv_lvl; + int aw8624_cont_drv_lvl_ov; + int aw8624_cont_td; + int aw8624_cont_zc_thr; + int aw8624_cont_num_brk; + int aw8624_f0_coeff; + int aw8624_duration_time[3]; + int aw8624_cont_brake[3][8]; + int aw8624_bemf_config[4]; + int aw8624_sw_brake[2]; + int aw8624_wavseq[16]; + int aw8624_wavloop[10]; + int aw8624_td_brake[3]; + int aw8624_tset; + unsigned int aw8624_f0_trace_parameter[4]; + unsigned int trig_config[5]; +}; + +struct aw8624 { + struct regmap *regmap; + struct i2c_client *i2c; + struct device *dev; + struct input_dev *input; + struct aw8624_container *rtp_container; + + struct mutex lock; + struct mutex rtp_lock; +#ifdef CONFIG_PM_WAKELOCKS + struct wakeup_source wk_lock; +#else + struct wake_lock wk_lock; +#endif + unsigned char wk_lock_flag; + struct hrtimer timer; + struct work_struct vibrator_work; + struct work_struct irq_work; + struct work_struct rtp_work; + struct delayed_work ram_work; + struct delayed_work stop_work; +#ifdef TIMED_OUTPUT + struct timed_output_dev to_dev; +#else + struct led_classdev cdev; +#endif + struct fileops fileops; + struct ram ram; + struct aw8624_trig trig; + bool haptic_ready; + bool audio_ready; + int pre_haptic_number; + + unsigned int timeval_flags; + unsigned int osc_cali_flag; + unsigned long int microsecond; + unsigned int theory_time; + unsigned int rtp_len; + int reset_gpio; + int irq_gpio; + int reset_gpio_ret; + int irq_gpio_ret; +#ifdef KERNEL_VERSION_49 + struct timeval current_time; + struct timeval pre_enter_time; + struct timeval start, end; +#else + ktime_t current_time; + ktime_t pre_enter_time; + ktime_t kstart, kend; +#endif + + unsigned char hwen_flag; + unsigned char flags; + unsigned char chipid; + unsigned char chipid_flag; + unsigned char singlecycle; + unsigned char play_mode; + unsigned char activate_mode; + unsigned char auto_boost; + unsigned char duration_time_size; + + char duration_time_flag; + + int state; + int duration; + int amplitude; + int index; + int vmax; + int gain; + int f0_value; + + unsigned char seq[AW8624_SEQUENCER_SIZE]; + unsigned char loop[AW8624_SEQUENCER_SIZE]; + + unsigned int gun_type; + unsigned int bullet_nr; + unsigned int rtp_cnt; + unsigned int rtp_file_num; + + unsigned char rtp_init; + unsigned char ram_init; + unsigned char rtp_routine_on; + + unsigned int f0; + unsigned int f0_pre; + unsigned int cont_td; + unsigned int cont_f0; + unsigned int cont_zc_thr; + unsigned char cont_drv_lvl; + unsigned char cont_drv_lvl_ov; + unsigned char cont_num_brk; + unsigned char max_pos_beme; + unsigned char max_neg_beme; + unsigned char f0_cali_flag; + bool IsUsedIRQ; + bool ram_update_delay; + struct haptic_audio haptic_audio; + + unsigned char ram_vbat_comp; + unsigned int vbat; + unsigned int lra; + unsigned int interval_us; + unsigned int ramupdate_flag; + unsigned int rtpupdate_flag; + unsigned int osc_cali_run; + unsigned int lra_calib_data; + unsigned int f0_calib_data; +}; + +struct aw8624_container { + int len; + unsigned char data[]; +}; + +/********************************************************* +* +* ioctl +* +********************************************************/ +struct aw8624_seq_loop { + unsigned char loop[AW8624_SEQUENCER_SIZE]; +}; + +struct aw8624_que_seq { + unsigned char index[AW8624_SEQUENCER_SIZE]; +}; + +#define AW8624_HAPTIC_IOCTL_MAGIC 'h' +#define AW8624_HAPTIC_SET_QUE_SEQ \ + _IOWR(AW8624_HAPTIC_IOCTL_MAGIC, 1, struct aw8624_que_seq*) +#define AW8624_HAPTIC_SET_SEQ_LOOP \ + _IOWR(AW8624_HAPTIC_IOCTL_MAGIC, 2, struct aw8624_seq_loop*) +#define AW8624_HAPTIC_PLAY_QUE_SEQ \ + _IOWR(AW8624_HAPTIC_IOCTL_MAGIC, 3, unsigned int) +#define AW8624_HAPTIC_SET_BST_VOL \ + _IOWR(AW8624_HAPTIC_IOCTL_MAGIC, 4, unsigned int) +#define AW8624_HAPTIC_SET_BST_PEAK_CUR \ + _IOWR(AW8624_HAPTIC_IOCTL_MAGIC, 5, unsigned int) +#define AW8624_HAPTIC_SET_GAIN \ + _IOWR(AW8624_HAPTIC_IOCTL_MAGIC, 6, unsigned int) +#define AW8624_HAPTIC_PLAY_REPEAT_SEQ \ + _IOWR(AW8624_HAPTIC_IOCTL_MAGIC, 7, unsigned int) + +extern int aw8624_parse_dt(struct aw8624 *aw8624, struct device *dev, + struct device_node *np); +extern void aw8624_interrupt_setup(struct aw8624 *aw8624); +extern int aw8624_vibrator_init(struct aw8624 *aw8624); +extern int aw8624_haptic_init(struct aw8624 *aw8624); +extern int aw8624_ram_init(struct aw8624 *aw8624); +extern struct attribute_group aw8624_vibrator_attribute_group; +extern struct miscdevice aw8624_haptic_misc; +extern irqreturn_t aw8624_irq(int irq, void *data); + +#endif + + + diff --git a/drivers/misc/aw862xx_haptic/aw8624_reg.h b/drivers/misc/aw862xx_haptic/aw8624_reg.h new file mode 100644 index 000000000000..5fffa0316139 --- /dev/null +++ b/drivers/misc/aw862xx_haptic/aw8624_reg.h @@ -0,0 +1,558 @@ +#ifndef _AW8624_REG_H_ +#define _AW8624_REG_H_ + + +/******************************************** + * Register List + *******************************************/ +#define AW8624_REG_ID (0x00) +#define AW8624_REG_SYSST (0x01) +#define AW8624_REG_SYSINT (0x02) +#define AW8624_REG_SYSINTM (0x03) +#define AW8624_REG_SYSCTRL (0x04) +#define AW8624_REG_GO (0x05) +#define AW8624_REG_RTP_DATA (0x06) +#define AW8624_REG_WAVSEQ1 (0x07) +#define AW8624_REG_WAVSEQ2 (0x08) +#define AW8624_REG_WAVSEQ3 (0x09) +#define AW8624_REG_WAVSEQ4 (0x0a) +#define AW8624_REG_WAVSEQ5 (0x0b) +#define AW8624_REG_WAVSEQ6 (0x0c) +#define AW8624_REG_WAVSEQ7 (0x0d) +#define AW8624_REG_WAVSEQ8 (0x0e) +#define AW8624_REG_WAVLOOP1 (0x0f) +#define AW8624_REG_WAVLOOP2 (0x10) +#define AW8624_REG_WAVLOOP3 (0x11) +#define AW8624_REG_WAVLOOP4 (0x12) +#define AW8624_REG_MAIN_LOOP (0x13) +#define AW8624_REG_TRG1_SEQP (0x14) +#define AW8624_REG_TRG1_SEQN (0x17) +#define AW8624_REG_PLAY_PRIO (0x1a) +#define AW8624_REG_TRG_CFG1 (0x1b) +#define AW8624_REG_TRG_CFG2 (0x1c) +#define AW8624_REG_DBGCTRL (0x20) +#define AW8624_REG_BASE_ADDRH (0x21) +#define AW8624_REG_BASE_ADDRL (0x22) +#define AW8624_REG_FIFO_AEH (0x23) +#define AW8624_REG_FIFO_AEL (0x24) +#define AW8624_REG_FIFO_AFH (0x25) +#define AW8624_REG_FIFO_AFL (0x26) +#define AW8624_REG_WAKE_DLY (0x27) +#define AW8624_REG_START_DLY (0x28) +#define AW8624_REG_END_DLY_H (0x29) +#define AW8624_REG_END_DLY_L (0x2a) +#define AW8624_REG_DATCTRL (0x2b) +#define AW8624_REG_PWMDEL (0x2c) +#define AW8624_REG_PWMPRC (0x2d) +#define AW8624_REG_PWMDBG (0x2e) +#define AW8624_REG_LDOCTRL (0x2f) +#define AW8624_REG_DBGSTAT (0x30) +#define AW8624_REG_WAVECTRL (0x31) +#define AW8624_REG_BRAKE0_CTRL (0x32) +#define AW8624_REG_BRAKE1_CTRL (0x33) +#define AW8624_REG_BRAKE2_CTRL (0x34) +#define AW8624_REG_BRAKE_NUM (0x35) +#define AW8624_REG_ANADBG1 (0x36) +#define AW8624_REG_ANADBG2 (0x37) +#define AW8624_REG_ANACTRL (0x38) +#define AW8624_REG_SW_BRAKE (0x39) +#define AW8624_REG_GLBDBG (0x3a) +#define AW8624_REG_DATDBG (0x3b) +#define AW8624_REG_WDCTRL (0x3c) +#define AW8624_REG_HDRVDBG (0x3d) +#define AW8624_REG_PRLVL (0x3e) +#define AW8624_REG_PRTIME (0x3f) +#define AW8624_REG_RAMADDRH (0x40) +#define AW8624_REG_RAMADDRL (0x41) +#define AW8624_REG_RAMDATA (0x42) +#define AW8624_REG_TM (0x43) +#define AW8624_REG_BRA_MAX_NUM (0x44) +#define AW8624_REG_BEMF_ERM_FAC (0x45) +#define AW8624_REG_BEMF_BRA_FAC (0x46) +#define AW8624_REG_GLB_STATE (0x47) +#define AW8624_REG_CONT_CTRL (0x48) +#define AW8624_REG_F_PRE_H (0x49) +#define AW8624_REG_F_PRE_L (0x4a) +#define AW8624_REG_TD_H (0x4b) +#define AW8624_REG_TD_L (0x4c) +#define AW8624_REG_TSET (0x4d) +#define AW8624_REG_THRS_BRA_RAP (0x4e) +#define AW8624_REG_THRS_BRA_END (0x4f) +#define AW8624_REG_EF_CTRL (0x50) +#define AW8624_REG_EF_WDATAH (0x53) +#define AW8624_REG_EF_WDATAL (0x54) +#define AW8624_REG_EF_RDATAH (0x55) +#define AW8624_REG_EF_RDATAL (0x56) +#define AW8624_REG_DLY (0x57) +#define AW8624_REG_EF_WR_WIDTH (0x58) +#define AW8624_REG_EF_RD_WIDTH (0x59) +#define AW8624_REG_TRIM_LRA (0x5b) +#define AW8624_REG_TRIM_OSC (0x5c) +#define AW8624_REG_R_SPARE (0x5d) +#define AW8624_REG_D2SCFG (0x5e) +#define AW8624_REG_DETCTRL (0x5f) +#define AW8624_REG_RLDET (0x60) +#define AW8624_REG_OSDET (0x61) +#define AW8624_REG_VBATDET (0x62) +#define AW8624_REG_TESTDET (0x63) +#define AW8624_REG_DETLO (0x64) +#define AW8624_REG_BEMFDBG (0x65) +#define AW8624_REG_ADCTEST (0x66) +#define AW8624_REG_BEMFTEST (0x67) +#define AW8624_REG_F_LRA_F0_H (0x68) +#define AW8624_REG_F_LRA_F0_L (0x69) +#define AW8624_REG_F_LRA_CONT_H (0x6a) +#define AW8624_REG_F_LRA_CONT_L (0x6b) +#define AW8624_REG_WAIT_VOL_MP (0x6e) +#define AW8624_REG_WAIT_VOL_MN (0x6f) +#define AW8624_REG_BEMF_VOL_H (0x70) +#define AW8624_REG_BEMF_VOL_L (0x71) +#define AW8624_REG_ZC_THRSH_H (0x72) +#define AW8624_REG_ZC_THRSH_L (0x73) +#define AW8624_REG_BEMF_VTHH_H (0x74) +#define AW8624_REG_BEMF_VTHH_L (0x75) +#define AW8624_REG_BEMF_VTHL_H (0x76) +#define AW8624_REG_BEMF_VTHL_L (0x77) +#define AW8624_REG_BEMF_NUM (0x78) +#define AW8624_REG_DRV_TIME (0x79) +#define AW8624_REG_TIME_NZC (0x7a) +#define AW8624_REG_DRV_LVL (0x7b) +#define AW8624_REG_DRV_LVL_OV (0x7c) +#define AW8624_REG_NUM_F0_1 (0x7d) +#define AW8624_REG_NUM_F0_2 (0x7e) +#define AW8624_REG_NUM_F0_3 (0x7f) + + + +/******************************************** + * Register Access + *******************************************/ +#define REG_NONE_ACCESS (0) +#define REG_RD_ACCESS (1 << 0) +#define REG_WR_ACCESS (1 << 1) +#define AW8624_REG_MAX (0xff) + +const unsigned char aw8624_reg_access[AW8624_REG_MAX] = { + [AW8624_REG_ID] = REG_RD_ACCESS, + [AW8624_REG_SYSST] = REG_RD_ACCESS, + [AW8624_REG_SYSINT] = REG_RD_ACCESS, + [AW8624_REG_SYSINTM] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_SYSCTRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_GO] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_RTP_DATA] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_WAVSEQ1] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_WAVSEQ2] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_WAVSEQ3] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_WAVSEQ4] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_WAVSEQ5] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_WAVSEQ6] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_WAVSEQ7] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_WAVSEQ8] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_WAVLOOP1] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_WAVLOOP2] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_WAVLOOP3] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_WAVLOOP4] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_MAIN_LOOP] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_TRG1_SEQP] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_TRG1_SEQN] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_PLAY_PRIO] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_TRG_CFG1] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_TRG_CFG2] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_DBGCTRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BASE_ADDRH] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BASE_ADDRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_FIFO_AEH] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_FIFO_AEL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_FIFO_AFH] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_FIFO_AFL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_WAKE_DLY] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_START_DLY] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_END_DLY_H] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_END_DLY_L] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_DATCTRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_PWMDEL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_PWMPRC] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_PWMDBG] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_LDOCTRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_DBGSTAT] = REG_RD_ACCESS, + [AW8624_REG_WAVECTRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BRAKE0_CTRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BRAKE1_CTRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BRAKE2_CTRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BRAKE_NUM] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_ANADBG1] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_ANADBG2] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_ANACTRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_SW_BRAKE] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_GLBDBG] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_DATDBG] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_WDCTRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_HDRVDBG] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_PRLVL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_PRTIME] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_RAMADDRH] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_RAMADDRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_RAMDATA] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_TM] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BRA_MAX_NUM] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BEMF_ERM_FAC] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BEMF_BRA_FAC] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_GLB_STATE] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_CONT_CTRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_F_PRE_H] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_F_PRE_L] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_TD_H] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_TD_L] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_TSET] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_THRS_BRA_RAP] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_THRS_BRA_END] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_EF_CTRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_EF_WDATAH] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_EF_WDATAL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_EF_RDATAH] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_EF_RDATAL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_DLY] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_EF_WR_WIDTH] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_EF_RD_WIDTH] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_TRIM_LRA] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_TRIM_OSC] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_R_SPARE] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_D2SCFG] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_DETCTRL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_RLDET] = REG_RD_ACCESS, + [AW8624_REG_OSDET] = REG_RD_ACCESS, + [AW8624_REG_VBATDET] = REG_RD_ACCESS, + [AW8624_REG_TESTDET] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_DETLO] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BEMFDBG] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_ADCTEST] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BEMFTEST] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_F_LRA_F0_H] = REG_RD_ACCESS, + [AW8624_REG_F_LRA_F0_L] = REG_RD_ACCESS, + [AW8624_REG_F_LRA_CONT_H] = REG_RD_ACCESS, + [AW8624_REG_F_LRA_CONT_L] = REG_RD_ACCESS, + [AW8624_REG_WAIT_VOL_MP] = REG_RD_ACCESS, + [AW8624_REG_WAIT_VOL_MN] = REG_RD_ACCESS, + [AW8624_REG_BEMF_VOL_H] = REG_RD_ACCESS, + [AW8624_REG_BEMF_VOL_L] = REG_RD_ACCESS, + [AW8624_REG_ZC_THRSH_H] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_ZC_THRSH_L] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BEMF_VTHH_H] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BEMF_VTHH_L] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BEMF_VTHL_H] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BEMF_VTHL_L] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_BEMF_NUM] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_DRV_TIME] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_TIME_NZC] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_DRV_LVL] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_DRV_LVL_OV] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_NUM_F0_1] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_NUM_F0_2] = REG_RD_ACCESS|REG_WR_ACCESS, + [AW8624_REG_NUM_F0_3] = REG_RD_ACCESS|REG_WR_ACCESS, +}; + + + + +/****************************************************** + * Register Detail + *****************************************************/ + /* SYSST 0x01 */ +#define AW8624_BIT_SYSST_OVS (1<<6) +#define AW8624_BIT_SYSST_UVLS (1<<5) +#define AW8624_BIT_SYSST_FF_AES (1<<4) +#define AW8624_BIT_SYSST_FF_AFS (1<<3) +#define AW8624_BIT_SYSST_OCDS (1<<2) +#define AW8624_BIT_SYSST_OTS (1<<1) +#define AW8624_BIT_SYSST_DONES (1<<0) + + /* SYSINT 0x02 */ +#define AW8624_BIT_SYSINT_OVI (1<<6) +#define AW8624_BIT_SYSINT_UVLI (1<<5) +#define AW8624_BIT_SYSINT_FF_AEI (1<<4) +#define AW8624_BIT_SYSINT_FF_AFI (1<<3) +#define AW8624_BIT_SYSINT_OCDI (1<<2) +#define AW8624_BIT_SYSINT_OTI (1<<1) +#define AW8624_BIT_SYSINT_DONEI (1<<0) + + /* SYSINTM 0x03 */ +#define AW8624_BIT_SYSINTM_OV_MASK (~(1<<6)) +#define AW8624_BIT_SYSINTM_OV_OFF (1<<6) +#define AW8624_BIT_SYSINTM_OV_EN (0<<6) +#define AW8624_BIT_SYSINTM_UVLO_MASK (~(1<<5)) +#define AW8624_BIT_SYSINTM_UVLO_OFF (1<<5) +#define AW8624_BIT_SYSINTM_UVLO_EN (0<<5) +#define AW8624_BIT_SYSINTM_FF_AE_MASK (~(1<<4)) +#define AW8624_BIT_SYSINTM_FF_AE_OFF (1<<4) +#define AW8624_BIT_SYSINTM_FF_AE_EN (0<<4) +#define AW8624_BIT_SYSINTM_FF_AF_MASK (~(1<<3)) +#define AW8624_BIT_SYSINTM_FF_AF_OFF (1<<3) +#define AW8624_BIT_SYSINTM_FF_AF_EN (0<<3) +#define AW8624_BIT_SYSINTM_OCD_MASK (~(1<<2)) +#define AW8624_BIT_SYSINTM_OCD_OFF (1<<2) +#define AW8624_BIT_SYSINTM_OCD_EN (0<<2) +#define AW8624_BIT_SYSINTM_OT_MASK (~(1<<1)) +#define AW8624_BIT_SYSINTM_OT_OFF (1<<1) +#define AW8624_BIT_SYSINTM_OT_EN (0<<1) +#define AW8624_BIT_SYSINTM_DONE_MASK (~(1<<0)) +#define AW8624_BIT_SYSINTM_DONE_OFF (1<<0) +#define AW8624_BIT_SYSINTM_DONE_EN (0<<0) + + /* SYSCTRL 0x04 */ +#define AW8624_BIT_SYSCTRL_WAVDAT_MODE_MASK (~(3<<6)) +#define AW8624_BIT_SYSCTRL_WAVDAT_MODE_4X (3<<6) +#define AW8624_BIT_SYSCTRL_WAVDAT_MODE_2X (0<<6) +#define AW8624_BIT_SYSCTRL_WAVDAT_MODE_1X (1<<6) +#define AW8624_BIT_SYSCTRL_RAMINIT_MASK (~(1<<5)) +#define AW8624_BIT_SYSCTRL_RAMINIT_EN (1<<5) +#define AW8624_BIT_SYSCTRL_RAMINIT_OFF (0<<5) +#define AW8624_BIT_SYSCTRL_PLAY_MODE_MASK (~(3<<2)) +#define AW8624_BIT_SYSCTRL_PLAY_MODE_CONT (2<<2) +#define AW8624_BIT_SYSCTRL_PLAY_MODE_RTP (1<<2) +#define AW8624_BIT_SYSCTRL_PLAY_MODE_RAM (0<<2) +#define AW8624_BIT_SYSCTRL_WORK_MODE_MASK (~(1<<0)) +#define AW8624_BIT_SYSCTRL_STANDBY (1<<0) +#define AW8624_BIT_SYSCTRL_ACTIVE (0<<0) + + /* GO 0x05 */ +#define AW8624_BIT_GO_MASK (~(1<<0)) +#define AW8624_BIT_GO_ENABLE (1<<0) +#define AW8624_BIT_GO_DISABLE (0<<0) + + /* WAVSEQ1 0x07 */ +#define AW8624_BIT_WAVSEQ1_WAIT (1<<7) +#define AW8624_BIT_WAVSEQ1_WAV_FRM_SEQ1_MASK (~(127<<0)) + + /* WAVSEQ2 0x08 */ +#define AW8624_BIT_WAVSEQ2_WAIT (1<<7) +#define AW8624_BIT_WAVSEQ2_WAV_FRM_SEQ2_MASK (~(127<<0)) + + /* WAVSEQ3 0x09 */ +#define AW8624_BIT_WAVSEQ3_WAIT (1<<7) +#define AW8624_BIT_WAVSEQ3_WAV_FRM_SEQ3_MASK (~(127<<0)) + + /* WAVSEQ4 0x0A */ +#define AW8624_BIT_WAVSEQ4_WAIT (1<<7) +#define AW8624_BIT_WAVSEQ4_WAV_FRM_SEQ4_MASK (~(127<<0)) + + /* WAVSEQ5 0X0B */ +#define AW8624_BIT_WAVSEQ5_WAIT (1<<7) +#define AW8624_BIT_WAVSEQ5_WAV_FRM_SEQ5_MASK (~(127<<0)) + + /* WAVSEQ6 0X0C */ +#define AW8624_BIT_WAVSEQ6_WAIT (1<<7) +#define AW8624_BIT_WAVSEQ6_WAV_FRM_SEQ6_MASK (~(127<<0)) + + /* WAVSEQ7 */ +#define AW8624_BIT_WAVSEQ7_WAIT (1<<7) +#define AW8624_BIT_WAVSEQ7_WAV_FRM_SEQ7_MASK (~(127<<0)) + + /* WAVSEQ8 */ +#define AW8624_BIT_WAVSEQ8_WAIT (1<<7) +#define AW8624_BIT_WAVSEQ8_WAV_FRM_SEQ8_MASK (~(127<<0)) + + /* WAVLOOP */ +#define AW8624_BIT_WAVLOOP_SEQN_MASK (~(15<<4)) +#define AW8624_BIT_WAVLOOP_SEQNP1_MASK (~(15<<0)) +#define AW8624_BIT_WAVLOOP_INIFINITELY (15<<0) + + /* WAVLOOP1 */ +#define AW8624_BIT_WAVLOOP1_SEQ1_MASK (~(15<<4)) +#define AW8624_BIT_WAVLOOP1_SEQ2_MASK (~(15<<0)) + + /* WAVLOOP2 */ +#define AW8624_BIT_WAVLOOP2_SEQ3_MASK (~(15<<4)) +#define AW8624_BIT_WAVLOOP2_SEQ4_MASK (~(15<<0)) + + /* WAVLOOP3 */ +#define AW8624_BIT_WAVLOOP3_SEQ5_MASK (~(15<<4)) +#define AW8624_BIT_WAVLOOP3_SEQ6_MASK (~(15<<0)) + + /* WAVLOOP4 */ +#define AW8624_BIT_WAVLOOP4_SEQ7_MASK (~(15<<4)) +#define AW8624_BIT_WAVLOOP4_SEQ8_MASK (~(15<<0)) + + + /* PLAYPRIO */ +#define AW8624_BIT_PLAYPRIO_GO_MASK (~(3<<6)) +#define AW8624_BIT_PLAYPRIO_TRIG3_MASK (~(3<<4)) +#define AW8624_BIT_PLAYPRIO_TRIG2_MASK (~(3<<2)) +#define AW8624_BIT_PLAYPRIO_TRIG1_MASK (~(3<<0)) + + /* TRGCFG1 */ +#define AW8624_BIT_TRGCFG1_TRG3_POLAR_MASK (~(1<<5)) +#define AW8624_BIT_TRGCFG1_TRG3_POLAR_NEG (1<<5) +#define AW8624_BIT_TRGCFG1_TRG3_POLAR_POS (0<<5) +#define AW8624_BIT_TRGCFG1_TRG3_EDGE_MASK (~(1<<4)) +#define AW8624_BIT_TRGCFG1_TRG3_EDGE_POS (1<<4) +#define AW8624_BIT_TRGCFG1_TRG3_EDGE_POS_NEG (0<<4) +#define AW8624_BIT_TRGCFG1_TRG2_POLAR_MASK (~(1<<3)) +#define AW8624_BIT_TRGCFG1_TRG2_POLAR_NEG (1<<3) +#define AW8624_BIT_TRGCFG1_TRG2_POLAR_POS (0<<3) +#define AW8624_BIT_TRGCFG1_TRG2_EDGE_MASK (~(1<<2)) +#define AW8624_BIT_TRGCFG1_TRG2_EDGE_POS (1<<2) +#define AW8624_BIT_TRGCFG1_TRG2_EDGE_POS_NEG (0<<2) +#define AW8624_BIT_TRGCFG1_TRG1_POLAR_MASK (~(1<<1)) +#define AW8624_BIT_TRGCFG1_TRG1_POLAR_NEG (1<<1) +#define AW8624_BIT_TRGCFG1_TRG1_POLAR_POS (0<<1) +#define AW8624_BIT_TRGCFG1_TRG1_EDGE_MASK (~(1<<0)) +#define AW8624_BIT_TRGCFG1_TRG1_EDGE_POS (1<<0) +#define AW8624_BIT_TRGCFG1_TRG1_EDGE_POS_NEG (0<<0) + + /* TRGCFG2 */ +#define AW8624_BIT_TRGCFG2_TRG3_ENABLE_MASK (~(1<<2)) +#define AW8624_BIT_TRGCFG2_TRG3_ENABLE (1<<2) +#define AW8624_BIT_TRGCFG2_TRG3_DISABLE (0<<2) +#define AW8624_BIT_TRGCFG2_TRG2_ENABLE_MASK (~(1<<1)) +#define AW8624_BIT_TRGCFG2_TRG2_ENABLE (1<<1) +#define AW8624_BIT_TRGCFG2_TRG2_DISABLE (0<<1) +#define AW8624_BIT_TRGCFG2_TRG1_ENABLE_MASK (~(1<<0)) +#define AW8624_BIT_TRGCFG2_TRG1_ENABLE (1<<0) +#define AW8624_BIT_TRGCFG2_TRG1_DISABLE (0<<0) + + /*DBGCTRL 0X20 */ +#define AW8624_BIT_DBGCTRL_INTN_TRG_SEL_MASK (~(1<<5)) +#define AW8624_BIT_DBGCTRL_INTN_SEL_ENABLE (1<<5) +#define AW8624_BIT_DBGCTRL_TRG_SEL_ENABLE (0<<5) +#define AW8624_BIT_DBGCTRL_INT_MODE_MASK (~(3<<2)) +#define AW8624_BIT_DBGCTRL_INTN_LEVEL_MODE (0<<2) +#define AW8624_BIT_DBGCTRL_INT_MODE_EDGE (1<<2) +#define AW8624_BIT_DBGCTRL_INTN_POSEDGE_MODE (2<<2) +#define AW8624_BIT_DBGCTRL_INTN_BOTH_EDGE_MODE (3<<2) + + /* DATCTRL */ +#define AW8624_BIT_DATCTRL_FC_MASK (~(1<<6)) +#define AW8624_BIT_DATCTRL_FC_1000HZ (3<<6) +#define AW8624_BIT_DATCTRL_FC_800HZ (3<<6) +#define AW8624_BIT_DATCTRL_FC_600HZ (1<<6) +#define AW8624_BIT_DATCTRL_FC_400HZ (0<<6) +#define AW8624_BIT_DATCTRL_LPF_ENABLE_MASK (~(1<<5)) +#define AW8624_BIT_DATCTRL_LPF_ENABLE (1<<5) +#define AW8624_BIT_DATCTRL_LPF_DISABLE (0<<5) + + /*PWMPRC 0X2D */ +#define AW8624_BIT_PWMPRC_PRC_EN_MASK (~(1<<7)) +#define AW8624_BIT_PWMPRC_PRC_ENABLE (1<<7) +#define AW8624_BIT_PWMPRC_PRC_DISABLE (0<<7) +#define AW8624_BIT_PWMPRC_PRCTIME_MASK (~(0x7f<<0)) + + /* PWMDBG */ +#define AW8624_BIT_PWMDBG_PWM_MODE_MASK (~(3<<5)) +#define AW8624_BIT_PWMDBG_PWM_12K (3<<5) +#define AW8624_BIT_PWMDBG_PWM_24K (2<<5) +#define AW8624_BIT_PWMDBG_PWM_48K (0<<5) + +/* GLB_STATE 0x47*/ +#define AW8624_BIT_GLBRD5_STATE_MASK (~(15<<0)) +#define AW8624_BIT_GLBRD5_STATE_STANDBY (0<<0) +#define AW8624_BIT_GLBRD5_STATE_WAKEUP (1<<0) +#define AW8624_BIT_GLBRD5_STATE_STARTUP (2<<0) +#define AW8624_BIT_GLBRD5_STATE_WAIT (3<<0) +#define AW8624_BIT_GLBRD5_STATE_CONT_GO (6<<0) +#define AW8624_BIT_GLBRD5_STATE_RAM_GO (7<<0) +#define AW8624_BIT_GLBRD5_STATE_RTP_GO (8<<0) +#define AW8624_BIT_GLBRD5_STATE_TRIG_GO (9<<0) +#define AW8624_BIT_GLBRD5_STATE_I2S_GO (10<<0) +#define AW8624_BIT_GLBRD5_STATE_BRAKE (11<<0) +#define AW8624_BIT_GLBRD5_STATE_END (12<<0) + + /* WAVECTRL */ +#define AW8624_BIT_WAVECTRL_NUM_OV_DRIVER_MASK (~(0xF<<4)) +#define AW8624_BIT_WAVECTRL_NUM_OV_DRIVER (0<<4) + + /* BST_AUTO */ +#define AW8624_BIT_BST_AUTO_BST_AUTOSW_MASK (~(1<<2)) +#define AW8624_BIT_BST_AUTO_BST_AUTOMATIC_BOOST (1<<2) +#define AW8624_BIT_BST_AUTO_BST_MANUAL_BOOST (0<<2) + + /* CONT_CTRL */ +#define AW8624_BIT_CONT_CTRL_ZC_DETEC_MASK (~(1<<7)) +#define AW8624_BIT_CONT_CTRL_ZC_DETEC_ENABLE (1<<7) +#define AW8624_BIT_CONT_CTRL_ZC_DETEC_DISABLE (0<<7) +#define AW8624_BIT_CONT_CTRL_WAIT_PERIOD_MASK (~(3<<5)) +#define AW8624_BIT_CONT_CTRL_WAIT_8PERIOD (3<<5) +#define AW8624_BIT_CONT_CTRL_WAIT_4PERIOD (2<<5) +#define AW8624_BIT_CONT_CTRL_WAIT_2PERIOD (1<<5) +#define AW8624_BIT_CONT_CTRL_WAIT_1PERIOD (0<<5) +#define AW8624_BIT_CONT_CTRL_MODE_MASK (~(1<<4)) +#define AW8624_BIT_CONT_CTRL_BY_DRV_TIME (1<<4) +#define AW8624_BIT_CONT_CTRL_BY_GO_SIGNAL (0<<4) +#define AW8624_BIT_CONT_CTRL_EN_CLOSE_MASK (~(1<<3)) +#define AW8624_BIT_CONT_CTRL_CLOSE_PLAYBACK (1<<3) +#define AW8624_BIT_CONT_CTRL_OPEN_PLAYBACK (0<<3) +#define AW8624_BIT_CONT_CTRL_F0_DETECT_MASK (~(1<<2)) +#define AW8624_BIT_CONT_CTRL_F0_DETECT_ENABLE (1<<2) +#define AW8624_BIT_CONT_CTRL_F0_DETECT_DISABLE (0<<2) +#define AW8624_BIT_CONT_CTRL_O2C_MASK (~(1<<1)) +#define AW8624_BIT_CONT_CTRL_O2C_ENABLE (1<<1) +#define AW8624_BIT_CONT_CTRL_O2C_DISABLE (0<<1) +#define AW8624_BIT_CONT_CTRL_AUTO_BRK_MASK (~(1<<0)) +#define AW8624_BIT_CONT_CTRL_AUTO_BRK_ENABLE (1<<0) +#define AW8624_BIT_CONT_CTRL_AUTO_BRK_DISABLE (0<<0) + +#define AW8624_BIT_D2SCFG_CLK_ADC_MASK (~(7<<5)) +#define AW8624_BIT_D2SCFG_CLK_ASC_1P5MHZ (3<<5) + +#define AW8624_BIT_D2SCFG_GAIN_MASK (~(7<<0)) +#define AW8624_BIT_D2SCFG_GAIN_40 (7<<0) + /* DETCTRL */ +#define AW8624_BIT_DETCTRL_RL_OS_MASK (~(1<<6)) +#define AW8624_BIT_DETCTRL_RL_DETECT (1<<6) +#define AW8624_BIT_DETCTRL_OS_DETECT (0<<6) +#define AW8624_BIT_DETCTRL_PROTECT_MASK (~(1<<5)) +#define AW8624_BIT_DETCTRL_PROTECT_NO_ACTION (1<<5) +#define AW8624_BIT_DETCTRL_PROTECT_SHUTDOWN (0<<5) +#define AW8624_BIT_DETCTRL_VBAT_GO_MASK (~(1<<1)) +#define AW8624_BIT_DETCTRL_VABT_GO_ENABLE (1<<1) +#define AW8624_BIT_DETCTRL_VBAT_GO_DISBALE (0<<1) +#define AW8624_BIT_DETCTRL_DIAG_GO_MASK (~(1<<0)) +#define AW8624_BIT_DETCTRL_DIAG_GO_ENABLE (1<<0) +#define AW8624_BIT_DETCTRL_DIAG_GO_DISABLE (0<<0) + + +#define AW8624_BIT_RAMADDRH_MASK (~(63<<0)) + + /* VBAT MODE */ +#define AW8624_BIT_DETCTRL_VBAT_MODE_MASK (~(1<<6)) +#define AW8624_BIT_DETCTRL_VBAT_HW_COMP (1<<6) +#define AW8624_BIT_DETCTRL_VBAT_SW_COMP (0<<6) + + + /* ANACTRL */ +#define AW8624_BIT_ANACTRL_LRA_SRC_MASK (~(1<<5)) +#define AW8624_BIT_ANACTRL_LRA_SRC_REG (1<<5) +#define AW8624_BIT_ANACTRL_LRA_SRC_EFUSE (0<<5) +#define AW8624_BIT_ANACTRL_EN_IO_PD1_MASK (~(1<<0)) +#define AW8624_BIT_ANACTRL_EN_IO_PD1_HIGH (1<<0) +#define AW8624_BIT_ANACTRL_EN_IO_PD1_LOW (0<<0) + +/* SW_BRAKE */ +#define AW8624_BIT_EN_BRAKE_CONT_MASK (~(1<<3)) +#define AW8624_BIT_EN_BRAKE_CONT_ENABLE (1<<3) +#define AW8624_BIT_EN_BRAKE_CONT_DISABLE (0<<3) +#define AW8624_BIT_EN_BRAKE_RAM_MASK (~(1<<2)) +#define AW8624_BIT_EN_BRAKE_RAM_ENABLE (1<<2) +#define AW8624_BIT_EN_BRAKE_RAM_DISABLE (0<<2) +#define AW8624_BIT_EN_BRAKE_RTP_MASK (~(1<<1)) +#define AW8624_BIT_EN_BRAKE_RTP_ENABLE (1<<1) +#define AW8624_BIT_EN_BRAKE_RTP_DISABLE (0<<1) +#define AW8624_BIT_EN_BRAKE_TRIG_MASK (~(1<<0)) +#define AW8624_BIT_EN_BRAKE_TRIG_ENABLE (1<<0) +#define AW8624_BIT_EN_BRAKE_TRIG_DISABLE (0<<0) + +/* PRLVL */ +#define AW8624_BIT_PRLVL_PR_EN_MASK (~(1<<7)) +#define AW8624_BIT_PRLVL_PR_ENABLE (1<<7) +#define AW8624_BIT_PRLVL_PR_DISABLE (0<<7) +#define AW8624_BIT_PRLVL_PRLVL_MASK (~(0x7f<<0)) + +/* PRTIME */ +#define AW8624_BIT_PRTIME_PRTIME_MASK (~(0xff<<0)) + +#define AW8624_BIT_BEMF_NUM_BRK_MASK (~(0xf<<0)) + +/* TD_H 0x4b TD_brake */ +#define AW8624_BIT_TDH_TD_BRAKE_MASK (~(0xF<<4)) +#define AW8624_BIT_R_SPARE_MASK (~(1<<7)) +#define AW8624_BIT_R_SPARE_ENABLE (1<<7) +#endif diff --git a/drivers/misc/aw862xx_haptic/haptic.c b/drivers/misc/aw862xx_haptic/haptic.c new file mode 100644 index 000000000000..98c664c7b286 --- /dev/null +++ b/drivers/misc/aw862xx_haptic/haptic.c @@ -0,0 +1,537 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_PM_WAKELOCKS +#include +#else +#include +#endif +#include +#include "haptic.h" +#include "aw8624.h" +#include "aw8622x.h" +#include +/****************************************************** + * + * Marco + * + ******************************************************/ +#define AWINIC_DRIVER_VERSION ("v1.0.1") +#define AWINIC_I2C_NAME ("awinic_haptic") +#define AW_READ_CHIPID_RETRIES (5) +#define AW_I2C_RETRIES (2) +#define AW8624_CHIP_ID (0x24) +#define AW8622X_CHIP_ID (0x00) +#define AW_REG_ID (0x00) +#define AW8622X_REG_EFRD9 (0x64) + +struct aw8624 *g_aw8624; +struct aw8622x *g_aw8622x; + +static int awinic_i2c_read(struct awinic *awinic, + unsigned char reg_addr, unsigned char *reg_data) +{ + int ret = -1; + unsigned char cnt = 0; + + while (cnt < AW_I2C_RETRIES) { + ret = i2c_smbus_read_byte_data(awinic->i2c, reg_addr); + if (ret < 0) { + aw_dev_err(awinic->dev, "%s: i2c_read cnt=%d error=%d\n", + __func__, cnt, ret); + } else { + *reg_data = ret; + break; + } + cnt++; + usleep_range(2000, 3000); + } + + return ret; +} + +static int awinic_i2c_write(struct awinic *awinic, + unsigned char reg_addr, unsigned char reg_data) +{ + int ret = -1; + unsigned char cnt = 0; + + while (cnt < AW_I2C_RETRIES) { + ret = + i2c_smbus_write_byte_data(awinic->i2c, reg_addr, reg_data); + if (ret < 0) { + aw_dev_err(awinic->dev, "%s: i2c_write cnt=%d error=%d\n", + __func__, cnt, ret); + } else { + break; + } + cnt++; + usleep_range(2000, 3000); + } + + return ret; +} + +static int awinic_hw_reset(struct awinic *awinic) +{ + aw_dev_info(awinic->dev, "%s enter\n", __func__); + + if (awinic && gpio_is_valid(awinic->reset_gpio)) { + gpio_set_value_cansleep(awinic->reset_gpio, 0); + usleep_range(1000, 2000); + gpio_set_value_cansleep(awinic->reset_gpio, 1); + usleep_range(3500, 4000); + } else { + dev_err(awinic->dev, "%s: failed\n", __func__); + } + return 0; +} + +static int awinic_haptic_softreset(struct awinic *awinic) +{ + aw_dev_info(awinic->dev, "%s enter\n", __func__); + awinic_i2c_write(awinic, AW_REG_ID, 0xAA); + usleep_range(2000, 2500); + return 0; +} +static int awinic_read_chipid(struct awinic *awinic) +{ + int ret = -1; + unsigned char cnt = 0; + unsigned char reg = 0; + unsigned char ef_id = 0xff; + + + while (cnt < AW_READ_CHIPID_RETRIES) { + /* hardware reset */ + awinic_hw_reset(awinic); + + ret = awinic_i2c_read(awinic, AW_REG_ID, ®); + if (ret < 0) { + aw_dev_err(awinic->dev, + "%s: failed to read register AW_REG_ID: %d\n", + __func__, ret); + } + + switch (reg) { + case AW8624_CHIP_ID: + aw_dev_info(awinic->dev, + "%s aw8624 detected\n", __func__); + awinic->name = AW8624; + awinic_haptic_softreset(awinic); + return 0; + case AW8622X_CHIP_ID: + /* Distinguish products by AW8622X_REG_EFRD9. */ + awinic_i2c_read(awinic, AW8622X_REG_EFRD9, &ef_id); + if ((ef_id & 0x41) == AW86224_5_EF_ID) { + awinic->name = AW86224_5; + aw_dev_info(awinic->dev, + "%s aw86224_5 detected\n", __func__); + awinic_haptic_softreset(awinic); + return 0; + } else if ((ef_id & 0x41) == AW86223_EF_ID) { + awinic->name = AW86223; + aw_dev_info(awinic->dev, + "%s aw86223 detected\n", __func__); + awinic_haptic_softreset(awinic); + return 0; + } else { + aw_dev_info(awinic->dev, + "%s unsupported ef_id = (0x%02X)\n", + __func__, ef_id); + break; + } + default: + aw_dev_info(awinic->dev, + "%s unsupported device revision (0x%x)\n", + __func__, reg); + break; + } + cnt++; + + usleep_range(2000, 3000); + } + + return -EINVAL; +} +static int awinic_parse_dt(struct device *dev, struct awinic *awinic, + struct device_node *np) { + unsigned int val = 0; + + awinic->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0); + if (awinic->reset_gpio >= 0) { + aw_dev_info(awinic->dev, + "%s: reset gpio provided ok\n", __func__); + } else { + awinic->reset_gpio = -1; + aw_dev_err(awinic->dev, + "%s: no reset gpio provided, will not HW reset device\n", + __func__); + return -1; + } + + awinic->irq_gpio = of_get_named_gpio(np, "irq-gpio", 0); + if (awinic->irq_gpio < 0) { + dev_err(dev, "%s: no irq gpio provided.\n", __func__); + awinic->IsUsedIRQ = false; + } else { + aw_dev_info(awinic->dev, + "%s: irq gpio provided ok.\n", __func__); + awinic->IsUsedIRQ = true; + } + + val = of_property_read_u32(np, + "aw8622x_i2c_addr", &awinic->aw8622x_i2c_addr); + if (val) + aw_dev_err(awinic->dev, + "%s:configure aw8622x_i2c_addr error\n", __func__); + else + aw_dev_info(awinic->dev, + "%s: configure aw8622x_i2c_addr ok\n", __func__); + return 0; +} + +static int +awinic_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + struct awinic *awinic; + struct device_node *np = i2c->dev.of_node; + int ret = -1; + int irq_flags = 0; + + aw_dev_info(&i2c->dev, "%s enter\n", __func__); + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C)) { + aw_dev_err(&i2c->dev, "check_functionality failed\n"); + return -EIO; + } + + awinic = devm_kzalloc(&i2c->dev, sizeof(struct awinic), GFP_KERNEL); + if (awinic == NULL) + return -ENOMEM; + + awinic->dev = &i2c->dev; + awinic->i2c = i2c; + + i2c_set_clientdata(i2c, awinic); + /* aw862xx rst & int */ + if (np) { + ret = awinic_parse_dt(&i2c->dev, awinic, np); + if (ret) { + aw_dev_err(&i2c->dev, + "%s: failed to parse device tree node\n", + __func__); + goto err_parse_dt; + } + } + if (gpio_is_valid(awinic->reset_gpio)) { + ret = devm_gpio_request_one(&i2c->dev, awinic->reset_gpio, + GPIOF_OUT_INIT_LOW, "awinic_rst"); + if (ret) { + aw_dev_err(&i2c->dev, + "%s: rst request failed\n", __func__); + goto err_reset_gpio_request; + } + } + + if (gpio_is_valid(awinic->irq_gpio)) { + ret = devm_gpio_request_one(&i2c->dev, awinic->irq_gpio, + GPIOF_DIR_IN, "awinic_int"); + if (ret) { + aw_dev_err(&i2c->dev, + "%s: int request failed\n", __func__); + goto err_irq_gpio_request; + } + } + /* read chip id */ + ret = awinic_read_chipid(awinic); + if (ret < 0) { + i2c->addr = (u16)awinic->aw8622x_i2c_addr; + aw_dev_info(&i2c->dev, "%s awinic->aw8622x_i2c_addr=0x%02x\n", + __func__, awinic->aw8622x_i2c_addr); + ret = awinic_read_chipid(awinic); + if (ret < 0) { + aw_dev_err(&i2c->dev, + "%s: awinic_read_chipid failed ret=%d\n", + __func__, ret); + goto err_id; + } + } + /* awinic device name */ + if (i2c->dev.of_node) + dev_set_name(&i2c->dev, "%s", AWINIC_DEV_NAME); + else + aw_dev_err(&i2c->dev, "%s failed to set device name: %d\n", + __func__, ret); + /*aw8624*/ + if (awinic->name == AW8624) { + awinic->aw8624 = devm_kzalloc(&i2c->dev, + sizeof(struct aw8624), GFP_KERNEL); + if (awinic->aw8624 == NULL) { + if (gpio_is_valid(awinic->irq_gpio)) + devm_gpio_free(&i2c->dev, awinic->irq_gpio); + if (gpio_is_valid(awinic->reset_gpio)) + devm_gpio_free(&i2c->dev, awinic->reset_gpio); + devm_kfree(&i2c->dev, awinic); + awinic = NULL; + return -ENOMEM; + } + awinic->aw8624->dev = awinic->dev; + awinic->aw8624->i2c = awinic->i2c; + awinic->aw8624->reset_gpio = awinic->reset_gpio; + awinic->aw8624->irq_gpio = awinic->irq_gpio; + awinic->aw8624->IsUsedIRQ = awinic->IsUsedIRQ; + if (np) { + ret = aw8624_parse_dt(awinic->aw8624, &i2c->dev, np); + if (ret) { + aw_dev_err(&i2c->dev, "%s: failed to parse device tree node\n", + __func__); + goto err_aw8624_parse_dt; + } + } + + /* aw8624 irq */ + if (gpio_is_valid(awinic->aw8624->irq_gpio) && + !(awinic->aw8624->flags & AW8624_FLAG_SKIP_INTERRUPTS)) { + /* register irq handler */ + aw8624_interrupt_setup(awinic->aw8624); + irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + ret = devm_request_threaded_irq(&i2c->dev, + gpio_to_irq(awinic->aw8624->irq_gpio), + NULL, aw8624_irq, irq_flags, + "aw8624", awinic->aw8624); + if (ret != 0) { + aw_dev_err(&i2c->dev, "%s: failed to request IRQ %d: %d\n", + __func__, + gpio_to_irq(awinic->aw8624->irq_gpio), + ret); + goto err_aw8624_irq; + } + } + dev_set_drvdata(&i2c->dev, awinic->aw8624); + g_aw8624 = awinic->aw8624; + aw8624_vibrator_init(awinic->aw8624); + aw8624_haptic_init(awinic->aw8624); + aw8624_ram_init(awinic->aw8624); + usleep_range(100000, 150000); + +#ifdef CONFIG_PM_WAKELOCKS +#ifdef KERNEL_VERSION_414 + wakeup_source_init(&awinic->aw8624->wk_lock, + "aw8624_wakelock"); +#endif +#else + wake_lock_init(&awinic->aw8624->wk_lock, WAKE_LOCK_SUSPEND, + "aw8624_wakelock"); +#endif + + } + /* aw8622x */ + if (awinic->name == AW86223 || awinic->name == AW86224_5) { + awinic->aw8622x = devm_kzalloc(&i2c->dev, + sizeof(struct aw8622x), GFP_KERNEL); + if (awinic == NULL) { + if (gpio_is_valid(awinic->irq_gpio)) + devm_gpio_free(&i2c->dev, awinic->irq_gpio); + if (gpio_is_valid(awinic->reset_gpio)) + devm_gpio_free(&i2c->dev, awinic->reset_gpio); + devm_kfree(&i2c->dev, awinic); + awinic = NULL; + return -ENOMEM; + } + awinic->aw8622x->dev = awinic->dev; + awinic->aw8622x->i2c = awinic->i2c; + awinic->aw8622x->reset_gpio = awinic->reset_gpio; + awinic->aw8622x->irq_gpio = awinic->irq_gpio; + awinic->aw8622x->isUsedIntn = awinic->IsUsedIRQ; + awinic->aw8622x->name = awinic->name; + /* chip qualify */ + if (!aw8622x_check_qualify(awinic->aw8622x)) { + aw_dev_err(&i2c->dev, + "%s:unqualified chip!\n", __func__); + goto err_aw8622x_check_qualify; + } + if (np) { + ret = aw8622x_parse_dt(awinic->aw8622x, &i2c->dev, np); + if (ret) { + aw_dev_err(&i2c->dev, + "%s: failed to parse device tree node\n", + __func__); + goto err_aw8622x_parse_dt; + } + } + /* aw8622x irq */ + if (gpio_is_valid(awinic->aw8622x->irq_gpio) && + !(awinic->aw8622x->flags & AW8622X_FLAG_SKIP_INTERRUPTS)) { + /* register irq handler */ + aw8622x_interrupt_setup(awinic->aw8622x); + irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + ret = devm_request_threaded_irq(&i2c->dev, + gpio_to_irq(awinic->aw8622x->irq_gpio), + NULL, aw8622x_irq, irq_flags, + "aw8622x", awinic->aw8622x); + if (ret != 0) { + aw_dev_err(&i2c->dev, + "%s: failed to request IRQ %d: %d\n", + __func__, + gpio_to_irq(awinic->aw8622x->irq_gpio), + ret); + goto err_aw8622x_irq; + } + } else { + aw_dev_info(&i2c->dev, + "%s skipping IRQ registration\n", __func__); + /* disable feature support if gpio was invalid */ + awinic->aw8622x->flags |= AW8622X_FLAG_SKIP_INTERRUPTS; + } + dev_set_drvdata(&i2c->dev, awinic->aw8622x); + g_aw8622x = awinic->aw8622x; + aw8622x_vibrator_init(awinic->aw8622x); + aw8622x_haptic_init(awinic->aw8622x); + aw8622x_ram_work_init(awinic->aw8622x); + } + aw_dev_info(&i2c->dev, "%s probe completed successfully!\n", __func__); + + return 0; + +err_aw8622x_irq: +err_aw8622x_parse_dt: +err_aw8622x_check_qualify: + if (awinic->name == AW86223 || awinic->name == AW86224_5) { + devm_kfree(&i2c->dev, awinic->aw8622x); + awinic->aw8622x = NULL; + } +err_aw8624_irq: +err_aw8624_parse_dt: + if (awinic->name == AW8624) { + devm_kfree(&i2c->dev, awinic->aw8624); + awinic->aw8624 = NULL; + } + +err_id: + if (gpio_is_valid(awinic->irq_gpio)) + devm_gpio_free(&i2c->dev, awinic->irq_gpio); +err_irq_gpio_request: + if (gpio_is_valid(awinic->reset_gpio)) + devm_gpio_free(&i2c->dev, awinic->reset_gpio); +err_reset_gpio_request: +err_parse_dt: + devm_kfree(&i2c->dev, awinic); + awinic = NULL; + return ret; + +} + +static int awinic_i2c_remove(struct i2c_client *i2c) +{ + struct awinic *awinic = i2c_get_clientdata(i2c); + + aw_dev_info(&i2c->dev, "%s enter\n", __func__); + + if (awinic->name == AW8624) { + aw_dev_info(&i2c->dev, "%s chip is aw8624\n", __func__); + cancel_delayed_work_sync(&g_aw8624->ram_work); + misc_deregister(&aw8624_haptic_misc); + + cancel_work_sync(&g_aw8624->haptic_audio.work); + hrtimer_cancel(&g_aw8624->haptic_audio.timer); + if (g_aw8624->IsUsedIRQ) + cancel_work_sync(&g_aw8624->rtp_work); + cancel_work_sync(&g_aw8624->vibrator_work); + hrtimer_cancel(&g_aw8624->timer); + + mutex_destroy(&g_aw8624->lock); + mutex_destroy(&g_aw8624->rtp_lock); + mutex_destroy(&g_aw8624->haptic_audio.lock); + + sysfs_remove_group(&g_aw8624->i2c->dev.kobj, + &aw8624_vibrator_attribute_group); + + devm_free_irq(&g_aw8624->i2c->dev, + gpio_to_irq(g_aw8624->irq_gpio), g_aw8624); + + } else if (awinic->name == AW86223 || awinic->name == AW86224_5) { + aw_dev_info(&i2c->dev, "%s chip is aw8622x\n", __func__); + cancel_delayed_work_sync(&g_aw8622x->ram_work); + cancel_work_sync(&g_aw8622x->haptic_audio.work); + hrtimer_cancel(&g_aw8622x->haptic_audio.timer); + if (g_aw8622x->isUsedIntn) + cancel_work_sync(&g_aw8622x->rtp_work); + cancel_work_sync(&g_aw8622x->long_vibrate_work); + + hrtimer_cancel(&g_aw8622x->timer); + mutex_destroy(&g_aw8622x->lock); + mutex_destroy(&g_aw8622x->rtp_lock); + mutex_destroy(&g_aw8622x->haptic_audio.lock); + sysfs_remove_group(&g_aw8622x->i2c->dev.kobj, + &aw8622x_vibrator_attribute_group); + devm_free_irq(&g_aw8622x->i2c->dev, + gpio_to_irq(g_aw8622x->irq_gpio), g_aw8622x); + + } else { + aw_dev_err(&i2c->dev, "%s no chip\n", __func__); + return -1; + } + + return 0; +} + +static const struct i2c_device_id awinic_i2c_id[] = { + { AWINIC_I2C_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, awinic_i2c_id); + +static const struct of_device_id awinic_dt_match[] = { + { .compatible = "awinic,awinic_haptic" }, + { }, +}; + +static struct i2c_driver awinic_i2c_driver = { + .driver = { + .name = AWINIC_I2C_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(awinic_dt_match), + }, + .probe = awinic_i2c_probe, + .remove = awinic_i2c_remove, + .id_table = awinic_i2c_id, +}; + +static int __init awinic_i2c_init(void) +{ + int ret = 0; + + pr_info("awinic driver version %s\n", AWINIC_DRIVER_VERSION); + + ret = i2c_add_driver(&awinic_i2c_driver); + if (ret) { + pr_err("fail to add awinic device into i2c\n"); + return ret; + } + + return 0; +} + +late_initcall(awinic_i2c_init); + +static void __exit awinic_i2c_exit(void) +{ + i2c_del_driver(&awinic_i2c_driver); +} +module_exit(awinic_i2c_exit); + +MODULE_DESCRIPTION("awinic Haptic Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/aw862xx_haptic/haptic.h b/drivers/misc/aw862xx_haptic/haptic.h new file mode 100644 index 000000000000..9c236b1e976b --- /dev/null +++ b/drivers/misc/aw862xx_haptic/haptic.h @@ -0,0 +1,93 @@ +#ifndef _HAPTIC_H_ +#define _HAPTIC_H_ +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************* +* +* marco +* +********************************************************/ +#define AW_CHECK_RAM_DATA +#define AW_READ_BIN_FLEXBALLY +#define AW_OSC_COARSE_CALI +/* #define AW_ENABLE_RTP_PRINT_LOG */ +#define AWINIC_DEV_NAME ("awinic_vibrator") + +/******************************************** + * print information control + *******************************************/ +#define aw_dev_err(dev, format, ...) \ + pr_err("[%s]" format, dev_name(dev), ##__VA_ARGS__) + +#define aw_dev_info(dev, format, ...) \ + pr_info("[%s]" format, dev_name(dev), ##__VA_ARGS__) + +#define aw_dev_dbg(dev, format, ...) \ + pr_debug("[%s]" format, dev_name(dev), ##__VA_ARGS__) + + +enum awinic_chip_name { + AW86223 = 0, + AW86224_5 = 1, + AW8624 = 2, +}; + +/*awinic*/ +struct awinic { + struct i2c_client *i2c; + struct device *dev; + unsigned char name; + bool IsUsedIRQ; + + unsigned int aw8622x_i2c_addr; + int reset_gpio; + int irq_gpio; + int reset_gpio_ret; + int irq_gpio_ret; + + struct aw8624 *aw8624; + struct aw8622x *aw8622x; +}; + + +struct ram { + unsigned int len; + unsigned int check_sum; + unsigned int base_addr; + unsigned char version; + unsigned char ram_shift; + unsigned char baseaddr_shift; + unsigned char ram_num; +}; + +struct haptic_ctr { + unsigned char cnt; + unsigned char cmd; + unsigned char play; + unsigned char wavseq; + unsigned char loop; + unsigned char gain; + struct list_head list; +}; + +struct haptic_audio { + struct mutex lock; + struct hrtimer timer; + struct work_struct work; + int delay_val; + int timer_val; + struct haptic_ctr ctr; + struct list_head ctr_list; +}; + +extern struct aw8624 *g_aw8624; +extern struct aw8622x *g_aw8622x; +#endif diff --git a/drivers/misc/fpr_FingerprintCard/Kconfig b/drivers/misc/fpr_FingerprintCard/Kconfig index c9599e6bb4a3..65b0ba5f81e6 100644 --- a/drivers/misc/fpr_FingerprintCard/Kconfig +++ b/drivers/misc/fpr_FingerprintCard/Kconfig @@ -1,10 +1,9 @@ -# -# FingerprintCard fingerprint driver -# -menu "FingerprintCard fingerprint driver" -config FPR_FPC - default n - tristate "FPC_BTP fingerprint sensor support" - depends on SPI_MASTER +# SPDX-License-Identifier: GPL-2.0-only -endmenu +config FPR_FPC + tristate "FPC fingerprint sensor support" + depends on SPI_MASTER + help + support fpc1020 and fpc1028. + This driver can also be built as a module. If so, the module + will be called fpc1020. diff --git a/drivers/misc/fpr_FingerprintCard/Makefile b/drivers/misc/fpr_FingerprintCard/Makefile index 96681eb834a6..58bbc70d24e2 100644 --- a/drivers/misc/fpr_FingerprintCard/Makefile +++ b/drivers/misc/fpr_FingerprintCard/Makefile @@ -1,5 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only + # Makefile for FingerprintCard fingerprint driver fpc1020-objs := fpc1020_platform_tee.o obj-$(CONFIG_FPR_FPC) += fpc1020.o - diff --git a/drivers/misc/fpr_FingerprintCard/fpc1020_platform_tee.c b/drivers/misc/fpr_FingerprintCard/fpc1020_platform_tee.c index 23a1d3c07e8c..0eda552934a8 100644 --- a/drivers/misc/fpr_FingerprintCard/fpc1020_platform_tee.c +++ b/drivers/misc/fpr_FingerprintCard/fpc1020_platform_tee.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * FPC1020 Fingerprint sensor device driver * @@ -18,7 +19,6 @@ * * * Copyright (c) 2015 Fingerprint Cards AB - * Copyright (C) 2020 XiaoMi, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License Version 2 @@ -38,23 +38,21 @@ #include #include -#define FPC_TTW_HOLD_TIME 1000 -#define CONFIG_FPC_COMPAT 1 -#define RESET_LOW_SLEEP_MIN_US 5000 -#define RESET_LOW_SLEEP_MAX_US (RESET_LOW_SLEEP_MIN_US + 100) -#define RESET_HIGH_SLEEP1_MIN_US 100 -#define RESET_HIGH_SLEEP1_MAX_US (RESET_HIGH_SLEEP1_MIN_US + 100) -#define RESET_HIGH_SLEEP2_MIN_US 5000 -#define RESET_HIGH_SLEEP2_MAX_US (RESET_HIGH_SLEEP2_MIN_US + 100) -#define PWR_ON_SLEEP_MIN_US 100 -#define PWR_ON_SLEEP_MAX_US (PWR_ON_SLEEP_MIN_US + 900) +#define FPC_TTW_HOLD_TIME 1000 +#define RESET_LOW_SLEEP_MIN_US 5000 +#define RESET_LOW_SLEEP_MAX_US (RESET_LOW_SLEEP_MIN_US + 100) +#define RESET_HIGH_SLEEP1_MIN_US 100 +#define RESET_HIGH_SLEEP1_MAX_US (RESET_HIGH_SLEEP1_MIN_US + 100) +#define RESET_HIGH_SLEEP2_MIN_US 5000 +#define RESET_HIGH_SLEEP2_MAX_US (RESET_HIGH_SLEEP2_MIN_US + 100) +#define PWR_ON_SLEEP_MIN_US 100 +#define PWR_ON_SLEEP_MAX_US (PWR_ON_SLEEP_MIN_US + 900) +#define NUM_PARAMS_REG_ENABLE_SET 2 -#define NUM_PARAMS_REG_ENABLE_SET 2 - -#define RELEASE_WAKELOCK_W_V "release_wakelock_with_verification" -#define RELEASE_WAKELOCK "release_wakelock" -#define START_IRQS_RECEIVED_CNT "start_irqs_received_counter" +#define RELEASE_WAKELOCK_W_V "release_wakelock_with_verification" +#define RELEASE_WAKELOCK "release_wakelock" +#define START_IRQS_RECEIVED_CNT "start_irqs_received_counter" static const char * const pctl_names[] = { "fpc1020_reset_reset", @@ -69,7 +67,7 @@ struct vreg_config { int ua_load; }; -static const struct vreg_config const vreg_conf[] = { +static const struct vreg_config vreg_conf[] = { { "vdd_ana", 1800000UL, 1800000UL, 6000, }, { "vcc_spi", 1800000UL, 1800000UL, 10, }, { "vdd_io", 1800000UL, 1800000UL, 6000, }, @@ -77,7 +75,6 @@ static const struct vreg_config const vreg_conf[] = { struct fpc1020_data { struct device *dev; - struct pinctrl *fingerprint_pinctrl; struct pinctrl_state *pinctrl_state[ARRAY_SIZE(pctl_names)]; struct regulator *vreg[ARRAY_SIZE(vreg_conf)]; @@ -85,22 +82,12 @@ struct fpc1020_data { struct mutex lock; /* To set/get exported values in sysfs */ int irq_gpio; int rst_gpio; - int nbr_irqs_received; int nbr_irqs_received_counter_start; - bool prepared; -#ifdef CONFIG_FPC_COMPAT - bool compatible_enabled; -#endif atomic_t wakeup_enabled; /* Used both in ISR and non-ISR */ }; -static irqreturn_t fpc1020_irq_handler(int irq, void *handle); -static int fpc1020_request_named_gpio(struct fpc1020_data *fpc1020, - const char *label, int *gpio); -static int hw_reset(struct fpc1020_data *fpc1020); - static int vreg_setup(struct fpc1020_data *fpc1020, const char *name, bool enable) { @@ -109,10 +96,10 @@ static int vreg_setup(struct fpc1020_data *fpc1020, const char *name, struct regulator *vreg; struct device *dev = fpc1020->dev; - for (i = 0; i < ARRAY_SIZE(fpc1020->vreg); i++) { + for (i = 0; i < ARRAY_SIZE(vreg_conf); i++) { const char *n = vreg_conf[i].name; - if (!strncmp(n, name, strlen(n))) + if (!memcmp(n, name, strlen(n))) goto found; } @@ -126,8 +113,10 @@ static int vreg_setup(struct fpc1020_data *fpc1020, const char *name, if (!vreg) { vreg = devm_regulator_get(dev, name); if (IS_ERR_OR_NULL(vreg)) { - dev_err(dev, "Unable to get %s\n", name); - return PTR_ERR(vreg); + dev_info(dev, + "No regulator %s, maybe fixed regulator\n", + name); + return 0; } } @@ -165,7 +154,7 @@ static int vreg_setup(struct fpc1020_data *fpc1020, const char *name, return rc; } -/** +/* * sysfs node for controlling clocks. * * This is disabled in platform variant of this driver but kept for @@ -183,7 +172,7 @@ static ssize_t clk_enable_store(struct device *dev, } static DEVICE_ATTR_WO(clk_enable); -/** +/* * Will try to select the set of pins (GPIOS) defined in a pin control node of * the device tree named @p name. * @@ -201,25 +190,23 @@ static int select_pin_ctl(struct fpc1020_data *fpc1020, const char *name) int rc; struct device *dev = fpc1020->dev; - for (i = 0; i < ARRAY_SIZE(fpc1020->pinctrl_state); i++) { + for (i = 0; i < ARRAY_SIZE(pctl_names); i++) { const char *n = pctl_names[i]; - if (!strncmp(n, name, strlen(n))) { + if (!memcmp(n, name, strlen(n))) { rc = pinctrl_select_state(fpc1020->fingerprint_pinctrl, fpc1020->pinctrl_state[i]); if (rc) dev_err(dev, "cannot select '%s'\n", name); else dev_dbg(dev, "Selected '%s'\n", name); - goto exit; + + return rc; } } - rc = -EINVAL; dev_err(dev, "%s:'%s' not found\n", __func__, name); - -exit: - return rc; + return -EINVAL; } static ssize_t pinctl_set_store(struct device *dev, @@ -245,7 +232,7 @@ static ssize_t regulator_enable_store(struct device *dev, int rc; bool enable; - if (NUM_PARAMS_REG_ENABLE_SET != sscanf(buf, "%15[^,],%c", name, &op)) + if (sscanf(buf, "%15[^,],%c", name, &op) != NUM_PARAMS_REG_ENABLE_SET) return -EINVAL; if (op == 'e') enable = true; @@ -262,55 +249,42 @@ static ssize_t regulator_enable_store(struct device *dev, } static DEVICE_ATTR_WO(regulator_enable); -static int hw_reset(struct fpc1020_data *fpc1020) +static void hw_reset(struct fpc1020_data *fpc1020) { - int irq_gpio; - int rc; - struct device *dev = fpc1020->dev; - irq_gpio = gpio_get_value(fpc1020->irq_gpio); - dev_info(dev, "IRQ before reset %d\n", irq_gpio); - rc = select_pin_ctl(fpc1020, "fpc1020_reset_active"); + (void)gpio_get_value(fpc1020->irq_gpio); + + select_pin_ctl(fpc1020, "fpc1020_reset_active"); - if (rc) - goto exit; usleep_range(RESET_HIGH_SLEEP1_MIN_US, RESET_HIGH_SLEEP1_MAX_US); - rc = select_pin_ctl(fpc1020, "fpc1020_reset_reset"); - if (rc) - goto exit; + select_pin_ctl(fpc1020, "fpc1020_reset_reset"); + usleep_range(RESET_LOW_SLEEP_MIN_US, RESET_LOW_SLEEP_MAX_US); - rc = select_pin_ctl(fpc1020, "fpc1020_reset_active"); - if (rc) - goto exit; + select_pin_ctl(fpc1020, "fpc1020_reset_active"); + usleep_range(RESET_HIGH_SLEEP2_MIN_US, RESET_HIGH_SLEEP2_MAX_US); - irq_gpio = gpio_get_value(fpc1020->irq_gpio); - dev_info(dev, "IRQ after reset %d\n", irq_gpio); - -exit: - return rc; + (void)gpio_get_value(fpc1020->irq_gpio); } static ssize_t hw_reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - int rc; struct fpc1020_data *fpc1020 = dev_get_drvdata(dev); - if (!strncmp(buf, "reset", strlen("reset"))) { + if (!memcmp(buf, "reset", strlen("reset"))) { mutex_lock(&fpc1020->lock); - rc = hw_reset(fpc1020); + hw_reset(fpc1020); mutex_unlock(&fpc1020->lock); - } else { - return -EINVAL; + return count; } - return rc ? rc : count; + return -EINVAL; } static DEVICE_ATTR_WO(hw_reset); -/** +/* * Will setup GPIOs, and regulators to correctly initialize the touch sensor to * be ready for work. * @@ -322,57 +296,35 @@ static DEVICE_ATTR_WO(hw_reset); * @note This function will not send any commands to the sensor it will only * control it "electrically". */ -static int device_prepare(struct fpc1020_data *fpc1020, bool enable) +static void device_prepare(struct fpc1020_data *fpc1020, bool enable) { - int rc; - mutex_lock(&fpc1020->lock); if (enable && !fpc1020->prepared) { fpc1020->prepared = true; select_pin_ctl(fpc1020, "fpc1020_reset_reset"); - rc = vreg_setup(fpc1020, "vcc_spi", true); - if (rc) - goto exit; - - rc = vreg_setup(fpc1020, "vdd_io", true); - if (rc) - goto exit_1; - - rc = vreg_setup(fpc1020, "vdd_ana", true); - if (rc) - goto exit_2; + vreg_setup(fpc1020, "vcc_spi", true); + vreg_setup(fpc1020, "vdd_io", true); + vreg_setup(fpc1020, "vdd_ana", true); usleep_range(PWR_ON_SLEEP_MIN_US, PWR_ON_SLEEP_MAX_US); - /* As we can't control chip select here the other part of the - * sensor driver eg. the TEE driver needs to do a _SOFT_ reset - * on the sensor after power up to be sure that the sensor is - * in a good state after power up. Okeyed by ASIC. */ - - (void)select_pin_ctl(fpc1020, "fpc1020_reset_active"); + select_pin_ctl(fpc1020, "fpc1020_reset_active"); } else if (!enable && fpc1020->prepared) { - rc = 0; - (void)select_pin_ctl(fpc1020, "fpc1020_reset_reset"); + select_pin_ctl(fpc1020, "fpc1020_reset_reset"); usleep_range(PWR_ON_SLEEP_MIN_US, PWR_ON_SLEEP_MAX_US); - (void)vreg_setup(fpc1020, "vdd_ana", false); -exit_2: - (void)vreg_setup(fpc1020, "vdd_io", false); -exit_1: - (void)vreg_setup(fpc1020, "vcc_spi", false); -exit: + vreg_setup(fpc1020, "vdd_ana", false); + vreg_setup(fpc1020, "vdd_io", false); + vreg_setup(fpc1020, "vcc_spi", false); + fpc1020->prepared = false; - } else { - rc = 0; } mutex_unlock(&fpc1020->lock); - - return rc; } -/** +/* * sysfs node to enable/disable (power up/power down) the touch sensor * * @see device_prepare @@ -380,19 +332,20 @@ static int device_prepare(struct fpc1020_data *fpc1020, bool enable) static ssize_t device_prepare_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - int rc; + int rc = 0; struct fpc1020_data *fpc1020 = dev_get_drvdata(dev); - if (!strncmp(buf, "enable", strlen("enable"))) - rc = device_prepare(fpc1020, true); - else if (!strncmp(buf, "disable", strlen("disable"))) - rc = device_prepare(fpc1020, false); + if (!memcmp(buf, "enable", strlen("enable"))) + device_prepare(fpc1020, true); + else if (!memcmp(buf, "disable", strlen("disable"))) + device_prepare(fpc1020, false); else - return -EINVAL; + rc = -EINVAL; return rc ? rc : count; } -DEVICE_ATTR_WO(device_prepare); +static DEVICE_ATTR_WO(device_prepare); + /** * sysfs node for controlling whether the driver is allowed * to wake up the platform on interrupt. @@ -404,9 +357,9 @@ static ssize_t wakeup_enable_store(struct device *dev, ssize_t ret = count; mutex_lock(&fpc1020->lock); - if (!strncmp(buf, "enable", strlen("enable"))) + if (!memcmp(buf, "enable", strlen("enable"))) atomic_set(&fpc1020->wakeup_enabled, 1); - else if (!strncmp(buf, "disable", strlen("disable"))) + else if (!memcmp(buf, "disable", strlen("disable"))) atomic_set(&fpc1020->wakeup_enabled, 0); else ret = -EINVAL; @@ -416,30 +369,31 @@ static ssize_t wakeup_enable_store(struct device *dev, } static DEVICE_ATTR_WO(wakeup_enable); -/** + +/* * sysfs node for controlling the wakelock. */ -static ssize_t handle_wakelock_cmd(struct device *dev, +static ssize_t handle_wakelock_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct fpc1020_data *fpc1020 = dev_get_drvdata(dev); ssize_t ret = count; mutex_lock(&fpc1020->lock); - if (!strncmp(buf, RELEASE_WAKELOCK_W_V, + if (!memcmp(buf, RELEASE_WAKELOCK_W_V, min(count, strlen(RELEASE_WAKELOCK_W_V)))) { if (fpc1020->nbr_irqs_received_counter_start == fpc1020->nbr_irqs_received) { __pm_relax(fpc1020->ttw_wl); } else { dev_dbg(dev, "Ignore releasing of wakelock %d != %d", - fpc1020->nbr_irqs_received_counter_start, - fpc1020->nbr_irqs_received); + fpc1020->nbr_irqs_received_counter_start, + fpc1020->nbr_irqs_received); } - } else if (!strncmp(buf, RELEASE_WAKELOCK, min(count, + } else if (!memcmp(buf, RELEASE_WAKELOCK, min(count, strlen(RELEASE_WAKELOCK)))) { __pm_relax(fpc1020->ttw_wl); - } else if (!strncmp(buf, START_IRQS_RECEIVED_CNT, + } else if (!memcmp(buf, START_IRQS_RECEIVED_CNT, min(count, strlen(START_IRQS_RECEIVED_CNT)))) { fpc1020->nbr_irqs_received_counter_start = fpc1020->nbr_irqs_received; @@ -449,131 +403,21 @@ static ssize_t handle_wakelock_cmd(struct device *dev, return ret; } -static DEVICE_ATTR(handle_wakelock, S_IWUSR, NULL, handle_wakelock_cmd); +static DEVICE_ATTR_WO(handle_wakelock); -/** +/* * sysf node to check the interrupt status of the sensor, the interrupt * handler should perform sysf_notify to allow userland to poll the node. */ -static ssize_t irq_get(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t irq_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct fpc1020_data *fpc1020 = dev_get_drvdata(dev); int irq = gpio_get_value(fpc1020->irq_gpio); return scnprintf(buf, PAGE_SIZE, "%i\n", irq); } - -/** - * writing to the irq node will just drop a printk message - * and return success, used for latency measurement. - */ -static ssize_t irq_ack(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct fpc1020_data *fpc1020 = dev_get_drvdata(dev); - - dev_dbg(fpc1020->dev, "%s\n", __func__); - - return count; -} -static DEVICE_ATTR(irq, S_IRUSR | S_IWUSR, irq_get, irq_ack); - -#ifdef CONFIG_FPC_COMPAT -static ssize_t compatible_all_set(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - int rc; - int i; - int irqf; - struct fpc1020_data *fpc1020 = dev_get_drvdata(dev); - dev_err(dev, "compatible all enter %d\n", fpc1020->compatible_enabled); - if (!strncmp(buf, "enable", strlen("enable")) && fpc1020->compatible_enabled != 1) { - rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_irq", - &fpc1020->irq_gpio); - if (rc) - goto exit; - - rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_rst", - &fpc1020->rst_gpio); - dev_err(dev, "fpc request reset result = %d\n", rc); - if (rc) - goto exit; - fpc1020->fingerprint_pinctrl = devm_pinctrl_get(dev); - if (IS_ERR(fpc1020->fingerprint_pinctrl)) { - if (PTR_ERR(fpc1020->fingerprint_pinctrl) == -EPROBE_DEFER) { - dev_info(dev, "pinctrl not ready\n"); - rc = -EPROBE_DEFER; - goto exit; - } - dev_err(dev, "Target does not use pinctrl\n"); - fpc1020->fingerprint_pinctrl = NULL; - rc = -EINVAL; - goto exit; - } - - for (i = 0; i < ARRAY_SIZE(fpc1020->pinctrl_state); i++) { - const char *n = pctl_names[i]; - struct pinctrl_state *state = - pinctrl_lookup_state(fpc1020->fingerprint_pinctrl, n); - if (IS_ERR(state)) { - dev_err(dev, "cannot find '%s'\n", n); - rc = -EINVAL; - goto exit; - } - dev_info(dev, "found pin control %s\n", n); - fpc1020->pinctrl_state[i] = state; - } - rc = select_pin_ctl(fpc1020, "fpc1020_reset_reset"); - if (rc) - goto exit; - rc = select_pin_ctl(fpc1020, "fpc1020_irq_active"); - if (rc) - goto exit; - irqf = IRQF_TRIGGER_RISING | IRQF_ONESHOT; - if (of_property_read_bool(dev->of_node, "fpc,enable-wakeup")) { - irqf |= IRQF_NO_SUSPEND; - device_init_wakeup(dev, 1); - } - rc = devm_request_threaded_irq(dev, gpio_to_irq(fpc1020->irq_gpio), - NULL, fpc1020_irq_handler, irqf, - dev_name(dev), fpc1020); - if (rc) { - dev_err(dev, "could not request irq %d\n", - gpio_to_irq(fpc1020->irq_gpio)); - goto exit; - } - dev_dbg(dev, "requested irq %d\n", gpio_to_irq(fpc1020->irq_gpio)); - - /* Request that the interrupt should be wakeable */ - enable_irq_wake(gpio_to_irq(fpc1020->irq_gpio)); - fpc1020->compatible_enabled = 1; - if (of_property_read_bool(dev->of_node, "fpc,enable-on-boot")) { - dev_info(dev, "Enabling hardware\n"); - (void)device_prepare(fpc1020, true); - - } - hw_reset(fpc1020); - } else if (!strncmp(buf, "disable", strlen("disable")) && fpc1020->compatible_enabled != 0) { - if (gpio_is_valid(fpc1020->irq_gpio)) { - devm_gpio_free(dev, fpc1020->irq_gpio); - pr_info("remove irq_gpio success\n"); - } - if (gpio_is_valid(fpc1020->rst_gpio)) { - devm_gpio_free(dev, fpc1020->rst_gpio); - pr_info("remove rst_gpio success\n"); - } - devm_free_irq(dev, gpio_to_irq(fpc1020->irq_gpio), fpc1020); - fpc1020->compatible_enabled = 0; - } - return count; -exit: - return -EINVAL; -} -static DEVICE_ATTR(compatible_all, S_IWUSR, NULL, compatible_all_set); -#endif +static DEVICE_ATTR_RO(irq); static struct attribute *attributes[] = { &dev_attr_pinctl_set.attr, @@ -584,9 +428,6 @@ static struct attribute *attributes[] = { &dev_attr_handle_wakelock.attr, &dev_attr_clk_enable.attr, &dev_attr_irq.attr, -#ifdef CONFIG_FPC_COMPAT - &dev_attr_compatible_all.attr, -#endif NULL }; @@ -598,8 +439,7 @@ static irqreturn_t fpc1020_irq_handler(int irq, void *handle) { struct fpc1020_data *fpc1020 = handle; - dev_dbg(fpc1020->dev, "%s\n", __func__); - + pr_info("fpc1020 irq handler: %s\n", __func__); mutex_lock(&fpc1020->lock); if (atomic_read(&fpc1020->wakeup_enabled)) { fpc1020->nbr_irqs_received++; @@ -618,7 +458,9 @@ static int fpc1020_request_named_gpio(struct fpc1020_data *fpc1020, { struct device *dev = fpc1020->dev; struct device_node *np = dev->of_node; - int rc = of_get_named_gpio(np, label, 0); + int rc; + + rc = of_get_named_gpio(np, label, 0); if (rc < 0) { dev_err(dev, "failed to get '%s'\n", label); @@ -639,94 +481,69 @@ static int fpc1020_request_named_gpio(struct fpc1020_data *fpc1020, static int fpc1020_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - int rc = 0; -#ifndef CONFIG_FPC_COMPAT + struct fpc1020_data *fpc1020; + int rc; size_t i; - int irqf; -#endif - struct device_node *np = dev->of_node; - struct fpc1020_data *fpc1020 = devm_kzalloc(dev, sizeof(*fpc1020), - GFP_KERNEL); + int irqf = 0; - if (!fpc1020) { - dev_err(dev, - "failed to allocate memory for struct fpc1020_data\n"); - rc = -ENOMEM; - goto exit; - } + fpc1020 = devm_kzalloc(dev, sizeof(*fpc1020), GFP_KERNEL); + if (!fpc1020) + return -ENOMEM; fpc1020->dev = dev; platform_set_drvdata(pdev, fpc1020); - if (!np) { - dev_err(dev, "no of node found\n"); - rc = -EINVAL; - goto exit; - } -#ifndef CONFIG_FPC_COMPAT rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_irq", &fpc1020->irq_gpio); if (rc) - goto exit; + return -EINVAL; rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_rst", &fpc1020->rst_gpio); if (rc) - goto exit; + return -EINVAL; fpc1020->fingerprint_pinctrl = devm_pinctrl_get(dev); if (IS_ERR(fpc1020->fingerprint_pinctrl)) { - if (PTR_ERR(fpc1020->fingerprint_pinctrl) == -EPROBE_DEFER) { - dev_info(dev, "pinctrl not ready\n"); - rc = -EPROBE_DEFER; - goto exit; - } - dev_err(dev, "Target does not use pinctrl\n"); - fpc1020->fingerprint_pinctrl = NULL; - rc = -EINVAL; - goto exit; + rc = PTR_ERR(fpc1020->fingerprint_pinctrl); + dev_err(dev, "Cannot get pinctrl\n", rc); + return rc; } - for (i = 0; i < ARRAY_SIZE(fpc1020->pinctrl_state); i++) { + for (i = 0; i < ARRAY_SIZE(pctl_names); i++) { const char *n = pctl_names[i]; struct pinctrl_state *state = pinctrl_lookup_state(fpc1020->fingerprint_pinctrl, n); if (IS_ERR(state)) { dev_err(dev, "cannot find '%s'\n", n); - rc = -EINVAL; - goto exit; + return PTR_ERR(state); } - dev_info(dev, "found pin control %s\n", n); + dev_dbg(dev, "found pin control %s\n", n); fpc1020->pinctrl_state[i] = state; } - rc = select_pin_ctl(fpc1020, "fpc1020_reset_reset"); - if (rc) - goto exit; - rc = select_pin_ctl(fpc1020, "fpc1020_irq_active"); - if (rc) - goto exit; + select_pin_ctl(fpc1020, "fpc1020_reset_reset"); + select_pin_ctl(fpc1020, "fpc1020_irq_active"); atomic_set(&fpc1020->wakeup_enabled, 0); - irqf = IRQF_TRIGGER_RISING | IRQF_ONESHOT; if (of_property_read_bool(dev->of_node, "fpc,enable-wakeup")) { - irqf |= IRQF_NO_SUSPEND; + irqf = IRQF_NO_SUSPEND; device_init_wakeup(dev, 1); } mutex_init(&fpc1020->lock); rc = devm_request_threaded_irq(dev, gpio_to_irq(fpc1020->irq_gpio), - NULL, fpc1020_irq_handler, irqf, + NULL, fpc1020_irq_handler, + irqf | IRQF_TRIGGER_RISING | IRQF_ONESHOT, dev_name(dev), fpc1020); if (rc) { dev_err(dev, "could not request irq %d\n", gpio_to_irq(fpc1020->irq_gpio)); - goto exit; + return rc; } dev_dbg(dev, "requested irq %d\n", gpio_to_irq(fpc1020->irq_gpio)); - /* Request that the interrupt should be wakeable */ enable_irq_wake(gpio_to_irq(fpc1020->irq_gpio)); fpc1020->ttw_wl = wakeup_source_register(dev, "fpc_ttw_wl"); @@ -736,36 +553,17 @@ static int fpc1020_probe(struct platform_device *pdev) rc = sysfs_create_group(&dev->kobj, &attribute_group); if (rc) { dev_err(dev, "could not create sysfs\n"); - goto exit; + return rc; } if (of_property_read_bool(dev->of_node, "fpc,enable-on-boot")) { - dev_info(dev, "Enabling hardware\n"); - (void)device_prepare(fpc1020, true); + dev_dbg(dev, "Enabling hardware\n"); + device_prepare(fpc1020, true); } - rc = hw_reset(fpc1020); - if (rc) { - dev_err(dev, "hardware reset failed\n"); - goto exit; - } -#else - mutex_init(&fpc1020->lock); + hw_reset(fpc1020); - fpc1020->ttw_wl = wakeup_source_register(dev, "fpc_ttw_wl"); - if (!fpc1020->ttw_wl) - return -ENOMEM; - - rc = sysfs_create_group(&dev->kobj, &attribute_group); - if (rc) { - dev_err(dev, "could not create sysfs\n"); - goto exit; - } -#endif - dev_info(dev, "%s: ok\n", __func__); - -exit: - return rc; + return 0; } static int fpc1020_remove(struct platform_device *pdev) @@ -782,7 +580,7 @@ static int fpc1020_remove(struct platform_device *pdev) return 0; } -static struct of_device_id fpc1020_of_match[] = { +static const struct of_device_id fpc1020_of_match[] = { { .compatible = "fpc,fpc1020", }, {} }; @@ -791,35 +589,13 @@ MODULE_DEVICE_TABLE(of, fpc1020_of_match); static struct platform_driver fpc1020_driver = { .driver = { .name = "fpc1020", - .owner = THIS_MODULE, .of_match_table = fpc1020_of_match, }, .probe = fpc1020_probe, .remove = fpc1020_remove, }; -static int __init fpc1020_init(void) -{ - int rc = platform_driver_register(&fpc1020_driver); +module_platform_driver(fpc1020_driver); - if (!rc) - pr_info("%s OK\n", __func__); - else - pr_err("%s %d\n", __func__, rc); - - return rc; -} - -static void __exit fpc1020_exit(void) -{ - pr_info("%s\n", __func__); - platform_driver_unregister(&fpc1020_driver); -} - -module_init(fpc1020_init); -module_exit(fpc1020_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Aleksej Makarov"); -MODULE_AUTHOR("Henrik Tillman "); MODULE_DESCRIPTION("FPC1020 Fingerprint sensor device driver."); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/qrc/Kconfig b/drivers/misc/qrc/Kconfig new file mode 100644 index 000000000000..59f734c02092 --- /dev/null +++ b/drivers/misc/qrc/Kconfig @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# QRC device driver configuration +# + +menu "qrc device driver" + +config QRC + bool "QRC device driver for Robotic SDK MCU" + help + This kernel configuration is used to enable robotic controller + device driver. Say Y here if you want to enable robotic + controller device driver. + When in doubt, say N. + +config QRC_DEBUG + bool "QRC Debugging" + depends on QRC + help + Say Y here if you want the robotic controller to produce + a bunch of debug messages to the system log. Select this if you + are having a problem with robotic controller support and want + to see more of what is going on. + When in doubt, say N. +endmenu diff --git a/drivers/misc/qrc/Makefile b/drivers/misc/qrc/Makefile new file mode 100644 index 000000000000..d811992aa0d9 --- /dev/null +++ b/drivers/misc/qrc/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the QRC bus specific drivers. + + +obj-$(CONFIG_QRC) += qrc_core.o qrc_uart.o + + +#ccflags-$(CONFIG_QRC_DEBUG) := -DDEBUG diff --git a/drivers/misc/qrc/qrc_core.c b/drivers/misc/qrc/qrc_core.c new file mode 100644 index 000000000000..af570b207e0f --- /dev/null +++ b/drivers/misc/qrc/qrc_core.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* driver/misc/qrc/qrc_core.c + * + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qrc_core.h" + +#define FIFO_CLEAR 0x1 + +#define QRC_DEVICE_NAME "qrc" + +static dev_t qrc_devt; +static struct class *qrc_class; + +static int qrc_cdev_fasync(int fd, struct file *filp, int mode) +{ + struct qrc_dev *qrc; + + qrc = filp->private_data; + return fasync_helper(fd, filp, mode, &qrc->async_queue); +} + +static int qrc_cdev_open(struct inode *inode, struct file *filp) +{ + struct qrc_dev *qrc; + + qrc = container_of(inode->i_cdev, + struct qrc_dev, cdev); + filp->private_data = qrc; + if (qrc->qrc_ops != NULL) + qrc->qrc_ops->qrcops_open(qrc); + return 0; +} + +static int qrc_cdev_release(struct inode *inode, struct file *filp) +{ + struct qrc_dev *qrc; + + qrc = filp->private_data; + if (qrc->qrc_ops != NULL) + qrc->qrc_ops->qrcops_close(qrc); + + return 0; +} + +/* GPIO control */ +static int +qrc_control_gpio_init(struct qrc_dev *qdev, struct device_node *node) +{ + int ret; + + /* QRC BOOT0 GPIO */ + qdev->qrc_boot0_gpio = of_get_named_gpio(node, + "qcom,qrc-boot-gpio", 0); + if (qdev->qrc_boot0_gpio < 0) + pr_err("qrc_boot0_gpio is not available\n"); + + /* UART RESET GPIO */ + qdev->qrc_reset_gpio = of_get_named_gpio(node, + "qcom,qrc-reset-gpio", 0); + if (qdev->qrc_reset_gpio < 0) + pr_err("qrc_reset_gpio is not available\n"); + + if (gpio_is_valid(qdev->qrc_boot0_gpio)) { + ret = gpio_request(qdev->qrc_boot0_gpio, "QRC_BOOT0_GPIO"); + if (unlikely(ret)) { + pr_err("gpio request qrc_boot0_gpio failed for:%d\n", + qdev->qrc_boot0_gpio); + return ret; + } + } + + if (gpio_is_valid(qdev->qrc_reset_gpio)) { + ret = gpio_request(qdev->qrc_reset_gpio, "QRC_RESET_GPIO"); + if (unlikely(ret)) { + pr_err("gpio request qrc_reset_gpio failed for:%d\n", + qdev->qrc_reset_gpio); + return ret; + } + } + + ret = gpio_direction_output(qdev->qrc_reset_gpio, 0); + ret += gpio_export(qdev->qrc_reset_gpio, 0); + + if (ret) { + pr_err("Unable to configure GPIO%d (RESET)\n", + qdev->qrc_reset_gpio); + ret = -EBUSY; + gpio_free(qdev->qrc_reset_gpio); + return ret; + } + + ret = gpio_direction_output(qdev->qrc_boot0_gpio, 1); + ret += gpio_export(qdev->qrc_boot0_gpio, 0); + if (ret) { + pr_err("Unable to configure GPIO%d (BOOT0)\n", + qdev->qrc_boot0_gpio); + ret = -EBUSY; + gpio_free(qdev->qrc_boot0_gpio); + return ret; + } + /* default config gpio status.boot=1,reset=0 */ + gpio_set_value(qdev->qrc_boot0_gpio, 1); + gpio_set_value(qdev->qrc_reset_gpio, 0); + + return 0; +} + +static void +qrc_control_gpio_uninit(struct qrc_dev *qdev) +{ + gpio_free(qdev->qrc_boot0_gpio); + gpio_free(qdev->qrc_reset_gpio); +} + +static void qrc_gpio_reboot(struct qrc_dev *qdev) +{ + gpio_set_value(qdev->qrc_reset_gpio, 0); + msleep(100); + gpio_set_value(qdev->qrc_reset_gpio, 1); +} +static long qrc_cdev_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct qrc_dev *qdev; + + qdev = filp->private_data; + switch (cmd) { + case QRC_FIFO_CLEAR: + mutex_lock(&qdev->mutex); + qdev->qrc_ops->qrcops_data_clean(qdev); + mutex_unlock(&qdev->mutex); + return 0; + case QRC_REBOOT: + if (gpio_is_valid(qdev->qrc_reset_gpio)) { + qrc_gpio_reboot(qdev); + return 0; + } else + return -EFAULT; + case QRC_BOOT_TO_MEM: + if (gpio_is_valid(qdev->qrc_boot0_gpio)) { + gpio_set_value(qdev->qrc_boot0_gpio, 1); + qrc_gpio_reboot(qdev); + return 0; + } else + return -EFAULT; + case QRC_BOOT_TO_FLASH: + if (gpio_is_valid(qdev->qrc_boot0_gpio)) { + gpio_set_value(qdev->qrc_boot0_gpio, 0); + qrc_gpio_reboot(qdev); + return 0; + } else + return -EFAULT; + default: + return -EINVAL; + } + return 0; +} + +static unsigned int qrc_cdev_poll(struct file *filp, poll_table *wait) +{ + unsigned int mask = 0; + struct qrc_dev *qrc; + + qrc = filp->private_data; + mutex_lock(&qrc->mutex); + + poll_wait(filp, &qrc->r_wait, wait); + + if (qrc->qrc_ops->qrcops_data_status(qrc) != 0) + mask |= POLLIN | POLLRDNORM; + + mutex_unlock(&qrc->mutex); + return mask; +} + +static ssize_t qrc_cdev_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + int ret; + struct qrc_dev *qrc; + + qrc = filp->private_data; + DECLARE_WAITQUEUE(wait, current); + + mutex_lock(&qrc->mutex); + add_wait_queue(&qrc->r_wait, &wait); + + while (qrc->qrc_ops->qrcops_data_status(qrc) == 0) { + + if (filp->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + goto out; + } + __set_current_state(TASK_INTERRUPTIBLE); + mutex_unlock(&qrc->mutex); + + schedule(); + if (signal_pending(current)) { + ret = -ERESTARTSYS; + goto out2; + } + + mutex_lock(&qrc->mutex); + } + + ret = qrc->qrc_ops->qrcops_receive(qrc, buf, count); + + out: + mutex_unlock(&qrc->mutex); + out2: + remove_wait_queue(&qrc->r_wait, &wait); + set_current_state(TASK_RUNNING); + return ret; +} + +static ssize_t qrc_cdev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct qrc_dev *qrc; + struct qrcuart *qrcuart; + enum qrcdev_tx ret; + + qrc = filp->private_data; + ret = qrc->qrc_ops->qrcops_xmit(buf, count, qrc); + if (ret == QRCDEV_TX_OK) + return count; + + return 0; +} + +static const struct file_operations qrc_cdev_fops = { + .owner = THIS_MODULE, + .read = qrc_cdev_read, + .write = qrc_cdev_write, + .unlocked_ioctl = qrc_cdev_ioctl, + .poll = qrc_cdev_poll, + .fasync = qrc_cdev_fasync, + .open = qrc_cdev_open, + .release = qrc_cdev_release, +}; + +/*-------Interface for qrc device ---------*/ +int qrc_register_device(struct qrc_dev *qdev, struct device *dev) +{ + int ret; + dev_t devt; + + if (!qdev) + return -ENOMEM; + + mutex_init(&qdev->mutex); + init_waitqueue_head(&qdev->r_wait); + init_waitqueue_head(&qdev->w_wait); + + //register cdev + qrc_class = class_create(THIS_MODULE, "qrc_class"); + if (IS_ERR(qrc_class)) { + pr_err("failed to allocate class\n"); + return PTR_ERR(qrc_class); + } + ret = alloc_chrdev_region(&qrc_devt, 0, 1, "qrc"); + if (ret < 0) { + pr_err("failed to allocate char device region\n"); + class_destroy(qrc_class); + return ret; + } + + devt = MKDEV(MAJOR(qrc_devt), 0); + + cdev_init(&qdev->cdev, &qrc_cdev_fops); + ret = qrc_control_gpio_init(qdev, dev->of_node); + + ret = cdev_add(&qdev->cdev, devt, 1); + if (ret) { + pr_err("qrc failed to add char device\n"); + return ret; + } + + qdev->dev = device_create(qrc_class, dev, devt, qdev, + "qrc"); + if (IS_ERR(qdev->dev)) { + ret = PTR_ERR(qdev->dev); + goto del_cdev; + } + + dev_info(dev, "qrc device registered\n"); + return 0; + +del_cdev: + cdev_del(&qdev->cdev); + return ret; +} + +void qrc_unregister(struct qrc_dev *qdev) +{ + device_destroy(qrc_class, qdev->dev->devt); + qrc_control_gpio_uninit(qdev); + dev_info(qdev->dev, "qrc drv unregistered\n"); +} diff --git a/drivers/misc/qrc/qrc_core.h b/drivers/misc/qrc/qrc_core.h new file mode 100644 index 000000000000..0196e0ed2f29 --- /dev/null +++ b/drivers/misc/qrc/qrc_core.h @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* driver/misc/qrc/qrc_core.h + * + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#ifndef _QRC_CORE_H +#define _QRC_CORE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#define QRC_NAME_SIZE 30 +#define QRC_INTERFACE_SIZE 30 +#define QRC_FIFO_SIZE 0x1000 + +struct qrc_dev; + +/* IOCTL commands */ +#define QRC_IOC_MAGIC 'q' + +/* Clear read fifo */ +#define QRC_FIFO_CLEAR _IO(QRC_IOC_MAGIC, 1) +/* Reboot QRC controller */ +#define QRC_REBOOT _IO(QRC_IOC_MAGIC, 2) +/* QRC boot from memory */ +#define QRC_BOOT_TO_MEM _IO(QRC_IOC_MAGIC, 3) +/* QRC boot from flash */ +#define QRC_BOOT_TO_FLASH _IO(QRC_IOC_MAGIC, 4) + + +enum qrcdev_state_t { + __STATE_IDLE, + __STATE_READING, + __STATE_WRITING, +}; + +enum qrc_interface { + UART = 0, + SPI, +}; + +enum qrcdev_tx { + __QRCDEV_TX_MIN = INT_MIN, /* make sure enum is signed (-1)*/ + QRCDEV_TX_OK = 0x00, /* driver took care of packet */ + QRCDEV_TX_BUSY = 0x10, /* driver tx path was busy*/ +}; + +struct qrc_device_stats { + unsigned long rx_bytes; + unsigned long tx_bytes; + unsigned long rx_errors; + unsigned long tx_errors; + unsigned long collisions; + unsigned long rx_length_errors; + unsigned long rx_over_errors; + unsigned long rx_fifo_errors; +}; + +struct qrc_device_ops { + int (*qrcops_init)(struct qrc_dev *dev); + void (*qrcops_uninit)(struct qrc_dev *dev); + int (*qrcops_open)(struct qrc_dev *dev); + int (*qrcops_close)(struct qrc_dev *dev); + int (*qrcops_setup)(struct qrc_dev *dev); + enum qrcdev_tx (*qrcops_xmit)(const char __user *buf, + size_t data_length, struct qrc_dev *dev); + int (*qrcops_receive)(struct qrc_dev *dev, + char __user *buf, size_t count); + int (*qrcops_data_status) + (struct qrc_dev *dev); + int (*qrcops_config)(struct qrc_dev *dev); + void (*qrcops_data_clean)(struct qrc_dev *dev); +}; + +/* qrc char device */ +struct qrc_dev { + struct qrc_device_stats stats; + /* qrc dev ops */ + struct qrc_device_ops *qrc_ops; + struct mutex mutex; + wait_queue_head_t r_wait; + wait_queue_head_t w_wait; + struct fasync_struct *async_queue; + struct cdev cdev; + struct device *dev; + void *data; + int qrc_boot0_gpio; + int qrc_reset_gpio; +}; + +/** + * struct qrcuart - The qrcuart device structure. + * @qrc_dev: This is robotic controller device structure. + * It include interface for qrcuart. + * @lock: spinlock for transmitting lock. + * @tx_work: Flushes transmit TX buffer. + * @serdev: Serial device bus structure. + * @qrc_rx_fifo: Qrcuart receive buffer. + * @tx_head: String head in XMIT queue. + * @tx_left: Bytes left in XMIT queue. + * @tx_buffer: XMIT buffer. + * This structure is used to define robotic controller uart device. + */ +struct qrcuart { + struct qrc_dev *qrc_dev; + spinlock_t lock; + struct work_struct tx_work; + struct serdev_device *serdev; + struct kfifo qrc_rx_fifo; + unsigned char *tx_head; + int tx_left; + unsigned char *tx_buffer; +}; + +struct qrcspi { + struct qrc_dev *qrc_dev; + spinlock_t lock; /* transmit lock */ +}; + +static inline void qrc_set_data(struct qrc_dev *dev, void *data) +{ + dev->data = data; +} +static inline void *qrc_get_data(const struct qrc_dev *dev) +{ + return dev->data; +} + +int qrc_register_device(struct qrc_dev *qdev, struct device *dev); +void qrc_unregister(struct qrc_dev *qdev); + +#endif + diff --git a/drivers/misc/qrc/qrc_uart.c b/drivers/misc/qrc/qrc_uart.c new file mode 100644 index 000000000000..af55e8c6acf7 --- /dev/null +++ b/drivers/misc/qrc/qrc_uart.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* driver/misc/qrc/qrc_uart.c + * + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qrc_core.h" + +#define QRC_RX_FIFO_SIZE 0x400 +#define QRC_TX_BUFF_SIZE 0x400 +#define QRCUART_DRV_NAME "qrcuart" +#define QRC_DRV_VERSION "0.1.0" + + +static int qrcuart_setup(struct qrc_dev *dev); + +static int +qrc_uart_receive(struct serdev_device *serdev, const unsigned char *data, + size_t count) +{ + struct qrcuart *qrc = serdev_device_get_drvdata(serdev); + struct qrc_dev *qrc_dev = qrc->qrc_dev; + int ret; + + /* check count */ + ret = kfifo_avail(&qrc->qrc_rx_fifo); + if (!ret) + return 0; + + if (count > ret) + count = ret; + + ret = kfifo_in(&qrc->qrc_rx_fifo, data, count); + if (!ret) + return 0; + + wake_up_interruptible(&qrc_dev->r_wait); + + return count; +} + +/* Write out any remaining transmit buffer. Scheduled when tty is writable */ +static void qrcuart_transmit(struct work_struct *work) +{ + struct qrcuart *qrc = container_of(work, struct qrcuart, tx_work); + int written; + + spin_lock_bh(&qrc->lock); + + if (qrc->tx_left <= 0) { + /* Now serial buffer is almost free & we can start + * transmission of another packet + */ + spin_unlock_bh(&qrc->lock); + return; + } + + written = serdev_device_write_buf(qrc->serdev, qrc->tx_head, + qrc->tx_left); + if (written > 0) { + qrc->tx_left -= written; + qrc->tx_head += written; + } + spin_unlock_bh(&qrc->lock); +} + +/* Called by the driver when there's room for more data. + * Schedule the transmit. + */ +static void qrc_uart_wakeup(struct serdev_device *serdev) +{ + struct qrcuart *qrc = serdev_device_get_drvdata(serdev); + + schedule_work(&qrc->tx_work); +} + +static struct serdev_device_ops qrc_serdev_ops = { + .receive_buf = qrc_uart_receive, + .write_wakeup = qrc_uart_wakeup, +}; + +/*----------------Interface to QRC core -----------------------------*/ + +static int qrcuart_open(struct qrc_dev *dev) +{ + return 0; +} + +static int qrcuart_close(struct qrc_dev *dev) +{ + struct qrcuart *qrc = qrc_get_data(dev); + + flush_work(&qrc->tx_work); + + spin_lock_bh(&qrc->lock); + qrc->tx_left = 0; + spin_unlock_bh(&qrc->lock); + + return 0; +} + +static int qrcuart_init(struct qrc_dev *dev) +{ + struct qrcuart *qrc = qrc_get_data(dev); + size_t len; + int ret; + + /* Finish setting up the device info. */ + len = QRC_TX_BUFF_SIZE; + qrc->tx_buffer = devm_kmalloc(&qrc->serdev->dev, len, GFP_KERNEL); + + if (!qrc->tx_buffer) + return -ENOMEM; + + qrc->tx_head = qrc->tx_buffer; + qrc->tx_left = 0; + + ret = kfifo_alloc(&qrc->qrc_rx_fifo, QRC_RX_FIFO_SIZE, + GFP_KERNEL); + if (ret) + return -ENOMEM; + + return 0; +} +static void qrcuart_uninit(struct qrc_dev *dev) +{ + struct qrcuart *qrc = qrc_get_data(dev); + + kfifo_free(&qrc->qrc_rx_fifo); +} + +/*put data from kfifo to qrc fifo */ +static int qrcuart_receive(struct qrc_dev *dev, char __user *buf, + size_t count) +{ + struct qrcuart *qrc = qrc_get_data(dev); + u32 fifo_len, trans_len; + + if (!kfifo_is_empty(&qrc->qrc_rx_fifo)) { + fifo_len = kfifo_len(&qrc->qrc_rx_fifo); + if (count > fifo_len) + count = fifo_len; + if (kfifo_to_user(&qrc->qrc_rx_fifo, + (void *)buf, count, &trans_len)) + return -EFAULT; + return trans_len; + } + return 0; +} + +static int qrcuart_data_status(struct qrc_dev *dev) +{ + struct qrcuart *qrc = qrc_get_data(dev); + + return kfifo_len(&qrc->qrc_rx_fifo); +} + +static void qrcuart_data_clean(struct qrc_dev *dev) +{ + struct qrcuart *qrc = qrc_get_data(dev); + + kfifo_reset(&qrc->qrc_rx_fifo); +} + + +static enum qrcdev_tx qrcuart_xmit(const char __user *buf, + size_t data_length, struct qrc_dev *dev) +{ + struct qrcuart *qrc = qrc_get_data(dev); + struct qrc_device_stats *n_stats = &dev->stats; + size_t written; + u8 *pos; + + WARN_ON(qrc->tx_left); + + pos = qrc->tx_buffer + qrc->tx_left; + if ((data_length + qrc->tx_left) > QRC_TX_BUFF_SIZE) { + pr_err("qrcuart transmit date overflow %d\n", data_length); + return __QRCDEV_TX_MIN; + } + + if (copy_from_user(pos, buf, data_length)) + return __QRCDEV_TX_MIN; + + pos += data_length; + + spin_lock(&qrc->lock); + + written = serdev_device_write_buf(qrc->serdev, qrc->tx_buffer, + pos - qrc->tx_buffer); + if (written > 0) { + qrc->tx_left = (pos - qrc->tx_buffer) - written; + qrc->tx_head = qrc->tx_buffer + written; + n_stats->tx_bytes += written; + } + + spin_unlock(&qrc->lock); + + return QRCDEV_TX_OK; +} + +static int qrcuart_config(struct qrc_dev *dev) +{ + //baudrate,wordlength ... config + return 0; +} + +static const struct qrc_device_ops qrcuart_qrc_ops = { + .qrcops_open = qrcuart_open, + .qrcops_close = qrcuart_close, + .qrcops_init = qrcuart_init, + .qrcops_uninit = qrcuart_uninit, + .qrcops_xmit = qrcuart_xmit, + .qrcops_receive = qrcuart_receive, + .qrcops_config = qrcuart_config, + .qrcops_setup = qrcuart_setup, + .qrcops_data_status = qrcuart_data_status, + .qrcops_data_clean = qrcuart_data_clean, +}; + +static int qrcuart_setup(struct qrc_dev *dev) +{ + dev->qrc_ops = &qrcuart_qrc_ops; + return 0; +} + +static int qrc_uart_probe(struct serdev_device *serdev) +{ + struct qrc_dev *qdev; + struct qrcuart *qrc; + const char *mac; + u32 speed = 115200; + int ret; + + qdev = kmalloc(sizeof(*qdev), GFP_KERNEL); + qrc = kmalloc(sizeof(*qrc), GFP_KERNEL); + if ((!qrc) || (!qdev)) { + pr_err("qrc_uart: Fail to retrieve private structure\n"); + goto free; + } + qrc_set_data(qdev, qrc); + + qrc->qrc_dev = qdev; + qrc->serdev = serdev; + spin_lock_init(&qrc->lock); + INIT_WORK(&qrc->tx_work, qrcuart_transmit); + qrcuart_setup(qdev); + ret = qrcuart_init(qdev); + if (ret) { + qrcuart_uninit(qdev); + pr_err("qrcuart: Fail to init qrc structure\n"); + return ret; + } + serdev_device_set_drvdata(serdev, qrc); + serdev_device_set_client_ops(serdev, &qrc_serdev_ops); + + ret = serdev_device_open(serdev); + if (ret) { + pr_err("qrcuart :Unable to open device\n"); + ret = -ENOMEM; + goto free; + } + + speed = serdev_device_set_baudrate(serdev, speed); + serdev_device_set_flow_control(serdev, false); + + ret = qrc_register_device(qdev, &serdev->dev); + + if (ret) { + pr_err("qrcuart: Unable to register qrc device %s\n"); + serdev_device_close(serdev); + cancel_work_sync(&qrc->tx_work); + goto free; + } + dev_info(&serdev->dev, "qrcuart drv probed\n"); + + return 0; + +free: + kfree(qdev); + kfree(qrc); + return ret; +} + +static void qrc_uart_remove(struct serdev_device *serdev) +{ + struct qrcuart *qrc = serdev_device_get_drvdata(serdev); + + serdev_device_close(serdev); + qrcuart_uninit(qrc->qrc_dev); + cancel_work_sync(&qrc->tx_work); + qrc_unregister(qrc->qrc_dev); + kfree(qrc->qrc_dev); + kfree(qrc); + dev_info(&serdev->dev, "qrcuart drv removed\n"); +} + +static const struct of_device_id qrc_uart_of_match[] = { + { + .compatible = "qcom,qrc-uart", + }, + {} +}; +MODULE_DEVICE_TABLE(of, qrc_of_match); + + +static struct serdev_device_driver qrc_uart_driver = { + .probe = qrc_uart_probe, + .remove = qrc_uart_remove, + .driver = { + .name = QRCUART_DRV_NAME, + .of_match_table = of_match_ptr(qrc_uart_of_match), + }, +}; + +module_serdev_device_driver(qrc_uart_driver); + +/**********************************************/ + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. QRC Uart Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/qseecom.c b/drivers/misc/qseecom.c index 57132988af63..5d2c707b27cc 100644 --- a/drivers/misc/qseecom.c +++ b/drivers/misc/qseecom.c @@ -2,7 +2,7 @@ /* * QTI Secure Execution Environment Communicator (QSEECOM) driver * - * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "QSEECOM: %s: " fmt, __func__ @@ -704,7 +704,7 @@ static int qseecom_scm_call2(uint32_t svc_id, uint32_t tz_cmd_id, qseecom.smcinvoke_support = true; smc_id = TZ_OS_REGISTER_LISTENER_SMCINVOKE_ID; ret = __qseecom_scm_call2_locked(smc_id, &desc); - if (ret == -EIO) { + if (ret == -EOPNOTSUPP) { /* smcinvoke is not supported */ qseecom.smcinvoke_support = false; smc_id = TZ_OS_REGISTER_LISTENER_ID; @@ -2624,6 +2624,11 @@ static int __qseecom_reentrancy_process_incomplete_cmd( pr_warn("get cback req app_id = %d, resp->data = %d\n", data->client.app_id, resp->data); resp->resp_type = SMCINVOKE_RESULT_INBOUND_REQ_NEEDED; + /* We are here because scm call sent to TZ has requested + * for another callback request. This call has been a + * success and hence setting result = 0 + */ + resp->result = 0; break; default: pr_err("fail:resp res= %d,app_id = %d,lstr = %d\n", @@ -3705,8 +3710,8 @@ static int __qseecom_send_cmd(struct qseecom_dev_handle *data, (uint32_t)(__qseecom_uvirt_to_kphys( data, (uintptr_t)req->resp_buf)); } else { - send_data_req.req_ptr = (uintptr_t)req->cmd_req_buf; - send_data_req.rsp_ptr = (uintptr_t)req->resp_buf; + send_data_req.req_ptr = (uint32_t)req->cmd_req_buf; + send_data_req.rsp_ptr = (uint32_t)req->resp_buf; } send_data_req.req_len = req->cmd_req_len; @@ -3839,54 +3844,60 @@ static int qseecom_send_cmd(struct qseecom_dev_handle *data, void __user *argp) int __boundary_checks_offset(struct qseecom_send_modfd_cmd_req *req, struct qseecom_send_modfd_listener_resp *lstnr_resp, - struct qseecom_dev_handle *data, int i) + struct qseecom_dev_handle *data, int i, size_t size) { + char *curr_field = NULL; + char *temp_field = NULL; + int j = 0; if ((data->type != QSEECOM_LISTENER_SERVICE) && (req->ifd_data[i].fd > 0)) { - if ((req->cmd_req_len < sizeof(uint32_t)) || + if ((req->cmd_req_len < size) || (req->ifd_data[i].cmd_buf_offset > - req->cmd_req_len - sizeof(uint32_t))) { + req->cmd_req_len - size)) { pr_err("Invalid offset (req len) 0x%x\n", req->ifd_data[i].cmd_buf_offset); return -EINVAL; } - } else if ((data->type == QSEECOM_LISTENER_SERVICE) && - (lstnr_resp->ifd_data[i].fd > 0)) { - if ((lstnr_resp->resp_len < sizeof(uint32_t)) || - (lstnr_resp->ifd_data[i].cmd_buf_offset > - lstnr_resp->resp_len - sizeof(uint32_t))) { - pr_err("Invalid offset (lstnr resp len) 0x%x\n", - lstnr_resp->ifd_data[i].cmd_buf_offset); - return -EINVAL; - } - } - return 0; -} -static int __boundary_checks_offset_64(struct qseecom_send_modfd_cmd_req *req, - struct qseecom_send_modfd_listener_resp *lstnr_resp, - struct qseecom_dev_handle *data, int i) -{ - - if ((data->type != QSEECOM_LISTENER_SERVICE) && - (req->ifd_data[i].fd > 0)) { - if ((req->cmd_req_len < sizeof(uint64_t)) || - (req->ifd_data[i].cmd_buf_offset > - req->cmd_req_len - sizeof(uint64_t))) { - pr_err("Invalid offset (req len) 0x%x\n", + curr_field = (char *) (req->cmd_req_buf + req->ifd_data[i].cmd_buf_offset); - return -EINVAL; + for (j = 0; j < MAX_ION_FD; j++) { + if ((req->ifd_data[j].fd > 0) && i != j) { + temp_field = (char *) (req->cmd_req_buf + + req->ifd_data[j].cmd_buf_offset); + if (temp_field >= curr_field && temp_field < + (curr_field + size)) { + pr_err("Invalid field offset 0x%x\n", + req->ifd_data[i].cmd_buf_offset); + return -EINVAL; + } + } } } else if ((data->type == QSEECOM_LISTENER_SERVICE) && (lstnr_resp->ifd_data[i].fd > 0)) { - if ((lstnr_resp->resp_len < sizeof(uint64_t)) || + if ((lstnr_resp->resp_len < size) || (lstnr_resp->ifd_data[i].cmd_buf_offset > - lstnr_resp->resp_len - sizeof(uint64_t))) { + lstnr_resp->resp_len - size)) { pr_err("Invalid offset (lstnr resp len) 0x%x\n", lstnr_resp->ifd_data[i].cmd_buf_offset); return -EINVAL; } + + curr_field = (char *) (lstnr_resp->resp_buf_ptr + + lstnr_resp->ifd_data[i].cmd_buf_offset); + for (j = 0; j < MAX_ION_FD; j++) { + if ((lstnr_resp->ifd_data[j].fd > 0) && i != j) { + temp_field = (char *) lstnr_resp->resp_buf_ptr + + lstnr_resp->ifd_data[j].cmd_buf_offset; + if (temp_field >= curr_field && temp_field < + (curr_field + size)) { + pr_err("Invalid lstnr field offset 0x%x\n", + lstnr_resp->ifd_data[i].cmd_buf_offset); + return -EINVAL; + } + } + } } return 0; } @@ -3961,8 +3972,10 @@ static int __qseecom_update_cmd_buf(void *msg, bool cleanup, if (sg_ptr->nents == 1) { uint32_t *update; - if (__boundary_checks_offset(req, lstnr_resp, data, i)) + if (__boundary_checks_offset(req, lstnr_resp, data, i, + sizeof(uint32_t))) goto err; + if ((data->type == QSEECOM_CLIENT_APP && (data->client.app_arch == ELFCLASS32 || data->client.app_arch == ELFCLASS64)) || @@ -3993,30 +4006,10 @@ static int __qseecom_update_cmd_buf(void *msg, bool cleanup, struct qseecom_sg_entry *update; int j = 0; - if ((data->type != QSEECOM_LISTENER_SERVICE) && - (req->ifd_data[i].fd > 0)) { + if (__boundary_checks_offset(req, lstnr_resp, data, i, + (SG_ENTRY_SZ * sg_ptr->nents))) + goto err; - if ((req->cmd_req_len < - SG_ENTRY_SZ * sg_ptr->nents) || - (req->ifd_data[i].cmd_buf_offset > - (req->cmd_req_len - - SG_ENTRY_SZ * sg_ptr->nents))) { - pr_err("Invalid offset = 0x%x\n", - req->ifd_data[i].cmd_buf_offset); - goto err; - } - - } else if ((data->type == QSEECOM_LISTENER_SERVICE) && - (lstnr_resp->ifd_data[i].fd > 0)) { - - if ((lstnr_resp->resp_len < - SG_ENTRY_SZ * sg_ptr->nents) || - (lstnr_resp->ifd_data[i].cmd_buf_offset > - (lstnr_resp->resp_len - - SG_ENTRY_SZ * sg_ptr->nents))) { - goto err; - } - } if ((data->type == QSEECOM_CLIENT_APP && (data->client.app_arch == ELFCLASS32 || data->client.app_arch == ELFCLASS64)) || @@ -4236,9 +4229,10 @@ static int __qseecom_update_cmd_buf_64(void *msg, bool cleanup, if (sg_ptr->nents == 1) { uint64_t *update_64bit; - if (__boundary_checks_offset_64(req, lstnr_resp, - data, i)) + if (__boundary_checks_offset(req, lstnr_resp, data, i, + sizeof(uint64_t))) goto err; + /* 64bit app uses 64bit address */ update_64bit = (uint64_t *) field; *update_64bit = cleanup ? 0 : @@ -4248,30 +4242,9 @@ static int __qseecom_update_cmd_buf_64(void *msg, bool cleanup, struct qseecom_sg_entry_64bit *update_64bit; int j = 0; - if ((data->type != QSEECOM_LISTENER_SERVICE) && - (req->ifd_data[i].fd > 0)) { - - if ((req->cmd_req_len < - SG_ENTRY_SZ_64BIT * sg_ptr->nents) || - (req->ifd_data[i].cmd_buf_offset > - (req->cmd_req_len - - SG_ENTRY_SZ_64BIT * sg_ptr->nents))) { - pr_err("Invalid offset = 0x%x\n", - req->ifd_data[i].cmd_buf_offset); - goto err; - } - - } else if ((data->type == QSEECOM_LISTENER_SERVICE) && - (lstnr_resp->ifd_data[i].fd > 0)) { - - if ((lstnr_resp->resp_len < - SG_ENTRY_SZ_64BIT * sg_ptr->nents) || - (lstnr_resp->ifd_data[i].cmd_buf_offset > - (lstnr_resp->resp_len - - SG_ENTRY_SZ_64BIT * sg_ptr->nents))) { - goto err; - } - } + if (__boundary_checks_offset(req, lstnr_resp, data, i, + (SG_ENTRY_SZ_64BIT * sg_ptr->nents))) + goto err; /* 64bit app uses 64bit address */ update_64bit = (struct qseecom_sg_entry_64bit *)field; for (j = 0; j < sg_ptr->nents; j++) { @@ -4387,6 +4360,11 @@ static int __qseecom_send_modfd_cmd(struct qseecom_dev_handle *data, /* Allocate kernel buffer for request and response*/ ret = __qseecom_alloc_coherent_buf(req.cmd_req_len + req.resp_len, &va, &pa); + if (ret) { + pr_err("Failed to allocate coherent buf, ret %d\n", ret); + return ret; + } + req.cmd_req_buf = va; send_cmd_req.cmd_req_buf = (void *)pa; @@ -5367,6 +5345,13 @@ int qseecom_process_listener_from_smcinvoke(struct scm_desc *desc) return -EINVAL; } + /* + * smcinvoke expects result in scm call resp.ret[1] and type in ret[0], + * while qseecom expects result in ret[0] and type in ret[1]. + * To simplify API interface and code changes in smcinvoke, here + * internally switch result and resp_type to let qseecom work with + * smcinvoke and upstream scm driver protocol. + */ resp.result = desc->ret[0]; /*req_cmd*/ resp.resp_type = desc->ret[1]; /*incomplete:unused;blocked:session_id*/ resp.data = desc->ret[2]; /*listener_id*/ @@ -5387,8 +5372,8 @@ int qseecom_process_listener_from_smcinvoke(struct scm_desc *desc) pr_err("Failed on cmd %d for lsnr %d session %d, ret = %d\n", (int)desc->ret[0], (int)desc->ret[2], (int)desc->ret[1], ret); - desc->ret[0] = resp.result; - desc->ret[1] = resp.resp_type; + desc->ret[0] = resp.resp_type; + desc->ret[1] = resp.result; desc->ret[2] = resp.data; return ret; } @@ -9716,7 +9701,8 @@ static int qseecom_suspend(struct platform_device *pdev, pm_message_t state) mutex_unlock(&clk_access_lock); mutex_unlock(&qsee_bw_mutex); - cancel_work_sync(&qseecom.bw_inactive_req_ws); + if (qseecom.support_bus_scaling) + cancel_work_sync(&qseecom.bw_inactive_req_ws); return 0; } diff --git a/drivers/misc/wigig_sensing.c b/drivers/misc/wigig_sensing.c index b8d4d6f084f5..f9bb53dbeb57 100644 --- a/drivers/misc/wigig_sensing.c +++ b/drivers/misc/wigig_sensing.c @@ -1371,11 +1371,29 @@ static irqreturn_t wigig_sensing_dri_isr_thread(int irq, void *cookie) if (spi_status.b.int_data_ready) { SPI_STATS_MEAS_START(ctx, SPI_STATS_MEAS_DATA_READY); pr_debug("DATA READY INTERRUPT\n"); + + /* + * Data ready interrupt is received when waiting for deep sleep + * exit, break and exit deep sleep + */ + if (ctx->stm.waiting_for_deep_sleep_exit) { + additional_inb_command = ctx->inb_cmd; + memset(&ctx->inb_cmd, 0, sizeof(ctx->inb_cmd)); + + pr_err("Received data ready interrupt when waiting for deep sleep exit, treating it so\n"); + ctx->stm.waiting_for_deep_sleep_exit = false; + ctx->stm.waiting_for_deep_sleep_exit_first_pass = false; + spi_status.v &= ~INT_DEEP_SLEEP_EXIT; + goto finish_data_ready; + } + if (!ctx->stm.change_mode_in_progress) wigig_sensing_chip_data_ready( ctx, spi_status.b.fill_level, ctx->stm.burst_size); else pr_debug("Change mode in progress, aborting data processing\n"); + +finish_data_ready: SPI_STATS_MEAS_STOP(ctx, SPI_STATS_MEAS_DATA_READY); spi_status.v &= ~INT_DATA_READY; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 177632035ead..b1685d852b4e 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2713,10 +2713,11 @@ int mmc_resume_bus(struct mmc_host *host) } if (host->card->ext_csd.cmdq_en && !host->cqe_enabled) { err = host->cqe_ops->cqe_enable(host, host->card); - host->cqe_enabled = true; if (err) pr_err("%s: %s: cqe enable failed: %d\n", mmc_hostname(host), __func__, err); + else + host->cqe_enabled = true; } } diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index 6fde68aa13a4..0a43cf65e10a 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -42,6 +42,7 @@ source "drivers/net/ethernet/chelsio/Kconfig" source "drivers/net/ethernet/cirrus/Kconfig" source "drivers/net/ethernet/cisco/Kconfig" source "drivers/net/ethernet/cortina/Kconfig" +source "drivers/net/ethernet/qcom/Kconfig" config CX_ECAT tristate "Beckhoff CX5020 EtherCAT master support" diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index b45d5f626b59..fc4a8e239d53 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -95,3 +95,4 @@ obj-$(CONFIG_NET_VENDOR_WIZNET) += wiznet/ obj-$(CONFIG_NET_VENDOR_XILINX) += xilinx/ obj-$(CONFIG_NET_VENDOR_XIRCOM) += xircom/ obj-$(CONFIG_NET_VENDOR_SYNOPSYS) += synopsys/ +obj-$(CONFIG_ARCH_QCOM) += qcom/ diff --git a/drivers/net/ethernet/qcom/Kconfig b/drivers/net/ethernet/qcom/Kconfig new file mode 100644 index 000000000000..0d527cbbd9f2 --- /dev/null +++ b/drivers/net/ethernet/qcom/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# msm network device configuration +# +config MSM_RMNET_BAM + bool "RMNET BAM Driver" + depends on MSM_BAM_DMUX + default n + help + Implements RMNET over BAM interface. + RMNET provides a virtual ethernet interface + for routing IP packets within the MSM using + BAM as a physical transport. + +config MSM_RMNET_DEBUG + bool "MSM RMNET debug interface" + depends on MSM_RMNET + default n + diff --git a/drivers/net/ethernet/qcom/Makefile b/drivers/net/ethernet/qcom/Makefile new file mode 100644 index 000000000000..3b53117172e5 --- /dev/null +++ b/drivers/net/ethernet/qcom/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the msm networking support. +# +obj-$(CONFIG_MSM_RMNET_BAM) += msm_rmnet_bam.o diff --git a/drivers/net/ethernet/qcom/msm_rmnet_bam.c b/drivers/net/ethernet/qcom/msm_rmnet_bam.c new file mode 100644 index 000000000000..8b2d72af9311 --- /dev/null +++ b/drivers/net/ethernet/qcom/msm_rmnet_bam.c @@ -0,0 +1,1002 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2011-2016, 2019, 2021, The Linux Foundation. All rights reserved. + * + */ + +/* RMNET BAM Module. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +/* Debug message support */ +static int msm_rmnet_bam_debug_mask; +static ssize_t debug_enable_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", msm_rmnet_bam_debug_mask); +} + +static ssize_t debug_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + msm_rmnet_bam_debug_mask = val; + + return count; +} +static DEVICE_ATTR_RW(debug_enable); + +static unsigned long msm_rmnet_bam_headroom_check_failure; +static ssize_t +msm_rmnet_bam_headroom_check_failure_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", + msm_rmnet_bam_headroom_check_failure); +} +static DEVICE_ATTR_RO(msm_rmnet_bam_headroom_check_failure); + +/* Packet threshold. */ +static unsigned int pkt_threshold = 1; +static ssize_t pkt_threshold_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", pkt_threshold); +} + +static ssize_t pkt_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + pkt_threshold = val; + + return count; +} +static DEVICE_ATTR_RW(pkt_threshold); + +#define DEBUG_MASK_LVL0 BIT(0) +#define DEBUG_MASK_LVL1 BIT(1) +#define DEBUG_MASK_LVL2 BIT(2) + +#define DBG(m, x...) do { \ + if (msm_rmnet_bam_debug_mask & (m)) \ + pr_info(x); \ +} while (0) +#define DBG0(x...) DBG(DEBUG_MASK_LVL0, x) +#define DBG1(x...) DBG(DEBUG_MASK_LVL1, x) +#define DBG2(x...) DBG(DEBUG_MASK_LVL2, x) + +/* allow larger frames */ +#define RMNET_DATA_LEN 2000 + +#define RMNET_BAM_DRIVER_NAME "rmnet_bam" + +#define DEVICE_ID_INVALID -1 + +#define DEVICE_INACTIVE 2 +#define DEVICE_ACTIVE 1 +#define DEVICE_UNINITIALIZED 0 + +#define HEADROOM_FOR_BAM 8 /* for mux header */ +#define HEADROOM_FOR_QOS 8 +#define TAILROOM 8 /* for padding by mux layer */ + +struct rmnet_private { + struct net_device_stats stats; + u32 ch_id; +#ifdef CONFIG_MSM_RMNET_DEBUG + ktime_t last_packet; + unsigned long wakeups_xmit; + unsigned long wakeups_rcv; + unsigned long timeout_us; +#endif + struct sk_buff *waiting_for_ul_skb; + + /* Lock to change operation mode */ + spinlock_t lock; + + /* Lock to check low water mark */ + spinlock_t tx_queue_lock; + struct tasklet_struct tsklt; + + /* IOCTL specified mode (protocol, QoS header) */ + u32 operation_mode; + u8 device_up; + u8 in_reset; +}; + +struct rmnet_free_bam_work { + struct work_struct work; + u32 ch_id; +}; + +#ifdef CONFIG_MSM_RMNET_DEBUG +static unsigned long timeout_us; + +/* Returns 1 if packet caused rmnet to wakeup, 0 otherwise. */ +static int rmnet_cause_wakeup(struct rmnet_private *p) +{ + int ret = 0; + ktime_t now; + + if (p->timeout_us == 0) /* Check if disabled */ + return 0; + + /* Use real (wall) time. */ + now = ktime_get_real(); + + if (ktime_us_delta(now, p->last_packet) > p->timeout_us) + ret = 1; + + p->last_packet = now; + return ret; +} + +static ssize_t wakeups_xmit_show(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + + return scnprintf(buf, PAGE_SIZE, "%lu\n", p->wakeups_xmit); +} + +DEVICE_ATTR_RO(wakeups_xmit); + +static ssize_t wakeups_rcv_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + + return scnprintf(buf, PAGE_SIZE, "%lu\n", p->wakeups_rcv); +} + +DEVICE_ATTR_RO(wakeups_rcv); + +/* Set timeout in us. */ +static ssize_t timeout_store(struct device *d, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + int ret; + + ret = kstrtoul(buf, 10, &timeout_us); + if (!ret) + p->timeout_us = timeout_us; + else + n = 0; + return n; +} + +static ssize_t timeout_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + + p = netdev_priv(to_net_dev(d)); + return scnprintf(buf, PAGE_SIZE, "%lu\n", timeout_us); +} + +DEVICE_ATTR_RW(timeout); +#endif + +/* Forward declaration */ +static int rmnet_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd); +static struct platform_driver bam_rmnet_drivers[BAM_DMUX_NUM_CHANNELS]; + +static struct net_device *netdevs[BAM_DMUX_NUM_CHANNELS]; + +static __be16 rmnet_ip_type_trans(struct sk_buff *skb, struct net_device *dev) +{ + __be16 protocol = 0; + + skb->dev = dev; + + /* Determine L3 protocol */ + switch (skb->data[0] & 0xf0) { + case 0x40: + protocol = htons(ETH_P_IP); + break; + case 0x60: + protocol = htons(ETH_P_IPV6); + break; + default: + protocol = htons(ETH_P_MAP); + } + return protocol; +} + +static int count_this_packet(void *_hdr, int len) +{ + struct ethhdr *hdr = _hdr; + + if (len >= ETH_HLEN && hdr->h_proto == htons(ETH_P_ARP)) + return 0; + + return 1; +} + +/* Rx Callback, Called in Work Queue context */ +static void bam_recv_notify(void *dev, struct sk_buff *skb) +{ + struct rmnet_private *p = netdev_priv(dev); + unsigned long flags; + u32 opmode; + + if (skb) { + skb->dev = dev; + /* Handle Rx frame format */ + spin_lock_irqsave(&p->lock, flags); + opmode = p->operation_mode; + spin_unlock_irqrestore(&p->lock, flags); + + if (RMNET_IS_MODE_IP(opmode)) { + /* Driver in IP mode */ + skb->protocol = rmnet_ip_type_trans(skb, dev); + } else { + /* Driver in Ethernet mode */ + skb->protocol = eth_type_trans(skb, dev); + } + if (RMNET_IS_MODE_IP(opmode) || + count_this_packet(skb->data, skb->len)) { +#ifdef CONFIG_MSM_RMNET_DEBUG + p->wakeups_rcv += rmnet_cause_wakeup(p); +#endif + p->stats.rx_packets++; + p->stats.rx_bytes += skb->len; + } + DBG1("[%s] Rx packet #%lu len=%d\n", + ((struct net_device *)dev)->name, + p->stats.rx_packets, skb->len); + + /* Deliver to network stack */ + if (pkt_threshold == 1) { + netif_rx_ni(skb); + } else { + /* For every nth packet, use netif_rx_ni(). */ + if (p->stats.rx_packets % pkt_threshold == 0) + netif_rx_ni(skb); + else + netif_rx(skb); + } + } else { + pr_err("[%s] %s: No skb received\n", + ((struct net_device *)dev)->name, __func__); + } +} + +static struct sk_buff *_rmnet_add_headroom(struct sk_buff **skb, + struct net_device *dev) +{ + struct sk_buff *skbn; + + if (skb_headroom(*skb) < dev->needed_headroom) { + msm_rmnet_bam_headroom_check_failure++; + skbn = skb_realloc_headroom(*skb, dev->needed_headroom); + kfree_skb(*skb); + *skb = skbn; + } else { + skbn = *skb; + } + + return skbn; +} + +static int _rmnet_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + int bam_ret; + struct QMI_QOS_HDR_S *qmih; + u32 opmode; + unsigned long flags; + + if (unlikely(!_rmnet_add_headroom(&skb, dev))) { + dev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + /* For QoS mode, prepend QMI header and assign flow ID from skb->mark */ + spin_lock_irqsave(&p->lock, flags); + opmode = p->operation_mode; + spin_unlock_irqrestore(&p->lock, flags); + + if (RMNET_IS_MODE_QOS(opmode)) { + qmih = (struct QMI_QOS_HDR_S *) + skb_push(skb, sizeof(struct QMI_QOS_HDR_S)); + qmih->version = 1; + qmih->flags = 0; + qmih->flow_id = skb->mark; + } + + /* if write() succeeds, skb access is unsafe in this process */ + bam_ret = msm_bam_dmux_write(p->ch_id, skb); + + if (bam_ret != 0 && bam_ret != -EAGAIN && bam_ret != -EFAULT) { + pr_err("[%s] %s: write returned error %d\n", + dev->name, __func__, bam_ret); + if (RMNET_IS_MODE_QOS(opmode)) + skb_pull(skb, sizeof(struct QMI_QOS_HDR_S)); + return -EPERM; + } + + return bam_ret; +} + +static void bam_write_done(void *dev, struct sk_buff *skb) +{ + struct rmnet_private *p = netdev_priv(dev); + u32 opmode = p->operation_mode; + unsigned long flags; + + DBG1("%s: write complete\n", __func__); + if (RMNET_IS_MODE_IP(opmode) || + count_this_packet(skb->data, skb->len)) { + p->stats.tx_packets++; + p->stats.tx_bytes += skb->len; +#ifdef CONFIG_MSM_RMNET_DEBUG + p->wakeups_xmit += rmnet_cause_wakeup(p); +#endif + } + DBG1("[%s] Tx packet #%lu len=%d mark=0x%x\n", + ((struct net_device *)(dev))->name, p->stats.tx_packets, + skb->len, skb->mark); + dev_kfree_skb_any(skb); + + spin_lock_irqsave(&p->tx_queue_lock, flags); + if (netif_queue_stopped(dev) && + msm_bam_dmux_is_ch_low(p->ch_id)) { + DBG0("%s: Low WM hit, waking queue=%p\n", + __func__, skb); + netif_wake_queue(dev); + } + spin_unlock_irqrestore(&p->tx_queue_lock, flags); +} + +static void bam_notify(void *dev, int event, unsigned long data) +{ + struct rmnet_private *p = netdev_priv(dev); + unsigned long flags; + + switch (event) { + case BAM_DMUX_RECEIVE: + bam_recv_notify(dev, (struct sk_buff *)(data)); + break; + case BAM_DMUX_WRITE_DONE: + bam_write_done(dev, (struct sk_buff *)(data)); + break; + case BAM_DMUX_UL_CONNECTED: + spin_lock_irqsave(&p->lock, flags); + if (p->waiting_for_ul_skb) { + struct sk_buff *skb; + int ret; + + skb = p->waiting_for_ul_skb; + p->waiting_for_ul_skb = NULL; + spin_unlock_irqrestore(&p->lock, flags); + ret = _rmnet_xmit(skb, dev); + if (ret) { + pr_err("%s: error %d dropping delayed TX SKB %p\n", + __func__, ret, skb); + dev_kfree_skb_any(skb); + } + netif_wake_queue(dev); + } else { + spin_unlock_irqrestore(&p->lock, flags); + } + break; + case BAM_DMUX_UL_DISCONNECTED: + break; + } +} + +static int __rmnet_open(struct net_device *dev) +{ + int r; + struct rmnet_private *p = netdev_priv(dev); + + DBG0("[%s] %s\n", dev->name, __func__); + + if (p->device_up == DEVICE_UNINITIALIZED) { + r = msm_bam_dmux_open(p->ch_id, dev, bam_notify); + if (r < 0) { + DBG0("%s: ch=%d failed with rc %d\n", + __func__, p->ch_id, r); + return -ENODEV; + } + } + + p->device_up = DEVICE_ACTIVE; + return 0; +} + +static int rmnet_open(struct net_device *dev) +{ + int rc = 0; + + DBG0("[%s] %s\n", dev->name, __func__); + + rc = __rmnet_open(dev); + + if (rc == 0) + netif_start_queue(dev); + + return rc; +} + +static int __rmnet_close(struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + int rc = 0; + + if (p->device_up == DEVICE_ACTIVE) { + /* do not close rmnet port once up, this causes + * remote side to hang if tried to open again + */ + p->device_up = DEVICE_INACTIVE; + return rc; + } else { + return -EBADF; + } +} + +static int rmnet_stop(struct net_device *dev) +{ + DBG0("[%s] %s\n", dev->name, __func__); + + __rmnet_close(dev); + netif_stop_queue(dev); + + return 0; +} + +static int rmnet_change_mtu(struct net_device *dev, int new_mtu) +{ + if (0 > new_mtu || RMNET_DATA_LEN < new_mtu) + return -EINVAL; + + DBG0("[%s] MTU change: old=%d new=%d\n", + dev->name, dev->mtu, new_mtu); + dev->mtu = new_mtu; + + return 0; +} + +static int rmnet_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + unsigned long flags; + int awake; + int ret = 0; + + if (netif_queue_stopped(dev)) { + pr_err("[%s]fatal: %s called when netif_queue is stopped\n", + dev->name, __func__); + return NETDEV_TX_BUSY; + } + + spin_lock_irqsave(&p->lock, flags); + awake = msm_bam_dmux_ul_power_vote(); + if (!awake) { + /* send SKB once wakeup is complete */ + netif_stop_queue(dev); + p->waiting_for_ul_skb = skb; + spin_unlock_irqrestore(&p->lock, flags); + ret = 0; + goto exit; + } + spin_unlock_irqrestore(&p->lock, flags); + + ret = _rmnet_xmit(skb, dev); + if (ret == -EPERM) { + ret = NETDEV_TX_BUSY; + goto exit; + } + + /* Detected SSR a bit early. shut some things down now, and leave + * the rest to the main ssr handling code when that happens later + */ + if (ret == -EFAULT) { + netif_carrier_off(dev); + dev_kfree_skb_any(skb); + ret = 0; + goto exit; + } + + if (ret == -EAGAIN) { + /* This should not happen + * EAGAIN means we attempted to overflow the high watermark + * Clearly the queue is not stopped like it should be, so + * stop it and return BUSY to the TCP/IP framework. It will + * retry this packet with the queue is restarted which happens + * in the write_done callback when the low watermark is hit. + */ + netif_stop_queue(dev); + ret = NETDEV_TX_BUSY; + goto exit; + } + + spin_lock_irqsave(&p->tx_queue_lock, flags); + if (msm_bam_dmux_is_ch_full(p->ch_id)) { + netif_stop_queue(dev); + DBG0("%s: High WM hit, stopping queue=%p\n", __func__, skb); + } + spin_unlock_irqrestore(&p->tx_queue_lock, flags); + +exit: + msm_bam_dmux_ul_power_unvote(); + return ret; +} + +static struct net_device_stats *rmnet_get_stats(struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + + return &p->stats; +} + +static void rmnet_tx_timeout(struct net_device *dev) +{ + pr_warn("[%s] %s\n", dev->name, __func__); +} + +static const struct net_device_ops rmnet_ops_ether = { + .ndo_open = rmnet_open, + .ndo_stop = rmnet_stop, + .ndo_start_xmit = rmnet_xmit, + .ndo_get_stats = rmnet_get_stats, + .ndo_tx_timeout = rmnet_tx_timeout, + .ndo_do_ioctl = rmnet_ioctl, + .ndo_change_mtu = rmnet_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static const struct net_device_ops rmnet_ops_ip = { + .ndo_open = rmnet_open, + .ndo_stop = rmnet_stop, + .ndo_start_xmit = rmnet_xmit, + .ndo_get_stats = rmnet_get_stats, + .ndo_tx_timeout = rmnet_tx_timeout, + .ndo_do_ioctl = rmnet_ioctl, + .ndo_change_mtu = rmnet_change_mtu, + .ndo_set_mac_address = 0, + .ndo_validate_addr = 0, +}; + +static void _rmnet_free_bam_later(struct work_struct *work) +{ + struct rmnet_free_bam_work *fwork; + + fwork = container_of(work, struct rmnet_free_bam_work, work); + + DBG0("%s: unregister_netdev, done", __func__); + + if (bam_rmnet_drivers[fwork->ch_id].remove) { + platform_driver_unregister(&bam_rmnet_drivers[fwork->ch_id]); + bam_rmnet_drivers[fwork->ch_id].remove = NULL; + } + + DBG0("%s: free_netdev, done", __func__); + + kfree(work); +} + +static int rmnet_ioctl_extended(struct net_device *dev, struct ifreq *ifr) +{ + struct rmnet_ioctl_extended_s ext_cmd; + int rc = 0; + struct rmnet_private *p = netdev_priv(dev); + struct rmnet_free_bam_work *work; + + rc = copy_from_user(&ext_cmd, ifr->ifr_ifru.ifru_data, + sizeof(ext_cmd)); + + if (rc) { + pr_err("%s: copy_from_user failed ,error %d\n", __func__, rc); + return rc; + } + + switch (ext_cmd.extended_ioctl) { + case RMNET_IOCTL_SET_MRU: + /* Transport MRU is fixed, so do nothing */ + break; + case RMNET_IOCTL_GET_EPID: + ext_cmd.u.data = p->ch_id; + break; + case RMNET_IOCTL_GET_SUPPORTED_FEATURES: + ext_cmd.u.data = 0; + break; + case RMNET_IOCTL_GET_DRIVER_NAME: + strlcpy(ext_cmd.u.if_name, RMNET_BAM_DRIVER_NAME, + sizeof(ext_cmd.u.if_name)); + break; + case RMNET_IOCTL_DEREGISTER_DEV: + work = kmalloc(sizeof(*work), GFP_KERNEL); + if (!work) + break; + INIT_WORK(&work->work, _rmnet_free_bam_later); + + work->ch_id = p->ch_id; + schedule_work(&work->work); + break; + default: + rc = -EINVAL; + break; + } + + rc = copy_to_user(ifr->ifr_ifru.ifru_data, &ext_cmd, sizeof(ext_cmd)); + + if (rc) + pr_err("%s: copy_to_user failed, error %d\n", __func__, rc); + + return rc; +} + +static int rmnet_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct rmnet_private *p = netdev_priv(dev); + u32 old_opmode = p->operation_mode; + unsigned long flags; + int prev_mtu = dev->mtu; + int rc = 0; + struct rmnet_ioctl_data_s ioctl_data; + + /* Process IOCTL command */ + switch (cmd) { + case RMNET_IOCTL_SET_LLP_ETHERNET: /* Set Ethernet protocol */ + /* Perform Ethernet config only if in IP mode currently*/ + if (p->operation_mode & RMNET_MODE_LLP_IP) { + ether_setup(dev); + random_ether_addr(dev->dev_addr); + dev->mtu = prev_mtu; + + dev->netdev_ops = &rmnet_ops_ether; + spin_lock_irqsave(&p->lock, flags); + p->operation_mode &= ~RMNET_MODE_LLP_IP; + p->operation_mode |= RMNET_MODE_LLP_ETH; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] %s: set Ethernet protocol mode\n", + dev->name, __func__); + } + break; + + case RMNET_IOCTL_SET_LLP_IP: /* Set RAWIP protocol */ + /* Perform IP config only if in Ethernet mode currently*/ + if (p->operation_mode & RMNET_MODE_LLP_ETH) { + /* Undo config done in ether_setup() */ + dev->header_ops = 0; /* No header */ + dev->type = ARPHRD_RAWIP; + dev->hard_header_len = 0; + dev->mtu = prev_mtu; + dev->addr_len = 0; + dev->flags &= ~(IFF_BROADCAST | + IFF_MULTICAST); + + dev->needed_headroom = HEADROOM_FOR_BAM + + HEADROOM_FOR_QOS; + dev->needed_tailroom = TAILROOM; + dev->netdev_ops = &rmnet_ops_ip; + spin_lock_irqsave(&p->lock, flags); + p->operation_mode &= ~RMNET_MODE_LLP_ETH; + p->operation_mode |= RMNET_MODE_LLP_IP; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] %s: set IP protocol mode\n", + dev->name, __func__); + } + break; + + case RMNET_IOCTL_GET_LLP: /* Get link protocol state */ + ioctl_data.u.operation_mode = (p->operation_mode & + (RMNET_MODE_LLP_ETH | RMNET_MODE_LLP_IP)); + if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data, + sizeof(struct rmnet_ioctl_data_s))) + rc = -EFAULT; + break; + + case RMNET_IOCTL_SET_QOS_ENABLE: /* Set QoS header enabled */ + spin_lock_irqsave(&p->lock, flags); + p->operation_mode |= RMNET_MODE_QOS; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] %s: set QMI QOS header enable\n", + dev->name, __func__); + break; + + case RMNET_IOCTL_SET_QOS_DISABLE: /* Set QoS header disabled */ + spin_lock_irqsave(&p->lock, flags); + p->operation_mode &= ~RMNET_MODE_QOS; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] %s: set QMI QOS header disable\n", + dev->name, __func__); + break; + + case RMNET_IOCTL_FLOW_ENABLE: + if (copy_from_user(&ioctl_data, ifr->ifr_ifru.ifru_data, + sizeof(struct rmnet_ioctl_data_s))) { + rc = -EFAULT; + break; + } + tc_qdisc_flow_control(dev, ioctl_data.u.tcm_handle, 1); + DBG0("[%s] %s: enabled flow", dev->name, __func__); + break; + + case RMNET_IOCTL_FLOW_DISABLE: + if (copy_from_user(&ioctl_data, ifr->ifr_ifru.ifru_data, + sizeof(struct rmnet_ioctl_data_s))) { + rc = -EFAULT; + break; + } + tc_qdisc_flow_control(dev, ioctl_data.u.tcm_handle, 0); + DBG0("[%s] %s: disabled flow", dev->name, __func__); + break; + + case RMNET_IOCTL_GET_QOS: /* Get QoS header state */ + ioctl_data.u.operation_mode = (p->operation_mode + & RMNET_MODE_QOS); + if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data, + sizeof(struct rmnet_ioctl_data_s))) + rc = -EFAULT; + break; + + case RMNET_IOCTL_GET_OPMODE: /* Get operation mode */ + ioctl_data.u.operation_mode = p->operation_mode; + if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data, + sizeof(struct rmnet_ioctl_data_s))) + rc = -EFAULT; + break; + + case RMNET_IOCTL_OPEN: /* Open transport port */ + rc = __rmnet_open(dev); + DBG0("[%s] %s: open transport port\n", + dev->name, __func__); + break; + + case RMNET_IOCTL_CLOSE: /* Close transport port */ + rc = __rmnet_close(dev); + DBG0("[%s] %s: close transport port\n", + dev->name, __func__); + break; + + case RMNET_IOCTL_EXTENDED: /* Extended IOCTL's */ + rc = rmnet_ioctl_extended(dev, ifr); + break; + + default: + pr_err("[%s] error: %s called for unsupported cmd[%d]\n", + dev->name, __func__, cmd); + return -EINVAL; + } + + DBG2("[%s] %s: cmd=0x%x opmode old=0x%08x new=0x%08x\n", + dev->name, __func__, cmd, old_opmode, p->operation_mode); + return rc; +} + +static void rmnet_setup(struct net_device *dev) +{ + /* Using Ethernet mode by default */ + dev->netdev_ops = &rmnet_ops_ether; + ether_setup(dev); + + /* set this after calling ether_setup */ + dev->mtu = RMNET_DATA_LEN; + dev->needed_headroom = HEADROOM_FOR_BAM + HEADROOM_FOR_QOS; + dev->needed_tailroom = TAILROOM; + random_ether_addr(dev->dev_addr); + + dev->watchdog_timeo = 1000; /* 10 seconds? */ +} + +#ifdef CONFIG_MSM_RMNET_DEBUG +static int rmnet_debug_init(struct net_device *dev) +{ + struct device *d; + struct rmnet_private *p; + int err = 0; + + d = &dev->dev; + p = netdev_priv(dev); + p->timeout_us = 0; + p->wakeups_xmit = 0; + p->wakeups_rcv = 0; + err = device_create_file(d, &dev_attr_timeout); + if (err) + return err; + err = device_create_file(d, &dev_attr_wakeups_xmit); + if (err) + return err; + err = device_create_file(d, &dev_attr_wakeups_rcv); + return err; +} +#else +static int rmnet_debug_init(struct net_device *dev) +{ + return 0; +} +#endif + +static int rmnet_sysnode_init(struct device *dev) +{ + int ret = 0; + + if (!dev) + return -EINVAL; + + device_create_file(dev, &dev_attr_debug_enable); + device_create_file(dev, + &dev_attr_msm_rmnet_bam_headroom_check_failure); + device_create_file(dev, &dev_attr_pkt_threshold); + + return ret; +} + +static void rmnet_remove_files(struct device *dev) +{ + device_remove_file(dev, &dev_attr_debug_enable); + device_remove_file(dev, &dev_attr_msm_rmnet_bam_headroom_check_failure); + device_remove_file(dev, &dev_attr_pkt_threshold); +} + +static int bam_rmnet_probe(struct platform_device *pdev) +{ + int i, ret; + struct rmnet_private *p; + struct device *d; + char name[BAM_DMUX_CH_NAME_MAX_LEN]; + struct net_device *dev; + const char *dev_name; + + for (i = 0; i < BAM_DMUX_NUM_CHANNELS; ++i) { + scnprintf(name, BAM_DMUX_CH_NAME_MAX_LEN, "bam_dmux_ch_%d", i); + if (!strcmp(pdev->name, name)) + break; + } + + if ((i > BAM_DMUX_DATA_RMNET_7 && i < BAM_DMUX_DATA_REV_RMNET_0) || + i >= BAM_DMUX_NUM_CHANNELS) { + pr_err("%s: wrong netdev %s\n", __func__, pdev->name); + return -ENODEV; + } + + if (i <= BAM_DMUX_DATA_RMNET_7) + dev_name = "rmnet%d"; + else + dev_name = "rev_rmnet%d"; + + dev = alloc_netdev(sizeof(*p), dev_name, NET_NAME_ENUM, rmnet_setup); + if (!dev) { + pr_err("%s: no memory for netdev %d\n", __func__, i); + return -ENOMEM; + } + + netdevs[i] = dev; + d = &dev->dev; + p = netdev_priv(dev); + /* Initial config uses Ethernet */ + p->operation_mode = RMNET_MODE_LLP_ETH; + p->ch_id = i; + p->waiting_for_ul_skb = NULL; + p->device_up = DEVICE_UNINITIALIZED; + spin_lock_init(&p->lock); + spin_lock_init(&p->tx_queue_lock); + + ret = register_netdev(dev); + if (ret) { + pr_err("%s: unable to register netdev %d rc=%d\n", + __func__, i, ret); + netdevs[i] = NULL; + free_netdev(dev); + return ret; + } + + rmnet_debug_init(dev); + rmnet_sysnode_init(d); + + return 0; +} + +static int bam_rmnet_remove(struct platform_device *pdev) +{ + int i; + struct rmnet_private *p; + char name[BAM_DMUX_CH_NAME_MAX_LEN]; + + for (i = 0; i < BAM_DMUX_NUM_CHANNELS; ++i) { + scnprintf(name, BAM_DMUX_CH_NAME_MAX_LEN, "bam_dmux_ch_%d", i); + if (!strcmp(pdev->name, name)) + break; + } + + if ((i > BAM_DMUX_DATA_RMNET_7 && i < BAM_DMUX_DATA_REV_RMNET_0) || + i >= BAM_DMUX_NUM_CHANNELS) { + pr_err("%s: wrong netdev %s\n", __func__, pdev->name); + return -ENODEV; + } + + p = netdev_priv(netdevs[i]); + if (p->waiting_for_ul_skb) { + dev_kfree_skb_any(p->waiting_for_ul_skb); + p->waiting_for_ul_skb = NULL; + } + msm_bam_dmux_close(p->ch_id); + netif_carrier_off(netdevs[i]); + netif_stop_queue(netdevs[i]); + + unregister_netdev(netdevs[i]); + free_netdev(netdevs[i]); + rmnet_remove_files(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_MSM_RMNET_DEBUG +static void rmnet_clear_timeout_us(void) +{ + timeout_us = 0; +} +#else +static void rmnet_clear_timeout_us(void) +{ +} +#endif /* CONFIG_MSM_RMNET_DEBUG */ + +static int __init rmnet_init(void) +{ + unsigned int n; + char *tempname; + + rmnet_clear_timeout_us(); + + n = 0; + while (n <= BAM_DMUX_DATA_REV_RMNET_8) { + if (n > BAM_DMUX_DATA_RMNET_7 && + n < BAM_DMUX_DATA_REV_RMNET_0) { + n++; + continue; + } + bam_rmnet_drivers[n].probe = bam_rmnet_probe; + bam_rmnet_drivers[n].remove = bam_rmnet_remove; + tempname = kmalloc(BAM_DMUX_CH_NAME_MAX_LEN, GFP_KERNEL); + if (!tempname) { + netdevs[n] = NULL; + return -ENOMEM; + } + scnprintf(tempname, BAM_DMUX_CH_NAME_MAX_LEN, "bam_dmux_ch_%d", + n); + bam_rmnet_drivers[n].driver.name = tempname; + bam_rmnet_drivers[n].driver.owner = THIS_MODULE; + platform_driver_register(&bam_rmnet_drivers[n]); + n++; + } + + return 0; +} + +module_init(rmnet_init); +MODULE_DESCRIPTION("MSM RMNET BAM TRANSPORT"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/wireless/cnss2/pci.c b/drivers/net/wireless/cnss2/pci.c index 54cd14411609..1d9f8a3b3dc3 100644 --- a/drivers/net/wireless/cnss2/pci.c +++ b/drivers/net/wireless/cnss2/pci.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ #include #include @@ -79,6 +79,8 @@ static DEFINE_SPINLOCK(time_sync_lock); #define HST_HANG_DATA_OFFSET ((3 * 1024 * 1024) - HANG_DATA_LENGTH) #define HSP_HANG_DATA_OFFSET ((2 * 1024 * 1024) - HANG_DATA_LENGTH) +#define MHI_SUSPEND_RETRY_CNT 3 + static struct cnss_pci_reg ce_src[] = { { "SRC_RING_BASE_LSB", QCA6390_CE_SRC_RING_BASE_LSB_OFFSET }, { "SRC_RING_BASE_MSB", QCA6390_CE_SRC_RING_BASE_MSB_OFFSET }, @@ -1100,6 +1102,7 @@ static int cnss_pci_set_mhi_state(struct cnss_pci_data *pci_priv, enum cnss_mhi_state mhi_state) { int ret = 0; + u8 retry = 0; if (pci_priv->device_id == QCA6174_DEVICE_ID) return 0; @@ -1147,10 +1150,21 @@ static int cnss_pci_set_mhi_state(struct cnss_pci_data *pci_priv, break; case CNSS_MHI_SUSPEND: mutex_lock(&pci_priv->mhi_ctrl->pm_mutex); - if (pci_priv->drv_connected_last) + if (pci_priv->drv_connected_last) { ret = mhi_pm_fast_suspend(pci_priv->mhi_ctrl, true); - else + } else { ret = mhi_pm_suspend(pci_priv->mhi_ctrl); + /* in some corner case, when cnss try to suspend, + * there is still packets pending in mhi layer, + * so retry suspend to save roll back effort. + */ + while (ret == -EBUSY && retry < MHI_SUSPEND_RETRY_CNT) { + usleep_range(5000, 6000); + retry++; + cnss_pr_err("mhi is busy, retry #%u", retry); + ret = mhi_pm_suspend(pci_priv->mhi_ctrl); + } + } mutex_unlock(&pci_priv->mhi_ctrl->pm_mutex); break; case CNSS_MHI_RESUME: @@ -1179,10 +1193,10 @@ static int cnss_pci_set_mhi_state(struct cnss_pci_data *pci_priv, return 0; out: - cnss_pr_err("Failed to set MHI state: %s(%d)\n", - cnss_mhi_state_to_str(mhi_state), mhi_state); + cnss_pr_err("Failed to set MHI state: %s(%d) ret: %d\n", + cnss_mhi_state_to_str(mhi_state), mhi_state, ret); - if (mhi_state == CNSS_MHI_RESUME) + if (mhi_state == CNSS_MHI_RESUME && ret != -ETIMEDOUT) cnss_force_fw_assert_async(&pci_priv->pci_dev->dev); return ret; @@ -2444,6 +2458,8 @@ static void cnss_pci_event_cb(struct msm_pcie_notify *notify) { struct pci_dev *pci_dev; struct cnss_pci_data *pci_priv; + struct cnss_plat_data *plat_priv = NULL; + int ret = 0; if (!notify) return; @@ -2457,6 +2473,23 @@ static void cnss_pci_event_cb(struct msm_pcie_notify *notify) return; switch (notify->event) { + case MSM_PCIE_EVENT_LINK_RECOVER: + cnss_pr_dbg("PCI link recover callback\n"); + + plat_priv = pci_priv->plat_priv; + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL\n"); + return; + } + + plat_priv->ctrl_params.quirks |= BIT(LINK_DOWN_SELF_RECOVERY); + + ret = msm_pcie_pm_control(MSM_PCIE_HANDLE_LINKDOWN, + pci_dev->bus->number, pci_dev, NULL, + PM_OPTIONS_DEFAULT); + if (ret) + cnss_pci_handle_linkdown(pci_priv); + break; case MSM_PCIE_EVENT_LINKDOWN: cnss_pr_dbg("PCI link down event callback\n"); cnss_pci_handle_linkdown(pci_priv); @@ -2490,8 +2523,9 @@ static int cnss_reg_pci_event(struct cnss_pci_data *pci_priv) struct msm_pcie_register_event *pci_event; pci_event = &pci_priv->msm_pci_event; - pci_event->events = MSM_PCIE_EVENT_LINKDOWN | - MSM_PCIE_EVENT_WAKEUP; + pci_event->events = MSM_PCIE_EVENT_LINK_RECOVER | + MSM_PCIE_EVENT_LINKDOWN | + MSM_PCIE_EVENT_WAKEUP; if (cnss_pci_is_drv_supported(pci_priv)) pci_event->events = pci_event->events | diff --git a/drivers/net/wireless/cnss_prealloc/cnss_prealloc.c b/drivers/net/wireless/cnss_prealloc/cnss_prealloc.c index bf0557f8403a..f32f400f2039 100644 --- a/drivers/net/wireless/cnss_prealloc/cnss_prealloc.c +++ b/drivers/net/wireless/cnss_prealloc/cnss_prealloc.c @@ -65,6 +65,16 @@ static struct wcnss_prealloc wcnss_allocs[] = { {0, 16 * 1024, NULL}, {0, 16 * 1024, NULL}, {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, {0, 32 * 1024, NULL}, {0, 32 * 1024, NULL}, {0, 32 * 1024, NULL}, @@ -75,6 +85,12 @@ static struct wcnss_prealloc wcnss_allocs[] = { {0, 64 * 1024, NULL}, {0, 64 * 1024, NULL}, {0, 64 * 1024, NULL}, + {0, 64 * 1024, NULL}, + {0, 64 * 1024, NULL}, + {0, 64 * 1024, NULL}, + {0, 64 * 1024, NULL}, + {0, 128 * 1024, NULL}, + {0, 128 * 1024, NULL}, }; int wcnss_prealloc_init(void) diff --git a/drivers/nfc/nq-nci.c b/drivers/nfc/nq-nci.c index 247df3492835..e404afdbc8cd 100644 --- a/drivers/nfc/nq-nci.c +++ b/drivers/nfc/nq-nci.c @@ -1,6 +1,13 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. +/* Copyright (c) 2015-2018, 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 @@ -18,11 +25,10 @@ #include #include "nq-nci.h" #include +#include #ifdef CONFIG_COMPAT #include #endif -#include -#include struct nqx_platform_data { unsigned int irq_gpio; @@ -30,8 +36,6 @@ struct nqx_platform_data { unsigned int clkreq_gpio; unsigned int firm_gpio; unsigned int ese_gpio; - int vdd_levels[2]; - int max_current; const char *clk_src_name; /* NFC_CLK pin voting state */ bool clk_pin_voting; @@ -44,16 +48,15 @@ static const struct of_device_id msm_match_table[] = { MODULE_DEVICE_TABLE(of, msm_match_table); +#define MAX_BUFFER_SIZE (320) +#define WAKEUP_SRC_TIMEOUT (2000) +#define MAX_RETRY_COUNT 3 + struct nqx_dev { wait_queue_head_t read_wq; - wait_queue_head_t cold_reset_read_wq; struct mutex read_mutex; - struct mutex dev_ref_mutex; struct i2c_client *client; - dev_t devno; - struct class *nqx_class; - struct device *nqx_device; - struct cdev c_dev; + struct miscdevice nqx_device; union nqx_uinfo nqx_info; /* NFC GPIO variables */ unsigned int irq_gpio; @@ -63,20 +66,12 @@ struct nqx_dev { unsigned int ese_gpio; /* NFC VEN pin state powered by Nfc */ bool nfc_ven_enabled; - /* NFC state reflected from MW */ - bool nfc_enabled; /* NFC_IRQ state */ bool irq_enabled; /* NFC_IRQ wake-up state */ bool irq_wake_up; - bool cold_reset_rsp_pending; - bool is_vreg_enabled; - bool is_ese_session_active; - uint8_t cold_reset_status; spinlock_t irq_enabled_lock; unsigned int count_irq; - /* NFC_IRQ Count */ - unsigned int dev_ref_count; /* Initial CORE RESET notification */ unsigned int core_reset_ntf; /* CLK control */ @@ -86,7 +81,6 @@ struct nqx_dev { size_t kbuflen; u8 *kbuf; struct nqx_platform_data *pdata; - struct regulator *reg; }; static int nfcc_reboot(struct notifier_block *notifier, unsigned long val, @@ -95,9 +89,6 @@ static int nfcc_reboot(struct notifier_block *notifier, unsigned long val, static int nqx_clock_select(struct nqx_dev *nqx_dev); /*clock disable function*/ static int nqx_clock_deselect(struct nqx_dev *nqx_dev); -static int nqx_standby_write(struct nqx_dev *nqx_dev, - const unsigned char *buf, size_t len); - static struct notifier_block nfcc_notifier = { .notifier_call = nfcc_reboot, .next = NULL, @@ -147,7 +138,7 @@ static irqreturn_t nqx_dev_irq_handler(int irq, void *dev_id) { struct nqx_dev *nqx_dev = dev_id; unsigned long flags; - + //pr_err("zuoweikai nqx_dev_irq_handler\n"); if (device_may_wakeup(&nqx_dev->client->dev)) pm_wakeup_event(&nqx_dev->client->dev, WAKEUP_SRC_TIMEOUT); @@ -160,92 +151,6 @@ static irqreturn_t nqx_dev_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } -static int is_data_available_for_read(struct nqx_dev *nqx_dev) -{ - int ret; - - nqx_enable_irq(nqx_dev); - ret = wait_event_interruptible_timeout(nqx_dev->read_wq, - !nqx_dev->irq_enabled, msecs_to_jiffies(MAX_IRQ_WAIT_TIME)); - return ret; -} - -static int send_cold_reset_cmd(struct nqx_dev *nqx_dev) -{ - int ret; - char *cold_reset_cmd = NULL; - - if (gpio_get_value(nqx_dev->firm_gpio)) { - dev_err(&nqx_dev->client->dev, "FW download in-progress\n"); - return -EBUSY; - } - if (!gpio_get_value(nqx_dev->en_gpio)) { - dev_err(&nqx_dev->client->dev, "VEN LOW - NFCC powered off\n"); - return -ENODEV; - } - cold_reset_cmd = kzalloc(COLD_RESET_CMD_LEN, GFP_DMA | GFP_KERNEL); - if (!cold_reset_cmd) - return -ENOMEM; - - cold_reset_cmd[0] = COLD_RESET_CMD_GID; - cold_reset_cmd[1] = COLD_RESET_OID; - cold_reset_cmd[2] = COLD_RESET_CMD_PAYLOAD_LEN; - - ret = nqx_standby_write(nqx_dev, cold_reset_cmd, COLD_RESET_CMD_LEN); - if (ret < 0) { - dev_err(&nqx_dev->client->dev, - "%s: write failed after max retry\n", __func__); - } - kfree(cold_reset_cmd); - return ret; -} - -static void read_cold_reset_rsp(struct nqx_dev *nqx_dev, bool isNfcEnabled, - char *header) -{ - int ret = -1; - char *cold_reset_rsp = NULL; - - cold_reset_rsp = kzalloc(COLD_RESET_RSP_LEN, GFP_DMA | GFP_KERNEL); - if (!cold_reset_rsp) - return; - - /* - * read header also if NFC is disabled - * for enable case, will be taken care by nfc_read thread - */ - if (!isNfcEnabled) { - ret = i2c_master_recv(nqx_dev->client, cold_reset_rsp, - NCI_HEADER_LEN); - if (ret != NCI_HEADER_LEN) { - dev_err(&nqx_dev->client->dev, - "%s: failure to read cold reset rsp header\n", - __func__); - goto error; - } - } else { - memcpy(cold_reset_rsp, header, NCI_HEADER_LEN); - } - - if ((NCI_HEADER_LEN + cold_reset_rsp[2]) > COLD_RESET_RSP_LEN) { - dev_err(&nqx_dev->client->dev, - "%s: - invalid response for cold_reset\n", __func__); - ret = -EINVAL; - goto error; - } - ret = i2c_master_recv(nqx_dev->client, &cold_reset_rsp[NCI_PAYLOAD_IDX], - cold_reset_rsp[2]); - if (ret != cold_reset_rsp[2]) { - dev_err(&nqx_dev->client->dev, - "%s: failure to read cold reset rsp status\n", - __func__); - goto error; - } - nqx_dev->cold_reset_status = cold_reset_rsp[NCI_PAYLOAD_IDX]; -error: - kfree(cold_reset_rsp); -} - static ssize_t nfc_read(struct file *filp, char __user *buf, size_t count, loff_t *offset) { @@ -319,24 +224,6 @@ static ssize_t nfc_read(struct file *filp, char __user *buf, ret = -EIO; goto err; } - /* check if it's response of cold reset command - * NFC HAL process shouldn't receive this data as - * command was sent by eSE HAL - */ - if (nqx_dev->cold_reset_rsp_pending - && (tmp[0] == COLD_RESET_RSP_GID) - && (tmp[1] == COLD_RESET_OID)) { - read_cold_reset_rsp(nqx_dev, true, tmp); - nqx_dev->cold_reset_rsp_pending = false; - wake_up_interruptible(&nqx_dev->cold_reset_read_wq); - mutex_unlock(&nqx_dev->read_mutex); - /* - * NFC process doesn't know about cold reset command - * being sent as it was initiated by eSE process - * we shouldn't return any data to NFC process - */ - return 0; - } #ifdef NFC_KERNEL_BU dev_dbg(&nqx_dev->client->dev, "%s : NfcNciRx %x %x %x\n", __func__, tmp[0], tmp[1], tmp[2]); @@ -431,96 +318,13 @@ static int nqx_standby_write(struct nqx_dev *nqx_dev, return ret; } - -/* - * Power management of the SN100 eSE - * eSE and NFCC both are powered using VEN gpio in SN100, - * VEN HIGH - eSE and NFCC both are powered on - * VEN LOW - eSE and NFCC both are power down - */ - -static int sn100_ese_pwr(struct nqx_dev *nqx_dev, unsigned long arg) -{ - int r = -1; - - if (arg == ESE_POWER_ON) { - /** - * Let's store the NFC VEN pin state - * will check stored value in case of eSE power off request, - * to find out if NFC MW also sent request to set VEN HIGH - * VEN state will remain HIGH if NFC is enabled otherwise - * it will be set as LOW - */ - nqx_dev->nfc_ven_enabled = - gpio_get_value(nqx_dev->en_gpio); - if (!nqx_dev->nfc_ven_enabled) { - dev_dbg(&nqx_dev->client->dev, "eSE HAL service setting en_gpio HIGH\n"); - gpio_set_value(nqx_dev->en_gpio, 1); - /* hardware dependent delay */ - usleep_range(1000, 1100); - } else { - dev_dbg(&nqx_dev->client->dev, "en_gpio already HIGH\n"); - } - nqx_dev->is_ese_session_active = true; - r = 0; - } else if (arg == ESE_POWER_OFF) { - if (!nqx_dev->nfc_ven_enabled) { - dev_dbg(&nqx_dev->client->dev, "NFC not enabled, disabling en_gpio\n"); - gpio_set_value(nqx_dev->en_gpio, 0); - /* hardware dependent delay */ - usleep_range(1000, 1100); - } else { - dev_dbg(&nqx_dev->client->dev, "keep en_gpio high as NFC is enabled\n"); - } - nqx_dev->is_ese_session_active = false; - r = 0; - } else if (arg == ESE_COLD_RESET) { - // set default value for status as failure - nqx_dev->cold_reset_status = EIO; - - r = send_cold_reset_cmd(nqx_dev); - if (r <= 0) { - dev_err(&nqx_dev->client->dev, - "failed to send cold reset command\n"); - return nqx_dev->cold_reset_status; - } - nqx_dev->cold_reset_rsp_pending = true; - // check if NFC is enabled - if (nqx_dev->nfc_enabled) { - /* - * nfc_read thread will initiate cold reset response - * and it will signal for data available - */ - wait_event_interruptible(nqx_dev->cold_reset_read_wq, - !nqx_dev->cold_reset_rsp_pending); - } else { - /* - * Read data as NFC thread is not active - */ - r = is_data_available_for_read(nqx_dev); - if (r <= 0) { - nqx_disable_irq(nqx_dev); - nqx_dev->cold_reset_rsp_pending = false; - return nqx_dev->cold_reset_status; - } - read_cold_reset_rsp(nqx_dev, false, NULL); - nqx_dev->cold_reset_rsp_pending = false; - } - r = nqx_dev->cold_reset_status; - } else if (arg == ESE_POWER_STATE) { - // eSE power state - r = gpio_get_value(nqx_dev->en_gpio); - } - return r; -} - /* * Power management of the eSE * NFC & eSE ON : NFC_EN high and eSE_pwr_req high. * NFC OFF & eSE ON : NFC_EN high and eSE_pwr_req high. * NFC OFF & eSE OFF : NFC_EN low and eSE_pwr_req low. */ -static int nqx_ese_pwr(struct nqx_dev *nqx_dev, unsigned long arg) +static int nqx_ese_pwr(struct nqx_dev *nqx_dev, unsigned long int arg) { int r = -1; const unsigned char svdd_off_cmd_warn[] = {0x2F, 0x31, 0x01, 0x01}; @@ -626,175 +430,18 @@ static int nqx_ese_pwr(struct nqx_dev *nqx_dev, unsigned long arg) return r; } -/** - * nfc_ldo_vote() - * @nqx_dev: NFC device containing regulator handle - * - * LDO voting based on voltage and current entries in DT - * - * Return: 0 on success and -ve on failure - */ -static int nfc_ldo_vote(struct nqx_dev *nqx_dev) -{ - struct device *dev = &nqx_dev->client->dev; - int ret; - - ret = regulator_set_voltage(nqx_dev->reg, - nqx_dev->pdata->vdd_levels[0], - nqx_dev->pdata->vdd_levels[1]); - if (ret < 0) { - dev_err(dev, "%s:set voltage failed\n", __func__); - return ret; - } - - /* pass expected current from NFC in uA */ - ret = regulator_set_load(nqx_dev->reg, nqx_dev->pdata->max_current); - if (ret < 0) { - dev_err(dev, "%s:set load failed\n", __func__); - return ret; - } - - ret = regulator_enable(nqx_dev->reg); - if (ret < 0) - dev_err(dev, "%s:regulator_enable failed\n", __func__); - else - nqx_dev->is_vreg_enabled = true; - return ret; -} - -/** - * nfc_ldo_config() - * @client: I2C client instance, containing node to read DT entry - * @nqx_dev: NFC device containing regulator handle - * - * Configure LDO if entry is present in DT file otherwise - * with success as it's optional - * - * Return: 0 on success and -ve on failure - */ -static int nfc_ldo_config(struct i2c_client *client, struct nqx_dev *nqx_dev) -{ - int r; - - if (of_get_property(client->dev.of_node, NFC_LDO_SUPPLY_NAME, NULL)) { - // Get the regulator handle - nqx_dev->reg = regulator_get(&client->dev, - NFC_LDO_SUPPLY_DT_NAME); - if (IS_ERR(nqx_dev->reg)) { - r = PTR_ERR(nqx_dev->reg); - nqx_dev->reg = NULL; - dev_err(&client->dev, - "%s: regulator_get failed, ret = %d\n", - __func__, r); - return r; - } - } else { - nqx_dev->reg = NULL; - dev_err(&client->dev, - "%s: regulator entry not present\n", __func__); - // return success as it's optional to configure LDO - return 0; - } - - // LDO config supported by platform DT - r = nfc_ldo_vote(nqx_dev); - if (r < 0) { - dev_err(&client->dev, - "%s: LDO voting failed, ret = %d\n", __func__, r); - regulator_put(nqx_dev->reg); - } - return r; -} - -/** - * nfc_ldo_unvote() - * @nqx_dev: NFC device containing regulator handle - * - * set voltage and load to zero and disable regulator - * - * Return: 0 on success and -ve on failure - */ -static int nfc_ldo_unvote(struct nqx_dev *nqx_dev) -{ - struct device *dev = &nqx_dev->client->dev; - int ret; - - if (!nqx_dev->is_vreg_enabled) { - dev_err(dev, "%s: regulator already disabled\n", __func__); - return -EINVAL; - } - - ret = regulator_disable(nqx_dev->reg); - if (ret < 0) { - dev_err(dev, "%s:regulator_disable failed\n", __func__); - return ret; - } - nqx_dev->is_vreg_enabled = false; - - ret = regulator_set_voltage(nqx_dev->reg, 0, NFC_VDDIO_MAX); - if (ret < 0) { - dev_err(dev, "%s:set voltage failed\n", __func__); - return ret; - } - - ret = regulator_set_load(nqx_dev->reg, 0); - if (ret < 0) - dev_err(dev, "%s:set load failed\n", __func__); - return ret; -} - static int nfc_open(struct inode *inode, struct file *filp) { - struct nqx_dev *nqx_dev = container_of(inode->i_cdev, - struct nqx_dev, c_dev); + int ret = 0; + struct nqx_dev *nqx_dev = container_of(filp->private_data, + struct nqx_dev, nqx_device); filp->private_data = nqx_dev; nqx_init_stat(nqx_dev); - mutex_lock(&nqx_dev->dev_ref_mutex); - - if (nqx_dev->dev_ref_count == 0) { - nqx_enable_irq(nqx_dev); - - if (gpio_is_valid(nqx_dev->firm_gpio)) { - gpio_set_value(nqx_dev->firm_gpio, 0); - usleep_range(10000, 10100); - } - } - - nqx_dev->dev_ref_count = nqx_dev->dev_ref_count + 1; - - mutex_unlock(&nqx_dev->dev_ref_mutex); - dev_dbg(&nqx_dev->client->dev, "%s: %d,%d\n", __func__, imajor(inode), iminor(inode)); - return 0; -} - -static int nfc_close(struct inode *inode, struct file *filp) -{ - struct nqx_dev *nqx_dev = container_of(inode->i_cdev, - struct nqx_dev, c_dev); - - mutex_lock(&nqx_dev->dev_ref_mutex); - - if (nqx_dev->dev_ref_count == 1) { - nqx_disable_irq(nqx_dev); - - if (gpio_is_valid(nqx_dev->firm_gpio)) { - gpio_set_value(nqx_dev->firm_gpio, 0); - usleep_range(10000, 10100); - } - } - - if (nqx_dev->dev_ref_count > 0) - nqx_dev->dev_ref_count = nqx_dev->dev_ref_count - 1; - - mutex_unlock(&nqx_dev->dev_ref_mutex); - - filp->private_data = NULL; - - return 0; + return ret; } /* @@ -815,7 +462,7 @@ int nfc_ioctl_power_states(struct file *filp, unsigned long arg) int r = 0; struct nqx_dev *nqx_dev = filp->private_data; - if (arg == NFC_POWER_OFF) { + if (arg == 0) { /* * We are attempting a hardware reset so let us disable * interrupts to avoid spurious notifications to upper @@ -849,7 +496,7 @@ int nfc_ioctl_power_states(struct file *filp, unsigned long arg) dev_err(&nqx_dev->client->dev, "unable to disable clock\n"); } nqx_dev->nfc_ven_enabled = false; - } else if (arg == NFC_POWER_ON) { + } else if (arg == 1) { nqx_enable_irq(nqx_dev); dev_dbg(&nqx_dev->client->dev, "gpio_set_value enable: %s: info: %p\n", @@ -866,7 +513,7 @@ int nfc_ioctl_power_states(struct file *filp, unsigned long arg) dev_err(&nqx_dev->client->dev, "unable to enable clock\n"); } nqx_dev->nfc_ven_enabled = true; - } else if (arg == NFC_FW_DWL_VEN_TOGGLE) { + } else if (arg == 2) { /* * We are switching to Dowload Mode, toggle the enable pin * in order to set the NFCC in the new mode @@ -888,41 +535,6 @@ int nfc_ioctl_power_states(struct file *filp, unsigned long arg) usleep_range(10000, 10100); gpio_set_value(nqx_dev->en_gpio, 1); usleep_range(10000, 10100); - } else if (arg == NFC_FW_DWL_HIGH) { - /* - * Setting firmware download gpio to HIGH for SN100U - * before FW download start - */ - dev_dbg(&nqx_dev->client->dev, "SN100 fw gpio HIGH\n"); - if (gpio_is_valid(nqx_dev->firm_gpio)) { - gpio_set_value(nqx_dev->firm_gpio, 1); - usleep_range(10000, 10100); - } else - dev_err(&nqx_dev->client->dev, - "firm_gpio is invalid\n"); - } else if (arg == NFC_FW_DWL_LOW) { - /* - * Setting firmware download gpio to LOW for SN100U - * FW download finished - */ - dev_dbg(&nqx_dev->client->dev, "SN100 fw gpio LOW\n"); - if (gpio_is_valid(nqx_dev->firm_gpio)) { - gpio_set_value(nqx_dev->firm_gpio, 0); - usleep_range(10000, 10100); - } else { - dev_err(&nqx_dev->client->dev, - "firm_gpio is invalid\n"); - } - } else if (arg == NFC_ENABLE) { - /* - * Setting flag true when NFC is enabled - */ - nqx_dev->nfc_enabled = true; - } else if (arg == NFC_DISABLE) { - /* - * Setting flag false when NFC is disabled - */ - nqx_dev->nfc_enabled = false; } else { r = -ENOIOCTLCMD; } @@ -989,7 +601,7 @@ unsigned int nfc_ioctl_nfcc_info(struct file *filp, unsigned long arg) r = nqx_dev->nqx_info.i; dev_dbg(&nqx_dev->client->dev, - "nqx nfc : %s r = %d\n", __func__, r); + "nqx nfc : nfc_ioctl_nfcc_info r = %d\n", r); return r; } @@ -998,28 +610,16 @@ static long nfc_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg) { int r = 0; - struct nqx_dev *nqx_dev = pfile->private_data; - - if (!nqx_dev) - return -ENODEV; switch (cmd) { case NFC_SET_PWR: r = nfc_ioctl_power_states(pfile, arg); break; case ESE_SET_PWR: - if ((nqx_dev->nqx_info.info.chip_type == NFCC_SN100_A) || - (nqx_dev->nqx_info.info.chip_type == NFCC_SN100_B)) - r = sn100_ese_pwr(nqx_dev, arg); - else - r = nqx_ese_pwr(nqx_dev, arg); + r = nqx_ese_pwr(pfile->private_data, arg); break; case ESE_GET_PWR: - if ((nqx_dev->nqx_info.info.chip_type == NFCC_SN100_A) || - (nqx_dev->nqx_info.info.chip_type == NFCC_SN100_B)) - r = sn100_ese_pwr(nqx_dev, 3); - else - r = nqx_ese_pwr(nqx_dev, 3); + r = nqx_ese_pwr(pfile->private_data, 3); break; case SET_RX_BLOCK: break; @@ -1043,296 +643,106 @@ static const struct file_operations nfc_dev_fops = { .read = nfc_read, .write = nfc_write, .open = nfc_open, - .release = nfc_close, .unlocked_ioctl = nfc_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = nfc_compat_ioctl #endif }; -/* - * function: get_nfcc_hw_info() - * - * @client: pointer to i2c_client - * @nqx_dev: pointer to nqx_dev structure - * @nci_reset_rsp_payload_len: payload length of NCI reset cmd - * - * Retrieves NFCC HW information based on the type of NFC chip - * used on the device. Depending on the nci_reset_rsp_payload_len - * value, core INIT command will be sent. - * - * NFC HW NCI version Send Core INIT cmd - * NQ3xx or old 1.0 Yes - * NQ4xx 2.0 No - * Sn1x0x 2.0 No - * - * Return: error codes in case of any failure, - * number of bytes read otherwise - */ -static int get_nfcc_hw_info(struct i2c_client *client, - struct nqx_dev *nqx_dev, char nci_reset_rsp_payload_len) -{ - int ret = 0; - - char *nci_init_cmd = NULL; - char *nci_init_rsp = NULL; - char *nci_reset_ntf = NULL; - char *nfcc_hw_info = NULL; - unsigned char nfcc_hw_info_len = 0; - - nci_init_cmd = kzalloc(NCI_INIT_CMD_LEN + 1, GFP_DMA | GFP_KERNEL); - if (!nci_init_cmd) { - ret = -ENOMEM; - goto err_nfcc_hw_info; - } - - nci_init_rsp = kzalloc(NCI_INIT_RSP_LEN + 1, GFP_DMA | GFP_KERNEL); - if (!nci_init_rsp) { - ret = -ENOMEM; - goto err_nfcc_hw_info; - } - - nci_reset_ntf = kzalloc(NCI_RESET_NTF_LEN + 1, GFP_DMA | GFP_KERNEL); - if (!nci_reset_ntf) { - ret = -ENOMEM; - goto err_nfcc_hw_info; - } - - if (nci_reset_rsp_payload_len == NCI_1_0_RESET_RSP_PAYLOAD_LEN) { - /* - * Chipset is NQ330 or older. - * Send core INIT command to get HW info. - */ - nci_init_cmd[0] = 0x20; - nci_init_cmd[1] = 0x01; - nci_init_cmd[2] = 0x00; - ret = nqx_standby_write(nqx_dev, nci_init_cmd, - NCI_INIT_CMD_LEN); - if (ret < 0) { - dev_dbg(&client->dev, - "%s: - i2c_master_send failed for Core INIT\n", - __func__); - goto err_nfcc_hw_info; - } - - ret = is_data_available_for_read(nqx_dev); - if (ret <= 0) { - nqx_disable_irq(nqx_dev); - goto err_nfcc_hw_info; - } - - /* Read Response of INIT command */ - ret = i2c_master_recv(client, nci_init_rsp, NCI_INIT_RSP_LEN); - if (ret < 0) { - dev_dbg(&client->dev, - "%s: - i2c_master_recv get INIT rsp Error\n", - __func__); - goto err_nfcc_hw_info; - } - nfcc_hw_info = nci_init_rsp; - } else { - /* - * Chipset is NQ4xx or later. - * Retrieve NTF data from wait queue. - */ - ret = is_data_available_for_read(nqx_dev); - if (ret <= 0) { - nqx_disable_irq(nqx_dev); - goto err_nfcc_hw_info; - } - - /* Read Notification of RESET command */ - ret = i2c_master_recv(client, nci_reset_ntf, NCI_RESET_NTF_LEN); - if (ret < 0) { - dev_dbg(&client->dev, - "%s: - i2c_master_recv get RESET ntf Error\n", - __func__); - goto err_nfcc_hw_info; - } - nfcc_hw_info = nci_reset_ntf; - } - - /* Save NFCC HW info */ - nfcc_hw_info_len = - NCI_HEADER_LEN + nfcc_hw_info[NCI_PAYLOAD_LENGTH_INDEX]; - if (nfcc_hw_info_len > PAYLOAD_HEADER_LENGTH) { - nqx_dev->nqx_info.info.chip_type = - nfcc_hw_info[nfcc_hw_info_len - - NFCC_HW_CHIP_ID_OFFSET]; - nqx_dev->nqx_info.info.rom_version = - nfcc_hw_info[nfcc_hw_info_len - - NFCC_HW_ROM_VER_OFFSET]; - nqx_dev->nqx_info.info.fw_major = - nfcc_hw_info[nfcc_hw_info_len - - NFCC_HW_MAJOR_NO_OFFSET]; - nqx_dev->nqx_info.info.fw_minor = - nfcc_hw_info[nfcc_hw_info_len - - NFCC_HW_MINOR_NO_OFFSET]; - } - -err_nfcc_hw_info: - - kfree(nci_reset_ntf); - kfree(nci_init_rsp); - kfree(nci_init_cmd); - - return ret; -} - /* Check for availability of NQ_ NFC controller hardware */ +/* static int nfcc_hw_check(struct i2c_client *client, struct nqx_dev *nqx_dev) { int ret = 0; - + int gpio_retry_count = 0; + unsigned char raw_nci_reset_cmd[] = {0x20, 0x00, 0x01, 0x00}; + unsigned char raw_nci_init_cmd[] = {0x20, 0x01, 0x00}; + unsigned char nci_init_rsp[28]; + unsigned char nci_reset_rsp[6]; + unsigned char init_rsp_len = 0; unsigned int enable_gpio = nqx_dev->en_gpio; - char *nci_reset_cmd = NULL; - char *nci_reset_rsp = NULL; - - char *nci_get_version_cmd = NULL; - char *nci_get_version_rsp = NULL; - - nci_reset_cmd = kzalloc(NCI_RESET_CMD_LEN + 1, GFP_DMA | GFP_KERNEL); - if (!nci_reset_cmd) { - ret = -ENOMEM; - goto done; - } - - nci_reset_rsp = kzalloc(NCI_RESET_RSP_LEN + 1, GFP_DMA | GFP_KERNEL); - if (!nci_reset_rsp) { - ret = -ENOMEM; - goto done; - } - nci_get_version_cmd = kzalloc(NCI_GET_VERSION_CMD_LEN + 1, - GFP_DMA | GFP_KERNEL); - if (!nci_get_version_cmd) { - ret = -ENOMEM; - goto done; - } - - nci_get_version_rsp = kzalloc(NCI_GET_VERSION_RSP_LEN + 1, - GFP_DMA | GFP_KERNEL); - if (!nci_get_version_rsp) { - ret = -ENOMEM; - goto done; - } +reset_enable_gpio:*/ /* making sure that the NFCC starts in a clean state. */ - gpio_set_value(enable_gpio, 1);/* HPD : Enable*/ + //gpio_set_value(enable_gpio, 0);/* ULPM: Disable */ /* hardware dependent delay */ - usleep_range(10000, 10100); - gpio_set_value(enable_gpio, 0);/* ULPM: Disable */ + //usleep_range(10000, 10100); + //gpio_set_value(enable_gpio, 1);/* HPD : Enable*/ /* hardware dependent delay */ - usleep_range(10000, 10100); - gpio_set_value(enable_gpio, 1);/* HPD : Enable*/ - /* hardware dependent delay */ - usleep_range(10000, 10100); + //usleep_range(10000, 10100); - nci_reset_cmd[0] = 0x20; - nci_reset_cmd[1] = 0x00; - nci_reset_cmd[2] = 0x01; - nci_reset_cmd[3] = 0x00; /* send NCI CORE RESET CMD with Keep Config parameters */ - ret = i2c_master_send(client, nci_reset_cmd, NCI_RESET_CMD_LEN); + /*ret = i2c_master_send(client, raw_nci_reset_cmd, + sizeof(raw_nci_reset_cmd)); if (ret < 0) { dev_err(&client->dev, - "%s: - i2c_master_send core reset Error\n", __func__); - - if (gpio_is_valid(nqx_dev->firm_gpio)) { - gpio_set_value(nqx_dev->firm_gpio, 1); - usleep_range(10000, 10100); - } - gpio_set_value(nqx_dev->en_gpio, 0); - usleep_range(10000, 10100); - gpio_set_value(nqx_dev->en_gpio, 1); - usleep_range(10000, 10100); - - nci_get_version_cmd[0] = 0x00; - nci_get_version_cmd[1] = 0x04; - nci_get_version_cmd[2] = 0xF1; - nci_get_version_cmd[3] = 0x00; - nci_get_version_cmd[4] = 0x00; - nci_get_version_cmd[5] = 0x00; - nci_get_version_cmd[6] = 0x6E; - nci_get_version_cmd[7] = 0xEF; - ret = i2c_master_send(client, nci_get_version_cmd, - NCI_GET_VERSION_CMD_LEN); - - if (ret < 0) { - dev_err(&client->dev, - "%s: - i2c_master_send get version cmd Error\n", - __func__); - goto err_nfcc_hw_check; - } - /* hardware dependent delay */ - usleep_range(10000, 10100); - - ret = i2c_master_recv(client, nci_get_version_rsp, - NCI_GET_VERSION_RSP_LEN); - if (ret < 0) { - dev_err(&client->dev, - "%s: - i2c_master_recv get version rsp Error\n", - __func__); - goto err_nfcc_hw_check; - } else { - nqx_dev->nqx_info.info.chip_type = - nci_get_version_rsp[3]; - nqx_dev->nqx_info.info.rom_version = - nci_get_version_rsp[4]; - nqx_dev->nqx_info.info.fw_minor = - nci_get_version_rsp[6]; - nqx_dev->nqx_info.info.fw_major = - nci_get_version_rsp[7]; - } - goto err_nfcc_reset_failed; - } - - ret = is_data_available_for_read(nqx_dev); - if (ret <= 0) { - nqx_disable_irq(nqx_dev); + "%s: - i2c_master_send Error\n", __func__); goto err_nfcc_hw_check; - } + }*/ + /* hardware dependent delay */ + //msleep(30); - - /* Read Header of RESET command */ - ret = i2c_master_recv(client, nci_reset_rsp, NCI_HEADER_LEN); - if (ret != NCI_HEADER_LEN) { - dev_dbg(&client->dev, - "%s: - i2c_master_recv get RESET rsp header Error\n", __func__); - goto err_nfcc_hw_check; - } - ret = i2c_master_recv(client, &nci_reset_rsp[NCI_PAYLOAD_START_INDEX], - nci_reset_rsp[NCI_PAYLOAD_LENGTH_INDEX]); - if (ret != nci_reset_rsp[NCI_PAYLOAD_LENGTH_INDEX]) { - dev_dbg(&client->dev, - "%s: - i2c_master_recv get RESET rsp data Error\n", __func__); - goto err_nfcc_hw_check; - } - - /* Retrieve NFCC HW info */ - ret = get_nfcc_hw_info(client, nqx_dev, - nci_reset_rsp[NCI_PAYLOAD_LENGTH_INDEX]); + /* Read Response of RESET command */ + /*ret = i2c_master_recv(client, nci_reset_rsp, + sizeof(nci_reset_rsp)); if (ret < 0) { - dev_dbg(&client->dev, - "%s: - Error in getting NFCC HW info\n", __func__); + dev_err(&client->dev, + "%s: - i2c_master_recv Error\n", __func__); + gpio_retry_count = gpio_retry_count + 1; + if (gpio_retry_count < MAX_RETRY_COUNT) + goto reset_enable_gpio; goto err_nfcc_hw_check; } - - + ret = nqx_standby_write(nqx_dev, raw_nci_init_cmd, + sizeof(raw_nci_init_cmd)); + if (ret < 0) { + dev_err(&client->dev, + "%s: - i2c_master_send Error\n", __func__); + goto err_nfcc_core_init_fail; + }*/ + /* hardware dependent delay */ + //msleep(30); + /* Read Response of INIT command */ +/* ret = i2c_master_recv(client, nci_init_rsp, + sizeof(nci_init_rsp)); + if (ret < 0) { + dev_err(&client->dev, + "%s: - i2c_master_recv Error\n", __func__); + goto err_nfcc_core_init_fail; + } */ +// init_rsp_len = 2 + nci_init_rsp[2]; +/* if (init_rsp_len > PAYLOAD_HEADER_LENGTH) { + nqx_dev->nqx_info.info.chip_type = + nci_init_rsp[init_rsp_len - 3]; + nqx_dev->nqx_info.info.rom_version = + nci_init_rsp[init_rsp_len - 2]; + nqx_dev->nqx_info.info.fw_major = + nci_init_rsp[init_rsp_len - 1]; + nqx_dev->nqx_info.info.fw_minor = + nci_init_rsp[init_rsp_len]; + } dev_dbg(&client->dev, "%s: - nq - reset cmd answer : NfcNciRx %x %x %x\n", __func__, nci_reset_rsp[0], nci_reset_rsp[1], nci_reset_rsp[2]); -err_nfcc_reset_failed: - dev_dbg(&nqx_dev->client->dev, "NQ NFCC chip_type = %x\n", + dev_err(&nqx_dev->client->dev, "NQ NFCC chip_type = %x\n", nqx_dev->nqx_info.info.chip_type); - dev_dbg(&nqx_dev->client->dev, "NQ fw version = %x.%x.%x\n", + dev_err(&nqx_dev->client->dev, "NQ fw version = %x.%x.%x\n", nqx_dev->nqx_info.info.rom_version, nqx_dev->nqx_info.info.fw_major, nqx_dev->nqx_info.info.fw_minor); switch (nqx_dev->nqx_info.info.chip_type) { + case NFCC_NQ_210: + dev_dbg(&client->dev, + "%s: ## NFCC == NQ210 ##\n", __func__); + break; + case NFCC_NQ_220: + dev_dbg(&client->dev, + "%s: ## NFCC == NQ220 ##\n", __func__); + break; case NFCC_NQ_310: dev_dbg(&client->dev, "%s: ## NFCC == NQ310 ##\n", __func__); @@ -1345,35 +755,31 @@ static int nfcc_hw_check(struct i2c_client *client, struct nqx_dev *nqx_dev) dev_dbg(&client->dev, "%s: ## NFCC == PN66T ##\n", __func__); break; - case NFCC_SN100_A: - case NFCC_SN100_B: - dev_dbg(&client->dev, - "%s: ## NFCC == SN100x ##\n", __func__); - break; default: dev_err(&client->dev, "%s: - NFCC HW not Supported\n", __func__); break; } - - ret = 0; - nqx_dev->nfc_ven_enabled = true; - goto done; +*/ + /*Disable NFC by default to save power on boot*/ +// gpio_set_value(enable_gpio, 0);/* ULPM: Disable */ +// ret = 0; +// goto done; +/* +err_nfcc_core_init_fail: + dev_err(&client->dev, + "%s: - nq - reset cmd answer : NfcNciRx %x %x %x\n", + __func__, nci_reset_rsp[0], + nci_reset_rsp[1], nci_reset_rsp[2]); err_nfcc_hw_check: ret = -ENXIO; dev_err(&client->dev, "%s: - NFCC HW not available\n", __func__); - done: - kfree(nci_reset_rsp); - kfree(nci_reset_cmd); - kfree(nci_get_version_cmd); - kfree(nci_get_version_rsp); - return ret; } - +*/ /* * Routine to enable clock. * this routine can be extended to select from multiple @@ -1388,7 +794,7 @@ static int nqx_clock_select(struct nqx_dev *nqx_dev) if (nqx_dev->s_clk == NULL) goto err_clk; - if (!nqx_dev->clk_run) + if (nqx_dev->clk_run == false) r = clk_prepare_enable(nqx_dev->s_clk); if (r) @@ -1411,7 +817,7 @@ static int nqx_clock_deselect(struct nqx_dev *nqx_dev) int r = -1; if (nqx_dev->s_clk != NULL) { - if (nqx_dev->clk_run) { + if (nqx_dev->clk_run == true) { clk_disable_unprepare(nqx_dev->s_clk); nqx_dev->clk_run = false; } @@ -1453,29 +859,9 @@ static int nfc_parse_dt(struct device *dev, struct nqx_platform_data *pdata) else pdata->clk_pin_voting = true; - // optional property - r = of_property_read_u32_array(np, NFC_LDO_VOL_DT_NAME, - (u32 *) pdata->vdd_levels, - ARRAY_SIZE(pdata->vdd_levels)); - if (r) { - dev_err(dev, "error reading NFC VDDIO min and max value\n"); - // set default as per datasheet - pdata->vdd_levels[0] = NFC_VDDIO_MIN; - pdata->vdd_levels[1] = NFC_VDDIO_MAX; - } - - // optional property - r = of_property_read_u32(np, NFC_LDO_CUR_DT_NAME, &pdata->max_current); - if (r) { - dev_err(dev, "error reading NFC current value\n"); - // set default as per datasheet - pdata->max_current = NFC_CURRENT_MAX; - } - pdata->clkreq_gpio = of_get_named_gpio(np, "qcom,nq-clkreq", 0); - // return success as above properties are optional - return 0; + return r; } static inline int gpio_input_init(const struct device * const dev, @@ -1646,6 +1032,8 @@ static int nqx_probe(struct i2c_client *client, "%s: ese gpio not provided\n", __func__); /* ese gpio optional so we should continue */ } + + /* if (gpio_is_valid(platform_data->clkreq_gpio)) { r = gpio_request(platform_data->clkreq_gpio, "nfc_clkreq_gpio"); @@ -1667,47 +1055,27 @@ static int nqx_probe(struct i2c_client *client, "%s: clkreq gpio not provided\n", __func__); goto err_ese_gpio; } + */ nqx_dev->en_gpio = platform_data->en_gpio; nqx_dev->irq_gpio = platform_data->irq_gpio; nqx_dev->firm_gpio = platform_data->firm_gpio; - nqx_dev->clkreq_gpio = platform_data->clkreq_gpio; + //nqx_dev->clkreq_gpio = platform_data->clkreq_gpio; nqx_dev->pdata = platform_data; /* init mutex and queues */ init_waitqueue_head(&nqx_dev->read_wq); - init_waitqueue_head(&nqx_dev->cold_reset_read_wq); mutex_init(&nqx_dev->read_mutex); - mutex_init(&nqx_dev->dev_ref_mutex); spin_lock_init(&nqx_dev->irq_enabled_lock); - r = alloc_chrdev_region(&nqx_dev->devno, 0, DEV_COUNT, DEVICE_NAME); - if (r < 0) { - dev_err(&client->dev, - "%s: failed to alloc chrdev region\n", __func__); - goto err_char_dev_register; - } + nqx_dev->nqx_device.minor = MISC_DYNAMIC_MINOR; + nqx_dev->nqx_device.name = "nq-nci"; + nqx_dev->nqx_device.fops = &nfc_dev_fops; - nqx_dev->nqx_class = class_create(THIS_MODULE, CLASS_NAME); - if (IS_ERR(nqx_dev->nqx_class)) { - dev_err(&client->dev, - "%s: failed to register device class\n", __func__); - goto err_class_create; - } - - cdev_init(&nqx_dev->c_dev, &nfc_dev_fops); - r = cdev_add(&nqx_dev->c_dev, nqx_dev->devno, DEV_COUNT); - if (r < 0) { - dev_err(&client->dev, "%s: failed to add cdev\n", __func__); - goto err_cdev_add; - } - - nqx_dev->nqx_device = device_create(nqx_dev->nqx_class, NULL, - nqx_dev->devno, nqx_dev, DEVICE_NAME); - if (IS_ERR(nqx_dev->nqx_device)) { - dev_err(&client->dev, - "%s: failed to create the device\n", __func__); - goto err_device_create; + r = misc_register(&nqx_dev->nqx_device); + if (r) { + dev_err(&client->dev, "%s: misc_register failed\n", __func__); + goto err_misc_register; } /* NFC_INT IRQ */ @@ -1720,24 +1088,19 @@ static int nqx_probe(struct i2c_client *client, } nqx_disable_irq(nqx_dev); - r = nfc_ldo_config(client, nqx_dev); - if (r) { - dev_err(&client->dev, "%s: LDO config failed\n", __func__); - goto err_ldo_config_failed; - } - /* * To be efficient we need to test whether nfcc hardware is physically * present before attempting further hardware initialisation. * */ - r = nfcc_hw_check(client, nqx_dev); - if (r) { + //r = nfcc_hw_check(client, nqx_dev); + //if (r) { /* make sure NFCC is not enabled */ - gpio_set_value(platform_data->en_gpio, 0); + // gpio_set_value(platform_data->en_gpio, 0); /* We don't think there is hardware switch NFC OFF */ - goto err_request_hw_check_failed; - } + // goto err_request_hw_check_failed; + //} + pr_err("zuoweikai: go on register reboot notifier\n"); /* Register reboot notifier here */ r = register_reboot_notifier(&nfcc_notifier); @@ -1765,9 +1128,6 @@ static int nqx_probe(struct i2c_client *client, device_set_wakeup_capable(&client->dev, true); i2c_set_clientdata(client, nqx_dev); nqx_dev->irq_wake_up = false; - nqx_dev->cold_reset_rsp_pending = false; - nqx_dev->nfc_enabled = false; - nqx_dev->is_ese_session_active = false; dev_err(&client->dev, "%s: probing NFCC NQxxx exited successfully\n", @@ -1779,28 +1139,17 @@ static int nqx_probe(struct i2c_client *client, unregister_reboot_notifier(&nfcc_notifier); #endif err_request_hw_check_failed: - if (nqx_dev->reg) { - nfc_ldo_unvote(nqx_dev); - regulator_put(nqx_dev->reg); - } -err_ldo_config_failed: free_irq(client->irq, nqx_dev); err_request_irq_failed: - device_destroy(nqx_dev->nqx_class, nqx_dev->devno); -err_device_create: - cdev_del(&nqx_dev->c_dev); -err_cdev_add: - class_destroy(nqx_dev->nqx_class); -err_class_create: - unregister_chrdev_region(nqx_dev->devno, DEV_COUNT); -err_char_dev_register: + misc_deregister(&nqx_dev->nqx_device); +err_misc_register: mutex_destroy(&nqx_dev->read_mutex); -err_clkreq_gpio: - gpio_free(platform_data->clkreq_gpio); -err_ese_gpio: +//err_clkreq_gpio: + //gpio_free(platform_data->clkreq_gpio); +//err_ese_gpio: /* optional gpio, not sure was configured in probe */ - if (gpio_is_valid(platform_data->ese_gpio)) - gpio_free(platform_data->ese_gpio); + //if (nqx_dev->ese_gpio > 0) + //gpio_free(platform_data->ese_gpio); err_firm_gpio: gpio_free(platform_data->firm_gpio); err_irq_gpio: @@ -1834,21 +1183,10 @@ static int nqx_remove(struct i2c_client *client) goto err; } - gpio_set_value(nqx_dev->en_gpio, 0); - // HW dependent delay before LDO goes into LPM mode - usleep_range(10000, 10100); - if (nqx_dev->reg) { - ret = nfc_ldo_unvote(nqx_dev); - regulator_put(nqx_dev->reg); - } unregister_reboot_notifier(&nfcc_notifier); free_irq(client->irq, nqx_dev); - cdev_del(&nqx_dev->c_dev); - device_destroy(nqx_dev->nqx_class, nqx_dev->devno); - class_destroy(nqx_dev->nqx_class); - unregister_chrdev_region(nqx_dev->devno, DEV_COUNT); + misc_deregister(&nqx_dev->nqx_device); mutex_destroy(&nqx_dev->read_mutex); - mutex_destroy(&nqx_dev->dev_ref_mutex); gpio_free(nqx_dev->clkreq_gpio); /* optional gpio, not sure was configured in probe */ if (nqx_dev->ese_gpio > 0) @@ -1903,6 +1241,7 @@ static struct i2c_driver nqx = { .probe = nqx_probe, .remove = nqx_remove, .driver = { + .owner = THIS_MODULE, .name = "nq-nci", .of_match_table = msm_match_table, .probe_type = PROBE_PREFER_ASYNCHRONOUS, diff --git a/drivers/nfc/nq-nci.h b/drivers/nfc/nq-nci.h index 6b81445d1780..a0acc60beae7 100644 --- a/drivers/nfc/nq-nci.h +++ b/drivers/nfc/nq-nci.h @@ -32,7 +32,7 @@ * frame size of 554 in FW download mode * Frame len(2) + Frame Header(6) + DATA(512) + HASH(32) + CRC(2) + RFU(4) */ -#define MAX_BUFFER_SIZE (558) +//#define MAX_BUFFER_SIZE (558) #define WAKEUP_SRC_TIMEOUT (2000) #define NCI_HEADER_LEN 3 #define NCI_PAYLOAD_IDX 3 diff --git a/drivers/of/of_batterydata.c b/drivers/of/of_batterydata.c index 7759d7f43f8a..0a6ad38653d7 100644 --- a/drivers/of/of_batterydata.c +++ b/drivers/of/of_batterydata.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2013-2019, The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #define pr_fmt(fmt) "%s: " fmt, __func__ @@ -15,8 +14,6 @@ #include #include -#define DEFAULT_ID 330 - static int of_batterydata_read_lut(const struct device_node *np, int max_cols, int max_rows, int *ncols, int *nrows, int *col_legend_data, int *row_legend_data, @@ -317,13 +314,11 @@ struct device_node *of_batterydata_get_best_profile( { struct batt_ids batt_ids; struct device_node *node, *best_node = NULL; - struct device_node *default_node = NULL; const char *battery_type = NULL; int delta = 0, best_delta = 0, best_id_kohm = 0, id_range_pct, i = 0, rc = 0, limit = 0; - int checknum = 0, match = 0; bool in_range = false; - printk("batt_id_kohm=%d\n", batt_id_kohm); + /* read battery id range percentage for best profile */ rc = of_property_read_u32(batterydata_container_node, "qcom,batt-id-range-pct", &id_range_pct); @@ -359,17 +354,11 @@ struct device_node *of_batterydata_get_best_profile( delta = abs(batt_ids.kohm[i] - batt_id_kohm); limit = (batt_ids.kohm[i] * id_range_pct) / 100; in_range = (delta <= limit); - if (in_range != 0) { - match = 1; - } /* * Check if the delta is the lowest one * and also if the limits are in range * before selecting the best node. */ - if (batt_ids.kohm[i] == DEFAULT_ID) { - default_node = node; - } if ((delta < best_delta || !best_node) && in_range) { best_node = node; @@ -379,12 +368,6 @@ struct device_node *of_batterydata_get_best_profile( } } } - checknum = abs(best_id_kohm - batt_id_kohm); - if (match == 0) { - printk("batt_id match error\n"); - best_node = default_node; - checknum = 0; - } if (best_node == NULL) { pr_err("No battery data found\n"); @@ -392,7 +375,8 @@ struct device_node *of_batterydata_get_best_profile( } /* check that profile id is in range of the measured batt_id */ - if (checknum > ((best_id_kohm * id_range_pct) / 100)) { + if (abs(best_id_kohm - batt_id_kohm) > + ((best_id_kohm * id_range_pct) / 100)) { pr_err("out of range: profile id %d batt id %d pct %d\n", best_id_kohm, batt_id_kohm, id_range_pct); return NULL; @@ -672,4 +656,3 @@ int of_batterydata_read_data(struct device_node *batterydata_container_node, } MODULE_LICENSE("GPL v2"); - diff --git a/drivers/pci/controller/pci-msm.c b/drivers/pci/controller/pci-msm.c index dee2751275b9..6809741c4c47 100644 --- a/drivers/pci/controller/pci-msm.c +++ b/drivers/pci/controller/pci-msm.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2014-2020, The Linux Foundation. All rights reserved.*/ +/* Copyright (c) 2014-2021, The Linux Foundation. All rights reserved.*/ #include #include @@ -835,6 +835,8 @@ struct msm_pcie_dev_t { struct pinctrl_state *pins_default; struct pinctrl_state *pins_sleep; struct msm_pcie_device_info pcidev_table[MAX_DEVICE_NUM]; + bool config_recovery; + struct work_struct link_recover_wq; struct msm_pcie_drv_info *drv_info; @@ -3217,7 +3219,7 @@ static inline int msm_pcie_oper_conf(struct pci_bus *bus, u32 devfn, int oper, if (dev->shadow_en) { if (rd_val == PCIE_LINK_DOWN && - (readl_relaxed(config_base) == PCIE_LINK_DOWN)) + (readl_relaxed(config_base) == PCIE_LINK_DOWN)) PCIE_ERR(dev, "Read of RC%d %d:0x%02x + 0x%04x[%d] is all FFs\n", rc_idx, bus->number, devfn, @@ -3233,6 +3235,17 @@ static inline int msm_pcie_oper_conf(struct pci_bus *bus, u32 devfn, int oper, wr_val, rd_val, *val); } + if (rd_val == PCIE_LINK_DOWN && + (readl_relaxed(config_base) == PCIE_LINK_DOWN)) { + if (dev->config_recovery) { + PCIE_ERR(dev, + "RC%d link recovery schedule\n", + rc_idx); + dev->cfg_access = false; + schedule_work(&dev->link_recover_wq); + } + } + unlock: spin_unlock_irqrestore(&dev->cfg_lock, dev->irqsave_flags); out: @@ -5151,7 +5164,10 @@ static void handle_sbr_func(struct work_struct *work) } else { PCIE_ERR(dev, "PCIe RC%d link initialization failed\n", dev->rc_idx); + return; } + /* restore BME that gets cleared after link_down reset */ + msm_pcie_write_mask(dev->dm_core + PCIE20_COMMAND_STATUS, 0, BIT(2)); } static irqreturn_t handle_flush_irq(int irq, void *data) @@ -5236,6 +5252,16 @@ static void handle_wake_func(struct work_struct *work) mutex_unlock(&dev->recovery_lock); } +static void handle_link_recover(struct work_struct *work) +{ + struct msm_pcie_dev_t *dev = container_of(work, struct msm_pcie_dev_t, + link_recover_wq); + + PCIE_DBG(dev, "PCIe: link recover start for RC%d\n", dev->rc_idx); + + msm_pcie_notify_client(dev, MSM_PCIE_EVENT_LINK_RECOVER); +} + static irqreturn_t handle_aer_irq(int irq, void *data) { struct msm_pcie_dev_t *dev = data; @@ -6444,6 +6470,14 @@ static int msm_pcie_probe(struct platform_device *pdev) msm_pcie_gpio_deinit(pcie_dev); goto decrease_rc_num; } + pcie_dev->config_recovery = of_property_read_bool(of_node, + "qcom,config-recovery"); + if (pcie_dev->config_recovery) { + PCIE_DUMP(pcie_dev, + "PCIe RC%d config space recovery enabled\n", + pcie_dev->rc_idx); + INIT_WORK(&pcie_dev->link_recover_wq, handle_link_recover); + } drv_supported = of_property_read_bool(of_node, "qcom,drv-supported"); if (drv_supported) { @@ -7241,6 +7275,15 @@ static int msm_pcie_pm_suspend(struct pci_dev *dev, pcie_dev->suspending = true; spin_unlock_irqrestore(&pcie_dev->irq_lock, irqsave_flags); + if (pcie_dev->config_recovery) { + if (work_pending(&pcie_dev->link_recover_wq)) { + PCIE_DBG(pcie_dev, + "RC%d: cancel link_recover_wq at pm suspend\n", + pcie_dev->rc_idx); + cancel_work_sync(&pcie_dev->link_recover_wq); + } + } + if (!pcie_dev->power_on) { PCIE_DBG(pcie_dev, "PCIe: power of RC%d has been turned off.\n", @@ -7457,6 +7500,7 @@ static int msm_pcie_drv_resume(struct msm_pcie_dev_t *pcie_dev) struct msm_pcie_clk_info_t *clk_info; u32 current_link_speed; int ret, i; + u32 val; mutex_lock(&pcie_dev->recovery_lock); mutex_lock(&pcie_dev->setup_lock); @@ -7533,6 +7577,10 @@ static int msm_pcie_drv_resume(struct msm_pcie_dev_t *pcie_dev) enable_irq(pcie_dev->irq[MSM_PCIE_INT_GLOBAL_INT].num); + val = readl_relaxed(pcie_dev->parf + PCIE20_PARF_LTSSM); + PCIE_DBG(pcie_dev, "PCIe RC%d: LTSSM_STATE: %s\n", + pcie_dev->rc_idx, TO_LTSSM_STR(val & 0x3f)); + mutex_unlock(&pcie_dev->setup_lock); mutex_unlock(&pcie_dev->recovery_lock); @@ -7548,6 +7596,7 @@ static int msm_pcie_drv_suspend(struct msm_pcie_dev_t *pcie_dev, struct msm_pcie_drv_tre *pkt = &drv_enable->pkt; struct msm_pcie_clk_info_t *clk_info; int ret, i; + u32 val; if (!rpdev) { PCIE_ERR(pcie_dev, "PCIe: RC%d: DRV: no rpmsg device\n", @@ -7579,6 +7628,10 @@ static int msm_pcie_drv_suspend(struct msm_pcie_dev_t *pcie_dev, if (unlikely(drv_info->seq == MSM_PCIE_DRV_SEQ_RESV)) drv_info->seq = 0; + val = readl_relaxed(pcie_dev->parf + PCIE20_PARF_LTSSM); + PCIE_DBG(pcie_dev, "PCIe RC%d: LTSSM_STATE: %s\n", + pcie_dev->rc_idx, TO_LTSSM_STR(val & 0x3f)); + ret = rpmsg_trysend(rpdev->ept, drv_enable, sizeof(*drv_enable)); if (ret) { PCIE_ERR(pcie_dev, "PCIe: RC%d: DRV: failed to send rpmsg\n", diff --git a/drivers/perf/qcom_llcc_pmu.c b/drivers/perf/qcom_llcc_pmu.c index c63c75765c94..accab0b4f846 100644 --- a/drivers/perf/qcom_llcc_pmu.c +++ b/drivers/perf/qcom_llcc_pmu.c @@ -255,7 +255,7 @@ static int qcom_llcc_pmu_probe(struct platform_device *pdev) if (!llccpmu) return -ENOMEM; - llccpmu->ver = (enum llcc_pmu_version)(long) + llccpmu->ver = (enum llcc_pmu_version) of_device_get_match_data(&pdev->dev); if (!llccpmu->ver) { pr_err("Unknown device type!\n"); diff --git a/drivers/pinctrl/qcom/Kconfig b/drivers/pinctrl/qcom/Kconfig index e773c733f29f..4be8b71bcf42 100644 --- a/drivers/pinctrl/qcom/Kconfig +++ b/drivers/pinctrl/qcom/Kconfig @@ -98,6 +98,26 @@ config PINCTRL_MSM8994 Qualcomm TLMM block found in the Qualcomm 8994 platform. The Qualcomm 8992 platform is also supported by this driver. +config PINCTRL_MSM8937 + tristate "Qualcomm Technologies Inc MSM8937 pin controller driver" + depends on GPIOLIB && OF + select PINCTRL_MSM + help + This is the pinctrl, pinmux, pinconf and gpiolib driver for the + Qualcomm Technologies Inc TLMM block found on the Qualcomm + Technologies Inc MSM8937 platform. + If unsure say N. + +config PINCTRL_MSM8917 + tristate "Qualcomm Technologies Inc MSM8917 pin controller driver" + depends on GPIOLIB && OF + select PINCTRL_MSM + help + This is the pinctrl, pinmux, pinconf and gpiolib driver for the + Qualcomm Technologies Inc TLMM block found on the Qualcomm + Technologies Inc MSM8917 platform. + If unsure say N. + config PINCTRL_MSM8996 tristate "Qualcomm MSM8996 pin controller driver" depends on GPIOLIB && OF diff --git a/drivers/pinctrl/qcom/Makefile b/drivers/pinctrl/qcom/Makefile index e178e7198a86..82932fcb3db9 100644 --- a/drivers/pinctrl/qcom/Makefile +++ b/drivers/pinctrl/qcom/Makefile @@ -15,6 +15,8 @@ obj-$(CONFIG_PINCTRL_MSM8996) += pinctrl-msm8996.o obj-$(CONFIG_PINCTRL_MSM8998) += pinctrl-msm8998.o obj-$(CONFIG_PINCTRL_QDF2XXX) += pinctrl-qdf2xxx.o obj-$(CONFIG_PINCTRL_MDM9615) += pinctrl-mdm9615.o +obj-$(CONFIG_PINCTRL_MSM8937) += pinctrl-msm8937.o +obj-$(CONFIG_PINCTRL_MSM8917) += pinctrl-msm8917.o obj-$(CONFIG_PINCTRL_QCOM_SPMI_PMIC) += pinctrl-spmi-gpio.o obj-$(CONFIG_PINCTRL_QCOM_SPMI_PMIC) += pinctrl-spmi-mpp.o obj-$(CONFIG_PINCTRL_QCOM_SSBI_PMIC) += pinctrl-ssbi-gpio.o diff --git a/drivers/pinctrl/qcom/pinctrl-msm.c b/drivers/pinctrl/qcom/pinctrl-msm.c index f8ce0eac1ae8..8634582aef2b 100644 --- a/drivers/pinctrl/qcom/pinctrl-msm.c +++ b/drivers/pinctrl/qcom/pinctrl-msm.c @@ -452,6 +452,15 @@ static int msm_gpio_direction_output(struct gpio_chip *chip, unsigned offset, in val &= ~BIT(g->out_bit); writel(val, pctrl->regs + g->io_reg); + if(offset == 98 ||offset == 99 ||offset == 100 ||offset == 101 ||offset == 108 || offset == 102 || offset == 103 || offset == 104 || offset == 107 || offset == 109 || offset == 110 || offset == 111 || offset == 112) + { + printk(KERN_ERR "%s write egpio func\n",__func__); + val = readl(pctrl->regs + g->ctl_reg); + val |= BIT(g->egpio_enable); + printk(KERN_ERR "%s pctrl->regs + g->ctl_reg=0x%x\n",__func__,pctrl->regs + g->ctl_reg); + writel(val, pctrl->regs + g->ctl_reg); + } + val = readl(pctrl->regs + g->ctl_reg); val |= BIT(g->oe_bit); writel(val, pctrl->regs + g->ctl_reg); diff --git a/drivers/pinctrl/qcom/pinctrl-msm8917.c b/drivers/pinctrl/qcom/pinctrl-msm8917.c new file mode 100644 index 000000000000..78ec2fb3f18c --- /dev/null +++ b/drivers/pinctrl/qcom/pinctrl-msm8917.c @@ -0,0 +1,1466 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2016, 2018, 2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include + +#include "pinctrl-msm.h" + +#define FUNCTION(fname) \ + [msm_mux_##fname] = { \ + .name = #fname, \ + .groups = fname##_groups, \ + .ngroups = ARRAY_SIZE(fname##_groups), \ + } + +#define REG_BASE 0x0 +#define REG_SIZE 0x1000 +#define PINGROUP(id, f1, f2, f3, f4, f5, f6, f7, f8, f9) \ + { \ + .name = "gpio" #id, \ + .pins = gpio##id##_pins, \ + .npins = (unsigned int)ARRAY_SIZE(gpio##id##_pins), \ + .funcs = (int[]){ \ + msm_mux_gpio, /* gpio mode */ \ + msm_mux_##f1, \ + msm_mux_##f2, \ + msm_mux_##f3, \ + msm_mux_##f4, \ + msm_mux_##f5, \ + msm_mux_##f6, \ + msm_mux_##f7, \ + msm_mux_##f8, \ + msm_mux_##f9 \ + }, \ + .nfuncs = 10, \ + .ctl_reg = REG_BASE + REG_SIZE * id, \ + .io_reg = REG_BASE + 0x4 + REG_SIZE * id, \ + .intr_cfg_reg = REG_BASE + 0x8 + REG_SIZE * id, \ + .intr_status_reg = REG_BASE + 0xc + REG_SIZE * id, \ + .intr_target_reg = REG_BASE + 0x8 + REG_SIZE * id, \ + .mux_bit = 2, \ + .pull_bit = 0, \ + .drv_bit = 6, \ + .oe_bit = 9, \ + .in_bit = 0, \ + .out_bit = 1, \ + .intr_enable_bit = 0, \ + .intr_status_bit = 0, \ + .intr_target_bit = 5, \ + .intr_target_kpss_val = 4, \ + .intr_raw_status_bit = 4, \ + .intr_polarity_bit = 1, \ + .intr_detection_bit = 2, \ + .intr_detection_width = 2, \ + } + +#define SDC_QDSD_PINGROUP(pg_name, ctl, pull, drv) \ + { \ + .name = #pg_name, \ + .pins = pg_name##_pins, \ + .npins = (unsigned int)ARRAY_SIZE(pg_name##_pins), \ + .ctl_reg = ctl, \ + .io_reg = 0, \ + .intr_cfg_reg = 0, \ + .intr_status_reg = 0, \ + .intr_target_reg = 0, \ + .mux_bit = -1, \ + .pull_bit = pull, \ + .drv_bit = drv, \ + .oe_bit = -1, \ + .in_bit = -1, \ + .out_bit = -1, \ + .intr_enable_bit = -1, \ + .intr_status_bit = -1, \ + .intr_target_bit = -1, \ + .intr_raw_status_bit = -1, \ + .intr_polarity_bit = -1, \ + .intr_detection_bit = -1, \ + .intr_detection_width = -1, \ + } +static const struct pinctrl_pin_desc msm8917_pins[] = { + PINCTRL_PIN(0, "GPIO_0"), + PINCTRL_PIN(1, "GPIO_1"), + PINCTRL_PIN(2, "GPIO_2"), + PINCTRL_PIN(3, "GPIO_3"), + PINCTRL_PIN(4, "GPIO_4"), + PINCTRL_PIN(5, "GPIO_5"), + PINCTRL_PIN(6, "GPIO_6"), + PINCTRL_PIN(7, "GPIO_7"), + PINCTRL_PIN(8, "GPIO_8"), + PINCTRL_PIN(9, "GPIO_9"), + PINCTRL_PIN(10, "GPIO_10"), + PINCTRL_PIN(11, "GPIO_11"), + PINCTRL_PIN(12, "GPIO_12"), + PINCTRL_PIN(13, "GPIO_13"), + PINCTRL_PIN(14, "GPIO_14"), + PINCTRL_PIN(15, "GPIO_15"), + PINCTRL_PIN(16, "GPIO_16"), + PINCTRL_PIN(17, "GPIO_17"), + PINCTRL_PIN(18, "GPIO_18"), + PINCTRL_PIN(19, "GPIO_19"), + PINCTRL_PIN(20, "GPIO_20"), + PINCTRL_PIN(21, "GPIO_21"), + PINCTRL_PIN(22, "GPIO_22"), + PINCTRL_PIN(23, "GPIO_23"), + PINCTRL_PIN(24, "GPIO_24"), + PINCTRL_PIN(25, "GPIO_25"), + PINCTRL_PIN(26, "GPIO_26"), + PINCTRL_PIN(27, "GPIO_27"), + PINCTRL_PIN(28, "GPIO_28"), + PINCTRL_PIN(29, "GPIO_29"), + PINCTRL_PIN(30, "GPIO_30"), + PINCTRL_PIN(31, "GPIO_31"), + PINCTRL_PIN(32, "GPIO_32"), + PINCTRL_PIN(33, "GPIO_33"), + PINCTRL_PIN(34, "GPIO_34"), + PINCTRL_PIN(35, "GPIO_35"), + PINCTRL_PIN(36, "GPIO_36"), + PINCTRL_PIN(37, "GPIO_37"), + PINCTRL_PIN(38, "GPIO_38"), + PINCTRL_PIN(39, "GPIO_39"), + PINCTRL_PIN(40, "GPIO_40"), + PINCTRL_PIN(41, "GPIO_41"), + PINCTRL_PIN(42, "GPIO_42"), + PINCTRL_PIN(43, "GPIO_43"), + PINCTRL_PIN(44, "GPIO_44"), + PINCTRL_PIN(45, "GPIO_45"), + PINCTRL_PIN(46, "GPIO_46"), + PINCTRL_PIN(47, "GPIO_47"), + PINCTRL_PIN(48, "GPIO_48"), + PINCTRL_PIN(49, "GPIO_49"), + PINCTRL_PIN(50, "GPIO_50"), + PINCTRL_PIN(51, "GPIO_51"), + PINCTRL_PIN(52, "GPIO_52"), + PINCTRL_PIN(53, "GPIO_53"), + PINCTRL_PIN(54, "GPIO_54"), + PINCTRL_PIN(55, "GPIO_55"), + PINCTRL_PIN(56, "GPIO_56"), + PINCTRL_PIN(57, "GPIO_57"), + PINCTRL_PIN(58, "GPIO_58"), + PINCTRL_PIN(59, "GPIO_59"), + PINCTRL_PIN(60, "GPIO_60"), + PINCTRL_PIN(61, "GPIO_61"), + PINCTRL_PIN(62, "GPIO_62"), + PINCTRL_PIN(63, "GPIO_63"), + PINCTRL_PIN(64, "GPIO_64"), + PINCTRL_PIN(65, "GPIO_65"), + PINCTRL_PIN(66, "GPIO_66"), + PINCTRL_PIN(67, "GPIO_67"), + PINCTRL_PIN(68, "GPIO_68"), + PINCTRL_PIN(69, "GPIO_69"), + PINCTRL_PIN(70, "GPIO_70"), + PINCTRL_PIN(71, "GPIO_71"), + PINCTRL_PIN(72, "GPIO_72"), + PINCTRL_PIN(73, "GPIO_73"), + PINCTRL_PIN(74, "GPIO_74"), + PINCTRL_PIN(75, "GPIO_75"), + PINCTRL_PIN(76, "GPIO_76"), + PINCTRL_PIN(77, "GPIO_77"), + PINCTRL_PIN(78, "GPIO_78"), + PINCTRL_PIN(79, "GPIO_79"), + PINCTRL_PIN(80, "GPIO_80"), + PINCTRL_PIN(81, "GPIO_81"), + PINCTRL_PIN(82, "GPIO_82"), + PINCTRL_PIN(83, "GPIO_83"), + PINCTRL_PIN(84, "GPIO_84"), + PINCTRL_PIN(85, "GPIO_85"), + PINCTRL_PIN(86, "GPIO_86"), + PINCTRL_PIN(87, "GPIO_87"), + PINCTRL_PIN(88, "GPIO_88"), + PINCTRL_PIN(89, "GPIO_89"), + PINCTRL_PIN(90, "GPIO_90"), + PINCTRL_PIN(91, "GPIO_91"), + PINCTRL_PIN(92, "GPIO_92"), + PINCTRL_PIN(93, "GPIO_93"), + PINCTRL_PIN(94, "GPIO_94"), + PINCTRL_PIN(95, "GPIO_95"), + PINCTRL_PIN(96, "GPIO_96"), + PINCTRL_PIN(97, "GPIO_97"), + PINCTRL_PIN(98, "GPIO_98"), + PINCTRL_PIN(99, "GPIO_99"), + PINCTRL_PIN(100, "GPIO_100"), + PINCTRL_PIN(101, "GPIO_101"), + PINCTRL_PIN(102, "GPIO_102"), + PINCTRL_PIN(103, "GPIO_103"), + PINCTRL_PIN(104, "GPIO_104"), + PINCTRL_PIN(105, "GPIO_105"), + PINCTRL_PIN(106, "GPIO_106"), + PINCTRL_PIN(107, "GPIO_107"), + PINCTRL_PIN(108, "GPIO_108"), + PINCTRL_PIN(109, "GPIO_109"), + PINCTRL_PIN(110, "GPIO_110"), + PINCTRL_PIN(111, "GPIO_111"), + PINCTRL_PIN(112, "GPIO_112"), + PINCTRL_PIN(113, "GPIO_113"), + PINCTRL_PIN(114, "GPIO_114"), + PINCTRL_PIN(115, "GPIO_115"), + PINCTRL_PIN(116, "GPIO_116"), + PINCTRL_PIN(117, "GPIO_117"), + PINCTRL_PIN(118, "GPIO_118"), + PINCTRL_PIN(119, "GPIO_119"), + PINCTRL_PIN(120, "GPIO_120"), + PINCTRL_PIN(121, "GPIO_121"), + PINCTRL_PIN(122, "GPIO_122"), + PINCTRL_PIN(123, "GPIO_123"), + PINCTRL_PIN(124, "GPIO_124"), + PINCTRL_PIN(125, "GPIO_125"), + PINCTRL_PIN(126, "GPIO_126"), + PINCTRL_PIN(127, "GPIO_127"), + PINCTRL_PIN(128, "GPIO_128"), + PINCTRL_PIN(129, "GPIO_129"), + PINCTRL_PIN(130, "GPIO_130"), + PINCTRL_PIN(131, "GPIO_131"), + PINCTRL_PIN(132, "GPIO_132"), + PINCTRL_PIN(133, "GPIO_133"), + PINCTRL_PIN(134, "SDC1_CLK"), + PINCTRL_PIN(135, "SDC1_CMD"), + PINCTRL_PIN(136, "SDC1_DATA"), + PINCTRL_PIN(137, "SDC1_RCLK"), + PINCTRL_PIN(138, "SDC2_CLK"), + PINCTRL_PIN(139, "SDC2_CMD"), + PINCTRL_PIN(140, "SDC2_DATA"), + PINCTRL_PIN(141, "QDSD_CLK"), + PINCTRL_PIN(142, "QDSD_CMD"), + PINCTRL_PIN(143, "QDSD_DATA0"), + PINCTRL_PIN(144, "QDSD_DATA1"), + PINCTRL_PIN(145, "QDSD_DATA2"), + PINCTRL_PIN(146, "QDSD_DATA3"), +}; + +#define DECLARE_MSM_GPIO_PINS(pin) \ + static const unsigned int gpio##pin##_pins[] = { pin } +DECLARE_MSM_GPIO_PINS(0); +DECLARE_MSM_GPIO_PINS(1); +DECLARE_MSM_GPIO_PINS(2); +DECLARE_MSM_GPIO_PINS(3); +DECLARE_MSM_GPIO_PINS(4); +DECLARE_MSM_GPIO_PINS(5); +DECLARE_MSM_GPIO_PINS(6); +DECLARE_MSM_GPIO_PINS(7); +DECLARE_MSM_GPIO_PINS(8); +DECLARE_MSM_GPIO_PINS(9); +DECLARE_MSM_GPIO_PINS(10); +DECLARE_MSM_GPIO_PINS(11); +DECLARE_MSM_GPIO_PINS(12); +DECLARE_MSM_GPIO_PINS(13); +DECLARE_MSM_GPIO_PINS(14); +DECLARE_MSM_GPIO_PINS(15); +DECLARE_MSM_GPIO_PINS(16); +DECLARE_MSM_GPIO_PINS(17); +DECLARE_MSM_GPIO_PINS(18); +DECLARE_MSM_GPIO_PINS(19); +DECLARE_MSM_GPIO_PINS(20); +DECLARE_MSM_GPIO_PINS(21); +DECLARE_MSM_GPIO_PINS(22); +DECLARE_MSM_GPIO_PINS(23); +DECLARE_MSM_GPIO_PINS(24); +DECLARE_MSM_GPIO_PINS(25); +DECLARE_MSM_GPIO_PINS(26); +DECLARE_MSM_GPIO_PINS(27); +DECLARE_MSM_GPIO_PINS(28); +DECLARE_MSM_GPIO_PINS(29); +DECLARE_MSM_GPIO_PINS(30); +DECLARE_MSM_GPIO_PINS(31); +DECLARE_MSM_GPIO_PINS(32); +DECLARE_MSM_GPIO_PINS(33); +DECLARE_MSM_GPIO_PINS(34); +DECLARE_MSM_GPIO_PINS(35); +DECLARE_MSM_GPIO_PINS(36); +DECLARE_MSM_GPIO_PINS(37); +DECLARE_MSM_GPIO_PINS(38); +DECLARE_MSM_GPIO_PINS(39); +DECLARE_MSM_GPIO_PINS(40); +DECLARE_MSM_GPIO_PINS(41); +DECLARE_MSM_GPIO_PINS(42); +DECLARE_MSM_GPIO_PINS(43); +DECLARE_MSM_GPIO_PINS(44); +DECLARE_MSM_GPIO_PINS(45); +DECLARE_MSM_GPIO_PINS(46); +DECLARE_MSM_GPIO_PINS(47); +DECLARE_MSM_GPIO_PINS(48); +DECLARE_MSM_GPIO_PINS(49); +DECLARE_MSM_GPIO_PINS(50); +DECLARE_MSM_GPIO_PINS(51); +DECLARE_MSM_GPIO_PINS(52); +DECLARE_MSM_GPIO_PINS(53); +DECLARE_MSM_GPIO_PINS(54); +DECLARE_MSM_GPIO_PINS(55); +DECLARE_MSM_GPIO_PINS(56); +DECLARE_MSM_GPIO_PINS(57); +DECLARE_MSM_GPIO_PINS(58); +DECLARE_MSM_GPIO_PINS(59); +DECLARE_MSM_GPIO_PINS(60); +DECLARE_MSM_GPIO_PINS(61); +DECLARE_MSM_GPIO_PINS(62); +DECLARE_MSM_GPIO_PINS(63); +DECLARE_MSM_GPIO_PINS(64); +DECLARE_MSM_GPIO_PINS(65); +DECLARE_MSM_GPIO_PINS(66); +DECLARE_MSM_GPIO_PINS(67); +DECLARE_MSM_GPIO_PINS(68); +DECLARE_MSM_GPIO_PINS(69); +DECLARE_MSM_GPIO_PINS(70); +DECLARE_MSM_GPIO_PINS(71); +DECLARE_MSM_GPIO_PINS(72); +DECLARE_MSM_GPIO_PINS(73); +DECLARE_MSM_GPIO_PINS(74); +DECLARE_MSM_GPIO_PINS(75); +DECLARE_MSM_GPIO_PINS(76); +DECLARE_MSM_GPIO_PINS(77); +DECLARE_MSM_GPIO_PINS(78); +DECLARE_MSM_GPIO_PINS(79); +DECLARE_MSM_GPIO_PINS(80); +DECLARE_MSM_GPIO_PINS(81); +DECLARE_MSM_GPIO_PINS(82); +DECLARE_MSM_GPIO_PINS(83); +DECLARE_MSM_GPIO_PINS(84); +DECLARE_MSM_GPIO_PINS(85); +DECLARE_MSM_GPIO_PINS(86); +DECLARE_MSM_GPIO_PINS(87); +DECLARE_MSM_GPIO_PINS(88); +DECLARE_MSM_GPIO_PINS(89); +DECLARE_MSM_GPIO_PINS(90); +DECLARE_MSM_GPIO_PINS(91); +DECLARE_MSM_GPIO_PINS(92); +DECLARE_MSM_GPIO_PINS(93); +DECLARE_MSM_GPIO_PINS(94); +DECLARE_MSM_GPIO_PINS(95); +DECLARE_MSM_GPIO_PINS(96); +DECLARE_MSM_GPIO_PINS(97); +DECLARE_MSM_GPIO_PINS(98); +DECLARE_MSM_GPIO_PINS(99); +DECLARE_MSM_GPIO_PINS(100); +DECLARE_MSM_GPIO_PINS(101); +DECLARE_MSM_GPIO_PINS(102); +DECLARE_MSM_GPIO_PINS(103); +DECLARE_MSM_GPIO_PINS(104); +DECLARE_MSM_GPIO_PINS(105); +DECLARE_MSM_GPIO_PINS(106); +DECLARE_MSM_GPIO_PINS(107); +DECLARE_MSM_GPIO_PINS(108); +DECLARE_MSM_GPIO_PINS(109); +DECLARE_MSM_GPIO_PINS(110); +DECLARE_MSM_GPIO_PINS(111); +DECLARE_MSM_GPIO_PINS(112); +DECLARE_MSM_GPIO_PINS(113); +DECLARE_MSM_GPIO_PINS(114); +DECLARE_MSM_GPIO_PINS(115); +DECLARE_MSM_GPIO_PINS(116); +DECLARE_MSM_GPIO_PINS(117); +DECLARE_MSM_GPIO_PINS(118); +DECLARE_MSM_GPIO_PINS(119); +DECLARE_MSM_GPIO_PINS(120); +DECLARE_MSM_GPIO_PINS(121); +DECLARE_MSM_GPIO_PINS(122); +DECLARE_MSM_GPIO_PINS(123); +DECLARE_MSM_GPIO_PINS(124); +DECLARE_MSM_GPIO_PINS(125); +DECLARE_MSM_GPIO_PINS(126); +DECLARE_MSM_GPIO_PINS(127); +DECLARE_MSM_GPIO_PINS(128); +DECLARE_MSM_GPIO_PINS(129); +DECLARE_MSM_GPIO_PINS(130); +DECLARE_MSM_GPIO_PINS(131); +DECLARE_MSM_GPIO_PINS(132); +DECLARE_MSM_GPIO_PINS(133); + +static const unsigned int sdc1_clk_pins[] = { 134 }; +static const unsigned int sdc1_cmd_pins[] = { 135 }; +static const unsigned int sdc1_data_pins[] = { 136 }; +static const unsigned int sdc1_rclk_pins[] = { 137 }; +static const unsigned int sdc2_clk_pins[] = { 138 }; +static const unsigned int sdc2_cmd_pins[] = { 139 }; +static const unsigned int sdc2_data_pins[] = { 140 }; +static const unsigned int qdsd_clk_pins[] = { 141 }; +static const unsigned int qdsd_cmd_pins[] = { 142 }; +static const unsigned int qdsd_data0_pins[] = { 143 }; +static const unsigned int qdsd_data1_pins[] = { 144 }; +static const unsigned int qdsd_data2_pins[] = { 145 }; +static const unsigned int qdsd_data3_pins[] = { 146 }; + +enum msm8917_functions { + msm_mux_qdss_tracedata_b, + msm_mux_blsp_uart1, + msm_mux_gpio, + msm_mux_blsp_spi1, + msm_mux_adsp_ext, + msm_mux_blsp_i2c1, + msm_mux_prng_rosc, + msm_mux_qdss_cti_trig_out_b0, + msm_mux_blsp_spi2, + msm_mux_blsp_uart2, + msm_mux_blsp_uart3, + msm_mux_pbs0, + msm_mux_pbs1, + msm_mux_pwr_modem_enabled_b, + msm_mux_blsp_i2c3, + msm_mux_gcc_gp2_clk_b, + msm_mux_ldo_update, + msm_mux_atest_combodac_to_gpio_native, + msm_mux_ldo_en, + msm_mux_blsp_i2c2, + msm_mux_gcc_gp1_clk_b, + msm_mux_pbs2, + msm_mux_atest_gpsadc_dtest0_native, + msm_mux_blsp_spi3, + msm_mux_gcc_gp3_clk_b, + msm_mux_blsp_spi4, + msm_mux_blsp_uart4, + msm_mux_sec_mi2s, + msm_mux_pwr_nav_enabled_b, + msm_mux_codec_mad, + msm_mux_pwr_crypto_enabled_b, + msm_mux_blsp_i2c4, + msm_mux_blsp_spi5, + msm_mux_blsp_uart5, + msm_mux_qdss_traceclk_a, + msm_mux_atest_bbrx1, + msm_mux_m_voc, + msm_mux_qdss_cti_trig_in_a0, + msm_mux_qdss_cti_trig_in_b0, + msm_mux_blsp_i2c6, + msm_mux_qdss_traceclk_b, + msm_mux_atest_wlan0, + msm_mux_atest_bbrx0, + msm_mux_blsp_i2c5, + msm_mux_qdss_tracectl_a, + msm_mux_atest_gpsadc_dtest1_native, + msm_mux_qdss_tracedata_a, + msm_mux_blsp_spi6, + msm_mux_blsp_uart6, + msm_mux_qdss_tracectl_b, + msm_mux_atest_wlan1, + msm_mux_mdp_vsync, + msm_mux_pri_mi2s_mclk_a, + msm_mux_sec_mi2s_mclk_a, + msm_mux_cam_mclk, + msm_mux_cci_i2c, + msm_mux_pwr_modem_enabled_a, + msm_mux_cci_timer0, + msm_mux_cci_timer1, + msm_mux_cam1_standby, + msm_mux_pwr_nav_enabled_a, + msm_mux_cam1_rst, + msm_mux_pwr_crypto_enabled_a, + msm_mux_forced_usb, + msm_mux_qdss_cti_trig_out_b1, + msm_mux_cam2_rst, + msm_mux_webcam_standby, + msm_mux_cci_async, + msm_mux_webcam_rst, + msm_mux_ov_ldo, + msm_mux_sd_write, + msm_mux_accel_int, + msm_mux_gcc_gp1_clk_a, + msm_mux_alsp_int, + msm_mux_gcc_gp2_clk_a, + msm_mux_mag_int, + msm_mux_gcc_gp3_clk_a, + msm_mux_blsp6_spi, + msm_mux_fp_int, + msm_mux_qdss_cti_trig_in_b1, + msm_mux_uim_batt, + msm_mux_cam2_standby, + msm_mux_uim1_data, + msm_mux_uim1_clk, + msm_mux_uim1_reset, + msm_mux_uim1_present, + msm_mux_uim2_data, + msm_mux_uim2_clk, + msm_mux_uim2_reset, + msm_mux_uim2_present, + msm_mux_sensor_rst, + msm_mux_mipi_dsi0, + msm_mux_smb_int, + msm_mux_cam0_ldo, + msm_mux_us_euro, + msm_mux_atest_char3, + msm_mux_dbg_out, + msm_mux_bimc_dte0, + msm_mux_ts_resout, + msm_mux_ts_sample, + msm_mux_sec_mi2s_mclk_b, + msm_mux_pri_mi2s, + msm_mux_sdcard_det, + msm_mux_atest_char1, + msm_mux_ebi_cdc, + msm_mux_audio_reset, + msm_mux_atest_char0, + msm_mux_audio_ref, + msm_mux_cdc_pdm0, + msm_mux_pri_mi2s_mclk_b, + msm_mux_lpass_slimbus, + msm_mux_lpass_slimbus0, + msm_mux_lpass_slimbus1, + msm_mux_codec_int1, + msm_mux_codec_int2, + msm_mux_wcss_bt, + msm_mux_atest_char2, + msm_mux_ebi_ch0, + msm_mux_wcss_wlan2, + msm_mux_wcss_wlan1, + msm_mux_wcss_wlan0, + msm_mux_wcss_wlan, + msm_mux_wcss_fm, + msm_mux_ext_lpass, + msm_mux_cri_trng, + msm_mux_cri_trng1, + msm_mux_cri_trng0, + msm_mux_blsp_spi7, + msm_mux_blsp_uart7, + msm_mux_pri_mi2s_ws, + msm_mux_blsp_i2c7, + msm_mux_gcc_tlmm, + msm_mux_dmic0_clk, + msm_mux_dmic0_data, + msm_mux_key_volp, + msm_mux_qdss_cti_trig_in_a1, + msm_mux_us_emitter, + msm_mux_wsa_irq, + msm_mux_wsa_io, + msm_mux_blsp_spi8, + msm_mux_blsp_uart8, + msm_mux_blsp_i2c8, + msm_mux_gcc_plltest, + msm_mux_nav_pps_in_a, + msm_mux_pa_indicator, + msm_mux_modem_tsync, + msm_mux_nav_tsync, + msm_mux_nav_pps_in_b, + msm_mux_nav_pps, + msm_mux_gsm0_tx, + msm_mux_atest_char, + msm_mux_atest_tsens, + msm_mux_bimc_dte1, + msm_mux_ssbi_wtr1, + msm_mux_fp_gpio, + msm_mux_coex_uart, + msm_mux_key_snapshot, + msm_mux_key_focus, + msm_mux_nfc_pwr, + msm_mux_blsp8_spi, + msm_mux_qdss_cti_trig_out_a0, + msm_mux_qdss_cti_trig_out_a1, + msm_mux_NA, +}; + +static const char * const qdss_tracedata_b_groups[] = { + "gpio0", "gpio1", "gpio6", "gpio7", "gpio12", "gpio13", "gpio23", + "gpio42", "gpio43", "gpio44", "gpio47", "gpio66", "gpio86", "gpio87", + "gpio88", "gpio92", +}; +static const char * const blsp_uart1_groups[] = { + "gpio0", "gpio1", "gpio2", "gpio3", +}; +static const char * const gpio_groups[] = { + "gpio0", "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6", "gpio7", + "gpio8", "gpio9", "gpio10", "gpio11", "gpio12", "gpio13", "gpio14", + "gpio15", "gpio16", "gpio17", "gpio18", "gpio19", "gpio20", "gpio21", + "gpio22", "gpio23", "gpio24", "gpio25", "gpio26", "gpio27", "gpio28", + "gpio29", "gpio30", "gpio31", "gpio32", "gpio33", "gpio34", "gpio35", + "gpio36", "gpio37", "gpio38", "gpio39", "gpio40", "gpio41", "gpio42", + "gpio43", "gpio44", "gpio45", "gpio46", "gpio47", "gpio48", "gpio49", + "gpio50", "gpio51", "gpio52", "gpio53", "gpio54", "gpio55", "gpio56", + "gpio57", "gpio58", "gpio59", "gpio60", "gpio61", "gpio62", "gpio63", + "gpio64", "gpio65", "gpio66", "gpio67", "gpio68", "gpio69", "gpio70", + "gpio71", "gpio72", "gpio73", "gpio74", "gpio75", "gpio76", "gpio77", + "gpio78", "gpio79", "gpio80", "gpio81", "gpio82", "gpio83", "gpio84", + "gpio85", "gpio86", "gpio87", "gpio88", "gpio89", "gpio90", "gpio91", + "gpio92", "gpio93", "gpio94", "gpio95", "gpio96", "gpio97", "gpio98", + "gpio99", "gpio100", "gpio101", "gpio102", "gpio103", "gpio104", + "gpio105", "gpio106", "gpio107", "gpio108", "gpio109", "gpio110", + "gpio111", "gpio112", "gpio113", "gpio114", "gpio115", "gpio116", + "gpio117", "gpio118", "gpio119", "gpio120", "gpio121", "gpio122", + "gpio123", "gpio124", "gpio125", "gpio126", "gpio127", "gpio128", + "gpio129", "gpio130", "gpio131", "gpio132", "gpio133", +}; +static const char * const blsp_spi1_groups[] = { + "gpio0", "gpio1", "gpio2", "gpio3", +}; +static const char * const adsp_ext_groups[] = { + "gpio1", +}; +static const char * const blsp_i2c1_groups[] = { + "gpio2", "gpio3", +}; +static const char * const prng_rosc_groups[] = { + "gpio2", +}; +static const char * const qdss_cti_trig_out_b0_groups[] = { + "gpio2", +}; +static const char * const blsp_spi2_groups[] = { + "gpio4", "gpio5", "gpio6", "gpio7", +}; +static const char * const blsp_uart2_groups[] = { + "gpio4", "gpio5", "gpio6", "gpio7", +}; +static const char * const blsp_uart3_groups[] = { + "gpio8", "gpio9", "gpio10", "gpio11", +}; +static const char * const pbs0_groups[] = { + "gpio8", +}; +static const char * const pbs1_groups[] = { + "gpio9", +}; +static const char * const pwr_modem_enabled_b_groups[] = { + "gpio9", +}; +static const char * const blsp_i2c3_groups[] = { + "gpio10", "gpio11", +}; +static const char * const gcc_gp2_clk_b_groups[] = { + "gpio10", +}; +static const char * const ldo_update_groups[] = { + "gpio4", +}; +static const char * const atest_combodac_to_gpio_native_groups[] = { + "gpio4", "gpio12", "gpio13", "gpio20", "gpio21", "gpio28", "gpio29", + "gpio30", "gpio39", "gpio40", "gpio41", "gpio42", "gpio43", "gpio44", + "gpio45", "gpio46", "gpio47", "gpio48", "gpio67", "gpio115", +}; +static const char * const ldo_en_groups[] = { + "gpio5", +}; +static const char * const blsp_i2c2_groups[] = { + "gpio6", "gpio7", +}; +static const char * const gcc_gp1_clk_b_groups[] = { + "gpio6", +}; +static const char * const pbs2_groups[] = { + "gpio7", +}; +static const char * const atest_gpsadc_dtest0_native_groups[] = { + "gpio7", +}; +static const char * const blsp_spi3_groups[] = { + "gpio8", "gpio9", "gpio10", "gpio11", +}; +static const char * const gcc_gp3_clk_b_groups[] = { + "gpio11", +}; +static const char * const blsp_spi4_groups[] = { + "gpio12", "gpio13", "gpio14", "gpio15", +}; +static const char * const blsp_uart4_groups[] = { + "gpio12", "gpio13", "gpio14", "gpio15", +}; +static const char * const sec_mi2s_groups[] = { + "gpio12", "gpio13", "gpio94", "gpio95", +}; +static const char * const pwr_nav_enabled_b_groups[] = { + "gpio12", +}; +static const char * const codec_mad_groups[] = { + "gpio13", +}; +static const char * const pwr_crypto_enabled_b_groups[] = { + "gpio13", +}; +static const char * const blsp_i2c4_groups[] = { + "gpio14", "gpio15", +}; +static const char * const blsp_spi5_groups[] = { + "gpio16", "gpio17", "gpio18", "gpio19", +}; +static const char * const blsp_uart5_groups[] = { + "gpio16", "gpio17", "gpio18", "gpio19", +}; +static const char * const qdss_traceclk_a_groups[] = { + "gpio16", +}; +static const char * const atest_bbrx1_groups[] = { + "gpio16", +}; +static const char * const m_voc_groups[] = { + "gpio17", "gpio21", +}; +static const char * const qdss_cti_trig_in_a0_groups[] = { + "gpio17", +}; +static const char * const qdss_cti_trig_in_b0_groups[] = { + "gpio21", +}; +static const char * const blsp_i2c6_groups[] = { + "gpio22", "gpio23", +}; +static const char * const qdss_traceclk_b_groups[] = { + "gpio22", +}; +static const char * const atest_wlan0_groups[] = { + "gpio22", +}; +static const char * const atest_bbrx0_groups[] = { + "gpio17", +}; +static const char * const blsp_i2c5_groups[] = { + "gpio18", "gpio19", +}; +static const char * const qdss_tracectl_a_groups[] = { + "gpio18", +}; +static const char * const atest_gpsadc_dtest1_native_groups[] = { + "gpio18", +}; +static const char * const qdss_tracedata_a_groups[] = { + "gpio19", "gpio26", "gpio27", "gpio28", "gpio29", "gpio30", "gpio31", + "gpio32", "gpio33", "gpio34", "gpio35", "gpio36", "gpio38", "gpio39", + "gpio40", "gpio50", +}; +static const char * const blsp_spi6_groups[] = { + "gpio20", "gpio21", "gpio22", "gpio23", +}; +static const char * const blsp_uart6_groups[] = { + "gpio20", "gpio21", "gpio22", "gpio23", +}; +static const char * const qdss_tracectl_b_groups[] = { + "gpio20", +}; +static const char * const atest_wlan1_groups[] = { + "gpio23", +}; +static const char * const mdp_vsync_groups[] = { + "gpio24", "gpio25", +}; +static const char * const pri_mi2s_mclk_a_groups[] = { + "gpio25", +}; +static const char * const sec_mi2s_mclk_a_groups[] = { + "gpio25", +}; +static const char * const cam_mclk_groups[] = { + "gpio26", "gpio27", "gpio28", +}; +static const char * const cci_i2c_groups[] = { + "gpio29", "gpio30", "gpio31", "gpio32", +}; +static const char * const pwr_modem_enabled_a_groups[] = { + "gpio29", +}; +static const char * const cci_timer0_groups[] = { + "gpio33", +}; +static const char * const cci_timer1_groups[] = { + "gpio34", +}; +static const char * const cam1_standby_groups[] = { + "gpio35", +}; +static const char * const pwr_nav_enabled_a_groups[] = { + "gpio35", +}; +static const char * const cam1_rst_groups[] = { + "gpio36", +}; +static const char * const pwr_crypto_enabled_a_groups[] = { + "gpio36", +}; +static const char * const forced_usb_groups[] = { + "gpio37", +}; +static const char * const qdss_cti_trig_out_b1_groups[] = { + "gpio37", +}; +static const char * const cam2_rst_groups[] = { + "gpio38", +}; +static const char * const webcam_standby_groups[] = { + "gpio39", +}; +static const char * const cci_async_groups[] = { + "gpio39", +}; +static const char * const webcam_rst_groups[] = { + "gpio40", +}; +static const char * const ov_ldo_groups[] = { + "gpio41", +}; +static const char * const sd_write_groups[] = { + "gpio41", +}; +static const char * const accel_int_groups[] = { + "gpio42", +}; +static const char * const gcc_gp1_clk_a_groups[] = { + "gpio42", +}; +static const char * const alsp_int_groups[] = { + "gpio43", +}; +static const char * const gcc_gp2_clk_a_groups[] = { + "gpio43", +}; +static const char * const mag_int_groups[] = { + "gpio44", +}; +static const char * const gcc_gp3_clk_a_groups[] = { + "gpio44", +}; +static const char * const blsp6_spi_groups[] = { + "gpio47", +}; +static const char * const fp_int_groups[] = { + "gpio48", +}; +static const char * const qdss_cti_trig_in_b1_groups[] = { + "gpio48", +}; +static const char * const uim_batt_groups[] = { + "gpio49", +}; +static const char * const cam2_standby_groups[] = { + "gpio50", +}; +static const char * const uim1_data_groups[] = { + "gpio51", +}; +static const char * const uim1_clk_groups[] = { + "gpio52", +}; +static const char * const uim1_reset_groups[] = { + "gpio53", +}; +static const char * const uim1_present_groups[] = { + "gpio54", +}; +static const char * const uim2_data_groups[] = { + "gpio55", +}; +static const char * const uim2_clk_groups[] = { + "gpio56", +}; +static const char * const uim2_reset_groups[] = { + "gpio57", +}; +static const char * const uim2_present_groups[] = { + "gpio58", +}; +static const char * const sensor_rst_groups[] = { + "gpio59", +}; +static const char * const mipi_dsi0_groups[] = { + "gpio60", +}; +static const char * const smb_int_groups[] = { + "gpio61", +}; +static const char * const cam0_ldo_groups[] = { + "gpio62", +}; +static const char * const us_euro_groups[] = { + "gpio63", +}; +static const char * const atest_char3_groups[] = { + "gpio63", +}; +static const char * const dbg_out_groups[] = { + "gpio63", +}; +static const char * const bimc_dte0_groups[] = { + "gpio63", "gpio65", +}; +static const char * const ts_resout_groups[] = { + "gpio64", +}; +static const char * const ts_sample_groups[] = { + "gpio65", +}; +static const char * const sec_mi2s_mclk_b_groups[] = { + "gpio66", +}; +static const char * const pri_mi2s_groups[] = { + "gpio66", "gpio85", "gpio86", "gpio88", "gpio94", "gpio95", +}; +static const char * const sdcard_det_groups[] = { + "gpio67", +}; +static const char * const atest_char1_groups[] = { + "gpio67", +}; +static const char * const ebi_cdc_groups[] = { + "gpio67", "gpio69", "gpio118", "gpio119", "gpio120", "gpio123", +}; +static const char * const audio_reset_groups[] = { + "gpio68", +}; +static const char * const atest_char0_groups[] = { + "gpio68", +}; +static const char * const audio_ref_groups[] = { + "gpio69", +}; +static const char * const cdc_pdm0_groups[] = { + "gpio69", "gpio70", "gpio71", "gpio72", "gpio73", "gpio74", +}; +static const char * const pri_mi2s_mclk_b_groups[] = { + "gpio69", +}; +static const char * const lpass_slimbus_groups[] = { + "gpio70", +}; +static const char * const lpass_slimbus0_groups[] = { + "gpio71", +}; +static const char * const lpass_slimbus1_groups[] = { + "gpio72", +}; +static const char * const codec_int1_groups[] = { + "gpio73", +}; +static const char * const codec_int2_groups[] = { + "gpio74", +}; +static const char * const wcss_bt_groups[] = { + "gpio75", "gpio83", "gpio84", +}; +static const char * const atest_char2_groups[] = { + "gpio75", +}; +static const char * const ebi_ch0_groups[] = { + "gpio75", +}; +static const char * const wcss_wlan2_groups[] = { + "gpio76", +}; +static const char * const wcss_wlan1_groups[] = { + "gpio77", +}; +static const char * const wcss_wlan0_groups[] = { + "gpio78", +}; +static const char * const wcss_wlan_groups[] = { + "gpio79", "gpio80", +}; +static const char * const wcss_fm_groups[] = { + "gpio81", "gpio82", +}; +static const char * const ext_lpass_groups[] = { + "gpio81", +}; +static const char * const cri_trng_groups[] = { + "gpio82", +}; +static const char * const cri_trng1_groups[] = { + "gpio83", +}; +static const char * const cri_trng0_groups[] = { + "gpio84", +}; +static const char * const blsp_spi7_groups[] = { + "gpio85", "gpio86", "gpio87", "gpio88", +}; +static const char * const blsp_uart7_groups[] = { + "gpio85", "gpio86", "gpio87", "gpio88", +}; +static const char * const pri_mi2s_ws_groups[] = { + "gpio87", +}; +static const char * const blsp_i2c7_groups[] = { + "gpio87", "gpio88", +}; +static const char * const gcc_tlmm_groups[] = { + "gpio87", +}; +static const char * const dmic0_clk_groups[] = { + "gpio89", +}; +static const char * const dmic0_data_groups[] = { + "gpio90", +}; +static const char * const key_volp_groups[] = { + "gpio91", +}; +static const char * const qdss_cti_trig_in_a1_groups[] = { + "gpio91", +}; +static const char * const us_emitter_groups[] = { + "gpio92", +}; +static const char * const wsa_irq_groups[] = { + "gpio93", +}; +static const char * const wsa_io_groups[] = { + "gpio94", "gpio95", +}; +static const char * const blsp_spi8_groups[] = { + "gpio96", "gpio97", "gpio98", "gpio99", +}; +static const char * const blsp_uart8_groups[] = { + "gpio96", "gpio97", "gpio98", "gpio99", +}; +static const char * const blsp_i2c8_groups[] = { + "gpio98", "gpio99", +}; +static const char * const gcc_plltest_groups[] = { + "gpio98", "gpio99", +}; +static const char * const nav_pps_in_a_groups[] = { + "gpio115", +}; +static const char * const pa_indicator_groups[] = { + "gpio116", +}; +static const char * const modem_tsync_groups[] = { + "gpio117", +}; +static const char * const nav_tsync_groups[] = { + "gpio117", +}; +static const char * const nav_pps_in_b_groups[] = { + "gpio117", +}; +static const char * const nav_pps_groups[] = { + "gpio117", +}; +static const char * const gsm0_tx_groups[] = { + "gpio119", +}; +static const char * const atest_char_groups[] = { + "gpio120", +}; +static const char * const atest_tsens_groups[] = { + "gpio120", +}; +static const char * const bimc_dte1_groups[] = { + "gpio121", "gpio122", +}; +static const char * const ssbi_wtr1_groups[] = { + "gpio122", "gpio123", +}; +static const char * const fp_gpio_groups[] = { + "gpio124", +}; +static const char * const coex_uart_groups[] = { + "gpio124", "gpio127", +}; +static const char * const key_snapshot_groups[] = { + "gpio127", +}; +static const char * const key_focus_groups[] = { + "gpio128", +}; +static const char * const nfc_pwr_groups[] = { + "gpio129", +}; +static const char * const blsp8_spi_groups[] = { + "gpio130", +}; +static const char * const qdss_cti_trig_out_a0_groups[] = { + "gpio132", +}; +static const char * const qdss_cti_trig_out_a1_groups[] = { + "gpio133", +}; + +static const struct msm_function msm8917_functions[] = { + FUNCTION(qdss_tracedata_b), + FUNCTION(blsp_uart1), + FUNCTION(gpio), + FUNCTION(blsp_spi1), + FUNCTION(adsp_ext), + FUNCTION(blsp_i2c1), + FUNCTION(prng_rosc), + FUNCTION(qdss_cti_trig_out_b0), + FUNCTION(blsp_spi2), + FUNCTION(blsp_uart2), + FUNCTION(blsp_uart3), + FUNCTION(pbs0), + FUNCTION(pbs1), + FUNCTION(pwr_modem_enabled_b), + FUNCTION(blsp_i2c3), + FUNCTION(gcc_gp2_clk_b), + FUNCTION(ldo_update), + FUNCTION(atest_combodac_to_gpio_native), + FUNCTION(ldo_en), + FUNCTION(blsp_i2c2), + FUNCTION(gcc_gp1_clk_b), + FUNCTION(pbs2), + FUNCTION(atest_gpsadc_dtest0_native), + FUNCTION(blsp_spi3), + FUNCTION(gcc_gp3_clk_b), + FUNCTION(blsp_spi4), + FUNCTION(blsp_uart4), + FUNCTION(sec_mi2s), + FUNCTION(pwr_nav_enabled_b), + FUNCTION(codec_mad), + FUNCTION(pwr_crypto_enabled_b), + FUNCTION(blsp_i2c4), + FUNCTION(blsp_spi5), + FUNCTION(blsp_uart5), + FUNCTION(qdss_traceclk_a), + FUNCTION(atest_bbrx1), + FUNCTION(m_voc), + FUNCTION(qdss_cti_trig_in_a0), + FUNCTION(qdss_cti_trig_in_b0), + FUNCTION(blsp_i2c6), + FUNCTION(qdss_traceclk_b), + FUNCTION(atest_wlan0), + FUNCTION(atest_bbrx0), + FUNCTION(blsp_i2c5), + FUNCTION(qdss_tracectl_a), + FUNCTION(atest_gpsadc_dtest1_native), + FUNCTION(qdss_tracedata_a), + FUNCTION(blsp_spi6), + FUNCTION(blsp_uart6), + FUNCTION(qdss_tracectl_b), + FUNCTION(atest_wlan1), + FUNCTION(mdp_vsync), + FUNCTION(pri_mi2s_mclk_a), + FUNCTION(sec_mi2s_mclk_a), + FUNCTION(cam_mclk), + FUNCTION(cci_i2c), + FUNCTION(pwr_modem_enabled_a), + FUNCTION(cci_timer0), + FUNCTION(cci_timer1), + FUNCTION(cam1_standby), + FUNCTION(pwr_nav_enabled_a), + FUNCTION(cam1_rst), + FUNCTION(pwr_crypto_enabled_a), + FUNCTION(forced_usb), + FUNCTION(qdss_cti_trig_out_b1), + FUNCTION(cam2_rst), + FUNCTION(webcam_standby), + FUNCTION(cci_async), + FUNCTION(webcam_rst), + FUNCTION(ov_ldo), + FUNCTION(sd_write), + FUNCTION(accel_int), + FUNCTION(gcc_gp1_clk_a), + FUNCTION(alsp_int), + FUNCTION(gcc_gp2_clk_a), + FUNCTION(mag_int), + FUNCTION(gcc_gp3_clk_a), + FUNCTION(blsp6_spi), + FUNCTION(fp_int), + FUNCTION(qdss_cti_trig_in_b1), + FUNCTION(uim_batt), + FUNCTION(cam2_standby), + FUNCTION(uim1_data), + FUNCTION(uim1_clk), + FUNCTION(uim1_reset), + FUNCTION(uim1_present), + FUNCTION(uim2_data), + FUNCTION(uim2_clk), + FUNCTION(uim2_reset), + FUNCTION(uim2_present), + FUNCTION(sensor_rst), + FUNCTION(mipi_dsi0), + FUNCTION(smb_int), + FUNCTION(cam0_ldo), + FUNCTION(us_euro), + FUNCTION(atest_char3), + FUNCTION(dbg_out), + FUNCTION(bimc_dte0), + FUNCTION(ts_resout), + FUNCTION(ts_sample), + FUNCTION(sec_mi2s_mclk_b), + FUNCTION(pri_mi2s), + FUNCTION(sdcard_det), + FUNCTION(atest_char1), + FUNCTION(ebi_cdc), + FUNCTION(audio_reset), + FUNCTION(atest_char0), + FUNCTION(audio_ref), + FUNCTION(cdc_pdm0), + FUNCTION(pri_mi2s_mclk_b), + FUNCTION(lpass_slimbus), + FUNCTION(lpass_slimbus0), + FUNCTION(lpass_slimbus1), + FUNCTION(codec_int1), + FUNCTION(codec_int2), + FUNCTION(wcss_bt), + FUNCTION(atest_char2), + FUNCTION(ebi_ch0), + FUNCTION(wcss_wlan2), + FUNCTION(wcss_wlan1), + FUNCTION(wcss_wlan0), + FUNCTION(wcss_wlan), + FUNCTION(wcss_fm), + FUNCTION(ext_lpass), + FUNCTION(cri_trng), + FUNCTION(cri_trng1), + FUNCTION(cri_trng0), + FUNCTION(blsp_spi7), + FUNCTION(blsp_uart7), + FUNCTION(pri_mi2s_ws), + FUNCTION(blsp_i2c7), + FUNCTION(gcc_tlmm), + FUNCTION(dmic0_clk), + FUNCTION(dmic0_data), + FUNCTION(key_volp), + FUNCTION(qdss_cti_trig_in_a1), + FUNCTION(us_emitter), + FUNCTION(wsa_irq), + FUNCTION(wsa_io), + FUNCTION(blsp_spi8), + FUNCTION(blsp_uart8), + FUNCTION(blsp_i2c8), + FUNCTION(gcc_plltest), + FUNCTION(nav_pps_in_a), + FUNCTION(pa_indicator), + FUNCTION(modem_tsync), + FUNCTION(nav_tsync), + FUNCTION(nav_pps_in_b), + FUNCTION(nav_pps), + FUNCTION(gsm0_tx), + FUNCTION(atest_char), + FUNCTION(atest_tsens), + FUNCTION(bimc_dte1), + FUNCTION(ssbi_wtr1), + FUNCTION(fp_gpio), + FUNCTION(coex_uart), + FUNCTION(key_snapshot), + FUNCTION(key_focus), + FUNCTION(nfc_pwr), + FUNCTION(blsp8_spi), + FUNCTION(qdss_cti_trig_out_a0), + FUNCTION(qdss_cti_trig_out_a1), +}; + +static const struct msm_pingroup msm8917_groups[] = { + PINGROUP(0, blsp_spi1, blsp_uart1, qdss_tracedata_b, NA, NA, NA, NA, + NA, NA), + PINGROUP(1, blsp_spi1, blsp_uart1, adsp_ext, NA, NA, NA, NA, NA, + qdss_tracedata_b), + PINGROUP(2, blsp_spi1, blsp_uart1, blsp_i2c1, prng_rosc, NA, NA, NA, + NA, NA), + PINGROUP(3, blsp_spi1, blsp_uart1, blsp_i2c1, NA, NA, NA, NA, NA, NA), + PINGROUP(4, blsp_spi2, blsp_uart2, ldo_update, NA, + atest_combodac_to_gpio_native, NA, NA, NA, NA), + PINGROUP(5, blsp_spi2, blsp_uart2, ldo_en, NA, NA, NA, NA, NA, NA), + PINGROUP(6, blsp_spi2, blsp_uart2, blsp_i2c2, gcc_gp1_clk_b, + qdss_tracedata_b, NA, NA, NA, NA), + PINGROUP(7, blsp_spi2, blsp_uart2, blsp_i2c2, pbs2, NA, + qdss_tracedata_b, NA, atest_gpsadc_dtest0_native, NA), + PINGROUP(8, blsp_spi3, blsp_uart3, pbs0, NA, NA, NA, NA, NA, NA), + PINGROUP(9, blsp_spi3, blsp_uart3, pbs1, pwr_modem_enabled_b, NA, NA, + NA, NA, NA), + PINGROUP(10, blsp_spi3, blsp_uart3, blsp_i2c3, gcc_gp2_clk_b, NA, NA, + NA, NA, NA), + PINGROUP(11, blsp_spi3, blsp_uart3, blsp_i2c3, gcc_gp3_clk_b, NA, NA, + NA, NA, NA), + PINGROUP(12, blsp_spi4, blsp_uart4, sec_mi2s, pwr_nav_enabled_b, NA, + NA, NA, NA, NA), + PINGROUP(13, blsp_spi4, blsp_uart4, sec_mi2s, pwr_crypto_enabled_b, NA, + NA, NA, NA, NA), + PINGROUP(14, blsp_spi4, blsp_uart4, blsp_i2c4, NA, NA, NA, NA, NA, NA), + PINGROUP(15, blsp_spi4, blsp_uart4, blsp_i2c4, NA, NA, NA, NA, NA, NA), + PINGROUP(16, blsp_spi5, blsp_uart5, NA, NA, NA, NA, qdss_traceclk_a, + NA, atest_bbrx1), + PINGROUP(17, blsp_spi5, blsp_uart5, m_voc, qdss_cti_trig_in_a0, NA, + atest_bbrx0, NA, NA, NA), + PINGROUP(18, blsp_spi5, blsp_uart5, blsp_i2c5, qdss_tracectl_a, NA, + atest_gpsadc_dtest1_native, NA, NA, NA), + PINGROUP(19, blsp_spi5, blsp_uart5, blsp_i2c5, qdss_tracedata_a, NA, + NA, NA, NA, NA), + PINGROUP(20, blsp_spi6, blsp_uart6, NA, NA, NA, NA, NA, NA, + qdss_tracectl_b), + PINGROUP(21, blsp_spi6, blsp_uart6, m_voc, NA, NA, NA, NA, NA, + qdss_cti_trig_in_b0), + PINGROUP(22, blsp_spi6, blsp_uart6, blsp_i2c6, qdss_traceclk_b, NA, + atest_wlan0, NA, NA, NA), + PINGROUP(23, blsp_spi6, blsp_uart6, blsp_i2c6, qdss_tracedata_b, NA, + atest_wlan1, NA, NA, NA), + PINGROUP(24, mdp_vsync, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(25, mdp_vsync, pri_mi2s_mclk_a, sec_mi2s_mclk_a, NA, NA, NA, + NA, NA, NA), + PINGROUP(26, cam_mclk, NA, NA, NA, NA, NA, qdss_tracedata_a, NA, NA), + PINGROUP(27, cam_mclk, NA, NA, NA, NA, NA, NA, NA, qdss_tracedata_a), + PINGROUP(28, cam_mclk, NA, NA, NA, NA, NA, qdss_tracedata_a, NA, + atest_combodac_to_gpio_native), + PINGROUP(29, cci_i2c, pwr_modem_enabled_a, NA, NA, NA, NA, NA, + qdss_tracedata_a, NA), + PINGROUP(30, cci_i2c, NA, NA, NA, NA, NA, NA, NA, qdss_tracedata_a), + PINGROUP(31, cci_i2c, NA, NA, NA, NA, NA, NA, NA, qdss_tracedata_a), + PINGROUP(32, cci_i2c, NA, NA, NA, NA, NA, NA, NA, qdss_tracedata_a), + PINGROUP(33, cci_timer0, NA, NA, NA, NA, NA, NA, NA, qdss_tracedata_a), + PINGROUP(34, cci_timer1, NA, NA, NA, NA, NA, NA, NA, qdss_tracedata_a), + PINGROUP(35, pwr_nav_enabled_a, NA, NA, NA, NA, NA, NA, NA, + qdss_tracedata_a), + PINGROUP(36, pwr_crypto_enabled_a, NA, NA, NA, NA, NA, NA, NA, + qdss_tracedata_a), + PINGROUP(37, NA, NA, NA, NA, NA, qdss_cti_trig_out_b1, NA, NA, NA), + PINGROUP(38, NA, qdss_tracedata_a, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(39, cci_async, NA, NA, NA, NA, NA, qdss_tracedata_a, NA, + atest_combodac_to_gpio_native), + PINGROUP(40, NA, NA, NA, NA, qdss_tracedata_a, NA, + atest_combodac_to_gpio_native, NA, NA), + PINGROUP(41, sd_write, NA, NA, NA, NA, NA, NA, NA, + atest_combodac_to_gpio_native), + PINGROUP(42, gcc_gp1_clk_a, qdss_tracedata_b, NA, + atest_combodac_to_gpio_native, NA, NA, NA, NA, NA), + PINGROUP(43, gcc_gp2_clk_a, qdss_tracedata_b, NA, + atest_combodac_to_gpio_native, NA, NA, NA, NA, NA), + PINGROUP(44, gcc_gp3_clk_a, qdss_tracedata_b, NA, + atest_combodac_to_gpio_native, NA, NA, NA, NA, NA), + PINGROUP(45, NA, NA, atest_combodac_to_gpio_native, NA, NA, NA, NA, NA, + NA), + PINGROUP(46, NA, NA, atest_combodac_to_gpio_native, NA, NA, NA, NA, NA, + NA), + PINGROUP(47, blsp6_spi, NA, qdss_tracedata_b, NA, + atest_combodac_to_gpio_native, NA, NA, NA, NA), + PINGROUP(48, NA, qdss_cti_trig_in_b1, NA, + atest_combodac_to_gpio_native, NA, NA, NA, NA, NA), + PINGROUP(49, uim_batt, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(50, qdss_tracedata_a, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(51, uim1_data, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(52, uim1_clk, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(53, uim1_reset, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(54, uim1_present, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(55, uim2_data, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(56, uim2_clk, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(57, uim2_reset, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(58, uim2_present, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(59, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(60, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(61, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(62, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(63, atest_char3, dbg_out, bimc_dte0, NA, NA, NA, NA, NA, NA), + PINGROUP(64, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(65, bimc_dte0, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(66, sec_mi2s_mclk_b, pri_mi2s, NA, qdss_tracedata_b, NA, NA, + NA, NA, NA), + PINGROUP(67, atest_char1, ebi_cdc, NA, atest_combodac_to_gpio_native, + NA, NA, NA, NA, NA), + PINGROUP(68, atest_char0, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(69, audio_ref, cdc_pdm0, pri_mi2s_mclk_b, ebi_cdc, NA, NA, NA, + NA, NA), + PINGROUP(70, lpass_slimbus, cdc_pdm0, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(71, lpass_slimbus0, cdc_pdm0, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(72, lpass_slimbus1, cdc_pdm0, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(73, cdc_pdm0, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(74, cdc_pdm0, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(75, wcss_bt, atest_char2, NA, ebi_ch0, NA, NA, NA, NA, NA), + PINGROUP(76, wcss_wlan2, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(77, wcss_wlan1, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(78, wcss_wlan0, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(79, wcss_wlan, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(80, wcss_wlan, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(81, wcss_fm, ext_lpass, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(82, wcss_fm, cri_trng, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(83, wcss_bt, cri_trng1, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(84, wcss_bt, cri_trng0, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(85, pri_mi2s, blsp_spi7, blsp_uart7, NA, NA, NA, NA, NA, NA), + PINGROUP(86, pri_mi2s, blsp_spi7, blsp_uart7, qdss_tracedata_b, NA, NA, + NA, NA, NA), + PINGROUP(87, pri_mi2s_ws, blsp_spi7, blsp_uart7, blsp_i2c7, + qdss_tracedata_b, gcc_tlmm, NA, NA, NA), + PINGROUP(88, pri_mi2s, blsp_spi7, blsp_uart7, blsp_i2c7, NA, NA, NA, + NA, NA), + PINGROUP(89, dmic0_clk, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(90, dmic0_data, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(91, NA, NA, NA, NA, NA, qdss_cti_trig_in_a1, NA, NA, NA), + PINGROUP(92, NA, NA, NA, NA, NA, qdss_tracedata_b, NA, NA, NA), + PINGROUP(93, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(94, wsa_io, sec_mi2s, pri_mi2s, NA, NA, NA, NA, NA, NA), + PINGROUP(95, wsa_io, sec_mi2s, pri_mi2s, NA, NA, NA, NA, NA, NA), + PINGROUP(96, blsp_spi8, blsp_uart8, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(97, blsp_spi8, blsp_uart8, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(98, blsp_spi8, blsp_uart8, blsp_i2c8, gcc_plltest, NA, NA, NA, + NA, NA), + PINGROUP(99, blsp_spi8, blsp_uart8, blsp_i2c8, gcc_plltest, NA, NA, NA, + NA, NA), + PINGROUP(100, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(101, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(102, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(103, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(104, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(105, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(106, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(107, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(108, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(109, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(110, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(111, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(112, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(113, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(114, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(115, NA, NA, nav_pps_in_a, NA, atest_combodac_to_gpio_native, + NA, NA, NA, NA), + PINGROUP(116, NA, pa_indicator, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(117, NA, modem_tsync, nav_tsync, nav_pps_in_b, nav_pps, NA, + NA, NA, NA), + PINGROUP(118, NA, ebi_cdc, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(119, gsm0_tx, NA, ebi_cdc, NA, NA, NA, NA, NA, NA), + PINGROUP(120, NA, atest_char, ebi_cdc, NA, atest_tsens, NA, NA, NA, NA), + PINGROUP(121, NA, NA, NA, bimc_dte1, NA, NA, NA, NA, NA), + PINGROUP(122, NA, ssbi_wtr1, NA, NA, bimc_dte1, NA, NA, NA, NA), + PINGROUP(123, NA, ssbi_wtr1, ebi_cdc, NA, NA, NA, NA, NA, NA), + PINGROUP(124, coex_uart, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(125, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(126, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(127, coex_uart, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(128, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(129, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(130, blsp8_spi, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(131, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(132, qdss_cti_trig_out_a0, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(133, qdss_cti_trig_out_a1, NA, NA, NA, NA, NA, NA, NA, NA), + SDC_QDSD_PINGROUP(sdc1_clk, 0x10a000, 13, 6), + SDC_QDSD_PINGROUP(sdc1_cmd, 0x10a000, 11, 3), + SDC_QDSD_PINGROUP(sdc1_data, 0x10a000, 9, 0), + SDC_QDSD_PINGROUP(sdc1_rclk, 0x10a000, 15, 0), + SDC_QDSD_PINGROUP(sdc2_clk, 0x109000, 14, 6), + SDC_QDSD_PINGROUP(sdc2_cmd, 0x109000, 11, 3), + SDC_QDSD_PINGROUP(sdc2_data, 0x109000, 9, 0), + SDC_QDSD_PINGROUP(qdsd_clk, 0x19c000, 3, 0), + SDC_QDSD_PINGROUP(qdsd_cmd, 0x19c000, 8, 5), + SDC_QDSD_PINGROUP(qdsd_data0, 0x19c000, 13, 10), + SDC_QDSD_PINGROUP(qdsd_data1, 0x19c000, 18, 15), + SDC_QDSD_PINGROUP(qdsd_data2, 0x19c000, 23, 20), + SDC_QDSD_PINGROUP(qdsd_data3, 0x19c000, 28, 25), +}; + +static const struct msm_pinctrl_soc_data msm8917_pinctrl = { + .pins = msm8917_pins, + .npins = ARRAY_SIZE(msm8917_pins), + .functions = msm8917_functions, + .nfunctions = ARRAY_SIZE(msm8917_functions), + .groups = msm8917_groups, + .ngroups = ARRAY_SIZE(msm8917_groups), + .ngpios = 134, +}; + +static int msm8917_pinctrl_probe(struct platform_device *pdev) +{ + return msm_pinctrl_probe(pdev, &msm8917_pinctrl); +} + +static const struct of_device_id msm8917_pinctrl_of_match[] = { + { .compatible = "qcom,msm8917-pinctrl", }, + { }, +}; + +static struct platform_driver msm8917_pinctrl_driver = { + .driver = { + .name = "msm8917-pinctrl", + .of_match_table = msm8917_pinctrl_of_match, + }, + .probe = msm8917_pinctrl_probe, + .remove = msm_pinctrl_remove, +}; + +static int __init msm8917_pinctrl_init(void) +{ + return platform_driver_register(&msm8917_pinctrl_driver); +} +arch_initcall(msm8917_pinctrl_init); + +static void __exit msm8917_pinctrl_exit(void) +{ + platform_driver_unregister(&msm8917_pinctrl_driver); +} +module_exit(msm8917_pinctrl_exit); + +MODULE_DESCRIPTION("QTI msm8917 pinctrl driver"); +MODULE_LICENSE("GPL v2"); +MODULE_DEVICE_TABLE(of, msm8917_pinctrl_of_match); diff --git a/drivers/pinctrl/qcom/pinctrl-msm8937.c b/drivers/pinctrl/qcom/pinctrl-msm8937.c new file mode 100644 index 000000000000..e1f1f0175acf --- /dev/null +++ b/drivers/pinctrl/qcom/pinctrl-msm8937.c @@ -0,0 +1,1471 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015, 2018, 2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include + +#include "pinctrl-msm.h" + +#define FUNCTION(fname) \ + [msm_mux_##fname] = { \ + .name = #fname, \ + .groups = fname##_groups, \ + .ngroups = ARRAY_SIZE(fname##_groups), \ + } + +#define REG_BASE 0x0 +#define REG_SIZE 0x1000 +#define PINGROUP(id, f1, f2, f3, f4, f5, f6, f7, f8, f9) \ + { \ + .name = "gpio" #id, \ + .pins = gpio##id##_pins, \ + .npins = (unsigned int)ARRAY_SIZE(gpio##id##_pins), \ + .funcs = (int[]){ \ + msm_mux_gpio, /* gpio mode */ \ + msm_mux_##f1, \ + msm_mux_##f2, \ + msm_mux_##f3, \ + msm_mux_##f4, \ + msm_mux_##f5, \ + msm_mux_##f6, \ + msm_mux_##f7, \ + msm_mux_##f8, \ + msm_mux_##f9 \ + }, \ + .nfuncs = 10, \ + .ctl_reg = REG_BASE + REG_SIZE * id, \ + .io_reg = REG_BASE + 0x4 + REG_SIZE * id, \ + .intr_cfg_reg = REG_BASE + 0x8 + REG_SIZE * id, \ + .intr_status_reg = REG_BASE + 0xc + REG_SIZE * id, \ + .intr_target_reg = REG_BASE + 0x8 + REG_SIZE * id, \ + .mux_bit = 2, \ + .pull_bit = 0, \ + .drv_bit = 6, \ + .oe_bit = 9, \ + .in_bit = 0, \ + .out_bit = 1, \ + .intr_enable_bit = 0, \ + .intr_status_bit = 0, \ + .intr_target_bit = 5, \ + .intr_target_kpss_val = 4, \ + .intr_raw_status_bit = 4, \ + .intr_polarity_bit = 1, \ + .intr_detection_bit = 2, \ + .intr_detection_width = 2, \ + } + +#define SDC_QDSD_PINGROUP(pg_name, ctl, pull, drv) \ + { \ + .name = #pg_name, \ + .pins = pg_name##_pins, \ + .npins = (unsigned int)ARRAY_SIZE(pg_name##_pins), \ + .ctl_reg = ctl, \ + .io_reg = 0, \ + .intr_cfg_reg = 0, \ + .intr_status_reg = 0, \ + .intr_target_reg = 0, \ + .mux_bit = -1, \ + .pull_bit = pull, \ + .drv_bit = drv, \ + .oe_bit = -1, \ + .in_bit = -1, \ + .out_bit = -1, \ + .intr_enable_bit = -1, \ + .intr_status_bit = -1, \ + .intr_target_bit = -1, \ + .intr_raw_status_bit = -1, \ + .intr_polarity_bit = -1, \ + .intr_detection_bit = -1, \ + .intr_detection_width = -1, \ + } +static const struct pinctrl_pin_desc msm8937_pins[] = { + PINCTRL_PIN(0, "GPIO_0"), + PINCTRL_PIN(1, "GPIO_1"), + PINCTRL_PIN(2, "GPIO_2"), + PINCTRL_PIN(3, "GPIO_3"), + PINCTRL_PIN(4, "GPIO_4"), + PINCTRL_PIN(5, "GPIO_5"), + PINCTRL_PIN(6, "GPIO_6"), + PINCTRL_PIN(7, "GPIO_7"), + PINCTRL_PIN(8, "GPIO_8"), + PINCTRL_PIN(9, "GPIO_9"), + PINCTRL_PIN(10, "GPIO_10"), + PINCTRL_PIN(11, "GPIO_11"), + PINCTRL_PIN(12, "GPIO_12"), + PINCTRL_PIN(13, "GPIO_13"), + PINCTRL_PIN(14, "GPIO_14"), + PINCTRL_PIN(15, "GPIO_15"), + PINCTRL_PIN(16, "GPIO_16"), + PINCTRL_PIN(17, "GPIO_17"), + PINCTRL_PIN(18, "GPIO_18"), + PINCTRL_PIN(19, "GPIO_19"), + PINCTRL_PIN(20, "GPIO_20"), + PINCTRL_PIN(21, "GPIO_21"), + PINCTRL_PIN(22, "GPIO_22"), + PINCTRL_PIN(23, "GPIO_23"), + PINCTRL_PIN(24, "GPIO_24"), + PINCTRL_PIN(25, "GPIO_25"), + PINCTRL_PIN(26, "GPIO_26"), + PINCTRL_PIN(27, "GPIO_27"), + PINCTRL_PIN(28, "GPIO_28"), + PINCTRL_PIN(29, "GPIO_29"), + PINCTRL_PIN(30, "GPIO_30"), + PINCTRL_PIN(31, "GPIO_31"), + PINCTRL_PIN(32, "GPIO_32"), + PINCTRL_PIN(33, "GPIO_33"), + PINCTRL_PIN(34, "GPIO_34"), + PINCTRL_PIN(35, "GPIO_35"), + PINCTRL_PIN(36, "GPIO_36"), + PINCTRL_PIN(37, "GPIO_37"), + PINCTRL_PIN(38, "GPIO_38"), + PINCTRL_PIN(39, "GPIO_39"), + PINCTRL_PIN(40, "GPIO_40"), + PINCTRL_PIN(41, "GPIO_41"), + PINCTRL_PIN(42, "GPIO_42"), + PINCTRL_PIN(43, "GPIO_43"), + PINCTRL_PIN(44, "GPIO_44"), + PINCTRL_PIN(45, "GPIO_45"), + PINCTRL_PIN(46, "GPIO_46"), + PINCTRL_PIN(47, "GPIO_47"), + PINCTRL_PIN(48, "GPIO_48"), + PINCTRL_PIN(49, "GPIO_49"), + PINCTRL_PIN(50, "GPIO_50"), + PINCTRL_PIN(51, "GPIO_51"), + PINCTRL_PIN(52, "GPIO_52"), + PINCTRL_PIN(53, "GPIO_53"), + PINCTRL_PIN(54, "GPIO_54"), + PINCTRL_PIN(55, "GPIO_55"), + PINCTRL_PIN(56, "GPIO_56"), + PINCTRL_PIN(57, "GPIO_57"), + PINCTRL_PIN(58, "GPIO_58"), + PINCTRL_PIN(59, "GPIO_59"), + PINCTRL_PIN(60, "GPIO_60"), + PINCTRL_PIN(61, "GPIO_61"), + PINCTRL_PIN(62, "GPIO_62"), + PINCTRL_PIN(63, "GPIO_63"), + PINCTRL_PIN(64, "GPIO_64"), + PINCTRL_PIN(65, "GPIO_65"), + PINCTRL_PIN(66, "GPIO_66"), + PINCTRL_PIN(67, "GPIO_67"), + PINCTRL_PIN(68, "GPIO_68"), + PINCTRL_PIN(69, "GPIO_69"), + PINCTRL_PIN(70, "GPIO_70"), + PINCTRL_PIN(71, "GPIO_71"), + PINCTRL_PIN(72, "GPIO_72"), + PINCTRL_PIN(73, "GPIO_73"), + PINCTRL_PIN(74, "GPIO_74"), + PINCTRL_PIN(75, "GPIO_75"), + PINCTRL_PIN(76, "GPIO_76"), + PINCTRL_PIN(77, "GPIO_77"), + PINCTRL_PIN(78, "GPIO_78"), + PINCTRL_PIN(79, "GPIO_79"), + PINCTRL_PIN(80, "GPIO_80"), + PINCTRL_PIN(81, "GPIO_81"), + PINCTRL_PIN(82, "GPIO_82"), + PINCTRL_PIN(83, "GPIO_83"), + PINCTRL_PIN(84, "GPIO_84"), + PINCTRL_PIN(85, "GPIO_85"), + PINCTRL_PIN(86, "GPIO_86"), + PINCTRL_PIN(87, "GPIO_87"), + PINCTRL_PIN(88, "GPIO_88"), + PINCTRL_PIN(89, "GPIO_89"), + PINCTRL_PIN(90, "GPIO_90"), + PINCTRL_PIN(91, "GPIO_91"), + PINCTRL_PIN(92, "GPIO_92"), + PINCTRL_PIN(93, "GPIO_93"), + PINCTRL_PIN(94, "GPIO_94"), + PINCTRL_PIN(95, "GPIO_95"), + PINCTRL_PIN(96, "GPIO_96"), + PINCTRL_PIN(97, "GPIO_97"), + PINCTRL_PIN(98, "GPIO_98"), + PINCTRL_PIN(99, "GPIO_99"), + PINCTRL_PIN(100, "GPIO_100"), + PINCTRL_PIN(101, "GPIO_101"), + PINCTRL_PIN(102, "GPIO_102"), + PINCTRL_PIN(103, "GPIO_103"), + PINCTRL_PIN(104, "GPIO_104"), + PINCTRL_PIN(105, "GPIO_105"), + PINCTRL_PIN(106, "GPIO_106"), + PINCTRL_PIN(107, "GPIO_107"), + PINCTRL_PIN(108, "GPIO_108"), + PINCTRL_PIN(109, "GPIO_109"), + PINCTRL_PIN(110, "GPIO_110"), + PINCTRL_PIN(111, "GPIO_111"), + PINCTRL_PIN(112, "GPIO_112"), + PINCTRL_PIN(113, "GPIO_113"), + PINCTRL_PIN(114, "GPIO_114"), + PINCTRL_PIN(115, "GPIO_115"), + PINCTRL_PIN(116, "GPIO_116"), + PINCTRL_PIN(117, "GPIO_117"), + PINCTRL_PIN(118, "GPIO_118"), + PINCTRL_PIN(119, "GPIO_119"), + PINCTRL_PIN(120, "GPIO_120"), + PINCTRL_PIN(121, "GPIO_121"), + PINCTRL_PIN(122, "GPIO_122"), + PINCTRL_PIN(123, "GPIO_123"), + PINCTRL_PIN(124, "GPIO_124"), + PINCTRL_PIN(125, "GPIO_125"), + PINCTRL_PIN(126, "GPIO_126"), + PINCTRL_PIN(127, "GPIO_127"), + PINCTRL_PIN(128, "GPIO_128"), + PINCTRL_PIN(129, "GPIO_129"), + PINCTRL_PIN(130, "GPIO_130"), + PINCTRL_PIN(131, "GPIO_131"), + PINCTRL_PIN(132, "GPIO_132"), + PINCTRL_PIN(133, "GPIO_133"), + PINCTRL_PIN(134, "SDC1_CLK"), + PINCTRL_PIN(135, "SDC1_CMD"), + PINCTRL_PIN(136, "SDC1_DATA"), + PINCTRL_PIN(137, "SDC1_RCLK"), + PINCTRL_PIN(138, "SDC2_CLK"), + PINCTRL_PIN(139, "SDC2_CMD"), + PINCTRL_PIN(140, "SDC2_DATA"), + PINCTRL_PIN(141, "QDSD_CLK"), + PINCTRL_PIN(142, "QDSD_CMD"), + PINCTRL_PIN(143, "QDSD_DATA0"), + PINCTRL_PIN(144, "QDSD_DATA1"), + PINCTRL_PIN(145, "QDSD_DATA2"), + PINCTRL_PIN(146, "QDSD_DATA3"), +}; + +#define DECLARE_MSM_GPIO_PINS(pin) \ + static const unsigned int gpio##pin##_pins[] = { pin } +DECLARE_MSM_GPIO_PINS(0); +DECLARE_MSM_GPIO_PINS(1); +DECLARE_MSM_GPIO_PINS(2); +DECLARE_MSM_GPIO_PINS(3); +DECLARE_MSM_GPIO_PINS(4); +DECLARE_MSM_GPIO_PINS(5); +DECLARE_MSM_GPIO_PINS(6); +DECLARE_MSM_GPIO_PINS(7); +DECLARE_MSM_GPIO_PINS(8); +DECLARE_MSM_GPIO_PINS(9); +DECLARE_MSM_GPIO_PINS(10); +DECLARE_MSM_GPIO_PINS(11); +DECLARE_MSM_GPIO_PINS(12); +DECLARE_MSM_GPIO_PINS(13); +DECLARE_MSM_GPIO_PINS(14); +DECLARE_MSM_GPIO_PINS(15); +DECLARE_MSM_GPIO_PINS(16); +DECLARE_MSM_GPIO_PINS(17); +DECLARE_MSM_GPIO_PINS(18); +DECLARE_MSM_GPIO_PINS(19); +DECLARE_MSM_GPIO_PINS(20); +DECLARE_MSM_GPIO_PINS(21); +DECLARE_MSM_GPIO_PINS(22); +DECLARE_MSM_GPIO_PINS(23); +DECLARE_MSM_GPIO_PINS(24); +DECLARE_MSM_GPIO_PINS(25); +DECLARE_MSM_GPIO_PINS(26); +DECLARE_MSM_GPIO_PINS(27); +DECLARE_MSM_GPIO_PINS(28); +DECLARE_MSM_GPIO_PINS(29); +DECLARE_MSM_GPIO_PINS(30); +DECLARE_MSM_GPIO_PINS(31); +DECLARE_MSM_GPIO_PINS(32); +DECLARE_MSM_GPIO_PINS(33); +DECLARE_MSM_GPIO_PINS(34); +DECLARE_MSM_GPIO_PINS(35); +DECLARE_MSM_GPIO_PINS(36); +DECLARE_MSM_GPIO_PINS(37); +DECLARE_MSM_GPIO_PINS(38); +DECLARE_MSM_GPIO_PINS(39); +DECLARE_MSM_GPIO_PINS(40); +DECLARE_MSM_GPIO_PINS(41); +DECLARE_MSM_GPIO_PINS(42); +DECLARE_MSM_GPIO_PINS(43); +DECLARE_MSM_GPIO_PINS(44); +DECLARE_MSM_GPIO_PINS(45); +DECLARE_MSM_GPIO_PINS(46); +DECLARE_MSM_GPIO_PINS(47); +DECLARE_MSM_GPIO_PINS(48); +DECLARE_MSM_GPIO_PINS(49); +DECLARE_MSM_GPIO_PINS(50); +DECLARE_MSM_GPIO_PINS(51); +DECLARE_MSM_GPIO_PINS(52); +DECLARE_MSM_GPIO_PINS(53); +DECLARE_MSM_GPIO_PINS(54); +DECLARE_MSM_GPIO_PINS(55); +DECLARE_MSM_GPIO_PINS(56); +DECLARE_MSM_GPIO_PINS(57); +DECLARE_MSM_GPIO_PINS(58); +DECLARE_MSM_GPIO_PINS(59); +DECLARE_MSM_GPIO_PINS(60); +DECLARE_MSM_GPIO_PINS(61); +DECLARE_MSM_GPIO_PINS(62); +DECLARE_MSM_GPIO_PINS(63); +DECLARE_MSM_GPIO_PINS(64); +DECLARE_MSM_GPIO_PINS(65); +DECLARE_MSM_GPIO_PINS(66); +DECLARE_MSM_GPIO_PINS(67); +DECLARE_MSM_GPIO_PINS(68); +DECLARE_MSM_GPIO_PINS(69); +DECLARE_MSM_GPIO_PINS(70); +DECLARE_MSM_GPIO_PINS(71); +DECLARE_MSM_GPIO_PINS(72); +DECLARE_MSM_GPIO_PINS(73); +DECLARE_MSM_GPIO_PINS(74); +DECLARE_MSM_GPIO_PINS(75); +DECLARE_MSM_GPIO_PINS(76); +DECLARE_MSM_GPIO_PINS(77); +DECLARE_MSM_GPIO_PINS(78); +DECLARE_MSM_GPIO_PINS(79); +DECLARE_MSM_GPIO_PINS(80); +DECLARE_MSM_GPIO_PINS(81); +DECLARE_MSM_GPIO_PINS(82); +DECLARE_MSM_GPIO_PINS(83); +DECLARE_MSM_GPIO_PINS(84); +DECLARE_MSM_GPIO_PINS(85); +DECLARE_MSM_GPIO_PINS(86); +DECLARE_MSM_GPIO_PINS(87); +DECLARE_MSM_GPIO_PINS(88); +DECLARE_MSM_GPIO_PINS(89); +DECLARE_MSM_GPIO_PINS(90); +DECLARE_MSM_GPIO_PINS(91); +DECLARE_MSM_GPIO_PINS(92); +DECLARE_MSM_GPIO_PINS(93); +DECLARE_MSM_GPIO_PINS(94); +DECLARE_MSM_GPIO_PINS(95); +DECLARE_MSM_GPIO_PINS(96); +DECLARE_MSM_GPIO_PINS(97); +DECLARE_MSM_GPIO_PINS(98); +DECLARE_MSM_GPIO_PINS(99); +DECLARE_MSM_GPIO_PINS(100); +DECLARE_MSM_GPIO_PINS(101); +DECLARE_MSM_GPIO_PINS(102); +DECLARE_MSM_GPIO_PINS(103); +DECLARE_MSM_GPIO_PINS(104); +DECLARE_MSM_GPIO_PINS(105); +DECLARE_MSM_GPIO_PINS(106); +DECLARE_MSM_GPIO_PINS(107); +DECLARE_MSM_GPIO_PINS(108); +DECLARE_MSM_GPIO_PINS(109); +DECLARE_MSM_GPIO_PINS(110); +DECLARE_MSM_GPIO_PINS(111); +DECLARE_MSM_GPIO_PINS(112); +DECLARE_MSM_GPIO_PINS(113); +DECLARE_MSM_GPIO_PINS(114); +DECLARE_MSM_GPIO_PINS(115); +DECLARE_MSM_GPIO_PINS(116); +DECLARE_MSM_GPIO_PINS(117); +DECLARE_MSM_GPIO_PINS(118); +DECLARE_MSM_GPIO_PINS(119); +DECLARE_MSM_GPIO_PINS(120); +DECLARE_MSM_GPIO_PINS(121); +DECLARE_MSM_GPIO_PINS(122); +DECLARE_MSM_GPIO_PINS(123); +DECLARE_MSM_GPIO_PINS(124); +DECLARE_MSM_GPIO_PINS(125); +DECLARE_MSM_GPIO_PINS(126); +DECLARE_MSM_GPIO_PINS(127); +DECLARE_MSM_GPIO_PINS(128); +DECLARE_MSM_GPIO_PINS(129); +DECLARE_MSM_GPIO_PINS(130); +DECLARE_MSM_GPIO_PINS(131); +DECLARE_MSM_GPIO_PINS(132); +DECLARE_MSM_GPIO_PINS(133); + +static const unsigned int sdc1_clk_pins[] = { 134 }; +static const unsigned int sdc1_cmd_pins[] = { 135 }; +static const unsigned int sdc1_data_pins[] = { 136 }; +static const unsigned int sdc1_rclk_pins[] = { 137 }; +static const unsigned int sdc2_clk_pins[] = { 138 }; +static const unsigned int sdc2_cmd_pins[] = { 139 }; +static const unsigned int sdc2_data_pins[] = { 140 }; +static const unsigned int qdsd_clk_pins[] = { 141 }; +static const unsigned int qdsd_cmd_pins[] = { 142 }; +static const unsigned int qdsd_data0_pins[] = { 143 }; +static const unsigned int qdsd_data1_pins[] = { 144 }; +static const unsigned int qdsd_data2_pins[] = { 145 }; +static const unsigned int qdsd_data3_pins[] = { 146 }; + +enum msm8937_functions { + msm_mux_qdss_tracedata_b, + msm_mux_blsp_uart1, + msm_mux_gpio, + msm_mux_blsp_spi1, + msm_mux_adsp_ext, + msm_mux_blsp_i2c1, + msm_mux_prng_rosc, + msm_mux_qdss_cti_trig_out_b0, + msm_mux_blsp_spi2, + msm_mux_blsp_uart2, + msm_mux_blsp_uart3, + msm_mux_pbs0, + msm_mux_pbs1, + msm_mux_pwr_modem_enabled_b, + msm_mux_blsp_i2c3, + msm_mux_gcc_gp2_clk_b, + msm_mux_ldo_update, + msm_mux_atest_combodac_to_gpio_native, + msm_mux_ldo_en, + msm_mux_blsp_i2c2, + msm_mux_gcc_gp1_clk_b, + msm_mux_pbs2, + msm_mux_atest_gpsadc_dtest0_native, + msm_mux_blsp_spi3, + msm_mux_gcc_gp3_clk_b, + msm_mux_blsp_spi4, + msm_mux_blsp_uart4, + msm_mux_sec_mi2s, + msm_mux_pwr_nav_enabled_b, + msm_mux_codec_mad, + msm_mux_pwr_crypto_enabled_b, + msm_mux_blsp_i2c4, + msm_mux_blsp_spi5, + msm_mux_blsp_uart5, + msm_mux_qdss_traceclk_a, + msm_mux_atest_bbrx1, + msm_mux_m_voc, + msm_mux_qdss_cti_trig_in_a0, + msm_mux_qdss_cti_trig_in_b0, + msm_mux_blsp_i2c6, + msm_mux_qdss_traceclk_b, + msm_mux_atest_wlan0, + msm_mux_atest_wlan1, + msm_mux_atest_bbrx0, + msm_mux_blsp_i2c5, + msm_mux_qdss_tracectl_a, + msm_mux_atest_gpsadc_dtest1_native, + msm_mux_qdss_tracedata_a, + msm_mux_blsp_spi6, + msm_mux_blsp_uart6, + msm_mux_qdss_tracectl_b, + msm_mux_mdp_vsync, + msm_mux_pri_mi2s_mclk_a, + msm_mux_sec_mi2s_mclk_a, + msm_mux_cam_mclk, + msm_mux_cci_i2c, + msm_mux_pwr_modem_enabled_a, + msm_mux_cci_timer0, + msm_mux_cci_timer1, + msm_mux_cam1_standby, + msm_mux_pwr_nav_enabled_a, + msm_mux_cam1_rst, + msm_mux_pwr_crypto_enabled_a, + msm_mux_forced_usb, + msm_mux_qdss_cti_trig_out_b1, + msm_mux_cam2_rst, + msm_mux_webcam_standby, + msm_mux_cci_async, + msm_mux_webcam_rst, + msm_mux_ov_ldo, + msm_mux_sd_write, + msm_mux_accel_int, + msm_mux_gcc_gp1_clk_a, + msm_mux_alsp_int, + msm_mux_gcc_gp2_clk_a, + msm_mux_mag_int, + msm_mux_gcc_gp3_clk_a, + msm_mux_blsp6_spi, + msm_mux_fp_int, + msm_mux_qdss_cti_trig_in_b1, + msm_mux_uim_batt, + msm_mux_cam2_standby, + msm_mux_uim1_data, + msm_mux_uim1_clk, + msm_mux_uim1_reset, + msm_mux_uim1_present, + msm_mux_uim2_data, + msm_mux_uim2_clk, + msm_mux_uim2_reset, + msm_mux_uim2_present, + msm_mux_sensor_rst, + msm_mux_mipi_dsi0, + msm_mux_smb_int, + msm_mux_cam0_ldo, + msm_mux_us_euro, + msm_mux_atest_char3, + msm_mux_dbg_out, + msm_mux_bimc_dte0, + msm_mux_ts_resout, + msm_mux_ts_sample, + msm_mux_sec_mi2s_mclk_b, + msm_mux_pri_mi2s, + msm_mux_sdcard_det, + msm_mux_atest_char1, + msm_mux_ebi_cdc, + msm_mux_audio_reset, + msm_mux_atest_char0, + msm_mux_audio_ref, + msm_mux_cdc_pdm0, + msm_mux_pri_mi2s_mclk_b, + msm_mux_lpass_slimbus, + msm_mux_lpass_slimbus0, + msm_mux_lpass_slimbus1, + msm_mux_codec_int1, + msm_mux_codec_int2, + msm_mux_wcss_bt, + msm_mux_atest_char2, + msm_mux_ebi_ch0, + msm_mux_wcss_wlan2, + msm_mux_wcss_wlan1, + msm_mux_wcss_wlan0, + msm_mux_wcss_wlan, + msm_mux_wcss_fm, + msm_mux_ext_lpass, + msm_mux_cri_trng, + msm_mux_cri_trng1, + msm_mux_cri_trng0, + msm_mux_blsp_spi7, + msm_mux_blsp_uart7, + msm_mux_pri_mi2s_ws, + msm_mux_blsp_i2c7, + msm_mux_gcc_tlmm, + msm_mux_dmic0_clk, + msm_mux_dmic0_data, + msm_mux_key_volp, + msm_mux_qdss_cti_trig_in_a1, + msm_mux_us_emitter, + msm_mux_wsa_irq, + msm_mux_wsa_io, + msm_mux_wsa_reset, + msm_mux_blsp_spi8, + msm_mux_blsp_uart8, + msm_mux_blsp_i2c8, + msm_mux_gcc_plltest, + msm_mux_nav_pps_in_a, + msm_mux_pa_indicator, + msm_mux_modem_tsync, + msm_mux_nav_tsync, + msm_mux_nav_pps_in_b, + msm_mux_nav_pps, + msm_mux_gsm0_tx, + msm_mux_atest_char, + msm_mux_atest_tsens, + msm_mux_bimc_dte1, + msm_mux_ssbi_wtr1, + msm_mux_fp_gpio, + msm_mux_coex_uart, + msm_mux_key_snapshot, + msm_mux_key_focus, + msm_mux_nfc_pwr, + msm_mux_blsp8_spi, + msm_mux_qdss_cti_trig_out_a0, + msm_mux_qdss_cti_trig_out_a1, + msm_mux_NA, +}; + +static const char * const qdss_tracedata_b_groups[] = { + "gpio0", "gpio1", "gpio6", "gpio7", "gpio12", "gpio13", "gpio23", + "gpio42", "gpio43", "gpio44", "gpio47", "gpio66", "gpio86", "gpio87", + "gpio88", "gpio92", +}; +static const char * const blsp_uart1_groups[] = { + "gpio0", "gpio1", "gpio2", "gpio3", +}; +static const char * const gpio_groups[] = { + "gpio0", "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6", "gpio7", + "gpio8", "gpio9", "gpio10", "gpio11", "gpio12", "gpio13", "gpio14", + "gpio15", "gpio16", "gpio17", "gpio18", "gpio19", "gpio20", "gpio21", + "gpio22", "gpio23", "gpio24", "gpio25", "gpio26", "gpio27", "gpio28", + "gpio29", "gpio30", "gpio31", "gpio32", "gpio33", "gpio34", "gpio35", + "gpio36", "gpio37", "gpio38", "gpio39", "gpio40", "gpio41", "gpio42", + "gpio43", "gpio44", "gpio45", "gpio46", "gpio47", "gpio48", "gpio49", + "gpio50", "gpio51", "gpio52", "gpio53", "gpio54", "gpio55", "gpio56", + "gpio57", "gpio58", "gpio59", "gpio60", "gpio61", "gpio62", "gpio63", + "gpio64", "gpio65", "gpio66", "gpio67", "gpio68", "gpio69", "gpio70", + "gpio71", "gpio72", "gpio73", "gpio74", "gpio75", "gpio76", "gpio77", + "gpio78", "gpio79", "gpio80", "gpio81", "gpio82", "gpio83", "gpio84", + "gpio85", "gpio86", "gpio87", "gpio88", "gpio89", "gpio90", "gpio91", + "gpio92", "gpio93", "gpio94", "gpio95", "gpio96", "gpio97", "gpio98", + "gpio99", "gpio100", "gpio101", "gpio102", "gpio103", "gpio104", + "gpio105", "gpio106", "gpio107", "gpio108", "gpio109", "gpio110", + "gpio111", "gpio112", "gpio113", "gpio114", "gpio115", "gpio116", + "gpio117", "gpio118", "gpio119", "gpio120", "gpio121", "gpio122", + "gpio123", "gpio124", "gpio125", "gpio126", "gpio127", "gpio128", + "gpio129", "gpio130", "gpio131", "gpio132", "gpio133", +}; +static const char * const blsp_spi1_groups[] = { + "gpio0", "gpio1", "gpio2", "gpio3", +}; +static const char * const adsp_ext_groups[] = { + "gpio1", +}; +static const char * const blsp_i2c1_groups[] = { + "gpio2", "gpio3", +}; +static const char * const prng_rosc_groups[] = { + "gpio2", +}; +static const char * const qdss_cti_trig_out_b0_groups[] = { + "gpio2", +}; +static const char * const blsp_spi2_groups[] = { + "gpio4", "gpio5", "gpio6", "gpio7", +}; +static const char * const blsp_uart2_groups[] = { + "gpio4", "gpio5", "gpio6", "gpio7", +}; +static const char * const blsp_uart3_groups[] = { + "gpio8", "gpio9", "gpio10", "gpio11", +}; +static const char * const pbs0_groups[] = { + "gpio8", +}; +static const char * const pbs1_groups[] = { + "gpio9", +}; +static const char * const pwr_modem_enabled_b_groups[] = { + "gpio9", +}; +static const char * const blsp_i2c3_groups[] = { + "gpio10", "gpio11", +}; +static const char * const gcc_gp2_clk_b_groups[] = { + "gpio10", +}; +static const char * const ldo_update_groups[] = { + "gpio4", +}; +static const char * const atest_combodac_to_gpio_native_groups[] = { + "gpio4", "gpio12", "gpio13", "gpio20", "gpio21", "gpio28", "gpio29", + "gpio30", "gpio39", "gpio40", "gpio41", "gpio42", "gpio43", "gpio44", + "gpio45", "gpio46", "gpio47", "gpio48", "gpio67", "gpio115", +}; +static const char * const ldo_en_groups[] = { + "gpio5", +}; +static const char * const blsp_i2c2_groups[] = { + "gpio6", "gpio7", +}; +static const char * const gcc_gp1_clk_b_groups[] = { + "gpio6", +}; +static const char * const pbs2_groups[] = { + "gpio7", +}; +static const char * const atest_gpsadc_dtest0_native_groups[] = { + "gpio7", +}; +static const char * const blsp_spi3_groups[] = { + "gpio8", "gpio9", "gpio10", "gpio11", +}; +static const char * const gcc_gp3_clk_b_groups[] = { + "gpio11", +}; +static const char * const blsp_spi4_groups[] = { + "gpio12", "gpio13", "gpio14", "gpio15", +}; +static const char * const blsp_uart4_groups[] = { + "gpio12", "gpio13", "gpio14", "gpio15", +}; +static const char * const sec_mi2s_groups[] = { + "gpio12", "gpio13", "gpio94", "gpio95", +}; +static const char * const pwr_nav_enabled_b_groups[] = { + "gpio12", +}; +static const char * const codec_mad_groups[] = { + "gpio13", +}; +static const char * const pwr_crypto_enabled_b_groups[] = { + "gpio13", +}; +static const char * const blsp_i2c4_groups[] = { + "gpio14", "gpio15", +}; +static const char * const blsp_spi5_groups[] = { + "gpio16", "gpio17", "gpio18", "gpio19", +}; +static const char * const blsp_uart5_groups[] = { + "gpio16", "gpio17", "gpio18", "gpio19", +}; +static const char * const qdss_traceclk_a_groups[] = { + "gpio16", +}; +static const char * const atest_bbrx1_groups[] = { + "gpio16", +}; +static const char * const m_voc_groups[] = { + "gpio17", "gpio21", +}; +static const char * const qdss_cti_trig_in_a0_groups[] = { + "gpio17", +}; +static const char * const qdss_cti_trig_in_b0_groups[] = { + "gpio21", +}; +static const char * const blsp_i2c6_groups[] = { + "gpio22", "gpio23", +}; +static const char * const qdss_traceclk_b_groups[] = { + "gpio22", +}; +static const char * const atest_wlan0_groups[] = { + "gpio22", +}; +static const char * const atest_wlan1_groups[] = { + "gpio23", +}; +static const char * const atest_bbrx0_groups[] = { + "gpio17", +}; +static const char * const blsp_i2c5_groups[] = { + "gpio18", "gpio19", +}; +static const char * const qdss_tracectl_a_groups[] = { + "gpio18", +}; +static const char * const atest_gpsadc_dtest1_native_groups[] = { + "gpio18", +}; +static const char * const qdss_tracedata_a_groups[] = { + "gpio19", "gpio26", "gpio27", "gpio28", "gpio29", "gpio30", "gpio31", + "gpio32", "gpio33", "gpio34", "gpio35", "gpio36", "gpio38", "gpio39", + "gpio40", "gpio50", +}; +static const char * const blsp_spi6_groups[] = { + "gpio20", "gpio21", "gpio22", "gpio23", +}; +static const char * const blsp_uart6_groups[] = { + "gpio20", "gpio21", "gpio22", "gpio23", +}; +static const char * const qdss_tracectl_b_groups[] = { + "gpio20", +}; +static const char * const mdp_vsync_groups[] = { + "gpio24", "gpio25", +}; +static const char * const pri_mi2s_mclk_a_groups[] = { + "gpio25", +}; +static const char * const sec_mi2s_mclk_a_groups[] = { + "gpio25", +}; +static const char * const cam_mclk_groups[] = { + "gpio26", "gpio27", "gpio28", +}; +static const char * const cci_i2c_groups[] = { + "gpio29", "gpio30", "gpio31", "gpio32", +}; +static const char * const pwr_modem_enabled_a_groups[] = { + "gpio29", +}; +static const char * const cci_timer0_groups[] = { + "gpio33", +}; +static const char * const cci_timer1_groups[] = { + "gpio34", +}; +static const char * const cam1_standby_groups[] = { + "gpio35", +}; +static const char * const pwr_nav_enabled_a_groups[] = { + "gpio35", +}; +static const char * const cam1_rst_groups[] = { + "gpio36", +}; +static const char * const pwr_crypto_enabled_a_groups[] = { + "gpio36", +}; +static const char * const forced_usb_groups[] = { + "gpio37", +}; +static const char * const qdss_cti_trig_out_b1_groups[] = { + "gpio37", +}; +static const char * const cam2_rst_groups[] = { + "gpio38", +}; +static const char * const webcam_standby_groups[] = { + "gpio39", +}; +static const char * const cci_async_groups[] = { + "gpio39", +}; +static const char * const webcam_rst_groups[] = { + "gpio40", +}; +static const char * const ov_ldo_groups[] = { + "gpio41", +}; +static const char * const sd_write_groups[] = { + "gpio41", +}; +static const char * const accel_int_groups[] = { + "gpio42", +}; +static const char * const gcc_gp1_clk_a_groups[] = { + "gpio42", +}; +static const char * const alsp_int_groups[] = { + "gpio43", +}; +static const char * const gcc_gp2_clk_a_groups[] = { + "gpio43", +}; +static const char * const mag_int_groups[] = { + "gpio44", +}; +static const char * const gcc_gp3_clk_a_groups[] = { + "gpio44", +}; +static const char * const blsp6_spi_groups[] = { + "gpio47", +}; +static const char * const fp_int_groups[] = { + "gpio48", +}; +static const char * const qdss_cti_trig_in_b1_groups[] = { + "gpio48", +}; +static const char * const uim_batt_groups[] = { + "gpio49", +}; +static const char * const cam2_standby_groups[] = { + "gpio50", +}; +static const char * const uim1_data_groups[] = { + "gpio51", +}; +static const char * const uim1_clk_groups[] = { + "gpio52", +}; +static const char * const uim1_reset_groups[] = { + "gpio53", +}; +static const char * const uim1_present_groups[] = { + "gpio54", +}; +static const char * const uim2_data_groups[] = { + "gpio55", +}; +static const char * const uim2_clk_groups[] = { + "gpio56", +}; +static const char * const uim2_reset_groups[] = { + "gpio57", +}; +static const char * const uim2_present_groups[] = { + "gpio58", +}; +static const char * const sensor_rst_groups[] = { + "gpio59", +}; +static const char * const mipi_dsi0_groups[] = { + "gpio60", +}; +static const char * const smb_int_groups[] = { + "gpio61", +}; +static const char * const cam0_ldo_groups[] = { + "gpio62", +}; +static const char * const us_euro_groups[] = { + "gpio63", +}; +static const char * const atest_char3_groups[] = { + "gpio63", +}; +static const char * const dbg_out_groups[] = { + "gpio63", +}; +static const char * const bimc_dte0_groups[] = { + "gpio63", "gpio65", +}; +static const char * const ts_resout_groups[] = { + "gpio64", +}; +static const char * const ts_sample_groups[] = { + "gpio65", +}; +static const char * const sec_mi2s_mclk_b_groups[] = { + "gpio66", +}; +static const char * const pri_mi2s_groups[] = { + "gpio66", "gpio85", "gpio86", "gpio88", "gpio94", "gpio95", +}; +static const char * const sdcard_det_groups[] = { + "gpio67", +}; +static const char * const atest_char1_groups[] = { + "gpio67", +}; +static const char * const ebi_cdc_groups[] = { + "gpio67", "gpio69", "gpio118", "gpio119", "gpio120", "gpio123", +}; +static const char * const audio_reset_groups[] = { + "gpio68", +}; +static const char * const atest_char0_groups[] = { + "gpio68", +}; +static const char * const audio_ref_groups[] = { + "gpio69", +}; +static const char * const cdc_pdm0_groups[] = { + "gpio69", "gpio70", "gpio71", "gpio72", "gpio73", "gpio74", +}; +static const char * const pri_mi2s_mclk_b_groups[] = { + "gpio69", +}; +static const char * const lpass_slimbus_groups[] = { + "gpio70", +}; +static const char * const lpass_slimbus0_groups[] = { + "gpio71", +}; +static const char * const lpass_slimbus1_groups[] = { + "gpio72", +}; +static const char * const codec_int1_groups[] = { + "gpio73", +}; +static const char * const codec_int2_groups[] = { + "gpio74", +}; +static const char * const wcss_bt_groups[] = { + "gpio75", "gpio83", "gpio84", +}; +static const char * const atest_char2_groups[] = { + "gpio75", +}; +static const char * const ebi_ch0_groups[] = { + "gpio75", +}; +static const char * const wcss_wlan2_groups[] = { + "gpio76", +}; +static const char * const wcss_wlan1_groups[] = { + "gpio77", +}; +static const char * const wcss_wlan0_groups[] = { + "gpio78", +}; +static const char * const wcss_wlan_groups[] = { + "gpio79", "gpio80", +}; +static const char * const wcss_fm_groups[] = { + "gpio81", "gpio82", +}; +static const char * const ext_lpass_groups[] = { + "gpio81", +}; +static const char * const cri_trng_groups[] = { + "gpio82", +}; +static const char * const cri_trng1_groups[] = { + "gpio83", +}; +static const char * const cri_trng0_groups[] = { + "gpio84", +}; +static const char * const blsp_spi7_groups[] = { + "gpio85", "gpio86", "gpio87", "gpio88", +}; +static const char * const blsp_uart7_groups[] = { + "gpio85", "gpio86", "gpio87", "gpio88", +}; +static const char * const pri_mi2s_ws_groups[] = { + "gpio87", +}; +static const char * const blsp_i2c7_groups[] = { + "gpio87", "gpio88", +}; +static const char * const gcc_tlmm_groups[] = { + "gpio87", +}; +static const char * const dmic0_clk_groups[] = { + "gpio89", +}; +static const char * const dmic0_data_groups[] = { + "gpio90", +}; +static const char * const key_volp_groups[] = { + "gpio91", +}; +static const char * const qdss_cti_trig_in_a1_groups[] = { + "gpio91", +}; +static const char * const us_emitter_groups[] = { + "gpio92", +}; +static const char * const wsa_irq_groups[] = { + "gpio93", +}; +static const char * const wsa_io_groups[] = { + "gpio94", "gpio95", +}; +static const char * const wsa_reset_groups[] = { + "gpio96", +}; +static const char * const blsp_spi8_groups[] = { + "gpio96", "gpio97", "gpio98", "gpio99", +}; +static const char * const blsp_uart8_groups[] = { + "gpio96", "gpio97", "gpio98", "gpio99", +}; +static const char * const blsp_i2c8_groups[] = { + "gpio98", "gpio99", +}; +static const char * const gcc_plltest_groups[] = { + "gpio98", "gpio99", +}; +static const char * const nav_pps_in_a_groups[] = { + "gpio115", +}; +static const char * const pa_indicator_groups[] = { + "gpio116", +}; +static const char * const modem_tsync_groups[] = { + "gpio117", +}; +static const char * const nav_tsync_groups[] = { + "gpio117", +}; +static const char * const nav_pps_in_b_groups[] = { + "gpio117", +}; +static const char * const nav_pps_groups[] = { + "gpio117", +}; +static const char * const gsm0_tx_groups[] = { + "gpio119", +}; +static const char * const atest_char_groups[] = { + "gpio120", +}; +static const char * const atest_tsens_groups[] = { + "gpio120", +}; +static const char * const bimc_dte1_groups[] = { + "gpio121", "gpio122", +}; +static const char * const ssbi_wtr1_groups[] = { + "gpio122", "gpio123", +}; +static const char * const fp_gpio_groups[] = { + "gpio124", +}; +static const char * const coex_uart_groups[] = { + "gpio124", "gpio127", +}; +static const char * const key_snapshot_groups[] = { + "gpio127", +}; +static const char * const key_focus_groups[] = { + "gpio128", +}; +static const char * const nfc_pwr_groups[] = { + "gpio129", +}; +static const char * const blsp8_spi_groups[] = { + "gpio130", +}; +static const char * const qdss_cti_trig_out_a0_groups[] = { + "gpio132", +}; +static const char * const qdss_cti_trig_out_a1_groups[] = { + "gpio133", +}; + +static const struct msm_function msm8937_functions[] = { + FUNCTION(qdss_tracedata_b), + FUNCTION(blsp_uart1), + FUNCTION(gpio), + FUNCTION(blsp_spi1), + FUNCTION(adsp_ext), + FUNCTION(blsp_i2c1), + FUNCTION(prng_rosc), + FUNCTION(qdss_cti_trig_out_b0), + FUNCTION(blsp_spi2), + FUNCTION(blsp_uart2), + FUNCTION(blsp_uart3), + FUNCTION(pbs0), + FUNCTION(pbs1), + FUNCTION(pwr_modem_enabled_b), + FUNCTION(blsp_i2c3), + FUNCTION(gcc_gp2_clk_b), + FUNCTION(ldo_update), + FUNCTION(atest_combodac_to_gpio_native), + FUNCTION(ldo_en), + FUNCTION(blsp_i2c2), + FUNCTION(gcc_gp1_clk_b), + FUNCTION(pbs2), + FUNCTION(atest_gpsadc_dtest0_native), + FUNCTION(blsp_spi3), + FUNCTION(gcc_gp3_clk_b), + FUNCTION(blsp_spi4), + FUNCTION(blsp_uart4), + FUNCTION(sec_mi2s), + FUNCTION(pwr_nav_enabled_b), + FUNCTION(codec_mad), + FUNCTION(pwr_crypto_enabled_b), + FUNCTION(blsp_i2c4), + FUNCTION(blsp_spi5), + FUNCTION(blsp_uart5), + FUNCTION(qdss_traceclk_a), + FUNCTION(atest_bbrx1), + FUNCTION(m_voc), + FUNCTION(qdss_cti_trig_in_a0), + FUNCTION(qdss_cti_trig_in_b0), + FUNCTION(blsp_i2c6), + FUNCTION(qdss_traceclk_b), + FUNCTION(atest_wlan0), + FUNCTION(atest_wlan1), + FUNCTION(atest_bbrx0), + FUNCTION(blsp_i2c5), + FUNCTION(qdss_tracectl_a), + FUNCTION(atest_gpsadc_dtest1_native), + FUNCTION(qdss_tracedata_a), + FUNCTION(blsp_spi6), + FUNCTION(blsp_uart6), + FUNCTION(qdss_tracectl_b), + FUNCTION(mdp_vsync), + FUNCTION(pri_mi2s_mclk_a), + FUNCTION(sec_mi2s_mclk_a), + FUNCTION(cam_mclk), + FUNCTION(cci_i2c), + FUNCTION(pwr_modem_enabled_a), + FUNCTION(cci_timer0), + FUNCTION(cci_timer1), + FUNCTION(cam1_standby), + FUNCTION(pwr_nav_enabled_a), + FUNCTION(cam1_rst), + FUNCTION(pwr_crypto_enabled_a), + FUNCTION(forced_usb), + FUNCTION(qdss_cti_trig_out_b1), + FUNCTION(cam2_rst), + FUNCTION(webcam_standby), + FUNCTION(cci_async), + FUNCTION(webcam_rst), + FUNCTION(ov_ldo), + FUNCTION(sd_write), + FUNCTION(accel_int), + FUNCTION(gcc_gp1_clk_a), + FUNCTION(alsp_int), + FUNCTION(gcc_gp2_clk_a), + FUNCTION(mag_int), + FUNCTION(gcc_gp3_clk_a), + FUNCTION(blsp6_spi), + FUNCTION(fp_int), + FUNCTION(qdss_cti_trig_in_b1), + FUNCTION(uim_batt), + FUNCTION(cam2_standby), + FUNCTION(uim1_data), + FUNCTION(uim1_clk), + FUNCTION(uim1_reset), + FUNCTION(uim1_present), + FUNCTION(uim2_data), + FUNCTION(uim2_clk), + FUNCTION(uim2_reset), + FUNCTION(uim2_present), + FUNCTION(sensor_rst), + FUNCTION(mipi_dsi0), + FUNCTION(smb_int), + FUNCTION(cam0_ldo), + FUNCTION(us_euro), + FUNCTION(atest_char3), + FUNCTION(dbg_out), + FUNCTION(bimc_dte0), + FUNCTION(ts_resout), + FUNCTION(ts_sample), + FUNCTION(sec_mi2s_mclk_b), + FUNCTION(pri_mi2s), + FUNCTION(sdcard_det), + FUNCTION(atest_char1), + FUNCTION(ebi_cdc), + FUNCTION(audio_reset), + FUNCTION(atest_char0), + FUNCTION(audio_ref), + FUNCTION(cdc_pdm0), + FUNCTION(pri_mi2s_mclk_b), + FUNCTION(lpass_slimbus), + FUNCTION(lpass_slimbus0), + FUNCTION(lpass_slimbus1), + FUNCTION(codec_int1), + FUNCTION(codec_int2), + FUNCTION(wcss_bt), + FUNCTION(atest_char2), + FUNCTION(ebi_ch0), + FUNCTION(wcss_wlan2), + FUNCTION(wcss_wlan1), + FUNCTION(wcss_wlan0), + FUNCTION(wcss_wlan), + FUNCTION(wcss_fm), + FUNCTION(ext_lpass), + FUNCTION(cri_trng), + FUNCTION(cri_trng1), + FUNCTION(cri_trng0), + FUNCTION(blsp_spi7), + FUNCTION(blsp_uart7), + FUNCTION(pri_mi2s_ws), + FUNCTION(blsp_i2c7), + FUNCTION(gcc_tlmm), + FUNCTION(dmic0_clk), + FUNCTION(dmic0_data), + FUNCTION(key_volp), + FUNCTION(qdss_cti_trig_in_a1), + FUNCTION(us_emitter), + FUNCTION(wsa_irq), + FUNCTION(wsa_io), + FUNCTION(wsa_reset), + FUNCTION(blsp_spi8), + FUNCTION(blsp_uart8), + FUNCTION(blsp_i2c8), + FUNCTION(gcc_plltest), + FUNCTION(nav_pps_in_a), + FUNCTION(pa_indicator), + FUNCTION(modem_tsync), + FUNCTION(nav_tsync), + FUNCTION(nav_pps_in_b), + FUNCTION(nav_pps), + FUNCTION(gsm0_tx), + FUNCTION(atest_char), + FUNCTION(atest_tsens), + FUNCTION(bimc_dte1), + FUNCTION(ssbi_wtr1), + FUNCTION(fp_gpio), + FUNCTION(coex_uart), + FUNCTION(key_snapshot), + FUNCTION(key_focus), + FUNCTION(nfc_pwr), + FUNCTION(blsp8_spi), + FUNCTION(qdss_cti_trig_out_a0), + FUNCTION(qdss_cti_trig_out_a1), +}; + +static const struct msm_pingroup msm8937_groups[] = { + PINGROUP(0, blsp_spi1, blsp_uart1, qdss_tracedata_b, NA, NA, NA, NA, + NA, NA), + PINGROUP(1, blsp_spi1, blsp_uart1, adsp_ext, NA, NA, NA, NA, NA, + qdss_tracedata_b), + PINGROUP(2, blsp_spi1, blsp_uart1, blsp_i2c1, prng_rosc, NA, NA, NA, + NA, NA), + PINGROUP(3, blsp_spi1, blsp_uart1, blsp_i2c1, NA, NA, NA, NA, NA, NA), + PINGROUP(4, blsp_spi2, blsp_uart2, ldo_update, NA, + atest_combodac_to_gpio_native, NA, NA, NA, NA), + PINGROUP(5, blsp_spi2, blsp_uart2, ldo_en, NA, NA, NA, NA, NA, NA), + PINGROUP(6, blsp_spi2, blsp_uart2, blsp_i2c2, gcc_gp1_clk_b, + qdss_tracedata_b, NA, NA, NA, NA), + PINGROUP(7, blsp_spi2, blsp_uart2, blsp_i2c2, pbs2, NA, + qdss_tracedata_b, NA, atest_gpsadc_dtest0_native, NA), + PINGROUP(8, blsp_spi3, blsp_uart3, pbs0, NA, NA, NA, NA, NA, NA), + PINGROUP(9, blsp_spi3, blsp_uart3, pbs1, pwr_modem_enabled_b, NA, NA, + NA, NA, NA), + PINGROUP(10, blsp_spi3, blsp_uart3, blsp_i2c3, gcc_gp2_clk_b, NA, NA, + NA, NA, NA), + PINGROUP(11, blsp_spi3, blsp_uart3, blsp_i2c3, gcc_gp3_clk_b, NA, NA, + NA, NA, NA), + PINGROUP(12, blsp_spi4, blsp_uart4, sec_mi2s, pwr_nav_enabled_b, NA, + NA, NA, NA, NA), + PINGROUP(13, blsp_spi4, blsp_uart4, sec_mi2s, pwr_crypto_enabled_b, NA, + NA, NA, NA, NA), + PINGROUP(14, blsp_spi4, blsp_uart4, blsp_i2c4, NA, NA, NA, NA, NA, NA), + PINGROUP(15, blsp_spi4, blsp_uart4, blsp_i2c4, NA, NA, NA, NA, NA, NA), + PINGROUP(16, blsp_spi5, blsp_uart5, NA, NA, NA, NA, qdss_traceclk_a, + NA, atest_bbrx1), + PINGROUP(17, blsp_spi5, blsp_uart5, m_voc, qdss_cti_trig_in_a0, NA, + atest_bbrx0, NA, NA, NA), + PINGROUP(18, blsp_spi5, blsp_uart5, blsp_i2c5, qdss_tracectl_a, NA, + atest_gpsadc_dtest1_native, NA, NA, NA), + PINGROUP(19, blsp_spi5, blsp_uart5, blsp_i2c5, qdss_tracedata_a, NA, + NA, NA, NA, NA), + PINGROUP(20, blsp_spi6, blsp_uart6, NA, NA, NA, NA, NA, NA, + qdss_tracectl_b), + PINGROUP(21, blsp_spi6, blsp_uart6, m_voc, NA, NA, NA, NA, NA, + qdss_cti_trig_in_b0), + PINGROUP(22, blsp_spi6, blsp_uart6, blsp_i2c6, qdss_traceclk_b, NA, + atest_wlan0, NA, NA, NA), + PINGROUP(23, blsp_spi6, blsp_uart6, blsp_i2c6, qdss_tracedata_b, NA, + atest_wlan1, NA, NA, NA), + PINGROUP(24, mdp_vsync, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(25, mdp_vsync, pri_mi2s_mclk_a, sec_mi2s_mclk_a, NA, NA, NA, + NA, NA, NA), + PINGROUP(26, cam_mclk, NA, NA, NA, NA, NA, qdss_tracedata_a, NA, NA), + PINGROUP(27, cam_mclk, NA, NA, NA, NA, NA, NA, NA, qdss_tracedata_a), + PINGROUP(28, cam_mclk, NA, NA, NA, NA, NA, qdss_tracedata_a, NA, + atest_combodac_to_gpio_native), + PINGROUP(29, cci_i2c, pwr_modem_enabled_a, NA, NA, NA, NA, NA, + qdss_tracedata_a, NA), + PINGROUP(30, cci_i2c, NA, NA, NA, NA, NA, NA, NA, qdss_tracedata_a), + PINGROUP(31, cci_i2c, NA, NA, NA, NA, NA, NA, NA, qdss_tracedata_a), + PINGROUP(32, cci_i2c, NA, NA, NA, NA, NA, NA, NA, qdss_tracedata_a), + PINGROUP(33, cci_timer0, NA, NA, NA, NA, NA, NA, NA, qdss_tracedata_a), + PINGROUP(34, cci_timer1, NA, NA, NA, NA, NA, NA, NA, qdss_tracedata_a), + PINGROUP(35, pwr_nav_enabled_a, NA, NA, NA, NA, NA, NA, NA, + qdss_tracedata_a), + PINGROUP(36, pwr_crypto_enabled_a, NA, NA, NA, NA, NA, NA, NA, + qdss_tracedata_a), + PINGROUP(37, NA, NA, NA, NA, NA, qdss_cti_trig_out_b1, NA, NA, NA), + PINGROUP(38, NA, qdss_tracedata_a, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(39, cci_async, NA, NA, NA, NA, NA, qdss_tracedata_a, NA, + atest_combodac_to_gpio_native), + PINGROUP(40, NA, NA, NA, NA, qdss_tracedata_a, NA, + atest_combodac_to_gpio_native, NA, NA), + PINGROUP(41, sd_write, NA, NA, NA, NA, NA, NA, NA, + atest_combodac_to_gpio_native), + PINGROUP(42, gcc_gp1_clk_a, qdss_tracedata_b, NA, + atest_combodac_to_gpio_native, NA, NA, NA, NA, NA), + PINGROUP(43, gcc_gp2_clk_a, qdss_tracedata_b, NA, + atest_combodac_to_gpio_native, NA, NA, NA, NA, NA), + PINGROUP(44, gcc_gp3_clk_a, qdss_tracedata_b, NA, + atest_combodac_to_gpio_native, NA, NA, NA, NA, NA), + PINGROUP(45, NA, NA, atest_combodac_to_gpio_native, NA, NA, NA, NA, NA, + NA), + PINGROUP(46, NA, NA, atest_combodac_to_gpio_native, NA, NA, NA, NA, NA, + NA), + PINGROUP(47, blsp6_spi, NA, qdss_tracedata_b, NA, + atest_combodac_to_gpio_native, NA, NA, NA, NA), + PINGROUP(48, NA, qdss_cti_trig_in_b1, NA, + atest_combodac_to_gpio_native, NA, NA, NA, NA, NA), + PINGROUP(49, uim_batt, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(50, qdss_tracedata_a, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(51, uim1_data, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(52, uim1_clk, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(53, uim1_reset, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(54, uim1_present, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(55, uim2_data, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(56, uim2_clk, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(57, uim2_reset, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(58, uim2_present, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(59, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(60, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(61, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(62, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(63, atest_char3, dbg_out, bimc_dte0, NA, NA, NA, NA, NA, NA), + PINGROUP(64, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(65, bimc_dte0, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(66, sec_mi2s_mclk_b, pri_mi2s, NA, qdss_tracedata_b, NA, NA, + NA, NA, NA), + PINGROUP(67, atest_char1, ebi_cdc, NA, atest_combodac_to_gpio_native, + NA, NA, NA, NA, NA), + PINGROUP(68, atest_char0, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(69, audio_ref, cdc_pdm0, pri_mi2s_mclk_b, ebi_cdc, NA, NA, NA, + NA, NA), + PINGROUP(70, lpass_slimbus, cdc_pdm0, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(71, lpass_slimbus0, cdc_pdm0, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(72, lpass_slimbus1, cdc_pdm0, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(73, cdc_pdm0, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(74, cdc_pdm0, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(75, wcss_bt, atest_char2, NA, ebi_ch0, NA, NA, NA, NA, NA), + PINGROUP(76, wcss_wlan2, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(77, wcss_wlan1, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(78, wcss_wlan0, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(79, wcss_wlan, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(80, wcss_wlan, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(81, wcss_fm, ext_lpass, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(82, wcss_fm, cri_trng, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(83, wcss_bt, cri_trng1, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(84, wcss_bt, cri_trng0, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(85, pri_mi2s, blsp_spi7, blsp_uart7, NA, NA, NA, NA, NA, NA), + PINGROUP(86, pri_mi2s, blsp_spi7, blsp_uart7, qdss_tracedata_b, NA, NA, + NA, NA, NA), + PINGROUP(87, pri_mi2s_ws, blsp_spi7, blsp_uart7, blsp_i2c7, + qdss_tracedata_b, gcc_tlmm, NA, NA, NA), + PINGROUP(88, pri_mi2s, blsp_spi7, blsp_uart7, blsp_i2c7, NA, NA, NA, + NA, NA), + PINGROUP(89, dmic0_clk, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(90, dmic0_data, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(91, NA, NA, NA, NA, NA, qdss_cti_trig_in_a1, NA, NA, NA), + PINGROUP(92, NA, NA, NA, NA, NA, qdss_tracedata_b, NA, NA, NA), + PINGROUP(93, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(94, wsa_io, sec_mi2s, pri_mi2s, NA, NA, NA, NA, NA, NA), + PINGROUP(95, wsa_io, sec_mi2s, pri_mi2s, NA, NA, NA, NA, NA, NA), + PINGROUP(96, blsp_spi8, blsp_uart8, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(97, blsp_spi8, blsp_uart8, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(98, blsp_spi8, blsp_uart8, blsp_i2c8, gcc_plltest, NA, NA, NA, + NA, NA), + PINGROUP(99, blsp_spi8, blsp_uart8, blsp_i2c8, gcc_plltest, NA, NA, NA, + NA, NA), + PINGROUP(100, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(101, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(102, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(103, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(104, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(105, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(106, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(107, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(108, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(109, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(110, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(111, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(112, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(113, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(114, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(115, NA, NA, nav_pps_in_a, NA, atest_combodac_to_gpio_native, + NA, NA, NA, NA), + PINGROUP(116, NA, pa_indicator, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(117, NA, modem_tsync, nav_tsync, nav_pps_in_b, nav_pps, NA, + NA, NA, NA), + PINGROUP(118, NA, ebi_cdc, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(119, gsm0_tx, NA, ebi_cdc, NA, NA, NA, NA, NA, NA), + PINGROUP(120, NA, atest_char, ebi_cdc, NA, atest_tsens, NA, NA, NA, NA), + PINGROUP(121, NA, NA, NA, bimc_dte1, NA, NA, NA, NA, NA), + PINGROUP(122, NA, ssbi_wtr1, NA, NA, bimc_dte1, NA, NA, NA, NA), + PINGROUP(123, NA, ssbi_wtr1, ebi_cdc, NA, NA, NA, NA, NA, NA), + PINGROUP(124, coex_uart, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(125, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(126, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(127, coex_uart, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(128, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(129, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(130, blsp8_spi, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(131, NA, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(132, qdss_cti_trig_out_a0, NA, NA, NA, NA, NA, NA, NA, NA), + PINGROUP(133, qdss_cti_trig_out_a1, NA, NA, NA, NA, NA, NA, NA, NA), + SDC_QDSD_PINGROUP(sdc1_clk, 0x10a000, 13, 6), + SDC_QDSD_PINGROUP(sdc1_cmd, 0x10a000, 11, 3), + SDC_QDSD_PINGROUP(sdc1_data, 0x10a000, 9, 0), + SDC_QDSD_PINGROUP(sdc1_rclk, 0x10a000, 15, 0), + SDC_QDSD_PINGROUP(sdc2_clk, 0x109000, 14, 6), + SDC_QDSD_PINGROUP(sdc2_cmd, 0x109000, 11, 3), + SDC_QDSD_PINGROUP(sdc2_data, 0x109000, 9, 0), + SDC_QDSD_PINGROUP(qdsd_clk, 0x19c000, 3, 0), + SDC_QDSD_PINGROUP(qdsd_cmd, 0x19c000, 8, 5), + SDC_QDSD_PINGROUP(qdsd_data0, 0x19c000, 13, 10), + SDC_QDSD_PINGROUP(qdsd_data1, 0x19c000, 18, 15), + SDC_QDSD_PINGROUP(qdsd_data2, 0x19c000, 23, 20), + SDC_QDSD_PINGROUP(qdsd_data3, 0x19c000, 28, 25), +}; + +static const struct msm_pinctrl_soc_data msm8937_pinctrl = { + .pins = msm8937_pins, + .npins = ARRAY_SIZE(msm8937_pins), + .functions = msm8937_functions, + .nfunctions = ARRAY_SIZE(msm8937_functions), + .groups = msm8937_groups, + .ngroups = ARRAY_SIZE(msm8937_groups), + .ngpios = 134, +}; + +static int msm8937_pinctrl_probe(struct platform_device *pdev) +{ + return msm_pinctrl_probe(pdev, &msm8937_pinctrl); +} + +static const struct of_device_id msm8937_pinctrl_of_match[] = { + { .compatible = "qcom,msm8937-pinctrl", }, + { }, +}; + +static struct platform_driver msm8937_pinctrl_driver = { + .driver = { + .name = "msm8937-pinctrl", + .of_match_table = msm8937_pinctrl_of_match, + }, + .probe = msm8937_pinctrl_probe, + .remove = msm_pinctrl_remove, +}; + +static int __init msm8937_pinctrl_init(void) +{ + return platform_driver_register(&msm8937_pinctrl_driver); +} +arch_initcall(msm8937_pinctrl_init); + +static void __exit msm8937_pinctrl_exit(void) +{ + platform_driver_unregister(&msm8937_pinctrl_driver); +} +module_exit(msm8937_pinctrl_exit); + +MODULE_DESCRIPTION("QTI msm8937 pinctrl driver"); +MODULE_LICENSE("GPL v2"); +MODULE_DEVICE_TABLE(of, msm8937_pinctrl_of_match); diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index 27adb4821ae3..c0db71f6eea9 100644 --- a/drivers/platform/Kconfig +++ b/drivers/platform/Kconfig @@ -12,7 +12,3 @@ source "drivers/platform/chrome/Kconfig" source "drivers/platform/mellanox/Kconfig" source "drivers/platform/msm/Kconfig" - -if ARCH_QCOM -source "drivers/platform/xiaomi/Kconfig" -endif diff --git a/drivers/platform/msm/Makefile b/drivers/platform/msm/Makefile index fbfb81272732..c16fd2aae52c 100644 --- a/drivers/platform/msm/Makefile +++ b/drivers/platform/msm/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_MSM_EXT_DISPLAY) += msm_ext_display.o obj-$(CONFIG_QPNP_REVID) += qpnp-revid.o obj-$(CONFIG_SPS) += sps/ obj-$(CONFIG_GSI) += gsi/ +obj-$(CONFIG_IPA) += ipa/ obj-$(CONFIG_IPA3) += ipa/ obj-$(CONFIG_USB_BAM) += usb_bam.o obj-$(CONFIG_QCOM_GENI_SE) += qcom-geni-se.o diff --git a/drivers/platform/msm/gsi/gsi.c b/drivers/platform/msm/gsi/gsi.c index 3abbf47244e9..365bb1d2b191 100644 --- a/drivers/platform/msm/gsi/gsi.c +++ b/drivers/platform/msm/gsi/gsi.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. */ #include @@ -11,6 +11,7 @@ #include #include #include +#include #include "gsi.h" #include "gsi_reg.h" #include "gsi_emulation.h" @@ -1787,7 +1788,7 @@ int gsi_alloc_evt_ring(struct gsi_evt_ring_props *props, unsigned long dev_hdl, EXPORT_SYMBOL(gsi_alloc_evt_ring); static void __gsi_write_evt_ring_scratch(unsigned long evt_ring_hdl, - union gsi_evt_scratch val) + union __packed gsi_evt_scratch val) { gsi_writel(val.data.word1, gsi_ctx->base + GSI_EE_n_EV_CH_k_SCRATCH_0_OFFS(evt_ring_hdl, @@ -1798,7 +1799,7 @@ static void __gsi_write_evt_ring_scratch(unsigned long evt_ring_hdl, } int gsi_write_evt_ring_scratch(unsigned long evt_ring_hdl, - union gsi_evt_scratch val) + union __packed gsi_evt_scratch val) { struct gsi_evt_ctx *ctx; @@ -2354,7 +2355,7 @@ int gsi_alloc_channel(struct gsi_chan_props *props, unsigned long dev_hdl, { struct gsi_chan_ctx *ctx; uint32_t val; - int res; + int res, size; int ee; enum gsi_ch_cmd_opcode op = GSI_CH_ALLOCATE; uint8_t erindex; @@ -2415,9 +2416,8 @@ int gsi_alloc_channel(struct gsi_chan_props *props, unsigned long dev_hdl, if (props->prot == GSI_CHAN_PROT_GCI) user_data_size += GSI_VEID_MAX; - user_data = devm_kzalloc(gsi_ctx->dev, - user_data_size * sizeof(*user_data), - GFP_KERNEL); + size = user_data_size * sizeof(*user_data); + user_data = kzalloc(size, GFP_KERNEL); if (user_data == NULL) { GSIERR("context not allocated\n"); return -GSI_STATUS_RES_ALLOC_FAILURE; @@ -2442,14 +2442,14 @@ int gsi_alloc_channel(struct gsi_chan_props *props, unsigned long dev_hdl, if (res == 0) { GSIERR("chan_hdl=%u timed out\n", props->ch_id); mutex_unlock(&gsi_ctx->mlock); - devm_kfree(gsi_ctx->dev, user_data); + kfree(user_data); return -GSI_STATUS_TIMED_OUT; } if (ctx->state != GSI_CHAN_STATE_ALLOCATED) { GSIERR("chan_hdl=%u allocation failed state=%d\n", props->ch_id, ctx->state); mutex_unlock(&gsi_ctx->mlock); - devm_kfree(gsi_ctx->dev, user_data); + kfree(user_data); return -GSI_STATUS_RES_ALLOC_FAILURE; } mutex_unlock(&gsi_ctx->mlock); @@ -2462,7 +2462,7 @@ int gsi_alloc_channel(struct gsi_chan_props *props, unsigned long dev_hdl, GSI_NO_EVT_ERINDEX; if (erindex != GSI_NO_EVT_ERINDEX && erindex >= GSI_EVT_RING_MAX) { GSIERR("invalid erindex %u\n", erindex); - devm_kfree(gsi_ctx->dev, user_data); + kfree(user_data); return -GSI_STATUS_INVALID_PARAMS; } @@ -2549,7 +2549,7 @@ static int gsi_alloc_ap_channel(unsigned int chan_hdl) } static void __gsi_write_channel_scratch(unsigned long chan_hdl, - union gsi_channel_scratch val) + union __packed gsi_channel_scratch val) { gsi_writel(val.data.word1, gsi_ctx->base + GSI_EE_n_GSI_CH_k_SCRATCH_0_OFFS(chan_hdl, @@ -2567,7 +2567,7 @@ static void __gsi_write_channel_scratch(unsigned long chan_hdl, } int gsi_write_channel_scratch3_reg(unsigned long chan_hdl, - union gsi_wdi_channel_scratch3_reg val) + union __packed gsi_wdi_channel_scratch3_reg val) { struct gsi_chan_ctx *ctx; @@ -2630,7 +2630,7 @@ int gsi_write_channel_scratch2_reg(unsigned long chan_hdl, EXPORT_SYMBOL(gsi_write_channel_scratch2_reg); static void __gsi_read_channel_scratch(unsigned long chan_hdl, - union gsi_channel_scratch * val) + union __packed gsi_channel_scratch * val) { val->data.word1 = gsi_readl(gsi_ctx->base + GSI_EE_n_GSI_CH_k_SCRATCH_0_OFFS(chan_hdl, @@ -2649,10 +2649,10 @@ static void __gsi_read_channel_scratch(unsigned long chan_hdl, gsi_ctx->per.ee)); } -static union gsi_channel_scratch __gsi_update_mhi_channel_scratch( - unsigned long chan_hdl, struct gsi_mhi_channel_scratch mscr) +static union __packed gsi_channel_scratch __gsi_update_mhi_channel_scratch( + unsigned long chan_hdl, struct __packed gsi_mhi_channel_scratch mscr) { - union gsi_channel_scratch scr; + union __packed gsi_channel_scratch scr; /* below sequence is not atomic. assumption is sequencer specific fields * will remain unchanged across this sequence @@ -2709,7 +2709,7 @@ static union gsi_channel_scratch __gsi_update_mhi_channel_scratch( } int gsi_write_channel_scratch(unsigned long chan_hdl, - union gsi_channel_scratch val) + union __packed gsi_channel_scratch val) { struct gsi_chan_ctx *ctx; @@ -2739,9 +2739,10 @@ int gsi_write_channel_scratch(unsigned long chan_hdl, return GSI_STATUS_SUCCESS; } +EXPORT_SYMBOL(gsi_write_channel_scratch); int gsi_read_channel_scratch(unsigned long chan_hdl, - union gsi_channel_scratch *val) + union __packed gsi_channel_scratch *val) { struct gsi_chan_ctx *ctx; @@ -2771,9 +2772,10 @@ int gsi_read_channel_scratch(unsigned long chan_hdl, return GSI_STATUS_SUCCESS; } +EXPORT_SYMBOL(gsi_read_channel_scratch); int gsi_update_mhi_channel_scratch(unsigned long chan_hdl, - struct gsi_mhi_channel_scratch mscr) + struct __packed gsi_mhi_channel_scratch mscr) { struct gsi_chan_ctx *ctx; @@ -2802,6 +2804,7 @@ int gsi_update_mhi_channel_scratch(unsigned long chan_hdl, return GSI_STATUS_SUCCESS; } +EXPORT_SYMBOL(gsi_update_mhi_channel_scratch); int gsi_query_channel_db_addr(unsigned long chan_hdl, uint32_t *db_addr_wp_lsb, uint32_t *db_addr_wp_msb) @@ -2835,6 +2838,7 @@ int gsi_query_channel_db_addr(unsigned long chan_hdl, return GSI_STATUS_SUCCESS; } +EXPORT_SYMBOL(gsi_query_channel_db_addr); int gsi_pending_irq_type(void) { @@ -2920,6 +2924,7 @@ int gsi_start_channel(unsigned long chan_hdl) return GSI_STATUS_SUCCESS; } +EXPORT_SYMBOL(gsi_start_channel); int gsi_stop_channel(unsigned long chan_hdl) { @@ -2997,6 +3002,7 @@ int gsi_stop_channel(unsigned long chan_hdl) mutex_unlock(&gsi_ctx->mlock); return res; } +EXPORT_SYMBOL(gsi_stop_channel); int gsi_stop_db_channel(unsigned long chan_hdl) { @@ -3065,6 +3071,7 @@ int gsi_stop_db_channel(unsigned long chan_hdl) mutex_unlock(&gsi_ctx->mlock); return res; } +EXPORT_SYMBOL(gsi_stop_db_channel); int gsi_reset_channel(unsigned long chan_hdl) { @@ -3161,6 +3168,7 @@ int gsi_reset_channel(unsigned long chan_hdl) return GSI_STATUS_SUCCESS; } +EXPORT_SYMBOL(gsi_reset_channel); int gsi_dealloc_channel(unsigned long chan_hdl) { @@ -3220,7 +3228,7 @@ int gsi_dealloc_channel(unsigned long chan_hdl) ctx->state); mutex_unlock(&gsi_ctx->mlock); } - devm_kfree(gsi_ctx->dev, ctx->user_data); + kfree(ctx->user_data); ctx->allocated = false; if (ctx->evtr && (ctx->props.prot != GSI_CHAN_PROT_GCI)) atomic_dec(&ctx->evtr->chan_ref_cnt); @@ -3232,6 +3240,7 @@ int gsi_dealloc_channel(unsigned long chan_hdl) } return GSI_STATUS_SUCCESS; } +EXPORT_SYMBOL(gsi_dealloc_channel); void gsi_update_ch_dp_stats(struct gsi_chan_ctx *ctx, uint16_t used) { @@ -3355,6 +3364,7 @@ int gsi_query_channel_info(unsigned long chan_hdl, return GSI_STATUS_SUCCESS; } +EXPORT_SYMBOL(gsi_query_channel_info); int gsi_is_channel_empty(unsigned long chan_hdl, bool *is_empty) { @@ -3435,6 +3445,7 @@ int gsi_is_channel_empty(unsigned long chan_hdl, bool *is_empty) return GSI_STATUS_SUCCESS; } +EXPORT_SYMBOL(gsi_is_channel_empty); int __gsi_get_gci_cookie(struct gsi_chan_ctx *ctx, uint16_t idx) { @@ -3646,6 +3657,7 @@ int gsi_queue_xfer(unsigned long chan_hdl, uint16_t num_xfers, return GSI_STATUS_SUCCESS; } +EXPORT_SYMBOL(gsi_queue_xfer); int gsi_start_xfer(unsigned long chan_hdl) { diff --git a/drivers/platform/msm/ipa/ipa_clients/ipa_wdi3.c b/drivers/platform/msm/ipa/ipa_clients/ipa_wdi3.c index c9b7a82035a7..9853058fe9ac 100644 --- a/drivers/platform/msm/ipa/ipa_clients/ipa_wdi3.c +++ b/drivers/platform/msm/ipa/ipa_clients/ipa_wdi3.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2017-2021. The Linux Foundation. All rights reserved. */ #include @@ -117,10 +117,12 @@ int ipa_wdi_init(struct ipa_wdi_init_in_params *in, ipa_wdi_ctx->is_smmu_enabled = out->is_smmu_enabled; +#ifdef CONFIG_IPA3 if (ipa3_ctx->ipa_wdi3_over_gsi) out->is_over_gsi = true; else out->is_over_gsi = false; +#endif return 0; } EXPORT_SYMBOL(ipa_wdi_init); @@ -249,7 +251,7 @@ int ipa_wdi_reg_intf(struct ipa_wdi_reg_intf_in_params *in) tx_prop = kmalloc( sizeof(*tx_prop) * IPA_TX_MAX_INTF_PROP, GFP_KERNEL); if (!tx_prop) { - IPAERR("failed to allocate memory\n"); + IPA_WDI_ERR("failed to allocate memory\n"); ret = -ENOMEM; goto fail_commit_hdr; } @@ -258,20 +260,28 @@ int ipa_wdi_reg_intf(struct ipa_wdi_reg_intf_in_params *in) tx.prop = tx_prop; tx_prop[0].ip = IPA_IP_v4; +#ifdef CONFIG_IPA3 if (!ipa3_ctx->ipa_wdi3_over_gsi) tx_prop[0].dst_pipe = IPA_CLIENT_WLAN1_CONS; else tx_prop[0].dst_pipe = IPA_CLIENT_WLAN2_CONS; +#else + tx_prop[0].dst_pipe = IPA_CLIENT_WLAN1_CONS; +#endif tx_prop[0].alt_dst_pipe = in->alt_dst_pipe; tx_prop[0].hdr_l2_type = in->hdr_info[0].hdr_type; strlcpy(tx_prop[0].hdr_name, hdr->hdr[IPA_IP_v4].name, sizeof(tx_prop[0].hdr_name)); tx_prop[1].ip = IPA_IP_v6; +#ifdef CONFIG_IPA3 if (!ipa3_ctx->ipa_wdi3_over_gsi) tx_prop[1].dst_pipe = IPA_CLIENT_WLAN1_CONS; else tx_prop[1].dst_pipe = IPA_CLIENT_WLAN2_CONS; +#else + tx_prop[1].dst_pipe = IPA_CLIENT_WLAN1_CONS; +#endif tx_prop[1].alt_dst_pipe = in->alt_dst_pipe; tx_prop[1].hdr_l2_type = in->hdr_info[1].hdr_type; strlcpy(tx_prop[1].hdr_name, hdr->hdr[IPA_IP_v6].name, @@ -281,7 +291,7 @@ int ipa_wdi_reg_intf(struct ipa_wdi_reg_intf_in_params *in) rx_prop = kmalloc( sizeof(*rx_prop) * IPA_RX_MAX_INTF_PROP, GFP_KERNEL); if (!rx_prop) { - IPAERR("failed to allocate memory\n"); + IPA_WDI_ERR("failed to allocate memory\n"); ret = -ENOMEM; goto fail_commit_hdr; } @@ -289,10 +299,14 @@ int ipa_wdi_reg_intf(struct ipa_wdi_reg_intf_in_params *in) memset(rx_prop, 0, sizeof(*rx_prop) * IPA_RX_MAX_INTF_PROP); rx.prop = rx_prop; rx_prop[0].ip = IPA_IP_v4; +#ifdef CONFIG_IPA3 if (!ipa3_ctx->ipa_wdi3_over_gsi) rx_prop[0].src_pipe = IPA_CLIENT_WLAN1_PROD; else rx_prop[0].src_pipe = IPA_CLIENT_WLAN2_PROD; +#else + rx_prop[0].src_pipe = IPA_CLIENT_WLAN1_PROD; +#endif rx_prop[0].hdr_l2_type = in->hdr_info[0].hdr_type; if (in->is_meta_data_valid) { rx_prop[0].attrib.attrib_mask |= IPA_FLT_META_DATA; @@ -301,10 +315,14 @@ int ipa_wdi_reg_intf(struct ipa_wdi_reg_intf_in_params *in) } rx_prop[1].ip = IPA_IP_v6; +#ifdef CONFIG_IPA3 if (!ipa3_ctx->ipa_wdi3_over_gsi) rx_prop[1].src_pipe = IPA_CLIENT_WLAN1_PROD; else rx_prop[1].src_pipe = IPA_CLIENT_WLAN2_PROD; +#else + rx_prop[1].src_pipe = IPA_CLIENT_WLAN1_PROD; +#endif rx_prop[1].hdr_l2_type = in->hdr_info[1].hdr_type; if (in->is_meta_data_valid) { rx_prop[1].attrib.attrib_mask |= IPA_FLT_META_DATA; @@ -609,6 +627,7 @@ int ipa_wdi_disconn_pipes(void) } } +#ifdef CONFIG_IPA3 if (!ipa3_ctx->ipa_wdi3_over_gsi) { ipa_ep_idx_rx = ipa_get_ep_mapping(IPA_CLIENT_WLAN1_PROD); ipa_ep_idx_tx = ipa_get_ep_mapping(IPA_CLIENT_WLAN1_CONS); @@ -616,6 +635,10 @@ int ipa_wdi_disconn_pipes(void) ipa_ep_idx_rx = ipa_get_ep_mapping(IPA_CLIENT_WLAN2_PROD); ipa_ep_idx_tx = ipa_get_ep_mapping(IPA_CLIENT_WLAN2_CONS); } +#else + ipa_ep_idx_rx = ipa_get_ep_mapping(IPA_CLIENT_WLAN1_PROD); + ipa_ep_idx_tx = ipa_get_ep_mapping(IPA_CLIENT_WLAN1_CONS); +#endif if (ipa_wdi_ctx->wdi_version == IPA_WDI_3) { if (ipa_disconn_wdi_pipes(ipa_ep_idx_rx, ipa_ep_idx_tx)) { @@ -652,6 +675,7 @@ int ipa_wdi_enable_pipes(void) return -EPERM; } +#ifdef CONFIG_IPA3 if (!ipa3_ctx->ipa_wdi3_over_gsi) { ipa_ep_idx_rx = ipa_get_ep_mapping(IPA_CLIENT_WLAN1_PROD); ipa_ep_idx_tx = ipa_get_ep_mapping(IPA_CLIENT_WLAN1_CONS); @@ -659,6 +683,10 @@ int ipa_wdi_enable_pipes(void) ipa_ep_idx_rx = ipa_get_ep_mapping(IPA_CLIENT_WLAN2_PROD); ipa_ep_idx_tx = ipa_get_ep_mapping(IPA_CLIENT_WLAN2_CONS); } +#else + ipa_ep_idx_rx = ipa_get_ep_mapping(IPA_CLIENT_WLAN1_PROD); + ipa_ep_idx_tx = ipa_get_ep_mapping(IPA_CLIENT_WLAN1_CONS); +#endif if (ipa_wdi_ctx->wdi_version == IPA_WDI_3) { if (ipa_enable_wdi_pipes(ipa_ep_idx_tx, ipa_ep_idx_rx)) { @@ -704,6 +732,7 @@ int ipa_wdi_disable_pipes(void) return -EPERM; } +#ifdef CONFIG_IPA3 if (!ipa3_ctx->ipa_wdi3_over_gsi) { ipa_ep_idx_rx = ipa_get_ep_mapping(IPA_CLIENT_WLAN1_PROD); ipa_ep_idx_tx = ipa_get_ep_mapping(IPA_CLIENT_WLAN1_CONS); @@ -711,6 +740,10 @@ int ipa_wdi_disable_pipes(void) ipa_ep_idx_rx = ipa_get_ep_mapping(IPA_CLIENT_WLAN2_PROD); ipa_ep_idx_tx = ipa_get_ep_mapping(IPA_CLIENT_WLAN2_CONS); } +#else + ipa_ep_idx_rx = ipa_get_ep_mapping(IPA_CLIENT_WLAN1_PROD); + ipa_ep_idx_tx = ipa_get_ep_mapping(IPA_CLIENT_WLAN1_CONS); +#endif if (ipa_wdi_ctx->wdi_version == IPA_WDI_3) { if (ipa_disable_wdi_pipes(ipa_ep_idx_tx, ipa_ep_idx_rx)) { diff --git a/drivers/platform/msm/ipa/ipa_common_i.h b/drivers/platform/msm/ipa/ipa_common_i.h index f76b3fe7a698..d2f3b32cfb90 100644 --- a/drivers/platform/msm/ipa/ipa_common_i.h +++ b/drivers/platform/msm/ipa/ipa_common_i.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2020, 2021 The Linux Foundation. All rights reserved. */ #include @@ -103,6 +103,23 @@ ipa_dec_client_disable_clks(&log_info); \ } while (0) +#define IPA_ACTIVE_CLIENTS_INC_EP_NO_BLOCK(client) ({\ + int __ret = 0; \ + do { \ + struct ipa_active_client_logging_info log_info; \ + IPA_ACTIVE_CLIENTS_PREP_EP(log_info, client); \ + __ret = ipa3_inc_client_enable_clks_no_block(&log_info); \ + } while (0); \ + (__ret); \ +}) + +#define IPA_ACTIVE_CLIENTS_DEC_EP_NO_BLOCK(client) \ + do { \ + struct ipa_active_client_logging_info log_info; \ + IPA_ACTIVE_CLIENTS_PREP_EP(log_info, client); \ + ipa3_dec_client_disable_clks_no_block(&log_info); \ + } while (0) + /* * Printing one warning message in 5 seconds if multiple warning messages * are coming back to back. diff --git a/drivers/platform/msm/ipa/ipa_v2/ipa_qmi_service.c b/drivers/platform/msm/ipa/ipa_v2/ipa_qmi_service.c index d322deebda4f..0392fad26e54 100644 --- a/drivers/platform/msm/ipa/ipa_v2/ipa_qmi_service.c +++ b/drivers/platform/msm/ipa/ipa_v2/ipa_qmi_service.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2013-2018, 2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2013-2018, 2020-2021, The Linux Foundation. All rights reserved. */ #include @@ -423,6 +423,54 @@ static int qmi_init_modem_send_sync_msg(void) resp.resp.error, "ipa_init_modem_driver_resp_msg_v01"); } +static int ipa_qmi_filter_request_ex_calc_length( + struct ipa_install_fltr_rule_req_msg_v01 *req) +{ + int len = 0; + + /* caller should validate and send the req */ + /* instead of sending max length,the approximate length is calculated */ + len += ((sizeof(struct ipa_install_fltr_rule_req_msg_v01)) - + (QMI_IPA_MAX_FILTERS_V01 * + sizeof(struct ipa_filter_spec_type_v01)) - + (QMI_IPA_MAX_FILTERS_V01 * sizeof(uint32_t)) - + (QMI_IPA_MAX_FILTERS_V01 * + sizeof(struct ipa_filter_spec_ex_type_v01))- + (QMI_IPA_MAX_FILTERS_V01 * + sizeof(struct ipa_filter_spec_ex2_type_v01))- + (QMI_IPA_MAX_FILTERS_V01 * sizeof(uint32_t))); + + if (req->filter_spec_list_valid && + req->filter_spec_list_len > 0) { + len += sizeof(struct ipa_filter_spec_type_v01)* + req->filter_spec_list_len; + } + + if (req->xlat_filter_indices_list_valid && + req->xlat_filter_indices_list_len > 0) { + len += sizeof(uint32_t)*req->xlat_filter_indices_list_len; + } + + if (req->filter_spec_ex_list_valid && + req->filter_spec_ex_list_len > 0) { + len += sizeof(struct ipa_filter_spec_ex_type_v01)* + req->filter_spec_ex_list_len; + } + + if (req->filter_spec_ex2_list_valid && + req->filter_spec_ex2_list_len > 0) { + len += sizeof(struct ipa_filter_spec_ex2_type_v01)* + req->filter_spec_ex2_list_len; + } + + if (req->ul_firewall_indices_list_valid && + req->ul_firewall_indices_list_len > 0) { + len += sizeof(uint32_t)*req->ul_firewall_indices_list_len; + } + + return len; +} + /* sending filter-install-request to modem*/ int qmi_filter_request_send(struct ipa_install_fltr_rule_req_msg_v01 *req) { @@ -491,7 +539,9 @@ int qmi_filter_request_send(struct ipa_install_fltr_rule_req_msg_v01 *req) } mutex_unlock(&ipa_qmi_lock); - req_desc.max_msg_len = QMI_IPA_INSTALL_FILTER_RULE_REQ_MAX_MSG_LEN_V01; + req_desc.max_msg_len = ipa_qmi_filter_request_ex_calc_length(req); + IPAWANDBG("QMI send request length = %d\n", req_desc.max_msg_len); + req_desc.msg_id = QMI_IPA_INSTALL_FILTER_RULE_REQ_V01; req_desc.ei_array = ipa_install_fltr_rule_req_msg_data_v01_ei; diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa.c b/drivers/platform/msm/ipa/ipa_v3/ipa.c index 8a32394f1189..7f03e6d17bc1 100644 --- a/drivers/platform/msm/ipa/ipa_v3/ipa.c +++ b/drivers/platform/msm/ipa/ipa_v3/ipa.c @@ -6845,6 +6845,13 @@ static int ipa3_pre_init(const struct ipa3_plat_drv_res *resource_p, result = -ENOMEM; goto fail_hdr_offset_cache; } + ipa3_ctx->fnr_stats_cache = kmem_cache_create("IPA_FNR_STATS", + sizeof(struct ipa_ioc_flt_rt_counter_alloc), 0, 0, NULL); + if (!ipa3_ctx->fnr_stats_cache) { + IPAERR(":ipa fnr stats cache create failed\n"); + result = -ENOMEM; + goto fail_fnr_stats_cache; + } ipa3_ctx->hdr_proc_ctx_cache = kmem_cache_create("IPA_HDR_PROC_CTX", sizeof(struct ipa3_hdr_proc_ctx_entry), 0, 0, NULL); if (!ipa3_ctx->hdr_proc_ctx_cache) { @@ -7079,6 +7086,8 @@ static int ipa3_pre_init(const struct ipa3_plat_drv_res *resource_p, fail_hdr_proc_ctx_offset_cache: kmem_cache_destroy(ipa3_ctx->hdr_proc_ctx_cache); fail_hdr_proc_ctx_cache: + kmem_cache_destroy(ipa3_ctx->fnr_stats_cache); +fail_fnr_stats_cache: kmem_cache_destroy(ipa3_ctx->hdr_offset_cache); fail_hdr_offset_cache: kmem_cache_destroy(ipa3_ctx->hdr_cache); diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_debugfs.c b/drivers/platform/msm/ipa/ipa_v3/ipa_debugfs.c index 5437e3280395..327122bd9c0d 100644 --- a/drivers/platform/msm/ipa/ipa_v3/ipa_debugfs.c +++ b/drivers/platform/msm/ipa/ipa_v3/ipa_debugfs.c @@ -794,7 +794,7 @@ static ssize_t ipa3_read_rt(struct file *file, char __user *ubuf, size_t count, struct ipa3_rt_tbl *tbl; struct ipa3_rt_entry *entry; struct ipa3_rt_tbl_set *set; - enum ipa_ip_type ip = (enum ipa_ip_type)(long)file->private_data; + enum ipa_ip_type ip = (enum ipa_ip_type)file->private_data; u32 ofst; u32 ofst_words; @@ -891,7 +891,7 @@ static ssize_t ipa3_read_rt(struct file *file, char __user *ubuf, size_t count, static ssize_t ipa3_read_rt_hw(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { - enum ipa_ip_type ip = (enum ipa_ip_type)(long)file->private_data; + enum ipa_ip_type ip = (enum ipa_ip_type)file->private_data; int tbls_num; int rules_num; int tbl; @@ -1059,7 +1059,7 @@ static ssize_t ipa3_read_flt(struct file *file, char __user *ubuf, size_t count, int j; struct ipa3_flt_tbl *tbl; struct ipa3_flt_entry *entry; - enum ipa_ip_type ip = (enum ipa_ip_type)(long)file->private_data; + enum ipa_ip_type ip = (enum ipa_ip_type)file->private_data; struct ipa3_rt_tbl *rt_tbl; u32 rt_tbl_idx; u32 bitmap; @@ -1130,7 +1130,7 @@ static ssize_t ipa3_read_flt_hw(struct file *file, char __user *ubuf, int rl; int rules_num; struct ipahal_flt_rule_entry *rules; - enum ipa_ip_type ip = (enum ipa_ip_type)(long)file->private_data; + enum ipa_ip_type ip = (enum ipa_ip_type)file->private_data; u32 rt_tbl_idx; u32 bitmap; int res = 0; diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_dp.c b/drivers/platform/msm/ipa/ipa_v3/ipa_dp.c index 2d0af4f6ba26..5d6191ebd1d4 100644 --- a/drivers/platform/msm/ipa/ipa_v3/ipa_dp.c +++ b/drivers/platform/msm/ipa/ipa_v3/ipa_dp.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. */ #include @@ -824,7 +824,7 @@ static int ipa3_rx_switch_to_intr_mode(struct ipa3_sys_context *sys) atomic_set(&sys->curr_polling_state, 0); __ipa3_update_curr_poll_state(sys->ep->client, 0); - + ipa_pm_deferred_deactivate(sys->pm_hdl); ipa3_dec_release_wakelock(); ret = gsi_config_channel_mode(sys->ep->gsi_chan_hdl, GSI_CHAN_MODE_CALLBACK); @@ -857,8 +857,8 @@ static void ipa3_handle_rx(struct ipa3_sys_context *sys) int cnt; int ret; - ipa_pm_activate_sync(sys->pm_hdl); start_poll: + ipa_pm_activate_sync(sys->pm_hdl); inactive_cycles = 0; do { cnt = ipa3_handle_rx_core(sys, true, true); @@ -886,7 +886,7 @@ static void ipa3_handle_rx(struct ipa3_sys_context *sys) if (ret == -GSI_STATUS_PENDING_IRQ) goto start_poll; - ipa_pm_deferred_deactivate(sys->pm_hdl); + IPA_ACTIVE_CLIENTS_DEC_EP(sys->ep->client); } static void ipa3_switch_to_intr_rx_work_func(struct work_struct *work) @@ -1814,11 +1814,21 @@ int ipa3_tx_dp(enum ipa_client_type dst, struct sk_buff *skb, static void ipa3_wq_handle_rx(struct work_struct *work) { struct ipa3_sys_context *sys; + enum ipa_client_type client_type; sys = container_of(work, struct ipa3_sys_context, work); + /* + * Mark client as WAN_COAL_CONS only as + * NAPI only use sys of WAN_COAL_CONS. + */ + if (IPA_CLIENT_IS_WAN_CONS(sys->ep->client)) + client_type = IPA_CLIENT_APPS_WAN_COAL_CONS; + else + client_type = sys->ep->client; + + IPA_ACTIVE_CLIENTS_INC_EP(client_type); if (sys->napi_obj) { - ipa_pm_activate_sync(sys->pm_hdl); napi_schedule(sys->napi_obj); IPA_STATS_INC_CNT(sys->napi_sch_cnt); } else @@ -4415,7 +4425,8 @@ static void ipa_gsi_irq_tx_notify_cb(struct gsi_chan_xfer_notify *notify) void __ipa_gsi_irq_rx_scedule_poll(struct ipa3_sys_context *sys) { - bool clk_off; + bool clk_off = true; + enum ipa_client_type client_type; atomic_set(&sys->curr_polling_state, 1); __ipa3_update_curr_poll_state(sys->ep->client, 1); @@ -4423,11 +4434,21 @@ void __ipa_gsi_irq_rx_scedule_poll(struct ipa3_sys_context *sys) ipa3_inc_acquire_wakelock(); /* - * pm deactivate is done in wq context - * or after NAPI poll + * Mark client as WAN_COAL_CONS only as + * NAPI only use sys of WAN_COAL_CONS. */ + if (IPA_CLIENT_IS_WAN_CONS(sys->ep->client)) + client_type = IPA_CLIENT_APPS_WAN_COAL_CONS; + else + client_type = sys->ep->client; + /* + * Have race condition to use PM on poll to isr + * switch. Use the active no block instead + * where we would have ref counts. + */ + if (sys->napi_obj) + clk_off = IPA_ACTIVE_CLIENTS_INC_EP_NO_BLOCK(client_type); - clk_off = ipa_pm_activate(sys->pm_hdl); if (!clk_off && sys->napi_obj) { napi_schedule(sys->napi_obj); IPA_STATS_INC_CNT(sys->napi_sch_cnt); @@ -4973,6 +4994,11 @@ int ipa3_lan_rx_poll(u32 clnt_hdl, int weight) ep = &ipa3_ctx->ep[clnt_hdl]; start_poll: + /* + * it is guaranteed we already have clock here. + * This is mainly for clock scaling. + */ + ipa_pm_activate(ep->sys->pm_hdl); while (remain_aggr_weight > 0 && atomic_read(&ep->sys->curr_polling_state)) { atomic_set(&ipa3_ctx->transport_pm.eot_activity, 1); @@ -5002,7 +5028,7 @@ int ipa3_lan_rx_poll(u32 clnt_hdl, int weight) napi_reschedule(ep->sys->napi_obj)) goto start_poll; - ipa_pm_deferred_deactivate(ep->sys->pm_hdl); + IPA_ACTIVE_CLIENTS_DEC_EP_NO_BLOCK(ep->client); } return cnt; @@ -5055,6 +5081,11 @@ int ipa3_rx_poll(u32 clnt_hdl, int weight) ep = &ipa3_ctx->ep[clnt_hdl]; start_poll: + /* + * it is guaranteed we already have clock here. + * This is mainly for clock scaling. + */ + ipa_pm_activate(ep->sys->pm_hdl); while (remain_aggr_weight > 0 && atomic_read(&ep->sys->curr_polling_state)) { atomic_set(&ipa3_ctx->transport_pm.eot_activity, 1); @@ -5094,7 +5125,7 @@ int ipa3_rx_poll(u32 clnt_hdl, int weight) if (ret == -GSI_STATUS_PENDING_IRQ && napi_reschedule(ep->sys->napi_obj)) goto start_poll; - ipa_pm_deferred_deactivate(ep->sys->pm_hdl); + IPA_ACTIVE_CLIENTS_DEC_EP_NO_BLOCK(ep->client); } else { cnt = weight; IPADBG_LOW("Client = %d not replenished free descripotrs\n", diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_hw_stats.c b/drivers/platform/msm/ipa/ipa_v3/ipa_hw_stats.c index 9466616b80b1..1b900a27173b 100644 --- a/drivers/platform/msm/ipa/ipa_v3/ipa_hw_stats.c +++ b/drivers/platform/msm/ipa/ipa_v3/ipa_hw_stats.c @@ -2248,7 +2248,7 @@ static ssize_t ipa_debugfs_enable_disable_drop_stats(struct file *file, ssize_t ret; mutex_lock(&ipa3_ctx->lock); - if (sizeof(dbg_buff) < count) { + if (sizeof(dbg_buff) < count + 1) { ret = -EFAULT; goto bail; } diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_i.h b/drivers/platform/msm/ipa/ipa_v3/ipa_i.h index 8e70692a6b70..29a8fbdb0e1b 100644 --- a/drivers/platform/msm/ipa/ipa_v3/ipa_i.h +++ b/drivers/platform/msm/ipa/ipa_v3/ipa_i.h @@ -1781,6 +1781,7 @@ struct ipa3_app_clock_vote { * @rt_rule_cache: routing rule cache * @hdr_cache: header cache * @hdr_offset_cache: header offset cache + * @fnr_stats_cache: FnR stats cache * @hdr_proc_ctx_cache: processing context cache * @hdr_proc_ctx_offset_cache: processing context offset cache * @rt_tbl_cache: routing table cache @@ -1884,6 +1885,7 @@ struct ipa3_context { struct kmem_cache *rt_rule_cache; struct kmem_cache *hdr_cache; struct kmem_cache *hdr_offset_cache; + struct kmem_cache *fnr_stats_cache; struct kmem_cache *hdr_proc_ctx_cache; struct kmem_cache *hdr_proc_ctx_offset_cache; struct kmem_cache *rt_tbl_cache; diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_pm.c b/drivers/platform/msm/ipa/ipa_v3/ipa_pm.c index 4d78b7a192b0..5409af242471 100644 --- a/drivers/platform/msm/ipa/ipa_v3/ipa_pm.c +++ b/drivers/platform/msm/ipa/ipa_v3/ipa_pm.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. */ #include @@ -459,6 +459,11 @@ static void delayed_deferred_deactivate_work_func(struct work_struct *work) dwork = container_of(work, struct delayed_work, work); client = container_of(dwork, struct ipa_pm_client, deactivate_work); + if (unlikely(client == NULL)) { + IPA_PM_ERR("Client already deregistered\n"); + return; + } + spin_lock_irqsave(&client->state_lock, flags); IPA_PM_DBG_STATE(client->hdl, client->name, client->state); switch (client->state) { @@ -1188,6 +1193,8 @@ int ipa_pm_deactivate_sync(u32 hdl) client->state = IPA_PM_DEACTIVATED; IPA_PM_DBG_STATE(hdl, client->name, client->state); spin_unlock_irqrestore(&client->state_lock, flags); + /*Check any delayed work queue scheduled*/ + cancel_delayed_work_sync(&client->deactivate_work); deactivate_client(hdl); do_clk_scaling(); diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_utils.c b/drivers/platform/msm/ipa/ipa_v3/ipa_utils.c index 63f4b4bfc35a..14699e988a04 100644 --- a/drivers/platform/msm/ipa/ipa_v3/ipa_utils.c +++ b/drivers/platform/msm/ipa/ipa_v3/ipa_utils.c @@ -6070,12 +6070,21 @@ static int __ipa3_alloc_counter_hdl return id; } -int ipa3_alloc_counter_id(struct ipa_ioc_flt_rt_counter_alloc *counter) +int ipa3_alloc_counter_id(struct ipa_ioc_flt_rt_counter_alloc *header) { int i, unused_cnt, unused_max, unused_start_id; + struct ipa_ioc_flt_rt_counter_alloc *counter; + + counter = kmem_cache_zalloc(ipa3_ctx->fnr_stats_cache, GFP_KERNEL); + if (!counter) { + IPAERR_RL("failed to alloc fnr stats counter object\n"); + spin_unlock(&ipa3_ctx->flt_rt_counters.hdl_lock); + return -ENOMEM; + } idr_preload(GFP_KERNEL); spin_lock(&ipa3_ctx->flt_rt_counters.hdl_lock); + memcpy(counter, header, sizeof(struct ipa_ioc_flt_rt_counter_alloc)); /* allocate hw counters */ counter->hw_counter.start_id = 0; @@ -6176,7 +6185,7 @@ int ipa3_alloc_counter_id(struct ipa_ioc_flt_rt_counter_alloc *counter) unused_start_id = counter->hw_counter.start_id; if (unused_start_id < 1 || unused_start_id > IPA_FLT_RT_HW_COUNTER) { - IPAERR("unexpected hw_counter start id %d\n", + IPAERR_RL("unexpected hw_counter start id %d\n", unused_start_id); goto err; } @@ -6191,7 +6200,7 @@ int ipa3_alloc_counter_id(struct ipa_ioc_flt_rt_counter_alloc *counter) - IPA_FLT_RT_HW_COUNTER; if (unused_start_id < 1 || unused_start_id > IPA_FLT_RT_SW_COUNTER) { - IPAERR("unexpected sw_counter start id %d\n", + IPAERR_RL("unexpected sw_counter start id %d\n", unused_start_id); goto err; } @@ -6201,12 +6210,14 @@ int ipa3_alloc_counter_id(struct ipa_ioc_flt_rt_counter_alloc *counter) done: /* get a handle from idr for dealloc */ counter->hdl = __ipa3_alloc_counter_hdl(counter); + memcpy(header, counter, sizeof(struct ipa_ioc_flt_rt_counter_alloc)); spin_unlock(&ipa3_ctx->flt_rt_counters.hdl_lock); idr_preload_end(); return 0; err: counter->hdl = -1; + kmem_cache_free(ipa3_ctx->fnr_stats_cache, counter); spin_unlock(&ipa3_ctx->flt_rt_counters.hdl_lock); idr_preload_end(); return -ENOMEM; @@ -6220,7 +6231,7 @@ void ipa3_counter_remove_hdl(int hdl) spin_lock(&ipa3_ctx->flt_rt_counters.hdl_lock); counter = idr_find(&ipa3_ctx->flt_rt_counters.hdl, hdl); if (counter == NULL) { - IPAERR("unexpected hdl %d\n", hdl); + IPAERR_RL("unexpected hdl %d\n", hdl); goto err; } /* remove counters belong to this hdl, set used back to 0 */ @@ -6230,7 +6241,7 @@ void ipa3_counter_remove_hdl(int hdl) memset(&ipa3_ctx->flt_rt_counters.used_hw + offset, 0, counter->hw_counter.num_counters * sizeof(bool)); } else { - IPAERR("unexpected hdl %d\n", hdl); + IPAERR_RL("unexpected hdl %d\n", hdl); goto err; } offset = counter->sw_counter.start_id - 1 - IPA_FLT_RT_HW_COUNTER; @@ -6239,11 +6250,12 @@ void ipa3_counter_remove_hdl(int hdl) memset(&ipa3_ctx->flt_rt_counters.used_sw + offset, 0, counter->sw_counter.num_counters * sizeof(bool)); } else { - IPAERR("unexpected hdl %d\n", hdl); + IPAERR_RL("unexpected hdl %d\n", hdl); goto err; } /* remove the handle */ idr_remove(&ipa3_ctx->flt_rt_counters.hdl, hdl); + kmem_cache_free(ipa3_ctx->fnr_stats_cache, counter); err: spin_unlock(&ipa3_ctx->flt_rt_counters.hdl_lock); } @@ -6260,8 +6272,10 @@ void ipa3_counter_id_remove_all(void) memset(&ipa3_ctx->flt_rt_counters.used_sw, 0, sizeof(ipa3_ctx->flt_rt_counters.used_sw)); /* remove all handles */ - idr_for_each_entry(&ipa3_ctx->flt_rt_counters.hdl, counter, hdl) + idr_for_each_entry(&ipa3_ctx->flt_rt_counters.hdl, counter, hdl) { idr_remove(&ipa3_ctx->flt_rt_counters.hdl, hdl); + kmem_cache_free(ipa3_ctx->fnr_stats_cache, counter); + } spin_unlock(&ipa3_ctx->flt_rt_counters.hdl_lock); } @@ -6802,7 +6816,7 @@ u32 ipa3_get_num_pipes(void) /** * ipa3_disable_apps_wan_cons_deaggr()- - * set ipa_ctx->ipa_client_apps_wan_cons_agg_gro + * set ipa3_ctx->ipa_client_apps_wan_cons_agg_gro * * Return value: 0 or negative in case of failure */ diff --git a/drivers/platform/msm/ipa/ipa_v3/ipahal/ipahal_hw_stats.c b/drivers/platform/msm/ipa/ipa_v3/ipahal/ipahal_hw_stats.c index 0a146d4858be..3edca59dcf8c 100644 --- a/drivers/platform/msm/ipa/ipa_v3/ipahal/ipahal_hw_stats.c +++ b/drivers/platform/msm/ipa/ipa_v3/ipahal/ipahal_hw_stats.c @@ -225,7 +225,7 @@ static struct ipahal_stats_init_pyld *ipahal_generate_init_pyld_flt_rt_v4_5( void *params, bool is_atomic_ctx) { struct ipahal_stats_init_pyld *pyld; - int num = (int)(long)params; + int num = (int)(params); if (num > IPA_MAX_FLT_RT_CNT_INDEX || num <= 0) { diff --git a/drivers/platform/msm/qcom-geni-se.c b/drivers/platform/msm/qcom-geni-se.c index 16e799ba9367..b8666893f168 100644 --- a/drivers/platform/msm/qcom-geni-se.c +++ b/drivers/platform/msm/qcom-geni-se.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ #include @@ -1597,16 +1597,16 @@ void geni_se_dump_dbg_regs(struct se_geni_rsc *rsc, void __iomem *base, se_dma_tx_len = geni_read_reg(base, SE_DMA_TX_LEN); se_dma_tx_len_in = geni_read_reg(base, SE_DMA_TX_LEN_IN); - GENI_SE_ERR(ipc, true, rsc->ctrl_dev, + GENI_SE_DBG(ipc, true, rsc->ctrl_dev, "%s: m_cmd0:0x%x, m_irq_status:0x%x, s_irq_status:0x%x, geni_status:0x%x, geni_ios:0x%x\n", __func__, m_cmd0, m_irq_status, s_irq_status, geni_status, geni_ios); - GENI_SE_ERR(ipc, true, rsc->ctrl_dev, + GENI_SE_DBG(ipc, true, rsc->ctrl_dev, "dma_rx_irq:0x%x, dma_tx_irq:0x%x, rx_fifo_sts:0x%x, tx_fifo_sts:0x%x\n" , dma_rx_irq, dma_tx_irq, rx_fifo_status, tx_fifo_status); - GENI_SE_ERR(ipc, true, rsc->ctrl_dev, + GENI_SE_DBG(ipc, true, rsc->ctrl_dev, "se_dma_dbg:0x%x, m_cmd_ctrl:0x%x, dma_rxlen:0x%x, dma_rxlen_in:0x%x\n", se_dma_dbg, m_cmd_ctrl, se_dma_rx_len, se_dma_rx_len_in); - GENI_SE_ERR(ipc, true, rsc->ctrl_dev, + GENI_SE_DBG(ipc, true, rsc->ctrl_dev, "dma_txlen:0x%x, dma_txlen_in:0x%x\n", se_dma_tx_len, se_dma_tx_len_in); } EXPORT_SYMBOL(geni_se_dump_dbg_regs); diff --git a/drivers/platform/msm/qpnp-revid.c b/drivers/platform/msm/qpnp-revid.c index 320bc1040aff..2d2980fffade 100644 --- a/drivers/platform/msm/qpnp-revid.c +++ b/drivers/platform/msm/qpnp-revid.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #include @@ -161,13 +160,14 @@ static size_t build_pmic_string(char *buf, size_t n, int sid, } #define PMIC_PERIPHERAL_TYPE 0x51 -char hq_pmic_string[PMIC_STRING_MAXLENGTH] = {'\0'}; +#define PMIC_STRING_MAXLENGTH 80 static int qpnp_revid_probe(struct platform_device *pdev) { u8 rev1, rev2, rev3, rev4, pmic_type, pmic_subtype, pmic_status; u8 option1, option2, option3, option4, spare0; unsigned int base; int rc, fab_id, tp_rev; + char pmic_string[PMIC_STRING_MAXLENGTH] = {'\0'}; struct revid_chip *revid_chip; struct regmap *regmap; @@ -248,11 +248,11 @@ static int qpnp_revid_probe(struct platform_device *pdev) option3 = (pmic_status >> 4) & 0x3; option4 = (pmic_status >> 6) & 0x3; - build_pmic_string(hq_pmic_string, PMIC_STRING_MAXLENGTH, + build_pmic_string(pmic_string, PMIC_STRING_MAXLENGTH, to_spmi_device(pdev->dev.parent)->usid, pmic_subtype, rev1, rev2, rev3, rev4); pr_info("%s options: %d, %d, %d, %d\n", - hq_pmic_string, option1, option2, option3, option4); + pmic_string, option1, option2, option3, option4); return 0; } diff --git a/drivers/platform/msm/usb_bam.c b/drivers/platform/msm/usb_bam.c index f9eb90e8dab2..157385855c43 100644 --- a/drivers/platform/msm/usb_bam.c +++ b/drivers/platform/msm/usb_bam.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2011-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2011-2020,2021 The Linux Foundation. All rights reserved. */ #include @@ -15,6 +15,7 @@ #include #include #include +#include #define USB_THRESHOLD 512 #define USB_BAM_MAX_STR_LEN 50 @@ -60,6 +61,9 @@ do { \ #define log_event_dbg(x, ...) log_event(LOGLEVEL_DEBUG, x, ##__VA_ARGS__) #define log_event_err(x, ...) log_event(LOGLEVEL_ERR, x, ##__VA_ARGS__) +/* Reset BAM with pipes connected */ +#define SPS_BAM_FORCE_RESET (1UL << 11) + enum usb_bam_event_type { USB_BAM_EVENT_WAKEUP_PIPE = 0, /* Wake a pipe */ USB_BAM_EVENT_WAKEUP, /* Wake a bam (first pipe waked) */ @@ -176,6 +180,7 @@ struct msm_usb_bam_data { bool reset_on_connect; bool reset_on_disconnect; bool disable_clk_gating; + bool ignore_core_reset_ack; u32 override_threshold; u32 max_mbps_highspeed; u32 max_mbps_superspeed; @@ -219,6 +224,8 @@ struct usb_bam_ctx_type { static char *bam_enable_strings[MAX_BAMS] = { [DWC3_CTRL] = "ssusb", + [CI_CTRL] = "hsusb", + [HSIC_CTRL] = "hsic", }; struct usb_bam_host_info { @@ -324,15 +331,25 @@ static void __maybe_unused put_timestamp(char *tbuf) nanosec_rem); } -static inline enum usb_ctrl get_bam_type_from_core_name(const char *name) +void msm_bam_set_hsic_host_dev(struct device *dev) { - if (strnstr(name, bam_enable_strings[DWC3_CTRL], - USB_BAM_MAX_STR_LEN) || - strnstr(name, "dwc3", USB_BAM_MAX_STR_LEN)) - return DWC3_CTRL; + if (dev) { + /* Hold the device until allowing lpm */ + log_event_dbg("%s: Getting hsic device %pK\n", __func__, dev); + pm_runtime_get(dev); + } else if (host_info[HSIC_CTRL].dev) { + log_event_dbg("%s: Try Putting hsic device %pK, lpm:%d\n", + __func__, host_info[HSIC_CTRL].dev, + host_info[HSIC_CTRL].in_lpm); + /* Just release previous device if not already done */ + if (!host_info[HSIC_CTRL].in_lpm) { + host_info[HSIC_CTRL].in_lpm = true; + pm_runtime_put(host_info[HSIC_CTRL].dev); + } + } - log_event_err("%s: invalid BAM name(%s)\n", __func__, name); - return -EINVAL; + host_info[HSIC_CTRL].dev = dev; + host_info[HSIC_CTRL].in_lpm = false; } static void usb_bam_set_inactivity_timer(enum usb_ctrl bam) @@ -379,6 +396,156 @@ void msm_bam_set_usb_host_dev(struct device *dev) host_info[CI_CTRL].in_lpm = false; } +static void _msm_bam_host_notify_on_resume(enum usb_ctrl bam_type) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + + spin_lock(&ctx->usb_bam_lock); + log_event_dbg("%s: enter bam=%s\n", __func__, + bam_enable_strings[bam_type]); + + host_info[bam_type].in_lpm = false; + + /* + * This function is called to notify the usb bam driver + * that the hsic core and hsic bam hw are fully resumed + * and clocked on. Therefore we can now set the inactivity + * timer to the hsic bam hw. + */ + if (ctx->inactivity_timer_ms) + usb_bam_set_inactivity_timer(bam_type); + + spin_unlock(&ctx->usb_bam_lock); +} + +/** + * msm_bam_hsic_host_pipe_empty - Check all HSIC host BAM pipe state + * + * return true if all BAM pipe used for HSIC Host mode is empty. + */ +bool msm_bam_hsic_host_pipe_empty(void) +{ + struct usb_bam_pipe_connect *pipe_connect; + struct sps_pipe *pipe = NULL; + enum usb_ctrl bam = HSIC_CTRL; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam]; + int i, ret; + u32 status; + + log_event_dbg("%s: enter\n", __func__); + + for (i = 0; i < ctx->max_connections; i++) { + pipe_connect = &ctx->usb_bam_connections[i]; + if (pipe_connect->enabled) { + pipe = ctx->usb_bam_sps.sps_pipes[i]; + ret = sps_is_pipe_empty(pipe, &status); + if (ret) { + log_event_err("%s(): sps_is_pipe_empty() failed\n", + __func__); + log_event_err("%s(): SRC index(%d), DEST index(%d):\n", + __func__, + pipe_connect->src_pipe_index, + pipe_connect->dst_pipe_index); + WARN_ON(1); + } + + if (!status) { + log_event_err("%s(): pipe is not empty.\n", + __func__); + log_event_err("%s(): SRC index(%d), DEST index(%d):\n", + __func__, + pipe_connect->src_pipe_index, + pipe_connect->dst_pipe_index); + return false; + } + + log_event_dbg("%s(): SRC index(%d), DEST index(%d):\n", + __func__, + pipe_connect->src_pipe_index, + pipe_connect->dst_pipe_index); + } + + } + + if (!pipe) + log_event_err("%s: Bam %s has no connected pipes\n", __func__, + bam_enable_strings[bam]); + + return true; +} +EXPORT_SYMBOL(msm_bam_hsic_host_pipe_empty); + +static bool msm_bam_host_lpm_ok(enum usb_ctrl bam_type) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + struct usb_bam_pipe_connect *pipe_iter; + int i; + + log_event_dbg("%s: enter bam=%s\n", __func__, + bam_enable_strings[bam_type]); + + if (!host_info[bam_type].dev) + return true; + + log_event_dbg("%s: Starting hsic full suspend sequence\n", + __func__); + + log_event_dbg("%s(): checking HSIC Host pipe state\n", __func__); + if (!msm_bam_hsic_host_pipe_empty()) { + log_event_err("%s(): HSIC HOST Pipe is not empty\n", + __func__); + return false; + } + + /* HSIC host will go now to lpm */ + log_event_dbg("%s: vote for suspend hsic %pK\n", + __func__, host_info[bam_type].dev); + + for (i = 0; i < ctx->max_connections; i++) { + pipe_iter = &ctx->usb_bam_connections[i]; + if (pipe_iter->bam_type == bam_type && + pipe_iter->enabled && !pipe_iter->suspended) + pipe_iter->suspended = true; + } + + host_info[bam_type].in_lpm = true; + + return true; +} + +bool msm_bam_hsic_lpm_ok(void) +{ + log_event_dbg("%s: enter\n", __func__); + + return msm_bam_host_lpm_ok(HSIC_CTRL); + +} +EXPORT_SYMBOL(msm_bam_hsic_lpm_ok); + +void msm_bam_hsic_host_notify_on_resume(void) +{ + _msm_bam_host_notify_on_resume(HSIC_CTRL); +} + +static inline enum usb_ctrl get_bam_type_from_core_name(const char *name) +{ + if (strnstr(name, bam_enable_strings[DWC3_CTRL], + USB_BAM_MAX_STR_LEN) || + strnstr(name, "dwc3", USB_BAM_MAX_STR_LEN)) + return DWC3_CTRL; + else if (strnstr(name, bam_enable_strings[HSIC_CTRL], + USB_BAM_MAX_STR_LEN) || + strnstr(name, "ci13xxx_msm_hsic", USB_BAM_MAX_STR_LEN)) + return HSIC_CTRL; + else if (strnstr(name, bam_enable_strings[CI_CTRL], + USB_BAM_MAX_STR_LEN) || + strnstr(name, "ci", USB_BAM_MAX_STR_LEN)) + return CI_CTRL; + + log_event_err("%s: invalid BAM name(%s)\n", __func__, name); + return -EINVAL; +} + static int usb_bam_alloc_buffer(struct usb_bam_pipe_connect *pipe_connect) { int ret = 0; @@ -389,7 +556,7 @@ static int usb_bam_alloc_buffer(struct usb_bam_pipe_connect *pipe_connect) struct sg_table data_sgt, desc_sgt; dma_addr_t data_iova, desc_iova; - pr_debug("%s: data_fifo size:%x desc_fifo_size:%x\n", + log_event_dbg("%s: data_fifo size:%x desc_fifo_size:%x\n", __func__, pipe_connect->data_fifo_size, pipe_connect->desc_fifo_size); @@ -563,7 +730,7 @@ int usb_bam_free_fifos(enum usb_ctrl cur_bam, u8 idx) struct device *dev = &ctx->usb_bam_pdev->dev; u32 data_fifo_size; - pr_debug("%s(): data size:%x desc size:%x\n", + log_event_dbg("%s(): data size:%x desc size:%x\n", __func__, sps_connection->data.size, sps_connection->desc.size); @@ -1053,7 +1220,6 @@ static int usb_bam_disconnect_ipa_cons( if (!pipe_empty) { if (inject_zlt) { - pr_debug("%s: Inject ZLT\n", __func__); log_event_dbg("%s: Inject ZLT\n", __func__); inject_zlt = false; sps_pipe_inject_zlt(sps_connection->destination, @@ -2045,12 +2211,12 @@ static int usb_bam_set_ipa_perf(enum usb_ctrl cur_bam, ctx->usb_bam_data->max_mbps_highspeed; /* - * Having a max mbps property in dtsi file is a must - * for target with IPA capability. - */ + * Having a max mbps property in dtsi file is a must + * for target with IPA capability. + */ if (!ipa_rm_perf_prof.max_supported_bandwidth_mbps) { log_event_err("%s: Max mbps is required for speed %d\n", - __func__, usb_connection_speed); + __func__, usb_connection_speed); return -EINVAL; } @@ -2059,18 +2225,69 @@ static int usb_bam_set_ipa_perf(enum usb_ctrl cur_bam, __func__, ipa_rm_resource_prod[cur_bam], ipa_rm_perf_prof.max_supported_bandwidth_mbps); ret = ipa_rm_set_perf_profile(ipa_rm_resource_prod[cur_bam], - &ipa_rm_perf_prof); + &ipa_rm_perf_prof); } else { log_event_dbg("%s: vote ipa_perf resource=%d perf=%d mbps\n", __func__, ipa_rm_resource_cons[cur_bam], ipa_rm_perf_prof.max_supported_bandwidth_mbps); ret = ipa_rm_set_perf_profile(ipa_rm_resource_cons[cur_bam], - &ipa_rm_perf_prof); + &ipa_rm_perf_prof); } return ret; } +static bool _hsic_host_bam_resume_core(void) +{ + log_event_dbg("%s: enter\n", __func__); + + /* Exit from "full suspend" in case of hsic host */ + if (host_info[HSIC_CTRL].dev) { + log_event_dbg("%s: Getting hsic device %pK\n", __func__, + host_info[HSIC_CTRL].dev); + pm_runtime_get(host_info[HSIC_CTRL].dev); + return true; + } + return false; +} + +static bool usb_bam_resume_core(enum usb_ctrl bam_type, + enum usb_bam_mode bam_mode) +{ + log_event_dbg("%s: enter bam=%s\n", __func__, + bam_enable_strings[bam_type]); + + if ((bam_mode == USB_BAM_DEVICE) || (bam_type != HSIC_CTRL)) { + log_event_err("%s: Invalid BAM type %d\n", __func__, bam_type); + return false; + } + + return _hsic_host_bam_resume_core(); +} + +static void _msm_bam_wait_for_host_prod_granted(enum usb_ctrl bam_type) +{ + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam_type]; + + spin_lock(&ctx->usb_bam_lock); + + log_event_dbg("%s: enter bam=%s\n", __func__, + bam_enable_strings[bam_type]); + ctx->is_bam_inactivity = false; + + /* Get back to resume state including wakeup ipa */ + usb_bam_resume_core(bam_type, USB_BAM_HOST); + + spin_unlock(&ctx->usb_bam_lock); + +} + +void msm_bam_wait_for_hsic_host_prod_granted(void) +{ + log_event_dbg("%s: start\n", __func__); + _msm_bam_wait_for_host_prod_granted(HSIC_CTRL); +} + int usb_bam_connect_ipa(enum usb_ctrl cur_bam, struct usb_bam_connect_ipa_params *ipa_params) { @@ -2170,7 +2387,7 @@ int usb_bam_connect_ipa(enum usb_ctrl cur_bam, } else { spin_unlock(&ctx->usb_bam_lock); if (!ctx->pipes_enabled_per_bam) - pr_debug("No BAM reset on connect, just pipe reset\n"); + log_event_dbg("No BAM reset on connect, just pipe reset\n"); } if (ipa_params->dir == USB_TO_PEER_PERIPHERAL) { @@ -2669,6 +2886,17 @@ static struct msm_usb_bam_data *usb_bam_dt_to_data( if (!usb_bam_data) return NULL; + rc = of_property_read_u32(node, "qcom,bam-type", &bam); + if (rc) { + log_event_err("%s: bam type is missing in device tree\n", + __func__); + return NULL; + } + if (bam >= MAX_BAMS) { + log_event_err("%s: Invalid bam type %d in device tree\n", + __func__, bam); + return NULL; + } usb_bam_data->bam_type = bam; usb_bam_data->reset_on_connect = of_property_read_bool(node, @@ -2696,7 +2924,8 @@ static struct msm_usb_bam_data *usb_bam_dt_to_data( rc = of_property_read_u32(node, "qcom,usb-bam-fifo-baseaddr", &addr); if (rc) - pr_debug("%s: Invalid usb base address property\n", __func__); + log_event_dbg("%s: Invalid usb base address property\n", + __func__); else usb_bam_data->usb_bam_fifo_baseaddr = addr; @@ -2786,7 +3015,7 @@ static struct msm_usb_bam_data *usb_bam_dt_to_data( rc = of_property_read_u32(node, "qcom,pipe-connection-type", &usb_bam_connections[i].pipe_type); if (rc) - pr_debug("%s: pipe type is defaulting to bam2bam\n", + log_event_dbg("%s: pipe type is defaulting to bam2bam\n", __func__); of_property_read_u32(node, "qcom,peer-bam-physical-address", @@ -2832,6 +3061,54 @@ static struct msm_usb_bam_data *usb_bam_dt_to_data( return NULL; } +static void msm_usb_bam_update_props(struct sps_bam_props *props, + struct platform_device *pdev) +{ + struct usb_bam_ctx_type *ctx = dev_get_drvdata(&pdev->dev); + enum usb_ctrl bam_type = ctx->usb_bam_data->bam_type; + struct device *dev; + + props->phys_addr = ctx->io_res->start; + props->virt_addr = NULL; + props->virt_size = resource_size(ctx->io_res); + props->irq = ctx->irq; + props->summing_threshold = ctx->usb_bam_data->override_threshold; + props->event_threshold = ctx->usb_bam_data->override_threshold; + props->num_pipes = ctx->usb_bam_data->usb_bam_num_pipes; + props->callback = usb_bam_sps_events; + props->user = bam_enable_strings[bam_type]; + + /* + * HSUSB and HSIC Cores don't support RESET ACK signal to BAMs + * Hence, let BAM to ignore acknowledge from USB while resetting PIPE + */ + if (ctx->usb_bam_data->ignore_core_reset_ack && bam_type != DWC3_CTRL) + props->options = SPS_BAM_NO_EXT_P_RST; + + if (ctx->usb_bam_data->disable_clk_gating) + props->options |= SPS_BAM_NO_LOCAL_CLK_GATING; + + /* + * HSUSB BAM is not NDP BAM and it must be enabled before + * starting peripheral controller to avoid switching USB core mode + * from legacy to BAM with ongoing data transfers. + */ + if (bam_type == CI_CTRL) { + log_event_dbg("Register and enable HSUSB BAM\n"); + props->options |= SPS_BAM_OPT_ENABLE_AT_BOOT; + props->options |= SPS_BAM_FORCE_RESET; + } + + dev = &ctx->usb_bam_pdev->dev; + if (dev && dev->parent && device_property_present(dev->parent, "iommus") + && !device_property_present(dev->parent, + "qcom,smmu-s1-bypass")) { + pr_info("%s: setting SPS_BAM_SMMU_EN flag with (%s)\n", + __func__, dev_name(dev)); + props->options |= SPS_BAM_SMMU_EN; + } +} + static int usb_bam_init(struct platform_device *pdev) { int ret; @@ -2842,7 +3119,7 @@ static int usb_bam_init(struct platform_device *pdev) memset(&props, 0, sizeof(props)); - pr_debug("%s\n", __func__); + log_event_dbg("%s\n", __func__); props.phys_addr = ctx->io_res->start; props.virt_size = resource_size(ctx->io_res); @@ -3120,6 +3397,54 @@ enum usb_ctrl usb_bam_get_bam_type(const char *core_name) } EXPORT_SYMBOL(usb_bam_get_bam_type); +bool msm_usb_bam_enable(enum usb_ctrl bam, bool bam_enable) +{ + struct msm_usb_bam_data *usb_bam_data; + struct usb_bam_ctx_type *ctx = &msm_usb_bam[bam]; + static bool bam_enabled; + int ret; + + if (!ctx->usb_bam_pdev) + return false; + + usb_bam_data = ctx->usb_bam_data; + if (bam != CI_CTRL) + return false; + + if (bam_enabled == bam_enable) { + log_event_dbg("%s: USB BAM is already %s\n", __func__, + bam_enable ? "Registered" : "De-registered"); + return false; + } + + if (bam_enable) { + struct sps_bam_props props; + + memset(&props, 0, sizeof(props)); + msm_usb_bam_update_props(&props, ctx->usb_bam_pdev); + msm_hw_bam_disable(1); + ret = sps_register_bam_device(&props, &ctx->h_bam); + bam_enabled = true; + if (ret < 0) { + log_event_err("%s: register bam error %d\n", + __func__, ret); + return false; + } + log_event_dbg("%s: USB BAM Registered\n", __func__); + msm_hw_bam_disable(0); + } else { + msm_hw_soft_reset(); + msm_hw_bam_disable(1); + sps_device_reset(ctx->h_bam); + sps_deregister_bam_device(ctx->h_bam); + log_event_dbg("%s: USB BAM De-registered\n", __func__); + bam_enabled = false; + } + + return true; +} +EXPORT_SYMBOL(msm_usb_bam_enable); + static int usb_bam_remove(struct platform_device *pdev) { struct usb_bam_ctx_type *ctx = dev_get_drvdata(&pdev->dev); diff --git a/drivers/power/reset/msm-poweroff.c b/drivers/power/reset/msm-poweroff.c index e818e0e5c58f..a63ce0b4c826 100644 --- a/drivers/power/reset/msm-poweroff.c +++ b/drivers/power/reset/msm-poweroff.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2013-2019, The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #include @@ -39,7 +38,7 @@ #define SCM_DLOAD_FULLDUMP 0X10 #define SCM_EDLOAD_MODE 0X01 #define SCM_DLOAD_CMD 0x10 -#define SCM_DLOAD_MINIDUMP 0X40 +#define SCM_DLOAD_MINIDUMP 0X20 #define SCM_DLOAD_BOTHDUMPS (SCM_DLOAD_MINIDUMP | SCM_DLOAD_FULLDUMP) #define DL_MODE_PROP "qcom,msm-imem-download_mode" @@ -63,11 +62,11 @@ static void scm_disable_sdi(void); * There is no API from TZ to re-enable the registers. * So the SDI cannot be re-enabled when it already by-passed. */ -static int download_mode = 1; +static int download_mode = 0; static struct kobject dload_kobj; static int in_panic; -static int dload_type = SCM_DLOAD_BOTHDUMPS; +static int dload_type = SCM_DLOAD_FULLDUMP; static void *dload_mode_addr; static bool dload_mode_enabled; static void *emergency_dload_mode_addr; @@ -187,7 +186,6 @@ static bool get_dload_mode(void) return dload_mode_enabled; } -#if 0 static void enable_emergency_dload_mode(void) { int ret; @@ -214,7 +212,6 @@ static void enable_emergency_dload_mode(void) if (ret) pr_err("Failed to set secure EDLOAD mode: %d\n", ret); } -#endif static int dload_set(const char *val, const struct kernel_param *kp) { @@ -498,10 +495,7 @@ static void msm_restart_prepare(const char *cmd) else qpnp_pon_system_pwr_off(PON_POWER_OFF_HARD_RESET); - if (in_panic) { - qpnp_pon_set_restart_reason(PON_RESTART_REASON_PANIC); - qpnp_pon_system_pwr_off(PON_POWER_OFF_WARM_RESET); - } else if (cmd != NULL) { + if (cmd != NULL) { if (!strncmp(cmd, "bootloader", 10)) { qpnp_pon_set_restart_reason( PON_RESTART_REASON_BOOTLOADER); @@ -535,14 +529,10 @@ static void msm_restart_prepare(const char *cmd) __raw_writel(0x6f656d00 | (code & 0xff), restart_reason); } else if (!strncmp(cmd, "edl", 3)) { - ;//enable_emergency_dload_mode(); + enable_emergency_dload_mode(); } else { - qpnp_pon_set_restart_reason(PON_RESTART_REASON_NORMAL); __raw_writel(0x77665501, restart_reason); } - } else { - qpnp_pon_set_restart_reason(PON_RESTART_REASON_NORMAL); - __raw_writel(0x77665501, restart_reason); } flush_cache_all(); diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 5c1d98657339..633e006b3984 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -355,8 +355,7 @@ static int __power_supply_is_system_supplied(struct device *dev, void *data) unsigned int *count = data; (*count)++; - if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY && - psy->desc->type != POWER_SUPPLY_TYPE_BMS) + if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY) if (!psy->desc->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &ret)) return ret.intval; diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index 7ec124878b0b..a8ad7464e251 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -345,8 +345,6 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(precharge_current), POWER_SUPPLY_ATTR(charge_term_current), POWER_SUPPLY_ATTR(calibrate), - POWER_SUPPLY_ATTR(otg_online), - POWER_SUPPLY_ATTR(battery_id), /* Local extensions */ POWER_SUPPLY_ATTR(usb_hc), POWER_SUPPLY_ATTR(usb_otg), @@ -430,6 +428,7 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(pd_voltage_max), POWER_SUPPLY_ATTR(pd_voltage_min), POWER_SUPPLY_ATTR(sdp_current_max), + POWER_SUPPLY_ATTR(fg_reset_clock), POWER_SUPPLY_ATTR(connector_type), POWER_SUPPLY_ATTR(parallel_batfet_mode), POWER_SUPPLY_ATTR(parallel_fcc_max), diff --git a/drivers/power/supply/qcom/battery.c b/drivers/power/supply/qcom/battery.c index 41f771873e36..ec45b7d1c502 100644 --- a/drivers/power/supply/qcom/battery.c +++ b/drivers/power/supply/qcom/battery.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2017-2020 The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #define pr_fmt(fmt) "QCOM-BATT: %s: " fmt, __func__ @@ -118,7 +117,7 @@ enum { FORCE_INOV_DISABLE_BIT = BIT(1), }; -static int debug_mask = 0xff; +static int debug_mask; #define pl_dbg(chip, reason, fmt, ...) \ do { \ @@ -1244,7 +1243,6 @@ static int pl_fv_vote_callback(struct votable *votable, void *data, * check for termination at reduced float voltage and re-trigger * charging if new float voltage is above last FV. */ - /* if ((chip->float_voltage_uv < fv_uv) && is_batt_available(chip)) { rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_STATUS, &pval); @@ -1265,7 +1263,7 @@ static int pl_fv_vote_callback(struct votable *votable, void *data, } chip->float_voltage_uv = fv_uv; - */ + return 0; } diff --git a/drivers/power/supply/qcom/fg-core.h b/drivers/power/supply/qcom/fg-core.h index aa3c9b13c999..7e3de38af9b5 100644 --- a/drivers/power/supply/qcom/fg-core.h +++ b/drivers/power/supply/qcom/fg-core.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ #ifndef __FG_CORE_H__ @@ -607,4 +607,5 @@ extern int fg_lerp(const struct fg_pt *pts, size_t tablesize, s32 input, s32 *output); void fg_stay_awake(struct fg_dev *fg, int awake_reason); void fg_relax(struct fg_dev *fg, int awake_reason); +extern int fg_dma_mem_req(struct fg_dev *fg, bool request); #endif diff --git a/drivers/power/supply/qcom/fg-memif.c b/drivers/power/supply/qcom/fg-memif.c index 89291897f9c5..32ece3c905c3 100644 --- a/drivers/power/supply/qcom/fg-memif.c +++ b/drivers/power/supply/qcom/fg-memif.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2018 The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2018, 2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "FG: %s: " fmt, __func__ @@ -1078,6 +1078,64 @@ static int __fg_direct_mem_rw(struct fg_dev *fg, u16 sram_addr, u8 offset, return rc; } +int fg_dma_mem_req(struct fg_dev *chip, bool request) +{ + int ret, rc = 0, retry_count = RETRY_COUNT; + u8 val; + + if (request) { + /* configure for DMA access */ + rc = fg_masked_write(chip, MEM_IF_MEM_INTF_CFG(chip), + MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT, + MEM_ACCESS_REQ_BIT); + if (rc < 0) { + pr_err("failed to set mem_access bit rc=%d\n", rc); + return rc; + } + + rc = fg_masked_write(chip, MEM_IF_MEM_ARB_CFG(chip), + MEM_IF_ARB_REQ_BIT, MEM_IF_ARB_REQ_BIT); + if (rc < 0) { + pr_err("failed to set mem_arb bit rc=%d\n", rc); + goto release_mem; + } + + while (retry_count--) { + rc = fg_read(chip, MEM_IF_INT_RT_STS(chip), &val, 1); + if (rc < 0) { + pr_err("failed to set ima_rt_sts rc=%d\n", rc); + goto release_mem; + } + if (val & MEM_GNT_BIT) + break; + msleep(20); + } + if ((retry_count < 0) && !(val & MEM_GNT_BIT)) { + pr_err("failed to get memory access\n"); + rc = -ETIMEDOUT; + goto release_mem; + } + + return 0; + } + +release_mem: + /* Release access */ + rc = fg_masked_write(chip, MEM_IF_MEM_INTF_CFG(chip), + MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT, 0); + if (rc < 0) + pr_err("failed to reset mem_access bit rc = %d\n", rc); + + ret = fg_masked_write(chip, MEM_IF_MEM_ARB_CFG(chip), + MEM_IF_ARB_REQ_BIT, 0); + if (ret < 0) { + pr_err("failed to release mem_arb bit rc=%d\n", ret); + return ret; + } + + return rc; +} + int fg_direct_mem_read(struct fg_dev *fg, u16 sram_addr, u8 offset, u8 *val, int len) { diff --git a/drivers/power/supply/qcom/fg-reg.h b/drivers/power/supply/qcom/fg-reg.h index 4db8cee57e98..eb5cf1b14ee0 100644 --- a/drivers/power/supply/qcom/fg-reg.h +++ b/drivers/power/supply/qcom/fg-reg.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2016-2018, 2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2018, 2020-2021, The Linux Foundation. All rights reserved. */ #ifndef __FG_REG_H__ @@ -58,6 +58,9 @@ #define BATT_SOC_RESTART(chip) (chip->batt_soc_base + 0x48) #define RESTART_GO_BIT BIT(0) +/* BCL_RESET */ +#define BCL_RESET_BIT BIT(2) + #define BATT_SOC_STS_CLR(chip) (chip->batt_soc_base + 0x4A) #define BATT_SOC_LOW_PWR_CFG(chip) (chip->batt_soc_base + 0x52) #define BATT_SOC_LOW_PWR_STS(chip) (chip->batt_soc_base + 0x56) @@ -68,6 +71,8 @@ /* FG_BATT_INFO register definitions */ #define BATT_INFO_BATT_TEMP_STS(chip) (chip->batt_info_base + 0x06) +#define BATT_INFO_PEEK_MUX1(chip) (chip->batt_info_base + 0xEB) +#define BATT_INFO_RDBACK(chip) (chip->batt_info_base + 0xEF) #define JEITA_TOO_HOT_STS_BIT BIT(7) #define JEITA_HOT_STS_BIT BIT(6) #define JEITA_COLD_STS_BIT BIT(5) @@ -285,6 +290,12 @@ #define ESR_REQ_CTL_BIT BIT(1) #define ESR_REQ_CTL_EN_BIT BIT(0) +/* BATT_INFO_PEEK_MUX1 */ +#define PEEK_MUX1_BIT BIT(0) + +#define MEM_IF_MEM_ARB_CFG(chip) ((chip->mem_if_base) + 0x40) +#define MEM_GNT_BIT BIT(2) + #define BATT_INFO_PEEK_MUX4(chip) (chip->batt_info_base + 0xEE) #define ALG_ACTIVE_PEEK_CFG 0xAC @@ -357,6 +368,9 @@ #define ADDR_KIND_BIT BIT(1) #define DMA_CLEAR_LOG_BIT BIT(0) +/* MEM_IF_REQ */ +#define MEM_IF_ARB_REQ_BIT BIT(0) + /* FG_DMAx */ #define FG_DMA0_BASE 0x4800 #define FG_DMA1_BASE 0x4900 diff --git a/drivers/power/supply/qcom/pmic-voter.c b/drivers/power/supply/qcom/pmic-voter.c index 2f8501abc4db..838e3f7ecb62 100644 --- a/drivers/power/supply/qcom/pmic-voter.c +++ b/drivers/power/supply/qcom/pmic-voter.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2015-2017, 2019 The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #include @@ -325,6 +324,7 @@ int get_effective_result_locked(struct votable *votable) if (votable->override_result != -EINVAL) return votable->override_result; + return votable->effective_result; } @@ -438,7 +438,7 @@ int vote(struct votable *votable, const char *client_str, bool enabled, int val) if ((votable->votes[client_id].enabled == enabled) && (votable->votes[client_id].value == val)) { - pr_err("%s: %s,%d same vote %s of val=%d\n", + pr_debug("%s: %s,%d same vote %s of val=%d\n", votable->name, client_str, client_id, enabled ? "on" : "off", @@ -450,13 +450,13 @@ int vote(struct votable *votable, const char *client_str, bool enabled, int val) votable->votes[client_id].value = val; if (similar_vote && votable->voted_on) { - pr_err("%s: %s,%d Ignoring similar vote %s of val=%d\n", + pr_debug("%s: %s,%d Ignoring similar vote %s of val=%d\n", votable->name, client_str, client_id, enabled ? "on" : "off", val); goto out; } - pr_err("%s: %s,%d voting %s of val=%d\n", + pr_debug("%s: %s,%d voting %s of val=%d\n", votable->name, client_str, client_id, enabled ? "on" : "off", val); switch (votable->type) { @@ -482,7 +482,7 @@ int vote(struct votable *votable, const char *client_str, bool enabled, int val) || (effective_result != votable->effective_result)) { votable->effective_client_id = effective_id; votable->effective_result = effective_result; - pr_err("%s: effective vote is now %d voted by %s,%d\n", + pr_debug("%s: effective vote is now %d voted by %s,%d\n", votable->name, effective_result, get_client_str(votable, effective_id), effective_id); diff --git a/drivers/power/supply/qcom/qg-core.h b/drivers/power/supply/qcom/qg-core.h index b33844b5024a..fb28230db608 100644 --- a/drivers/power/supply/qcom/qg-core.h +++ b/drivers/power/supply/qcom/qg-core.h @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #ifndef __QG_CORE_H__ @@ -195,7 +194,6 @@ struct qpnp_qg { int last_adj_ssoc; int recharge_soc; int batt_age_level; - int batt_id; struct alarm alarm_timer; u32 sdam_data[SDAM_MAX]; diff --git a/drivers/power/supply/qcom/qg-soc.c b/drivers/power/supply/qcom/qg-soc.c index 94361d872224..5b6a70bce855 100644 --- a/drivers/power/supply/qcom/qg-soc.c +++ b/drivers/power/supply/qcom/qg-soc.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #define pr_fmt(fmt) "QG-K: %s: " fmt, __func__ @@ -29,7 +28,7 @@ enum soc_scaling_feature { #define DEFAULT_UPDATE_TIME_MS 64000 #define SOC_SCALE_HYST_MS 2000 -#define VBAT_LOW_HYST_UV 25000 +#define VBAT_LOW_HYST_UV 50000 #define FULL_SOC 100 static int qg_ss_feature; diff --git a/drivers/power/supply/qcom/qg-util.c b/drivers/power/supply/qcom/qg-util.c index 36b5fefa42aa..cee6285fce4a 100644 --- a/drivers/power/supply/qcom/qg-util.c +++ b/drivers/power/supply/qcom/qg-util.c @@ -374,7 +374,8 @@ int qg_get_battery_temp(struct qpnp_qg *chip, int *temp) pr_err("Failed reading BAT_TEMP over ADC rc=%d\n", rc); return rc; } - pr_debug("batt_temp = %d\n", *temp); + //pr_err("batt_temp = %d,by eric.wang\n", *temp);//by eric.wang + //*temp = 250; return 0; } diff --git a/drivers/power/supply/qcom/qpnp-fg-gen3.c b/drivers/power/supply/qcom/qpnp-fg-gen3.c index 0e589c9d0764..f23f36d1b749 100644 --- a/drivers/power/supply/qcom/qpnp-fg-gen3.c +++ b/drivers/power/supply/qcom/qpnp-fg-gen3.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "FG: %s: " fmt, __func__ @@ -3766,6 +3766,9 @@ static int fg_psy_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CC_STEP_SEL: pval->intval = chip->ttf.cc_step.sel; break; + case POWER_SUPPLY_PROP_FG_RESET_CLOCK: + pval->intval = 0; + break; default: pr_err("unsupported property %d\n", psp); rc = -EINVAL; @@ -3778,6 +3781,100 @@ static int fg_psy_get_property(struct power_supply *psy, return 0; } +#define BCL_RESET_RETRY_COUNT 4 +static int fg_bcl_reset(struct fg_dev *chip) +{ + int i, ret, rc = 0; + u8 val, peek_mux; + bool success = false; + + /* Read initial value of peek mux1 */ + rc = fg_read(chip, BATT_INFO_PEEK_MUX1(chip), &peek_mux, 1); + if (rc < 0) { + pr_err("Error in writing peek mux1, rc=%d\n", rc); + return rc; + } + + val = 0x83; + rc = fg_write(chip, BATT_INFO_PEEK_MUX1(chip), &val, 1); + if (rc < 0) { + pr_err("Error in writing peek mux1, rc=%d\n", rc); + return rc; + } + + mutex_lock(&chip->sram_rw_lock); + for (i = 0; i < BCL_RESET_RETRY_COUNT; i++) { + rc = fg_dma_mem_req(chip, true); + if (rc < 0) { + pr_err("Error in locking memory, rc=%d\n", rc); + goto unlock; + } + + rc = fg_read(chip, BATT_INFO_RDBACK(chip), &val, 1); + if (rc < 0) { + pr_err("Error in reading rdback, rc=%d\n", rc); + goto release_mem; + } + + if (val & PEEK_MUX1_BIT) { + rc = fg_masked_write(chip, BATT_SOC_RST_CTRL0(chip), + BCL_RESET_BIT, BCL_RESET_BIT); + if (rc < 0) { + pr_err("Error in writing RST_CTRL0, rc=%d\n", + rc); + goto release_mem; + } + + rc = fg_dma_mem_req(chip, false); + if (rc < 0) + pr_err("Error in unlocking memory, rc=%d\n", + rc); + + /* Delay of 2ms */ + usleep_range(2000, 3000); + ret = fg_masked_write(chip, BATT_SOC_RST_CTRL0(chip), + BCL_RESET_BIT, 0); + if (ret < 0) + pr_err("Error in writing RST_CTRL0, rc=%d\n", + rc); + if (!rc && !ret) + success = true; + + goto unlock; + } else { + rc = fg_dma_mem_req(chip, false); + if (rc < 0) { + pr_err("Error in unlocking memory, rc=%d\n", + rc); + goto unlock; + } + success = false; + pr_err_ratelimited("PEEK_MUX1 not set retrying...\n"); + msleep(1000); + } + } + +release_mem: + rc = fg_dma_mem_req(chip, false); + if (rc < 0) + pr_err("Error in unlocking memory, rc=%d\n", rc); + +unlock: + ret = fg_write(chip, BATT_INFO_PEEK_MUX1(chip), &peek_mux, 1); + if (ret < 0) { + pr_err("Error in writing peek mux1, rc=%d\n", rc); + mutex_unlock(&chip->sram_rw_lock); + return ret; + } + + mutex_unlock(&chip->sram_rw_lock); + + if (!success) + return -EAGAIN; + else + return rc; +} + static int fg_psy_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *pval) @@ -3816,6 +3913,7 @@ static int fg_psy_set_property(struct power_supply *psy, return -EINVAL; } break; + case POWER_SUPPLY_PROP_CHARGE_FULL: if (chip->cl.active) { pr_warn("Capacity learning active!\n"); @@ -3858,6 +3956,14 @@ static int fg_psy_set_property(struct power_supply *psy, return rc; } break; + + case POWER_SUPPLY_PROP_FG_RESET_CLOCK: + rc = fg_bcl_reset(fg); + if (rc < 0) { + pr_err("Error in resetting BCL clock, rc=%d\n", rc); + return rc; + } + break; default: break; } @@ -3972,6 +4078,7 @@ static enum power_supply_property fg_psy_props[] = { POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, POWER_SUPPLY_PROP_CC_STEP, POWER_SUPPLY_PROP_CC_STEP_SEL, + POWER_SUPPLY_PROP_FG_RESET_CLOCK, }; static const struct power_supply_desc fg_psy_desc = { diff --git a/drivers/power/supply/qcom/qpnp-qg.c b/drivers/power/supply/qcom/qpnp-qg.c index a5204f907762..49d9caefa1ab 100644 --- a/drivers/power/supply/qcom/qpnp-qg.c +++ b/drivers/power/supply/qcom/qpnp-qg.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #define pr_fmt(fmt) "QG-K: %s: " fmt, __func__ @@ -29,7 +28,6 @@ #include #include #include -#include #include "fg-alg.h" #include "qg-sdam.h" #include "qg-core.h" @@ -39,7 +37,7 @@ #include "qg-battery-profile.h" #include "qg-defs.h" -static int qg_debug_mask = 0xff; +static int qg_debug_mask; static int qg_esr_mod_count = 30; static ssize_t esr_mod_count_show(struct device *dev, struct device_attribute @@ -2156,9 +2154,6 @@ static int qg_psy_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CAPACITY: rc = qg_get_battery_capacity(chip, &pval->intval); break; - case POWER_SUPPLY_PROP_BATTERY_ID: - pval->intval = chip->batt_id; - break; case POWER_SUPPLY_PROP_CAPACITY_RAW: pval->intval = chip->sys_soc; break; @@ -2227,7 +2222,9 @@ static int qg_psy_get_property(struct power_supply *psy, pval->intval = (int)temp; break; case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - pval->intval = 6000000; + rc = qg_get_nominal_capacity((int *)&temp, 250, true); + if (!rc) + pval->intval = (int)temp; break; case POWER_SUPPLY_PROP_CYCLE_COUNTS: rc = get_cycle_counts(chip->counter, &pval->strval); @@ -2308,7 +2305,6 @@ static int qg_property_is_writeable(struct power_supply *psy, static enum power_supply_property qg_psy_props[] = { POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_BATTERY_ID, POWER_SUPPLY_PROP_CAPACITY_RAW, POWER_SUPPLY_PROP_REAL_CAPACITY, POWER_SUPPLY_PROP_TEMP, @@ -2401,7 +2397,7 @@ static int qg_charge_full_update(struct qpnp_qg *chip) chip->msoc, health, chip->charge_full, chip->charge_done); if (chip->charge_done && !chip->charge_full) { - if (chip->msoc >= 99 && (health == POWER_SUPPLY_HEALTH_GOOD || health == POWER_SUPPLY_HEALTH_COOL)) { + if (chip->msoc >= 99 && health == POWER_SUPPLY_HEALTH_GOOD) { chip->charge_full = true; qg_dbg(chip, QG_DEBUG_STATUS, "Setting charge_full (0->1) @ msoc=%d\n", chip->msoc); @@ -2602,7 +2598,6 @@ static int qg_battery_status_update(struct qpnp_qg *chip) if (rc < 0) pr_err("Failed in battery-insertion rc=%d\n", rc); } else if (!chip->battery_missing && !prop.intval) { - kernel_power_off(); pr_warn("Battery removed!\n"); rc = qg_handle_battery_removal(chip); if (rc < 0) @@ -3145,13 +3140,7 @@ static int qg_load_battery_profile(struct qpnp_qg *chip) static int qg_setup_battery(struct qpnp_qg *chip) { int rc; - get_batt_id_ohm(chip, &chip->batt_id_ohm); - if (chip->batt_id_ohm >= 313000 && chip->batt_id_ohm <= 350000) - chip->batt_id = 1; - else if (chip->batt_id_ohm >= 65000 && chip->batt_id_ohm <= 73000) - chip->batt_id = 2; - else - chip->batt_id = 0 ; + if (!is_battery_present(chip)) { qg_dbg(chip, QG_DEBUG_PROFILE, "Battery Missing!\n"); chip->battery_missing = true; @@ -4739,7 +4728,7 @@ static int qpnp_qg_probe(struct platform_device *pdev) chip->batt_age_level = -EINVAL; chip->qg_charge_counter = -EINVAL; - chip->qg_version = (u8)(uintptr_t)of_device_get_match_data(&pdev->dev); + chip->qg_version = (u8)of_device_get_match_data(&pdev->dev); switch (chip->qg_version) { case QG_LITE: diff --git a/drivers/power/supply/qcom/qpnp-smb2.c b/drivers/power/supply/qcom/qpnp-smb2.c index 14ad3444f4a8..24e424b5d6b8 100644 --- a/drivers/power/supply/qcom/qpnp-smb2.c +++ b/drivers/power/supply/qcom/qpnp-smb2.c @@ -413,6 +413,7 @@ static enum power_supply_property smb2_usb_props[] = { POWER_SUPPLY_PROP_TYPEC_MODE, POWER_SUPPLY_PROP_TYPEC_POWER_ROLE, POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION, + POWER_SUPPLY_PROP_TYPEC_SRC_RP, POWER_SUPPLY_PROP_PD_ALLOWED, POWER_SUPPLY_PROP_PD_ACTIVE, POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, @@ -503,6 +504,9 @@ static int smb2_usb_get_prop(struct power_supply *psy, else rc = smblib_get_prop_typec_cc_orientation(chg, val); break; + case POWER_SUPPLY_PROP_TYPEC_SRC_RP: + rc = smblib_get_prop_typec_select_rp(chg, val); + break; case POWER_SUPPLY_PROP_PD_ALLOWED: rc = smblib_get_prop_pd_allowed(chg, val); break; @@ -595,6 +599,9 @@ static int smb2_usb_set_prop(struct power_supply *psy, case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE: rc = smblib_set_prop_typec_power_role(chg, val); break; + case POWER_SUPPLY_PROP_TYPEC_SRC_RP: + rc = smblib_set_prop_typec_select_rp(chg, val); + break; case POWER_SUPPLY_PROP_PD_ACTIVE: rc = smblib_set_prop_pd_active(chg, val); break; diff --git a/drivers/power/supply/qcom/qpnp-smb5.c b/drivers/power/supply/qcom/qpnp-smb5.c index aa273b149314..3bd5f9eab737 100644 --- a/drivers/power/supply/qcom/qpnp-smb5.c +++ b/drivers/power/supply/qcom/qpnp-smb5.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. */ #include @@ -228,7 +227,7 @@ struct smb5 { struct smb_dt_props dt; }; -static int __debug_mask = 0xff; +static int __debug_mask; static ssize_t pd_disabled_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -447,9 +446,6 @@ static int smb5_parse_dt_misc(struct smb5 *chip, struct device_node *node) of_property_read_u32(node, "qcom,sec-charger-config", &chip->dt.sec_charger_config); - - printk("sec-charger-config=%d\n", chip->dt.sec_charger_config); - chg->sec_cp_present = chip->dt.sec_charger_config == POWER_SUPPLY_CHARGER_SEC_CP || chip->dt.sec_charger_config == POWER_SUPPLY_CHARGER_SEC_CP_PL; @@ -467,6 +463,9 @@ static int smb5_parse_dt_misc(struct smb5 *chip, struct device_node *node) chg->sw_jeita_enabled = of_property_read_bool(node, "qcom,sw-jeita-enable"); + chg->jeita_arb_enable = of_property_read_bool(node, + "qcom,jeita-arb-enable"); + chg->pd_not_supported = chg->pd_not_supported || of_property_read_bool(node, "qcom,usb-pd-disable"); @@ -886,7 +885,6 @@ static enum power_supply_property smb5_usb_props[] = { POWER_SUPPLY_PROP_APSD_TIMEOUT, POWER_SUPPLY_PROP_CHARGER_STATUS, POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED, - POWER_SUPPLY_PROP_OTG_ONLINE, }; static int smb5_usb_get_prop(struct power_supply *psy, @@ -1054,8 +1052,6 @@ static int smb5_usb_get_prop(struct power_supply *psy, if (!rc) val->intval = (buff[1] << 8 | buff[0]) * 1038; } - case POWER_SUPPLY_PROP_OTG_ONLINE: - val->intval = chg->otg_present; break; default: pr_err("get prop %d is not supported in usb\n", psp); @@ -1151,9 +1147,6 @@ static int smb5_usb_set_prop(struct power_supply *psy, chg->apsd_ext_timeout = false; smblib_rerun_apsd(chg); break; - case POWER_SUPPLY_PROP_OTG_ONLINE: - rc = chg->otg_present; - break; default: pr_err("set prop %d is not supported\n", psp); rc = -EINVAL; @@ -1829,7 +1822,7 @@ static int smb5_batt_get_prop(struct power_supply *psy, POWER_SUPPLY_PROP_TEMP, val); break; case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO; + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; break; case POWER_SUPPLY_PROP_CHARGE_DONE: rc = smblib_get_prop_batt_charge_done(chg, val); @@ -2019,7 +2012,6 @@ static int smb5_batt_prop_is_writeable(struct power_supply *psy, case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED: case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED: case POWER_SUPPLY_PROP_DIE_HEALTH: - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: return 1; default: break; @@ -2279,11 +2271,6 @@ static int smb5_configure_typec(struct smb_charger *chg) dev_err(chg->dev, "Couldn't configure CC threshold voltage rc=%d\n", rc); - rc = smblib_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG, 0); - if (rc < 0) { - dev_err(chg->dev, "Couldn't configure Type-C interrupts rc=%d\n", rc); - return rc; - } return rc; } @@ -2469,8 +2456,9 @@ static int smb5_configure_mitigation(struct smb_charger *chg) } rc = smblib_masked_write(chg, MISC_THERMREG_SRC_CFG_REG, - THERMREG_DIE_ADC_SRC_EN_BIT - | THERMREG_DIE_CMP_SRC_EN_BIT, src_cfg); + THERMREG_SW_ICL_ADJUST_BIT | THERMREG_DIE_ADC_SRC_EN_BIT | + THERMREG_DIE_CMP_SRC_EN_BIT | THERMREG_SKIN_ADC_SRC_EN_BIT | + SKIN_ADC_CFG_BIT | THERMREG_CONNECTOR_ADC_SRC_EN_BIT, src_cfg); if (rc < 0) { dev_err(chg->dev, "Couldn't configure THERM_SRC reg rc=%d\n", rc); @@ -2970,16 +2958,6 @@ static int smb5_init_hw(struct smb5 *chip) } } - rc = smblib_write(chg, 0x1670, 0x5); - if (rc < 0) { - dev_err(chg->dev, "Couldn't set 0x1670 rc=%d\n", rc); - } - - rc = smblib_write(chg, 0x1380, 0xc4); - if (rc < 0) { - dev_err(chg->dev, "Couldn't set 0x1380 rc=%d\n", rc); - } - return rc; } diff --git a/drivers/power/supply/qcom/smb-lib.c b/drivers/power/supply/qcom/smb-lib.c index 00e05b7ee38f..101f21fcbeb3 100644 --- a/drivers/power/supply/qcom/smb-lib.c +++ b/drivers/power/supply/qcom/smb-lib.c @@ -1187,6 +1187,44 @@ static int __smblib_set_prop_typec_power_role(struct smb_charger *chg, return rc; } +static inline bool typec_in_src_mode(struct smb_charger *chg) +{ + return (chg->typec_mode > POWER_SUPPLY_TYPEC_NONE && + chg->typec_mode < POWER_SUPPLY_TYPEC_SOURCE_DEFAULT); +} + +int smblib_get_prop_typec_select_rp(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc, rp; + u8 stat; + + if (!typec_in_src_mode(chg)) + return -ENODATA; + + rc = smblib_read(chg, TYPE_C_CFG_2_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_CURRSRC_CFG_REG rc=%d\n", + rc); + return rc; + } + + switch (stat & EN_80UA_180UA_CUR_SOURCE_BIT) { + case TYPEC_SRC_RP_STD: + rp = POWER_SUPPLY_TYPEC_SRC_RP_STD; + break; + case TYPEC_SRC_RP_1P5A: + rp = POWER_SUPPLY_TYPEC_SRC_RP_1P5A; + break; + default: + return -EINVAL; + } + + val->intval = rp; + + return 0; +} + /********************* * VOTABLE CALLBACKS * *********************/ @@ -2881,6 +2919,42 @@ int smblib_set_prop_typec_power_role(struct smb_charger *chg, return 0; } +int smblib_set_prop_typec_select_rp(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc = 0; + + if (!typec_in_src_mode(chg)) { + smblib_err(chg, "Couldn't set curr src: not in SRC mode\n"); + return -EINVAL; + } + + if (val->intval < 0 || val->intval >= TYPEC_SRC_RP_MAX_ELEMENTS) + return -EINVAL; + + switch (val->intval) { + case TYPEC_SRC_RP_STD: + rc = smblib_masked_write(chg, TYPE_C_CFG_2_REG, + EN_80UA_180UA_CUR_SOURCE_BIT, + TYPEC_SRC_RP_STD); + break; + case TYPEC_SRC_RP_1P5A: + case TYPEC_SRC_RP_3A: + case TYPEC_SRC_RP_3A_DUPLICATE: + rc = smblib_masked_write(chg, TYPE_C_CFG_2_REG, + EN_80UA_180UA_CUR_SOURCE_BIT, + TYPEC_SRC_RP_1P5A); + break; + default: + return -EINVAL; + } + + if (rc < 0) + smblib_err(chg, "Couldn't write to TYPE_C_CURRSRC_CFG rc=%d\n", + rc); + return rc; +} + int smblib_set_prop_pd_voltage_min(struct smb_charger *chg, const union power_supply_propval *val) { diff --git a/drivers/power/supply/qcom/smb-lib.h b/drivers/power/supply/qcom/smb-lib.h index cdacd92f2ae3..046e6218d4ea 100644 --- a/drivers/power/supply/qcom/smb-lib.h +++ b/drivers/power/supply/qcom/smb-lib.h @@ -483,6 +483,8 @@ int smblib_get_prop_usb_current_now(struct smb_charger *chg, union power_supply_propval *val); int smblib_get_prop_typec_cc_orientation(struct smb_charger *chg, union power_supply_propval *val); +int smblib_get_prop_typec_select_rp(struct smb_charger *chg, + union power_supply_propval *val); int smblib_get_prop_typec_power_role(struct smb_charger *chg, union power_supply_propval *val); int smblib_get_prop_pd_allowed(struct smb_charger *chg, @@ -515,6 +517,8 @@ int smblib_set_prop_boost_current(struct smb_charger *chg, const union power_supply_propval *val); int smblib_set_prop_typec_power_role(struct smb_charger *chg, const union power_supply_propval *val); +int smblib_set_prop_typec_select_rp(struct smb_charger *chg, + const union power_supply_propval *val); int smblib_set_prop_pd_active(struct smb_charger *chg, const union power_supply_propval *val); int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg, diff --git a/drivers/power/supply/qcom/smb-reg.h b/drivers/power/supply/qcom/smb-reg.h index 718e51b1fa13..f6abe6e31c9f 100644 --- a/drivers/power/supply/qcom/smb-reg.h +++ b/drivers/power/supply/qcom/smb-reg.h @@ -568,6 +568,13 @@ enum { #define USB_FACTORY_MODE_ENABLE_BIT BIT(2) #define TYPE_C_UFP_MODE_BIT BIT(1) #define EN_80UA_180UA_CUR_SOURCE_BIT BIT(0) +enum { + TYPEC_SRC_RP_STD, + TYPEC_SRC_RP_1P5A, + TYPEC_SRC_RP_3A, + TYPEC_SRC_RP_3A_DUPLICATE, + TYPEC_SRC_RP_MAX_ELEMENTS +}; #define TYPE_C_CFG_3_REG (USBIN_BASE + 0x5A) #define TVBUS_DEBOUNCE_BIT BIT(7) diff --git a/drivers/power/supply/qcom/smb1355-charger.c b/drivers/power/supply/qcom/smb1355-charger.c index c479865209c8..fb7098e11bbe 100644 --- a/drivers/power/supply/qcom/smb1355-charger.c +++ b/drivers/power/supply/qcom/smb1355-charger.c @@ -758,7 +758,7 @@ static int smb1355_get_prop_charge_type(struct smb1355 *chip, return rc; } -#define MIN_PARALLEL_ICL_UA 250000 +#define MIN_PARALLEL_ICL_UA 100000 #define SUSPEND_CURRENT_UA 2000 static int smb1355_parallel_get_prop(struct power_supply *psy, enum power_supply_property prop, @@ -948,7 +948,7 @@ static int smb1355_set_current_max(struct smb1355 *chip, int curr) rc = smb1355_set_parallel_charging(chip, false); if (rc < 0) return rc; - + curr+=1000000; rc = smb1355_set_charge_param(chip, &chip->param.usb_icl, curr); chip->suspended_usb_icl = 0; diff --git a/drivers/power/supply/qcom/smb5-lib.c b/drivers/power/supply/qcom/smb5-lib.c index 6f8a81a10147..555a12824d46 100644 --- a/drivers/power/supply/qcom/smb5-lib.c +++ b/drivers/power/supply/qcom/smb5-lib.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. */ #include @@ -21,29 +20,26 @@ #include "step-chg-jeita.h" #include "storm-watch.h" #include "schgm-flash.h" -#include - -#define COLLAPSE_DETACH_MAX_INTERVAL ((unsigned long)5) -#define ATTACH_DETACH_MAX_INTERVAL ((unsigned long)310) -#define DETACH_ATTACH_MAX_INTERVAL ((unsigned long)330) #define smblib_err(chg, fmt, ...) \ pr_err("%s: %s: " fmt, chg->name, \ __func__, ##__VA_ARGS__) \ #define smblib_dbg(chg, reason, fmt, ...) \ - do { } while (0) + do { \ + if (*chg->debug_mask & (reason)) \ + pr_info("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__); \ + else \ + pr_debug("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__); \ + } while (0) #define typec_rp_med_high(chg, typec_mode) \ ((typec_mode == POWER_SUPPLY_TYPEC_SOURCE_MEDIUM \ || typec_mode == POWER_SUPPLY_TYPEC_SOURCE_HIGH) \ && (!chg->typec_legacy || chg->typec_legacy_use_rp_icl)) -int ther_ibat_olive[] = {3000000, 3000000, 2500000, 2000000, - 1500000, 1500000, 1000000, 1000000, 700000, - 500000, 2700000, 2500000, 2500000, 2500000, - 2000000, 2000000, 2000000, 1500000, 1500000}; - static void update_sw_icl_max(struct smb_charger *chg, int pst); static int smblib_get_prop_typec_mode(struct smb_charger *chg); @@ -577,6 +573,7 @@ static int smblib_is_input_present(struct smb_charger *chg, { int rc; union power_supply_propval pval = {0, }; + *present = INPUT_NOT_PRESENT; rc = smblib_get_prop_usb_present(chg, &pval); @@ -1456,6 +1453,7 @@ int smblib_set_icl_current(struct smb_charger *chg, int icl_ua) if (rc >= 0) goto unsuspend; } + rc = smblib_set_charge_param(chg, &chg->param.usb_icl, icl_ua); if (rc < 0) { smblib_err(chg, "Couldn't set HC ICL rc=%d\n", rc); @@ -1867,7 +1865,6 @@ int smblib_vbus_regulator_enable(struct regulator_dev *rdev) smblib_dbg(chg, PR_OTG, "enabling OTG\n"); rc = smblib_masked_write(chg, DCDC_CMD_OTG_REG, OTG_EN_BIT, OTG_EN_BIT); - chg->otg_present = true; if (rc < 0) { smblib_err(chg, "Couldn't enable OTG rc=%d\n", rc); return rc; @@ -1882,7 +1879,7 @@ int smblib_vbus_regulator_disable(struct regulator_dev *rdev) int rc; smblib_dbg(chg, PR_OTG, "disabling OTG\n"); - chg->otg_present = false; + rc = smblib_masked_write(chg, DCDC_CMD_OTG_REG, OTG_EN_BIT, 0); if (rc < 0) { smblib_err(chg, "Couldn't disable OTG regulator rc=%d\n", rc); @@ -1975,12 +1972,7 @@ int smblib_get_prop_batt_status(struct smb_charger *chg, bool usb_online, dc_online; u8 stat; int rc, suspend = 0, input_present = 0; - union power_supply_propval health_pval = {0, }; - - rc = smblib_get_prop_batt_health(chg, &health_pval); - if (rc < 0) - pr_err("get healthd fail\n"); if (chg->fake_chg_status_on_debug_batt) { rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_DEBUG_BATTERY, &pval); @@ -2084,10 +2076,7 @@ int smblib_get_prop_batt_status(struct smb_charger *chg, switch (stat) { case TERMINATE_CHARGE: case INHIBIT_CHARGE: - if (health_pval.intval != POWER_SUPPLY_HEALTH_WARM) - val->intval = POWER_SUPPLY_STATUS_FULL; - else - val->intval = POWER_SUPPLY_STATUS_CHARGING; + val->intval = POWER_SUPPLY_STATUS_FULL; break; default: val->intval = POWER_SUPPLY_STATUS_DISCHARGING; @@ -2096,11 +2085,6 @@ int smblib_get_prop_batt_status(struct smb_charger *chg, return rc; } - if (rc < 0) { - smblib_err(chg, "Couldn't get battery health rc=%d\n", rc); - return rc; - } - switch (stat) { case TRICKLE_CHARGE: case PRE_CHARGE: @@ -2110,17 +2094,11 @@ int smblib_get_prop_batt_status(struct smb_charger *chg, break; case TERMINATE_CHARGE: case INHIBIT_CHARGE: - if (health_pval.intval != POWER_SUPPLY_HEALTH_WARM) - val->intval = POWER_SUPPLY_STATUS_FULL; - else - val->intval = POWER_SUPPLY_STATUS_CHARGING; + val->intval = POWER_SUPPLY_STATUS_FULL; break; case DISABLE_CHARGE: case PAUSE_CHARGE: - if (health_pval.intval != POWER_SUPPLY_HEALTH_WARM) - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - else - val->intval = POWER_SUPPLY_STATUS_CHARGING; + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; default: val->intval = POWER_SUPPLY_STATUS_UNKNOWN; @@ -2138,10 +2116,7 @@ int smblib_get_prop_batt_status(struct smb_charger *chg, */ if (is_client_vote_enabled_locked(chg->usb_icl_votable, CHG_TERMINATION_VOTER)) { - if (health_pval.intval != POWER_SUPPLY_HEALTH_WARM) - val->intval = POWER_SUPPLY_STATUS_FULL; - else - val->intval = POWER_SUPPLY_STATUS_CHARGING; + val->intval = POWER_SUPPLY_STATUS_FULL; return 0; } @@ -2164,12 +2139,9 @@ int smblib_get_prop_batt_status(struct smb_charger *chg, stat &= ENABLE_TRICKLE_BIT | ENABLE_PRE_CHARGING_BIT | ENABLE_FULLON_MODE_BIT; - if (!stat) { - if (health_pval.intval != POWER_SUPPLY_HEALTH_WARM) - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - else - val->intval = POWER_SUPPLY_STATUS_CHARGING; - } + if (!stat) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + return 0; } @@ -2366,7 +2338,9 @@ int smblib_get_batt_current_now(struct smb_charger *chg, rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_CURRENT_NOW, val); - printk("current_now=%d\n", val->intval); + if (!rc) + val->intval *= (-1); + return rc; } @@ -2425,14 +2399,13 @@ int smblib_set_prop_batt_status(struct smb_charger *chg, int smblib_set_prop_system_temp_level(struct smb_charger *chg, const union power_supply_propval *val) { - printk("thermal test,the thermal_level=%d\n", val->intval); if (val->intval < 0) return -EINVAL; if (chg->thermal_levels <= 0) return -EINVAL; - if (val->intval > 18) + if (val->intval > chg->thermal_levels) return -EINVAL; chg->system_temp_level = val->intval; @@ -2442,16 +2415,11 @@ int smblib_set_prop_system_temp_level(struct smb_charger *chg, THERMAL_DAEMON_VOTER, true, 0); vote(chg->chg_disable_votable, THERMAL_DAEMON_VOTER, false, 0); - //if (chg->system_temp_level == 0) - // return vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, false, 0); + if (chg->system_temp_level == 0) + return vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, false, 0); vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, true, - ther_ibat_olive[chg->system_temp_level]); - - smblib_err(chg, "system_temp_level = %d, ibat =%d\n", - chg->system_temp_level, - ther_ibat_olive[chg->system_temp_level]); - + chg->thermal_mitigation[chg->system_temp_level]); return 0; } @@ -3724,13 +3692,11 @@ static int smblib_get_prop_ufp_mode(struct smb_charger *chg) } smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_1 = 0x%02x\n", stat); - if (stat & - (SNK_RP_STD_DAM_BIT | SNK_RP_1P5_DAM_BIT | SNK_RP_3P0_DAM_BIT)) { - printk("szw:entery test\n"); - smblib_masked_write(chg, TYPE_C_DEBUG_ACCESS_SINK_REG, - TYPEC_DEBUG_ACCESS_SINK_MASK, - 0x17); - } + /* config 0x154A to 0x17 */ + if (stat & (SNK_RP_STD_DAM_BIT | SNK_RP_1P5_DAM_BIT | SNK_RP_3P0_DAM_BIT)){ + smblib_masked_write(chg, TYPE_C_DEBUG_ACCESS_SINK_REG, + TYPEC_DEBUG_ACCESS_SINK_MASK, 0x17); + } switch (stat & DETECTED_SRC_TYPE_MASK) { case SNK_RP_STD_BIT: @@ -3744,12 +3710,10 @@ static int smblib_get_prop_ufp_mode(struct smb_charger *chg) return POWER_SUPPLY_TYPEC_SOURCE_HIGH; case SNK_RP_SHORT_BIT: return POWER_SUPPLY_TYPEC_NON_COMPLIANT; - #if 0 - case SNK_DAM_500MA_BIT: - case SNK_DAM_1500MA_BIT: - case SNK_DAM_3000MA_BIT: - return POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY; - #endif + // case SNK_DAM_500MA_BIT: + // case SNK_DAM_1500MA_BIT: + // case SNK_DAM_3000MA_BIT: + // return POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY; default: break; } @@ -3830,18 +3794,19 @@ int smblib_get_prop_typec_power_role(struct smb_charger *chg, return 0; } + spin_lock(&chg->typec_pr_lock); rc = smblib_read(chg, TYPE_C_MODE_CFG_REG, &ctrl); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_MODE_CFG_REG rc=%d\n", rc); - return rc; + goto unlock; } smblib_dbg(chg, PR_REGISTER, "TYPE_C_MODE_CFG_REG = 0x%02x\n", ctrl); if (ctrl & TYPEC_DISABLE_CMD_BIT) { val->intval = POWER_SUPPLY_TYPEC_PR_NONE; - return rc; + goto unlock; } switch (ctrl & (EN_SRC_ONLY_BIT | EN_SNK_ONLY_BIT)) { @@ -3858,10 +3823,14 @@ int smblib_get_prop_typec_power_role(struct smb_charger *chg, val->intval = POWER_SUPPLY_TYPEC_PR_NONE; smblib_err(chg, "unsupported power role 0x%02lx\n", ctrl & (EN_SRC_ONLY_BIT | EN_SNK_ONLY_BIT)); - return -EINVAL; + rc = -EINVAL; + goto unlock; } chg->power_role = val->intval; +unlock: + spin_unlock(&chg->typec_pr_lock); + return rc; } @@ -4311,7 +4280,7 @@ static int smblib_handle_usb_current(struct smb_charger *chg, * configured for SDP mode. */ rc = vote(chg->usb_icl_votable, - SW_ICL_MAX_VOTER, true, USBIN_1000MA); + SW_ICL_MAX_VOTER, true, USBIN_500MA); if (rc < 0) smblib_err(chg, "Couldn't set SDP ICL rc=%d\n", @@ -4329,12 +4298,12 @@ static int smblib_handle_usb_current(struct smb_charger *chg, rp_ua = get_rp_based_dcp_current(chg, typec_mode); rc = vote(chg->usb_icl_votable, - SW_ICL_MAX_VOTER, true, USBIN_1000MA); + SW_ICL_MAX_VOTER, true, rp_ua); if (rc < 0) return rc; } else { rc = vote(chg->usb_icl_votable, - SW_ICL_MAX_VOTER, true, USBIN_1000MA); + SW_ICL_MAX_VOTER, true, DCP_CURRENT_UA); if (rc < 0) return rc; } @@ -4489,6 +4458,7 @@ int smblib_set_prop_typec_power_role(struct smb_charger *chg, if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) return 0; + spin_lock(&chg->typec_pr_lock); smblib_dbg(chg, PR_MISC, "power role change: %d --> %d!", chg->power_role, val->intval); @@ -4500,7 +4470,7 @@ int smblib_set_prop_typec_power_role(struct smb_charger *chg, chg->power_role != POWER_SUPPLY_TYPEC_PR_NONE) { smblib_dbg(chg, PR_MISC, "power role already in %d, ignore!", chg->power_role); - return 0; + goto unlock; } typec_mode = smblib_get_prop_typec_mode(chg); @@ -4526,7 +4496,6 @@ int smblib_set_prop_typec_power_role(struct smb_charger *chg, smblib_dbg(chg, PR_MISC, "snk_attached = %d, src_attached = %d, is_pr_lock = %d\n", snk_attached, src_attached, is_pr_lock); cancel_delayed_work(&chg->pr_lock_clear_work); - spin_lock(&chg->typec_pr_lock); if (!chg->pr_lock_in_progress && is_pr_lock) { smblib_dbg(chg, PR_MISC, "disable type-c interrupts for power role locking\n"); smblib_typec_irq_config(chg, false); @@ -4538,7 +4507,6 @@ int smblib_set_prop_typec_power_role(struct smb_charger *chg, } chg->pr_lock_in_progress = is_pr_lock; - spin_unlock(&chg->typec_pr_lock); switch (val->intval) { case POWER_SUPPLY_TYPEC_PR_NONE: @@ -4555,7 +4523,8 @@ int smblib_set_prop_typec_power_role(struct smb_charger *chg, break; default: smblib_err(chg, "power role %d not supported\n", val->intval); - return -EINVAL; + rc = -EINVAL; + goto unlock; } rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG, @@ -4564,10 +4533,13 @@ int smblib_set_prop_typec_power_role(struct smb_charger *chg, if (rc < 0) { smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", power_role, rc); - return rc; + goto unlock; } chg->power_role = val->intval; +unlock: + spin_unlock(&chg->typec_pr_lock); + return rc; } @@ -4806,7 +4778,6 @@ static int smblib_update_jeita(struct smb_charger *chg, u32 *thresholds, return 0; } -#if 0 static int smblib_charge_inhibit_en(struct smb_charger *chg, bool enable) { int rc; @@ -4816,11 +4787,9 @@ static int smblib_charge_inhibit_en(struct smb_charger *chg, bool enable) enable ? CHARGER_INHIBIT_BIT : 0); return rc; } -#endif static int smblib_soft_jeita_arb_wa(struct smb_charger *chg) { - #if 0 union power_supply_propval pval; int rc = 0; bool soft_jeita; @@ -4906,8 +4875,6 @@ static int smblib_soft_jeita_arb_wa(struct smb_charger *chg) smblib_dbg(chg, PR_MISC, "JEITA ARB status %d, soft JEITA status %d\n", chg->jeita_arb_flag, soft_jeita); return rc; - #endif - return 0; } /************************ @@ -5060,17 +5027,7 @@ irqreturn_t default_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; - const struct apsd_result *apsd_result; - if (!strcmp(irq_data->name, "usbin-collapse")) { - apsd_result = smblib_get_apsd_result(chg); - smblib_err(chg, "IRQ: usbin-collapse, APSD = %s\n", apsd_result->name); - if (!strncmp(apsd_result->name, "HVDCP", 5)) { - chg->collapsed = true; - chg->recent_collapse_time = jiffies; - smblib_err(chg, "HVDCP-usbin-collapse, time = %lu\n", chg->recent_collapse_time); - } - } smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); return IRQ_HANDLED; } @@ -5147,7 +5104,9 @@ static void smblib_eval_chg_termination(struct smb_charger *chg, u8 batt_status) * battery. Trigger the charge termination WA once charging is completed * to prevent overcharing. */ - if ((batt_status == TERMINATE_CHARGE) && (pval.intval == 100)) { + if ((batt_status == TERMINATE_CHARGE) && (pval.intval == 100) && + (ktime_to_ms(alarm_expires_remaining(/* alarm not pending */ + &chg->chg_termination_alarm)) <= 0)) { chg->cc_soc_ref = 0; chg->last_cc_soc = 0; chg->term_vbat_uv = 0; @@ -5566,7 +5525,6 @@ void smblib_usb_plugin_hard_reset_locked(struct smb_charger *chg) } #define PL_DELAY_MS 30000 -static int float_check = 0; void smblib_usb_plugin_locked(struct smb_charger *chg) { int rc; @@ -5574,9 +5532,6 @@ void smblib_usb_plugin_locked(struct smb_charger *chg) bool vbus_rising; struct smb_irq_data *data; struct storm_watch *wdata; - static unsigned long detach_time; - static unsigned long attach_time; - static bool need_confirm; rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); if (rc < 0) { @@ -5589,19 +5544,6 @@ void smblib_usb_plugin_locked(struct smb_charger *chg) chg->chg_freq.freq_removal); if (vbus_rising) { - float_check = 1; - pr_err("dhx---rising:1\n"); - attach_time = jiffies; - smblib_err(chg, "attach_time = %lu\n", attach_time); - if (need_confirm && attach_time - detach_time < DETACH_ATTACH_MAX_INTERVAL) { - rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, HVDCP_EN_BIT, 0); - if (rc < 0) - smblib_err(chg, "XIAOMI can't disable HVDCP for workaround\n"); - else - chg->hvdcp_disabled = true; - } - need_confirm = false; - cancel_delayed_work_sync(&chg->pr_swap_detach_work); vote(chg->awake_votable, DETACH_DETECT_VOTER, false, 0); rc = smblib_request_dpdm(chg, true); @@ -5623,25 +5565,7 @@ void smblib_usb_plugin_locked(struct smb_charger *chg) schedule_delayed_work(&chg->pl_enable_work, msecs_to_jiffies(PL_DELAY_MS)); } else { - if (chg->hvdcp_disabled) { - rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, HVDCP_EN_BIT, HVDCP_EN_BIT); - if (rc < 0) { - smblib_err(chg, "XIAOMI can't restore HVDCP_EN_BIT\n"); - } else { - chg->hvdcp_disabled = false; - } - } - if (chg->collapsed) { - detach_time = jiffies; - smblib_err(chg, "detached after collapse --> time = %lu, collapse-time = %lu, last_atta" - "ch_time = %lu\n", detach_time, chg->recent_collapse_time, attach_time); - if (detach_time - chg->recent_collapse_time < COLLAPSE_DETACH_MAX_INTERVAL && - detach_time - attach_time < ATTACH_DETACH_MAX_INTERVAL) - need_confirm = true; - chg->collapsed = false; - } /* Disable SW Thermal Regulation */ - pr_err("dhx---faling:0\n"); rc = smblib_set_sw_thermal_regulation(chg, false); if (rc < 0) smblib_err(chg, "Couldn't stop SW thermal regulation WA, rc=%d\n", @@ -5664,11 +5588,13 @@ void smblib_usb_plugin_locked(struct smb_charger *chg) if (chg->fcc_stepper_enable) vote(chg->fcc_votable, FCC_STEPPER_VOTER, true, 1500000); + if (chg->wa_flags & WEAK_ADAPTER_WA) { chg->aicl_5v_threshold_mv = chg->default_aicl_5v_threshold_mv; chg->aicl_cont_threshold_mv = chg->default_aicl_cont_threshold_mv; + smblib_set_charge_param(chg, &chg->param.aicl_5v_threshold, chg->aicl_5v_threshold_mv); @@ -5695,6 +5621,10 @@ void smblib_usb_plugin_locked(struct smb_charger *chg) smblib_err(chg, "Couldn't disable DPDM rc=%d\n", rc); smblib_update_usb_type(chg); + + if(chg->use_extcon) + smblib_notify_device_mode(chg,false); + vote(chg->usb_icl_votable, USB_PSY_VOTER,false, 0);//remove USB_PSY voting when plugin detach } if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) @@ -5712,10 +5642,12 @@ irqreturn_t usb_plugin_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; + if (chg->pd_hard_reset) smblib_usb_plugin_hard_reset_locked(chg); else smblib_usb_plugin_locked(chg); + return IRQ_HANDLED; } @@ -5841,6 +5773,7 @@ static void update_sw_icl_max(struct smb_charger *chg, int pst) /* TypeC rp med or high, use rp value */ typec_mode = smblib_get_prop_typec_mode(chg); + if (typec_rp_med_high(chg, typec_mode)) { rp_ua = get_rp_based_dcp_current(chg, typec_mode); vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, rp_ua); @@ -5895,14 +5828,8 @@ static void smblib_handle_apsd_done(struct smb_charger *chg, bool rising) return; apsd_result = smblib_update_usb_type(chg); - update_sw_icl_max(chg, apsd_result->pst); - pr_err("dhx---rerun_apsd1:%s\n",apsd_result->name); - if (float_check == 1 && apsd_result->bit == FLOAT_CHARGER_BIT){ - smblib_rerun_apsd_if_required(chg); - float_check =0; - pr_err("dhx---rerun_apsd:%s\n",apsd_result->name); - } + switch (apsd_result->bit) { case SDP_CHARGER_BIT: case CDP_CHARGER_BIT: @@ -6466,7 +6393,6 @@ static void smblib_handle_rp_change(struct smb_charger *chg, int typec_mode) == FORCE_FLOAT_SDP_CFG_BIT) return; } - update_sw_icl_max(chg, apsd->pst); smblib_dbg(chg, PR_MISC, "CC change old_mode=%d new_mode=%d\n", @@ -6974,6 +6900,7 @@ irqreturn_t dc_plugin_irq_handler(int irq, void *data) if (chg->fcc_stepper_enable && !vbus_present) vote(chg->fcc_votable, FCC_STEPPER_VOTER, !dcin_present, dcin_present ? 0 : 1500000); + if (chg->dc_psy) power_supply_changed(chg->dc_psy); @@ -7569,6 +7496,7 @@ static void smblib_chg_termination_work(struct work_struct *work) chg_termination_work); int rc, input_present, delay = CHG_TERM_WA_ENTRY_DELAY_MS; int vbat_now_uv, max_fv_uv; + u8 stat = 0; /* * Hold awake votable to prevent pm_relax being called prior to @@ -7583,16 +7511,31 @@ static void smblib_chg_termination_work(struct work_struct *work) rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_REAL_CAPACITY, &pval); if ((rc < 0) || (pval.intval < 100)) { - vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, false, 0); - vote(chg->dc_suspend_votable, CHG_TERMINATION_VOTER, false, 0); - goto out; + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); + if (rc < 0) + goto out; + + /* check we are not in termination to exit the WA */ + if ((stat & BATTERY_CHARGER_STATUS_MASK) != TERMINATE_CHARGE) { + vote(chg->usb_icl_votable, + CHG_TERMINATION_VOTER, false, 0); + vote(chg->dc_suspend_votable, + CHG_TERMINATION_VOTER, false, 0); + goto out; + } } /* Get the battery float voltage */ rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_VOLTAGE_MAX, &pval); - if (rc < 0) - goto out; + if (rc < 0) { + /* FG based targets supports only MAX_DESIGN property */ + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + &pval); + if (rc < 0) + goto out; + } max_fv_uv = pval.intval; @@ -8197,7 +8140,6 @@ int smblib_init(struct smb_charger *chg) smblib_pr_lock_clear_work); timer_setup(&chg->apsd_timer, apsd_timer_cb, 0); - INIT_DELAYED_WORK(&chg->role_reversal_check, smblib_typec_role_check_work); @@ -8257,7 +8199,7 @@ int smblib_init(struct smb_charger *chg) } rc = qcom_step_chg_init(chg->dev, chg->step_chg_enabled, - chg->sw_jeita_enabled, false); + chg->sw_jeita_enabled, chg->jeita_arb_enable); if (rc < 0) { smblib_err(chg, "Couldn't init qcom_step_chg_init rc=%d\n", rc); diff --git a/drivers/power/supply/qcom/smb5-lib.h b/drivers/power/supply/qcom/smb5-lib.h index c4ef4869eb70..02f1b46d0da9 100644 --- a/drivers/power/supply/qcom/smb5-lib.h +++ b/drivers/power/supply/qcom/smb5-lib.h @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. */ #ifndef __SMB5_CHARGER_H @@ -95,7 +94,7 @@ enum print_reason { #define SDP_100_MA 100000 #define SDP_CURRENT_UA 500000 #define CDP_CURRENT_UA 1500000 -#define DCP_CURRENT_UA 2000000 +#define DCP_CURRENT_UA 1500000 #define HVDCP_CURRENT_UA 3000000 #define TYPEC_DEFAULT_CURRENT_UA 900000 #define TYPEC_MEDIUM_CURRENT_UA 1500000 @@ -471,7 +470,6 @@ struct smb_charger { struct delayed_work pr_swap_detach_work; struct delayed_work pr_lock_clear_work; struct delayed_work role_reversal_check; - struct delayed_work temp_limit_work; struct alarm lpd_recheck_timer; struct alarm moisture_protection_alarm; @@ -512,6 +510,7 @@ struct smb_charger { int fake_batt_status; bool step_chg_enabled; bool sw_jeita_enabled; + bool jeita_arb_enable; bool typec_legacy_use_rp_icl; bool is_hdc; bool chg_done; @@ -617,9 +616,6 @@ struct smb_charger { int dcin_uv_count; ktime_t dcin_uv_last_time; int last_wls_vout; - unsigned long recent_collapse_time; - bool hvdcp_disabled; - bool collapsed; }; int smblib_read(struct smb_charger *chg, u16 addr, u8 *val); diff --git a/drivers/power/supply/qcom/smb5-reg.h b/drivers/power/supply/qcom/smb5-reg.h index 4a6b6bbad949..5f61b34d609d 100644 --- a/drivers/power/supply/qcom/smb5-reg.h +++ b/drivers/power/supply/qcom/smb5-reg.h @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #ifndef __SMB5_CHARGER_REG_H @@ -336,20 +335,20 @@ enum { ********************************/ #define TYPE_C_SNK_STATUS_REG (TYPEC_BASE + 0x06) #define DETECTED_SRC_TYPE_MASK GENMASK(6, 0) + +#define SNK_RP_STD_DAM_BIT BIT(6) +#define SNK_RP_1P5_DAM_BIT BIT(5) +#define SNK_RP_3P0_DAM_BIT BIT(4) + #define SNK_DAM_MASK GENMASK(6, 4) -#define SNK_RP_STD_DAM_BIT BIT(6) -#define SNK_RP_1P5_DAM_BIT BIT(5) -#define SNK_RP_3P0_DAM_BIT BIT(4) #define SNK_DAM_500MA_BIT BIT(6) #define SNK_DAM_1500MA_BIT BIT(5) -#define SNK_DAM_3000MA_BIT BIT(4) +//#define SNK_DAM_3000MA_BIT BIT(4) #define SNK_RP_STD_BIT BIT(3) #define SNK_RP_1P5_BIT BIT(2) #define SNK_RP_3P0_BIT BIT(1) #define SNK_RP_SHORT_BIT BIT(0) - - #define TYPE_C_SRC_STATUS_REG (TYPEC_BASE + 0x08) #define DETECTED_SNK_TYPE_MASK GENMASK(4, 0) #define SRC_HIGH_BATT_BIT BIT(5) @@ -401,8 +400,9 @@ enum { #define TYPEC_CCOUT_VALUE_BIT BIT(1) #define TYPEC_CCOUT_SRC_BIT BIT(0) -#define TYPE_C_DEBUG_ACCESS_SINK_REG (TYPEC_BASE + 0x4A) -#define TYPEC_DEBUG_ACCESS_SINK_MASK GENMASK(4, 0) +#define TYPE_C_DEBUG_ACCESS_SINK_REG (TYPEC_BASE + 0x4A) +#define TYPEC_DEBUG_ACCESS_SINK_MASK GENMASK(4, 0) + #define DEBUG_ACCESS_SRC_CFG_REG (TYPEC_BASE + 0x4C) #define EN_UNORIENTED_DEBUG_ACCESS_SRC_BIT BIT(0) diff --git a/drivers/power/supply/qcom/step-chg-jeita.c b/drivers/power/supply/qcom/step-chg-jeita.c index 27b3199c232a..369fc6601070 100644 --- a/drivers/power/supply/qcom/step-chg-jeita.c +++ b/drivers/power/supply/qcom/step-chg-jeita.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2017-2019 The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. + * Copyright (c) 2017-2019, 2021 The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "QCOM-STEPCHG: %s: " fmt, __func__ @@ -17,6 +16,7 @@ #define STEP_CHG_VOTER "STEP_CHG_VOTER" #define JEITA_VOTER "JEITA_VOTER" +#define JEITA_FCC_SCALE_VOTER "JEITA_FCC_SCALE_VOTER" #define is_between(left, right, value) \ (((left) >= (right) && (left) >= (value) \ @@ -54,10 +54,15 @@ struct step_chg_info { bool vbat_avg_based_step_chg; bool batt_missing; bool taper_fcc; + bool jeita_fcc_scaling; int jeita_fcc_index; int jeita_fv_index; int step_index; int get_config_retry_count; + int jeita_last_update_temp; + int jeita_fcc_scaling_temp_threshold[2]; + long jeita_max_fcc_ua; + long jeita_fcc_step_size; struct step_chg_cfg *step_chg_config; struct jeita_fcc_cfg *jeita_fcc_config; @@ -231,6 +236,7 @@ static int get_step_chg_jeita_setting_from_profile(struct step_chg_info *chip) const char *batt_type_str; const __be32 *handle; int batt_id_ohms, rc, hysteresis[2] = {0}; + u32 jeita_scaling_min_fcc_ua = 0; union power_supply_propval prop = {0, }; handle = of_get_property(chip->dev->of_node, @@ -289,6 +295,7 @@ static int get_step_chg_jeita_setting_from_profile(struct step_chg_info *chip) pr_err("max-fastchg-current-ma reading failed, rc=%d\n", rc); return rc; } + chip->jeita_max_fcc_ua = max_fcc_ma * 1000; chip->taper_fcc = of_property_read_bool(profile_node, "qcom,taper-fcc"); @@ -367,6 +374,48 @@ static int get_step_chg_jeita_setting_from_profile(struct step_chg_info *chip) chip->sw_jeita_cfg_valid = false; } + if (of_property_read_bool(profile_node, "qcom,jeita-fcc-scaling")) { + + rc = of_property_read_u32_array(profile_node, + "qcom,jeita-fcc-scaling-temp-threshold", + chip->jeita_fcc_scaling_temp_threshold, 2); + if (rc < 0) + pr_debug("Read jeita-fcc-scaling-temp-threshold from battery profile, rc=%d\n", + rc); + + rc = of_property_read_u32(profile_node, + "qcom,jeita-scaling-min-fcc-ua", + &jeita_scaling_min_fcc_ua); + if (rc < 0) + pr_debug("Read jeita-scaling-min-fcc-ua from battery profile, rc=%d\n", + rc); + + if ((jeita_scaling_min_fcc_ua && + (jeita_scaling_min_fcc_ua < chip->jeita_max_fcc_ua)) && + (chip->jeita_fcc_scaling_temp_threshold[0] < + chip->jeita_fcc_scaling_temp_threshold[1])) { + /* + * Calculate jeita-fcc-step-size = + * (difference-in-fcc) / ( difference-in-temp) + */ + chip->jeita_fcc_step_size = div_s64( + (chip->jeita_max_fcc_ua - jeita_scaling_min_fcc_ua), + (chip->jeita_fcc_scaling_temp_threshold[1] - + chip->jeita_fcc_scaling_temp_threshold[0])); + + if (chip->jeita_fcc_step_size > 0) + chip->jeita_fcc_scaling = true; + } + + pr_debug("jeita-fcc-scaling: enabled = %d, jeita-fcc-scaling-temp-threshold = [%d, %d], jeita-scaling-min-fcc-ua = %ld, jeita-scaling-max_fcc_ua = %ld,jeita-fcc-step-size = %ld\n", + chip->jeita_fcc_scaling, + chip->jeita_fcc_scaling_temp_threshold[0], + chip->jeita_fcc_scaling_temp_threshold[1], + jeita_scaling_min_fcc_ua, chip->jeita_max_fcc_ua, + chip->jeita_fcc_step_size + ); + } + return rc; } @@ -623,6 +672,76 @@ static int handle_step_chg_config(struct step_chg_info *chip) return 0; } +static void handle_jeita_fcc_scaling(struct step_chg_info *chip) +{ + union power_supply_propval pval = {0, }; + int fcc_ua = 0, temp_diff = 0, rc; + bool first_time_entry; + + if (chip->jeita_fcc_config->param.use_bms) + rc = power_supply_get_property(chip->bms_psy, + chip->jeita_fcc_config->param.psy_prop, &pval); + else + rc = power_supply_get_property(chip->batt_psy, + chip->jeita_fcc_config->param.psy_prop, &pval); + + if (rc < 0) { + pr_err("Couldn't read %s property rc=%d\n", + chip->jeita_fcc_config->param.prop_name, rc); + return; + } + + /* Skip mitigation if temp is not within min-max thresholds */ + if (!is_between(chip->jeita_fcc_scaling_temp_threshold[0], + chip->jeita_fcc_scaling_temp_threshold[1], pval.intval)) { + pr_debug("jeita-fcc-scaling : Skip jeita scaling, temp out of range temp = %d\n", + pval.intval); + chip->jeita_last_update_temp = pval.intval; + vote(chip->fcc_votable, JEITA_FCC_SCALE_VOTER, false, 0); + return; + } + + /* + * We determine this is the first time entry to jeita-fcc-scaling if + * jeita_last_update_temp is not within entry/exist thresholds. + */ + first_time_entry = !is_between(chip->jeita_fcc_scaling_temp_threshold[0] + , chip->jeita_fcc_scaling_temp_threshold[1], + chip->jeita_last_update_temp); + + /* + * VOTE on FCC only when temp is within hys or if this the very first + * time we crossed the entry threshold. + */ + if (first_time_entry || + ((pval.intval > (chip->jeita_last_update_temp + + chip->jeita_fcc_config->param.rise_hys)) || + (pval.intval < (chip->jeita_last_update_temp - + chip->jeita_fcc_config->param.fall_hys)))) { + + /* + * New FCC step is calculated as : + * fcc_ua = (max-fcc - ((current_temp - min-temp) * + * jeita-step-size)) + */ + temp_diff = pval.intval - + chip->jeita_fcc_scaling_temp_threshold[0]; + fcc_ua = div_s64((chip->jeita_max_fcc_ua - + (chip->jeita_fcc_step_size * temp_diff)), 100) * 100; + + vote(chip->fcc_votable, JEITA_FCC_SCALE_VOTER, true, fcc_ua); + pr_debug("jeita-fcc-scaling: first_time_entry = %d, max_fcc_ua = %ld, voted_fcc_ua = %d, temp_diff = %d, prev_temp = %d, current_temp = %d\n", + first_time_entry, chip->jeita_max_fcc_ua, fcc_ua, + temp_diff, chip->jeita_last_update_temp, pval.intval); + + chip->jeita_last_update_temp = pval.intval; + } else { + pr_debug("jeita-fcc-scaling: Skip jeita mitigation temp within first_time_entry = %d, hys temp = %d, last_updated_temp = %d\n", + first_time_entry, pval.intval, + chip->jeita_last_update_temp); + } +} + #define JEITA_SUSPEND_HYST_UV 50000 static int handle_jeita(struct step_chg_info *chip) { @@ -637,6 +756,10 @@ static int handle_jeita(struct step_chg_info *chip) else chip->sw_jeita_enable = pval.intval; + /* Handle jeita-fcc-scaling if enabled */ + if (chip->jeita_fcc_scaling) + handle_jeita_fcc_scaling(chip); + if (!chip->sw_jeita_enable || !chip->sw_jeita_cfg_valid) { if (chip->fcc_votable) vote(chip->fcc_votable, JEITA_VOTER, false, 0); @@ -681,7 +804,7 @@ static int handle_jeita(struct step_chg_info *chip) /* changing FCC is a must */ return -EINVAL; - vote(chip->fcc_votable, JEITA_VOTER, true, fcc_ua); + vote(chip->fcc_votable, JEITA_VOTER, fcc_ua ? true : false, fcc_ua); rc = get_val(chip->jeita_fv_config->fv_cfg, chip->jeita_fv_config->param.rise_hys, diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 0e18e5206aae..97197e43fb1f 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1065,6 +1065,16 @@ config REGULATOR_MEM_ACC controls delays applied for memory accesses. This driver configures the power-mode(corner) for the memory accelerator. +config REGULATOR_CPR + bool "RBCPR regulator driver for APC" + depends on OF + help + Compile in RBCPR (RapidBridge Core Power Reduction) driver to support + corner vote for APC power rail. The driver takes PTE process voltage + suggestions in efuse as initial settings. It converts corner vote + to voltage value before writing to a voltage regulator API, such as + that provided by spm-regulator driver. + config REGULATOR_CPR3 bool "CPR3 regulator core support" help diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 1d5b3bc5a249..45394b845245 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -137,6 +137,7 @@ obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o obj-$(CONFIG_REGULATOR_MEM_ACC) += mem-acc-regulator.o obj-$(CONFIG_REGULATOR_MSM_GFX_LDO) += msm_gfx_ldo.o +obj-$(CONFIG_REGULATOR_CPR) += cpr-regulator.o obj-$(CONFIG_REGULATOR_CPR3) += cpr3-regulator.o cpr3-util.o obj-$(CONFIG_REGULATOR_CPR4_MMSS_LDO) += cpr4-mmss-ldo-regulator.o obj-$(CONFIG_REGULATOR_CPRH_KBSS) += cprh-kbss-regulator.o diff --git a/drivers/regulator/cpr-regulator.c b/drivers/regulator/cpr-regulator.c new file mode 100644 index 000000000000..674af9f5d2dd --- /dev/null +++ b/drivers/regulator/cpr-regulator.c @@ -0,0 +1,5756 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register Offsets for RB-CPR and Bit Definitions */ + +/* RBCPR Version Register */ +#define REG_RBCPR_VERSION 0 +#define RBCPR_VER_2 0x02 + +/* RBCPR Gate Count and Target Registers */ +#define REG_RBCPR_GCNT_TARGET(n) (0x60 + 4 * n) + +#define RBCPR_GCNT_TARGET_GCNT_BITS 10 +#define RBCPR_GCNT_TARGET_GCNT_SHIFT 12 +#define RBCPR_GCNT_TARGET_GCNT_MASK ((1<>RBCPR_DEBUG1_QUOT_SLOW_SHIFT) & \ + ((1<rdesc.name, \ + ##__VA_ARGS__); \ + } while (0) +#define cpr_debug_irq(cpr_vreg, message, ...) \ + do { \ + if (cpr_debug_enable & CPR_DEBUG_MASK_IRQ) \ + pr_info("%s: " message, (cpr_vreg)->rdesc.name, \ + ##__VA_ARGS__); \ + else \ + pr_debug("%s: " message, (cpr_vreg)->rdesc.name, \ + ##__VA_ARGS__); \ + } while (0) +#define cpr_info(cpr_vreg, message, ...) \ + pr_info("%s: " message, (cpr_vreg)->rdesc.name, ##__VA_ARGS__) +#define cpr_err(cpr_vreg, message, ...) \ + pr_err("%s: " message, (cpr_vreg)->rdesc.name, ##__VA_ARGS__) + +static u64 cpr_read_remapped_efuse_row(struct cpr_regulator *cpr_vreg, + u32 row_num) +{ + if (row_num - cpr_vreg->remapped_row_base + >= cpr_vreg->num_remapped_rows) { + cpr_err(cpr_vreg, "invalid row=%u, max remapped row=%u\n", + row_num, cpr_vreg->remapped_row_base + + cpr_vreg->num_remapped_rows - 1); + return 0; + } + + return cpr_vreg->remapped_row[row_num - cpr_vreg->remapped_row_base]; +} + +static u64 cpr_read_efuse_row(struct cpr_regulator *cpr_vreg, u32 row_num, + bool use_tz_api) +{ + int rc; + u64 efuse_bits; + struct scm_desc desc = {0}; + + struct cpr_read_req { + u32 row_address; + int addr_type; + } req; + + struct cpr_read_rsp { + u32 row_data[2]; + u32 status; + } rsp; + + if (cpr_vreg->remapped_row && row_num >= cpr_vreg->remapped_row_base) + return cpr_read_remapped_efuse_row(cpr_vreg, row_num); + + if (!use_tz_api) { + efuse_bits = readq_relaxed(cpr_vreg->efuse_base + + row_num * BYTES_PER_FUSE_ROW); + return efuse_bits; + } + desc.args[0] = req.row_address = cpr_vreg->efuse_addr + + row_num * BYTES_PER_FUSE_ROW; + desc.args[1] = req.addr_type = 0; + desc.arginfo = SCM_ARGS(2); + efuse_bits = 0; + + rc = scm_call2(SCM_SIP_FNID(SCM_SVC_FUSE, SCM_FUSE_READ), + &desc); + rsp.row_data[0] = desc.ret[0]; + rsp.row_data[1] = desc.ret[1]; + rsp.status = desc.ret[2]; + + if (rc) { + cpr_err(cpr_vreg, "read row %d failed, err code = %d", + row_num, rc); + } else { + efuse_bits = ((u64)(rsp.row_data[1]) << 32) + + (u64)rsp.row_data[0]; + } + return efuse_bits; +} + +/** + * cpr_read_efuse_param() - read a parameter from one or two eFuse rows + * @cpr_vreg: Pointer to cpr_regulator struct for this regulator. + * @row_start: Fuse row number to start reading from. + * @bit_start: The LSB of the parameter to read from the fuse. + * @bit_len: The length of the parameter in bits. + * @use_tz_api: Flag to indicate if an SCM call should be used to read the fuse. + * + * This function reads a parameter of specified offset and bit size out of one + * or two consecutive eFuse rows. This allows for the reading of parameters + * that happen to be split between two eFuse rows. + * + * Returns the fuse parameter on success or 0 on failure. + */ +static u64 cpr_read_efuse_param(struct cpr_regulator *cpr_vreg, int row_start, + int bit_start, int bit_len, bool use_tz_api) +{ + u64 fuse[2]; + u64 param = 0; + int bits_first, bits_second; + + if (bit_start < 0) { + cpr_err(cpr_vreg, "Invalid LSB = %d specified\n", bit_start); + return 0; + } + + if (bit_len < 0 || bit_len > 64) { + cpr_err(cpr_vreg, "Invalid bit length = %d specified\n", + bit_len); + return 0; + } + + /* Allow bit indexing to start beyond the end of the start row. */ + if (bit_start >= 64) { + row_start += bit_start >> 6; /* equivalent to bit_start / 64 */ + bit_start &= 0x3F; + } + + fuse[0] = cpr_read_efuse_row(cpr_vreg, row_start, use_tz_api); + + if (bit_start == 0 && bit_len == 64) { + param = fuse[0]; + } else if (bit_start + bit_len <= 64) { + param = (fuse[0] >> bit_start) & ((1ULL << bit_len) - 1); + } else { + fuse[1] = cpr_read_efuse_row(cpr_vreg, row_start + 1, + use_tz_api); + bits_first = 64 - bit_start; + bits_second = bit_len - bits_first; + param = (fuse[0] >> bit_start) & ((1ULL << bits_first) - 1); + param |= (fuse[1] & ((1ULL << bits_second) - 1)) << bits_first; + } + + return param; +} + +static bool cpr_is_allowed(struct cpr_regulator *cpr_vreg) +{ + if (cpr_vreg->cpr_fuse_disable || !cpr_vreg->enable) + return false; + else + return true; +} + +static void cpr_write(struct cpr_regulator *cpr_vreg, u32 offset, u32 value) +{ + writel_relaxed(value, cpr_vreg->rbcpr_base + offset); +} + +static u32 cpr_read(struct cpr_regulator *cpr_vreg, u32 offset) +{ + return readl_relaxed(cpr_vreg->rbcpr_base + offset); +} + +static void cpr_masked_write(struct cpr_regulator *cpr_vreg, u32 offset, + u32 mask, u32 value) +{ + u32 reg_val; + + reg_val = readl_relaxed(cpr_vreg->rbcpr_base + offset); + reg_val &= ~mask; + reg_val |= value & mask; + writel_relaxed(reg_val, cpr_vreg->rbcpr_base + offset); +} + +static void cpr_irq_clr(struct cpr_regulator *cpr_vreg) +{ + cpr_write(cpr_vreg, REG_RBIF_IRQ_CLEAR, CPR_INT_ALL); +} + +static void cpr_irq_clr_nack(struct cpr_regulator *cpr_vreg) +{ + cpr_irq_clr(cpr_vreg); + cpr_write(cpr_vreg, REG_RBIF_CONT_NACK_CMD, 1); +} + +static void cpr_irq_clr_ack(struct cpr_regulator *cpr_vreg) +{ + cpr_irq_clr(cpr_vreg); + cpr_write(cpr_vreg, REG_RBIF_CONT_ACK_CMD, 1); +} + +static void cpr_irq_set(struct cpr_regulator *cpr_vreg, u32 int_bits) +{ + cpr_write(cpr_vreg, REG_RBIF_IRQ_EN(cpr_vreg->irq_line), int_bits); +} + +static void cpr_ctl_modify(struct cpr_regulator *cpr_vreg, u32 mask, u32 value) +{ + cpr_masked_write(cpr_vreg, REG_RBCPR_CTL, mask, value); +} + +static void cpr_ctl_enable(struct cpr_regulator *cpr_vreg, int corner) +{ + u32 val; + + if (cpr_vreg->is_cpr_suspended) + return; + + /* Program Consecutive Up & Down */ + val = ((cpr_vreg->timer_cons_down & RBIF_TIMER_ADJ_CONS_DOWN_MASK) + << RBIF_TIMER_ADJ_CONS_DOWN_SHIFT) | + (cpr_vreg->timer_cons_up & RBIF_TIMER_ADJ_CONS_UP_MASK); + cpr_masked_write(cpr_vreg, REG_RBIF_TIMER_ADJUST, + RBIF_TIMER_ADJ_CONS_UP_MASK | + RBIF_TIMER_ADJ_CONS_DOWN_MASK, val); + cpr_masked_write(cpr_vreg, REG_RBCPR_CTL, + RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN | + RBCPR_CTL_SW_AUTO_CONT_ACK_EN, + cpr_vreg->save_ctl[corner]); + cpr_irq_set(cpr_vreg, cpr_vreg->save_irq[corner]); + + if (cpr_is_allowed(cpr_vreg) && cpr_vreg->vreg_enabled && + (cpr_vreg->ceiling_volt[corner] > + cpr_vreg->floor_volt[corner])) + val = RBCPR_CTL_LOOP_EN; + else + val = 0; + cpr_ctl_modify(cpr_vreg, RBCPR_CTL_LOOP_EN, val); +} + +static void cpr_ctl_disable(struct cpr_regulator *cpr_vreg) +{ + if (cpr_vreg->is_cpr_suspended) + return; + + cpr_irq_set(cpr_vreg, 0); + cpr_ctl_modify(cpr_vreg, RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN | + RBCPR_CTL_SW_AUTO_CONT_ACK_EN, 0); + cpr_masked_write(cpr_vreg, REG_RBIF_TIMER_ADJUST, + RBIF_TIMER_ADJ_CONS_UP_MASK | + RBIF_TIMER_ADJ_CONS_DOWN_MASK, 0); + cpr_irq_clr(cpr_vreg); + cpr_write(cpr_vreg, REG_RBIF_CONT_ACK_CMD, 1); + cpr_write(cpr_vreg, REG_RBIF_CONT_NACK_CMD, 1); + cpr_ctl_modify(cpr_vreg, RBCPR_CTL_LOOP_EN, 0); +} + +static bool cpr_ctl_is_enabled(struct cpr_regulator *cpr_vreg) +{ + u32 reg_val; + + reg_val = cpr_read(cpr_vreg, REG_RBCPR_CTL); + return reg_val & RBCPR_CTL_LOOP_EN; +} + +static bool cpr_ctl_is_busy(struct cpr_regulator *cpr_vreg) +{ + u32 reg_val; + + reg_val = cpr_read(cpr_vreg, REG_RBCPR_RESULT_0); + return reg_val & RBCPR_RESULT0_BUSY_MASK; +} + +static void cpr_corner_save(struct cpr_regulator *cpr_vreg, int corner) +{ + cpr_vreg->save_ctl[corner] = cpr_read(cpr_vreg, REG_RBCPR_CTL); + cpr_vreg->save_irq[corner] = + cpr_read(cpr_vreg, REG_RBIF_IRQ_EN(cpr_vreg->irq_line)); +} + +static void cpr_corner_restore(struct cpr_regulator *cpr_vreg, int corner) +{ + u32 gcnt, ctl, irq, ro_sel, step_quot; + int fuse_corner = cpr_vreg->corner_map[corner]; + int i; + + ro_sel = cpr_vreg->cpr_fuse_ro_sel[fuse_corner]; + gcnt = cpr_vreg->gcnt | (cpr_vreg->cpr_fuse_target_quot[fuse_corner] - + cpr_vreg->quot_adjust[corner]); + + /* Program the step quotient and idle clocks */ + step_quot = ((cpr_vreg->idle_clocks & RBCPR_STEP_QUOT_IDLE_CLK_MASK) + << RBCPR_STEP_QUOT_IDLE_CLK_SHIFT) | + (cpr_vreg->step_quotient[fuse_corner] + & RBCPR_STEP_QUOT_STEPQUOT_MASK); + cpr_write(cpr_vreg, REG_RBCPR_STEP_QUOT, step_quot); + + /* Clear the target quotient value and gate count of all ROs */ + for (i = 0; i < CPR_NUM_RING_OSC; i++) + cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(i), 0); + + cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(ro_sel), gcnt); + ctl = cpr_vreg->save_ctl[corner]; + cpr_write(cpr_vreg, REG_RBCPR_CTL, ctl); + irq = cpr_vreg->save_irq[corner]; + cpr_irq_set(cpr_vreg, irq); + cpr_debug(cpr_vreg, "gcnt = 0x%08x, ctl = 0x%08x, irq = 0x%08x\n", + gcnt, ctl, irq); +} + +static void cpr_corner_switch(struct cpr_regulator *cpr_vreg, int corner) +{ + if (cpr_vreg->corner == corner) + return; + + cpr_corner_restore(cpr_vreg, corner); +} + +static int cpr_apc_set(struct cpr_regulator *cpr_vreg, u32 new_volt) +{ + int max_volt, rc; + + max_volt = cpr_vreg->ceiling_max; + rc = regulator_set_voltage(cpr_vreg->vdd_apc, new_volt, max_volt); + if (rc) + cpr_err(cpr_vreg, "set: vdd_apc = %d uV: rc=%d\n", + new_volt, rc); + return rc; +} + +static int cpr_mx_get(struct cpr_regulator *cpr_vreg, int corner, int apc_volt) +{ + int vdd_mx; + int fuse_corner = cpr_vreg->corner_map[corner]; + int highest_fuse_corner = cpr_vreg->num_fuse_corners; + + switch (cpr_vreg->vdd_mx_vmin_method) { + case VDD_MX_VMIN_APC: + vdd_mx = apc_volt; + break; + case VDD_MX_VMIN_APC_CORNER_CEILING: + vdd_mx = cpr_vreg->fuse_ceiling_volt[fuse_corner]; + break; + case VDD_MX_VMIN_APC_SLOW_CORNER_CEILING: + vdd_mx = cpr_vreg->fuse_ceiling_volt[highest_fuse_corner]; + break; + case VDD_MX_VMIN_MX_VMAX: + vdd_mx = cpr_vreg->vdd_mx_vmax; + break; + case VDD_MX_VMIN_APC_FUSE_CORNER_MAP: + vdd_mx = cpr_vreg->vdd_mx_corner_map[fuse_corner]; + break; + case VDD_MX_VMIN_APC_CORNER_MAP: + vdd_mx = cpr_vreg->vdd_mx_corner_map[corner]; + break; + default: + vdd_mx = 0; + break; + } + + return vdd_mx; +} + +static int cpr_mx_set(struct cpr_regulator *cpr_vreg, int corner, + int vdd_mx_vmin) +{ + int rc; + int fuse_corner = cpr_vreg->corner_map[corner]; + + rc = regulator_set_voltage(cpr_vreg->vdd_mx, vdd_mx_vmin, + cpr_vreg->vdd_mx_vmax); + cpr_debug(cpr_vreg, "[corner:%d, fuse_corner:%d] %d uV\n", corner, + fuse_corner, vdd_mx_vmin); + + if (!rc) { + cpr_vreg->vdd_mx_vmin = vdd_mx_vmin; + } else { + cpr_err(cpr_vreg, "set: vdd_mx [corner:%d, fuse_corner:%d] = %d uV failed: rc=%d\n", + corner, fuse_corner, vdd_mx_vmin, rc); + } + return rc; +} + +static int cpr_scale_voltage(struct cpr_regulator *cpr_vreg, int corner, + int new_apc_volt, enum voltage_change_dir dir) +{ + int rc = 0, vdd_mx_vmin = 0; + int mem_acc_corner = cpr_vreg->mem_acc_corner_map[corner]; + int fuse_corner = cpr_vreg->corner_map[corner]; + int apc_corner, vsens_corner; + + /* Determine the vdd_mx voltage */ + if (dir != NO_CHANGE && cpr_vreg->vdd_mx != NULL) + vdd_mx_vmin = cpr_mx_get(cpr_vreg, corner, new_apc_volt); + + + if (cpr_vreg->vdd_vsens_voltage && cpr_vreg->vsens_enabled) { + rc = regulator_disable(cpr_vreg->vdd_vsens_voltage); + if (!rc) + cpr_vreg->vsens_enabled = false; + } + + if (dir == DOWN) { + if (!rc && cpr_vreg->mem_acc_vreg) + rc = regulator_set_voltage(cpr_vreg->mem_acc_vreg, + mem_acc_corner, mem_acc_corner); + if (!rc && cpr_vreg->rpm_apc_vreg) { + apc_corner = cpr_vreg->rpm_apc_corner_map[corner]; + rc = regulator_set_voltage(cpr_vreg->rpm_apc_vreg, + apc_corner, apc_corner); + if (rc) + cpr_err(cpr_vreg, "apc_corner voting failed rc=%d\n", + rc); + } + } + + if (!rc && vdd_mx_vmin && dir == UP) { + if (vdd_mx_vmin != cpr_vreg->vdd_mx_vmin) + rc = cpr_mx_set(cpr_vreg, corner, vdd_mx_vmin); + } + + if (!rc) + rc = cpr_apc_set(cpr_vreg, new_apc_volt); + + if (dir == UP) { + if (!rc && cpr_vreg->mem_acc_vreg) + rc = regulator_set_voltage(cpr_vreg->mem_acc_vreg, + mem_acc_corner, mem_acc_corner); + if (!rc && cpr_vreg->rpm_apc_vreg) { + apc_corner = cpr_vreg->rpm_apc_corner_map[corner]; + rc = regulator_set_voltage(cpr_vreg->rpm_apc_vreg, + apc_corner, apc_corner); + if (rc) + cpr_err(cpr_vreg, "apc_corner voting failed rc=%d\n", + rc); + } + } + + if (!rc && vdd_mx_vmin && dir == DOWN) { + if (vdd_mx_vmin != cpr_vreg->vdd_mx_vmin) + rc = cpr_mx_set(cpr_vreg, corner, vdd_mx_vmin); + } + + if (!rc && cpr_vreg->vdd_vsens_corner) { + vsens_corner = cpr_vreg->vsens_corner_map[fuse_corner]; + rc = regulator_set_voltage(cpr_vreg->vdd_vsens_corner, + vsens_corner, vsens_corner); + } + if (!rc && cpr_vreg->vdd_vsens_voltage) { + rc = regulator_set_voltage(cpr_vreg->vdd_vsens_voltage, + cpr_vreg->floor_volt[corner], + cpr_vreg->ceiling_volt[corner]); + if (!rc && !cpr_vreg->vsens_enabled) { + rc = regulator_enable(cpr_vreg->vdd_vsens_voltage); + if (!rc) + cpr_vreg->vsens_enabled = true; + } + } + + return rc; +} + +static void cpr_scale(struct cpr_regulator *cpr_vreg, + enum voltage_change_dir dir) +{ + u32 reg_val, error_steps, reg_mask; + int last_volt, new_volt, corner, fuse_corner; + u32 gcnt, quot; + + corner = cpr_vreg->corner; + fuse_corner = cpr_vreg->corner_map[corner]; + + reg_val = cpr_read(cpr_vreg, REG_RBCPR_RESULT_0); + + error_steps = (reg_val >> RBCPR_RESULT0_ERROR_STEPS_SHIFT) + & RBCPR_RESULT0_ERROR_STEPS_MASK; + last_volt = cpr_vreg->last_volt[corner]; + + cpr_debug_irq(cpr_vreg, + "last_volt[corner:%d, fuse_corner:%d] = %d uV\n", + corner, fuse_corner, last_volt); + + gcnt = cpr_read(cpr_vreg, REG_RBCPR_GCNT_TARGET + (cpr_vreg->cpr_fuse_ro_sel[fuse_corner])); + quot = gcnt & ((1 << RBCPR_GCNT_TARGET_GCNT_SHIFT) - 1); + + if (dir == UP) { + if (cpr_vreg->clamp_timer_interval + && error_steps < cpr_vreg->up_threshold) { + /* + * Handle the case where another measurement started + * after the interrupt was triggered due to a core + * exiting from power collapse. + */ + error_steps = max(cpr_vreg->up_threshold, + cpr_vreg->vdd_apc_step_up_limit); + } + cpr_debug_irq(cpr_vreg, + "Up: cpr status = 0x%08x (error_steps=%d)\n", + reg_val, error_steps); + + if (last_volt >= cpr_vreg->ceiling_volt[corner]) { + cpr_debug_irq(cpr_vreg, + "[corn:%d, fuse_corn:%d] @ ceiling: %d >= %d: NACK\n", + corner, fuse_corner, last_volt, + cpr_vreg->ceiling_volt[corner]); + cpr_irq_clr_nack(cpr_vreg); + + cpr_debug_irq(cpr_vreg, "gcnt = 0x%08x (quot = %d)\n", + gcnt, quot); + + /* Maximize the UP threshold */ + reg_mask = RBCPR_CTL_UP_THRESHOLD_MASK << + RBCPR_CTL_UP_THRESHOLD_SHIFT; + reg_val = reg_mask; + cpr_ctl_modify(cpr_vreg, reg_mask, reg_val); + + /* Disable UP interrupt */ + cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT & ~CPR_INT_UP); + + return; + } + + if (error_steps > cpr_vreg->vdd_apc_step_up_limit) { + cpr_debug_irq(cpr_vreg, + "%d is over up-limit(%d): Clamp\n", + error_steps, + cpr_vreg->vdd_apc_step_up_limit); + error_steps = cpr_vreg->vdd_apc_step_up_limit; + } + + /* Calculate new voltage */ + new_volt = last_volt + (error_steps * cpr_vreg->step_volt); + if (new_volt > cpr_vreg->ceiling_volt[corner]) { + cpr_debug_irq(cpr_vreg, + "new_volt(%d) >= ceiling(%d): Clamp\n", + new_volt, + cpr_vreg->ceiling_volt[corner]); + + new_volt = cpr_vreg->ceiling_volt[corner]; + } + + if (cpr_scale_voltage(cpr_vreg, corner, new_volt, dir)) { + cpr_irq_clr_nack(cpr_vreg); + return; + } + cpr_vreg->last_volt[corner] = new_volt; + + /* Disable auto nack down */ + reg_mask = RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN; + reg_val = 0; + + cpr_ctl_modify(cpr_vreg, reg_mask, reg_val); + + /* Re-enable default interrupts */ + cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT); + + /* Ack */ + cpr_irq_clr_ack(cpr_vreg); + + cpr_debug_irq(cpr_vreg, + "UP: -> new_volt[corner:%d, fuse_corner:%d] = %d uV\n", + corner, fuse_corner, new_volt); + } else if (dir == DOWN) { + if (cpr_vreg->clamp_timer_interval + && error_steps < cpr_vreg->down_threshold) { + /* + * Handle the case where another measurement started + * after the interrupt was triggered due to a core + * exiting from power collapse. + */ + error_steps = max(cpr_vreg->down_threshold, + cpr_vreg->vdd_apc_step_down_limit); + } + cpr_debug_irq(cpr_vreg, + "Down: cpr status = 0x%08x (error_steps=%d)\n", + reg_val, error_steps); + + if (last_volt <= cpr_vreg->floor_volt[corner]) { + cpr_debug_irq(cpr_vreg, + "[corn:%d, fuse_corner:%d] @ floor: %d <= %d: NACK\n", + corner, fuse_corner, last_volt, + cpr_vreg->floor_volt[corner]); + cpr_irq_clr_nack(cpr_vreg); + + cpr_debug_irq(cpr_vreg, "gcnt = 0x%08x (quot = %d)\n", + gcnt, quot); + + /* Enable auto nack down */ + reg_mask = RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN; + reg_val = RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN; + + cpr_ctl_modify(cpr_vreg, reg_mask, reg_val); + + /* Disable DOWN interrupt */ + cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT & ~CPR_INT_DOWN); + + return; + } + + if (error_steps > cpr_vreg->vdd_apc_step_down_limit) { + cpr_debug_irq(cpr_vreg, + "%d is over down-limit(%d): Clamp\n", + error_steps, + cpr_vreg->vdd_apc_step_down_limit); + error_steps = cpr_vreg->vdd_apc_step_down_limit; + } + + /* Calculte new voltage */ + new_volt = last_volt - (error_steps * cpr_vreg->step_volt); + if (new_volt < cpr_vreg->floor_volt[corner]) { + cpr_debug_irq(cpr_vreg, + "new_volt(%d) < floor(%d): Clamp\n", + new_volt, + cpr_vreg->floor_volt[corner]); + new_volt = cpr_vreg->floor_volt[corner]; + } + + if (cpr_scale_voltage(cpr_vreg, corner, new_volt, dir)) { + cpr_irq_clr_nack(cpr_vreg); + return; + } + cpr_vreg->last_volt[corner] = new_volt; + + /* Restore default threshold for UP */ + reg_mask = RBCPR_CTL_UP_THRESHOLD_MASK << + RBCPR_CTL_UP_THRESHOLD_SHIFT; + reg_val = cpr_vreg->up_threshold << + RBCPR_CTL_UP_THRESHOLD_SHIFT; + cpr_ctl_modify(cpr_vreg, reg_mask, reg_val); + + /* Re-enable default interrupts */ + cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT); + + /* Ack */ + cpr_irq_clr_ack(cpr_vreg); + + cpr_debug_irq(cpr_vreg, + "DOWN: -> new_volt[corner:%d, fuse_corner:%d] = %d uV\n", + corner, fuse_corner, new_volt); + } +} + +static irqreturn_t cpr_irq_handler(int irq, void *dev) +{ + struct cpr_regulator *cpr_vreg = dev; + u32 reg_val; + + mutex_lock(&cpr_vreg->cpr_mutex); + + reg_val = cpr_read(cpr_vreg, REG_RBIF_IRQ_STATUS); + if (cpr_vreg->flags & FLAGS_IGNORE_1ST_IRQ_STATUS) + reg_val = cpr_read(cpr_vreg, REG_RBIF_IRQ_STATUS); + + cpr_debug_irq(cpr_vreg, "IRQ_STATUS = 0x%02X\n", reg_val); + + if (!cpr_ctl_is_enabled(cpr_vreg)) { + cpr_debug_irq(cpr_vreg, "CPR is disabled\n"); + goto _exit; + } else if (cpr_ctl_is_busy(cpr_vreg) + && !cpr_vreg->clamp_timer_interval) { + cpr_debug_irq(cpr_vreg, "CPR measurement is not ready\n"); + goto _exit; + } else if (!cpr_is_allowed(cpr_vreg)) { + reg_val = cpr_read(cpr_vreg, REG_RBCPR_CTL); + cpr_err(cpr_vreg, "Interrupt broken? RBCPR_CTL = 0x%02X\n", + reg_val); + goto _exit; + } + + /* Following sequence of handling is as per each IRQ's priority */ + if (reg_val & CPR_INT_UP) { + cpr_scale(cpr_vreg, UP); + } else if (reg_val & CPR_INT_DOWN) { + cpr_scale(cpr_vreg, DOWN); + } else if (reg_val & CPR_INT_MIN) { + cpr_irq_clr_nack(cpr_vreg); + } else if (reg_val & CPR_INT_MAX) { + cpr_irq_clr_nack(cpr_vreg); + } else if (reg_val & CPR_INT_MID) { + /* RBCPR_CTL_SW_AUTO_CONT_ACK_EN is enabled */ + cpr_debug_irq(cpr_vreg, "IRQ occurred for Mid Flag\n"); + } else { + cpr_debug_irq(cpr_vreg, + "IRQ occurred for unknown flag (0x%08x)\n", reg_val); + } + + /* Save register values for the corner */ + cpr_corner_save(cpr_vreg, cpr_vreg->corner); + +_exit: + mutex_unlock(&cpr_vreg->cpr_mutex); + return IRQ_HANDLED; +} + +/** + * cmp_int() - int comparison function to be passed into the sort() function + * which leads to ascending sorting + * @a: First int value + * @b: Second int value + * + * Return: >0 if a > b, 0 if a == b, <0 if a < b + */ +static int cmp_int(const void *a, const void *b) +{ + return *(int *)a - *(int *)b; +} + +static int cpr_get_aging_quot_delta(struct cpr_regulator *cpr_vreg, + struct cpr_aging_sensor_info *aging_sensor_info) +{ + int quot_min, quot_max, is_aging_measurement, aging_measurement_count; + int quot_min_scaled, quot_max_scaled, quot_delta_scaled_sum; + int retries, rc = 0, sel_fast = 0, i, quot_delta_scaled; + u32 val, gcnt_ref, gcnt; + int *quot_delta_results, filtered_count; + + + quot_delta_results = kcalloc(CPR_AGING_MEASUREMENT_ITERATIONS, + sizeof(*quot_delta_results), GFP_ATOMIC); + if (!quot_delta_results) + return -ENOMEM; + + /* Clear the target quotient value and gate count of all ROs */ + for (i = 0; i < CPR_NUM_RING_OSC; i++) + cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(i), 0); + + /* Program GCNT0/1 for getting aging data */ + gcnt_ref = (cpr_vreg->ref_clk_khz * cpr_vreg->gcnt_time_us) / 1000; + gcnt = gcnt_ref * 3 / 2; + val = (gcnt & RBCPR_GCNT_TARGET_GCNT_MASK) << + RBCPR_GCNT_TARGET_GCNT_SHIFT; + cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(0), val); + cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(1), val); + + val = cpr_read(cpr_vreg, REG_RBCPR_GCNT_TARGET(0)); + cpr_debug(cpr_vreg, "RBCPR_GCNT_TARGET0 = 0x%08x\n", val); + + val = cpr_read(cpr_vreg, REG_RBCPR_GCNT_TARGET(1)); + cpr_debug(cpr_vreg, "RBCPR_GCNT_TARGET1 = 0x%08x\n", val); + + /* Program TIMER_INTERVAL to zero */ + cpr_write(cpr_vreg, REG_RBCPR_TIMER_INTERVAL, 0); + + /* Bypass sensors in collapsible domain */ + if (cpr_vreg->aging_info->aging_sensor_bypass) + cpr_write(cpr_vreg, REG_RBCPR_SENSOR_BYPASS0, + (cpr_vreg->aging_info->aging_sensor_bypass & + RBCPR_SENSOR_MASK0_SENSOR(aging_sensor_info->sensor_id))); + + /* Mask other sensors */ + cpr_write(cpr_vreg, REG_RBCPR_SENSOR_MASK0, + RBCPR_SENSOR_MASK0_SENSOR(aging_sensor_info->sensor_id)); + val = cpr_read(cpr_vreg, REG_RBCPR_SENSOR_MASK0); + cpr_debug(cpr_vreg, "RBCPR_SENSOR_MASK0 = 0x%08x\n", val); + + /* Enable cpr controller */ + cpr_ctl_modify(cpr_vreg, RBCPR_CTL_LOOP_EN, RBCPR_CTL_LOOP_EN); + + /* Make sure cpr starts measurement with toggling busy bit */ + mb(); + + /* Wait and Ignore the first measurement. Time-out after 5ms */ + retries = 50; + while (retries-- && cpr_ctl_is_busy(cpr_vreg)) + udelay(100); + + if (retries < 0) { + cpr_err(cpr_vreg, "Aging calibration failed\n"); + rc = -EBUSY; + goto _exit; + } + + /* Set age page mode */ + cpr_write(cpr_vreg, REG_RBCPR_HTOL_AGE, RBCPR_HTOL_AGE_PAGE); + + aging_measurement_count = 0; + quot_delta_scaled_sum = 0; + + for (i = 0; i < CPR_AGING_MEASUREMENT_ITERATIONS; i++) { + /* Send cont nack */ + cpr_write(cpr_vreg, REG_RBIF_CONT_NACK_CMD, 1); + + /* + * Make sure cpr starts next measurement with + * toggling busy bit + */ + mb(); + + /* + * Wait for controller to finish measurement + * and time-out after 5ms + */ + retries = 50; + while (retries-- && cpr_ctl_is_busy(cpr_vreg)) + udelay(100); + + if (retries < 0) { + cpr_err(cpr_vreg, "Aging calibration failed\n"); + rc = -EBUSY; + goto _exit; + } + + /* Check for PAGE_IS_AGE flag in status register */ + val = cpr_read(cpr_vreg, REG_RBCPR_HTOL_AGE); + is_aging_measurement = val & RBCPR_AGE_DATA_STATUS; + + val = cpr_read(cpr_vreg, REG_RBCPR_RESULT_1); + sel_fast = RBCPR_RESULT_1_SEL_FAST(val); + cpr_debug(cpr_vreg, "RBCPR_RESULT_1 = 0x%08x\n", val); + + val = cpr_read(cpr_vreg, REG_RBCPR_DEBUG1); + cpr_debug(cpr_vreg, "RBCPR_DEBUG1 = 0x%08x\n", val); + + if (sel_fast == 1) { + quot_min = RBCPR_DEBUG1_QUOT_FAST(val); + quot_max = RBCPR_DEBUG1_QUOT_SLOW(val); + } else { + quot_min = RBCPR_DEBUG1_QUOT_SLOW(val); + quot_max = RBCPR_DEBUG1_QUOT_FAST(val); + } + + /* + * Scale the quotients so that they are equivalent to the fused + * values. This accounts for the difference in measurement + * interval times. + */ + + quot_min_scaled = quot_min * (gcnt_ref + 1) / (gcnt + 1); + quot_max_scaled = quot_max * (gcnt_ref + 1) / (gcnt + 1); + + quot_delta_scaled = 0; + if (is_aging_measurement) { + quot_delta_scaled = quot_min_scaled - quot_max_scaled; + quot_delta_results[aging_measurement_count++] = + quot_delta_scaled; + } + + cpr_debug(cpr_vreg, + "Age sensor[%d]: measurement[%d]: page_is_age=%u quot_min = %d, quot_max = %d quot_min_scaled = %d, quot_max_scaled = %d quot_delta_scaled = %d\n", + aging_sensor_info->sensor_id, i, is_aging_measurement, + quot_min, quot_max, quot_min_scaled, quot_max_scaled, + quot_delta_scaled); + } + + filtered_count + = aging_measurement_count - CPR_AGING_MEASUREMENT_FILTER * 2; + if (filtered_count > 0) { + sort(quot_delta_results, aging_measurement_count, + sizeof(*quot_delta_results), cmp_int, NULL); + + quot_delta_scaled_sum = 0; + for (i = 0; i < filtered_count; i++) + quot_delta_scaled_sum + += quot_delta_results[i + + CPR_AGING_MEASUREMENT_FILTER]; + + aging_sensor_info->current_quot_diff + = quot_delta_scaled_sum / filtered_count; + cpr_debug(cpr_vreg, + "Age sensor[%d]: average aging quotient delta = %d (count = %d)\n", + aging_sensor_info->sensor_id, + aging_sensor_info->current_quot_diff, filtered_count); + } else { + cpr_err(cpr_vreg, "%d aging measurements completed after %d iterations\n", + aging_measurement_count, + CPR_AGING_MEASUREMENT_ITERATIONS); + rc = -EBUSY; + } + +_exit: + /* Clear age page bit */ + cpr_write(cpr_vreg, REG_RBCPR_HTOL_AGE, 0x0); + + /* Disable the CPR controller after aging procedure */ + cpr_ctl_modify(cpr_vreg, RBCPR_CTL_LOOP_EN, 0x0); + + /* Clear the sensor bypass */ + if (cpr_vreg->aging_info->aging_sensor_bypass) + cpr_write(cpr_vreg, REG_RBCPR_SENSOR_BYPASS0, 0x0); + + /* Unmask all sensors */ + cpr_write(cpr_vreg, REG_RBCPR_SENSOR_MASK0, 0x0); + + /* Clear gcnt0/1 registers */ + cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(0), 0x0); + cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(1), 0x0); + + /* Program the delay count for the timer */ + val = (cpr_vreg->ref_clk_khz * cpr_vreg->timer_delay_us) / 1000; + cpr_write(cpr_vreg, REG_RBCPR_TIMER_INTERVAL, val); + + kfree(quot_delta_results); + + return rc; +} + +static void cpr_de_aging_adjustment(void *data) +{ + struct cpr_regulator *cpr_vreg = (struct cpr_regulator *)data; + struct cpr_aging_info *aging_info = cpr_vreg->aging_info; + struct cpr_aging_sensor_info *aging_sensor_info; + int i, num_aging_sensors, retries, rc = 0; + int max_quot_diff = 0, ro_sel = 0; + u32 voltage_adjust, aging_voltage_adjust = 0; + + aging_sensor_info = aging_info->sensor_info; + num_aging_sensors = aging_info->num_aging_sensors; + + for (i = 0; i < num_aging_sensors; i++, aging_sensor_info++) { + retries = 2; + while (retries--) { + rc = cpr_get_aging_quot_delta(cpr_vreg, + aging_sensor_info); + if (!rc) + break; + } + if (rc && retries < 0) { + cpr_err(cpr_vreg, "error in age calibration: rc = %d\n", + rc); + aging_info->cpr_aging_error = true; + return; + } + + max_quot_diff = max(max_quot_diff, + (aging_sensor_info->current_quot_diff - + aging_sensor_info->initial_quot_diff)); + } + + cpr_debug(cpr_vreg, "Max aging quot delta = %d\n", + max_quot_diff); + aging_voltage_adjust = DIV_ROUND_UP(max_quot_diff * 1000000, + aging_info->aging_ro_kv); + + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) { + /* Remove initial max aging adjustment */ + ro_sel = cpr_vreg->cpr_fuse_ro_sel[i]; + cpr_vreg->cpr_fuse_target_quot[i] -= + (aging_info->cpr_ro_kv[ro_sel] + * aging_info->max_aging_margin) / 1000000; + aging_info->voltage_adjust[i] = 0; + + if (aging_voltage_adjust > 0) { + /* Add required aging adjustment */ + voltage_adjust = (aging_voltage_adjust + * aging_info->aging_derate[i]) / 1000; + voltage_adjust = min(voltage_adjust, + aging_info->max_aging_margin); + cpr_vreg->cpr_fuse_target_quot[i] += + (aging_info->cpr_ro_kv[ro_sel] + * voltage_adjust) / 1000000; + aging_info->voltage_adjust[i] = voltage_adjust; + } + } +} + +static int cpr_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev); + + return cpr_vreg->vreg_enabled; +} + +static int cpr_regulator_enable(struct regulator_dev *rdev) +{ + struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev); + int rc = 0; + + /* Enable dependency power before vdd_apc */ + if (cpr_vreg->vdd_mx) { + rc = regulator_enable(cpr_vreg->vdd_mx); + if (rc) { + cpr_err(cpr_vreg, "regulator_enable: vdd_mx: rc=%d\n", + rc); + return rc; + } + } + + rc = regulator_enable(cpr_vreg->vdd_apc); + if (rc) { + cpr_err(cpr_vreg, "regulator_enable: vdd_apc: rc=%d\n", rc); + return rc; + } + + mutex_lock(&cpr_vreg->cpr_mutex); + cpr_vreg->vreg_enabled = true; + if (cpr_is_allowed(cpr_vreg) && cpr_vreg->corner) { + cpr_irq_clr(cpr_vreg); + cpr_corner_restore(cpr_vreg, cpr_vreg->corner); + cpr_ctl_enable(cpr_vreg, cpr_vreg->corner); + } + mutex_unlock(&cpr_vreg->cpr_mutex); + + return rc; +} + +static int cpr_regulator_disable(struct regulator_dev *rdev) +{ + struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev); + int rc; + + rc = regulator_disable(cpr_vreg->vdd_apc); + if (!rc) { + if (cpr_vreg->vdd_mx) + rc = regulator_disable(cpr_vreg->vdd_mx); + + if (rc) { + cpr_err(cpr_vreg, "regulator_disable: vdd_mx: rc=%d\n", + rc); + return rc; + } + + mutex_lock(&cpr_vreg->cpr_mutex); + cpr_vreg->vreg_enabled = false; + if (cpr_is_allowed(cpr_vreg)) + cpr_ctl_disable(cpr_vreg); + mutex_unlock(&cpr_vreg->cpr_mutex); + } else { + cpr_err(cpr_vreg, "regulator_disable: vdd_apc: rc=%d\n", rc); + } + + return rc; +} + +static int cpr_calculate_de_aging_margin(struct cpr_regulator *cpr_vreg) +{ + struct cpr_aging_info *aging_info = cpr_vreg->aging_info; + enum voltage_change_dir change_dir = NO_CHANGE; + u32 save_ctl, save_irq; + cpumask_t tmp_mask; + int rc = 0, i; + unsigned int current_mode; + + save_ctl = cpr_read(cpr_vreg, REG_RBCPR_CTL); + save_irq = cpr_read(cpr_vreg, REG_RBIF_IRQ_EN(cpr_vreg->irq_line)); + + /* Disable interrupt and CPR */ + cpr_irq_set(cpr_vreg, 0); + cpr_write(cpr_vreg, REG_RBCPR_CTL, 0); + + if (aging_info->aging_corner > cpr_vreg->corner) + change_dir = UP; + else if (aging_info->aging_corner < cpr_vreg->corner) + change_dir = DOWN; + + /* set selected reference voltage for de-aging */ + rc = cpr_scale_voltage(cpr_vreg, + aging_info->aging_corner, + aging_info->aging_ref_voltage, + change_dir); + if (rc) { + cpr_err(cpr_vreg, "Unable to set aging reference voltage, rc = %d\n", + rc); + return rc; + } + + current_mode = regulator_get_mode(cpr_vreg->vdd_apc); + if (current_mode < 0) { + cpr_err(cpr_vreg, "Failed to get vdd-supply mode, error=%d\n", + current_mode); + return current_mode; + } + + /* Force PWM mode */ + rc = regulator_set_mode(cpr_vreg->vdd_apc, REGULATOR_MODE_NORMAL); + if (rc) { + cpr_err(cpr_vreg, "unable to configure vdd-supply for mode=%u, rc=%d\n", + REGULATOR_MODE_NORMAL, rc); + return rc; + } + + get_online_cpus(); + cpumask_and(&tmp_mask, &cpr_vreg->cpu_mask, cpu_online_mask); + if (!cpumask_empty(&tmp_mask)) { + smp_call_function_any(&tmp_mask, + cpr_de_aging_adjustment, + cpr_vreg, true); + aging_info->cpr_aging_done = true; + if (!aging_info->cpr_aging_error) + for (i = CPR_FUSE_CORNER_MIN; + i <= cpr_vreg->num_fuse_corners; i++) + cpr_info(cpr_vreg, "Corner[%d]: age adjusted target quot = %d\n", + i, cpr_vreg->cpr_fuse_target_quot[i]); + } + + put_online_cpus(); + + /* Set to initial mode */ + rc = regulator_set_mode(cpr_vreg->vdd_apc, current_mode); + if (rc) { + cpr_err(cpr_vreg, "unable to configure vdd-supply for mode=%u, rc=%d\n", + current_mode, rc); + return rc; + } + + /* Clear interrupts */ + cpr_irq_clr(cpr_vreg); + + /* Restore register values */ + cpr_irq_set(cpr_vreg, save_irq); + cpr_write(cpr_vreg, REG_RBCPR_CTL, save_ctl); + + return rc; +} + +/* Note that cpr_vreg->cpr_mutex must be held by the caller. */ +static int cpr_regulator_set_voltage(struct regulator_dev *rdev, + int corner, bool reset_quot) +{ + struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev); + struct cpr_aging_info *aging_info = cpr_vreg->aging_info; + int rc; + int new_volt; + enum voltage_change_dir change_dir = NO_CHANGE; + int fuse_corner = cpr_vreg->corner_map[corner]; + + if (cpr_is_allowed(cpr_vreg)) { + cpr_ctl_disable(cpr_vreg); + new_volt = cpr_vreg->last_volt[corner]; + } else { + new_volt = cpr_vreg->open_loop_volt[corner]; + } + + cpr_debug(cpr_vreg, "[corner:%d, fuse_corner:%d] = %d uV\n", + corner, fuse_corner, new_volt); + + if (corner > cpr_vreg->corner) + change_dir = UP; + else if (corner < cpr_vreg->corner) + change_dir = DOWN; + + /* Read age sensor data and apply de-aging adjustments */ + if (cpr_vreg->vreg_enabled && aging_info && !aging_info->cpr_aging_done + && (corner <= aging_info->aging_corner)) { + rc = cpr_calculate_de_aging_margin(cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "failed in de-aging calibration: rc=%d\n", + rc); + } else { + change_dir = NO_CHANGE; + if (corner > aging_info->aging_corner) + change_dir = UP; + else if (corner < aging_info->aging_corner) + change_dir = DOWN; + } + reset_quot = true; + } + + rc = cpr_scale_voltage(cpr_vreg, corner, new_volt, change_dir); + if (rc) + return rc; + + if (cpr_vreg->vdd_mode_map) { + rc = regulator_set_mode(cpr_vreg->vdd_apc, + cpr_vreg->vdd_mode_map[corner]); + if (rc) { + cpr_err(cpr_vreg, "unable to configure vdd-supply for mode=%u, rc=%d\n", + cpr_vreg->vdd_mode_map[corner], rc); + return rc; + } + } + + + if (cpr_is_allowed(cpr_vreg) && cpr_vreg->vreg_enabled) { + cpr_irq_clr(cpr_vreg); + if (reset_quot) + cpr_corner_restore(cpr_vreg, corner); + else + cpr_corner_switch(cpr_vreg, corner); + cpr_ctl_enable(cpr_vreg, corner); + } + + cpr_vreg->corner = corner; + + return rc; +} + +static int cpr_regulator_set_voltage_op(struct regulator_dev *rdev, + int corner, int corner_max, unsigned int *selector) +{ + struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev); + int rc; + + mutex_lock(&cpr_vreg->cpr_mutex); + rc = cpr_regulator_set_voltage(rdev, corner, false); + mutex_unlock(&cpr_vreg->cpr_mutex); + + return rc; +} + +static int cpr_regulator_get_voltage(struct regulator_dev *rdev) +{ + struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev); + + return cpr_vreg->corner; +} + +/** + * cpr_regulator_list_corner_voltage() - return the ceiling voltage mapped to + * the specified voltage corner + * @rdev: Regulator device pointer for the cpr-regulator + * @corner: Voltage corner + * + * This function is passed as a callback function into the regulator ops that + * are registered for each cpr-regulator device. + * + * Return: voltage value in microvolts or -EINVAL if the corner is out of range + */ +static int cpr_regulator_list_corner_voltage(struct regulator_dev *rdev, + int corner) +{ + struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev); + + if (corner >= CPR_CORNER_MIN && corner <= cpr_vreg->num_corners) + return cpr_vreg->ceiling_volt[corner]; + else + return -EINVAL; +} + +static struct regulator_ops cpr_corner_ops = { + .enable = cpr_regulator_enable, + .disable = cpr_regulator_disable, + .is_enabled = cpr_regulator_is_enabled, + .set_voltage = cpr_regulator_set_voltage_op, + .get_voltage = cpr_regulator_get_voltage, + .list_corner_voltage = cpr_regulator_list_corner_voltage, +}; + +#ifdef CONFIG_PM +static int cpr_suspend(struct cpr_regulator *cpr_vreg) +{ + cpr_debug(cpr_vreg, "suspend\n"); + + cpr_ctl_disable(cpr_vreg); + + cpr_irq_clr(cpr_vreg); + + return 0; +} + +static int cpr_resume(struct cpr_regulator *cpr_vreg) + +{ + cpr_debug(cpr_vreg, "resume\n"); + + cpr_irq_clr(cpr_vreg); + + cpr_ctl_enable(cpr_vreg, cpr_vreg->corner); + + return 0; +} + +static int cpr_regulator_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct cpr_regulator *cpr_vreg = platform_get_drvdata(pdev); + int rc = 0; + + mutex_lock(&cpr_vreg->cpr_mutex); + + if (cpr_is_allowed(cpr_vreg)) + rc = cpr_suspend(cpr_vreg); + + cpr_vreg->is_cpr_suspended = true; + + mutex_unlock(&cpr_vreg->cpr_mutex); + + return rc; +} + +static int cpr_regulator_resume(struct platform_device *pdev) +{ + struct cpr_regulator *cpr_vreg = platform_get_drvdata(pdev); + int rc = 0; + + mutex_lock(&cpr_vreg->cpr_mutex); + + cpr_vreg->is_cpr_suspended = false; + + if (cpr_is_allowed(cpr_vreg)) + rc = cpr_resume(cpr_vreg); + + mutex_unlock(&cpr_vreg->cpr_mutex); + + return rc; +} +#else +#define cpr_regulator_suspend NULL +#define cpr_regulator_resume NULL +#endif + +static int cpr_config(struct cpr_regulator *cpr_vreg, struct device *dev) +{ + int i; + u32 val, gcnt, reg; + void __iomem *rbcpr_clk; + int size; + + if (cpr_vreg->rbcpr_clk_addr) { + /* Use 19.2 MHz clock for CPR. */ + rbcpr_clk = ioremap(cpr_vreg->rbcpr_clk_addr, 4); + if (!rbcpr_clk) { + cpr_err(cpr_vreg, "Unable to map rbcpr_clk\n"); + return -EINVAL; + } + reg = readl_relaxed(rbcpr_clk); + reg &= ~RBCPR_CLK_SEL_MASK; + reg |= RBCPR_CLK_SEL_19P2_MHZ & RBCPR_CLK_SEL_MASK; + writel_relaxed(reg, rbcpr_clk); + iounmap(rbcpr_clk); + } + + /* Disable interrupt and CPR */ + cpr_write(cpr_vreg, REG_RBIF_IRQ_EN(cpr_vreg->irq_line), 0); + cpr_write(cpr_vreg, REG_RBCPR_CTL, 0); + + /* Program the default HW Ceiling, Floor and vlevel */ + val = ((RBIF_LIMIT_CEILING_DEFAULT & RBIF_LIMIT_CEILING_MASK) + << RBIF_LIMIT_CEILING_SHIFT) + | (RBIF_LIMIT_FLOOR_DEFAULT & RBIF_LIMIT_FLOOR_MASK); + cpr_write(cpr_vreg, REG_RBIF_LIMIT, val); + cpr_write(cpr_vreg, REG_RBIF_SW_VLEVEL, RBIF_SW_VLEVEL_DEFAULT); + + /* Clear the target quotient value and gate count of all ROs */ + for (i = 0; i < CPR_NUM_RING_OSC; i++) + cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(i), 0); + + /* Init and save gcnt */ + gcnt = (cpr_vreg->ref_clk_khz * cpr_vreg->gcnt_time_us) / 1000; + gcnt = (gcnt & RBCPR_GCNT_TARGET_GCNT_MASK) << + RBCPR_GCNT_TARGET_GCNT_SHIFT; + cpr_vreg->gcnt = gcnt; + + /* Program the delay count for the timer */ + val = (cpr_vreg->ref_clk_khz * cpr_vreg->timer_delay_us) / 1000; + cpr_write(cpr_vreg, REG_RBCPR_TIMER_INTERVAL, val); + cpr_info(cpr_vreg, "Timer count: 0x%0x (for %d us)\n", val, + cpr_vreg->timer_delay_us); + + /* Program Consecutive Up & Down */ + val = ((cpr_vreg->timer_cons_down & RBIF_TIMER_ADJ_CONS_DOWN_MASK) + << RBIF_TIMER_ADJ_CONS_DOWN_SHIFT) | + (cpr_vreg->timer_cons_up & RBIF_TIMER_ADJ_CONS_UP_MASK) | + ((cpr_vreg->clamp_timer_interval & RBIF_TIMER_ADJ_CLAMP_INT_MASK) + << RBIF_TIMER_ADJ_CLAMP_INT_SHIFT); + cpr_write(cpr_vreg, REG_RBIF_TIMER_ADJUST, val); + + /* Program the control register */ + cpr_vreg->up_threshold &= RBCPR_CTL_UP_THRESHOLD_MASK; + cpr_vreg->down_threshold &= RBCPR_CTL_DN_THRESHOLD_MASK; + val = (cpr_vreg->up_threshold << RBCPR_CTL_UP_THRESHOLD_SHIFT) + | (cpr_vreg->down_threshold << RBCPR_CTL_DN_THRESHOLD_SHIFT); + val |= RBCPR_CTL_TIMER_EN | RBCPR_CTL_COUNT_MODE; + val |= RBCPR_CTL_SW_AUTO_CONT_ACK_EN; + cpr_write(cpr_vreg, REG_RBCPR_CTL, val); + + cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT); + + val = cpr_read(cpr_vreg, REG_RBCPR_VERSION); + if (val <= RBCPR_VER_2) + cpr_vreg->flags |= FLAGS_IGNORE_1ST_IRQ_STATUS; + + size = cpr_vreg->num_corners + 1; + cpr_vreg->save_ctl = devm_kzalloc(dev, sizeof(int) * size, GFP_KERNEL); + cpr_vreg->save_irq = devm_kzalloc(dev, sizeof(int) * size, GFP_KERNEL); + if (!cpr_vreg->save_ctl || !cpr_vreg->save_irq) + return -ENOMEM; + + for (i = 1; i < size; i++) + cpr_corner_save(cpr_vreg, i); + + return 0; +} + +static int cpr_fuse_is_setting_expected(struct cpr_regulator *cpr_vreg, + u32 sel_array[5]) +{ + u64 fuse_bits; + u32 ret; + + fuse_bits = cpr_read_efuse_row(cpr_vreg, sel_array[0], sel_array[4]); + ret = (fuse_bits >> sel_array[1]) & ((1 << sel_array[2]) - 1); + if (ret == sel_array[3]) + ret = 1; + else + ret = 0; + + cpr_info(cpr_vreg, "[row:%d] = 0x%llx @%d:%d == %d ?: %s\n", + sel_array[0], fuse_bits, + sel_array[1], sel_array[2], + sel_array[3], + (ret == 1) ? "yes" : "no"); + return ret; +} + +static int cpr_voltage_uplift_wa_inc_volt(struct cpr_regulator *cpr_vreg, + struct device_node *of_node) +{ + u32 uplift_voltage; + u32 uplift_max_volt = 0; + int highest_fuse_corner = cpr_vreg->num_fuse_corners; + int rc; + + rc = of_property_read_u32(of_node, + "qcom,cpr-uplift-voltage", &uplift_voltage); + if (rc < 0) { + cpr_err(cpr_vreg, "cpr-uplift-voltage is missing, rc = %d", rc); + return rc; + } + rc = of_property_read_u32(of_node, + "qcom,cpr-uplift-max-volt", &uplift_max_volt); + if (rc < 0) { + cpr_err(cpr_vreg, "cpr-uplift-max-volt is missing, rc = %d", + rc); + return rc; + } + + cpr_vreg->pvs_corner_v[highest_fuse_corner] += uplift_voltage; + if (cpr_vreg->pvs_corner_v[highest_fuse_corner] > uplift_max_volt) + cpr_vreg->pvs_corner_v[highest_fuse_corner] = uplift_max_volt; + + return rc; +} + +static int cpr_adjust_init_voltages(struct device_node *of_node, + struct cpr_regulator *cpr_vreg) +{ + int tuple_count, tuple_match, i; + u32 index; + u32 volt_adjust = 0; + int len = 0; + int rc = 0; + + if (!of_find_property(of_node, "qcom,cpr-init-voltage-adjustment", + &len)) { + /* No initial voltage adjustment needed. */ + return 0; + } + + if (cpr_vreg->cpr_fuse_map_count) { + if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) { + /* + * No matching index to use for initial voltage + * adjustment. + */ + return 0; + } + tuple_count = cpr_vreg->cpr_fuse_map_count; + tuple_match = cpr_vreg->cpr_fuse_map_match; + } else { + tuple_count = 1; + tuple_match = 0; + } + + if (len != cpr_vreg->num_fuse_corners * tuple_count * sizeof(u32)) { + cpr_err(cpr_vreg, "qcom,cpr-init-voltage-adjustment length=%d is invalid\n", + len); + return -EINVAL; + } + + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) { + index = tuple_match * cpr_vreg->num_fuse_corners + + i - CPR_FUSE_CORNER_MIN; + rc = of_property_read_u32_index(of_node, + "qcom,cpr-init-voltage-adjustment", index, + &volt_adjust); + if (rc) { + cpr_err(cpr_vreg, "could not read qcom,cpr-init-voltage-adjustment index %u, rc=%d\n", + index, rc); + return rc; + } + + if (volt_adjust) { + cpr_vreg->pvs_corner_v[i] += volt_adjust; + cpr_info(cpr_vreg, "adjusted initial voltage[%d]: %d -> %d uV\n", + i, cpr_vreg->pvs_corner_v[i] - volt_adjust, + cpr_vreg->pvs_corner_v[i]); + } + } + + return rc; +} + +/* + * Property qcom,cpr-fuse-init-voltage specifies the fuse position of the + * initial voltage for each fuse corner. MSB of the fuse value is a sign + * bit, and the remaining bits define the steps of the offset. Each step has + * units of microvolts defined in the qcom,cpr-fuse-init-voltage-step property. + * The initial voltages can be calculated using the formula: + * pvs_corner_v[corner] = ceiling_volt[corner] + (sign * steps * step_size_uv) + */ +static int cpr_pvs_per_corner_init(struct device_node *of_node, + struct cpr_regulator *cpr_vreg) +{ + u64 efuse_bits; + int i, size, sign, steps, step_size_uv, rc; + u32 *fuse_sel, *tmp, *ref_uv; + struct property *prop; + char *init_volt_str; + + init_volt_str = cpr_vreg->cpr_fuse_redundant + ? "qcom,cpr-fuse-redun-init-voltage" + : "qcom,cpr-fuse-init-voltage"; + + prop = of_find_property(of_node, init_volt_str, NULL); + if (!prop) { + cpr_err(cpr_vreg, "%s is missing\n", init_volt_str); + return -EINVAL; + } + size = prop->length / sizeof(u32); + if (size != cpr_vreg->num_fuse_corners * 4) { + cpr_err(cpr_vreg, + "fuse position for init voltages is invalid\n"); + return -EINVAL; + } + fuse_sel = kzalloc(sizeof(u32) * size, GFP_KERNEL); + if (!fuse_sel) + return -ENOMEM; + rc = of_property_read_u32_array(of_node, init_volt_str, + fuse_sel, size); + if (rc < 0) { + cpr_err(cpr_vreg, + "read cpr-fuse-init-voltage failed, rc = %d\n", rc); + kfree(fuse_sel); + return rc; + } + rc = of_property_read_u32(of_node, "qcom,cpr-init-voltage-step", + &step_size_uv); + if (rc < 0) { + cpr_err(cpr_vreg, + "read cpr-init-voltage-step failed, rc = %d\n", rc); + kfree(fuse_sel); + return rc; + } + + ref_uv = kzalloc((cpr_vreg->num_fuse_corners + 1) * sizeof(*ref_uv), + GFP_KERNEL); + if (!ref_uv) { + kfree(fuse_sel); + return -ENOMEM; + } + + rc = of_property_read_u32_array(of_node, "qcom,cpr-init-voltage-ref", + &ref_uv[CPR_FUSE_CORNER_MIN], cpr_vreg->num_fuse_corners); + if (rc < 0) { + cpr_err(cpr_vreg, + "read qcom,cpr-init-voltage-ref failed, rc = %d\n", rc); + kfree(fuse_sel); + kfree(ref_uv); + return rc; + } + + tmp = fuse_sel; + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) { + efuse_bits = cpr_read_efuse_param(cpr_vreg, fuse_sel[0], + fuse_sel[1], fuse_sel[2], fuse_sel[3]); + sign = (efuse_bits & (1 << (fuse_sel[2] - 1))) ? -1 : 1; + steps = efuse_bits & ((1 << (fuse_sel[2] - 1)) - 1); + cpr_vreg->pvs_corner_v[i] = + ref_uv[i] + sign * steps * step_size_uv; + cpr_vreg->pvs_corner_v[i] = DIV_ROUND_UP( + cpr_vreg->pvs_corner_v[i], + cpr_vreg->step_volt) * + cpr_vreg->step_volt; + cpr_debug(cpr_vreg, "corner %d: sign = %d, steps = %d, volt = %d uV\n", + i, sign, steps, cpr_vreg->pvs_corner_v[i]); + fuse_sel += 4; + } + + rc = cpr_adjust_init_voltages(of_node, cpr_vreg); + if (rc) + goto done; + + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) { + if (cpr_vreg->pvs_corner_v[i] + > cpr_vreg->fuse_ceiling_volt[i]) { + cpr_info(cpr_vreg, "Warning: initial voltage[%d] %d above ceiling %d\n", + i, cpr_vreg->pvs_corner_v[i], + cpr_vreg->fuse_ceiling_volt[i]); + cpr_vreg->pvs_corner_v[i] + = cpr_vreg->fuse_ceiling_volt[i]; + } else if (cpr_vreg->pvs_corner_v[i] < + cpr_vreg->fuse_floor_volt[i]) { + cpr_info(cpr_vreg, "Warning: initial voltage[%d] %d below floor %d\n", + i, cpr_vreg->pvs_corner_v[i], + cpr_vreg->fuse_floor_volt[i]); + cpr_vreg->pvs_corner_v[i] + = cpr_vreg->fuse_floor_volt[i]; + } + } + +done: + kfree(tmp); + kfree(ref_uv); + + return rc; +} + +/* + * A single PVS bin is stored in a fuse that's position is defined either + * in the qcom,pvs-fuse-redun property or in the qcom,pvs-fuse property. + * The fuse value defined in the qcom,pvs-fuse-redun-sel property is used + * to pick between the primary or redudant PVS fuse position. + * After the PVS bin value is read out successfully, it is used as the row + * index to get initial voltages for each fuse corner from the voltage table + * defined in the qcom,pvs-voltage-table property. + */ +static int cpr_pvs_single_bin_init(struct device_node *of_node, + struct cpr_regulator *cpr_vreg) +{ + u64 efuse_bits; + u32 pvs_fuse[4], pvs_fuse_redun_sel[5]; + int rc, i, stripe_size; + bool redundant; + size_t pvs_bins; + u32 *tmp; + + rc = of_property_read_u32_array(of_node, "qcom,pvs-fuse-redun-sel", + pvs_fuse_redun_sel, 5); + if (rc < 0) { + cpr_err(cpr_vreg, "pvs-fuse-redun-sel missing: rc=%d\n", rc); + return rc; + } + + redundant = cpr_fuse_is_setting_expected(cpr_vreg, pvs_fuse_redun_sel); + if (redundant) { + rc = of_property_read_u32_array(of_node, "qcom,pvs-fuse-redun", + pvs_fuse, 4); + if (rc < 0) { + cpr_err(cpr_vreg, "pvs-fuse-redun missing: rc=%d\n", + rc); + return rc; + } + } else { + rc = of_property_read_u32_array(of_node, "qcom,pvs-fuse", + pvs_fuse, 4); + if (rc < 0) { + cpr_err(cpr_vreg, "pvs-fuse missing: rc=%d\n", rc); + return rc; + } + } + + /* Construct PVS process # from the efuse bits */ + efuse_bits = cpr_read_efuse_row(cpr_vreg, pvs_fuse[0], pvs_fuse[3]); + cpr_vreg->pvs_bin = (efuse_bits >> pvs_fuse[1]) & + ((1 << pvs_fuse[2]) - 1); + pvs_bins = 1 << pvs_fuse[2]; + stripe_size = cpr_vreg->num_fuse_corners; + tmp = kzalloc(sizeof(u32) * pvs_bins * stripe_size, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + rc = of_property_read_u32_array(of_node, "qcom,pvs-voltage-table", + tmp, pvs_bins * stripe_size); + if (rc < 0) { + cpr_err(cpr_vreg, "pvs-voltage-table missing: rc=%d\n", rc); + kfree(tmp); + return rc; + } + + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) + cpr_vreg->pvs_corner_v[i] = tmp[cpr_vreg->pvs_bin * + stripe_size + i - 1]; + kfree(tmp); + + rc = cpr_adjust_init_voltages(of_node, cpr_vreg); + if (rc) + return rc; + + return 0; +} + +/* + * The function reads VDD_MX dependency parameters from device node. + * Select the qcom,vdd-mx-corner-map length equal to either num_fuse_corners + * or num_corners based on selected vdd-mx-vmin-method. + */ +static int cpr_parse_vdd_mx_parameters(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + u32 corner_map_len; + int rc, len, size; + + rc = of_property_read_u32(of_node, "qcom,vdd-mx-vmax", + &cpr_vreg->vdd_mx_vmax); + if (rc < 0) { + cpr_err(cpr_vreg, "vdd-mx-vmax missing: rc=%d\n", rc); + return rc; + } + + rc = of_property_read_u32(of_node, "qcom,vdd-mx-vmin-method", + &cpr_vreg->vdd_mx_vmin_method); + if (rc < 0) { + cpr_err(cpr_vreg, "vdd-mx-vmin-method missing: rc=%d\n", + rc); + return rc; + } + if (cpr_vreg->vdd_mx_vmin_method > VDD_MX_VMIN_APC_CORNER_MAP) { + cpr_err(cpr_vreg, "Invalid vdd-mx-vmin-method(%d)\n", + cpr_vreg->vdd_mx_vmin_method); + return -EINVAL; + } + + switch (cpr_vreg->vdd_mx_vmin_method) { + case VDD_MX_VMIN_APC_FUSE_CORNER_MAP: + corner_map_len = cpr_vreg->num_fuse_corners; + break; + case VDD_MX_VMIN_APC_CORNER_MAP: + corner_map_len = cpr_vreg->num_corners; + break; + default: + cpr_vreg->vdd_mx_corner_map = NULL; + return 0; + } + + if (!of_find_property(of_node, "qcom,vdd-mx-corner-map", &len)) { + cpr_err(cpr_vreg, "qcom,vdd-mx-corner-map missing"); + return -EINVAL; + } + + size = len / sizeof(u32); + if (size != corner_map_len) { + cpr_err(cpr_vreg, + "qcom,vdd-mx-corner-map length=%d is invalid: required:%u\n", + size, corner_map_len); + return -EINVAL; + } + + cpr_vreg->vdd_mx_corner_map = devm_kzalloc(&pdev->dev, + (corner_map_len + 1) * sizeof(*cpr_vreg->vdd_mx_corner_map), + GFP_KERNEL); + if (!cpr_vreg->vdd_mx_corner_map) + return -ENOMEM; + + rc = of_property_read_u32_array(of_node, + "qcom,vdd-mx-corner-map", + &cpr_vreg->vdd_mx_corner_map[1], + corner_map_len); + if (rc) + cpr_err(cpr_vreg, + "read qcom,vdd-mx-corner-map failed, rc = %d\n", rc); + + return rc; +} + +#define MAX_CHARS_PER_INT 10 + +/* + * The initial voltage for each fuse corner may be determined by one of two + * possible styles of fuse. If qcom,cpr-fuse-init-voltage is present, then + * the initial voltages are encoded in a fuse for each fuse corner. If it is + * not present, then the initial voltages are all determined using a single + * PVS bin fuse value. + */ +static int cpr_pvs_init(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + int highest_fuse_corner = cpr_vreg->num_fuse_corners; + int i, rc, pos; + size_t buflen; + char *buf; + + rc = of_property_read_u32(of_node, "qcom,cpr-apc-volt-step", + &cpr_vreg->step_volt); + if (rc < 0) { + cpr_err(cpr_vreg, "read cpr-apc-volt-step failed, rc = %d\n", + rc); + return rc; + } else if (cpr_vreg->step_volt == 0) { + cpr_err(cpr_vreg, "apc voltage step size can't be set to 0.\n"); + return -EINVAL; + } + + if (of_find_property(of_node, "qcom,cpr-fuse-init-voltage", NULL)) { + rc = cpr_pvs_per_corner_init(of_node, cpr_vreg); + if (rc < 0) { + cpr_err(cpr_vreg, "get pvs per corner failed, rc = %d", + rc); + return rc; + } + } else { + rc = cpr_pvs_single_bin_init(of_node, cpr_vreg); + if (rc < 0) { + cpr_err(cpr_vreg, + "get pvs from single bin failed, rc = %d", rc); + return rc; + } + } + + if (cpr_vreg->flags & FLAGS_UPLIFT_QUOT_VOLT) { + rc = cpr_voltage_uplift_wa_inc_volt(cpr_vreg, of_node); + if (rc < 0) { + cpr_err(cpr_vreg, "pvs volt uplift wa apply failed: %d", + rc); + return rc; + } + } + + /* + * Allow the highest fuse corner's PVS voltage to define the ceiling + * voltage for that corner in order to support SoC's in which variable + * ceiling values are required. + */ + if (cpr_vreg->pvs_corner_v[highest_fuse_corner] > + cpr_vreg->fuse_ceiling_volt[highest_fuse_corner]) + cpr_vreg->fuse_ceiling_volt[highest_fuse_corner] = + cpr_vreg->pvs_corner_v[highest_fuse_corner]; + + /* + * Restrict all fuse corner PVS voltages based upon per corner + * ceiling and floor voltages. + */ + for (i = CPR_FUSE_CORNER_MIN; i <= highest_fuse_corner; i++) + if (cpr_vreg->pvs_corner_v[i] > cpr_vreg->fuse_ceiling_volt[i]) + cpr_vreg->pvs_corner_v[i] + = cpr_vreg->fuse_ceiling_volt[i]; + else if (cpr_vreg->pvs_corner_v[i] + < cpr_vreg->fuse_floor_volt[i]) + cpr_vreg->pvs_corner_v[i] + = cpr_vreg->fuse_floor_volt[i]; + + cpr_vreg->ceiling_max + = cpr_vreg->fuse_ceiling_volt[highest_fuse_corner]; + + /* + * Log ceiling, floor, and initial voltages since they are critical for + * all CPR debugging. + */ + buflen = cpr_vreg->num_fuse_corners * (MAX_CHARS_PER_INT + 2) + * sizeof(*buf); + buf = kzalloc(buflen, GFP_KERNEL); + if (buf == NULL) { + cpr_err(cpr_vreg, "Could not allocate memory for corner voltage logging\n"); + return 0; + } + + for (i = CPR_FUSE_CORNER_MIN, pos = 0; i <= highest_fuse_corner; i++) + pos += scnprintf(buf + pos, buflen - pos, "%u%s", + cpr_vreg->pvs_corner_v[i], + i < highest_fuse_corner ? " " : ""); + cpr_info(cpr_vreg, "pvs voltage: [%s] uV\n", buf); + + for (i = CPR_FUSE_CORNER_MIN, pos = 0; i <= highest_fuse_corner; i++) + pos += scnprintf(buf + pos, buflen - pos, "%d%s", + cpr_vreg->fuse_ceiling_volt[i], + i < highest_fuse_corner ? " " : ""); + cpr_info(cpr_vreg, "ceiling voltage: [%s] uV\n", buf); + + for (i = CPR_FUSE_CORNER_MIN, pos = 0; i <= highest_fuse_corner; i++) + pos += scnprintf(buf + pos, buflen - pos, "%d%s", + cpr_vreg->fuse_floor_volt[i], + i < highest_fuse_corner ? " " : ""); + cpr_info(cpr_vreg, "floor voltage: [%s] uV\n", buf); + + kfree(buf); + return 0; +} + +#define CPR_PROP_READ_U32(cpr_vreg, of_node, cpr_property, cpr_config, rc) \ +do { \ + if (!rc) { \ + rc = of_property_read_u32(of_node, \ + "qcom," cpr_property, \ + cpr_config); \ + if (rc) { \ + cpr_err(cpr_vreg, "Missing " #cpr_property \ + ": rc = %d\n", rc); \ + } \ + } \ +} while (0) + +static int cpr_apc_init(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + int i, rc = 0; + + for (i = 0; i < ARRAY_SIZE(vdd_apc_name); i++) { + cpr_vreg->vdd_apc = devm_regulator_get_optional(&pdev->dev, + vdd_apc_name[i]); + rc = PTR_RET(cpr_vreg->vdd_apc); + if (!IS_ERR_OR_NULL(cpr_vreg->vdd_apc)) + break; + } + + if (rc) { + if (rc != -EPROBE_DEFER) + cpr_err(cpr_vreg, "devm_regulator_get: rc=%d\n", rc); + return rc; + } + + /* Check dependencies */ + if (of_find_property(of_node, "vdd-mx-supply", NULL)) { + cpr_vreg->vdd_mx = devm_regulator_get(&pdev->dev, "vdd-mx"); + if (IS_ERR_OR_NULL(cpr_vreg->vdd_mx)) { + rc = PTR_RET(cpr_vreg->vdd_mx); + if (rc != -EPROBE_DEFER) + cpr_err(cpr_vreg, + "devm_regulator_get: vdd-mx: rc=%d\n", + rc); + return rc; + } + } + + return 0; +} + +static void cpr_apc_exit(struct cpr_regulator *cpr_vreg) +{ + if (cpr_vreg->vreg_enabled) { + regulator_disable(cpr_vreg->vdd_apc); + + if (cpr_vreg->vdd_mx) + regulator_disable(cpr_vreg->vdd_mx); + } +} + +static int cpr_voltage_uplift_wa_inc_quot(struct cpr_regulator *cpr_vreg, + struct device_node *of_node) +{ + u32 delta_quot[3]; + int rc, i; + + rc = of_property_read_u32_array(of_node, + "qcom,cpr-uplift-quotient", delta_quot, 3); + if (rc < 0) { + cpr_err(cpr_vreg, "cpr-uplift-quotient is missing: %d", rc); + return rc; + } + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) + cpr_vreg->cpr_fuse_target_quot[i] += delta_quot[i-1]; + return rc; +} + +static void cpr_parse_pvs_version_fuse(struct cpr_regulator *cpr_vreg, + struct device_node *of_node) +{ + int rc; + u64 fuse_bits; + u32 fuse_sel[4]; + + rc = of_property_read_u32_array(of_node, + "qcom,pvs-version-fuse-sel", fuse_sel, 4); + if (!rc) { + fuse_bits = cpr_read_efuse_row(cpr_vreg, + fuse_sel[0], fuse_sel[3]); + cpr_vreg->pvs_version = (fuse_bits >> fuse_sel[1]) & + ((1 << fuse_sel[2]) - 1); + cpr_info(cpr_vreg, "[row: %d]: 0x%llx, pvs_version = %d\n", + fuse_sel[0], fuse_bits, cpr_vreg->pvs_version); + } else { + cpr_vreg->pvs_version = 0; + } +} + +/** + * cpr_get_open_loop_voltage() - fill the open_loop_volt array with linearly + * interpolated open-loop CPR voltage values. + * @cpr_vreg: Handle to the cpr-regulator device + * @dev: Device pointer for the cpr-regulator device + * @corner_max: Array of length (cpr_vreg->num_fuse_corners + 1) which maps from + * fuse corners to the highest virtual corner corresponding to a + * given fuse corner + * @freq_map: Array of length (cpr_vreg->num_corners + 1) which maps from + * virtual corners to frequencies in Hz. + * @maps_valid: Boolean which indicates if the values in corner_max and freq_map + * are valid. If they are not valid, then the open_loop_volt + * values are not interpolated. + */ +static int cpr_get_open_loop_voltage(struct cpr_regulator *cpr_vreg, + struct device *dev, const u32 *corner_max, const u32 *freq_map, + bool maps_valid) +{ + int rc = 0; + int i, j; + u64 volt_high, volt_low, freq_high, freq_low, freq, temp, temp_limit; + u32 *max_factor = NULL; + + cpr_vreg->open_loop_volt = devm_kzalloc(dev, + sizeof(int) * (cpr_vreg->num_corners + 1), GFP_KERNEL); + if (!cpr_vreg->open_loop_volt) + return -ENOMEM; + + /* + * Set open loop voltage to be equal to per-fuse-corner initial voltage + * by default. This ensures that the open loop voltage is valid for + * all virtual corners even if some virtual corner to frequency mappings + * are missing. It also ensures that the voltage is valid for the + * higher corners not utilized by a given speed-bin. + */ + for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) + cpr_vreg->open_loop_volt[i] + = cpr_vreg->pvs_corner_v[cpr_vreg->corner_map[i]]; + + if (!maps_valid || !corner_max || !freq_map + || !of_find_property(dev->of_node, + "qcom,cpr-voltage-scaling-factor-max", NULL)) { + /* Not using interpolation */ + return 0; + } + + max_factor + = kzalloc(sizeof(*max_factor) * (cpr_vreg->num_fuse_corners + 1), + GFP_KERNEL); + if (!max_factor) + return -ENOMEM; + + rc = of_property_read_u32_array(dev->of_node, + "qcom,cpr-voltage-scaling-factor-max", + &max_factor[CPR_FUSE_CORNER_MIN], + cpr_vreg->num_fuse_corners); + if (rc) { + cpr_debug(cpr_vreg, "failed to read qcom,cpr-voltage-scaling-factor-max; initial voltage interpolation not possible\n"); + kfree(max_factor); + return 0; + } + + for (j = CPR_FUSE_CORNER_MIN + 1; j <= cpr_vreg->num_fuse_corners; + j++) { + freq_high = freq_map[corner_max[j]]; + freq_low = freq_map[corner_max[j - 1]]; + volt_high = cpr_vreg->pvs_corner_v[j]; + volt_low = cpr_vreg->pvs_corner_v[j - 1]; + if (freq_high <= freq_low || volt_high <= volt_low) + continue; + + for (i = corner_max[j - 1] + 1; i < corner_max[j]; i++) { + freq = freq_map[i]; + if (freq_high <= freq) + continue; + + temp = (freq_high - freq) * (volt_high - volt_low); + do_div(temp, (u32)(freq_high - freq_low)); + + /* + * max_factor[j] has units of uV/MHz while freq values + * have units of Hz. Divide by 1000000 to convert. + */ + temp_limit = (freq_high - freq) * max_factor[j]; + do_div(temp_limit, 1000000); + + cpr_vreg->open_loop_volt[i] + = volt_high - min(temp, temp_limit); + cpr_vreg->open_loop_volt[i] + = DIV_ROUND_UP(cpr_vreg->open_loop_volt[i], + cpr_vreg->step_volt) + * cpr_vreg->step_volt; + } + } + + kfree(max_factor); + return 0; +} + +/* + * Limit the per-virtual-corner open-loop voltages using the per-virtual-corner + * ceiling and floor voltage values. This must be called only after the + * open_loop_volt, ceiling, and floor arrays have all been initialized. + */ +static int cpr_limit_open_loop_voltage(struct cpr_regulator *cpr_vreg) +{ + int i; + + for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) { + if (cpr_vreg->open_loop_volt[i] > cpr_vreg->ceiling_volt[i]) + cpr_vreg->open_loop_volt[i] = cpr_vreg->ceiling_volt[i]; + else if (cpr_vreg->open_loop_volt[i] < cpr_vreg->floor_volt[i]) + cpr_vreg->open_loop_volt[i] = cpr_vreg->floor_volt[i]; + } + + return 0; +} + +/* + * Fill an OPP table for the cpr-regulator device struct with pairs of + * tuples. + */ +static int cpr_populate_opp_table(struct cpr_regulator *cpr_vreg, + struct device *dev) +{ + int i, rc = 0; + + for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) { + rc |= dev_pm_opp_add(dev, i, cpr_vreg->open_loop_volt[i]); + if (rc) + cpr_debug(cpr_vreg, "could not add OPP entry <%d, %d>, rc=%d\n", + i, cpr_vreg->open_loop_volt[i], rc); + } + if (rc) + cpr_err(cpr_vreg, "adding OPP entry failed - OPP may not be enabled, rc=%d\n", + rc); + + return 0; +} + +/* + * Conditionally reduce the per-virtual-corner ceiling voltages if certain + * device tree flags are present. This must be called only after the ceiling + * array has been initialized and the open_loop_volt array values have been + * initialized and limited to the existing floor to ceiling voltage range. + */ +static int cpr_reduce_ceiling_voltage(struct cpr_regulator *cpr_vreg, + struct device *dev) +{ + bool reduce_to_fuse_open_loop, reduce_to_interpolated_open_loop; + int i; + + reduce_to_fuse_open_loop = of_property_read_bool(dev->of_node, + "qcom,cpr-init-voltage-as-ceiling"); + reduce_to_interpolated_open_loop = of_property_read_bool(dev->of_node, + "qcom,cpr-scaled-init-voltage-as-ceiling"); + + if (!reduce_to_fuse_open_loop && !reduce_to_interpolated_open_loop) + return 0; + + for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) { + if (reduce_to_interpolated_open_loop && + cpr_vreg->open_loop_volt[i] < cpr_vreg->ceiling_volt[i]) + cpr_vreg->ceiling_volt[i] = cpr_vreg->open_loop_volt[i]; + else if (reduce_to_fuse_open_loop && + cpr_vreg->pvs_corner_v[cpr_vreg->corner_map[i]] + < cpr_vreg->ceiling_volt[i]) + cpr_vreg->ceiling_volt[i] + = max((u32)cpr_vreg->floor_volt[i], + cpr_vreg->pvs_corner_v[cpr_vreg->corner_map[i]]); + cpr_debug(cpr_vreg, "lowered ceiling[%d] = %d uV\n", + i, cpr_vreg->ceiling_volt[i]); + } + + return 0; +} + +static int cpr_adjust_target_quot_offsets(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + int tuple_count, tuple_match, i; + u32 index; + u32 quot_offset_adjust = 0; + int len = 0; + int rc = 0; + char *quot_offset_str; + + quot_offset_str = "qcom,cpr-quot-offset-adjustment"; + if (!of_find_property(of_node, quot_offset_str, &len)) { + /* No static quotient adjustment needed. */ + return 0; + } + + if (cpr_vreg->cpr_fuse_map_count) { + if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) { + /* No matching index to use for quotient adjustment. */ + return 0; + } + tuple_count = cpr_vreg->cpr_fuse_map_count; + tuple_match = cpr_vreg->cpr_fuse_map_match; + } else { + tuple_count = 1; + tuple_match = 0; + } + + if (len != cpr_vreg->num_fuse_corners * tuple_count * sizeof(u32)) { + cpr_err(cpr_vreg, "%s length=%d is invalid\n", quot_offset_str, + len); + return -EINVAL; + } + + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) { + index = tuple_match * cpr_vreg->num_fuse_corners + + i - CPR_FUSE_CORNER_MIN; + rc = of_property_read_u32_index(of_node, quot_offset_str, index, + "_offset_adjust); + if (rc) { + cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n", + quot_offset_str, index, rc); + return rc; + } + + if (quot_offset_adjust) { + cpr_vreg->fuse_quot_offset[i] += quot_offset_adjust; + cpr_info(cpr_vreg, "Corner[%d]: adjusted target quot = %d\n", + i, cpr_vreg->fuse_quot_offset[i]); + } + } + + return rc; +} + +static int cpr_get_fuse_quot_offset(struct cpr_regulator *cpr_vreg, + struct platform_device *pdev, + struct cpr_quot_scale *quot_scale) +{ + struct device *dev = &pdev->dev; + struct property *prop; + u32 *fuse_sel, *tmp, *offset_multiplier = NULL; + int rc = 0, i, size, len; + char *quot_offset_str; + + quot_offset_str = cpr_vreg->cpr_fuse_redundant + ? "qcom,cpr-fuse-redun-quot-offset" + : "qcom,cpr-fuse-quot-offset"; + + prop = of_find_property(dev->of_node, quot_offset_str, NULL); + if (!prop) { + cpr_debug(cpr_vreg, "%s not present\n", quot_offset_str); + return 0; + } + + size = prop->length / sizeof(u32); + if (size != cpr_vreg->num_fuse_corners * 4) { + cpr_err(cpr_vreg, "fuse position for quot offset is invalid\n"); + return -EINVAL; + } + + fuse_sel = kzalloc(sizeof(u32) * size, GFP_KERNEL); + if (!fuse_sel) + return -ENOMEM; + + rc = of_property_read_u32_array(dev->of_node, quot_offset_str, + fuse_sel, size); + + if (rc < 0) { + cpr_err(cpr_vreg, "read %s failed, rc = %d\n", quot_offset_str, + rc); + kfree(fuse_sel); + return rc; + } + + cpr_vreg->fuse_quot_offset = devm_kzalloc(dev, + sizeof(u32) * (cpr_vreg->num_fuse_corners + 1), + GFP_KERNEL); + if (!cpr_vreg->fuse_quot_offset) { + kfree(fuse_sel); + return -ENOMEM; + } + + if (!of_find_property(dev->of_node, + "qcom,cpr-fuse-quot-offset-scale", &len)) { + cpr_debug(cpr_vreg, "qcom,cpr-fuse-quot-offset-scale not present\n"); + } else { + if (len != cpr_vreg->num_fuse_corners * sizeof(u32)) { + cpr_err(cpr_vreg, "the size of qcom,cpr-fuse-quot-offset-scale is invalid\n"); + kfree(fuse_sel); + return -EINVAL; + } + + offset_multiplier = kzalloc(sizeof(*offset_multiplier) + * (cpr_vreg->num_fuse_corners + 1), + GFP_KERNEL); + if (!offset_multiplier) { + kfree(fuse_sel); + return -ENOMEM; + } + + rc = of_property_read_u32_array(dev->of_node, + "qcom,cpr-fuse-quot-offset-scale", + &offset_multiplier[1], + cpr_vreg->num_fuse_corners); + if (rc < 0) { + cpr_err(cpr_vreg, "read qcom,cpr-fuse-quot-offset-scale failed, rc = %d\n", + rc); + kfree(fuse_sel); + goto out; + } + } + + tmp = fuse_sel; + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) { + cpr_vreg->fuse_quot_offset[i] = cpr_read_efuse_param(cpr_vreg, + fuse_sel[0], fuse_sel[1], fuse_sel[2], + fuse_sel[3]); + if (offset_multiplier) + cpr_vreg->fuse_quot_offset[i] *= offset_multiplier[i]; + fuse_sel += 4; + } + + rc = cpr_adjust_target_quot_offsets(pdev, cpr_vreg); + kfree(tmp); +out: + kfree(offset_multiplier); + return rc; +} + +/* + * Adjust the per-virtual-corner open loop voltage with an offset specfied by a + * device-tree property. This must be called after open-loop voltage scaling. + */ +static int cpr_virtual_corner_voltage_adjust(struct cpr_regulator *cpr_vreg, + struct device *dev) +{ + char *prop_name = "qcom,cpr-virtual-corner-init-voltage-adjustment"; + int i, rc, tuple_count, tuple_match, index, len; + u32 voltage_adjust; + + if (!of_find_property(dev->of_node, prop_name, &len)) { + cpr_debug(cpr_vreg, "%s not specified\n", prop_name); + return 0; + } + + if (cpr_vreg->cpr_fuse_map_count) { + if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) { + /* No matching index to use for voltage adjustment. */ + return 0; + } + tuple_count = cpr_vreg->cpr_fuse_map_count; + tuple_match = cpr_vreg->cpr_fuse_map_match; + } else { + tuple_count = 1; + tuple_match = 0; + } + + if (len != cpr_vreg->num_corners * tuple_count * sizeof(u32)) { + cpr_err(cpr_vreg, "%s length=%d is invalid\n", prop_name, + len); + return -EINVAL; + } + + for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) { + index = tuple_match * cpr_vreg->num_corners + + i - CPR_CORNER_MIN; + rc = of_property_read_u32_index(dev->of_node, prop_name, + index, &voltage_adjust); + if (rc) { + cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n", + prop_name, index, rc); + return rc; + } + + if (voltage_adjust) { + cpr_vreg->open_loop_volt[i] += (int)voltage_adjust; + cpr_info(cpr_vreg, "corner=%d adjusted open-loop voltage=%d\n", + i, cpr_vreg->open_loop_volt[i]); + } + } + + return 0; +} + +/* + * Adjust the per-virtual-corner quot with an offset specfied by a + * device-tree property. This must be called after the quot-scaling adjustments + * are completed. + */ +static int cpr_virtual_corner_quot_adjust(struct cpr_regulator *cpr_vreg, + struct device *dev) +{ + char *prop_name = "qcom,cpr-virtual-corner-quotient-adjustment"; + int i, rc, tuple_count, tuple_match, index, len; + u32 quot_adjust; + + if (!of_find_property(dev->of_node, prop_name, &len)) { + cpr_debug(cpr_vreg, "%s not specified\n", prop_name); + return 0; + } + + if (cpr_vreg->cpr_fuse_map_count) { + if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) { + /* No matching index to use for quotient adjustment. */ + return 0; + } + tuple_count = cpr_vreg->cpr_fuse_map_count; + tuple_match = cpr_vreg->cpr_fuse_map_match; + } else { + tuple_count = 1; + tuple_match = 0; + } + + if (len != cpr_vreg->num_corners * tuple_count * sizeof(u32)) { + cpr_err(cpr_vreg, "%s length=%d is invalid\n", prop_name, + len); + return -EINVAL; + } + + for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) { + index = tuple_match * cpr_vreg->num_corners + + i - CPR_CORNER_MIN; + rc = of_property_read_u32_index(dev->of_node, prop_name, + index, "_adjust); + if (rc) { + cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n", + prop_name, index, rc); + return rc; + } + + if (quot_adjust) { + cpr_vreg->quot_adjust[i] -= (int)quot_adjust; + cpr_info(cpr_vreg, "corner=%d adjusted quotient=%d\n", + i, + cpr_vreg->cpr_fuse_target_quot[cpr_vreg->corner_map[i]] + - cpr_vreg->quot_adjust[i]); + } + } + + return 0; +} + +/* + * cpr_get_corner_quot_adjustment() -- get the quot_adjust for each corner. + * + * Get the virtual corner to fuse corner mapping and virtual corner to APC clock + * frequency mapping from device tree. + * Calculate the quotient adjustment scaling factor for those corners mapping to + * all fuse corners except for the lowest one using linear interpolation. + * Calculate the quotient adjustment for each of these virtual corners using the + * min of the calculated scaling factor and the constant max scaling factor + * defined for each fuse corner in device tree. + */ +static int cpr_get_corner_quot_adjustment(struct cpr_regulator *cpr_vreg, + struct device *dev) +{ + int rc = 0; + int highest_fuse_corner = cpr_vreg->num_fuse_corners; + int i, j, size; + struct property *prop; + bool corners_mapped, match_found; + u32 *tmp, *freq_map = NULL; + u32 corner, freq_corner; + u32 *freq_max = NULL; + u32 *scaling = NULL; + u32 *max_factor = NULL; + u32 *corner_max = NULL; + bool maps_valid = false; + + prop = of_find_property(dev->of_node, "qcom,cpr-corner-map", NULL); + + if (prop) { + size = prop->length / sizeof(u32); + corners_mapped = true; + } else { + size = cpr_vreg->num_fuse_corners; + corners_mapped = false; + } + + cpr_vreg->corner_map = devm_kzalloc(dev, sizeof(int) * (size + 1), + GFP_KERNEL); + if (!cpr_vreg->corner_map) + return -ENOMEM; + cpr_vreg->num_corners = size; + + cpr_vreg->quot_adjust = devm_kzalloc(dev, + sizeof(u32) * (cpr_vreg->num_corners + 1), + GFP_KERNEL); + if (!cpr_vreg->quot_adjust) + return -ENOMEM; + + if (!corners_mapped) { + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; + i++) + cpr_vreg->corner_map[i] = i; + goto free_arrays; + } else { + rc = of_property_read_u32_array(dev->of_node, + "qcom,cpr-corner-map", &cpr_vreg->corner_map[1], size); + + if (rc) { + cpr_err(cpr_vreg, + "qcom,cpr-corner-map missing, rc = %d\n", rc); + return rc; + } + + /* + * Verify that the virtual corner to fuse corner mapping is + * valid. + */ + for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) { + if (cpr_vreg->corner_map[i] > cpr_vreg->num_fuse_corners + || cpr_vreg->corner_map[i] < CPR_FUSE_CORNER_MIN) { + cpr_err(cpr_vreg, "qcom,cpr-corner-map contains an element %d which isn't in the allowed range [%d, %d]\n", + cpr_vreg->corner_map[i], + CPR_FUSE_CORNER_MIN, + cpr_vreg->num_fuse_corners); + return -EINVAL; + } + } + } + + prop = of_find_property(dev->of_node, + "qcom,cpr-speed-bin-max-corners", NULL); + if (!prop) { + cpr_debug(cpr_vreg, "qcom,cpr-speed-bin-max-corner missing\n"); + goto free_arrays; + } + + size = prop->length / sizeof(u32); + tmp = kcalloc(size, sizeof(*tmp), GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + rc = of_property_read_u32_array(dev->of_node, + "qcom,cpr-speed-bin-max-corners", tmp, size); + if (rc < 0) { + kfree(tmp); + cpr_err(cpr_vreg, + "get cpr-speed-bin-max-corners failed, rc = %d\n", rc); + return rc; + } + + corner_max = kcalloc((cpr_vreg->num_fuse_corners + 1), + sizeof(*corner_max), GFP_KERNEL); + freq_max = kcalloc((cpr_vreg->num_fuse_corners + 1), sizeof(*freq_max), + GFP_KERNEL); + if (corner_max == NULL || freq_max == NULL) { + kfree(tmp); + rc = -ENOMEM; + goto free_arrays; + } + + /* + * Get the maximum virtual corner for each fuse corner based upon the + * speed_bin and pvs_version values. + */ + match_found = false; + for (i = 0; i < size; i += cpr_vreg->num_fuse_corners + 2) { + if (tmp[i] != cpr_vreg->speed_bin && + tmp[i] != FUSE_PARAM_MATCH_ANY) + continue; + if (tmp[i + 1] != cpr_vreg->pvs_version && + tmp[i + 1] != FUSE_PARAM_MATCH_ANY) + continue; + for (j = CPR_FUSE_CORNER_MIN; + j <= cpr_vreg->num_fuse_corners; j++) + corner_max[j] = tmp[i + 2 + j - CPR_FUSE_CORNER_MIN]; + match_found = true; + break; + } + kfree(tmp); + + if (!match_found) { + cpr_debug(cpr_vreg, "No quotient adjustment possible for speed bin=%u, pvs version=%u\n", + cpr_vreg->speed_bin, cpr_vreg->pvs_version); + goto free_arrays; + } + + /* Verify that fuse corner to max virtual corner mapping is valid. */ + for (i = CPR_FUSE_CORNER_MIN; i <= highest_fuse_corner; i++) { + if (corner_max[i] < CPR_CORNER_MIN + || corner_max[i] > cpr_vreg->num_corners) { + cpr_err(cpr_vreg, "Invalid corner=%d in qcom,cpr-speed-bin-max-corners\n", + corner_max[i]); + goto free_arrays; + } + } + + /* + * Return success if the virtual corner values read from + * qcom,cpr-speed-bin-max-corners property are incorrect. This allows + * the driver to continue to run without quotient scaling. + */ + for (i = CPR_FUSE_CORNER_MIN + 1; i <= highest_fuse_corner; i++) { + if (corner_max[i] <= corner_max[i - 1]) { + cpr_err(cpr_vreg, "fuse corner=%d (%u) should be larger than the fuse corner=%d (%u)\n", + i, corner_max[i], i - 1, corner_max[i - 1]); + goto free_arrays; + } + } + + prop = of_find_property(dev->of_node, + "qcom,cpr-corner-frequency-map", NULL); + if (!prop) { + cpr_debug(cpr_vreg, "qcom,cpr-corner-frequency-map missing\n"); + goto free_arrays; + } + + size = prop->length / sizeof(u32); + tmp = kcalloc(size, sizeof(*tmp), GFP_KERNEL); + if (!tmp) { + rc = -ENOMEM; + goto free_arrays; + } + rc = of_property_read_u32_array(dev->of_node, + "qcom,cpr-corner-frequency-map", tmp, size); + if (rc < 0) { + cpr_err(cpr_vreg, + "get cpr-corner-frequency-map failed, rc = %d\n", rc); + kfree(tmp); + goto free_arrays; + } + freq_map = kcalloc(cpr_vreg->num_corners + 1, sizeof(*freq_map), + GFP_KERNEL); + if (!freq_map) { + kfree(tmp); + rc = -ENOMEM; + goto free_arrays; + } + for (i = 0; i < size; i += 2) { + corner = tmp[i]; + if ((corner < 1) || (corner > cpr_vreg->num_corners)) { + cpr_err(cpr_vreg, + "corner should be in 1~%d range: %d\n", + cpr_vreg->num_corners, corner); + continue; + } + freq_map[corner] = tmp[i + 1]; + cpr_debug(cpr_vreg, + "Frequency at virtual corner %d is %d Hz.\n", + corner, freq_map[corner]); + } + kfree(tmp); + + prop = of_find_property(dev->of_node, + "qcom,cpr-quot-adjust-scaling-factor-max", NULL); + if (!prop) { + cpr_debug(cpr_vreg, "qcom,cpr-quot-adjust-scaling-factor-max missing\n"); + rc = 0; + goto free_arrays; + } + + size = prop->length / sizeof(u32); + if ((size != 1) && (size != cpr_vreg->num_fuse_corners)) { + cpr_err(cpr_vreg, "The size of qcom,cpr-quot-adjust-scaling-factor-max should be 1 or %d\n", + cpr_vreg->num_fuse_corners); + rc = 0; + goto free_arrays; + } + + max_factor = kcalloc(cpr_vreg->num_fuse_corners + 1, + sizeof(*max_factor), GFP_KERNEL); + if (!max_factor) { + rc = -ENOMEM; + goto free_arrays; + } + /* + * Leave max_factor[CPR_FUSE_CORNER_MIN ... highest_fuse_corner-1] = 0 + * if cpr-quot-adjust-scaling-factor-max is a single value in order to + * maintain backward compatibility. + */ + i = (size == cpr_vreg->num_fuse_corners) ? CPR_FUSE_CORNER_MIN + : highest_fuse_corner; + rc = of_property_read_u32_array(dev->of_node, + "qcom,cpr-quot-adjust-scaling-factor-max", + &max_factor[i], size); + if (rc < 0) { + cpr_debug(cpr_vreg, "could not read qcom,cpr-quot-adjust-scaling-factor-max, rc=%d\n", + rc); + rc = 0; + goto free_arrays; + } + + /* + * Get the quotient adjustment scaling factor, according to: + * scaling = min(1000 * (QUOT(corner_N) - QUOT(corner_N-1)) + * / (freq(corner_N) - freq(corner_N-1)), max_factor) + * + * QUOT(corner_N): quotient read from fuse for fuse corner N + * QUOT(corner_N-1): quotient read from fuse for fuse corner (N - 1) + * freq(corner_N): max frequency in MHz supported by fuse corner N + * freq(corner_N-1): max frequency in MHz supported by fuse corner + * (N - 1) + */ + + for (i = CPR_FUSE_CORNER_MIN; i <= highest_fuse_corner; i++) + freq_max[i] = freq_map[corner_max[i]]; + for (i = CPR_FUSE_CORNER_MIN + 1; i <= highest_fuse_corner; i++) { + if (freq_max[i] <= freq_max[i - 1] || freq_max[i - 1] == 0) { + cpr_err(cpr_vreg, "fuse corner %d freq=%u should be larger than fuse corner %d freq=%u\n", + i, freq_max[i], i - 1, freq_max[i - 1]); + rc = -EINVAL; + goto free_arrays; + } + } + scaling = kcalloc(cpr_vreg->num_fuse_corners + 1, sizeof(*scaling), + GFP_KERNEL); + if (!scaling) { + rc = -ENOMEM; + goto free_arrays; + } + /* Convert corner max frequencies from Hz to MHz. */ + for (i = CPR_FUSE_CORNER_MIN; i <= highest_fuse_corner; i++) + freq_max[i] /= 1000000; + + for (i = CPR_FUSE_CORNER_MIN + 1; i <= highest_fuse_corner; i++) { + if (cpr_vreg->fuse_quot_offset && + (cpr_vreg->cpr_fuse_ro_sel[i] != + cpr_vreg->cpr_fuse_ro_sel[i - 1])) { + scaling[i] = 1000 * cpr_vreg->fuse_quot_offset[i] + / (freq_max[i] - freq_max[i - 1]); + } else { + scaling[i] = 1000 * (cpr_vreg->cpr_fuse_target_quot[i] + - cpr_vreg->cpr_fuse_target_quot[i - 1]) + / (freq_max[i] - freq_max[i - 1]); + if (cpr_vreg->cpr_fuse_target_quot[i] + < cpr_vreg->cpr_fuse_target_quot[i - 1]) + scaling[i] = 0; + } + scaling[i] = min(scaling[i], max_factor[i]); + cpr_info(cpr_vreg, "fuse corner %d quotient adjustment scaling factor: %d.%03d\n", + i, scaling[i] / 1000, scaling[i] % 1000); + } + + /* + * Walk through the virtual corners mapped to each fuse corner + * and calculate the quotient adjustment for each one using the + * following formula: + * quot_adjust = (freq_max - freq_corner) * scaling / 1000 + * + * @freq_max: max frequency in MHz supported by the fuse corner + * @freq_corner: frequency in MHz corresponding to the virtual corner + */ + for (j = CPR_FUSE_CORNER_MIN + 1; j <= highest_fuse_corner; j++) { + for (i = corner_max[j - 1] + 1; i < corner_max[j]; i++) { + freq_corner = freq_map[i] / 1000000; /* MHz */ + if (freq_corner > 0) { + cpr_vreg->quot_adjust[i] = scaling[j] * + (freq_max[j] - freq_corner) / 1000; + } + } + } + + rc = cpr_virtual_corner_quot_adjust(cpr_vreg, dev); + if (rc) { + cpr_err(cpr_vreg, "count not adjust virtual-corner quot rc=%d\n", + rc); + goto free_arrays; + } + + for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) + cpr_info(cpr_vreg, "adjusted quotient[%d] = %d\n", i, + cpr_vreg->cpr_fuse_target_quot[cpr_vreg->corner_map[i]] + - cpr_vreg->quot_adjust[i]); + + maps_valid = true; + +free_arrays: + if (!rc) { + + rc = cpr_get_open_loop_voltage(cpr_vreg, dev, corner_max, + freq_map, maps_valid); + if (rc) { + cpr_err(cpr_vreg, "could not fill open loop voltage array, rc=%d\n", + rc); + goto free_arrays_1; + } + + rc = cpr_virtual_corner_voltage_adjust(cpr_vreg, dev); + if (rc) + cpr_err(cpr_vreg, "count not adjust virtual-corner voltage rc=%d\n", + rc); + } + +free_arrays_1: + kfree(max_factor); + kfree(scaling); + kfree(freq_map); + kfree(corner_max); + kfree(freq_max); + return rc; +} + +/* + * Check if the redundant set of CPR fuses should be used in place of the + * primary set and configure the cpr_fuse_redundant element accordingly. + */ +static int cpr_check_redundant(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + u32 cpr_fuse_redun_sel[5]; + int rc; + + if (of_find_property(of_node, "qcom,cpr-fuse-redun-sel", NULL)) { + rc = of_property_read_u32_array(of_node, + "qcom,cpr-fuse-redun-sel", cpr_fuse_redun_sel, 5); + if (rc < 0) { + cpr_err(cpr_vreg, "qcom,cpr-fuse-redun-sel missing: rc=%d\n", + rc); + return rc; + } + cpr_vreg->cpr_fuse_redundant + = cpr_fuse_is_setting_expected(cpr_vreg, + cpr_fuse_redun_sel); + } else { + cpr_vreg->cpr_fuse_redundant = false; + } + + if (cpr_vreg->cpr_fuse_redundant) + cpr_info(cpr_vreg, "using redundant fuse parameters\n"); + + return 0; +} + +static int cpr_read_fuse_revision(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + u32 fuse_sel[4]; + int rc; + + if (of_find_property(of_node, "qcom,cpr-fuse-revision", NULL)) { + rc = of_property_read_u32_array(of_node, + "qcom,cpr-fuse-revision", fuse_sel, 4); + if (rc < 0) { + cpr_err(cpr_vreg, "qcom,cpr-fuse-revision read failed: rc=%d\n", + rc); + return rc; + } + cpr_vreg->cpr_fuse_revision + = cpr_read_efuse_param(cpr_vreg, fuse_sel[0], + fuse_sel[1], fuse_sel[2], fuse_sel[3]); + cpr_info(cpr_vreg, "fuse revision = %d\n", + cpr_vreg->cpr_fuse_revision); + } else { + cpr_vreg->cpr_fuse_revision = FUSE_REVISION_UNKNOWN; + } + + return 0; +} + +static int cpr_read_ro_select(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + int rc = 0; + u32 cpr_fuse_row[2]; + char *ro_sel_str; + int *bp_ro_sel; + int i; + + bp_ro_sel + = kzalloc((cpr_vreg->num_fuse_corners + 1) * sizeof(*bp_ro_sel), + GFP_KERNEL); + if (!bp_ro_sel) + return -ENOMEM; + + if (cpr_vreg->cpr_fuse_redundant) { + rc = of_property_read_u32_array(of_node, + "qcom,cpr-fuse-redun-row", + cpr_fuse_row, 2); + ro_sel_str = "qcom,cpr-fuse-redun-ro-sel"; + } else { + rc = of_property_read_u32_array(of_node, "qcom,cpr-fuse-row", + cpr_fuse_row, 2); + ro_sel_str = "qcom,cpr-fuse-ro-sel"; + } + if (rc) + goto error; + + rc = of_property_read_u32_array(of_node, ro_sel_str, + &bp_ro_sel[CPR_FUSE_CORNER_MIN], cpr_vreg->num_fuse_corners); + if (rc) { + cpr_err(cpr_vreg, "%s read error, rc=%d\n", ro_sel_str, rc); + goto error; + } + + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) + cpr_vreg->cpr_fuse_ro_sel[i] + = cpr_read_efuse_param(cpr_vreg, cpr_fuse_row[0], + bp_ro_sel[i], CPR_FUSE_RO_SEL_BITS, + cpr_fuse_row[1]); + +error: + kfree(bp_ro_sel); + + return rc; +} + +static int cpr_find_fuse_map_match(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + int i, j, rc, tuple_size; + int len = 0; + u32 *tmp, val, ro; + + /* Specify default no match case. */ + cpr_vreg->cpr_fuse_map_match = FUSE_MAP_NO_MATCH; + cpr_vreg->cpr_fuse_map_count = 0; + + if (!of_find_property(of_node, "qcom,cpr-fuse-version-map", &len)) { + /* No mapping present. */ + return 0; + } + + tuple_size = cpr_vreg->num_fuse_corners + 3; + cpr_vreg->cpr_fuse_map_count = len / (sizeof(u32) * tuple_size); + + if (len == 0 || len % (sizeof(u32) * tuple_size)) { + cpr_err(cpr_vreg, "qcom,cpr-fuse-version-map length=%d is invalid\n", + len); + return -EINVAL; + } + + tmp = kzalloc(len, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + rc = of_property_read_u32_array(of_node, "qcom,cpr-fuse-version-map", + tmp, cpr_vreg->cpr_fuse_map_count * tuple_size); + if (rc) { + cpr_err(cpr_vreg, "could not read qcom,cpr-fuse-version-map, rc=%d\n", + rc); + goto done; + } + + /* + * qcom,cpr-fuse-version-map tuple format: + * for n == number of fuse corners + */ + for (i = 0; i < cpr_vreg->cpr_fuse_map_count; i++) { + if (tmp[i * tuple_size] != cpr_vreg->speed_bin + && tmp[i * tuple_size] != FUSE_PARAM_MATCH_ANY) + continue; + if (tmp[i * tuple_size + 1] != cpr_vreg->pvs_version + && tmp[i * tuple_size + 1] != FUSE_PARAM_MATCH_ANY) + continue; + if (tmp[i * tuple_size + 2] != cpr_vreg->cpr_fuse_revision + && tmp[i * tuple_size + 2] != FUSE_PARAM_MATCH_ANY) + continue; + for (j = 0; j < cpr_vreg->num_fuse_corners; j++) { + val = tmp[i * tuple_size + 3 + j]; + ro = cpr_vreg->cpr_fuse_ro_sel[j + CPR_FUSE_CORNER_MIN]; + if (val != ro && val != FUSE_PARAM_MATCH_ANY) + break; + } + if (j == cpr_vreg->num_fuse_corners) { + cpr_vreg->cpr_fuse_map_match = i; + break; + } + } + + if (cpr_vreg->cpr_fuse_map_match != FUSE_MAP_NO_MATCH) + cpr_debug(cpr_vreg, "qcom,cpr-fuse-version-map tuple match found: %d\n", + cpr_vreg->cpr_fuse_map_match); + else + cpr_debug(cpr_vreg, "qcom,cpr-fuse-version-map tuple match not found\n"); + +done: + kfree(tmp); + return rc; +} + +static int cpr_minimum_quot_difference_adjustment(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + int tuple_count, tuple_match; + int rc, i, len = 0; + u32 index, adjust_quot = 0; + u32 *min_diff_quot; + + if (!of_find_property(of_node, "qcom,cpr-fuse-min-quot-diff", NULL)) + /* No conditional adjustment needed on revised quotients. */ + return 0; + + if (!of_find_property(of_node, "qcom,cpr-min-quot-diff-adjustment", + &len)) { + cpr_err(cpr_vreg, "qcom,cpr-min-quot-diff-adjustment not specified\n"); + return -ENODEV; + } + + if (cpr_vreg->cpr_fuse_map_count) { + if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) + /* No matching index to use for quotient adjustment. */ + return 0; + tuple_count = cpr_vreg->cpr_fuse_map_count; + tuple_match = cpr_vreg->cpr_fuse_map_match; + } else { + tuple_count = 1; + tuple_match = 0; + } + + if (len != cpr_vreg->num_fuse_corners * tuple_count * sizeof(u32)) { + cpr_err(cpr_vreg, "qcom,cpr-min-quot-diff-adjustment length=%d is invalid\n", + len); + return -EINVAL; + } + + min_diff_quot = kcalloc(cpr_vreg->num_fuse_corners, + sizeof(*min_diff_quot), GFP_KERNEL); + if (!min_diff_quot) + return -ENOMEM; + + rc = of_property_read_u32_array(of_node, "qcom,cpr-fuse-min-quot-diff", + min_diff_quot, + cpr_vreg->num_fuse_corners); + if (rc < 0) { + cpr_err(cpr_vreg, "qcom,cpr-fuse-min-quot-diff reading failed, rc = %d\n", + rc); + goto error; + } + + for (i = CPR_FUSE_CORNER_MIN + 1; + i <= cpr_vreg->num_fuse_corners; i++) { + if ((cpr_vreg->cpr_fuse_target_quot[i] + - cpr_vreg->cpr_fuse_target_quot[i - 1]) + <= (int)min_diff_quot[i - CPR_FUSE_CORNER_MIN]) { + index = tuple_match * cpr_vreg->num_fuse_corners + + i - CPR_FUSE_CORNER_MIN; + rc = of_property_read_u32_index(of_node, + "qcom,cpr-min-quot-diff-adjustment", + index, &adjust_quot); + if (rc) { + cpr_err(cpr_vreg, "could not read qcom,cpr-min-quot-diff-adjustment index %u, rc=%d\n", + index, rc); + goto error; + } + + cpr_vreg->cpr_fuse_target_quot[i] + = cpr_vreg->cpr_fuse_target_quot[i - 1] + + adjust_quot; + cpr_info(cpr_vreg, "Corner[%d]: revised adjusted quotient = %d\n", + i, cpr_vreg->cpr_fuse_target_quot[i]); + } + } + +error: + kfree(min_diff_quot); + return rc; +} + +static int cpr_adjust_target_quots(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + int tuple_count, tuple_match, i; + u32 index; + u32 quot_adjust = 0; + int len = 0; + int rc = 0; + + if (!of_find_property(of_node, "qcom,cpr-quotient-adjustment", &len)) { + /* No static quotient adjustment needed. */ + return 0; + } + + if (cpr_vreg->cpr_fuse_map_count) { + if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) { + /* No matching index to use for quotient adjustment. */ + return 0; + } + tuple_count = cpr_vreg->cpr_fuse_map_count; + tuple_match = cpr_vreg->cpr_fuse_map_match; + } else { + tuple_count = 1; + tuple_match = 0; + } + + if (len != cpr_vreg->num_fuse_corners * tuple_count * sizeof(u32)) { + cpr_err(cpr_vreg, "qcom,cpr-quotient-adjustment length=%d is invalid\n", + len); + return -EINVAL; + } + + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) { + index = tuple_match * cpr_vreg->num_fuse_corners + + i - CPR_FUSE_CORNER_MIN; + rc = of_property_read_u32_index(of_node, + "qcom,cpr-quotient-adjustment", index, "_adjust); + if (rc) { + cpr_err(cpr_vreg, "could not read qcom,cpr-quotient-adjustment index %u, rc=%d\n", + index, rc); + return rc; + } + + if (quot_adjust) { + cpr_vreg->cpr_fuse_target_quot[i] += quot_adjust; + cpr_info(cpr_vreg, "Corner[%d]: adjusted target quot = %d\n", + i, cpr_vreg->cpr_fuse_target_quot[i]); + } + } + + rc = cpr_minimum_quot_difference_adjustment(pdev, cpr_vreg); + if (rc) + cpr_err(cpr_vreg, "failed to apply minimum quot difference rc=%d\n", + rc); + + return rc; +} + +static int cpr_check_allowed(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + char *allow_str = "qcom,cpr-allowed"; + int rc = 0, count; + int tuple_count, tuple_match; + u32 allow_status; + + if (!of_find_property(of_node, allow_str, &count)) + /* CPR is allowed for all fuse revisions. */ + return 0; + + count /= sizeof(u32); + if (cpr_vreg->cpr_fuse_map_count) { + if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) + /* No matching index to use for CPR allowed. */ + return 0; + tuple_count = cpr_vreg->cpr_fuse_map_count; + tuple_match = cpr_vreg->cpr_fuse_map_match; + } else { + tuple_count = 1; + tuple_match = 0; + } + + if (count != tuple_count) { + cpr_err(cpr_vreg, "%s count=%d is invalid\n", allow_str, + count); + return -EINVAL; + } + + rc = of_property_read_u32_index(of_node, allow_str, tuple_match, + &allow_status); + if (rc) { + cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n", + allow_str, tuple_match, rc); + return rc; + } + + if (allow_status && !cpr_vreg->cpr_fuse_disable) + cpr_vreg->cpr_fuse_disable = false; + else + cpr_vreg->cpr_fuse_disable = true; + + cpr_info(cpr_vreg, "CPR closed loop is %s for fuse revision %d\n", + cpr_vreg->cpr_fuse_disable ? "disabled" : "enabled", + cpr_vreg->cpr_fuse_revision); + + return rc; +} + +static int cpr_check_de_aging_allowed(struct cpr_regulator *cpr_vreg, + struct device *dev) +{ + struct device_node *of_node = dev->of_node; + char *allow_str = "qcom,cpr-de-aging-allowed"; + int rc = 0, count; + int tuple_count, tuple_match; + u32 allow_status = 0; + + if (!of_find_property(of_node, allow_str, &count)) { + /* CPR de-aging is not allowed for all fuse revisions. */ + return allow_status; + } + + count /= sizeof(u32); + if (cpr_vreg->cpr_fuse_map_count) { + if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) + /* No matching index to use for CPR de-aging allowed. */ + return 0; + tuple_count = cpr_vreg->cpr_fuse_map_count; + tuple_match = cpr_vreg->cpr_fuse_map_match; + } else { + tuple_count = 1; + tuple_match = 0; + } + + if (count != tuple_count) { + cpr_err(cpr_vreg, "%s count=%d is invalid\n", allow_str, + count); + return -EINVAL; + } + + rc = of_property_read_u32_index(of_node, allow_str, tuple_match, + &allow_status); + if (rc) { + cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n", + allow_str, tuple_match, rc); + return rc; + } + + cpr_info(cpr_vreg, "CPR de-aging is %s for fuse revision %d\n", + allow_status ? "allowed" : "not allowed", + cpr_vreg->cpr_fuse_revision); + + return allow_status; +} + +static int cpr_aging_init(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + struct cpr_aging_info *aging_info; + struct cpr_aging_sensor_info *sensor_info; + int num_fuse_corners = cpr_vreg->num_fuse_corners; + int i, rc = 0, len = 0, num_aging_sensors, ro_sel, bits; + u32 *aging_sensor_id, *fuse_sel, *fuse_sel_orig; + u32 sensor = 0, non_collapsible_sensor_mask = 0; + u64 efuse_val; + struct property *prop; + + if (!of_find_property(of_node, "qcom,cpr-aging-sensor-id", &len)) { + /* No CPR de-aging adjustments needed */ + return 0; + } + + if (len == 0) { + cpr_err(cpr_vreg, "qcom,cpr-aging-sensor-id property format is invalid\n"); + return -EINVAL; + } + num_aging_sensors = len / sizeof(u32); + cpr_debug(cpr_vreg, "No of aging sensors = %d\n", num_aging_sensors); + + if (cpumask_empty(&cpr_vreg->cpu_mask)) { + cpr_err(cpr_vreg, "qcom,cpr-cpus property missing\n"); + return -EINVAL; + } + + rc = cpr_check_de_aging_allowed(cpr_vreg, &pdev->dev); + if (rc < 0) { + cpr_err(cpr_vreg, "cpr_check_de_aging_allowed failed: rc=%d\n", + rc); + return rc; + } else if (rc == 0) { + /* CPR de-aging is not allowed for the current fuse combo */ + return 0; + } + + aging_info = devm_kzalloc(&pdev->dev, sizeof(*aging_info), + GFP_KERNEL); + if (!aging_info) + return -ENOMEM; + + cpr_vreg->aging_info = aging_info; + aging_info->num_aging_sensors = num_aging_sensors; + + rc = of_property_read_u32(of_node, "qcom,cpr-aging-ref-corner", + &aging_info->aging_corner); + if (rc) { + cpr_err(cpr_vreg, "qcom,cpr-aging-ref-corner missing rc=%d\n", + rc); + return rc; + } + + CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-aging-ref-voltage", + &aging_info->aging_ref_voltage, rc); + if (rc) + return rc; + + CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-max-aging-margin", + &aging_info->max_aging_margin, rc); + if (rc) + return rc; + + CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-aging-ro-scaling-factor", + &aging_info->aging_ro_kv, rc); + if (rc) + return rc; + + /* Check for DIV by 0 error */ + if (aging_info->aging_ro_kv == 0) { + cpr_err(cpr_vreg, "invalid cpr-aging-ro-scaling-factor value: %u\n", + aging_info->aging_ro_kv); + return -EINVAL; + } + + rc = of_property_read_u32_array(of_node, "qcom,cpr-ro-scaling-factor", + aging_info->cpr_ro_kv, CPR_NUM_RING_OSC); + if (rc) { + cpr_err(cpr_vreg, "qcom,cpr-ro-scaling-factor property read failed, rc = %d\n", + rc); + return rc; + } + + if (of_find_property(of_node, "qcom,cpr-non-collapsible-sensors", + &len)) { + len = len / sizeof(u32); + if (len <= 0 || len > 32) { + cpr_err(cpr_vreg, "qcom,cpr-non-collapsible-sensors has an incorrect size\n"); + return -EINVAL; + } + + for (i = 0; i < len; i++) { + rc = of_property_read_u32_index(of_node, + "qcom,cpr-non-collapsible-sensors", + i, &sensor); + if (rc) { + cpr_err(cpr_vreg, "could not read qcom,cpr-non-collapsible-sensors index %u, rc=%d\n", + i, rc); + return rc; + } + + if (sensor > 31) { + cpr_err(cpr_vreg, "invalid non-collapsible sensor = %u\n", + sensor); + return -EINVAL; + } + + non_collapsible_sensor_mask |= BIT(sensor); + } + + /* + * Bypass the sensors in collapsible domain for + * de-aging measurements + */ + aging_info->aging_sensor_bypass = + ~(non_collapsible_sensor_mask); + cpr_debug(cpr_vreg, "sensor bypass mask for aging = 0x%08x\n", + aging_info->aging_sensor_bypass); + } + + prop = of_find_property(pdev->dev.of_node, "qcom,cpr-aging-derate", + NULL); + if ((!prop) || + (prop->length != num_fuse_corners * sizeof(u32))) { + cpr_err(cpr_vreg, "qcom,cpr-aging-derate incorrectly configured\n"); + return -EINVAL; + } + + aging_sensor_id = kcalloc(num_aging_sensors, sizeof(*aging_sensor_id), + GFP_KERNEL); + fuse_sel = kcalloc(num_aging_sensors * 4, sizeof(*fuse_sel), + GFP_KERNEL); + aging_info->voltage_adjust = devm_kcalloc(&pdev->dev, + num_fuse_corners + 1, + sizeof(*aging_info->voltage_adjust), + GFP_KERNEL); + aging_info->sensor_info = devm_kcalloc(&pdev->dev, num_aging_sensors, + sizeof(*aging_info->sensor_info), + GFP_KERNEL); + aging_info->aging_derate = devm_kcalloc(&pdev->dev, + num_fuse_corners + 1, + sizeof(*aging_info->aging_derate), + GFP_KERNEL); + + if (!aging_info->aging_derate || !aging_sensor_id + || !aging_info->sensor_info || !fuse_sel + || !aging_info->voltage_adjust) + goto err; + + rc = of_property_read_u32_array(of_node, "qcom,cpr-aging-sensor-id", + aging_sensor_id, num_aging_sensors); + if (rc) { + cpr_err(cpr_vreg, "qcom,cpr-aging-sensor-id property read failed, rc = %d\n", + rc); + goto err; + } + + for (i = 0; i < num_aging_sensors; i++) + if (aging_sensor_id[i] < 0 || aging_sensor_id[i] > 31) { + cpr_err(cpr_vreg, "Invalid aging sensor id: %u\n", + aging_sensor_id[i]); + rc = -EINVAL; + goto err; + } + + rc = of_property_read_u32_array(of_node, "qcom,cpr-aging-derate", + &aging_info->aging_derate[CPR_FUSE_CORNER_MIN], + num_fuse_corners); + if (rc) { + cpr_err(cpr_vreg, "qcom,cpr-aging-derate property read failed, rc = %d\n", + rc); + goto err; + } + + rc = of_property_read_u32_array(of_node, + "qcom,cpr-fuse-aging-init-quot-diff", + fuse_sel, (num_aging_sensors * 4)); + if (rc) { + cpr_err(cpr_vreg, "qcom,cpr-fuse-aging-init-quot-diff read failed, rc = %d\n", + rc); + goto err; + } + + fuse_sel_orig = fuse_sel; + sensor_info = aging_info->sensor_info; + for (i = 0; i < num_aging_sensors; i++, sensor_info++) { + sensor_info->sensor_id = aging_sensor_id[i]; + efuse_val = cpr_read_efuse_param(cpr_vreg, fuse_sel[0], + fuse_sel[1], fuse_sel[2], fuse_sel[3]); + bits = fuse_sel[2]; + sensor_info->initial_quot_diff = ((efuse_val & BIT(bits - 1)) ? + -1 : 1) * (efuse_val & (BIT(bits - 1) - 1)); + + cpr_debug(cpr_vreg, "Age sensor[%d] Initial quot diff = %d\n", + sensor_info->sensor_id, + sensor_info->initial_quot_diff); + fuse_sel += 4; + } + + /* + * Add max aging margin here. This can be adjusted later in + * de-aging algorithm. + */ + for (i = CPR_FUSE_CORNER_MIN; i <= num_fuse_corners; i++) { + ro_sel = cpr_vreg->cpr_fuse_ro_sel[i]; + cpr_vreg->cpr_fuse_target_quot[i] += + (aging_info->cpr_ro_kv[ro_sel] + * aging_info->max_aging_margin) / 1000000; + aging_info->voltage_adjust[i] = aging_info->max_aging_margin; + cpr_info(cpr_vreg, "Corner[%d]: age margin adjusted quotient = %d\n", + i, cpr_vreg->cpr_fuse_target_quot[i]); + } + + kfree(fuse_sel_orig); +err: + kfree(aging_sensor_id); + return rc; +} + +static int cpr_cpu_map_init(struct cpr_regulator *cpr_vreg, struct device *dev) +{ + struct device_node *cpu_node; + int i, cpu; + + if (!of_find_property(dev->of_node, "qcom,cpr-cpus", + &cpr_vreg->num_adj_cpus)) { + /* No adjustments based on online cores */ + return 0; + } + cpr_vreg->num_adj_cpus /= sizeof(u32); + + cpr_vreg->adj_cpus = devm_kcalloc(dev, cpr_vreg->num_adj_cpus, + sizeof(int), GFP_KERNEL); + if (!cpr_vreg->adj_cpus) + return -ENOMEM; + + for (i = 0; i < cpr_vreg->num_adj_cpus; i++) { + cpu_node = of_parse_phandle(dev->of_node, "qcom,cpr-cpus", i); + if (!cpu_node) { + cpr_err(cpr_vreg, "could not find CPU node %d\n", i); + return -EINVAL; + } + cpr_vreg->adj_cpus[i] = -1; + for_each_possible_cpu(cpu) { + if (of_get_cpu_node(cpu, NULL) == cpu_node) { + cpr_vreg->adj_cpus[i] = cpu; + cpumask_set_cpu(cpu, &cpr_vreg->cpu_mask); + break; + } + } + of_node_put(cpu_node); + } + + return 0; +} + +static int cpr_init_cpr_efuse(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + int i, rc = 0; + bool scheme_fuse_valid = false; + bool disable_fuse_valid = false; + char *targ_quot_str; + u32 cpr_fuse_row[2]; + u32 bp_cpr_disable, bp_scheme; + size_t len; + int *bp_target_quot; + u64 fuse_bits, fuse_bits_2; + u32 *target_quot_size; + struct cpr_quot_scale *quot_scale; + + len = cpr_vreg->num_fuse_corners + 1; + + bp_target_quot = kcalloc(len, sizeof(*bp_target_quot), GFP_KERNEL); + target_quot_size = kcalloc(len, sizeof(*target_quot_size), GFP_KERNEL); + quot_scale = kcalloc(len, sizeof(*quot_scale), GFP_KERNEL); + + if (!bp_target_quot || !target_quot_size || !quot_scale) { + rc = -ENOMEM; + goto error; + } + + if (cpr_vreg->cpr_fuse_redundant) { + rc = of_property_read_u32_array(of_node, + "qcom,cpr-fuse-redun-row", + cpr_fuse_row, 2); + targ_quot_str = "qcom,cpr-fuse-redun-target-quot"; + } else { + rc = of_property_read_u32_array(of_node, "qcom,cpr-fuse-row", + cpr_fuse_row, 2); + targ_quot_str = "qcom,cpr-fuse-target-quot"; + } + if (rc) + goto error; + + rc = of_property_read_u32_array(of_node, targ_quot_str, + &bp_target_quot[CPR_FUSE_CORNER_MIN], + cpr_vreg->num_fuse_corners); + if (rc < 0) { + cpr_err(cpr_vreg, "missing %s: rc=%d\n", targ_quot_str, rc); + goto error; + } + + if (of_find_property(of_node, "qcom,cpr-fuse-target-quot-size", NULL)) { + rc = of_property_read_u32_array(of_node, + "qcom,cpr-fuse-target-quot-size", + &target_quot_size[CPR_FUSE_CORNER_MIN], + cpr_vreg->num_fuse_corners); + if (rc < 0) { + cpr_err(cpr_vreg, "error while reading qcom,cpr-fuse-target-quot-size: rc=%d\n", + rc); + goto error; + } + } else { + /* + * Default fuse quotient parameter size to match target register + * size. + */ + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; + i++) + target_quot_size[i] = CPR_FUSE_TARGET_QUOT_BITS; + } + + if (of_find_property(of_node, "qcom,cpr-fuse-target-quot-scale", + NULL)) { + for (i = 0; i < cpr_vreg->num_fuse_corners; i++) { + rc = of_property_read_u32_index(of_node, + "qcom,cpr-fuse-target-quot-scale", i * 2, + "_scale[i + CPR_FUSE_CORNER_MIN].offset); + if (rc < 0) { + cpr_err(cpr_vreg, "error while reading qcom,cpr-fuse-target-quot-scale: rc=%d\n", + rc); + goto error; + } + + rc = of_property_read_u32_index(of_node, + "qcom,cpr-fuse-target-quot-scale", i * 2 + 1, + "_scale[i + CPR_FUSE_CORNER_MIN].multiplier); + if (rc < 0) { + cpr_err(cpr_vreg, "error while reading qcom,cpr-fuse-target-quot-scale: rc=%d\n", + rc); + goto error; + } + } + } else { + /* + * In the default case, target quotients require no scaling so + * use offset = 0, multiplier = 1. + */ + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; + i++) { + quot_scale[i].offset = 0; + quot_scale[i].multiplier = 1; + } + } + + /* Read the control bits of eFuse */ + fuse_bits = cpr_read_efuse_row(cpr_vreg, cpr_fuse_row[0], + cpr_fuse_row[1]); + cpr_info(cpr_vreg, "[row:%d] = 0x%llx\n", cpr_fuse_row[0], fuse_bits); + + if (cpr_vreg->cpr_fuse_redundant) { + if (of_find_property(of_node, + "qcom,cpr-fuse-redun-bp-cpr-disable", NULL)) { + CPR_PROP_READ_U32(cpr_vreg, of_node, + "cpr-fuse-redun-bp-cpr-disable", + &bp_cpr_disable, rc); + disable_fuse_valid = true; + if (of_find_property(of_node, + "qcom,cpr-fuse-redun-bp-scheme", + NULL)) { + CPR_PROP_READ_U32(cpr_vreg, of_node, + "cpr-fuse-redun-bp-scheme", + &bp_scheme, rc); + scheme_fuse_valid = true; + } + if (rc) + goto error; + fuse_bits_2 = fuse_bits; + } else { + u32 temp_row[2]; + + /* Use original fuse if no optional property */ + if (of_find_property(of_node, + "qcom,cpr-fuse-bp-cpr-disable", NULL)) { + CPR_PROP_READ_U32(cpr_vreg, of_node, + "cpr-fuse-bp-cpr-disable", + &bp_cpr_disable, rc); + disable_fuse_valid = true; + } + if (of_find_property(of_node, + "qcom,cpr-fuse-bp-scheme", + NULL)) { + CPR_PROP_READ_U32(cpr_vreg, of_node, + "cpr-fuse-bp-scheme", + &bp_scheme, rc); + scheme_fuse_valid = true; + } + rc = of_property_read_u32_array(of_node, + "qcom,cpr-fuse-row", + temp_row, 2); + if (rc) + goto error; + + fuse_bits_2 = cpr_read_efuse_row(cpr_vreg, temp_row[0], + temp_row[1]); + cpr_info(cpr_vreg, "[original row:%d] = 0x%llx\n", + temp_row[0], fuse_bits_2); + } + } else { + if (of_find_property(of_node, "qcom,cpr-fuse-bp-cpr-disable", + NULL)) { + CPR_PROP_READ_U32(cpr_vreg, of_node, + "cpr-fuse-bp-cpr-disable", &bp_cpr_disable, rc); + disable_fuse_valid = true; + } + if (of_find_property(of_node, "qcom,cpr-fuse-bp-scheme", + NULL)) { + CPR_PROP_READ_U32(cpr_vreg, of_node, + "cpr-fuse-bp-scheme", &bp_scheme, rc); + scheme_fuse_valid = true; + } + if (rc) + goto error; + fuse_bits_2 = fuse_bits; + } + + if (disable_fuse_valid) { + cpr_vreg->cpr_fuse_disable = + (fuse_bits_2 >> bp_cpr_disable) & 0x01; + cpr_info(cpr_vreg, "CPR disable fuse = %d\n", + cpr_vreg->cpr_fuse_disable); + } else { + cpr_vreg->cpr_fuse_disable = false; + } + + if (scheme_fuse_valid) { + cpr_vreg->cpr_fuse_local = (fuse_bits_2 >> bp_scheme) & 0x01; + cpr_info(cpr_vreg, "local = %d\n", cpr_vreg->cpr_fuse_local); + } else { + cpr_vreg->cpr_fuse_local = true; + } + + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) { + cpr_vreg->cpr_fuse_target_quot[i] + = cpr_read_efuse_param(cpr_vreg, cpr_fuse_row[0], + bp_target_quot[i], target_quot_size[i], + cpr_fuse_row[1]); + /* Unpack the target quotient by scaling. */ + cpr_vreg->cpr_fuse_target_quot[i] *= quot_scale[i].multiplier; + cpr_vreg->cpr_fuse_target_quot[i] += quot_scale[i].offset; + cpr_info(cpr_vreg, + "Corner[%d]: ro_sel = %d, target quot = %d\n", i, + cpr_vreg->cpr_fuse_ro_sel[i], + cpr_vreg->cpr_fuse_target_quot[i]); + } + + rc = cpr_cpu_map_init(cpr_vreg, &pdev->dev); + if (rc) { + cpr_err(cpr_vreg, "CPR cpu map init failed: rc=%d\n", rc); + goto error; + } + + rc = cpr_aging_init(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "CPR aging init failed: rc=%d\n", rc); + goto error; + } + + rc = cpr_adjust_target_quots(pdev, cpr_vreg); + if (rc) + goto error; + + for (i = CPR_FUSE_CORNER_MIN + 1; + i <= cpr_vreg->num_fuse_corners; i++) { + if (cpr_vreg->cpr_fuse_target_quot[i] + < cpr_vreg->cpr_fuse_target_quot[i - 1] && + cpr_vreg->cpr_fuse_ro_sel[i] == + cpr_vreg->cpr_fuse_ro_sel[i - 1]) { + cpr_vreg->cpr_fuse_disable = true; + cpr_err(cpr_vreg, "invalid quotient values; permanently disabling CPR\n"); + } + } + + if (cpr_vreg->flags & FLAGS_UPLIFT_QUOT_VOLT) { + cpr_voltage_uplift_wa_inc_quot(cpr_vreg, of_node); + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; + i++) { + cpr_info(cpr_vreg, + "Corner[%d]: uplifted target quot = %d\n", + i, cpr_vreg->cpr_fuse_target_quot[i]); + } + } + + /* + * Check whether the fuse-quot-offset is defined per fuse corner. + * If it is defined, use it (quot_offset) in the calculation + * below for obtaining scaling factor per fuse corner. + */ + rc = cpr_get_fuse_quot_offset(cpr_vreg, pdev, quot_scale); + if (rc < 0) + goto error; + + rc = cpr_get_corner_quot_adjustment(cpr_vreg, &pdev->dev); + if (rc) + goto error; + + cpr_vreg->cpr_fuse_bits = fuse_bits; + if (!cpr_vreg->cpr_fuse_bits) { + cpr_vreg->cpr_fuse_disable = true; + cpr_err(cpr_vreg, + "cpr_fuse_bits == 0; permanently disabling CPR\n"); + } else if (!cpr_vreg->fuse_quot_offset) { + /* + * Check if the target quotients for the highest two fuse + * corners are too close together. + */ + int *quot = cpr_vreg->cpr_fuse_target_quot; + int highest_fuse_corner = cpr_vreg->num_fuse_corners; + u32 min_diff_quot; + bool valid_fuse = true; + + min_diff_quot = CPR_FUSE_MIN_QUOT_DIFF; + of_property_read_u32(of_node, "qcom,cpr-quot-min-diff", + &min_diff_quot); + + if (quot[highest_fuse_corner] > quot[highest_fuse_corner - 1]) { + if ((quot[highest_fuse_corner] + - quot[highest_fuse_corner - 1]) + <= min_diff_quot) + valid_fuse = false; + } else { + valid_fuse = false; + } + + if (!valid_fuse) { + cpr_vreg->cpr_fuse_disable = true; + cpr_err(cpr_vreg, "invalid quotient values; permanently disabling CPR\n"); + } + } + rc = cpr_check_allowed(pdev, cpr_vreg); + +error: + kfree(bp_target_quot); + kfree(target_quot_size); + kfree(quot_scale); + + return rc; +} + +static int cpr_init_cpr_voltages(struct cpr_regulator *cpr_vreg, + struct device *dev) +{ + int i; + int size = cpr_vreg->num_corners + 1; + + cpr_vreg->last_volt = devm_kzalloc(dev, sizeof(int) * size, GFP_KERNEL); + if (!cpr_vreg->last_volt) + return -EINVAL; + + for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) + cpr_vreg->last_volt[i] = cpr_vreg->open_loop_volt[i]; + + return 0; +} + +/* + * This function fills the virtual_limit array with voltages read from the + * prop_name device tree property if a given tuple in the property matches + * the speedbin and PVS version fuses found on the chip. Otherwise, + * it fills the virtual_limit_array with corresponding values from the + * fuse_limit_array. + */ +static int cpr_fill_override_voltage(struct cpr_regulator *cpr_vreg, + struct device *dev, const char *prop_name, const char *label, + int *virtual_limit, int *fuse_limit) +{ + int rc = 0; + int i, j, size, pos; + struct property *prop; + bool match_found = false; + size_t buflen; + char *buf; + u32 *tmp; + + prop = of_find_property(dev->of_node, prop_name, NULL); + if (!prop) + goto use_fuse_corner_limits; + + size = prop->length / sizeof(u32); + if (size == 0 || size % (cpr_vreg->num_corners + 2)) { + cpr_err(cpr_vreg, "%s property format is invalid; reusing per-fuse-corner limits\n", + prop_name); + goto use_fuse_corner_limits; + } + + tmp = kcalloc(size, sizeof(*tmp), GFP_KERNEL); + if (!tmp) + return -ENOMEM; + rc = of_property_read_u32_array(dev->of_node, prop_name, tmp, size); + if (rc < 0) { + kfree(tmp); + cpr_err(cpr_vreg, "%s reading failed, rc = %d\n", prop_name, + rc); + return rc; + } + + /* + * Get limit voltage for each virtual corner based upon the speed_bin + * and pvs_version values. + */ + for (i = 0; i < size; i += cpr_vreg->num_corners + 2) { + if (tmp[i] != cpr_vreg->speed_bin && + tmp[i] != FUSE_PARAM_MATCH_ANY) + continue; + if (tmp[i + 1] != cpr_vreg->pvs_version && + tmp[i + 1] != FUSE_PARAM_MATCH_ANY) + continue; + for (j = CPR_CORNER_MIN; j <= cpr_vreg->num_corners; j++) + virtual_limit[j] = tmp[i + 2 + j - CPR_FUSE_CORNER_MIN]; + match_found = true; + break; + } + kfree(tmp); + + if (!match_found) + goto use_fuse_corner_limits; + + /* + * Log per-virtual-corner voltage limits since they are useful for + * baseline CPR debugging. + */ + buflen = cpr_vreg->num_corners * (MAX_CHARS_PER_INT + 2) * sizeof(*buf); + buf = kzalloc(buflen, GFP_KERNEL); + if (buf == NULL) { + cpr_err(cpr_vreg, "Could not allocate memory for corner limit voltage logging\n"); + return 0; + } + + for (i = CPR_CORNER_MIN, pos = 0; i <= cpr_vreg->num_corners; i++) + pos += scnprintf(buf + pos, buflen - pos, "%d%s", + virtual_limit[i], i < cpr_vreg->num_corners ? " " : ""); + cpr_info(cpr_vreg, "%s override voltage: [%s] uV\n", label, buf); + kfree(buf); + + return rc; + +use_fuse_corner_limits: + for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) + virtual_limit[i] = fuse_limit[cpr_vreg->corner_map[i]]; + return rc; +} + +/* + * This function loads per-virtual-corner ceiling and floor voltages from device + * tree if their respective device tree properties are present. These limits + * override those found in the per-fuse-corner arrays fuse_ceiling_volt and + * fuse_floor_volt. + */ +static int cpr_init_ceiling_floor_override_voltages( + struct cpr_regulator *cpr_vreg, struct device *dev) +{ + int rc, i; + int size = cpr_vreg->num_corners + 1; + + cpr_vreg->ceiling_volt = devm_kzalloc(dev, sizeof(int) * size, + GFP_KERNEL); + cpr_vreg->floor_volt = devm_kzalloc(dev, sizeof(int) * size, + GFP_KERNEL); + cpr_vreg->cpr_max_ceiling = devm_kzalloc(dev, sizeof(int) * size, + GFP_KERNEL); + if (!cpr_vreg->ceiling_volt || !cpr_vreg->floor_volt || + !cpr_vreg->cpr_max_ceiling) + return -ENOMEM; + + rc = cpr_fill_override_voltage(cpr_vreg, dev, + "qcom,cpr-voltage-ceiling-override", "ceiling", + cpr_vreg->ceiling_volt, cpr_vreg->fuse_ceiling_volt); + if (rc) + return rc; + + rc = cpr_fill_override_voltage(cpr_vreg, dev, + "qcom,cpr-voltage-floor-override", "floor", + cpr_vreg->floor_volt, cpr_vreg->fuse_floor_volt); + if (rc) + return rc; + + for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) { + if (cpr_vreg->floor_volt[i] > cpr_vreg->ceiling_volt[i]) { + cpr_err(cpr_vreg, "virtual corner %d floor=%d uV > ceiling=%d uV\n", + i, cpr_vreg->floor_volt[i], + cpr_vreg->ceiling_volt[i]); + return -EINVAL; + } + + if (cpr_vreg->ceiling_max < cpr_vreg->ceiling_volt[i]) + cpr_vreg->ceiling_max = cpr_vreg->ceiling_volt[i]; + cpr_vreg->cpr_max_ceiling[i] = cpr_vreg->ceiling_volt[i]; + } + + return rc; +} + +/* + * This function computes the per-virtual-corner floor voltages from + * per-virtual-corner ceiling voltages with an offset specified by a + * device-tree property. This must be called after open-loop voltage + * scaling, floor_volt array loading and the ceiling voltage is + * conditionally reduced to the open-loop voltage. It selects the + * maximum value between the calculated floor voltage values and + * the floor_volt array values and stores them in the floor_volt array. + */ +static int cpr_init_floor_to_ceiling_range( + struct cpr_regulator *cpr_vreg, struct device *dev) +{ + int rc, i, tuple_count, tuple_match, len, pos; + u32 index, floor_volt_adjust = 0; + char *prop_str, *buf; + size_t buflen; + + prop_str = "qcom,cpr-floor-to-ceiling-max-range"; + + if (!of_find_property(dev->of_node, prop_str, &len)) + return 0; + + if (cpr_vreg->cpr_fuse_map_count) { + if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) { + /* + * No matching index to use for floor-to-ceiling + * max range. + */ + return 0; + } + tuple_count = cpr_vreg->cpr_fuse_map_count; + tuple_match = cpr_vreg->cpr_fuse_map_match; + } else { + tuple_count = 1; + tuple_match = 0; + } + + if (len != cpr_vreg->num_corners * tuple_count * sizeof(u32)) { + cpr_err(cpr_vreg, "%s length=%d is invalid\n", prop_str, len); + return -EINVAL; + } + + for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) { + index = tuple_match * cpr_vreg->num_corners + + i - CPR_CORNER_MIN; + rc = of_property_read_u32_index(dev->of_node, prop_str, + index, &floor_volt_adjust); + if (rc) { + cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n", + prop_str, index, rc); + return rc; + } + + if ((int)floor_volt_adjust >= 0) { + cpr_vreg->floor_volt[i] = max(cpr_vreg->floor_volt[i], + (cpr_vreg->ceiling_volt[i] + - (int)floor_volt_adjust)); + cpr_vreg->floor_volt[i] + = DIV_ROUND_UP(cpr_vreg->floor_volt[i], + cpr_vreg->step_volt) * + cpr_vreg->step_volt; + if (cpr_vreg->open_loop_volt[i] + < cpr_vreg->floor_volt[i]) + cpr_vreg->open_loop_volt[i] + = cpr_vreg->floor_volt[i]; + } + } + + /* + * Log per-virtual-corner voltage limits resulted after considering the + * floor-to-ceiling max range since they are useful for baseline CPR + * debugging. + */ + buflen = cpr_vreg->num_corners * (MAX_CHARS_PER_INT + 2) * sizeof(*buf); + buf = kzalloc(buflen, GFP_KERNEL); + if (buf == NULL) { + cpr_err(cpr_vreg, "Could not allocate memory for corner limit voltage logging\n"); + return 0; + } + + for (i = CPR_CORNER_MIN, pos = 0; i <= cpr_vreg->num_corners; i++) + pos += scnprintf(buf + pos, buflen - pos, "%d%s", + cpr_vreg->floor_volt[i], + i < cpr_vreg->num_corners ? " " : ""); + cpr_info(cpr_vreg, "Final floor override voltages: [%s] uV\n", buf); + kfree(buf); + + return 0; +} + +static int cpr_init_step_quotient(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + int len = 0; + u32 step_quot[CPR_NUM_RING_OSC]; + int i, rc; + + if (!of_find_property(of_node, "qcom,cpr-step-quotient", &len)) { + cpr_err(cpr_vreg, "qcom,cpr-step-quotient property missing\n"); + return -EINVAL; + } + + if (len == sizeof(u32)) { + /* Single step quotient used for all ring oscillators. */ + rc = of_property_read_u32(of_node, "qcom,cpr-step-quotient", + step_quot); + if (rc) { + cpr_err(cpr_vreg, "could not read qcom,cpr-step-quotient, rc=%d\n", + rc); + return rc; + } + + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; + i++) + cpr_vreg->step_quotient[i] = step_quot[0]; + } else if (len == sizeof(u32) * CPR_NUM_RING_OSC) { + /* Unique step quotient used per ring oscillator. */ + rc = of_property_read_u32_array(of_node, + "qcom,cpr-step-quotient", step_quot, CPR_NUM_RING_OSC); + if (rc) { + cpr_err(cpr_vreg, "could not read qcom,cpr-step-quotient, rc=%d\n", + rc); + return rc; + } + + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; + i++) + cpr_vreg->step_quotient[i] + = step_quot[cpr_vreg->cpr_fuse_ro_sel[i]]; + } else { + cpr_err(cpr_vreg, "qcom,cpr-step-quotient has invalid length=%d\n", + len); + return -EINVAL; + } + + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) + cpr_debug(cpr_vreg, "step_quotient[%d]=%u\n", i, + cpr_vreg->step_quotient[i]); + + return 0; +} + +static int cpr_init_cpr_parameters(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + int rc = 0; + + CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-ref-clk", + &cpr_vreg->ref_clk_khz, rc); + if (rc) + return rc; + CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-timer-delay", + &cpr_vreg->timer_delay_us, rc); + if (rc) + return rc; + CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-timer-cons-up", + &cpr_vreg->timer_cons_up, rc); + if (rc) + return rc; + CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-timer-cons-down", + &cpr_vreg->timer_cons_down, rc); + if (rc) + return rc; + CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-irq-line", + &cpr_vreg->irq_line, rc); + if (rc) + return rc; + + rc = cpr_init_step_quotient(pdev, cpr_vreg); + if (rc) + return rc; + + CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-up-threshold", + &cpr_vreg->up_threshold, rc); + if (rc) + return rc; + CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-down-threshold", + &cpr_vreg->down_threshold, rc); + if (rc) + return rc; + cpr_info(cpr_vreg, "up threshold = %u, down threshold = %u\n", + cpr_vreg->up_threshold, cpr_vreg->down_threshold); + + CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-idle-clocks", + &cpr_vreg->idle_clocks, rc); + if (rc) + return rc; + CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-gcnt-time", + &cpr_vreg->gcnt_time_us, rc); + if (rc) + return rc; + CPR_PROP_READ_U32(cpr_vreg, of_node, "vdd-apc-step-up-limit", + &cpr_vreg->vdd_apc_step_up_limit, rc); + if (rc) + return rc; + CPR_PROP_READ_U32(cpr_vreg, of_node, "vdd-apc-step-down-limit", + &cpr_vreg->vdd_apc_step_down_limit, rc); + if (rc) + return rc; + + rc = of_property_read_u32(of_node, "qcom,cpr-clamp-timer-interval", + &cpr_vreg->clamp_timer_interval); + if (rc && rc != -EINVAL) { + cpr_err(cpr_vreg, + "error reading qcom,cpr-clamp-timer-interval, rc=%d\n", + rc); + return rc; + } + + cpr_vreg->clamp_timer_interval = min(cpr_vreg->clamp_timer_interval, + (u32)RBIF_TIMER_ADJ_CLAMP_INT_MASK); + + /* Init module parameter with the DT value */ + cpr_vreg->enable = of_property_read_bool(of_node, "qcom,cpr-enable"); + cpr_info(cpr_vreg, "CPR is %s by default.\n", + cpr_vreg->enable ? "enabled" : "disabled"); + + return 0; +} + +static void cpr_pm_disable(struct cpr_regulator *cpr_vreg, bool disable) +{ + u32 reg_val; + + if (cpr_vreg->is_cpr_suspended) + return; + + reg_val = cpr_read(cpr_vreg, REG_RBCPR_CTL); + + if (disable) { + /* Proceed only if CPR is enabled */ + if (!(reg_val & RBCPR_CTL_LOOP_EN)) + return; + cpr_ctl_disable(cpr_vreg); + cpr_vreg->cpr_disabled_in_pc = true; + } else { + /* Proceed only if CPR was disabled in PM_ENTER */ + if (!cpr_vreg->cpr_disabled_in_pc) + return; + cpr_vreg->cpr_disabled_in_pc = false; + cpr_ctl_enable(cpr_vreg, cpr_vreg->corner); + } + + /* Make sure register write is complete */ + mb(); +} + +static int cpr_pm_callback(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct cpr_regulator *cpr_vreg = container_of(nb, + struct cpr_regulator, pm_notifier); + + if (action != CPU_PM_ENTER && action != CPU_PM_ENTER_FAILED && + action != CPU_PM_EXIT) + return NOTIFY_OK; + + switch (action) { + case CPU_PM_ENTER: + cpr_pm_disable(cpr_vreg, true); + break; + case CPU_PM_ENTER_FAILED: + case CPU_PM_EXIT: + cpr_pm_disable(cpr_vreg, false); + break; + } + + return NOTIFY_OK; +} + +static int cpr_init_pm_notification(struct cpr_regulator *cpr_vreg) +{ + int rc; + + /* enabled only for single-core designs */ + if (cpr_vreg->num_adj_cpus != 1) { + pr_warn("qcom,cpr-cpus not defined or invalid %d\n", + cpr_vreg->num_adj_cpus); + return 0; + } + + cpr_vreg->pm_notifier.notifier_call = cpr_pm_callback; + rc = cpu_pm_register_notifier(&cpr_vreg->pm_notifier); + if (rc) + cpr_err(cpr_vreg, "Unable to register pm notifier rc=%d\n", rc); + + return rc; +} + +static int cpr_rpm_apc_init(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + int rc, len = 0; + struct device_node *of_node = pdev->dev.of_node; + + if (!of_find_property(of_node, "rpm-apc-supply", NULL)) + return 0; + + cpr_vreg->rpm_apc_vreg = devm_regulator_get(&pdev->dev, "rpm-apc"); + if (IS_ERR_OR_NULL(cpr_vreg->rpm_apc_vreg)) { + rc = PTR_RET(cpr_vreg->rpm_apc_vreg); + if (rc != -EPROBE_DEFER) + cpr_err(cpr_vreg, "devm_regulator_get: rpm-apc: rc=%d\n", + rc); + return rc; + } + + if (!of_find_property(of_node, "qcom,rpm-apc-corner-map", &len)) { + cpr_err(cpr_vreg, + "qcom,rpm-apc-corner-map missing:\n"); + return -EINVAL; + } + if (len != cpr_vreg->num_corners * sizeof(u32)) { + cpr_err(cpr_vreg, + "qcom,rpm-apc-corner-map length=%d is invalid: required:%d\n", + len, cpr_vreg->num_corners); + return -EINVAL; + } + + cpr_vreg->rpm_apc_corner_map = devm_kzalloc(&pdev->dev, + (cpr_vreg->num_corners + 1) * + sizeof(*cpr_vreg->rpm_apc_corner_map), GFP_KERNEL); + if (!cpr_vreg->rpm_apc_corner_map) + return -ENOMEM; + + rc = of_property_read_u32_array(of_node, "qcom,rpm-apc-corner-map", + &cpr_vreg->rpm_apc_corner_map[1], cpr_vreg->num_corners); + if (rc) + cpr_err(cpr_vreg, "read qcom,rpm-apc-corner-map failed, rc = %d\n", + rc); + + return rc; +} + +static int cpr_parse_vdd_mode_config(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + int rc, len = 0, i, mode; + struct device_node *of_node = pdev->dev.of_node; + const char *prop_str = "qcom,cpr-vdd-mode-map"; + + if (!of_find_property(of_node, prop_str, &len)) + return 0; + + if (len != cpr_vreg->num_corners * sizeof(u32)) { + cpr_err(cpr_vreg, "%s length=%d is invalid: required:%d\n", + prop_str, len, cpr_vreg->num_corners); + return -EINVAL; + } + + cpr_vreg->vdd_mode_map = devm_kcalloc(&pdev->dev, + cpr_vreg->num_corners + 1, + sizeof(*cpr_vreg->vdd_mode_map), + GFP_KERNEL); + if (!cpr_vreg->vdd_mode_map) + return -ENOMEM; + + for (i = 0; i < cpr_vreg->num_corners; i++) { + rc = of_property_read_u32_index(of_node, prop_str, i, &mode); + if (rc) { + cpr_err(cpr_vreg, "read %s index %d failed, rc = %d\n", + prop_str, i, rc); + return rc; + } + cpr_vreg->vdd_mode_map[i + CPR_CORNER_MIN] + = mode ? REGULATOR_MODE_NORMAL + : REGULATOR_MODE_IDLE; + } + + return rc; +} + +static int cpr_vsens_init(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + int rc = 0, len = 0; + struct device_node *of_node = pdev->dev.of_node; + + if (of_find_property(of_node, "vdd-vsens-voltage-supply", NULL)) { + cpr_vreg->vdd_vsens_voltage = devm_regulator_get(&pdev->dev, + "vdd-vsens-voltage"); + if (IS_ERR_OR_NULL(cpr_vreg->vdd_vsens_voltage)) { + rc = PTR_ERR(cpr_vreg->vdd_vsens_voltage); + cpr_vreg->vdd_vsens_voltage = NULL; + if (rc == -EPROBE_DEFER) + return rc; + /* device not found */ + cpr_debug(cpr_vreg, "regulator_get: vdd-vsens-voltage: rc=%d\n", + rc); + return 0; + } + } + + if (of_find_property(of_node, "vdd-vsens-corner-supply", NULL)) { + cpr_vreg->vdd_vsens_corner = devm_regulator_get(&pdev->dev, + "vdd-vsens-corner"); + if (IS_ERR_OR_NULL(cpr_vreg->vdd_vsens_corner)) { + rc = PTR_ERR(cpr_vreg->vdd_vsens_corner); + cpr_vreg->vdd_vsens_corner = NULL; + if (rc == -EPROBE_DEFER) + return rc; + /* device not found */ + cpr_debug(cpr_vreg, "regulator_get: vdd-vsens-corner: rc=%d\n", + rc); + return 0; + } + + if (!of_find_property(of_node, "qcom,vsens-corner-map", &len)) { + cpr_err(cpr_vreg, "qcom,vsens-corner-map missing\n"); + return -EINVAL; + } + + if (len != cpr_vreg->num_fuse_corners * sizeof(u32)) { + cpr_err(cpr_vreg, "qcom,vsens-corner-map length=%d is invalid: required:%d\n", + len, cpr_vreg->num_fuse_corners); + return -EINVAL; + } + + cpr_vreg->vsens_corner_map = devm_kcalloc(&pdev->dev, + (cpr_vreg->num_fuse_corners + 1), + sizeof(*cpr_vreg->vsens_corner_map), GFP_KERNEL); + if (!cpr_vreg->vsens_corner_map) + return -ENOMEM; + + rc = of_property_read_u32_array(of_node, + "qcom,vsens-corner-map", + &cpr_vreg->vsens_corner_map[1], + cpr_vreg->num_fuse_corners); + if (rc) + cpr_err(cpr_vreg, "read qcom,vsens-corner-map failed, rc = %d\n", + rc); + } + + return rc; +} + +static int cpr_init_cpr(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct resource *res; + int rc = 0; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rbcpr_clk"); + if (res && res->start) + cpr_vreg->rbcpr_clk_addr = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rbcpr"); + if (!res || !res->start) { + cpr_err(cpr_vreg, "missing rbcpr address: res=%p\n", res); + return -EINVAL; + } + cpr_vreg->rbcpr_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + + /* Init CPR configuration parameters */ + rc = cpr_init_cpr_parameters(pdev, cpr_vreg); + if (rc) + return rc; + + rc = cpr_init_cpr_efuse(pdev, cpr_vreg); + if (rc) + return rc; + + /* Load per corner ceiling and floor voltages if they exist. */ + rc = cpr_init_ceiling_floor_override_voltages(cpr_vreg, &pdev->dev); + if (rc) + return rc; + + /* + * Limit open loop voltages based upon per corner ceiling and floor + * voltages. + */ + rc = cpr_limit_open_loop_voltage(cpr_vreg); + if (rc) + return rc; + + /* + * Fill the OPP table for this device with virtual voltage corner to + * open-loop voltage pairs. + */ + rc = cpr_populate_opp_table(cpr_vreg, &pdev->dev); + if (rc) + return rc; + + /* Reduce the ceiling voltage if allowed. */ + rc = cpr_reduce_ceiling_voltage(cpr_vreg, &pdev->dev); + if (rc) + return rc; + + /* Load CPR floor to ceiling range if exist. */ + rc = cpr_init_floor_to_ceiling_range(cpr_vreg, &pdev->dev); + if (rc) + return rc; + + /* Init all voltage set points of APC regulator for CPR */ + rc = cpr_init_cpr_voltages(cpr_vreg, &pdev->dev); + if (rc) + return rc; + + /* Get and Init interrupt */ + cpr_vreg->cpr_irq = platform_get_irq(pdev, 0); + if (!cpr_vreg->cpr_irq) { + cpr_err(cpr_vreg, "missing CPR IRQ\n"); + return -EINVAL; + } + + /* Configure CPR HW but keep it disabled */ + rc = cpr_config(cpr_vreg, &pdev->dev); + if (rc) + return rc; + + rc = request_threaded_irq(cpr_vreg->cpr_irq, NULL, cpr_irq_handler, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, "cpr", + cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "CPR: request irq failed for IRQ %d\n", + cpr_vreg->cpr_irq); + return rc; + } + + return 0; +} + +/* + * Create a set of virtual fuse rows if optional device tree properties are + * present. + */ +static int cpr_remap_efuse_data(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + struct property *prop; + u64 fuse_param; + u32 *temp; + int size, rc, i, bits, in_row, in_bit, out_row, out_bit; + + prop = of_find_property(of_node, "qcom,fuse-remap-source", NULL); + if (!prop) { + /* No fuse remapping needed. */ + return 0; + } + + size = prop->length / sizeof(u32); + if (size == 0 || size % 4) { + cpr_err(cpr_vreg, "qcom,fuse-remap-source has invalid size=%d\n", + size); + return -EINVAL; + } + size /= 4; + + rc = of_property_read_u32(of_node, "qcom,fuse-remap-base-row", + &cpr_vreg->remapped_row_base); + if (rc) { + cpr_err(cpr_vreg, "could not read qcom,fuse-remap-base-row, rc=%d\n", + rc); + return rc; + } + + temp = kzalloc(sizeof(*temp) * size * 4, GFP_KERNEL); + if (!temp) + return -ENOMEM; + + rc = of_property_read_u32_array(of_node, "qcom,fuse-remap-source", temp, + size * 4); + if (rc) { + cpr_err(cpr_vreg, "could not read qcom,fuse-remap-source, rc=%d\n", + rc); + goto done; + } + + /* + * Format of tuples in qcom,fuse-remap-source property: + * + */ + for (i = 0, bits = 0; i < size; i++) + bits += temp[i * 4 + 2]; + + cpr_vreg->num_remapped_rows = DIV_ROUND_UP(bits, 64); + cpr_vreg->remapped_row = devm_kzalloc(&pdev->dev, + sizeof(*cpr_vreg->remapped_row) * cpr_vreg->num_remapped_rows, + GFP_KERNEL); + if (!cpr_vreg->remapped_row) { + rc = -ENOMEM; + goto done; + } + + for (i = 0, out_row = 0, out_bit = 0; i < size; i++) { + in_row = temp[i * 4]; + in_bit = temp[i * 4 + 1]; + bits = temp[i * 4 + 2]; + + while (bits > 64) { + fuse_param = cpr_read_efuse_param(cpr_vreg, in_row, + in_bit, 64, temp[i * 4 + 3]); + + cpr_vreg->remapped_row[out_row++] + |= fuse_param << out_bit; + if (out_bit > 0) + cpr_vreg->remapped_row[out_row] + |= fuse_param >> (64 - out_bit); + + bits -= 64; + in_bit += 64; + } + + fuse_param = cpr_read_efuse_param(cpr_vreg, in_row, in_bit, + bits, temp[i * 4 + 3]); + + cpr_vreg->remapped_row[out_row] |= fuse_param << out_bit; + if (bits < 64 - out_bit) { + out_bit += bits; + } else { + out_row++; + if (out_bit > 0) + cpr_vreg->remapped_row[out_row] + |= fuse_param >> (64 - out_bit); + out_bit = bits - (64 - out_bit); + } + } + +done: + kfree(temp); + return rc; +} + +static int cpr_efuse_init(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct resource *res; + int len; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "efuse_addr"); + if (!res || !res->start) { + cpr_err(cpr_vreg, "efuse_addr missing: res=%p\n", res); + return -EINVAL; + } + + cpr_vreg->efuse_addr = res->start; + len = resource_size(res); + + cpr_info(cpr_vreg, "efuse_addr = %pa (len=0x%x)\n", &res->start, len); + + cpr_vreg->efuse_base = ioremap(cpr_vreg->efuse_addr, len); + if (!cpr_vreg->efuse_base) { + cpr_err(cpr_vreg, "Unable to map efuse_addr %pa\n", + &cpr_vreg->efuse_addr); + return -EINVAL; + } + + return 0; +} + +static void cpr_efuse_free(struct cpr_regulator *cpr_vreg) +{ + iounmap(cpr_vreg->efuse_base); +} + +static void cpr_parse_cond_min_volt_fuse(struct cpr_regulator *cpr_vreg, + struct device_node *of_node) +{ + int rc; + u32 fuse_sel[5]; + /* + * Restrict all pvs corner voltages to a minimum value of + * qcom,cpr-cond-min-voltage if the fuse defined in + * qcom,cpr-fuse-cond-min-volt-sel does not read back with + * the expected value. + */ + rc = of_property_read_u32_array(of_node, + "qcom,cpr-fuse-cond-min-volt-sel", fuse_sel, 5); + if (!rc) { + if (!cpr_fuse_is_setting_expected(cpr_vreg, fuse_sel)) + cpr_vreg->flags |= FLAGS_SET_MIN_VOLTAGE; + } +} + +static void cpr_parse_speed_bin_fuse(struct cpr_regulator *cpr_vreg, + struct device_node *of_node) +{ + int rc; + u64 fuse_bits; + u32 fuse_sel[4]; + u32 speed_bits; + + rc = of_property_read_u32_array(of_node, + "qcom,speed-bin-fuse-sel", fuse_sel, 4); + + if (!rc) { + fuse_bits = cpr_read_efuse_row(cpr_vreg, + fuse_sel[0], fuse_sel[3]); + speed_bits = (fuse_bits >> fuse_sel[1]) & + ((1 << fuse_sel[2]) - 1); + cpr_info(cpr_vreg, "[row: %d]: 0x%llx, speed_bits = %d\n", + fuse_sel[0], fuse_bits, speed_bits); + cpr_vreg->speed_bin = speed_bits; + } else { + cpr_vreg->speed_bin = SPEED_BIN_NONE; + } +} + +static int cpr_voltage_uplift_enable_check(struct cpr_regulator *cpr_vreg, + struct device_node *of_node) +{ + int rc; + u32 fuse_sel[5]; + u32 uplift_speed_bin; + + rc = of_property_read_u32_array(of_node, + "qcom,cpr-fuse-uplift-sel", fuse_sel, 5); + if (!rc) { + rc = of_property_read_u32(of_node, + "qcom,cpr-uplift-speed-bin", + &uplift_speed_bin); + if (rc < 0) { + cpr_err(cpr_vreg, + "qcom,cpr-uplift-speed-bin missing\n"); + return rc; + } + if (cpr_fuse_is_setting_expected(cpr_vreg, fuse_sel) + && (uplift_speed_bin == cpr_vreg->speed_bin) + && !(cpr_vreg->flags & FLAGS_SET_MIN_VOLTAGE)) { + cpr_vreg->flags |= FLAGS_UPLIFT_QUOT_VOLT; + } + } + return 0; +} + +/* + * Read in the number of fuse corners and then allocate memory for arrays that + * are sized based upon the number of fuse corners. + */ +static int cpr_fuse_corner_array_alloc(struct device *dev, + struct cpr_regulator *cpr_vreg) +{ + int rc; + size_t len; + + rc = of_property_read_u32(dev->of_node, "qcom,cpr-fuse-corners", + &cpr_vreg->num_fuse_corners); + if (rc < 0) { + cpr_err(cpr_vreg, "qcom,cpr-fuse-corners missing: rc=%d\n", rc); + return rc; + } + + if (cpr_vreg->num_fuse_corners < CPR_FUSE_CORNER_MIN + || cpr_vreg->num_fuse_corners > CPR_FUSE_CORNER_LIMIT) { + cpr_err(cpr_vreg, "corner count=%d is invalid\n", + cpr_vreg->num_fuse_corners); + return -EINVAL; + } + + /* + * The arrays sized based on the fuse corner count ignore element 0 + * in order to simplify indexing throughout the driver since min_uV = 0 + * cannot be passed into a set_voltage() callback. + */ + len = cpr_vreg->num_fuse_corners + 1; + + cpr_vreg->pvs_corner_v = devm_kzalloc(dev, + len * sizeof(*cpr_vreg->pvs_corner_v), GFP_KERNEL); + cpr_vreg->cpr_fuse_target_quot = devm_kzalloc(dev, + len * sizeof(*cpr_vreg->cpr_fuse_target_quot), GFP_KERNEL); + cpr_vreg->cpr_fuse_ro_sel = devm_kzalloc(dev, + len * sizeof(*cpr_vreg->cpr_fuse_ro_sel), GFP_KERNEL); + cpr_vreg->fuse_ceiling_volt = devm_kzalloc(dev, + len * (sizeof(*cpr_vreg->fuse_ceiling_volt)), GFP_KERNEL); + cpr_vreg->fuse_floor_volt = devm_kzalloc(dev, + len * (sizeof(*cpr_vreg->fuse_floor_volt)), GFP_KERNEL); + cpr_vreg->step_quotient = devm_kzalloc(dev, + len * sizeof(*cpr_vreg->step_quotient), GFP_KERNEL); + + if (cpr_vreg->pvs_corner_v == NULL || cpr_vreg->cpr_fuse_ro_sel == NULL + || cpr_vreg->fuse_ceiling_volt == NULL + || cpr_vreg->fuse_floor_volt == NULL + || cpr_vreg->cpr_fuse_target_quot == NULL + || cpr_vreg->step_quotient == NULL) + return -ENOMEM; + + return 0; +} + +static int cpr_voltage_plan_init(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + struct device_node *of_node = pdev->dev.of_node; + int rc, i; + u32 min_uv = 0; + + rc = of_property_read_u32_array(of_node, "qcom,cpr-voltage-ceiling", + &cpr_vreg->fuse_ceiling_volt[CPR_FUSE_CORNER_MIN], + cpr_vreg->num_fuse_corners); + if (rc < 0) { + cpr_err(cpr_vreg, "cpr-voltage-ceiling missing: rc=%d\n", rc); + return rc; + } + + rc = of_property_read_u32_array(of_node, "qcom,cpr-voltage-floor", + &cpr_vreg->fuse_floor_volt[CPR_FUSE_CORNER_MIN], + cpr_vreg->num_fuse_corners); + if (rc < 0) { + cpr_err(cpr_vreg, "cpr-voltage-floor missing: rc=%d\n", rc); + return rc; + } + + cpr_parse_cond_min_volt_fuse(cpr_vreg, of_node); + rc = cpr_voltage_uplift_enable_check(cpr_vreg, of_node); + if (rc < 0) { + cpr_err(cpr_vreg, "voltage uplift enable check failed, %d\n", + rc); + return rc; + } + if (cpr_vreg->flags & FLAGS_SET_MIN_VOLTAGE) { + of_property_read_u32(of_node, "qcom,cpr-cond-min-voltage", + &min_uv); + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; + i++) + if (cpr_vreg->fuse_ceiling_volt[i] < min_uv) { + cpr_vreg->fuse_ceiling_volt[i] = min_uv; + cpr_vreg->fuse_floor_volt[i] = min_uv; + } else if (cpr_vreg->fuse_floor_volt[i] < min_uv) { + cpr_vreg->fuse_floor_volt[i] = min_uv; + } + } + + return 0; +} + +static int cpr_mem_acc_init(struct platform_device *pdev, + struct cpr_regulator *cpr_vreg) +{ + int rc, size; + struct property *prop; + char *corner_map_str; + + if (of_find_property(pdev->dev.of_node, "mem-acc-supply", NULL)) { + cpr_vreg->mem_acc_vreg = devm_regulator_get(&pdev->dev, + "mem-acc"); + if (IS_ERR_OR_NULL(cpr_vreg->mem_acc_vreg)) { + rc = PTR_RET(cpr_vreg->mem_acc_vreg); + if (rc != -EPROBE_DEFER) + cpr_err(cpr_vreg, + "devm_regulator_get: mem-acc: rc=%d\n", + rc); + return rc; + } + } + + corner_map_str = "qcom,mem-acc-corner-map"; + prop = of_find_property(pdev->dev.of_node, corner_map_str, NULL); + if (!prop) { + corner_map_str = "qcom,cpr-corner-map"; + prop = of_find_property(pdev->dev.of_node, corner_map_str, + NULL); + if (!prop) { + cpr_err(cpr_vreg, "qcom,cpr-corner-map missing\n"); + return -EINVAL; + } + } + + size = prop->length / sizeof(u32); + cpr_vreg->mem_acc_corner_map = devm_kzalloc(&pdev->dev, + sizeof(int) * (size + 1), + GFP_KERNEL); + + rc = of_property_read_u32_array(pdev->dev.of_node, corner_map_str, + &cpr_vreg->mem_acc_corner_map[CPR_FUSE_CORNER_MIN], + size); + if (rc) { + cpr_err(cpr_vreg, "%s missing, rc = %d\n", corner_map_str, rc); + return rc; + } + + return 0; +} + +#if defined(CONFIG_DEBUG_FS) + +static int cpr_enable_set(void *data, u64 val) +{ + struct cpr_regulator *cpr_vreg = data; + bool old_cpr_enable; + + mutex_lock(&cpr_vreg->cpr_mutex); + + old_cpr_enable = cpr_vreg->enable; + cpr_vreg->enable = val; + + if (old_cpr_enable == cpr_vreg->enable) + goto _exit; + + if (cpr_vreg->enable && cpr_vreg->cpr_fuse_disable) { + cpr_info(cpr_vreg, + "CPR permanently disabled due to fuse values\n"); + cpr_vreg->enable = false; + goto _exit; + } + + cpr_debug(cpr_vreg, "%s CPR [corner=%d, fuse_corner=%d]\n", + cpr_vreg->enable ? "enabling" : "disabling", + cpr_vreg->corner, cpr_vreg->corner_map[cpr_vreg->corner]); + + if (cpr_vreg->corner) { + if (cpr_vreg->enable) { + cpr_ctl_disable(cpr_vreg); + cpr_irq_clr(cpr_vreg); + cpr_corner_restore(cpr_vreg, cpr_vreg->corner); + cpr_ctl_enable(cpr_vreg, cpr_vreg->corner); + } else { + cpr_ctl_disable(cpr_vreg); + cpr_irq_set(cpr_vreg, 0); + } + } + +_exit: + mutex_unlock(&cpr_vreg->cpr_mutex); + + return 0; +} + +static int cpr_enable_get(void *data, u64 *val) +{ + struct cpr_regulator *cpr_vreg = data; + + *val = cpr_vreg->enable; + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(cpr_enable_fops, cpr_enable_get, cpr_enable_set, + "%llu\n"); + +static int cpr_get_cpr_ceiling(void *data, u64 *val) +{ + struct cpr_regulator *cpr_vreg = data; + + *val = cpr_vreg->ceiling_volt[cpr_vreg->corner]; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(cpr_ceiling_fops, cpr_get_cpr_ceiling, NULL, + "%llu\n"); + +static int cpr_get_cpr_floor(void *data, u64 *val) +{ + struct cpr_regulator *cpr_vreg = data; + + *val = cpr_vreg->floor_volt[cpr_vreg->corner]; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(cpr_floor_fops, cpr_get_cpr_floor, NULL, + "%llu\n"); + +static int cpr_get_cpr_max_ceiling(void *data, u64 *val) +{ + struct cpr_regulator *cpr_vreg = data; + + *val = cpr_vreg->cpr_max_ceiling[cpr_vreg->corner]; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(cpr_max_ceiling_fops, cpr_get_cpr_max_ceiling, NULL, + "%llu\n"); + +static ssize_t cpr_debug_info_read(struct file *file, char __user *buff, + size_t count, loff_t *ppos) +{ + struct cpr_regulator *cpr_vreg = file->private_data; + char *debugfs_buf; + ssize_t len, ret = 0; + u32 gcnt, ro_sel, ctl, irq_status, reg, error_steps; + u32 step_dn, step_up, error, error_lt0, busy; + int fuse_corner; + + debugfs_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!debugfs_buf) + return -ENOMEM; + + mutex_lock(&cpr_vreg->cpr_mutex); + + fuse_corner = cpr_vreg->corner_map[cpr_vreg->corner]; + + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + "corner = %d, current_volt = %d uV\n", + cpr_vreg->corner, cpr_vreg->last_volt[cpr_vreg->corner]); + ret += len; + + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + "fuse_corner = %d, current_volt = %d uV\n", + fuse_corner, cpr_vreg->last_volt[cpr_vreg->corner]); + ret += len; + + ro_sel = cpr_vreg->cpr_fuse_ro_sel[fuse_corner]; + gcnt = cpr_read(cpr_vreg, REG_RBCPR_GCNT_TARGET(ro_sel)); + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + "rbcpr_gcnt_target (%u) = 0x%02X\n", ro_sel, gcnt); + ret += len; + + ctl = cpr_read(cpr_vreg, REG_RBCPR_CTL); + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + "rbcpr_ctl = 0x%02X\n", ctl); + ret += len; + + irq_status = cpr_read(cpr_vreg, REG_RBIF_IRQ_STATUS); + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + "rbcpr_irq_status = 0x%02X\n", irq_status); + ret += len; + + reg = cpr_read(cpr_vreg, REG_RBCPR_RESULT_0); + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + "rbcpr_result_0 = 0x%02X\n", reg); + ret += len; + + step_dn = reg & 0x01; + step_up = (reg >> RBCPR_RESULT0_STEP_UP_SHIFT) & 0x01; + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + " [step_dn = %u", step_dn); + ret += len; + + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + ", step_up = %u", step_up); + ret += len; + + error_steps = (reg >> RBCPR_RESULT0_ERROR_STEPS_SHIFT) + & RBCPR_RESULT0_ERROR_STEPS_MASK; + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + ", error_steps = %u", error_steps); + ret += len; + + error = (reg >> RBCPR_RESULT0_ERROR_SHIFT) & RBCPR_RESULT0_ERROR_MASK; + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + ", error = %u", error); + ret += len; + + error_lt0 = (reg >> RBCPR_RESULT0_ERROR_LT0_SHIFT) & 0x01; + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + ", error_lt_0 = %u", error_lt0); + ret += len; + + busy = (reg >> RBCPR_RESULT0_BUSY_SHIFT) & 0x01; + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + ", busy = %u]\n", busy); + ret += len; + mutex_unlock(&cpr_vreg->cpr_mutex); + + ret = simple_read_from_buffer(buff, count, ppos, debugfs_buf, ret); + kfree(debugfs_buf); + return ret; +} + +static const struct file_operations cpr_debug_info_fops = { + .open = simple_open, + .read = cpr_debug_info_read, +}; + +static ssize_t cpr_aging_debug_info_read(struct file *file, char __user *buff, + size_t count, loff_t *ppos) +{ + struct cpr_regulator *cpr_vreg = file->private_data; + struct cpr_aging_info *aging_info = cpr_vreg->aging_info; + char *debugfs_buf; + ssize_t len, ret = 0; + int i; + + debugfs_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!debugfs_buf) + return -ENOMEM; + + mutex_lock(&cpr_vreg->cpr_mutex); + + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + "aging_adj_volt = ["); + ret += len; + + for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) { + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + " %d", aging_info->voltage_adjust[i]); + ret += len; + } + + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + " ]uV\n"); + ret += len; + + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + "aging_measurement_done = %s\n", + aging_info->cpr_aging_done ? "true" : "false"); + ret += len; + + len = scnprintf(debugfs_buf + ret, PAGE_SIZE - ret, + "aging_measurement_error = %s\n", + aging_info->cpr_aging_error ? "true" : "false"); + ret += len; + + mutex_unlock(&cpr_vreg->cpr_mutex); + + ret = simple_read_from_buffer(buff, count, ppos, debugfs_buf, ret); + kfree(debugfs_buf); + return ret; +} + +static const struct file_operations cpr_aging_debug_info_fops = { + .open = simple_open, + .read = cpr_aging_debug_info_read, +}; + +static void cpr_debugfs_init(struct cpr_regulator *cpr_vreg) +{ + struct dentry *temp; + + if (IS_ERR_OR_NULL(cpr_debugfs_base)) { + cpr_err(cpr_vreg, "Could not create debugfs nodes since base directory is missing\n"); + return; + } + + cpr_vreg->debugfs = debugfs_create_dir(cpr_vreg->rdesc.name, + cpr_debugfs_base); + if (IS_ERR_OR_NULL(cpr_vreg->debugfs)) { + cpr_err(cpr_vreg, "debugfs directory creation failed\n"); + return; + } + + temp = debugfs_create_file("debug_info", 0444, cpr_vreg->debugfs, + cpr_vreg, &cpr_debug_info_fops); + if (IS_ERR_OR_NULL(temp)) { + cpr_err(cpr_vreg, "debug_info node creation failed\n"); + return; + } + + temp = debugfs_create_file("cpr_enable", 0644, cpr_vreg->debugfs, + cpr_vreg, &cpr_enable_fops); + if (IS_ERR_OR_NULL(temp)) { + cpr_err(cpr_vreg, "cpr_enable node creation failed\n"); + return; + } + + temp = debugfs_create_file("cpr_ceiling", 0444, cpr_vreg->debugfs, + cpr_vreg, &cpr_ceiling_fops); + if (IS_ERR_OR_NULL(temp)) { + cpr_err(cpr_vreg, "cpr_ceiling node creation failed\n"); + return; + } + + temp = debugfs_create_file("cpr_floor", 0444, cpr_vreg->debugfs, + cpr_vreg, &cpr_floor_fops); + if (IS_ERR_OR_NULL(temp)) { + cpr_err(cpr_vreg, "cpr_floor node creation failed\n"); + return; + } + + temp = debugfs_create_file("cpr_max_ceiling", 0444, cpr_vreg->debugfs, + cpr_vreg, &cpr_max_ceiling_fops); + if (IS_ERR_OR_NULL(temp)) { + cpr_err(cpr_vreg, "cpr_max_ceiling node creation failed\n"); + return; + } + + if (cpr_vreg->aging_info) { + temp = debugfs_create_file("aging_debug_info", 0444, + cpr_vreg->debugfs, cpr_vreg, + &cpr_aging_debug_info_fops); + if (IS_ERR_OR_NULL(temp)) { + cpr_err(cpr_vreg, "aging_debug_info node creation failed\n"); + return; + } + } +} + +static void cpr_debugfs_remove(struct cpr_regulator *cpr_vreg) +{ + debugfs_remove_recursive(cpr_vreg->debugfs); +} + +static void cpr_debugfs_base_init(void) +{ + cpr_debugfs_base = debugfs_create_dir("cpr-regulator", NULL); + if (IS_ERR_OR_NULL(cpr_debugfs_base)) + pr_err("cpr-regulator debugfs base directory creation failed\n"); +} + +static void cpr_debugfs_base_remove(void) +{ + debugfs_remove_recursive(cpr_debugfs_base); +} + +#else + +static void cpr_debugfs_init(struct cpr_regulator *cpr_vreg) +{} + +static void cpr_debugfs_remove(struct cpr_regulator *cpr_vreg) +{} + +static void cpr_debugfs_base_init(void) +{} + +static void cpr_debugfs_base_remove(void) +{} + +#endif + +/** + * cpr_panic_callback() - panic notification callback function. This function + * is invoked when a kernel panic occurs. + * @nfb: Notifier block pointer of CPR regulator + * @event: Value passed unmodified to notifier function + * @data: Pointer passed unmodified to notifier function + * + * Return: NOTIFY_OK + */ +static int cpr_panic_callback(struct notifier_block *nfb, + unsigned long event, void *data) +{ + struct cpr_regulator *cpr_vreg = container_of(nfb, + struct cpr_regulator, panic_notifier); + int corner, fuse_corner, volt; + + corner = cpr_vreg->corner; + fuse_corner = cpr_vreg->corner_map[corner]; + if (cpr_is_allowed(cpr_vreg)) + volt = cpr_vreg->last_volt[corner]; + else + volt = cpr_vreg->open_loop_volt[corner]; + + cpr_err(cpr_vreg, "[corner:%d, fuse_corner:%d] = %d uV\n", + corner, fuse_corner, volt); + + return NOTIFY_OK; +} + +static int cpr_regulator_probe(struct platform_device *pdev) +{ + struct regulator_config reg_config = {}; + struct cpr_regulator *cpr_vreg; + struct regulator_desc *rdesc; + struct device *dev = &pdev->dev; + struct regulator_init_data *init_data = pdev->dev.platform_data; + int rc; + + if (!pdev->dev.of_node) { + dev_err(dev, "Device tree node is missing\n"); + return -EINVAL; + } + + cpr_vreg = devm_kzalloc(&pdev->dev, sizeof(struct cpr_regulator), + GFP_KERNEL); + if (!cpr_vreg) + return -ENOMEM; + + init_data = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node, + &cpr_vreg->rdesc); + if (!init_data) { + dev_err(dev, "regulator init data is missing\n"); + return -EINVAL; + } + init_data->constraints.input_uV = init_data->constraints.max_uV; + init_data->constraints.valid_ops_mask + |= REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS; + + cpr_vreg->rdesc.name = init_data->constraints.name; + if (cpr_vreg->rdesc.name == NULL) { + dev_err(dev, "regulator-name missing\n"); + return -EINVAL; + } + + rc = cpr_fuse_corner_array_alloc(&pdev->dev, cpr_vreg); + if (rc) + return rc; + + rc = cpr_mem_acc_init(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "mem_acc initialization error rc=%d\n", rc); + return rc; + } + + rc = cpr_efuse_init(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "Wrong eFuse address specified: rc=%d\n", rc); + return rc; + } + + rc = cpr_remap_efuse_data(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "Could not remap fuse data: rc=%d\n", rc); + return rc; + } + + rc = cpr_check_redundant(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "Could not check redundant fuse: rc=%d\n", + rc); + goto err_out; + } + + rc = cpr_read_fuse_revision(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "Could not read fuse revision: rc=%d\n", rc); + goto err_out; + } + + cpr_parse_speed_bin_fuse(cpr_vreg, dev->of_node); + cpr_parse_pvs_version_fuse(cpr_vreg, dev->of_node); + + rc = cpr_read_ro_select(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "Could not read RO select: rc=%d\n", rc); + goto err_out; + } + + rc = cpr_find_fuse_map_match(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "Could not determine fuse mapping match: rc=%d\n", + rc); + goto err_out; + } + + rc = cpr_voltage_plan_init(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "Wrong DT parameter specified: rc=%d\n", rc); + goto err_out; + } + + rc = cpr_pvs_init(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "Initialize PVS wrong: rc=%d\n", rc); + goto err_out; + } + + rc = cpr_vsens_init(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "Initialize vsens configuration failed rc=%d\n", + rc); + return rc; + } + + rc = cpr_apc_init(pdev, cpr_vreg); + if (rc) { + if (rc != -EPROBE_DEFER) + cpr_err(cpr_vreg, "Initialize APC wrong: rc=%d\n", rc); + goto err_out; + } + + rc = cpr_init_cpr(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "Initialize CPR failed: rc=%d\n", rc); + goto err_out; + } + + rc = cpr_rpm_apc_init(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "Initialize RPM APC regulator failed rc=%d\n", + rc); + return rc; + } + + rc = cpr_parse_vdd_mode_config(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "vdd-mode parsing failed, rc=%d\n", rc); + return rc; + } + + if (of_property_read_bool(pdev->dev.of_node, + "qcom,disable-closed-loop-in-pc")) { + rc = cpr_init_pm_notification(cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, + "cpr_init_pm_notification failed rc=%d\n", rc); + return rc; + } + } + + /* Parse dependency parameters */ + if (cpr_vreg->vdd_mx) { + rc = cpr_parse_vdd_mx_parameters(pdev, cpr_vreg); + if (rc) { + cpr_err(cpr_vreg, "parsing vdd_mx parameters failed: rc=%d\n", + rc); + goto err_out; + } + } + + cpr_efuse_free(cpr_vreg); + + /* + * Ensure that enable state accurately reflects the case in which CPR + * is permanently disabled. + */ + cpr_vreg->enable &= !cpr_vreg->cpr_fuse_disable; + + mutex_init(&cpr_vreg->cpr_mutex); + + rdesc = &cpr_vreg->rdesc; + rdesc->owner = THIS_MODULE; + rdesc->type = REGULATOR_VOLTAGE; + rdesc->ops = &cpr_corner_ops; + + reg_config.dev = &pdev->dev; + reg_config.init_data = init_data; + reg_config.driver_data = cpr_vreg; + reg_config.of_node = pdev->dev.of_node; + cpr_vreg->rdev = regulator_register(rdesc, ®_config); + if (IS_ERR(cpr_vreg->rdev)) { + rc = PTR_ERR(cpr_vreg->rdev); + cpr_err(cpr_vreg, "regulator_register failed: rc=%d\n", rc); + + cpr_apc_exit(cpr_vreg); + return rc; + } + + platform_set_drvdata(pdev, cpr_vreg); + cpr_debugfs_init(cpr_vreg); + + /* Register panic notification call back */ + cpr_vreg->panic_notifier.notifier_call = cpr_panic_callback; + atomic_notifier_chain_register(&panic_notifier_list, + &cpr_vreg->panic_notifier); + + mutex_lock(&cpr_regulator_list_mutex); + list_add(&cpr_vreg->list, &cpr_regulator_list); + mutex_unlock(&cpr_regulator_list_mutex); + + return 0; + +err_out: + cpr_efuse_free(cpr_vreg); + return rc; +} + +static int cpr_regulator_remove(struct platform_device *pdev) +{ + struct cpr_regulator *cpr_vreg; + + cpr_vreg = platform_get_drvdata(pdev); + if (cpr_vreg) { + /* Disable CPR */ + if (cpr_is_allowed(cpr_vreg)) { + cpr_ctl_disable(cpr_vreg); + cpr_irq_set(cpr_vreg, 0); + } + + mutex_lock(&cpr_regulator_list_mutex); + list_del(&cpr_vreg->list); + mutex_unlock(&cpr_regulator_list_mutex); + + atomic_notifier_chain_unregister(&panic_notifier_list, + &cpr_vreg->panic_notifier); + + cpr_apc_exit(cpr_vreg); + cpr_debugfs_remove(cpr_vreg); + regulator_unregister(cpr_vreg->rdev); + } + + return 0; +} + +static const struct of_device_id cpr_regulator_match_table[] = { + { .compatible = CPR_REGULATOR_DRIVER_NAME, }, + {} +}; +MODULE_DEVICE_TABLE(of, cpr_regulator_match_table); + +static struct platform_driver cpr_regulator_driver = { + .driver = { + .name = CPR_REGULATOR_DRIVER_NAME, + .of_match_table = cpr_regulator_match_table, + }, + .probe = cpr_regulator_probe, + .remove = cpr_regulator_remove, + .suspend = cpr_regulator_suspend, + .resume = cpr_regulator_resume, +}; + +/** + * cpr_regulator_init() - register cpr-regulator driver + * + * This initialization function should be called in systems in which driver + * registration ordering must be controlled precisely. + */ +int __init cpr_regulator_init(void) +{ + static bool initialized; + + if (initialized) + return 0; + + initialized = true; + cpr_debugfs_base_init(); + return platform_driver_register(&cpr_regulator_driver); +} +EXPORT_SYMBOL(cpr_regulator_init); + +static void __exit cpr_regulator_exit(void) +{ + platform_driver_unregister(&cpr_regulator_driver); + cpr_debugfs_base_remove(); +} + +MODULE_DESCRIPTION("CPR regulator driver"); +MODULE_LICENSE("GPL v2"); + +arch_initcall(cpr_regulator_init); +module_exit(cpr_regulator_exit); diff --git a/drivers/regulator/qpnp-lcdb-regulator.c b/drivers/regulator/qpnp-lcdb-regulator.c index b8bd66a7df21..62cd2520f0c0 100644 --- a/drivers/regulator/qpnp-lcdb-regulator.c +++ b/drivers/regulator/qpnp-lcdb-regulator.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "LCDB: %s: " fmt, __func__ @@ -1010,7 +1010,9 @@ static irqreturn_t qpnp_lcdb_sc_irq_handler(int irq, void *data) int rc; u8 val, val2[2] = {0}; + mutex_lock(&lcdb->lcdb_mutex); rc = qpnp_lcdb_read(lcdb, lcdb->base + INT_RT_STATUS_REG, &val, 1); + mutex_unlock(&lcdb->lcdb_mutex); if (rc < 0) goto irq_handled; @@ -1050,8 +1052,15 @@ static irqreturn_t qpnp_lcdb_sc_irq_handler(int irq, void *data) /* blanking time */ usleep_range(2000, 2100); /* Read the SC status again to confirm true SC */ + mutex_lock(&lcdb->lcdb_mutex); + /* + * Wait for the completion of LCDB module enable, + * which could be initiated in a previous SC event, + * to avoid multiple module disable/enable calls. + */ rc = qpnp_lcdb_read(lcdb, lcdb->base + INT_RT_STATUS_REG, &val, 1); + mutex_unlock(&lcdb->lcdb_mutex); if (rc < 0) goto irq_handled; @@ -2084,7 +2093,12 @@ static void qpnp_lcdb_pmic_config(struct qpnp_lcdb *lcdb) lcdb->wa_flags |= NCP_SCP_DISABLE_WA; break; case PMI632_SUBTYPE: - case PM6150L_SUBTYPE: + lcdb->wa_flags |= FORCE_PD_ENABLE_WA; + break; + case PM8150L_SUBTYPE: + if (lcdb->pmic_rev_id->rev4 >= PM8150L_V3P0_REV4) + lcdb->voltage_step_ramp = false; + lcdb->wa_flags |= FORCE_PD_ENABLE_WA; break; default: diff --git a/drivers/rpmsg/qcom_smd.c b/drivers/rpmsg/qcom_smd.c index aa008fa11002..0bf9e026bae9 100644 --- a/drivers/rpmsg/qcom_smd.c +++ b/drivers/rpmsg/qcom_smd.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2015, Sony Mobile Communications AB. - * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2013, 2020 The Linux Foundation. All rights reserved. */ #include @@ -17,9 +17,11 @@ #include #include #include +#include #include #include #include +#include #include "rpmsg_internal.h" @@ -92,6 +94,17 @@ static const struct { }, }; +/* ipc logging wrapper */ +#define smd_ipc(log_ctx, print, dev, x...) do { \ +ipc_log_string(log_ctx, x); \ +if (print) { \ + if (dev) \ + dev_err((dev), x); \ + else \ + pr_err(x); \ +} \ +} while (0) + /** * struct qcom_smd_edge - representing a remote processor * @dev: device associated with this edge @@ -142,6 +155,7 @@ struct qcom_smd_edge { struct work_struct scan_work; struct work_struct state_work; + void *ipc; /* ipc logging hanlder */ }; /* @@ -195,6 +209,9 @@ struct qcom_smd_endpoint { * @pkt_size: size of the currently handled packet * @drvdata: driver private data * @list: lite entry for @channels in qcom_smd_edge + * @extended_buf: buffer for reading data greater than fifo size + * @ext_buf: helper pointer for reading data greater than fifo size + * @ext_pkt_size: size of data greater than fifo size */ struct qcom_smd_channel { struct qcom_smd_edge *edge; @@ -226,6 +243,11 @@ struct qcom_smd_channel { void *drvdata; struct list_head list; + u32 rsigs; + + void *extended_buf; + void *ext_buf; + int ext_pkt_size; }; /* @@ -288,6 +310,16 @@ struct smd_channel_info_word_pair { channel->info->rx.param); \ }) +#define GET_RX_CHANNEL_SIGNAL(channel) \ + ({ \ + (GET_RX_CHANNEL_FLAG(channel, fDSR) ? TIOCM_DSR : 0) | \ + (GET_RX_CHANNEL_FLAG(channel, fCTS) ? TIOCM_CTS : 0) | \ + (GET_RX_CHANNEL_FLAG(channel, fCD) ? TIOCM_CD : 0) | \ + (GET_RX_CHANNEL_FLAG(channel, fRI) ? TIOCM_RI : 0) | \ + (GET_TX_CHANNEL_FLAG(channel, fDSR) ? TIOCM_DTR : 0) | \ + (GET_TX_CHANNEL_FLAG(channel, fCTS) ? TIOCM_RTS : 0); \ + }) + #define SET_RX_CHANNEL_FLAG(channel, param, value) \ ({ \ BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \ @@ -402,6 +434,8 @@ static void qcom_smd_channel_reset(struct qcom_smd_channel *channel) SET_RX_CHANNEL_INFO(channel, tail, 0); qcom_smd_signal_channel(channel); + smd_ipc(channel->edge->ipc, false, NULL, "%s: ch %s ed %s\n", + __func__, channel->name, channel->edge->name); channel->state = SMD_CHANNEL_CLOSED; channel->pkt_size = 0; @@ -432,6 +466,9 @@ static size_t qcom_smd_channel_get_rx_avail(struct qcom_smd_channel *channel) head = GET_RX_CHANNEL_INFO(channel, head); tail = GET_RX_CHANNEL_INFO(channel, tail); + smd_ipc(channel->edge->ipc, false, NULL, + "%s: h: 0x%x t: 0x%x ch %s ed %s\n", + __func__, head, tail, channel->name, channel->edge->name); return (head - tail) & (channel->fifo_size - 1); } @@ -458,6 +495,8 @@ static void qcom_smd_channel_set_state(struct qcom_smd_channel *channel, channel->state = state; qcom_smd_signal_channel(channel); + smd_ipc(channel->edge->ipc, false, NULL, "%s: ch %s ed %s\n", + __func__, channel->name, channel->edge->name); } /* @@ -536,6 +575,62 @@ static void qcom_smd_channel_advance(struct qcom_smd_channel *channel, SET_RX_CHANNEL_INFO(channel, tail, tail); } +/* Read the data of size greater than fifo size */ +static size_t qcom_smd_channel_ext_read(struct qcom_smd_channel *channel) +{ + struct rpmsg_endpoint *ept = &channel->qsept->ept; + size_t len; + void *ptr; + int ret; + int avail; + + if (!channel->extended_buf) { + channel->ext_pkt_size = channel->pkt_size; + channel->extended_buf = kmalloc(channel->ext_pkt_size, + GFP_ATOMIC); + if (!channel->extended_buf) { + smd_ipc(channel->edge->ipc, true, NULL, + "%s: mem alloc failed\n", __func__); + return 0; + } + ptr = channel->extended_buf; + channel->ext_buf = ptr; + } else { + ptr = channel->ext_buf; + } + + if (channel->pkt_size >= channel->fifo_size) { + avail = qcom_smd_channel_get_rx_avail(channel); + len = qcom_smd_channel_peek(channel, ptr, avail); + } else { + len = qcom_smd_channel_peek(channel, ptr, channel->pkt_size); + } + + qcom_smd_channel_advance(channel, len); + channel->pkt_size = channel->pkt_size - len; + channel->ext_buf = ptr + len; + + if (channel->pkt_size == 0) { + ptr = channel->extended_buf; + len = channel->ext_pkt_size; + ret = ept->cb(ept->rpdev, ptr, len, ept->priv, RPMSG_ADDR_ANY); + if (ret < 0) { + smd_ipc(channel->edge->ipc, false, NULL, + "%s: ret %d len %d ch %s\n", __func__, ret, len, + channel->name); + } + kfree(channel->extended_buf); + channel->extended_buf = NULL; + channel->ext_buf = NULL; + channel->ext_pkt_size = 0; + } + + /* Indicate that we have seen and updated tail */ + SET_TX_CHANNEL_FLAG(channel, fTAIL, 1); + + return len; +} + /* * Read out a single packet from the rx fifo and deliver it to the device */ @@ -549,6 +644,15 @@ static int qcom_smd_channel_recv_single(struct qcom_smd_channel *channel) tail = GET_RX_CHANNEL_INFO(channel, tail); + /* extended buffer if data size is greter than or equal to fifo size */ + if ((channel->pkt_size >= channel->fifo_size) || + channel->ext_pkt_size) { + len = qcom_smd_channel_ext_read(channel); + if (len == 0) + return -ENOMEM; + goto exit; + } + /* Use bounce buffer if the data wraps */ if (tail + channel->pkt_size >= channel->fifo_size) { ptr = channel->bounce_buffer; @@ -559,14 +663,21 @@ static int qcom_smd_channel_recv_single(struct qcom_smd_channel *channel) } ret = ept->cb(ept->rpdev, ptr, len, ept->priv, RPMSG_ADDR_ANY); - if (ret < 0) + if (ret < 0) { + smd_ipc(channel->edge->ipc, false, NULL, + "%s: ret %d len %d ch %s\n", __func__, ret, len, + channel->name); return ret; + } /* Only forward the tail if the client consumed the data */ qcom_smd_channel_advance(channel, len); channel->pkt_size = 0; +exit: + smd_ipc(channel->edge->ipc, false, NULL, "%s: len %d ch %s ed %s\n", + __func__, len, channel->name, channel->edge->name); return 0; } @@ -575,9 +686,11 @@ static int qcom_smd_channel_recv_single(struct qcom_smd_channel *channel) */ static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel) { + struct rpmsg_endpoint *ept = &channel->qsept->ept; bool need_state_scan = false; int remote_state; __le32 pktlen; + u32 rsig = 0; int avail; int ret; @@ -600,6 +713,14 @@ static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel) if (channel->state != SMD_CHANNEL_OPENED) goto out; + /* Check if any signal updated */ + rsig = GET_RX_CHANNEL_SIGNAL(channel); + + if ((ept->sig_cb) && (channel->rsigs != rsig)) { + ept->sig_cb(ept->rpdev, channel->rsigs, rsig); + channel->rsigs = rsig; + } + /* Indicate that we've seen the new data */ SET_RX_CHANNEL_FLAG(channel, fHEAD, 0); @@ -611,17 +732,25 @@ static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel) qcom_smd_channel_peek(channel, &pktlen, sizeof(pktlen)); qcom_smd_channel_advance(channel, SMD_PACKET_HEADER_LEN); channel->pkt_size = le32_to_cpu(pktlen); - } else if (channel->pkt_size && avail >= channel->pkt_size) { + smd_ipc(channel->edge->ipc, false, NULL, + "%s: pkt_size: %d ch %s ed %s\n", __func__, + channel->pkt_size, channel->name, channel->edge->name); + } else if (channel->pkt_size && (avail >= channel->pkt_size || + channel->pkt_size >= channel->fifo_size)) { ret = qcom_smd_channel_recv_single(channel); - if (ret) + if (ret) { + smd_ipc(channel->edge->ipc, false, NULL, + "%s: fail ret %d ch %s ed %s\n", __func__, + ret, channel->name, channel->edge->name); break; + } } else { break; } } /* Indicate that we have seen and updated tail */ - SET_RX_CHANNEL_FLAG(channel, fTAIL, 1); + SET_TX_CHANNEL_FLAG(channel, fTAIL, 1); /* Signal the remote that we've consumed the data (if requested) */ if (!GET_RX_CHANNEL_FLAG(channel, fBLOCKREADINTR)) { @@ -629,6 +758,8 @@ static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel) wmb(); qcom_smd_signal_channel(channel); + smd_ipc(channel->edge->ipc, false, NULL, "%s: ch %s ed %s\n", + __func__, channel->name, channel->edge->name); } out: @@ -654,6 +785,9 @@ static irqreturn_t qcom_smd_edge_intr(int irq, void *data) list_for_each_entry(channel, &edge->channels, list) { spin_lock(&channel->recv_lock); kick_state |= qcom_smd_channel_intr(channel); + smd_ipc(channel->edge->ipc, false, NULL, + "%s: to APPS ch %s ed %s\n", __func__, channel->name, + edge->name); spin_unlock(&channel->recv_lock); } spin_unlock(&edge->channels_lock); @@ -802,6 +936,9 @@ static int __qcom_smd_send(struct qcom_smd_channel *channel, const void *data, wmb(); qcom_smd_signal_channel(channel); + smd_ipc(channel->edge->ipc, false, NULL, + "%s: hdr:%d len:%d ch %s ed %s\n", __func__, + sizeof(hdr), len, channel->name, channel->edge->name); out_unlock: spin_unlock_irqrestore(&channel->tx_lock, flags); @@ -820,9 +957,9 @@ static int qcom_smd_channel_open(struct qcom_smd_channel *channel, int ret; /* - * Packets are maximum 4k, but reduce if the fifo is smaller + * Packets are maximum 8k, but reduce if the fifo is smaller */ - bb_size = min(channel->fifo_size, SZ_4K); + bb_size = min(channel->fifo_size, SZ_8K); channel->bounce_buffer = kmalloc(bb_size, GFP_KERNEL); if (!channel->bounce_buffer) return -ENOMEM; @@ -989,6 +1126,41 @@ static __poll_t qcom_smd_poll(struct rpmsg_endpoint *ept, return mask; } +static int qcom_smd_get_sigs(struct rpmsg_endpoint *ept, + u32 *lsigs, u32 *rsigs) +{ + struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); + struct qcom_smd_channel *channel = qsept->qsch; + + *lsigs = 0; + *rsigs = 0; + *rsigs = GET_RX_CHANNEL_SIGNAL(channel); + return 0; +} + +static int qcom_smd_set_sigs(struct rpmsg_endpoint *ept, u32 sigs) +{ + struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept); + struct qcom_smd_channel *channel = qsept->qsch; + + if (sigs & TIOCM_DTR) + SET_TX_CHANNEL_FLAG(channel, fDSR, 1); + else + SET_TX_CHANNEL_FLAG(channel, fDSR, 0); + + if (sigs & TIOCM_RTS) + SET_TX_CHANNEL_FLAG(channel, fCTS, 1); + else + SET_TX_CHANNEL_FLAG(channel, fDSR, 0); + + SET_TX_CHANNEL_FLAG(channel, fSTATE, 1); + qcom_smd_signal_channel(channel); + smd_ipc(channel->edge->ipc, false, NULL, "%s: ch %s ed %s\n", + __func__, channel->name, channel->edge->name); + + return 0; +} + /* * Finds the device_node for the smd child interested in this channel. */ @@ -1040,6 +1212,8 @@ static const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops = { .send = qcom_smd_send, .trysend = qcom_smd_trysend, .poll = qcom_smd_poll, + .get_sigs = qcom_smd_get_sigs, + .set_sigs = qcom_smd_set_sigs, }; static void qcom_smd_release_device(struct device *dev) @@ -1081,6 +1255,8 @@ static int qcom_smd_create_device(struct qcom_smd_channel *channel) rpdev->dev.parent = &edge->dev; rpdev->dev.release = qcom_smd_release_device; + smd_ipc(channel->edge->ipc, false, NULL, + "%s: registering ch %s\n", __func__, channel->name); return rpmsg_register_device(rpdev); } @@ -1274,7 +1450,8 @@ static void qcom_channel_state_worker(struct work_struct *work) remote_state = GET_RX_CHANNEL_INFO(channel, state); if (remote_state != SMD_CHANNEL_OPENING && - remote_state != SMD_CHANNEL_OPENED) + remote_state != SMD_CHANNEL_OPENED && + remote_state != SMD_CHANNEL_CLOSING) continue; if (channel->registered) @@ -1307,6 +1484,8 @@ static void qcom_channel_state_worker(struct work_struct *work) strncpy(chinfo.name, channel->name, sizeof(chinfo.name)); chinfo.src = RPMSG_ADDR_ANY; chinfo.dst = RPMSG_ADDR_ANY; + smd_ipc(channel->edge->ipc, false, NULL, + "%s: unregistering ch %s\n", __func__, channel->name); rpmsg_unregister_device(&edge->dev, &chinfo); channel->registered = false; spin_lock_irqsave(&edge->channels_lock, flags); @@ -1395,8 +1574,9 @@ static int qcom_smd_parse_edge(struct device *dev, } ret = devm_request_irq(dev, irq, - qcom_smd_edge_intr, IRQF_TRIGGER_RISING, - node->name, edge); + qcom_smd_edge_intr, IRQF_TRIGGER_RISING + | IRQF_NO_SUSPEND, node->name, edge); + if (ret) { dev_err(dev, "failed to request smd irq\n"); goto put_node; @@ -1470,6 +1650,13 @@ struct qcom_smd_edge *qcom_smd_register_edge(struct device *parent, edge->dev.of_node = node; edge->dev.groups = qcom_smd_edge_groups; dev_set_name(&edge->dev, "%s:%s", dev_name(parent), node->name); + + /* ipc logging handler */ + edge->ipc = ipc_log_context_create(4, dev_name(&edge->dev), 0); + if (!edge->ipc) + dev_info(&edge->dev, "%s: failed to create ipc log cntxt\n", + __func__); + ret = device_register(&edge->dev); if (ret) { pr_err("failed to register smd edge\n"); @@ -1491,6 +1678,8 @@ struct qcom_smd_edge *qcom_smd_register_edge(struct device *parent, schedule_work(&edge->scan_work); + smd_ipc(edge->ipc, false, NULL, + "%s: %s:%s\n", __func__, dev_name(parent), node->name); return edge; unregister_dev: @@ -1552,6 +1741,8 @@ static int qcom_smd_remove_edge(struct device *dev, void *data) { struct qcom_smd_edge *edge = to_smd_edge(dev); + ipc_log_context_destroy(edge->ipc); + return qcom_smd_unregister_edge(edge); } @@ -1589,7 +1780,7 @@ static int __init qcom_smd_init(void) { return platform_driver_register(&qcom_smd_driver); } -subsys_initcall(qcom_smd_init); +postcore_initcall(qcom_smd_init); static void __exit qcom_smd_exit(void) { diff --git a/drivers/rpmsg/rpm-smd.c b/drivers/rpmsg/rpm-smd.c index 5550068262d0..9977289c083b 100644 --- a/drivers/rpmsg/rpm-smd.c +++ b/drivers/rpmsg/rpm-smd.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2019, 2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "%s: " fmt, __func__ @@ -94,8 +94,6 @@ struct msm_rpm_driver_data { uint32_t ch_type; struct smd_channel *ch_info; struct work_struct work; - spinlock_t smd_lock_write; - spinlock_t smd_lock_read; struct completion smd_open; }; @@ -206,7 +204,6 @@ enum rpm_msg_fmts { }; static struct rb_root tr_root = RB_ROOT; -static int msm_rpm_send_smd_buffer(char *buf, uint32_t size); static uint32_t msm_rpm_get_next_msg_id(void); static inline uint32_t get_offset_value(uint32_t val, uint32_t offset, @@ -722,8 +719,8 @@ static int msm_rpm_flush_requests(bool print) set_msg_id(s->buf, msm_rpm_get_next_msg_id()); - ret = msm_rpm_send_smd_buffer(s->buf, - get_buf_len(s->buf)); + ret = rpmsg_send(rpm->rpm_channel, s->buf, get_buf_len(s->buf)); + WARN_ON(ret != 0); trace_rpm_smd_send_sleep_set(get_msg_id(s->buf), type, id); @@ -1185,17 +1182,6 @@ static void msm_rpm_log_request(struct msm_rpm_request *cdata) pr_info("request info %s\n", buf); } -static int msm_rpm_send_smd_buffer(char *buf, uint32_t size) -{ - unsigned long flags; - int ret; - - spin_lock_irqsave(&msm_rpm_data.smd_lock_write, flags); - ret = rpmsg_send(rpm->rpm_channel, buf, size); - spin_unlock_irqrestore(&msm_rpm_data.smd_lock_write, flags); - return ret; -} - static int msm_rpm_send_data(struct msm_rpm_request *cdata, int msg_type, bool noack) { @@ -1290,7 +1276,7 @@ static int msm_rpm_send_data(struct msm_rpm_request *cdata, msm_rpm_add_wait_list(msg_id, noack); - ret = msm_rpm_send_smd_buffer(&cdata->buf[0], msg_size); + ret = rpmsg_send(rpm->rpm_channel, &cdata->buf[0], msg_size); if (!ret) { for (i = 0; (i < cdata->write_idx); i++) @@ -1599,8 +1585,6 @@ static int qcom_smd_rpm_probe(struct rpmsg_device *rpdev) mutex_init(&rpm->lock); init_completion(&rpm->ack); - spin_lock_init(&msm_rpm_data.smd_lock_write); - spin_lock_init(&msm_rpm_data.smd_lock_read); skip_init: probe_status = of_platform_populate(p, NULL, NULL, &rpdev->dev); diff --git a/drivers/rtc/rtc-pm8xxx.c b/drivers/rtc/rtc-pm8xxx.c index 40fefd9c38b1..f5d40c6de54c 100644 --- a/drivers/rtc/rtc-pm8xxx.c +++ b/drivers/rtc/rtc-pm8xxx.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2010-2011, 2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2010-2011, 2020-2021, The Linux Foundation. All rights reserved. */ #include @@ -470,6 +470,16 @@ static const struct pm8xxx_rtc_regs pmk8350_regs = { .alarm_en = BIT(7), }; +static const struct pm8xxx_rtc_regs pm8916_regs = { + .ctrl = 0x6046, + .write = 0x6040, + .read = 0x6048, + .alarm_rw = 0x6140, + .alarm_ctrl = 0x6146, + .alarm_ctrl2 = 0x6148, + .alarm_en = BIT(7), +}; + /* * Hardcoded RTC bases until IORESOURCE_REG mapping is figured out */ @@ -479,6 +489,7 @@ static const struct of_device_id pm8xxx_id_table[] = { { .compatible = "qcom,pm8058-rtc", .data = &pm8058_regs }, { .compatible = "qcom,pm8941-rtc", .data = &pm8941_regs }, { .compatible = "qcom,pmk8350-rtc", .data = &pmk8350_regs }, + { .compatible = "qcom,pm8916-rtc", .data = &pm8916_regs }, { }, }; MODULE_DEVICE_TABLE(of, pm8xxx_id_table); diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index a4cfa40aeb86..bdf5089167f1 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -3,7 +3,7 @@ * * This code is based on drivers/scsi/ufs/ufshcd.c * Copyright (C) 2011-2013 Samsung India Software Operations - * Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2013-2021, The Linux Foundation. All rights reserved. * * Authors: * Santosh Yaraganavi @@ -9510,13 +9510,14 @@ static int ufshcd_ioctl(struct scsi_device *dev, int cmd, void __user *buffer) int err = 0; BUG_ON(!hba); - if (!buffer) { - dev_err(hba->dev, "%s: User buffer is NULL!\n", __func__); - return -EINVAL; - } switch (cmd) { case UFS_IOCTL_QUERY: + if (!buffer) { + dev_err(hba->dev, "%s: User buffer is NULL!\n", + __func__); + return -EINVAL; + } pm_runtime_get_sync(hba->dev); err = ufshcd_query_ioctl(hba, ufshcd_scsi_to_upiu_lun(dev->lun), buffer); diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index f8cea4258630..b6067f7f227b 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -407,7 +407,7 @@ config MSM_REMOTEQDSS /sys/class/remoteqdss. config MSM_TZ_SMMU - depends on ARCH_MSM8953 || ARCH_QCS405 + depends on ARCH_MSM8953 || ARCH_QCS405 || ARCH_MSM8917 bool "Helper functions for SMMU configuration through TZ" help Say 'Y' here for targets that need to call into TZ to configure @@ -742,11 +742,6 @@ config QCOM_SMCINVOKE and high level operating system. It exposes APIs for both userspace and kernel clients. -config SERIAL_NUM - bool "Enable Serial Number Proc Interface" - help - Provide a interface for reading CPU serial number - config MSM_EVENT_TIMER bool "Event timer" help @@ -968,3 +963,5 @@ config ICNSS_QMI handshake messages to WLAN FW, which includes hardware capabilities and configurations. It also send WLAN on/off control message to FW over QMI channel. + +source "drivers/soc/qcom/wcnss/Kconfig" diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 97f51a0d6086..f24825b7d6f3 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -103,9 +103,9 @@ obj-$(CONFIG_RMNET_CTL) += rmnet_ctl/ obj-$(CONFIG_QCOM_CX_IPEAK) += cx_ipeak.o obj-$(CONFIG_QTI_L2_REUSE) += l2_reuse.o obj-$(CONFIG_ICNSS2) += icnss2/ +obj-$(CONFIG_WCNSS_CORE) += wcnss/ obj-$(CONFIG_QTI_CRYPTO_COMMON) += crypto-qti-common.o obj-$(CONFIG_QTI_CRYPTO_TZ) += crypto-qti-tz.o obj-$(CONFIG_QTI_HW_KEY_MANAGER) += hwkm_qti.o hwkm_qti-y += hwkm.o -obj-$(CONFIG_SERIAL_NUM) += serial_num.o obj-$(CONFIG_MSM_BAM_DMUX) += bam_dmux.o diff --git a/drivers/soc/qcom/bam_dmux.c b/drivers/soc/qcom/bam_dmux.c index e76ded1c3f28..6e6669a13059 100644 --- a/drivers/soc/qcom/bam_dmux.c +++ b/drivers/soc/qcom/bam_dmux.c @@ -37,6 +37,8 @@ #include #include #include +#include +#include #include "bam_dmux_private.h" @@ -74,8 +76,7 @@ static struct bam_ops_if bam_default_ops = { /* smsm */ .smsm_change_state_ptr = &qcom_smem_state_update_bits, .smsm_get_state_ptr = &qcom_smem_state_get, - .smsm_state_cb_register_ptr = &qcom_smem_state_register, - .smsm_state_cb_deregister_ptr = &qcom_smem_state_unregister, + .smsm_put_state_ptr = &qcom_smem_state_put, /* sps */ .sps_connect_ptr = &sps_connect, @@ -94,8 +95,6 @@ static struct bam_ops_if bam_default_ops = { .dma_to = DMA_TO_DEVICE, .dma_from = DMA_FROM_DEVICE, - .node = NULL, - .smem_state = NULL, .pwr_state = NULL, .pwr_ack_state = NULL, }; @@ -266,7 +265,7 @@ static DEFINE_RWLOCK(ul_wakeup_lock); static DECLARE_WORK(kickoff_ul_wakeup, kickoff_ul_wakeup_func); static int bam_connection_is_active; static int wait_for_ack; -static struct wakeup_source bam_wakelock; +static struct wakeup_source *bam_wakelock; static int a2_pc_disabled; static DEFINE_MUTEX(dfab_status_lock); static int dfab_is_on; @@ -2160,7 +2159,7 @@ static void grab_wakelock(void) BAM_DMUX_LOG("%s: ref count = %d\n", __func__, wakelock_reference_count); if (wakelock_reference_count == 0) - __pm_stay_awake(&bam_wakelock); + __pm_stay_awake(bam_wakelock); ++wakelock_reference_count; spin_unlock_irqrestore(&wakelock_reference_lock, flags); } @@ -2180,7 +2179,7 @@ static void release_wakelock(void) wakelock_reference_count); --wakelock_reference_count; if (wakelock_reference_count == 0) - __pm_relax(&bam_wakelock); + __pm_relax(bam_wakelock); spin_unlock_irqrestore(&wakelock_reference_lock, flags); } @@ -2522,26 +2521,29 @@ static int bam_dmux_smsm_cb(void *state, u32 old_state, u32 new_state) return 0; } -static int bam_dmux_smsm_ack_cb(void *state, u32 old_state, u32 new_state) +static irqreturn_t bam_dmux_smsm_ack_cb(int irq, void *data) { int rcu_id; rcu_id = srcu_read_lock(&bam_dmux_srcu); DBG_INC_ACK_IN_CNT(); - BAM_DMUX_LOG("%s: 0x%08x -> 0x%08x\n", __func__, old_state, - new_state); complete_all(&ul_wakeup_ack_completion); srcu_read_unlock(&bam_dmux_srcu, rcu_id); - return 0; + return IRQ_HANDLED; } -static const struct qcom_smem_state_ops dmux_smsm_state_ops = { - .update_bits = bam_dmux_smsm_cb, -}; +static irqreturn_t a2_power_on_off_cb(int irq, void *data) +{ -static const struct qcom_smem_state_ops dmux_smsm_state_ack_ops = { - .update_bits = bam_dmux_smsm_ack_cb, -}; + if (!bam_ops->a2_pwr_state) { + bam_dmux_smsm_cb(NULL, 0, SMSM_A2_POWER_CONTROL); + bam_ops->a2_pwr_state = SMSM_A2_POWER_CONTROL; + } else { + bam_dmux_smsm_cb(NULL, SMSM_A2_POWER_CONTROL, 0); + bam_ops->a2_pwr_state = 0; + } + return IRQ_HANDLED; +} /** * msm_bam_dmux_set_bam_ops() - sets the bam_ops @@ -2581,10 +2583,6 @@ EXPORT_SYMBOL(msm_bam_dmux_deinit); void msm_bam_dmux_reinit(void) { bam_mux_initialized = 0; - bam_ops->smsm_state_cb_register_ptr(bam_ops->node, - &dmux_smsm_state_ops, NULL); - bam_ops->smsm_state_cb_register_ptr(bam_ops->node, - &dmux_smsm_state_ack_ops, NULL); } EXPORT_SYMBOL(msm_bam_dmux_reinit); @@ -2649,10 +2647,13 @@ static void set_dl_mtu(int requested_mtu) static int bam_dmux_probe(struct platform_device *pdev) { - int rc; + int rc, i; struct resource *r; void *subsys_h; - uint32_t requested_dl_mtu; + u32 requested_dl_mtu; + int a2_pwr_ctrl_irq; + int a2_pwr_ctrl_ack_irq; + u32 bit_pos; DBG("%s probe called\n", __func__); if (bam_mux_initialized) @@ -2666,11 +2667,27 @@ static int bam_dmux_probe(struct platform_device *pdev) } a2_phys_base = r->start; a2_phys_size = (uint32_t)(resource_size(r)); - a2_bam_irq = platform_get_irq(pdev, 0); - if (a2_bam_irq == -ENXIO) { - pr_err("%s: irq field missing\n", __func__); - return -ENODEV; + a2_bam_irq = of_irq_get_byname(pdev->dev.of_node, "dmux"); + if (a2_bam_irq < 0) { + pr_err("%s: bam dmux irq field missing\n", __func__); + return a2_bam_irq; } + + a2_pwr_ctrl_irq = of_irq_get_byname(pdev->dev.of_node, "ctrl"); + if (a2_pwr_ctrl_irq < 0) { + pr_err("%s: bam power ctrl irq field missing\n", + __func__); + return a2_pwr_ctrl_irq; + } + + a2_pwr_ctrl_ack_irq = of_irq_get_byname(pdev->dev.of_node, + "ack"); + if (a2_pwr_ctrl_ack_irq < 0) { + pr_err("%s: bam power ack irq field missing\n", + __func__); + return a2_pwr_ctrl_ack_irq; + } + satellite_mode = of_property_read_bool(pdev->dev.of_node, "qcom,satellite-mode"); @@ -2721,11 +2738,14 @@ static int bam_dmux_probe(struct platform_device *pdev) a2_bam_irq = A2_BAM_IRQ; num_buffers = DEFAULT_NUM_BUFFERS; set_rx_buffer_ring_pool(num_buffers); + a2_pwr_ctrl_irq = -ENODEV; + a2_pwr_ctrl_ack_irq = -ENODEV; } dma_dev = &pdev->dev; /* The BAM only suports 32 bits of address */ - dma_dev->dma_mask = kmalloc(sizeof(*dma_dev->dma_mask), GFP_KERNEL); + dma_dev->dma_mask = devm_kmalloc(&pdev->dev, + sizeof(*dma_dev->dma_mask), GFP_KERNEL); if (!dma_dev->dma_mask) { DMUX_LOG_KERR("%s: cannot allocate dma_mask\n", __func__); return -ENOMEM; @@ -2733,12 +2753,12 @@ static int bam_dmux_probe(struct platform_device *pdev) *dma_dev->dma_mask = DMA_BIT_MASK(32); dma_dev->coherent_dma_mask = DMA_BIT_MASK(32); - xo_clk = clk_get(&pdev->dev, "xo"); + xo_clk = devm_clk_get(&pdev->dev, "xo"); if (IS_ERR(xo_clk)) { BAM_DMUX_LOG("%s: did not get xo clock\n", __func__); xo_clk = NULL; } - dfab_clk = clk_get(&pdev->dev, "bus_clk"); + dfab_clk = devm_clk_get(&pdev->dev, "bus_clk"); if (IS_ERR(dfab_clk)) { BAM_DMUX_LOG("%s: did not get dfab clock\n", __func__); dfab_clk = NULL; @@ -2764,21 +2784,20 @@ static int bam_dmux_probe(struct platform_device *pdev) bam_mux_tx_workqueue = create_singlethread_workqueue("bam_dmux_tx"); if (!bam_mux_tx_workqueue) { - destroy_workqueue(bam_mux_rx_workqueue); - return -ENOMEM; + rc = -ENOMEM; + goto free_wq_rx; } - for (rc = 0; rc < BAM_DMUX_NUM_CHANNELS; ++rc) { - spin_lock_init(&bam_ch[rc].lock); - scnprintf(bam_ch[rc].name, BAM_DMUX_CH_NAME_MAX_LEN, - "bam_dmux_ch_%d", rc); + for (i = 0; i < BAM_DMUX_NUM_CHANNELS; i++) { + spin_lock_init(&bam_ch[i].lock); + scnprintf(bam_ch[i].name, BAM_DMUX_CH_NAME_MAX_LEN, + "bam_dmux_ch_%d", i); /* bus 2, ie a2 stream 2 */ - bam_ch[rc].pdev = platform_device_alloc(bam_ch[rc].name, 2); - if (!bam_ch[rc].pdev) { + bam_ch[i].pdev = platform_device_alloc(bam_ch[i].name, 2); + if (!bam_ch[i].pdev) { + rc = -ENOMEM; pr_err("%s: platform device alloc failed\n", __func__); - destroy_workqueue(bam_mux_rx_workqueue); - destroy_workqueue(bam_mux_tx_workqueue); - return -ENOMEM; + goto free_platform_dev; } } @@ -2788,64 +2807,81 @@ static int bam_dmux_probe(struct platform_device *pdev) init_completion(&shutdown_completion); complete_all(&shutdown_completion); INIT_DELAYED_WORK(&ul_timeout_work, ul_timeout); - wakeup_source_init(&bam_wakelock, "bam_dmux_wakelock"); + bam_wakelock = wakeup_source_register(NULL, "bam_dmux_wakelock"); init_srcu_struct(&bam_dmux_srcu); subsys_h = subsys_notif_register_notifier("modem", &restart_notifier); if (IS_ERR(subsys_h)) { - destroy_workqueue(bam_mux_rx_workqueue); - destroy_workqueue(bam_mux_tx_workqueue); - rc = (int)PTR_ERR(subsys_h); + rc = PTR_ERR(subsys_h); pr_err("%s: failed to register for ssr rc: %d\n", __func__, rc); - return rc; + goto free_platform_dev; } - bam_ops->pwr_state = bam_ops->smsm_state_cb_register_ptr( - pdev->dev.of_node, - &dmux_smsm_state_ops, NULL); - + bam_ops->pwr_state = bam_ops->smsm_get_state_ptr(&pdev->dev, + "pwrctrl", &bit_pos); if (IS_ERR(bam_ops->pwr_state)) { - subsys_notif_unregister_notifier(subsys_h, &restart_notifier); - destroy_workqueue(bam_mux_rx_workqueue); - destroy_workqueue(bam_mux_tx_workqueue); - pr_err("%s: smsm cb register failed, rc: %d\n", __func__, rc); - return -ENOMEM; + rc = PTR_ERR(bam_ops->pwr_ack_state); + pr_err("%s: smsm power control state get failed, rc: %d\n", + __func__, rc); + goto free_subsys_reg; } - bam_ops->pwr_ack_state = bam_ops->smsm_state_cb_register_ptr( - pdev->dev.of_node, - &dmux_smsm_state_ack_ops, NULL); - + bam_ops->pwr_ack_state = bam_ops->smsm_get_state_ptr(&pdev->dev, + "pwrctrlack", &bit_pos); if (IS_ERR(bam_ops->pwr_ack_state)) { - subsys_notif_unregister_notifier(subsys_h, &restart_notifier); - destroy_workqueue(bam_mux_rx_workqueue); - destroy_workqueue(bam_mux_tx_workqueue); - bam_ops->smsm_state_cb_deregister_ptr(bam_ops->pwr_state); - pr_err("%s: smsm ack cb register failed, rc: %d\n", __func__, - rc); - for (rc = 0; rc < BAM_DMUX_NUM_CHANNELS; ++rc) - platform_device_put(bam_ch[rc].pdev); - return -ENOMEM; + rc = PTR_ERR(bam_ops->pwr_ack_state); + pr_err("%s: smsm power control ack state get failed, rc: %d\n", + __func__, rc); + goto free_pwr_state; } - bam_ops->smem_state = qcom_smem_get(QCOM_SMEM_HOST_ANY, - SMSM_A2_POWER_CONTROL, NULL); - if (IS_ERR(bam_ops->smem_state)) { - subsys_notif_unregister_notifier(subsys_h, &restart_notifier); - destroy_workqueue(bam_mux_rx_workqueue); - destroy_workqueue(bam_mux_tx_workqueue); - bam_ops->smsm_state_cb_deregister_ptr(bam_ops->pwr_state); - bam_ops->smsm_state_cb_deregister_ptr(bam_ops->pwr_ack_state); - dev_err(&pdev->dev, "Unable to acquire smem state entry\n"); - for (rc = 0; rc < BAM_DMUX_NUM_CHANNELS; ++rc) - platform_device_put(bam_ch[rc].pdev); - return PTR_ERR(bam_ops->smem_state); + bam_ops->a2_pwr_state = 0; + if (a2_pwr_ctrl_irq >= 0) { + rc = devm_request_threaded_irq(dma_dev, + a2_pwr_ctrl_irq, NULL, + a2_power_on_off_cb, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + NULL, dma_dev); + if (rc < 0) { + dev_err(dma_dev, "smsm power control irq %d attach failed\n", + a2_pwr_ctrl_irq); + goto free_pwr_ack_state; + } } - bam_ops->node = pdev->dev.of_node; - bam_dmux_smsm_cb(NULL, 0, *bam_ops->smem_state); + if (a2_pwr_ctrl_ack_irq >= 0) { + rc = devm_request_threaded_irq(dma_dev, + a2_pwr_ctrl_ack_irq, NULL, + bam_dmux_smsm_ack_cb, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + NULL, dma_dev); + if (rc < 0) { + dev_err(dma_dev, "smsm power control ack irq %d attach failed\n", + a2_pwr_ctrl_ack_irq); + goto free_pwr_ack_state; + } + } return 0; + +free_pwr_ack_state: + bam_ops->smsm_put_state_ptr(bam_ops->pwr_ack_state); +free_pwr_state: + bam_ops->smsm_put_state_ptr(bam_ops->pwr_state); +free_subsys_reg: + subsys_notif_unregister_notifier(subsys_h, &restart_notifier); +free_platform_dev: + for (i = 0; i < BAM_DMUX_NUM_CHANNELS; i++) { + if (bam_ch[i].pdev) { + platform_device_put(bam_ch[i].pdev); + bam_ch[i].pdev = NULL; + } + } + destroy_workqueue(bam_mux_tx_workqueue); +free_wq_rx: + destroy_workqueue(bam_mux_rx_workqueue); + + return rc; } static const struct of_device_id msm_match_table[] = { diff --git a/drivers/soc/qcom/bam_dmux_private.h b/drivers/soc/qcom/bam_dmux_private.h index 50f5fbf5fd41..b1caef1d1b18 100644 --- a/drivers/soc/qcom/bam_dmux_private.h +++ b/drivers/soc/qcom/bam_dmux_private.h @@ -68,11 +68,7 @@ struct bam_ops_if { struct qcom_smem_state *(*smsm_get_state_ptr)(struct device *dev, const char *con_id, unsigned int *bit); - struct qcom_smem_state *(*smsm_state_cb_register_ptr)( - struct device_node *of_node, - const struct qcom_smem_state_ops *ops, void *priv); - - void (*smsm_state_cb_deregister_ptr)(struct qcom_smem_state *state); + void (*smsm_put_state_ptr)(struct qcom_smem_state *state); /* sps */ int (*sps_connect_ptr)(struct sps_pipe *h, struct sps_connect *connect); @@ -114,10 +110,7 @@ struct bam_ops_if { enum dma_data_direction dma_from; - - struct device_node *node; - - u32 *smem_state; + u32 a2_pwr_state; void *pwr_state; diff --git a/drivers/soc/qcom/icnss2/main.c b/drivers/soc/qcom/icnss2/main.c index 6355706e12f5..ca0703b26949 100644 --- a/drivers/soc/qcom/icnss2/main.c +++ b/drivers/soc/qcom/icnss2/main.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "icnss2: " fmt @@ -2219,7 +2219,7 @@ int icnss_unregister_driver(struct icnss_driver_ops *ops) icnss_pr_dbg("Unregistering driver, state: 0x%lx\n", priv->state); - if (!priv->ops || (!test_bit(ICNSS_DRIVER_PROBED, &penv->state))) { + if (!priv->ops) { icnss_pr_err("Driver not registered\n"); penv->ops = NULL; ret = -ENOENT; @@ -3351,6 +3351,32 @@ static int icnss_msa_dt_parse(struct icnss_priv *priv) return ret; } +static int icnss_smmu_fault_handler(struct iommu_domain *domain, + struct device *dev, unsigned long iova, + int flags, void *handler_token) +{ + struct icnss_priv *priv = handler_token; + struct icnss_uevent_fw_down_data fw_down_data = {0}; + + icnss_fatal_err("SMMU fault happened with IOVA 0x%lx\n", iova); + + if (!priv) { + icnss_pr_err("priv is NULL\n"); + return -ENODEV; + } + + if (test_bit(ICNSS_FW_READY, &priv->state)) { + fw_down_data.crashed = true; + icnss_call_driver_uevent(priv, ICNSS_UEVENT_FW_DOWN, + &fw_down_data); + } + + icnss_trigger_recovery(&priv->pdev->dev); + + /* IOMMU driver requires non-zero return value to print debug info. */ + return -EINVAL; +} + static int icnss_smmu_dt_parse(struct icnss_priv *priv) { int ret = 0; @@ -3382,6 +3408,10 @@ static int icnss_smmu_dt_parse(struct icnss_priv *priv) if (!ret && !strcmp("fastmap", iommu_dma_type)) { icnss_pr_dbg("SMMU S1 stage enabled\n"); priv->smmu_s1_enable = true; + if (priv->device_id == WCN6750_DEVICE_ID) + iommu_set_fault_handler(priv->iommu_domain, + icnss_smmu_fault_handler, + priv); } res = platform_get_resource_byname(pdev, diff --git a/drivers/soc/qcom/msm_bus/msm_bus_arb_adhoc.c b/drivers/soc/qcom/msm_bus/msm_bus_arb_adhoc.c index 4592bc8b8acf..524fb0c7f20e 100644 --- a/drivers/soc/qcom/msm_bus/msm_bus_arb_adhoc.c +++ b/drivers/soc/qcom/msm_bus/msm_bus_arb_adhoc.c @@ -18,8 +18,6 @@ #define NUM_LNODES 3 #define MAX_STR_CL 50 -#define DEBUG_REC_TRANSACTION 0 - struct bus_search_type { struct list_head link; struct list_head node_list; @@ -1243,8 +1241,7 @@ static int update_bw_adhoc(struct msm_bus_client_handle *cl, u64 ab, u64 ib) if (!strcmp(test_cl, cl->name)) log_transaction = true; - if (DEBUG_REC_TRANSACTION) - msm_bus_dbg_rec_transaction(cl, ab, ib); + msm_bus_dbg_rec_transaction(cl, ab, ib); if ((cl->cur_act_ib == ib) && (cl->cur_act_ab == ab)) { MSM_BUS_DBG("%s:no change in request", cl->name); @@ -1305,9 +1302,7 @@ static int update_bw_context(struct msm_bus_client_handle *cl, u64 act_ab, if (!slp_ab && !slp_ib) cl->active_only = true; - if (DEBUG_REC_TRANSACTION) - msm_bus_dbg_rec_transaction(cl, cl->cur_act_ab, - cl->cur_dual_ib); + msm_bus_dbg_rec_transaction(cl, cl->cur_act_ab, cl->cur_dual_ib); ret = update_path(cl->mas_dev, cl->slv, act_ib, act_ab, slp_ib, slp_ab, cl->cur_act_ab, cl->cur_act_ab, cl->first_hop, cl->active_only); diff --git a/drivers/soc/qcom/msm_bus/msm_bus_arb_rpmh.c b/drivers/soc/qcom/msm_bus/msm_bus_arb_rpmh.c index a20765e1f6b2..801be4315736 100644 --- a/drivers/soc/qcom/msm_bus/msm_bus_arb_rpmh.c +++ b/drivers/soc/qcom/msm_bus/msm_bus_arb_rpmh.c @@ -1323,7 +1323,8 @@ static uint32_t register_client_adhoc(struct msm_bus_scale_pdata *pdata) return handle; } -static int update_client_paths(struct msm_bus_client *client, unsigned int idx) +static int update_client_paths(struct msm_bus_client *client, bool log_trns, + unsigned int idx) { int lnode, src, dest, cur_idx; uint64_t req_clk, req_bw, curr_clk, curr_bw, slp_clk, slp_bw; @@ -1384,13 +1385,16 @@ static int update_client_paths(struct msm_bus_client *client, unsigned int idx) goto exit_update_client_paths; } + if (log_trns) + getpath_debug(src, lnode, pdata->active_only); } commit_data(); exit_update_client_paths: return ret; } -static int update_client_alc(struct msm_bus_client *client, unsigned int idx) +static int update_client_alc(struct msm_bus_client *client, bool log_trns, + unsigned int idx) { int lnode, cur_idx; uint64_t req_idle_time, req_fal, dual_idle_time, dual_fal, @@ -1569,7 +1573,7 @@ static int update_context(uint32_t cl, bool active_only, pdata->active_only = active_only; msm_bus_dbg_client_data(client->pdata, ctx_idx, cl); - ret = update_client_paths(client, ctx_idx); + ret = update_client_paths(client, false, ctx_idx); if (ret) { pr_err("%s: Err updating path\n", __func__); goto exit_update_context; @@ -1587,6 +1591,8 @@ static int update_request_adhoc(uint32_t cl, unsigned int index) int ret = 0; struct msm_bus_scale_pdata *pdata; struct msm_bus_client *client; + const char *test_cl = "Null"; + bool log_transaction = false; rt_mutex_lock(&msm_bus_adhoc_lock); @@ -1624,14 +1630,17 @@ static int update_request_adhoc(uint32_t cl, unsigned int index) goto exit_update_request; } + if (!strcmp(test_cl, pdata->name)) + log_transaction = true; + MSM_BUS_DBG("%s: cl: %u index: %d curr: %d num_paths: %d\n", __func__, cl, index, client->curr, client->pdata->usecase->num_paths); if (pdata->alc) - ret = update_client_alc(client, index); + ret = update_client_alc(client, log_transaction, index); else { msm_bus_dbg_client_data(client->pdata, index, cl); - ret = update_client_paths(client, index); + ret = update_client_paths(client, log_transaction, index); } if (ret) { pr_err("%s: Err updating path\n", __func__); diff --git a/drivers/soc/qcom/qdss_bridge.c b/drivers/soc/qcom/qdss_bridge.c index 13e5f2f98e39..d48500b84ea4 100644 --- a/drivers/soc/qcom/qdss_bridge.c +++ b/drivers/soc/qcom/qdss_bridge.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. */ #define KMSG_COMPONENT "QDSS diag bridge" @@ -510,14 +510,10 @@ static int mhi_ch_open(struct qdss_bridge_drvdata *drvdata) ret = mhi_prepare_for_transfer(drvdata->mhi_dev); if (ret) { pr_err("Unable to open MHI channel\n"); - goto err; + return ret; } return 0; -err: - spin_lock_bh(&drvdata->lock); - spin_unlock_bh(&drvdata->lock); - return ret; } static void qdss_bridge_open_work_fn(struct work_struct *work) @@ -713,9 +709,10 @@ static ssize_t mhi_uci_read(struct file *file, pr_err("Failed to recycle element, ret: %d\n", ret); qdss_del_buf_tbl_entry(drvdata, uci_buf->buf); uci_buf->buf = NULL; - uci_buf = NULL; + kfree(uci_buf); return ret; } + kfree(uci_buf); } pr_debug("Returning %lu bytes\n", to_copy); @@ -776,13 +773,12 @@ static int mhi_uci_open(struct inode *inode, struct file *filp) spin_unlock_bh(&drvdata->lock); return ret; } - drvdata->opened = ENABLE; spin_unlock_bh(&drvdata->lock); ret = mhi_prepare_for_transfer(drvdata->mhi_dev); if (ret) { pr_err("Error starting transfer channels\n"); - goto error_open_chan; + return ret; } ret = mhi_queue_inbound(drvdata); @@ -790,6 +786,7 @@ static int mhi_uci_open(struct inode *inode, struct file *filp) goto error_rx_queue; filp->private_data = drvdata; + drvdata->opened = ENABLE; return ret; error_rx_queue: @@ -797,12 +794,9 @@ static int mhi_uci_open(struct inode *inode, struct file *filp) list_for_each_entry_safe(buf_itr, tmp, &drvdata->read_done_list, link) { list_del(&buf_itr->link); kfree(buf_itr->buf); + kfree(buf_itr); } -error_open_chan: - spin_lock_bh(&drvdata->lock); - drvdata->opened = DISABLE; - spin_unlock_bh(&drvdata->lock); return ret; } diff --git a/drivers/soc/qcom/qtee_shmbridge.c b/drivers/soc/qcom/qtee_shmbridge.c index 84b9de331d46..d064860c11a8 100644 --- a/drivers/soc/qcom/qtee_shmbridge.c +++ b/drivers/soc/qcom/qtee_shmbridge.c @@ -2,7 +2,7 @@ /* * QTI TEE shared memory bridge driver * - * Copyright (c) 2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2019,2021, The Linux Foundation. All rights reserved. */ #include @@ -128,7 +128,8 @@ static int32_t qtee_shmbridge_enable(bool enable) if (ret || desc.ret[0]) { pr_err("Failed to enable shmbridge, rsp = %lld, ret = %d\n", desc.ret[0], ret); - if (ret == -EIO || desc.ret[0] == SHMBRIDGE_E_NOT_SUPPORTED) + if (ret == -EOPNOTSUPP || + desc.ret[0] == SHMBRIDGE_E_NOT_SUPPORTED) pr_warn("shmbridge is not supported by this target\n"); return ret | desc.ret[0]; } diff --git a/drivers/soc/qcom/smcinvoke.c b/drivers/soc/qcom/smcinvoke.c index d3857dd4833d..3aabadb495c1 100644 --- a/drivers/soc/qcom/smcinvoke.c +++ b/drivers/soc/qcom/smcinvoke.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "smcinvoke: %s: " fmt, __func__ @@ -1152,7 +1152,8 @@ static int prepare_send_scm_msg(const uint8_t *in_buf, phys_addr_t in_paddr, desc.ret[0] == QSEOS_RESULT_BLOCKED_ON_LISTENER) { ret = qseecom_process_listener_from_smcinvoke(&desc); req->result = (int32_t)desc.ret[1]; - if (!req->result) { + if (!req->result && + desc.ret[0] != SMCINVOKE_RESULT_INBOUND_REQ_NEEDED) { dmac_inv_range(in_buf, in_buf + in_buf_len); ret = marshal_out_invoke_req(in_buf, in_buf_len, req, args_buf); diff --git a/drivers/soc/qcom/smp2p.c b/drivers/soc/qcom/smp2p.c index 09e549d100bb..19c47078881a 100644 --- a/drivers/soc/qcom/smp2p.c +++ b/drivers/soc/qcom/smp2p.c @@ -289,11 +289,7 @@ static void qcom_smp2p_notify_in(struct qcom_smp2p *smp2p) (!(val & BIT(i)) && test_bit(i, entry->irq_falling))) { irq_pin = irq_find_mapping(entry->domain, i); handle_nested_irq(irq_pin); - - if (test_bit(i, entry->irq_enabled)) - clear_bit(i, entry->irq_pending); - else - set_bit(i, entry->irq_pending); + clear_bit(i, entry->irq_pending); } } } @@ -392,11 +388,23 @@ static int smp2p_set_irq_type(struct irq_data *irqd, unsigned int type) return 0; } +static int smp2p_retrigger_irq(struct irq_data *irqd) +{ + struct smp2p_entry *entry = irq_data_get_irq_chip_data(irqd); + irq_hw_number_t irq = irqd_to_hwirq(irqd); + + SMP2P_INFO("%d: %s: %lu\n", entry->smp2p->remote_pid, entry->name, irq); + set_bit(irq, entry->irq_pending); + + return 0; +} + static struct irq_chip smp2p_irq_chip = { .name = "smp2p", .irq_mask = smp2p_mask_irq, .irq_unmask = smp2p_unmask_irq, .irq_set_type = smp2p_set_irq_type, + .irq_retrigger = smp2p_retrigger_irq, }; static int smp2p_irq_map(struct irq_domain *d, diff --git a/drivers/soc/qcom/smsm.c b/drivers/soc/qcom/smsm.c index 50214b620865..d0cb6288d4d1 100644 --- a/drivers/soc/qcom/smsm.c +++ b/drivers/soc/qcom/smsm.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2015, Sony Mobile Communications Inc. - * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2013,2019, 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 @@ -96,6 +96,7 @@ struct qcom_smsm { struct smsm_entry *entries; struct smsm_host *hosts; + int irq; }; /** @@ -331,6 +332,9 @@ static int smsm_irq_map(struct irq_domain *d, irq_set_chip_and_handler(irq, &smsm_irq_chip, handle_level_irq); irq_set_chip_data(irq, entry); irq_set_nested_thread(irq, 1); + irq_set_noprobe(irq); + irq_set_parent(irq, entry->smsm->irq); + irq_set_status_flags(irq, IRQ_DISABLE_UNLAZY); return 0; } @@ -391,15 +395,14 @@ static int smsm_inbound_entry(struct qcom_smsm *smsm, struct device_node *node) { int ret; - int irq; - irq = irq_of_parse_and_map(node, 0); - if (!irq) { + smsm->irq = irq_of_parse_and_map(node, 0); + if (!smsm->irq) { dev_err(smsm->dev, "failed to parse smsm interrupt\n"); return -EINVAL; } - ret = devm_request_threaded_irq(smsm->dev, irq, + ret = devm_request_threaded_irq(smsm->dev, smsm->irq, NULL, smsm_intr, IRQF_ONESHOT, "smsm", (void *)entry); diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c index bc24c1fb3def..22931f5f67f9 100644 --- a/drivers/soc/qcom/socinfo.c +++ b/drivers/soc/qcom/socinfo.c @@ -371,6 +371,16 @@ static struct msm_soc_info cpu_of_id[] = { [354] = {MSM_CPU_SDM429, "SDM429"}, + /* QM215 ID */ + [386] = {MSM_CPU_QM215, "QM215"}, + + /* 8953 ID */ + [293] = {MSM_CPU_8953, "MSM8953"}, + [304] = {MSM_CPU_8953, "APQ8053"}, + + /* SDM450 ID */ + [338] = {MSM_CPU_SDM450, "SDM450"}, + /* Uninitialized IDs are not known to run Linux. * MSM_CPU_UNKNOWN is set to 0 to ensure these IDs are * considered as unknown CPU. @@ -1314,6 +1324,18 @@ static void * __init setup_dummy_socinfo(void) dummy_socinfo.id = 354; strlcpy(dummy_socinfo.build_id, "sdm429 - ", sizeof(dummy_socinfo.build_id)); + } else if (early_machine_is_qm215()) { + dummy_socinfo.id = 386; + strlcpy(dummy_socinfo.build_id, "qm215 - ", + sizeof(dummy_socinfo.build_id)); + } else if (early_machine_is_msm8953()) { + dummy_socinfo.id = 293; + strlcpy(dummy_socinfo.build_id, "msm8953 - ", + sizeof(dummy_socinfo.build_id)); + } else if (early_machine_is_sdm450()) { + dummy_socinfo.id = 338; + strlcpy(dummy_socinfo.build_id, "sdm450 - ", + sizeof(dummy_socinfo.build_id)); } else strlcat(dummy_socinfo.build_id, "Dummy socinfo", sizeof(dummy_socinfo.build_id)); diff --git a/drivers/soc/qcom/subsys-pil-tz.c b/drivers/soc/qcom/subsys-pil-tz.c index c05d2732f232..e979abd14ee3 100644 --- a/drivers/soc/qcom/subsys-pil-tz.c +++ b/drivers/soc/qcom/subsys-pil-tz.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2014-2020, The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #include @@ -19,9 +18,6 @@ #include #include -#include -#include - #include #include #include @@ -37,79 +33,12 @@ #define STOP_ACK_TIMEOUT_MS 1000 #define CRASH_STOP_ACK_TO_MS 200 -/*xionghaifeng 20200817 add for xiaomi subsystem ramdump start*/ -#include -#include -static char last_ssr_reason[MAX_SSR_REASON_LEN] = "none"; -static struct proc_dir_entry *last_ssr_reason_entry; -/*xionghaifeng 20200817 add for xiaomi subsystem ramdump end*/ - #define ERR_READY 0 #define PBL_DONE 1 #define desc_to_data(d) container_of(d, struct pil_tz_data, desc) #define subsys_to_data(d) container_of(d, struct pil_tz_data, subsys_desc) -#define STR_NV_SIGNATURE_DESTROYED "CRITICAL_DATA_CHECK_FAILED" - -static char last_modem_sfr_reason[MAX_SSR_REASON_LEN] = "none"; -static struct kobject *checknv_kobj; -static struct kset *checknv_kset; - -static const struct sysfs_ops checknv_sysfs_ops = { -}; - -static void kobj_release(struct kobject *kobj) -{ - kfree(kobj); -} -static struct kobj_type checknv_ktype = { - .sysfs_ops = &checknv_sysfs_ops, - .release = kobj_release, -}; -static void checknv_kobj_clean(struct work_struct *work) -{ - kobject_uevent(checknv_kobj, KOBJ_REMOVE); - kobject_put(checknv_kobj); - kset_unregister(checknv_kset); -} -static void checknv_kobj_create(struct work_struct *work) -{ - int ret; - if (checknv_kset != NULL) { - pr_err("checknv_kset is not NULL, should clean up."); - kobject_uevent(checknv_kobj, KOBJ_REMOVE); - kobject_put(checknv_kobj); - } - checknv_kobj = kzalloc(sizeof(struct kobject), GFP_KERNEL); - if (!checknv_kobj) { - pr_err("kobject alloc failed."); - return; - } - if (checknv_kset == NULL) { - checknv_kset = kset_create_and_add("checknv_errimei", NULL, NULL); - if (!checknv_kset) { - pr_err("kset creation failed."); - goto free_kobj; - } - } - checknv_kobj->kset = checknv_kset; - ret = kobject_init_and_add(checknv_kobj, &checknv_ktype, NULL, "%s", "errimei"); - if (ret) { - pr_err("%s: Error in creation kobject", __func__); - goto del_kobj; - } - kobject_uevent(checknv_kobj, KOBJ_ADD); - return; -del_kobj: - kobject_put(checknv_kobj); - kset_unregister(checknv_kset); -free_kobj: - kfree(checknv_kobj); -} -static DECLARE_DELAYED_WORK(create_kobj_work, checknv_kobj_create); -static DECLARE_WORK(clean_kobj_work, checknv_kobj_clean); - /** * struct reg_info - regulator info * @reg: regulator handle @@ -229,27 +158,6 @@ static struct msm_bus_scale_pdata scm_pas_bus_pdata = { .name = "scm_pas", }; -/*xionghaifeng 20200817 add for xiaomi subsystem ramdump start*/ -static int last_ssr_reason_proc_show(struct seq_file *m, void *v) -{ - seq_printf(m, "%s\n", last_ssr_reason); - return 0; -} - -static int last_ssr_reason_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, last_ssr_reason_proc_show, NULL); -} - -static const struct file_operations last_ssr_reason_file_ops = { - .owner = THIS_MODULE, - .open = last_ssr_reason_proc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; -/*xionghaifeng 20200817 add for xiaomi subsystem ramdump end*/ - static uint32_t scm_perf_client; static int scm_pas_bw_count; static DEFINE_MUTEX(scm_pas_bw_mutex); @@ -907,16 +815,8 @@ static void log_failure_reason(const struct pil_tz_data *d) return; } - /*xionghaifeng 20200817 add for xiaomi subsystem ramdump start*/ - memset(last_ssr_reason, 0, (size_t)MAX_SSR_REASON_LEN); - /*xionghaifeng 20200817 add for xiaomi subsystem ramdump end*/ strlcpy(reason, smem_reason, min(size, (size_t)MAX_SSR_REASON_LEN)); - strlcpy(last_modem_sfr_reason, smem_reason, min(size, (size_t)MAX_SSR_REASON_LEN)); pr_err("%s subsystem failure reason: %s.\n", name, reason); - /*xionghaifeng 20200817 add for xiaomi subsystem ramdump start*/ - snprintf(last_ssr_reason, (size_t)MAX_SSR_REASON_LEN, - "%s: %s", name, reason); - /*xionghaifeng 20200817 add for xiaomi subsystem ramdump end*/ } static int subsys_shutdown(const struct subsys_desc *subsys, bool force_stop) @@ -985,17 +885,6 @@ static void subsys_crash_shutdown(const struct subsys_desc *subsys) } } -static void check_nv(void *dev_id) -{ - struct pil_tz_data *d = subsys_to_data(dev_id); - if (strnstr(last_modem_sfr_reason, STR_NV_SIGNATURE_DESTROYED, strlen(last_modem_sfr_reason))) { - pr_err("errimei_dev: the NV has been destroyed, should restart to recovery\n"); - schedule_delayed_work(&create_kobj_work, msecs_to_jiffies(1*1000)); - } else { - subsystem_restart_dev(d->subsys); - } -} - static irqreturn_t subsys_err_fatal_intr_handler (int irq, void *dev_id) { struct pil_tz_data *d = subsys_to_data(dev_id); @@ -1008,7 +897,7 @@ static irqreturn_t subsys_err_fatal_intr_handler (int irq, void *dev_id) } subsys_set_crash_status(d->subsys, CRASH_STATUS_ERR_FATAL); log_failure_reason(d); - check_nv(dev_id); + subsystem_restart_dev(d->subsys); return IRQ_HANDLED; } @@ -1026,7 +915,7 @@ static irqreturn_t subsys_wdog_bite_irq_handler(int irq, void *dev_id) __func__); subsys_set_crash_status(d->subsys, CRASH_STATUS_WDOG_BITE); log_failure_reason(d); - check_nv(dev_id); + subsystem_restart_dev(d->subsys); return IRQ_HANDLED; } @@ -1418,25 +1307,12 @@ static struct platform_driver pil_tz_driver = { static int __init pil_tz_init(void) { - /*xionghaifeng 20200817 add for xiaomi subsystem ramdump start*/ - last_ssr_reason_entry = proc_create("last_mcrash", - S_IFREG | S_IRUGO, NULL, &last_ssr_reason_file_ops); - if (!last_ssr_reason_entry) { - printk(KERN_ERR "pil: cannot create proc entry last_mcrash\n"); - } - /*xionghaifeng 20200817 add for xiaomi subsystem ramdump end*/ return platform_driver_register(&pil_tz_driver); } module_init(pil_tz_init); static void __exit pil_tz_exit(void) { - /*xionghaifeng 20200817 add for xiaomi subsystem ramdump start*/ - if (last_ssr_reason_entry) { - remove_proc_entry("last_mcrash", NULL); - last_ssr_reason_entry = NULL; - } - /*xionghaifeng 20200817 add for xiaomi subsystem ramdump end*/ platform_driver_unregister(&pil_tz_driver); } module_exit(pil_tz_exit); diff --git a/drivers/soc/qcom/wcnss/Kconfig b/drivers/soc/qcom/wcnss/Kconfig new file mode 100644 index 000000000000..fa0f3183bca3 --- /dev/null +++ b/drivers/soc/qcom/wcnss/Kconfig @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-2.0-only +config WCNSS_CORE + tristate "Qualcomm Technologies Inc. WCNSS CORE driver" + select WIRELESS_EXT + select WEXT_PRIV + select WEXT_CORE + select WEXT_SPY + help + This module adds support for WLAN connectivity subsystem + This module is responsible for communicating WLAN on/off + Core driver for the Qualcomm Technologies Inc. WCNSS triple play + connectivity subsystem, Enable WCNSS core platform driver + for WLAN. + +config WCNSS_CORE_PRONTO + tristate "Qualcomm Technologies Inc. WCNSS Pronto Support" + depends on WCNSS_CORE + help + Pronto Support for the Qualcomm Technologies Inc. WCNSS triple + play connectivity subsystem, Enable WCNSS core platform driver + for WLAN. This module adds support for WLAN connectivity subsystem + This module is responsible for communicating WLAN on/off + +config WCNSS_REGISTER_DUMP_ON_BITE + bool "Enable/disable WCNSS register dump when there is a WCNSS bite" + depends on WCNSS_CORE_PRONTO + help + When Apps receives a WDOG bite from WCNSS, collecting a register dump + of WCNSS is helpful to root cause the failure. WCNSS may not be + properly clocked in some WCNSS bite cases, and that may cause unclocked + register access failures. So this feature is to enable/disable the + register dump on WCNSS WDOG bite. + +config CNSS_CRYPTO + tristate "Enable CNSS crypto support" + help + Add crypto support for the WLAN driver module. + This feature enable wlan driver to use the crypto APIs exported + from cnss platform driver. This crypto APIs used to generate cipher + key and add support for the WLAN driver module security protocol. diff --git a/drivers/soc/qcom/wcnss/Makefile b/drivers/soc/qcom/wcnss/Makefile new file mode 100644 index 000000000000..0dd537b84b67 --- /dev/null +++ b/drivers/soc/qcom/wcnss/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Makefile for WCNSS triple-play driver + +wcnsscore-objs += wcnss_wlan.o wcnss_vreg.o + +obj-$(CONFIG_WCNSS_CORE) += wcnsscore.o diff --git a/drivers/soc/qcom/wcnss/wcnss_vreg.c b/drivers/soc/qcom/wcnss/wcnss_vreg.c new file mode 100644 index 000000000000..e38f4a24c286 --- /dev/null +++ b/drivers/soc/qcom/wcnss/wcnss_vreg.c @@ -0,0 +1,826 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void __iomem *msm_wcnss_base; +static LIST_HEAD(power_on_lock_list); +static DEFINE_MUTEX(list_lock); +static DEFINE_SEMAPHORE(wcnss_power_on_lock); +static int auto_detect; +static int is_power_on; + +#define RIVA_PMU_OFFSET 0x28 + +#define RIVA_SPARE_OFFSET 0x0b4 +#define PRONTO_SPARE_OFFSET 0x1088 +#define NVBIN_DLND_BIT BIT(25) + +#define PRONTO_IRIS_REG_READ_OFFSET 0x1134 +#define PRONTO_IRIS_REG_CHIP_ID 0x04 +#define PRONTO_IRIS_REG_CHIP_ID_MASK 0xffff +/* IRIS card chip ID's */ +#define WCN3660 0x0200 +#define WCN3660A 0x0300 +#define WCN3660B 0x0400 +#define WCN3620 0x5111 +#define WCN3620A 0x5112 +#define WCN3610 0x9101 +#define WCN3610V1 0x9110 +#define WCN3615 0x8110 + +#define WCNSS_PMU_CFG_IRIS_XO_CFG BIT(3) +#define WCNSS_PMU_CFG_IRIS_XO_EN BIT(4) +#define WCNSS_PMU_CFG_IRIS_XO_CFG_STS BIT(6) /* 1: in progress, 0: done */ + +#define WCNSS_PMU_CFG_IRIS_RESET BIT(7) +#define WCNSS_PMU_CFG_IRIS_RESET_STS BIT(8) /* 1: in progress, 0: done */ +#define WCNSS_PMU_CFG_IRIS_XO_READ BIT(9) +#define WCNSS_PMU_CFG_IRIS_XO_READ_STS BIT(10) + +#define WCNSS_PMU_CFG_IRIS_XO_MODE 0x6 +#define WCNSS_PMU_CFG_IRIS_XO_MODE_48 (3 << 1) + +#define VREG_NULL_CONFIG 0x0000 +#define VREG_GET_REGULATOR_MASK 0x0001 +#define VREG_SET_VOLTAGE_MASK 0x0002 +#define VREG_OPTIMUM_MODE_MASK 0x0004 +#define VREG_ENABLE_MASK 0x0008 +#define VDD_PA "qcom,iris-vddpa" + +#define WCNSS_INVALID_IRIS_REG 0xbaadbaad + +struct vregs_info { + const char * const name; + const char * const curr; + const char * const volt; + int state; + bool required; + struct regulator *regulator; +}; + +/* IRIS regulators for Pronto hardware */ +static struct vregs_info iris_vregs[] = { + {"qcom,iris-vddxo", "qcom,iris-vddxo-current", + "qcom,iris-vddxo-voltage-level", VREG_NULL_CONFIG, true, NULL}, + {"qcom,iris-vddrfa", "qcom,iris-vddrfa-current", + "qcom,iris-vddrfa-voltage-level", VREG_NULL_CONFIG, true, NULL}, + {"qcom,iris-vddpa", "qcom,iris-vddpa-current", + "qcom,iris-vddpa-voltage-level", VREG_NULL_CONFIG, false, NULL}, + {"qcom,iris-vdddig", "qcom,iris-vdddig-current", + "qcom,iris-vdddig-voltage-level", VREG_NULL_CONFIG, true, NULL}, +}; + +/* WCNSS regulators for Pronto hardware */ +static struct vregs_info pronto_vregs[] = { + {"qcom,pronto-vddmx", "qcom,pronto-vddmx-current", + "qcom,vddmx-voltage-level", VREG_NULL_CONFIG, true, NULL}, + {"qcom,pronto-vddcx", "qcom,pronto-vddcx-current", + "qcom,vddcx-voltage-level", VREG_NULL_CONFIG, true, NULL}, + {"qcom,pronto-vddpx", "qcom,pronto-vddpx-current", + "qcom,vddpx-voltage-level", VREG_NULL_CONFIG, true, NULL}, +}; + +struct host_driver { + char name[20]; + struct list_head list; +}; + +enum { + IRIS_3660, /* also 3660A and 3680 */ + IRIS_3620, + IRIS_3610, + IRIS_3615 +}; + +int xo_auto_detect(u32 reg) +{ + reg >>= 30; + + switch (reg) { + case IRIS_3660: + return WCNSS_XO_48MHZ; + + case IRIS_3620: + return WCNSS_XO_19MHZ; + + case IRIS_3610: + return WCNSS_XO_19MHZ; + + case IRIS_3615: + return WCNSS_XO_19MHZ; + + default: + return WCNSS_XO_INVALID; + } +} + +int wcnss_get_iris_name(char *iris_name) +{ + struct wcnss_wlan_config *cfg = NULL; + u32 iris_id; + + cfg = wcnss_get_wlan_config(); + + if (cfg) { + iris_id = cfg->iris_id; + iris_id = PRONTO_IRIS_REG_CHIP_ID_MASK & (iris_id >> 16); + } else { + return 1; + } + + switch (iris_id) { + case WCN3660: + memcpy(iris_name, "WCN3660", sizeof("WCN3660")); + break; + case WCN3660A: + memcpy(iris_name, "WCN3660A", sizeof("WCN3660A")); + break; + case WCN3660B: + memcpy(iris_name, "WCN3660B", sizeof("WCN3660B")); + break; + case WCN3620: + memcpy(iris_name, "WCN3620", sizeof("WCN3620")); + break; + case WCN3620A: + memcpy(iris_name, "WCN3620A", sizeof("WCN3620A")); + break; + case WCN3610: + memcpy(iris_name, "WCN3610", sizeof("WCN3610")); + break; + case WCN3610V1: + memcpy(iris_name, "WCN3610V1", sizeof("WCN3610V1")); + break; + case WCN3615: + memcpy(iris_name, "WCN3615", sizeof("WCN3615")); + break; + default: + return 1; + } + + return 0; +} +EXPORT_SYMBOL(wcnss_get_iris_name); + +int validate_iris_chip_id(u32 reg) +{ + u32 iris_id; + + iris_id = PRONTO_IRIS_REG_CHIP_ID_MASK & (reg >> 16); + + switch (iris_id) { + case WCN3660: + case WCN3660A: + case WCN3660B: + case WCN3620: + case WCN3620A: + case WCN3610: + case WCN3610V1: + case WCN3615: + return 0; + default: + return 1; + } +} + +static int +wcnss_dt_parse_vreg_level(struct device *dev, int index, + const char *current_vreg_name, const char *vreg_name, + struct vregs_level *vlevel) +{ + int ret = 0; + /* array used to store nominal, low and high voltage values */ + u32 voltage_levels[3], current_vreg; + + ret = of_property_read_u32_array(dev->of_node, vreg_name, + voltage_levels, + ARRAY_SIZE(voltage_levels)); + if (ret) { + wcnss_log(ERR, "error reading %s property\n", vreg_name); + return ret; + } + + vlevel[index].nominal_min = voltage_levels[0]; + vlevel[index].low_power_min = voltage_levels[1]; + vlevel[index].max_voltage = voltage_levels[2]; + + ret = of_property_read_u32(dev->of_node, current_vreg_name, + ¤t_vreg); + if (ret) { + wcnss_log(ERR, "error reading %s property\n", + current_vreg_name); + return ret; + } + + vlevel[index].uA_load = current_vreg; + + return ret; +} + +int +wcnss_parse_voltage_regulator(struct wcnss_wlan_config *wlan_config, + struct device *dev) +{ + int rc, vreg_i; + + /* Parse pronto voltage regulators from device node */ + for (vreg_i = 0; vreg_i < PRONTO_REGULATORS; vreg_i++) { + pronto_vregs[vreg_i].regulator = + devm_regulator_get_optional(dev, + pronto_vregs[vreg_i].name); + if (IS_ERR(pronto_vregs[vreg_i].regulator)) { + if (pronto_vregs[vreg_i].required) { + rc = PTR_ERR(pronto_vregs[vreg_i].regulator); + wcnss_log(ERR, + "regulator get of %s failed (%d)\n", + pronto_vregs[vreg_i].name, rc); + return rc; + } + + wcnss_log(DBG, + "Skip optional regulator configuration: %s\n", + pronto_vregs[vreg_i].name); + continue; + } + + rc = wcnss_dt_parse_vreg_level(dev, vreg_i, + pronto_vregs[vreg_i].curr, + pronto_vregs[vreg_i].volt, + wlan_config->pronto_vlevel); + if (rc) { + wcnss_log(ERR, + "error reading voltage-level property\n"); + return rc; + } + pronto_vregs[vreg_i].state |= VREG_GET_REGULATOR_MASK; + } + + /* Parse iris voltage regulators from device node */ + for (vreg_i = 0; vreg_i < IRIS_REGULATORS; vreg_i++) { + iris_vregs[vreg_i].regulator = + devm_regulator_get_optional(dev, + iris_vregs[vreg_i].name); + if (IS_ERR(iris_vregs[vreg_i].regulator)) { + if (iris_vregs[vreg_i].required) { + rc = PTR_ERR(iris_vregs[vreg_i].regulator); + wcnss_log(ERR, + "regulator get of %s failed (%d)\n", + iris_vregs[vreg_i].name, rc); + return rc; + } + + wcnss_log(DBG, + "Skip optional regulator configuration: %s\n", + iris_vregs[vreg_i].name); + continue; + } + + rc = wcnss_dt_parse_vreg_level(dev, vreg_i, + iris_vregs[vreg_i].curr, + iris_vregs[vreg_i].volt, + wlan_config->iris_vlevel); + if (rc) { + wcnss_log(ERR, + "error reading voltage-level property\n"); + return rc; + } + iris_vregs[vreg_i].state |= VREG_GET_REGULATOR_MASK; + } + + return 0; +} + +void wcnss_iris_reset(u32 reg, void __iomem *pmu_conf_reg) +{ + /* Reset IRIS */ + reg |= WCNSS_PMU_CFG_IRIS_RESET; + writel_relaxed(reg, pmu_conf_reg); + + /* Wait for PMU_CFG.iris_reg_reset_sts */ + while (readl_relaxed(pmu_conf_reg) & + WCNSS_PMU_CFG_IRIS_RESET_STS) + cpu_relax(); + + /* Reset iris reset bit */ + reg &= ~WCNSS_PMU_CFG_IRIS_RESET; + writel_relaxed(reg, pmu_conf_reg); +} + +static int +configure_iris_xo(struct device *dev, + struct wcnss_wlan_config *cfg, + int on, int *iris_xo_set) +{ + u32 reg = 0, i = 0; + u32 iris_reg = WCNSS_INVALID_IRIS_REG; + int rc = 0; + int pmu_offset = 0; + int spare_offset = 0; + void __iomem *pmu_conf_reg; + void __iomem *spare_reg; + void __iomem *iris_read_reg; + struct clk *clk; + struct clk *clk_rf = NULL; + bool use_48mhz_xo; + + use_48mhz_xo = cfg->use_48mhz_xo; + + if (wcnss_hardware_type() == WCNSS_PRONTO_HW) { + pmu_offset = PRONTO_PMU_OFFSET; + spare_offset = PRONTO_SPARE_OFFSET; + + clk = clk_get(dev, "xo"); + if (IS_ERR(clk)) { + wcnss_log(ERR, "Couldn't get xo clock\n"); + return PTR_ERR(clk); + } + + } else { + pmu_offset = RIVA_PMU_OFFSET; + spare_offset = RIVA_SPARE_OFFSET; + + clk = clk_get(dev, "cxo"); + if (IS_ERR(clk)) { + wcnss_log(ERR, "Couldn't get cxo clock\n"); + return PTR_ERR(clk); + } + } + + if (on) { + msm_wcnss_base = cfg->msm_wcnss_base; + if (!msm_wcnss_base) { + wcnss_log(ERR, "ioremap wcnss physical failed\n"); + goto fail; + } + + /* Enable IRIS XO */ + rc = clk_prepare_enable(clk); + if (rc) { + wcnss_log(ERR, "clk enable failed\n"); + goto fail; + } + + /* NV bit is set to indicate that platform driver is capable + * of doing NV download. + */ + wcnss_log(DBG, "Indicate NV bin download\n"); + spare_reg = msm_wcnss_base + spare_offset; + reg = readl_relaxed(spare_reg); + reg |= NVBIN_DLND_BIT; + writel_relaxed(reg, spare_reg); + + pmu_conf_reg = msm_wcnss_base + pmu_offset; + writel_relaxed(0, pmu_conf_reg); + reg = readl_relaxed(pmu_conf_reg); + reg |= WCNSS_PMU_CFG_GC_BUS_MUX_SEL_TOP | + WCNSS_PMU_CFG_IRIS_XO_EN; + writel_relaxed(reg, pmu_conf_reg); + + if (wcnss_xo_auto_detect_enabled()) { + iris_read_reg = msm_wcnss_base + + PRONTO_IRIS_REG_READ_OFFSET; + iris_reg = readl_relaxed(iris_read_reg); + } + + wcnss_iris_reset(reg, pmu_conf_reg); + + if (iris_reg != WCNSS_INVALID_IRIS_REG) { + iris_reg &= 0xffff; + iris_reg |= PRONTO_IRIS_REG_CHIP_ID; + writel_relaxed(iris_reg, iris_read_reg); + do { + /* Iris read */ + reg = readl_relaxed(pmu_conf_reg); + reg |= WCNSS_PMU_CFG_IRIS_XO_READ; + writel_relaxed(reg, pmu_conf_reg); + + /* Wait for PMU_CFG.iris_reg_read_sts */ + while (readl_relaxed(pmu_conf_reg) & + WCNSS_PMU_CFG_IRIS_XO_READ_STS) + cpu_relax(); + + iris_reg = readl_relaxed(iris_read_reg); + wcnss_log(INFO, "IRIS Reg: %08x\n", iris_reg); + + if (validate_iris_chip_id(iris_reg) && i >= 4) { + wcnss_log(INFO, + "IRIS Card absent/invalid\n"); + auto_detect = WCNSS_XO_INVALID; + /* Reset iris read bit */ + reg &= ~WCNSS_PMU_CFG_IRIS_XO_READ; + /* Clear XO_MODE[b2:b1] bits. + * Clear implies 19.2 MHz TCXO + */ + reg &= ~(WCNSS_PMU_CFG_IRIS_XO_MODE); + goto xo_configure; + } else if (!validate_iris_chip_id(iris_reg)) { + wcnss_log(DBG, + "IRIS Card is present\n"); + break; + } + reg &= ~WCNSS_PMU_CFG_IRIS_XO_READ; + writel_relaxed(reg, pmu_conf_reg); + wcnss_iris_reset(reg, pmu_conf_reg); + } while (i++ < 5); + auto_detect = xo_auto_detect(iris_reg); + + /* Reset iris read bit */ + reg &= ~WCNSS_PMU_CFG_IRIS_XO_READ; + + } else if (wcnss_xo_auto_detect_enabled()) { + /* Default to 48 MHZ */ + auto_detect = WCNSS_XO_48MHZ; + } else { + auto_detect = WCNSS_XO_INVALID; + } + + cfg->iris_id = iris_reg; + + /* Clear XO_MODE[b2:b1] bits. Clear implies 19.2 MHz TCXO */ + reg &= ~(WCNSS_PMU_CFG_IRIS_XO_MODE); + + if ((use_48mhz_xo && auto_detect == WCNSS_XO_INVALID) || + auto_detect == WCNSS_XO_48MHZ) { + reg |= WCNSS_PMU_CFG_IRIS_XO_MODE_48; + + if (iris_xo_set) + *iris_xo_set = WCNSS_XO_48MHZ; + } + +xo_configure: + writel_relaxed(reg, pmu_conf_reg); + + wcnss_iris_reset(reg, pmu_conf_reg); + + /* Start IRIS XO configuration */ + reg |= WCNSS_PMU_CFG_IRIS_XO_CFG; + writel_relaxed(reg, pmu_conf_reg); + + /* Wait for XO configuration to finish */ + while (readl_relaxed(pmu_conf_reg) & + WCNSS_PMU_CFG_IRIS_XO_CFG_STS) + cpu_relax(); + + /* Stop IRIS XO configuration */ + reg &= ~(WCNSS_PMU_CFG_GC_BUS_MUX_SEL_TOP | + WCNSS_PMU_CFG_IRIS_XO_CFG); + writel_relaxed(reg, pmu_conf_reg); + clk_disable_unprepare(clk); + + if ((!use_48mhz_xo && auto_detect == WCNSS_XO_INVALID) || + auto_detect == WCNSS_XO_19MHZ) { + clk_rf = clk_get(dev, "rf_clk"); + if (IS_ERR(clk_rf)) { + wcnss_log(ERR, "Couldn't get rf_clk\n"); + goto fail; + } + + rc = clk_prepare_enable(clk_rf); + if (rc) { + wcnss_log(ERR, "clk_rf enable failed\n"); + goto fail; + } + if (iris_xo_set) + *iris_xo_set = WCNSS_XO_19MHZ; + } + + } else if ((!use_48mhz_xo && auto_detect == WCNSS_XO_INVALID) || + auto_detect == WCNSS_XO_19MHZ) { + clk_rf = clk_get(dev, "rf_clk"); + if (IS_ERR(clk_rf)) { + wcnss_log(ERR, "Couldn't get rf_clk\n"); + goto fail; + } + clk_disable_unprepare(clk_rf); + } + + /* Add some delay for XO to settle */ + msleep(20); + +fail: + clk_put(clk); + + if (clk_rf) + clk_put(clk_rf); + + return rc; +} + +/* Helper routine to turn off all WCNSS & IRIS vregs */ +static void wcnss_vregs_off(struct vregs_info regulators[], uint size, + struct vregs_level *voltage_level) +{ + int i, rc = 0; + struct wcnss_wlan_config *cfg; + + cfg = wcnss_get_wlan_config(); + + if (!cfg) { + wcnss_log(ERR, "Failed to get WLAN configuration\n"); + return; + } + + /* Regulators need to be turned off in the reverse order */ + for (i = (size - 1); i >= 0; i--) { + if (regulators[i].state == VREG_NULL_CONFIG) + continue; + + /* Remove PWM mode */ + if (regulators[i].state & VREG_OPTIMUM_MODE_MASK) { + rc = regulator_set_load(regulators[i].regulator, 0); + if (rc < 0) { + wcnss_log(ERR, + "regulator set load(%s) failed (%d)\n", + regulators[i].name, rc); + } + } + + /* Set voltage to lowest level */ + if (regulators[i].state & VREG_SET_VOLTAGE_MASK) { + if (cfg->is_pronto_vadc) { + if (cfg->vbatt < WCNSS_VBATT_THRESHOLD && + !memcmp(regulators[i].name, + VDD_PA, sizeof(VDD_PA))) { + voltage_level[i].max_voltage = + WCNSS_VBATT_LOW; + } + } + + rc = regulator_set_voltage(regulators[i].regulator, + voltage_level[i].low_power_min, + voltage_level[i].max_voltage); + if (rc) + wcnss_log(ERR, + "regulator_set_voltage(%s) failed (%d)\n", + regulators[i].name, rc); + } + + /* Disable regulator */ + if (regulators[i].state & VREG_ENABLE_MASK) { + rc = regulator_disable(regulators[i].regulator); + if (rc < 0) + wcnss_log(ERR, "vreg %s disable failed (%d)\n", + regulators[i].name, rc); + } + } + +} + +/* Common helper routine to turn on all WCNSS & IRIS vregs */ +static int wcnss_vregs_on(struct device *dev, + struct vregs_info regulators[], uint size, + struct vregs_level *voltage_level) +{ + int i, rc = 0, reg_cnt; + struct wcnss_wlan_config *cfg; + + cfg = wcnss_get_wlan_config(); + + if (!cfg) { + wcnss_log(ERR, "Failed to get WLAN configuration\n"); + return -EINVAL; + } + + for (i = 0; i < size; i++) { + if (regulators[i].state == VREG_NULL_CONFIG) + continue; + + reg_cnt = regulator_count_voltages(regulators[i].regulator); + /* Set voltage to nominal. Exclude swtiches e.g. LVS */ + if ((voltage_level[i].nominal_min || + voltage_level[i].max_voltage) && (reg_cnt > 0)) { + if (cfg->is_pronto_vadc) { + if (cfg->vbatt < WCNSS_VBATT_THRESHOLD && + !memcmp(regulators[i].name, + VDD_PA, sizeof(VDD_PA))) { + voltage_level[i].nominal_min = + WCNSS_VBATT_INITIAL; + voltage_level[i].max_voltage = + WCNSS_VBATT_LOW; + } + } + + rc = regulator_set_voltage(regulators[i].regulator, + voltage_level[i].nominal_min, + voltage_level[i].max_voltage); + + if (rc) { + wcnss_log(ERR, + "regulator_set_voltage(%s) failed (%d)\n", + regulators[i].name, rc); + goto fail; + } + regulators[i].state |= VREG_SET_VOLTAGE_MASK; + } + + /* Vote for PWM/PFM mode if needed */ + if (voltage_level[i].uA_load && (reg_cnt > 0)) { + rc = regulator_set_load(regulators[i].regulator, + voltage_level[i].uA_load); + if (rc < 0) { + wcnss_log(ERR, + "regulator set load(%s) failed (%d)\n", + regulators[i].name, rc); + goto fail; + } + regulators[i].state |= VREG_OPTIMUM_MODE_MASK; + } + + /* Enable the regulator */ + rc = regulator_enable(regulators[i].regulator); + if (rc) { + wcnss_log(ERR, "vreg %s enable failed (%d)\n", + regulators[i].name, rc); + goto fail; + } + regulators[i].state |= VREG_ENABLE_MASK; + } + + return rc; + +fail: + wcnss_vregs_off(regulators, size, voltage_level); + return rc; +} + +static void wcnss_iris_vregs_off(enum wcnss_hw_type hw_type, + struct wcnss_wlan_config *cfg) +{ + switch (hw_type) { + case WCNSS_PRONTO_HW: + wcnss_vregs_off(iris_vregs, ARRAY_SIZE(iris_vregs), + cfg->iris_vlevel); + break; + default: + wcnss_log(ERR, "%s invalid hardware %d\n", __func__, hw_type); + } +} + +static int wcnss_iris_vregs_on(struct device *dev, + enum wcnss_hw_type hw_type, + struct wcnss_wlan_config *cfg) +{ + int ret = -1; + + switch (hw_type) { + case WCNSS_PRONTO_HW: + ret = wcnss_vregs_on(dev, iris_vregs, ARRAY_SIZE(iris_vregs), + cfg->iris_vlevel); + break; + default: + wcnss_log(ERR, "%s invalid hardware %d\n", __func__, hw_type); + } + return ret; +} + +static void wcnss_core_vregs_off(enum wcnss_hw_type hw_type, + struct wcnss_wlan_config *cfg) +{ + switch (hw_type) { + case WCNSS_PRONTO_HW: + wcnss_vregs_off(pronto_vregs, + ARRAY_SIZE(pronto_vregs), cfg->pronto_vlevel); + break; + default: + wcnss_log(ERR, "%s invalid hardware %d\n", __func__, hw_type); + } +} + +static int wcnss_core_vregs_on(struct device *dev, + enum wcnss_hw_type hw_type, + struct wcnss_wlan_config *cfg) +{ + int ret = -1; + + switch (hw_type) { + case WCNSS_PRONTO_HW: + ret = wcnss_vregs_on(dev, pronto_vregs, + ARRAY_SIZE(pronto_vregs), + cfg->pronto_vlevel); + break; + default: + wcnss_log(ERR, "%s invalid hardware %d\n", __func__, hw_type); + } + + return ret; +} + +int wcnss_wlan_power(struct device *dev, + struct wcnss_wlan_config *cfg, + enum wcnss_opcode on, int *iris_xo_set) +{ + int rc = 0; + enum wcnss_hw_type hw_type = wcnss_hardware_type(); + + down(&wcnss_power_on_lock); + if (on) { + /* RIVA regulator settings */ + rc = wcnss_core_vregs_on(dev, hw_type, + cfg); + if (rc) + goto fail_wcnss_on; + + /* IRIS regulator settings */ + rc = wcnss_iris_vregs_on(dev, hw_type, + cfg); + if (rc) + goto fail_iris_on; + + /* Configure IRIS XO */ + rc = configure_iris_xo(dev, cfg, + WCNSS_WLAN_SWITCH_ON, iris_xo_set); + if (rc) + goto fail_iris_xo; + + is_power_on = true; + + } else if (is_power_on) { + is_power_on = false; + configure_iris_xo(dev, cfg, + WCNSS_WLAN_SWITCH_OFF, NULL); + wcnss_iris_vregs_off(hw_type, cfg); + wcnss_core_vregs_off(hw_type, cfg); + } + + up(&wcnss_power_on_lock); + return rc; + +fail_iris_xo: + wcnss_iris_vregs_off(hw_type, cfg); + +fail_iris_on: + wcnss_core_vregs_off(hw_type, cfg); + +fail_wcnss_on: + up(&wcnss_power_on_lock); + return rc; +} +EXPORT_SYMBOL(wcnss_wlan_power); + +/* + * During SSR WCNSS should not be 'powered on' until all the host drivers + * finish their shutdown routines. Host drivers use below APIs to + * synchronize power-on. WCNSS will not be 'powered on' until all the + * requests(to lock power-on) are freed. + */ +int wcnss_req_power_on_lock(char *driver_name) +{ + struct host_driver *node; + + if (!driver_name) + goto err; + + node = kmalloc(sizeof(*node), GFP_KERNEL); + if (!node) + goto err; + strlcpy(node->name, driver_name, sizeof(node->name)); + + mutex_lock(&list_lock); + /* Lock when the first request is added */ + if (list_empty(&power_on_lock_list)) + down(&wcnss_power_on_lock); + list_add(&node->list, &power_on_lock_list); + mutex_unlock(&list_lock); + + return 0; + +err: + return -EINVAL; +} +EXPORT_SYMBOL(wcnss_req_power_on_lock); + +int wcnss_free_power_on_lock(char *driver_name) +{ + int ret = -1; + struct host_driver *node; + + mutex_lock(&list_lock); + list_for_each_entry(node, &power_on_lock_list, list) { + if (!strcmp(node->name, driver_name)) { + list_del(&node->list); + kfree(node); + ret = 0; + break; + } + } + /* unlock when the last host driver frees the lock */ + if (list_empty(&power_on_lock_list)) + up(&wcnss_power_on_lock); + mutex_unlock(&list_lock); + + return ret; +} +EXPORT_SYMBOL(wcnss_free_power_on_lock); diff --git a/drivers/soc/qcom/wcnss/wcnss_wlan.c b/drivers/soc/qcom/wcnss/wcnss_wlan.c new file mode 100644 index 000000000000..7141b4997f46 --- /dev/null +++ b/drivers/soc/qcom/wcnss/wcnss_wlan.c @@ -0,0 +1,3961 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +#define DEVICE "wcnss_wlan" +#define CTRL_DEVICE "wcnss_ctrl" +#define VERSION "1.01" +#define WCNSS_PIL_DEVICE "wcnss" + +#define WCNSS_PINCTRL_STATE_DEFAULT "wcnss_default" +#define WCNSS_PINCTRL_STATE_SLEEP "wcnss_sleep" +#define WCNSS_PINCTRL_GPIO_STATE_DEFAULT "wcnss_gpio_default" + +#define WCNSS_DISABLE_PC_LATENCY 100 +#define WCNSS_ENABLE_PC_LATENCY PM_QOS_DEFAULT_VALUE +#define WCNSS_PM_QOS_TIMEOUT 15000 +#define IS_CAL_DATA_PRESENT 0 +#define WAIT_FOR_CBC_IND 2 +#define WCNSS_DUAL_BAND_CAPABILITY_OFFSET BIT(8) + +/* module params */ +#define WCNSS_CONFIG_UNSPECIFIED (-1) +#define UINT32_MAX (0xFFFFFFFFU) + +#define SUBSYS_NOTIF_MIN_INDEX 0 +#define SUBSYS_NOTIF_MAX_INDEX 9 +#define PROC_AWAKE_ID 12 /* 12th bit */ +#define AWAKE_BIT BIT(PROC_AWAKE_ID) +char *wcnss_subsys_notif_type[] = { + "SUBSYS_BEFORE_SHUTDOWN", + "SUBSYS_AFTER_SHUTDOWN", + "SUBSYS_BEFORE_POWERUP", + "SUBSYS_AFTER_POWERUP", + "SUBSYS_RAMDUMP_NOTIFICATION", + "SUBSYS_POWERUP_FAILURE", + "SUBSYS_PROXY_VOTE", + "SUBSYS_PROXY_UNVOTE", + "SUBSYS_SOC_RESET", + "SUBSYS_NOTIF_TYPE_COUNT" +}; + +static int has_48mhz_xo = WCNSS_CONFIG_UNSPECIFIED; +module_param(has_48mhz_xo, int, 0644); +MODULE_PARM_DESC(has_48mhz_xo, "Is an external 48 MHz XO present"); + +static int has_calibrated_data = WCNSS_CONFIG_UNSPECIFIED; +module_param(has_calibrated_data, int, 0644); +MODULE_PARM_DESC(has_calibrated_data, "whether calibrated data file available"); + +static int has_autodetect_xo = WCNSS_CONFIG_UNSPECIFIED; +module_param(has_autodetect_xo, int, 0644); +MODULE_PARM_DESC(has_autodetect_xo, "Perform auto detect to configure IRIS XO"); + +static DEFINE_SPINLOCK(reg_spinlock); + +#define RIVA_SPARE_OFFSET 0x0b4 +#define RIVA_SUSPEND_BIT BIT(24) + +#define CCU_RIVA_INVALID_ADDR_OFFSET 0x100 +#define CCU_RIVA_LAST_ADDR0_OFFSET 0x104 +#define CCU_RIVA_LAST_ADDR1_OFFSET 0x108 +#define CCU_RIVA_LAST_ADDR2_OFFSET 0x10c + +#define PRONTO_PMU_SPARE_OFFSET 0x1088 +#define PMU_A2XB_CFG_HSPLIT_RESP_LIMIT_OFFSET 0x117C + +#define PRONTO_PMU_COM_GDSCR_OFFSET 0x0024 +#define PRONTO_PMU_COM_GDSCR_SW_COLLAPSE BIT(0) +#define PRONTO_PMU_COM_GDSCR_HW_CTRL BIT(1) + +#define PRONTO_PMU_WLAN_BCR_OFFSET 0x0050 +#define PRONTO_PMU_WLAN_BCR_BLK_ARES BIT(0) + +#define PRONTO_PMU_WLAN_GDSCR_OFFSET 0x0054 +#define PRONTO_PMU_WLAN_GDSCR_SW_COLLAPSE BIT(0) + +#define PRONTO_PMU_WDOG_CTL 0x0068 + +#define PRONTO_PMU_CBCR_OFFSET 0x0008 +#define PRONTO_PMU_CBCR_CLK_EN BIT(0) + +#define PRONTO_PMU_COM_CPU_CBCR_OFFSET 0x0030 +#define PRONTO_PMU_COM_AHB_CBCR_OFFSET 0x0034 + +#define PRONTO_PMU_WLAN_AHB_CBCR_OFFSET 0x0074 +#define PRONTO_PMU_WLAN_AHB_CBCR_CLK_EN BIT(0) +#define PRONTO_PMU_WLAN_AHB_CBCR_CLK_OFF BIT(31) + +#define PRONTO_PMU_CPU_AHB_CMD_RCGR_OFFSET 0x0120 +#define PRONTO_PMU_CPU_AHB_CMD_RCGR_ROOT_EN BIT(1) + +#define PRONTO_PMU_CFG_OFFSET 0x1004 +#define PRONTO_PMU_COM_CSR_OFFSET 0x1040 +#define PRONTO_PMU_SOFT_RESET_OFFSET 0x104C + +#define PRONTO_QFUSE_DUAL_BAND_OFFSET 0x0018 + +#define A2XB_CFG_OFFSET 0x00 +#define A2XB_INT_SRC_OFFSET 0x0c +#define A2XB_TSTBUS_CTRL_OFFSET 0x14 +#define A2XB_TSTBUS_OFFSET 0x18 +#define A2XB_ERR_INFO_OFFSET 0x1c +#define A2XB_FIFO_FILL_OFFSET 0x07 +#define A2XB_READ_FIFO_FILL_MASK 0x3F +#define A2XB_CMD_FIFO_FILL_MASK 0x0F +#define A2XB_WRITE_FIFO_FILL_MASK 0x1F +#define A2XB_FIFO_EMPTY 0x2 +#define A2XB_FIFO_COUNTER 0xA + +#define WCNSS_TSTBUS_CTRL_EN BIT(0) +#define WCNSS_TSTBUS_CTRL_AXIM (0x02 << 1) +#define WCNSS_TSTBUS_CTRL_CMDFIFO (0x03 << 1) +#define WCNSS_TSTBUS_CTRL_WRFIFO (0x04 << 1) +#define WCNSS_TSTBUS_CTRL_RDFIFO (0x05 << 1) +#define WCNSS_TSTBUS_CTRL_CTRL (0x07 << 1) +#define WCNSS_TSTBUS_CTRL_AXIM_CFG0 (0x00 << 8) +#define WCNSS_TSTBUS_CTRL_AXIM_CFG1 (0x01 << 8) +#define WCNSS_TSTBUS_CTRL_CTRL_CFG0 (0x00 << 28) +#define WCNSS_TSTBUS_CTRL_CTRL_CFG1 (0x01 << 28) + +#define CCU_PRONTO_INVALID_ADDR_OFFSET 0x08 +#define CCU_PRONTO_LAST_ADDR0_OFFSET 0x0c +#define CCU_PRONTO_LAST_ADDR1_OFFSET 0x10 +#define CCU_PRONTO_LAST_ADDR2_OFFSET 0x14 + +#define CCU_PRONTO_AOWBR_ERR_ADDR_OFFSET 0x28 +#define CCU_PRONTO_AOWBR_TIMEOUT_REG_OFFSET 0xcc +#define CCU_PRONTO_AOWBR_ERR_TIMEOUT_OFFSET 0xd0 +#define CCU_PRONTO_A2AB_ERR_ADDR_OFFSET 0x18 + +#define PRONTO_SAW2_SPM_STS_OFFSET 0x0c +#define PRONTO_SAW2_SPM_CTL 0x30 +#define PRONTO_SAW2_SAW2_VERSION 0xFD0 +#define PRONTO_SAW2_MAJOR_VER_OFFSET 0x1C + +#define PRONTO_PLL_STATUS_OFFSET 0x1c +#define PRONTO_PLL_MODE_OFFSET 0x1c0 + +#define MCU_APB2PHY_STATUS_OFFSET 0xec +#define MCU_CBR_CCAHB_ERR_OFFSET 0x380 +#define MCU_CBR_CAHB_ERR_OFFSET 0x384 +#define MCU_CBR_CCAHB_TIMEOUT_OFFSET 0x388 +#define MCU_CBR_CAHB_TIMEOUT_OFFSET 0x38c +#define MCU_DBR_CDAHB_ERR_OFFSET 0x390 +#define MCU_DBR_DAHB_ERR_OFFSET 0x394 +#define MCU_DBR_CDAHB_TIMEOUT_OFFSET 0x398 +#define MCU_DBR_DAHB_TIMEOUT_OFFSET 0x39c +#define MCU_FDBR_CDAHB_ERR_OFFSET 0x3a0 +#define MCU_FDBR_FDAHB_ERR_OFFSET 0x3a4 +#define MCU_FDBR_CDAHB_TIMEOUT_OFFSET 0x3a8 +#define MCU_FDBR_FDAHB_TIMEOUT_OFFSET 0x3ac +#define PRONTO_PMU_CCPU_BOOT_REMAP_OFFSET 0x2004 + +#define WCNSS_DEF_WLAN_RX_BUFF_COUNT 1024 + +#define WCNSS_CTRL_CHANNEL "WCNSS_CTRL" +#define WCNSS_MAX_FRAME_SIZE (4 * 1024) +#define WCNSS_VERSION_LEN 30 +#define WCNSS_MAX_BUILD_VER_LEN 256 +#define WCNSS_MAX_CMD_LEN (128) +#define WCNSS_MIN_CMD_LEN (3) + +/* control messages from userspace */ +#define WCNSS_USR_CTRL_MSG_START 0x00000000 +#define WCNSS_USR_HAS_CAL_DATA (WCNSS_USR_CTRL_MSG_START + 2) +#define WCNSS_USR_WLAN_MAC_ADDR (WCNSS_USR_CTRL_MSG_START + 3) +#define WCNSS_MAX_USR_BT_PROFILE_IND_CMD_SIZE 64 + +#define MAC_ADDRESS_STR "%02x:%02x:%02x:%02x:%02x:%02x" +#define SHOW_MAC_ADDRESS_STR "%02x:%02x:%02x:%02x:%02x:%02x\n" +#define WCNSS_USER_MAC_ADDR_LENGTH 18 + +/* message types */ +#define WCNSS_CTRL_MSG_START 0x01000000 +#define WCNSS_VERSION_REQ (WCNSS_CTRL_MSG_START + 0) +#define WCNSS_VERSION_RSP (WCNSS_CTRL_MSG_START + 1) +#define WCNSS_NVBIN_DNLD_REQ (WCNSS_CTRL_MSG_START + 2) +#define WCNSS_NVBIN_DNLD_RSP (WCNSS_CTRL_MSG_START + 3) +#define WCNSS_CALDATA_UPLD_REQ (WCNSS_CTRL_MSG_START + 4) +#define WCNSS_CALDATA_UPLD_RSP (WCNSS_CTRL_MSG_START + 5) +#define WCNSS_CALDATA_DNLD_REQ (WCNSS_CTRL_MSG_START + 6) +#define WCNSS_CALDATA_DNLD_RSP (WCNSS_CTRL_MSG_START + 7) +#define WCNSS_VBATT_LEVEL_IND (WCNSS_CTRL_MSG_START + 8) +#define WCNSS_BUILD_VER_REQ (WCNSS_CTRL_MSG_START + 9) +#define WCNSS_BUILD_VER_RSP (WCNSS_CTRL_MSG_START + 10) +#define WCNSS_PM_CONFIG_REQ (WCNSS_CTRL_MSG_START + 11) +#define WCNSS_CBC_COMPLETE_IND (WCNSS_CTRL_MSG_START + 12) + +/* max 20mhz channel count */ +#define WCNSS_MAX_CH_NUM 45 +#define WCNSS_MAX_PIL_RETRY 2 + +#define VALID_VERSION(version) \ + ((strcmp(version, "INVALID")) ? 1 : 0) + +#define FW_CALDATA_CAPABLE() \ + ((penv->fw_major >= 1) && (penv->fw_minor >= 5) ? 1 : 0) + +static int wcnss_pinctrl_set_state(bool active); + +struct smd_msg_hdr { + unsigned int msg_type; + unsigned int msg_len; +}; + +struct wcnss_version { + struct smd_msg_hdr hdr; + unsigned char major; + unsigned char minor; + unsigned char version; + unsigned char revision; +}; + +struct wcnss_download_nv_resp { + struct smd_msg_hdr hdr; + u8 status; +} __packed; + +struct wcnss_download_cal_data_resp { + struct smd_msg_hdr hdr; + u8 status; +} __packed; + +struct wcnss_pmic_dump { + char reg_name[10]; + u16 reg_addr; +}; + +static int wcnss_notif_cb(struct notifier_block *this, unsigned long code, + void *ss_handle); + +static struct notifier_block wnb = { + .notifier_call = wcnss_notif_cb, +}; + +#define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv" + +/* On SMD channel 4K of maximum data can be transferred, including message + * header, so NV fragment size as next multiple of 1Kb is 3Kb. + */ +#define NV_FRAGMENT_SIZE 3072 +#define NVBIN_FILE_SIZE 64 +#define IRIS_VARIANT_SIZE 8 +#define MAX_CALIBRATED_DATA_SIZE (64 * 1024) +#define LAST_FRAGMENT BIT(0) +#define MESSAGE_TO_FOLLOW BIT(1) +#define CAN_RECEIVE_CALDATA BIT(15) +#define WCNSS_RESP_SUCCESS 1 +#define WCNSS_RESP_FAIL 0 + +/* Macro to find the total number fragments of the NV bin Image */ +#define TOTALFRAGMENTS(x) ((((x) % NV_FRAGMENT_SIZE) == 0) ? \ + ((x) / NV_FRAGMENT_SIZE) : (((x) / NV_FRAGMENT_SIZE) + 1)) + +struct nvbin_dnld_req_params { + /* Fragment sequence number of the NV bin Image. NV Bin Image + * might not fit into one message due to size limitation of + * the SMD channel FIFO so entire NV blob is chopped into + * multiple fragments starting with sequence number 0. The + * last fragment is indicated by marking is_last_fragment field + * to 1. At receiving side, NV blobs would be concatenated + * together without any padding bytes in between. + */ + unsigned short frag_number; + + /* bit 0: When set to 1 it indicates that no more fragments will + * be sent. + * bit 1: When set, a new message will be followed by this message + * bit 2- bit 14: Reserved + * bit 15: when set, it indicates that the sender is capable of + * receiving Calibrated data. + */ + unsigned short msg_flags; + + /* NV Image size (number of bytes) */ + unsigned int nvbin_buffer_size; + + /* Following the 'nvbin_buffer_size', there should be + * nvbin_buffer_size bytes of NV bin Image i.e. + * uint8[nvbin_buffer_size]. + */ +}; + +struct nvbin_dnld_req_msg { + /* Note: The length specified in nvbin_dnld_req_msg messages + * should be hdr.msg_len = sizeof(nvbin_dnld_req_msg) + + * nvbin_buffer_size. + */ + struct smd_msg_hdr hdr; + struct nvbin_dnld_req_params dnld_req_params; +}; + +struct cal_data_params { + /* The total size of the calibrated data, including all the + * fragments. + */ + unsigned int total_size; + unsigned short frag_number; + /* bit 0: When set to 1 it indicates that no more fragments will + * be sent. + * bit 1: When set, a new message will be followed by this message + * bit 2- bit 15: Reserved + */ + unsigned short msg_flags; + /* fragment size + */ + unsigned int frag_size; + /* Following the frag_size, frag_size of fragmented + * data will be followed. + */ +}; + +struct cal_data_msg { + /* The length specified in cal_data_msg should be + * hdr.msg_len = sizeof(cal_data_msg) + frag_size + */ + struct smd_msg_hdr hdr; + struct cal_data_params cal_params; +}; + +struct vbatt_level { + u32 curr_volt; + u32 threshold; +}; + +struct vbatt_message { + struct smd_msg_hdr hdr; + struct vbatt_level vbatt; +}; + +struct rpmsg_event { + struct list_head list; + int len; + u8 data[]; +}; + +static struct { + struct platform_device *pdev; + void *pil; + struct resource *mmio_res; + struct resource *tx_irq_res; + struct resource *rx_irq_res; + struct resource *gpios_5wire; + const struct dev_pm_ops *pm_ops; + int triggered; + int smd_channel_ready; + u32 wlan_rx_buff_count; + int is_vsys_adc_channel; + int is_a2xb_split_reg; + struct rpmsg_device *rpdev; + struct rpmsg_endpoint *channel; + /* rpmsg event list lock */ + spinlock_t event_lock; + struct list_head event_list; + struct workqueue_struct *event_wq; + unsigned char wcnss_version[WCNSS_VERSION_LEN]; + unsigned char fw_major; + unsigned char fw_minor; + unsigned int serial_number; + int thermal_mitigation; + enum wcnss_hw_type wcnss_hw_type; + void (*tm_notify)(struct device *dev, int val); + struct wcnss_wlan_config wlan_config; + struct delayed_work wcnss_work; + struct delayed_work vbatt_work; + struct work_struct wcnssctrl_version_work; + struct work_struct wcnss_pm_config_work; + struct work_struct wcnssctrl_nvbin_dnld_work; + struct work_struct wcnssctrl_rx_work; + struct work_struct wcnss_vadc_work; + struct wakeup_source *wcnss_wake_lock; + void __iomem *msm_wcnss_base; + void __iomem *riva_ccu_base; + void __iomem *pronto_a2xb_base; + void __iomem *pronto_ccpu_base; + void __iomem *pronto_saw2_base; + void __iomem *pronto_pll_base; + void __iomem *pronto_mcu_base; + void __iomem *pronto_qfuse; + void __iomem *wlan_tx_status; + void __iomem *wlan_tx_phy_aborts; + void __iomem *wlan_brdg_err_source; + void __iomem *alarms_txctl; + void __iomem *alarms_tactl; + void __iomem *fiq_reg; + int nv_downloaded; + int is_cbc_done; + unsigned char *fw_cal_data; + unsigned char *user_cal_data; + int fw_cal_rcvd; + int fw_cal_exp_frag; + int fw_cal_available; + int user_cal_read; + int user_cal_available; + u32 user_cal_rcvd; + u32 user_cal_exp_size; + int iris_xo_mode_set; + int fw_vbatt_state; + char wlan_nv_mac_addr[WLAN_MAC_ADDR_SIZE]; + int ctrl_device_opened; + /* dev node lock */ + struct mutex dev_lock; + /* dev control lock */ + struct mutex ctrl_lock; + wait_queue_head_t read_wait; + struct adc_tm_param vbat_monitor_params; + struct adc_tm_chip *adc_tm_dev; + struct iio_channel *adc_channel; + u32 vph_pwr; + /* battery monitor lock */ + struct mutex vbat_monitor_mutex; + u16 unsafe_ch_count; + u16 unsafe_ch_list[WCNSS_MAX_CH_NUM]; + void *wcnss_notif_hdle; + struct pinctrl *pinctrl; + struct pinctrl_state *wcnss_5wire_active; + struct pinctrl_state *wcnss_5wire_suspend; + struct pinctrl_state *wcnss_gpio_active; + int gpios[WCNSS_WLAN_MAX_GPIO]; + int use_pinctrl; + u8 is_shutdown; + struct pm_qos_request wcnss_pm_qos_request; + int pc_disabled; + struct delayed_work wcnss_pm_qos_del_req; + /* power manager QOS lock */ + struct mutex pm_qos_mutex; + struct clk *snoc_wcnss; + unsigned int snoc_wcnss_clock_freq; + bool is_dual_band_disabled; + dev_t dev_ctrl, dev_node; + struct class *node_class; + struct cdev ctrl_dev, node_dev; + unsigned long state; + struct wcnss_driver_ops *ops; + struct qcom_smem_state *wake_state; + unsigned int wake_state_bit; + struct bt_profile_state bt_state; + u32 multi_sku; + char nv_name[NVBIN_FILE_SIZE]; + u32 sw_pta; +} *penv = NULL; + +static void *wcnss_ipc_log; + +#define IPC_NUM_LOG_PAGES 12 +#define wcnss_ipc_log_string(_x...) ipc_log_string(wcnss_ipc_log, _x) + +void wcnss_log(enum wcnss_log_type type, const char *_fmt, ...) +{ + struct va_format vaf = { + .fmt = _fmt, + }; + va_list args; + + va_start(args, _fmt); + vaf.va = &args; + switch (type) { + case ERR: + pr_err("wcnss: %pV\n", &vaf); + wcnss_ipc_log_string("wcnss: %pV", &vaf); + break; + case WARN: + pr_warn("wcnss: %pV\n", &vaf); + wcnss_ipc_log_string("wcnss: %pV", &vaf); + break; + case INFO: + pr_info("wcnss: %pV\n", &vaf); + wcnss_ipc_log_string("wcnss: %pV", &vaf); + break; + case DBG: +#if defined(CONFIG_DYNAMIC_DEBUG) + pr_debug("wcnss: %pV\n", &vaf); + wcnss_ipc_log_string("wcnss: %pV", &vaf); +#elif defined(DEBUG) + pr_debug("wcnss: %pV\n", &vaf); + wcnss_ipc_log_string("wcnss: %pV", &vaf); +#else + pr_devel("wcnss: %pV\n", &vaf); + wcnss_ipc_log_string("wcnss: %pV", &vaf); +#endif + break; + } + va_end(args); +} +EXPORT_SYMBOL(wcnss_log); + +static ssize_t wcnss_mac_addr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int index; + int mac_addr[WLAN_MAC_ADDR_SIZE]; + + if (!penv) + return -ENODEV; + + if (strlen(buf) != WCNSS_USER_MAC_ADDR_LENGTH) { + wcnss_log(ERR, "%s: Invalid MAC addr length\n", __func__); + return -EINVAL; + } + + if (sscanf(buf, MAC_ADDRESS_STR, &mac_addr[0], &mac_addr[1], + &mac_addr[2], &mac_addr[3], &mac_addr[4], + &mac_addr[5]) != WLAN_MAC_ADDR_SIZE) { + wcnss_log(ERR, "%s: Failed to Copy MAC\n", __func__); + return -EINVAL; + } + + for (index = 0; index < WLAN_MAC_ADDR_SIZE; index++) { + memcpy(&penv->wlan_nv_mac_addr[index], + (char *)&mac_addr[index], sizeof(char)); + } + + wcnss_log(INFO, "%s: Write MAC Addr:" MAC_ADDRESS_STR "\n", __func__, + penv->wlan_nv_mac_addr[0], penv->wlan_nv_mac_addr[1], + penv->wlan_nv_mac_addr[2], penv->wlan_nv_mac_addr[3], + penv->wlan_nv_mac_addr[4], penv->wlan_nv_mac_addr[5]); + + return count; +} + +static ssize_t wcnss_mac_addr_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + if (!penv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, SHOW_MAC_ADDRESS_STR, + penv->wlan_nv_mac_addr[0], penv->wlan_nv_mac_addr[1], + penv->wlan_nv_mac_addr[2], penv->wlan_nv_mac_addr[3], + penv->wlan_nv_mac_addr[4], penv->wlan_nv_mac_addr[5]); +} + +static DEVICE_ATTR_RW(wcnss_mac_addr); + +static ssize_t thermal_mitigation_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + if (!penv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%u\n", penv->thermal_mitigation); +} + +static ssize_t thermal_mitigation_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int value; + + if (!penv) + return -ENODEV; + + if (kstrtoint(buf, 10, &value) != 1) + return -EINVAL; + penv->thermal_mitigation = value; + if (penv->tm_notify) + penv->tm_notify(dev, value); + return count; +} + +static DEVICE_ATTR_RW(thermal_mitigation); + +static ssize_t wcnss_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!penv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%s", penv->wcnss_version); +} + +static DEVICE_ATTR_RO(wcnss_version); + +static int wcnss_bt_profile_validate_cmd(char *dest_buf, size_t dest_buf_size, + char const *source_buf, + size_t source_buf_size) +{ + char *found; + int profile_idx = 0; + + if (source_buf_size > (dest_buf_size - 1)) { + wcnss_log(ERR, "%s:Command length is larger than %zu bytes\n", + __func__, dest_buf_size); + return -EINVAL; + } + + /* sysfs already provides kernel space buffer so copy from user + * is not needed. Doing this extra copy operation just to ensure + * the local buf is properly null-terminated. + */ + strlcpy(dest_buf, source_buf, dest_buf_size); + + /* default 'echo' cmd takes new line character to here */ + if (dest_buf[source_buf_size - 1] == '\n') + dest_buf[source_buf_size - 1] = '\0'; + + while (profile_idx++ <= 4 && (found = strsep(&dest_buf, " ")) != NULL) { + if (profile_idx == 1 && !strcmp(found, "BT_ENABLED")) { + found = strsep(&dest_buf, " "); + penv->bt_state.bt_enabled = strcmp(found, "0"); + } else if (profile_idx == 2 && !strcmp(found, "BT_ADV")) { + found = strsep(&dest_buf, " "); + penv->bt_state.bt_adv = strcmp(found, "0"); + } else if (profile_idx == 3 && !strcmp(found, "BLE")) { + found = strsep(&dest_buf, " "); + penv->bt_state.bt_ble = strcmp(found, "0"); + } else if (profile_idx == 4 && !strcmp(found, "A2DP")) { + found = strsep(&dest_buf, " "); + penv->bt_state.bt_a2dp = strcmp(found, "0"); + } else if (profile_idx == 5 && !strcmp(found, "SCO")) { + found = strsep(&dest_buf, " "); + penv->bt_state.bt_sco = strcmp(found, "0"); + } else { + return -EINVAL; + } + } + + return 0; +} + +static ssize_t bt_profile_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + char buf_local[WCNSS_MAX_USR_BT_PROFILE_IND_CMD_SIZE + 1]; + int ret; + + ret = wcnss_bt_profile_validate_cmd(buf_local, sizeof(buf_local), + buf, count); + if (ret) + return -EINVAL; + + if (penv->ops) { + ret = penv->ops->bt_profile_state(penv->ops->priv_data, + &penv->bt_state); + if (ret) + return ret; + } + + return count; +} + +static ssize_t bt_profile_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + if (!penv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, + "BT_ENABLED = %d\nBT_ADV = %d\nBLE = %d\nA2DP = %d\nSCO = %d\n", + penv->bt_state.bt_enabled, + penv->bt_state.bt_adv, + penv->bt_state.bt_ble, + penv->bt_state.bt_a2dp, + penv->bt_state.bt_sco); +} + +static DEVICE_ATTR_RW(bt_profile); + +/* wcnss_reset_fiq() is invoked when host drivers fails to + * communicate with WCNSS over SMD; so logging these registers + * helps to know WCNSS failure reason + */ +void wcnss_riva_log_debug_regs(void) +{ + void __iomem *ccu_reg; + u32 reg = 0; + + ccu_reg = penv->riva_ccu_base + CCU_RIVA_INVALID_ADDR_OFFSET; + reg = readl_relaxed(ccu_reg); + wcnss_log(INFO, "%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg); + + ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR0_OFFSET; + reg = readl_relaxed(ccu_reg); + wcnss_log(INFO, "%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg); + + ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR1_OFFSET; + reg = readl_relaxed(ccu_reg); + wcnss_log(INFO, "%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg); + + ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR2_OFFSET; + reg = readl_relaxed(ccu_reg); + wcnss_log(INFO, "%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg); +} +EXPORT_SYMBOL(wcnss_riva_log_debug_regs); + +void wcnss_pronto_is_a2xb_bus_stall(void *tst_addr, u32 fifo_mask, char *type) +{ + u32 iter = 0, reg = 0; + u32 axi_fifo_count = 0, axi_fifo_count_last = 0; + + reg = readl_relaxed(tst_addr); + axi_fifo_count = (reg >> A2XB_FIFO_FILL_OFFSET) & fifo_mask; + while ((++iter < A2XB_FIFO_COUNTER) && axi_fifo_count) { + axi_fifo_count_last = axi_fifo_count; + reg = readl_relaxed(tst_addr); + axi_fifo_count = (reg >> A2XB_FIFO_FILL_OFFSET) & fifo_mask; + if (axi_fifo_count < axi_fifo_count_last) + break; + } + + if (iter == A2XB_FIFO_COUNTER) + wcnss_log(ERR, + "%s data FIFO testbus possibly stalled reg%08x\n", type, reg); + else + wcnss_log(ERR, + "%s data FIFO tstbus not stalled reg%08x\n", type, reg); +} + +int wcnss_get_dual_band_capability_info(struct platform_device *pdev) +{ + u32 reg = 0; + struct resource *res; + + res = platform_get_resource_byname( + pdev, IORESOURCE_MEM, "pronto_qfuse"); + if (!res) + return -EINVAL; + + penv->pronto_qfuse = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(penv->pronto_qfuse)) + return -ENOMEM; + + reg = readl_relaxed(penv->pronto_qfuse + + PRONTO_QFUSE_DUAL_BAND_OFFSET); + if (reg & WCNSS_DUAL_BAND_CAPABILITY_OFFSET) + penv->is_dual_band_disabled = true; + else + penv->is_dual_band_disabled = false; + + return 0; +} + +/* Log pronto debug registers during SSR Timeout CB */ +void wcnss_pronto_log_debug_regs(void) +{ + void __iomem *reg_addr, *tst_addr, *tst_ctrl_addr; + u32 reg = 0, reg2 = 0, reg3 = 0, reg4 = 0; + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_SPARE_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "PRONTO_PMU_SPARE %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_CPU_CBCR_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "PRONTO_PMU_COM_CPU_CBCR %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_AHB_CBCR_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "PRONTO_PMU_COM_AHB_CBCR %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_CFG_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "PRONTO_PMU_CFG %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_CSR_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "PRONTO_PMU_COM_CSR %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_SOFT_RESET_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "PRONTO_PMU_SOFT_RESET %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WDOG_CTL; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "PRONTO_PMU_WDOG_CTL %08x\n", reg); + + reg_addr = penv->pronto_saw2_base + PRONTO_SAW2_SPM_STS_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "PRONTO_SAW2_SPM_STS %08x\n", reg); + + reg_addr = penv->pronto_saw2_base + PRONTO_SAW2_SPM_CTL; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "PRONTO_SAW2_SPM_CTL %08x\n", reg); + + if (penv->is_a2xb_split_reg) { + reg_addr = penv->msm_wcnss_base + + PMU_A2XB_CFG_HSPLIT_RESP_LIMIT_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "PMU_A2XB_CFG_HSPLIT_RESP_LIMIT %08x\n", reg); + } + + reg_addr = penv->pronto_saw2_base + PRONTO_SAW2_SAW2_VERSION; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "PRONTO_SAW2_SAW2_VERSION %08x\n", reg); + reg >>= PRONTO_SAW2_MAJOR_VER_OFFSET; + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_CCPU_BOOT_REMAP_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "PRONTO_PMU_CCPU_BOOT_REMAP %08x\n", reg); + + reg_addr = penv->pronto_pll_base + PRONTO_PLL_STATUS_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "PRONTO_PLL_STATUS %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_CPU_AHB_CMD_RCGR_OFFSET; + reg4 = readl_relaxed(reg_addr); + wcnss_log(ERR, "PMU_CPU_CMD_RCGR %08x\n", reg4); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_GDSCR_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "PRONTO_PMU_COM_GDSCR %08x\n", reg); + reg >>= 31; + + if (!reg) { + wcnss_log(ERR, + "Cannot log, Pronto common SS is power collapsed\n"); + return; + } + reg &= ~(PRONTO_PMU_COM_GDSCR_SW_COLLAPSE + | PRONTO_PMU_COM_GDSCR_HW_CTRL); + writel_relaxed(reg, reg_addr); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_CBCR_OFFSET; + reg = readl_relaxed(reg_addr); + reg |= PRONTO_PMU_CBCR_CLK_EN; + writel_relaxed(reg, reg_addr); + + reg_addr = penv->pronto_a2xb_base + A2XB_CFG_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "A2XB_CFG_OFFSET %08x\n", reg); + + reg_addr = penv->pronto_a2xb_base + A2XB_INT_SRC_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "A2XB_INT_SRC_OFFSET %08x\n", reg); + + reg_addr = penv->pronto_a2xb_base + A2XB_ERR_INFO_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "A2XB_ERR_INFO_OFFSET %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_INVALID_ADDR_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "CCU_CCPU_INVALID_ADDR %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR0_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "CCU_CCPU_LAST_ADDR0 %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR1_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "CCU_CCPU_LAST_ADDR1 %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR2_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "CCU_CCPU_LAST_ADDR2 %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_AOWBR_ERR_ADDR_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "CCU_PRONTO_AOWBR_ERR_ADDR_OFFSET %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_AOWBR_TIMEOUT_REG_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "CCU_PRONTO_AOWBR_TIMEOUT_REG_OFFSET %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_AOWBR_ERR_TIMEOUT_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "CCU_PRONTO_AOWBR_ERR_TIMEOUT_OFFSET %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_A2AB_ERR_ADDR_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "CCU_PRONTO_A2AB_ERR_ADDR_OFFSET %08x\n", reg); + + tst_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_OFFSET; + tst_ctrl_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_CTRL_OFFSET; + + /* read data FIFO */ + reg = 0; + reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_RDFIFO; + writel_relaxed(reg, tst_ctrl_addr); + reg = readl_relaxed(tst_addr); + if (!(reg & A2XB_FIFO_EMPTY)) { + wcnss_pronto_is_a2xb_bus_stall(tst_addr, + A2XB_READ_FIFO_FILL_MASK, + "Read"); + } else { + wcnss_log(ERR, "Read data FIFO testbus %08x\n", reg); + } + /* command FIFO */ + reg = 0; + reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CMDFIFO; + writel_relaxed(reg, tst_ctrl_addr); + reg = readl_relaxed(tst_addr); + if (!(reg & A2XB_FIFO_EMPTY)) { + wcnss_pronto_is_a2xb_bus_stall(tst_addr, + A2XB_CMD_FIFO_FILL_MASK, "Cmd"); + } else { + wcnss_log(ERR, "Command FIFO testbus %08x\n", reg); + } + + /* write data FIFO */ + reg = 0; + reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_WRFIFO; + writel_relaxed(reg, tst_ctrl_addr); + reg = readl_relaxed(tst_addr); + if (!(reg & A2XB_FIFO_EMPTY)) { + wcnss_pronto_is_a2xb_bus_stall(tst_addr, + A2XB_WRITE_FIFO_FILL_MASK, + "Write"); + } else { + wcnss_log(ERR, "Write data FIFO testbus %08x\n", reg); + } + + /* AXIM SEL CFG0 */ + reg = 0; + reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM | + WCNSS_TSTBUS_CTRL_AXIM_CFG0; + writel_relaxed(reg, tst_ctrl_addr); + reg = readl_relaxed(tst_addr); + wcnss_log(ERR, "AXIM SEL CFG0 testbus %08x\n", reg); + + /* AXIM SEL CFG1 */ + reg = 0; + reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM | + WCNSS_TSTBUS_CTRL_AXIM_CFG1; + writel_relaxed(reg, tst_ctrl_addr); + reg = readl_relaxed(tst_addr); + wcnss_log(ERR, "AXIM SEL CFG1 testbus %08x\n", reg); + + /* CTRL SEL CFG0 */ + reg = 0; + reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL | + WCNSS_TSTBUS_CTRL_CTRL_CFG0; + writel_relaxed(reg, tst_ctrl_addr); + reg = readl_relaxed(tst_addr); + wcnss_log(ERR, "CTRL SEL CFG0 testbus %08x\n", reg); + + /* CTRL SEL CFG1 */ + reg = 0; + reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL | + WCNSS_TSTBUS_CTRL_CTRL_CFG1; + writel_relaxed(reg, tst_ctrl_addr); + reg = readl_relaxed(tst_addr); + wcnss_log(ERR, "CTRL SEL CFG1 testbus %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WLAN_BCR_OFFSET; + reg = readl_relaxed(reg_addr); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WLAN_GDSCR_OFFSET; + reg2 = readl_relaxed(reg_addr); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WLAN_AHB_CBCR_OFFSET; + reg3 = readl_relaxed(reg_addr); + wcnss_log(ERR, "PMU_WLAN_AHB_CBCR %08x\n", reg3); + + msleep(50); + + if ((reg & PRONTO_PMU_WLAN_BCR_BLK_ARES) || + (reg2 & PRONTO_PMU_WLAN_GDSCR_SW_COLLAPSE) || + (!(reg4 & PRONTO_PMU_CPU_AHB_CMD_RCGR_ROOT_EN)) || + (reg3 & PRONTO_PMU_WLAN_AHB_CBCR_CLK_OFF) || + (!(reg3 & PRONTO_PMU_WLAN_AHB_CBCR_CLK_EN))) { + wcnss_log(ERR, "Cannot log, wlan domain is power collapsed\n"); + return; + } + + reg = readl_relaxed(penv->wlan_tx_phy_aborts); + wcnss_log(ERR, "WLAN_TX_PHY_ABORTS %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_APB2PHY_STATUS_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "MCU_APB2PHY_STATUS %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_CBR_CCAHB_ERR_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "MCU_CBR_CCAHB_ERR %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_CBR_CAHB_ERR_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "MCU_CBR_CAHB_ERR %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_CBR_CCAHB_TIMEOUT_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "MCU_CBR_CCAHB_TIMEOUT %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_CBR_CAHB_TIMEOUT_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "MCU_CBR_CAHB_TIMEOUT %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_DBR_CDAHB_ERR_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "MCU_DBR_CDAHB_ERR %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_DBR_DAHB_ERR_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "MCU_DBR_DAHB_ERR %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_DBR_CDAHB_TIMEOUT_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "MCU_DBR_CDAHB_TIMEOUT %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_DBR_DAHB_TIMEOUT_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "MCU_DBR_DAHB_TIMEOUT %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_FDBR_CDAHB_ERR_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "MCU_FDBR_CDAHB_ERR %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_FDBR_FDAHB_ERR_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "MCU_FDBR_FDAHB_ERR %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_FDBR_CDAHB_TIMEOUT_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "MCU_FDBR_CDAHB_TIMEOUT %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_FDBR_FDAHB_TIMEOUT_OFFSET; + reg = readl_relaxed(reg_addr); + wcnss_log(ERR, "MCU_FDBR_FDAHB_TIMEOUT %08x\n", reg); + + reg = readl_relaxed(penv->wlan_brdg_err_source); + wcnss_log(ERR, "WLAN_BRDG_ERR_SOURCE %08x\n", reg); + + reg = readl_relaxed(penv->wlan_tx_status); + wcnss_log(ERR, "WLAN_TXP_STATUS %08x\n", reg); + + reg = readl_relaxed(penv->alarms_txctl); + wcnss_log(ERR, "ALARMS_TXCTL %08x\n", reg); + + reg = readl_relaxed(penv->alarms_tactl); + wcnss_log(ERR, "ALARMS_TACTL %08x\n", reg); +} +EXPORT_SYMBOL(wcnss_pronto_log_debug_regs); + +#ifdef CONFIG_WCNSS_REGISTER_DUMP_ON_BITE + +static int wcnss_gpio_set_state(bool is_enable) +{ + struct pinctrl_state *pin_state; + int ret; + int i; + + if (!is_enable) { + for (i = 0; i < WCNSS_WLAN_MAX_GPIO; i++) { + if (gpio_is_valid(penv->gpios[i])) + gpio_free(penv->gpios[i]); + } + + return 0; + } + + pin_state = penv->wcnss_gpio_active; + if (!IS_ERR_OR_NULL(pin_state)) { + ret = pinctrl_select_state(penv->pinctrl, pin_state); + if (ret < 0) { + wcnss_log(ERR, "%s: can not set gpio pins err: %d\n", + __func__, ret); + goto pinctrl_set_err; + } + + } else { + wcnss_log(ERR, "%s: invalid gpio pinstate err: %lu\n", + __func__, PTR_ERR(pin_state)); + goto pinctrl_set_err; + } + + for (i = WCNSS_WLAN_DATA2; i <= WCNSS_WLAN_DATA0; i++) { + ret = gpio_request_one(penv->gpios[i], + GPIOF_DIR_IN, NULL); + if (ret) { + wcnss_log(ERR, "%s: request failed for gpio:%d\n", + __func__, penv->gpios[i]); + i--; + goto gpio_req_err; + } + } + + for (i = WCNSS_WLAN_SET; i <= WCNSS_WLAN_CLK; i++) { + ret = gpio_request_one(penv->gpios[i], + GPIOF_OUT_INIT_LOW, NULL); + if (ret) { + wcnss_log(ERR, "%s: request failed for gpio:%d\n", + __func__, penv->gpios[i]); + i--; + goto gpio_req_err; + } + } + + return 0; + +gpio_req_err: + for (; i >= WCNSS_WLAN_DATA2; --i) + gpio_free(penv->gpios[i]); + +pinctrl_set_err: + return -EINVAL; +} + +static u32 wcnss_rf_read_reg(u32 rf_reg_addr) +{ + int count = 0; + u32 rf_cmd_and_addr = 0; + u32 rf_data_received = 0; + u32 rf_bit = 0; + + if (wcnss_gpio_set_state(true)) + return 0; + + /* Reset the signal if it is already being used. */ + gpio_set_value(penv->gpios[WCNSS_WLAN_SET], 0); + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 0); + + /* We start with cmd_set high penv->gpio_base + WCNSS_WLAN_SET = 1. */ + gpio_set_value(penv->gpios[WCNSS_WLAN_SET], 1); + + gpio_direction_output(penv->gpios[WCNSS_WLAN_DATA0], 1); + gpio_direction_output(penv->gpios[WCNSS_WLAN_DATA1], 1); + gpio_direction_output(penv->gpios[WCNSS_WLAN_DATA2], 1); + + gpio_set_value(penv->gpios[WCNSS_WLAN_DATA0], 0); + gpio_set_value(penv->gpios[WCNSS_WLAN_DATA1], 0); + gpio_set_value(penv->gpios[WCNSS_WLAN_DATA2], 0); + + /* Prepare command and RF register address that need to sent out. */ + rf_cmd_and_addr = (((WLAN_RF_READ_REG_CMD) | + (rf_reg_addr << WLAN_RF_REG_ADDR_START_OFFSET)) & + WLAN_RF_READ_CMD_MASK); + /* Send 15 bit RF register address */ + for (count = 0; count < WLAN_RF_PREPARE_CMD_DATA; count++) { + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 0); + + rf_bit = (rf_cmd_and_addr & 0x1); + gpio_set_value(penv->gpios[WCNSS_WLAN_DATA0], + rf_bit ? 1 : 0); + rf_cmd_and_addr = (rf_cmd_and_addr >> 1); + + rf_bit = (rf_cmd_and_addr & 0x1); + gpio_set_value(penv->gpios[WCNSS_WLAN_DATA1], rf_bit ? 1 : 0); + rf_cmd_and_addr = (rf_cmd_and_addr >> 1); + + rf_bit = (rf_cmd_and_addr & 0x1); + gpio_set_value(penv->gpios[WCNSS_WLAN_DATA2], rf_bit ? 1 : 0); + rf_cmd_and_addr = (rf_cmd_and_addr >> 1); + + /* Send the data out penv->gpio_base + WCNSS_WLAN_CLK = 1 */ + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 1); + } + + /* Pull down the clock signal */ + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 0); + + /* Configure data pins to input IO pins */ + gpio_direction_input(penv->gpios[WCNSS_WLAN_DATA0]); + gpio_direction_input(penv->gpios[WCNSS_WLAN_DATA1]); + gpio_direction_input(penv->gpios[WCNSS_WLAN_DATA2]); + + for (count = 0; count < WLAN_RF_CLK_WAIT_CYCLE; count++) { + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 1); + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 0); + } + + rf_bit = 0; + /* Read 16 bit RF register value */ + for (count = 0; count < WLAN_RF_READ_DATA; count++) { + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 1); + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 0); + + rf_bit = gpio_get_value(penv->gpios[WCNSS_WLAN_DATA0]); + rf_data_received |= (rf_bit << (count * WLAN_RF_DATA_LEN + + WLAN_RF_DATA0_SHIFT)); + + if (count != 5) { + rf_bit = gpio_get_value(penv->gpios[WCNSS_WLAN_DATA1]); + rf_data_received |= (rf_bit << (count * WLAN_RF_DATA_LEN + + WLAN_RF_DATA1_SHIFT)); + + rf_bit = gpio_get_value(penv->gpios[WCNSS_WLAN_DATA2]); + rf_data_received |= (rf_bit << (count * WLAN_RF_DATA_LEN + + WLAN_RF_DATA2_SHIFT)); + } + } + + gpio_set_value(penv->gpios[WCNSS_WLAN_SET], 0); + wcnss_gpio_set_state(false); + wcnss_pinctrl_set_state(true); + + return rf_data_received; +} + +static void wcnss_log_iris_regs(void) +{ + int i; + u32 reg_val; + u32 regs_array[] = { + 0x04, 0x05, 0x11, 0x1e, 0x40, 0x48, + 0x49, 0x4b, 0x00, 0x01, 0x4d}; + + wcnss_log(INFO, "%s: IRIS Registers [address] : value\n", __func__); + + for (i = 0; i < ARRAY_SIZE(regs_array); i++) { + reg_val = wcnss_rf_read_reg(regs_array[i]); + + wcnss_log(INFO, "IRIS Reg Addr: [0x%08x] : Reg val: 0x%08x\n", + regs_array[i], reg_val); + } +} + +int wcnss_get_mux_control(void) +{ + void __iomem *pmu_conf_reg; + u32 reg = 0; + + if (!penv) + return 0; + + pmu_conf_reg = penv->msm_wcnss_base + PRONTO_PMU_OFFSET; + reg = readl_relaxed(pmu_conf_reg); + reg |= WCNSS_PMU_CFG_GC_BUS_MUX_SEL_TOP; + writel_relaxed(reg, pmu_conf_reg); + return 1; +} + +void wcnss_log_debug_regs_on_bite(void) +{ + struct platform_device *pdev = wcnss_get_platform_device(); + struct clk *measure; + struct clk *wcnss_debug_mux; + unsigned long clk_rate; + + if (wcnss_hardware_type() != WCNSS_PRONTO_HW) + return; + + measure = clk_get(&pdev->dev, "measure"); + wcnss_debug_mux = clk_get(&pdev->dev, "wcnss_debug"); + + if (!IS_ERR(measure) && !IS_ERR(wcnss_debug_mux)) { + if (clk_set_parent(measure, wcnss_debug_mux)) { + wcnss_log(ERR, "Setting measure clk parent failed\n"); + return; + } + + if (clk_prepare_enable(measure)) { + wcnss_log(ERR, "measure clk enable failed\n"); + return; + } + + clk_rate = clk_get_rate(measure); + wcnss_log(DBG, "clock frequency is: %luHz\n", clk_rate); + + if (clk_rate) { + wcnss_pronto_log_debug_regs(); + if (wcnss_get_mux_control()) + wcnss_log_iris_regs(); + } else { + wcnss_log(ERR, "clock frequency is zero, cannot"); + wcnss_log(ERR, " access PMU or other registers\n"); + wcnss_log_iris_regs(); + } + + clk_disable_unprepare(measure); + } +} +#endif + +/* interface to reset wcnss by sending the reset interrupt */ +void wcnss_reset_fiq(bool clk_chk_en) +{ + if (wcnss_hardware_type() == WCNSS_PRONTO_HW) { + if (clk_chk_en) { + wcnss_log_debug_regs_on_bite(); + } else { + wcnss_pronto_log_debug_regs(); + if (wcnss_get_mux_control()) + wcnss_log_iris_regs(); + } + if (!wcnss_device_is_shutdown()) { + /* Insert memory barrier before writing fiq register */ + wmb(); + __raw_writel(1 << 16, penv->fiq_reg); + } else { + wcnss_log(INFO, + "%s: Block FIQ during power up sequence\n", __func__); + } + } else { + wcnss_riva_log_debug_regs(); + } +} +EXPORT_SYMBOL(wcnss_reset_fiq); + +static struct attribute *wcnss_attrs[] = { + &dev_attr_thermal_mitigation.attr, + &dev_attr_wcnss_version.attr, + &dev_attr_wcnss_mac_addr.attr, + &dev_attr_bt_profile.attr, + NULL, +}; + +static struct attribute_group wcnss_attr_group = { + .attrs = wcnss_attrs, +}; + +static int wcnss_create_sysfs_link(struct device *dev) +{ + int ret = 0; + + ret = sysfs_create_link(kernel_kobj, &dev->kobj, "wcnss"); + if (ret) { + wcnss_log(ERR, + "Failed to create wcnss link, err = %d\n", + ret); + } + + return ret; +} +static int wcnss_create_sysfs(struct device *dev) +{ + int ret; + + if (!dev) + return -ENODEV; + + ret = devm_device_add_group(dev, &wcnss_attr_group); + if (ret) { + wcnss_log(ERR, + "Failed to create wcnss device group, err = %d\n", + ret); + return ret; + } + + wcnss_create_sysfs_link(dev); + return ret; +} + +static void wcnss_remove_sysfs_link(void) +{ + sysfs_remove_link(kernel_kobj, "wcnss"); +} + +static void wcnss_remove_sysfs(struct device *dev) +{ + if (dev) { + wcnss_remove_sysfs_link(); + devm_device_remove_group(dev, &wcnss_attr_group); + } +} + +static void wcnss_pm_qos_add_request(void) +{ + wcnss_log(INFO, "%s: add request\n", __func__); + pm_qos_add_request(&penv->wcnss_pm_qos_request, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); +} + +static void wcnss_pm_qos_remove_request(void) +{ + wcnss_log(INFO, "%s: remove request\n", __func__); + pm_qos_remove_request(&penv->wcnss_pm_qos_request); +} + +void wcnss_pm_qos_update_request(int val) +{ + wcnss_log(INFO, "%s: update request %d\n", __func__, val); + pm_qos_update_request(&penv->wcnss_pm_qos_request, val); +} + +void wcnss_disable_pc_remove_req(void) +{ + mutex_lock(&penv->pm_qos_mutex); + if (penv->pc_disabled) { + penv->pc_disabled = 0; + wcnss_pm_qos_update_request(WCNSS_ENABLE_PC_LATENCY); + wcnss_pm_qos_remove_request(); + wcnss_allow_suspend(); + } + mutex_unlock(&penv->pm_qos_mutex); +} + +void wcnss_disable_pc_add_req(void) +{ + mutex_lock(&penv->pm_qos_mutex); + if (!penv->pc_disabled) { + wcnss_pm_qos_add_request(); + wcnss_prevent_suspend(); + wcnss_pm_qos_update_request(WCNSS_DISABLE_PC_LATENCY); + penv->pc_disabled = 1; + } + mutex_unlock(&penv->pm_qos_mutex); +} + +/** + * wcnss_open_channel() - open additional SMD channel to WCNSS + * @name: SMD channel name + * @cb: callback to handle incoming data on the channel + */ +struct rpmsg_endpoint *wcnss_open_channel(const char *name, rpmsg_rx_cb_t cb, + void *priv) +{ + struct rpmsg_channel_info chinfo; + + strscpy(chinfo.name, name, sizeof(chinfo.name)); + chinfo.src = RPMSG_ADDR_ANY; + chinfo.dst = RPMSG_ADDR_ANY; + + return rpmsg_create_ept(penv->rpdev, cb, priv, chinfo); +} +EXPORT_SYMBOL(wcnss_open_channel); + +void wcnss_close_channel(struct rpmsg_endpoint *channel) +{ + rpmsg_destroy_ept(channel); +} +EXPORT_SYMBOL(wcnss_close_channel); + +static int wcnss_ctrl_smd_callback(struct rpmsg_device *rpdev, + void *data, + int count, + void *priv, + u32 addr) +{ + struct rpmsg_event *event; + + event = kzalloc(sizeof(*event) + count, GFP_ATOMIC); + if (!event) + return -ENOMEM; + memcpy(event->data, data, count); + event->len = count; + + spin_lock(&penv->event_lock); + list_add_tail(&event->list, &penv->event_list); + spin_unlock(&penv->event_lock); + + queue_work(penv->event_wq, &penv->wcnssctrl_rx_work); + return 0; +} + +static int +wcnss_pinctrl_set_state(bool active) +{ + struct pinctrl_state *pin_state; + int ret; + + wcnss_log(DBG, "%s: Set GPIO state : %d\n", __func__, active); + + pin_state = active ? penv->wcnss_5wire_active + : penv->wcnss_5wire_suspend; + + if (!IS_ERR_OR_NULL(pin_state)) { + ret = pinctrl_select_state(penv->pinctrl, pin_state); + if (ret < 0) { + wcnss_log(ERR, "%s: can not set %s pins\n", __func__, + active ? WCNSS_PINCTRL_STATE_DEFAULT + : WCNSS_PINCTRL_STATE_SLEEP); + return ret; + } + } else { + wcnss_log(ERR, "%s: invalid '%s' pinstate\n", __func__, + active ? WCNSS_PINCTRL_STATE_DEFAULT + : WCNSS_PINCTRL_STATE_SLEEP); + return PTR_ERR(pin_state); + } + + return 0; +} + +static int +wcnss_pinctrl_init(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + int i; + + /* Get pinctrl if target uses pinctrl */ + penv->pinctrl = devm_pinctrl_get(&pdev->dev); + + if (IS_ERR_OR_NULL(penv->pinctrl)) { + wcnss_log(ERR, "%s: failed to get pinctrl\n", __func__); + return PTR_ERR(penv->pinctrl); + } + + penv->wcnss_5wire_active + = pinctrl_lookup_state(penv->pinctrl, + WCNSS_PINCTRL_STATE_DEFAULT); + + if (IS_ERR_OR_NULL(penv->wcnss_5wire_active)) { + wcnss_log(ERR, "%s: can not get default pinstate\n", __func__); + return PTR_ERR(penv->wcnss_5wire_active); + } + + penv->wcnss_5wire_suspend + = pinctrl_lookup_state(penv->pinctrl, + WCNSS_PINCTRL_STATE_SLEEP); + + if (IS_ERR_OR_NULL(penv->wcnss_5wire_suspend)) { + wcnss_log(WARN, "%s: can not get sleep pinstate\n", __func__); + return PTR_ERR(penv->wcnss_5wire_suspend); + } + + penv->wcnss_gpio_active = pinctrl_lookup_state(penv->pinctrl, + WCNSS_PINCTRL_GPIO_STATE_DEFAULT); + if (IS_ERR_OR_NULL(penv->wcnss_gpio_active)) + wcnss_log(WARN, "%s: can not get gpio default pinstate\n", + __func__); + + for (i = 0; i < WCNSS_WLAN_MAX_GPIO; i++) { + penv->gpios[i] = of_get_gpio(node, i); + if (penv->gpios[i] < 0) + wcnss_log(WARN, "%s: Fail to get 5wire gpio: %d\n", + __func__, i); + } + + return 0; +} + +static int +wcnss_pronto_gpios_config(struct platform_device *pdev, bool enable) +{ + int rc = 0; + int i, j; + int WCNSS_WLAN_NUM_GPIOS = 5; + + /* Use Pinctrl to configure 5 wire GPIOs */ + rc = wcnss_pinctrl_init(pdev); + if (rc) { + wcnss_log(ERR, "%s: failed to get pin resources\n", __func__); + penv->pinctrl = NULL; + goto gpio_probe; + } else { + rc = wcnss_pinctrl_set_state(true); + if (rc) + wcnss_log(ERR, "%s: failed to set pin state\n", + __func__); + penv->use_pinctrl = true; + return rc; + } + +gpio_probe: + for (i = 0; i < WCNSS_WLAN_NUM_GPIOS; i++) { + int gpio = of_get_gpio(pdev->dev.of_node, i); + + if (enable) { + rc = gpio_request(gpio, "wcnss_wlan"); + if (rc) { + wcnss_log(ERR, "WCNSS gpio_request %d err %d\n", + gpio, rc); + goto fail; + } + } else { + gpio_free(gpio); + } + } + return rc; + +fail: + for (j = WCNSS_WLAN_NUM_GPIOS - 1; j >= 0; j--) { + int gpio = of_get_gpio(pdev->dev.of_node, i); + + gpio_free(gpio); + } + return rc; +} + +static int +wcnss_gpios_config(struct resource *gpios_5wire, bool enable) +{ + int i, j; + int rc = 0; + + for (i = gpios_5wire->start; i <= gpios_5wire->end; i++) { + if (enable) { + rc = gpio_request(i, gpios_5wire->name); + if (rc) { + wcnss_log(ERR, + "gpio_request %d err %d\n", i, rc); + goto fail; + } + } else { + gpio_free(i); + } + } + + return rc; + +fail: + for (j = i - 1; j >= gpios_5wire->start; j--) + gpio_free(j); + return rc; +} + +static int +wcnss_wlan_ctrl_probe(struct platform_device *pdev) +{ + if (!penv || !penv->triggered) + return -ENODEV; + + penv->smd_channel_ready = 1; + + wcnss_log(INFO, "%s: SMD ctrl channel up\n", __func__); + return 0; +} + +static int +wcnss_wlan_ctrl_remove(struct platform_device *pdev) +{ + if (penv) + penv->smd_channel_ready = 0; + + wcnss_log(INFO, "%s: SMD ctrl channel down\n", __func__); + + return 0; +} + +static struct platform_driver wcnss_wlan_ctrl_driver = { + .driver = { + .name = "WLAN_CTRL", + }, + .probe = wcnss_wlan_ctrl_probe, + .remove = wcnss_wlan_ctrl_remove, +}; + +static int wcnss_ctrl_probe(struct rpmsg_device *rpdev) +{ + if (!penv || !penv->triggered) + return -ENODEV; + + penv->rpdev = rpdev; + penv->channel = rpdev->ept; + wcnss_log(DBG, "WCNSS SMD channel opened:%s", + WCNSS_CTRL_CHANNEL); + schedule_work(&penv->wcnssctrl_version_work); + schedule_work(&penv->wcnss_pm_config_work); + cancel_delayed_work(&penv->wcnss_pm_qos_del_req); + schedule_delayed_work(&penv->wcnss_pm_qos_del_req, 0); + if (penv->wlan_config.is_pronto_vadc && penv->adc_channel) + schedule_work(&penv->wcnss_vadc_work); + + penv->state = WCNSS_SMD_OPEN; + + return 0; +} + +static void wcnss_ctrl_remove(struct rpmsg_device *rpdev) +{ + penv->smd_channel_ready = 0; + of_platform_depopulate(&rpdev->dev); +} + +static int wcnss_rpmsg_resource_init(void) +{ + penv->event_wq = alloc_workqueue("wcnss_rpmsg_event", + WQ_UNBOUND, 1); + if (!penv->event_wq) { + wcnss_log(ERR, "failed to allocate workqueue"); + return -EFAULT; + } + INIT_LIST_HEAD(&penv->event_list); + spin_lock_init(&penv->event_lock); + + penv->state = WCNSS_SMD_CLOSE; + + return 0; +} + +static void wcnss_rpmsg_resource_deinit(void) +{ + destroy_workqueue(penv->event_wq); +} + +static const struct rpmsg_device_id wcnss_ctrl_of_match[] = { + { "WCNSS_CTRL" }, + {} +}; + +static struct rpmsg_driver wcnss_rpmsg_client = { + .probe = wcnss_ctrl_probe, + .remove = wcnss_ctrl_remove, + .callback = wcnss_ctrl_smd_callback, + .id_table = wcnss_ctrl_of_match, + .drv = { + .name = "WCNSS_CTRL", + }, +}; + +struct device *wcnss_wlan_get_device(void) +{ + if (penv && penv->pdev && penv->smd_channel_ready) + return &penv->pdev->dev; + return NULL; +} +EXPORT_SYMBOL(wcnss_wlan_get_device); + +void wcnss_get_monotonic_boottime(struct timespec *ts) +{ + get_monotonic_boottime(ts); +} +EXPORT_SYMBOL(wcnss_get_monotonic_boottime); + +struct platform_device *wcnss_get_platform_device(void) +{ + if (penv && penv->pdev) + return penv->pdev; + return NULL; +} +EXPORT_SYMBOL(wcnss_get_platform_device); + +struct wcnss_wlan_config *wcnss_get_wlan_config(void) +{ + if (penv && penv->pdev) + return &penv->wlan_config; + return NULL; +} +EXPORT_SYMBOL(wcnss_get_wlan_config); + +int wcnss_is_hw_pronto_ver3(void) +{ + if (penv && penv->pdev) { + if (penv->wlan_config.is_pronto_v3) + return penv->wlan_config.is_pronto_v3; + } + return 0; +} +EXPORT_SYMBOL(wcnss_is_hw_pronto_ver3); + +int wcnss_device_ready(void) +{ + if (penv && penv->pdev && penv->nv_downloaded && + !wcnss_device_is_shutdown()) + return 1; + return 0; +} +EXPORT_SYMBOL(wcnss_device_ready); + +bool wcnss_cbc_complete(void) +{ + if (penv && penv->pdev && penv->is_cbc_done && + !wcnss_device_is_shutdown()) + return true; + return false; +} +EXPORT_SYMBOL(wcnss_cbc_complete); + +int wcnss_device_is_shutdown(void) +{ + if (penv && penv->is_shutdown) + return 1; + return 0; +} +EXPORT_SYMBOL(wcnss_device_is_shutdown); + +struct resource *wcnss_wlan_get_memory_map(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && penv->smd_channel_ready) + return penv->mmio_res; + return NULL; +} +EXPORT_SYMBOL(wcnss_wlan_get_memory_map); + +int wcnss_wlan_get_dxe_tx_irq(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && + penv->tx_irq_res && penv->smd_channel_ready) + return penv->tx_irq_res->start; + return WCNSS_WLAN_IRQ_INVALID; +} +EXPORT_SYMBOL(wcnss_wlan_get_dxe_tx_irq); + +int wcnss_wlan_get_dxe_rx_irq(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && + penv->rx_irq_res && penv->smd_channel_ready) + return penv->rx_irq_res->start; + return WCNSS_WLAN_IRQ_INVALID; +} +EXPORT_SYMBOL(wcnss_wlan_get_dxe_rx_irq); + +int wcnss_register_driver(struct wcnss_driver_ops *ops, void *priv) +{ + int ret = 0; + + if (!penv || !penv->pdev) { + ret = -ENODEV; + goto out; + } + + wcnss_log(ERR, "Registering driver, state: 0x%lx\n", penv->state); + + if (penv->ops) { + wcnss_log(ERR, "Driver already registered\n"); + ret = -EEXIST; + goto out; + } + + penv->ops = ops; + penv->ops->priv_data = priv; + + if (penv->state == WCNSS_SMD_OPEN) + ops->driver_state(ops->priv_data, WCNSS_SMD_OPEN); + + if (penv->bt_state.bt_enabled) + ops->bt_profile_state(ops->priv_data, &penv->bt_state); + +out: + return ret; +} +EXPORT_SYMBOL(wcnss_register_driver); + +int wcnss_unregister_driver(struct wcnss_driver_ops *ops) +{ + int ret; + + if (!penv || !penv->pdev) { + ret = -ENODEV; + goto out; + } + + wcnss_log(ERR, "Unregistering driver, state: 0x%lx\n", penv->state); + + if (!penv->ops) { + wcnss_log(ERR, "Driver not registered\n"); + ret = -ENOENT; + goto out; + } + + penv->ops = NULL; + +out: + return ret; +} +EXPORT_SYMBOL(wcnss_unregister_driver); + +void wcnss_update_bt_profile(void) +{ + if (!penv || !penv->pdev || !penv->ops) + return; + + if (penv->bt_state.bt_enabled) + penv->ops->bt_profile_state(penv->ops->priv_data, + &penv->bt_state); +} +EXPORT_SYMBOL(wcnss_update_bt_profile); + +void wcnss_wlan_register_pm_ops(struct device *dev, + const struct dev_pm_ops *pm_ops) +{ + if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) + penv->pm_ops = pm_ops; +} +EXPORT_SYMBOL(wcnss_wlan_register_pm_ops); + +void wcnss_wlan_unregister_pm_ops(struct device *dev, + const struct dev_pm_ops *pm_ops) +{ + if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) { + if (!penv->pm_ops) { + wcnss_log(ERR, "%s: pm_ops is already unregistered.\n", + __func__); + return; + } + + if (pm_ops->suspend != penv->pm_ops->suspend || + pm_ops->resume != penv->pm_ops->resume) + wcnss_log(ERR, + "PM APIs dont match with registered APIs\n"); + penv->pm_ops = NULL; + } +} +EXPORT_SYMBOL(wcnss_wlan_unregister_pm_ops); + +void wcnss_register_thermal_mitigation(struct device *dev, + void (*tm_notify)(struct device *, int)) +{ + if (penv && dev && tm_notify) + penv->tm_notify = tm_notify; +} +EXPORT_SYMBOL(wcnss_register_thermal_mitigation); + +void wcnss_unregister_thermal_mitigation( + void (*tm_notify)(struct device *, int)) +{ + if (penv && tm_notify) { + if (tm_notify != penv->tm_notify) + wcnss_log(ERR, "tm_notify doesn't match registered\n"); + penv->tm_notify = NULL; + } +} +EXPORT_SYMBOL(wcnss_unregister_thermal_mitigation); + +unsigned int wcnss_get_serial_number(void) +{ + if (penv) { + penv->serial_number = socinfo_get_serial_number(); + wcnss_log(INFO, "%s: Device serial number: %u\n", + __func__, penv->serial_number); + return penv->serial_number; + } + + return 0; +} +EXPORT_SYMBOL(wcnss_get_serial_number); + +int wcnss_get_wlan_mac_address(char mac_addr[WLAN_MAC_ADDR_SIZE]) +{ + if (!penv) + return -ENODEV; + + memcpy(mac_addr, penv->wlan_nv_mac_addr, WLAN_MAC_ADDR_SIZE); + wcnss_log(DBG, "%s: Get MAC Addr:" MAC_ADDRESS_STR "\n", __func__, + penv->wlan_nv_mac_addr[0], penv->wlan_nv_mac_addr[1], + penv->wlan_nv_mac_addr[2], penv->wlan_nv_mac_addr[3], + penv->wlan_nv_mac_addr[4], penv->wlan_nv_mac_addr[5]); + return 0; +} +EXPORT_SYMBOL(wcnss_get_wlan_mac_address); + +static int enable_wcnss_suspend_notify; + +static int enable_wcnss_suspend_notify_set(const char *val, + const struct kernel_param *kp) +{ + int ret; + + ret = param_set_int(val, kp); + if (ret) + return ret; + + if (enable_wcnss_suspend_notify) + wcnss_log(DBG, "Suspend notification activated for wcnss\n"); + + return 0; +} +module_param_call(enable_wcnss_suspend_notify, enable_wcnss_suspend_notify_set, + param_get_int, &enable_wcnss_suspend_notify, 0644); + +int wcnss_xo_auto_detect_enabled(void) +{ + return (has_autodetect_xo == 1 ? 1 : 0); +} + +void wcnss_set_iris_xo_mode(int iris_xo_mode_set) +{ + penv->iris_xo_mode_set = iris_xo_mode_set; +} +EXPORT_SYMBOL(wcnss_set_iris_xo_mode); + +int wcnss_wlan_iris_xo_mode(void) +{ + if (penv && penv->pdev && penv->smd_channel_ready) + return penv->iris_xo_mode_set; + return -ENODEV; +} +EXPORT_SYMBOL(wcnss_wlan_iris_xo_mode); + +int wcnss_wlan_dual_band_disabled(void) +{ + if (penv && penv->pdev) + return penv->is_dual_band_disabled; + + return -EINVAL; +} +EXPORT_SYMBOL(wcnss_wlan_dual_band_disabled); + +void wcnss_suspend_notify(void) +{ + void __iomem *pmu_spare_reg; + u32 reg = 0; + unsigned long flags; + + if (!enable_wcnss_suspend_notify) + return; + + if (wcnss_hardware_type() == WCNSS_PRONTO_HW) + return; + + /* For Riva */ + pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET; + spin_lock_irqsave(®_spinlock, flags); + reg = readl_relaxed(pmu_spare_reg); + reg |= RIVA_SUSPEND_BIT; + writel_relaxed(reg, pmu_spare_reg); + spin_unlock_irqrestore(®_spinlock, flags); +} +EXPORT_SYMBOL(wcnss_suspend_notify); + +void wcnss_resume_notify(void) +{ + void __iomem *pmu_spare_reg; + u32 reg = 0; + unsigned long flags; + + if (!enable_wcnss_suspend_notify) + return; + + if (wcnss_hardware_type() == WCNSS_PRONTO_HW) + return; + + /* For Riva */ + pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET; + + spin_lock_irqsave(®_spinlock, flags); + reg = readl_relaxed(pmu_spare_reg); + reg &= ~RIVA_SUSPEND_BIT; + writel_relaxed(reg, pmu_spare_reg); + spin_unlock_irqrestore(®_spinlock, flags); +} +EXPORT_SYMBOL(wcnss_resume_notify); + +static int wcnss_wlan_suspend(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && + penv->smd_channel_ready && + penv->pm_ops && penv->pm_ops->suspend) + return penv->pm_ops->suspend(dev); + return 0; +} + +static int wcnss_wlan_suspend_noirq(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && + penv->smd_channel_ready && + penv->pm_ops && penv->pm_ops->suspend_noirq) + return penv->pm_ops->suspend_noirq(dev); + return 0; +} + +static int wcnss_wlan_resume(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && + penv->smd_channel_ready && + penv->pm_ops && penv->pm_ops->resume) + return penv->pm_ops->resume(dev); + return 0; +} + +static int wcnss_wlan_resume_noirq(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && + penv->smd_channel_ready && + penv->pm_ops && penv->pm_ops->resume_noirq) + return penv->pm_ops->resume_noirq(dev); + return 0; +} + +void wcnss_prevent_suspend(void) +{ + if (penv) + __pm_stay_awake(penv->wcnss_wake_lock); +} +EXPORT_SYMBOL(wcnss_prevent_suspend); + +void wcnss_allow_suspend(void) +{ + if (penv) + __pm_relax(penv->wcnss_wake_lock); +} +EXPORT_SYMBOL(wcnss_allow_suspend); + +int wcnss_hardware_type(void) +{ + if (penv) + return penv->wcnss_hw_type; + else + return -ENODEV; +} +EXPORT_SYMBOL(wcnss_hardware_type); + +int fw_cal_data_available(void) +{ + if (penv) + return penv->fw_cal_available; + else + return -ENODEV; +} + +u32 wcnss_get_wlan_rx_buff_count(void) +{ + if (penv) + return penv->wlan_rx_buff_count; + else + return WCNSS_DEF_WLAN_RX_BUFF_COUNT; +} +EXPORT_SYMBOL(wcnss_get_wlan_rx_buff_count); + +int wcnss_set_wlan_unsafe_channel(u16 *unsafe_ch_list, u16 ch_count) +{ + if (penv && unsafe_ch_list && + (ch_count <= WCNSS_MAX_CH_NUM)) { + memcpy((char *)penv->unsafe_ch_list, + (char *)unsafe_ch_list, ch_count * sizeof(u16)); + penv->unsafe_ch_count = ch_count; + return 0; + } else { + return -ENODEV; + } +} +EXPORT_SYMBOL(wcnss_set_wlan_unsafe_channel); + +int wcnss_get_wlan_unsafe_channel(u16 *unsafe_ch_list, u16 buffer_size, + u16 *ch_count) +{ + if (penv) { + if (buffer_size < penv->unsafe_ch_count * sizeof(u16)) + return -ENODEV; + memcpy((char *)unsafe_ch_list, + (char *)penv->unsafe_ch_list, + penv->unsafe_ch_count * sizeof(u16)); + *ch_count = penv->unsafe_ch_count; + return 0; + } else { + return -ENODEV; + } +} +EXPORT_SYMBOL(wcnss_get_wlan_unsafe_channel); + +int wcnss_smd_tx(struct rpmsg_endpoint *channel, void *data, int len) +{ + int ret = 0; + + ret = rpmsg_send(channel, data, len); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(wcnss_smd_tx); + +static int wcnss_get_battery_volt(u32 *result_uv) +{ + int ret = 0; + + if (!penv->channel) { + wcnss_log(ERR, "Channel doesn't exists\n"); + ret = -EINVAL; + goto out; + } + + ret = iio_read_channel_processed(penv->adc_channel, result_uv); + if (ret < 0) + wcnss_log(ERR, "Error reading channel, ret = %d\n", ret); + + wcnss_log(INFO, "Battery uvolts meas=0x%llx\n", + *result_uv); + +out: + return ret; +} + +static void wcnss_notify_vbat(enum adc_tm_state state, void *ctx) +{ + int rc = 0; + u32 vph_pwr = 0; + u32 vph_pwr_prev; + + mutex_lock(&penv->vbat_monitor_mutex); + cancel_delayed_work_sync(&penv->vbatt_work); + + vph_pwr_prev = penv->vph_pwr; + wcnss_get_battery_volt(&vph_pwr); + if (state == ADC_TM_LOW_STATE) { + wcnss_log(DBG, "low voltage notification triggered\n"); + penv->vbat_monitor_params.state_request = + ADC_TM_HIGH_THR_ENABLE; + penv->vbat_monitor_params.high_thr = WCNSS_VBATT_THRESHOLD + + WCNSS_VBATT_GUARD; + penv->vbat_monitor_params.low_thr = 0; + } else if (state == ADC_TM_HIGH_STATE) { + penv->vbat_monitor_params.state_request = + ADC_TM_LOW_THR_ENABLE; + penv->vbat_monitor_params.low_thr = WCNSS_VBATT_THRESHOLD - + WCNSS_VBATT_GUARD; + penv->vbat_monitor_params.high_thr = 0; + wcnss_log(DBG, "high voltage notification triggered\n"); + } else { + wcnss_log(DBG, "unknown voltage notification state: %d\n", + state); + mutex_unlock(&penv->vbat_monitor_mutex); + return; + } + wcnss_log(DBG, "set low thr to %d and high to %d\n", + penv->vbat_monitor_params.low_thr, + penv->vbat_monitor_params.high_thr); + + penv->vph_pwr = vph_pwr; + rc = adc_tm5_channel_measure(penv->adc_tm_dev, + &penv->vbat_monitor_params); + + if (rc) + wcnss_log(ERR, "%s: tm setup failed: %d\n", __func__, rc); + else + schedule_delayed_work(&penv->vbatt_work, + msecs_to_jiffies(2000)); + + mutex_unlock(&penv->vbat_monitor_mutex); +} + +static int wcnss_setup_vbat_monitoring(void) +{ + int rc = -1; + + if (!penv->adc_tm_dev) { + wcnss_log(ERR, "not setting up vbatt\n"); + return rc; + } + penv->vbat_monitor_params.low_thr = WCNSS_VBATT_THRESHOLD; + penv->vbat_monitor_params.high_thr = WCNSS_VBATT_THRESHOLD; + penv->vbat_monitor_params.state_request = ADC_TM_HIGH_LOW_THR_ENABLE; + + if (penv->is_vsys_adc_channel) + penv->vbat_monitor_params.channel = VADC_VSYS; + else + penv->vbat_monitor_params.channel = VADC_VBAT_SNS; + + penv->vbat_monitor_params.btm_ctx = (void *)penv; + penv->vbat_monitor_params.threshold_notification = &wcnss_notify_vbat; + wcnss_log(DBG, "set low thr to %d and high to %d\n", + penv->vbat_monitor_params.low_thr, + penv->vbat_monitor_params.high_thr); + + rc = adc_tm5_channel_measure(penv->adc_tm_dev, + &penv->vbat_monitor_params); + if (rc) + wcnss_log(ERR, "%s: tm setup failed: %d\n", __func__, rc); + + return rc; +} + +static void wcnss_send_vbatt_indication(struct work_struct *work) +{ + struct vbatt_message vbatt_msg; + int ret = 0; + + vbatt_msg.hdr.msg_type = WCNSS_VBATT_LEVEL_IND; + vbatt_msg.hdr.msg_len = sizeof(struct vbatt_message); + vbatt_msg.vbatt.threshold = WCNSS_VBATT_THRESHOLD; + + mutex_lock(&penv->vbat_monitor_mutex); + vbatt_msg.vbatt.curr_volt = penv->wlan_config.vbatt; + mutex_unlock(&penv->vbat_monitor_mutex); + wcnss_log(DBG, "send curr_volt: %d to FW\n", + vbatt_msg.vbatt.curr_volt); + + ret = wcnss_smd_tx(penv->channel, &vbatt_msg, vbatt_msg.hdr.msg_len); + if (ret < 0) + wcnss_log(ERR, "smd tx failed\n"); +} + +static void wcnss_update_vbatt(struct work_struct *work) +{ + struct vbatt_message vbatt_msg; + int ret = 0; + + vbatt_msg.hdr.msg_type = WCNSS_VBATT_LEVEL_IND; + vbatt_msg.hdr.msg_len = sizeof(struct vbatt_message); + vbatt_msg.vbatt.threshold = WCNSS_VBATT_THRESHOLD; + + mutex_lock(&penv->vbat_monitor_mutex); + if (penv->vbat_monitor_params.low_thr && + (penv->fw_vbatt_state == WCNSS_VBATT_LOW || + penv->fw_vbatt_state == WCNSS_CONFIG_UNSPECIFIED)) { + vbatt_msg.vbatt.curr_volt = WCNSS_VBATT_HIGH; + penv->fw_vbatt_state = WCNSS_VBATT_HIGH; + wcnss_log(DBG, "send HIGH BATT to FW\n"); + } else if (!penv->vbat_monitor_params.low_thr && + (penv->fw_vbatt_state == WCNSS_VBATT_HIGH || + penv->fw_vbatt_state == WCNSS_CONFIG_UNSPECIFIED)){ + vbatt_msg.vbatt.curr_volt = WCNSS_VBATT_LOW; + penv->fw_vbatt_state = WCNSS_VBATT_LOW; + wcnss_log(DBG, "send LOW BATT to FW\n"); + } else { + mutex_unlock(&penv->vbat_monitor_mutex); + return; + } + mutex_unlock(&penv->vbat_monitor_mutex); + ret = wcnss_smd_tx(penv->channel, &vbatt_msg, vbatt_msg.hdr.msg_len); + if (ret < 0) + wcnss_log(ERR, "smd tx failed\n"); +} + +static void wcnss_send_cal_rsp(unsigned char fw_status) +{ + struct smd_msg_hdr *rsphdr; + unsigned char *msg = NULL; + int rc; + + msg = kmalloc((sizeof(*rsphdr) + 1), GFP_KERNEL); + if (!msg) + return; + + rsphdr = (struct smd_msg_hdr *)msg; + rsphdr->msg_type = WCNSS_CALDATA_UPLD_RSP; + rsphdr->msg_len = sizeof(struct smd_msg_hdr) + 1; + memcpy(msg + sizeof(struct smd_msg_hdr), &fw_status, 1); + + rc = wcnss_smd_tx(penv->channel, msg, rsphdr->msg_len); + if (rc < 0) + wcnss_log(ERR, "smd tx failed\n"); + + kfree(msg); +} + +/* Collect calibrated data from WCNSS */ +void extract_cal_data(void *buf, int len) +{ + struct cal_data_params *calhdr; + unsigned char fw_status = WCNSS_RESP_FAIL; + + if (len < sizeof(struct cal_data_params)) { + wcnss_log(ERR, "incomplete cal header length\n"); + return; + } + + mutex_lock(&penv->dev_lock); + calhdr = buf + sizeof(struct smd_msg_hdr); + + if (penv->fw_cal_exp_frag != calhdr->frag_number) { + wcnss_log(ERR, "Invalid frgament"); + goto unlock_exit; + } + + if (calhdr->frag_size > WCNSS_MAX_FRAME_SIZE) { + wcnss_log(ERR, "Invalid fragment size"); + goto unlock_exit; + } + + if (penv->fw_cal_available) { + /* ignore cal upload from SSR */ + penv->fw_cal_exp_frag++; + if (calhdr->msg_flags & LAST_FRAGMENT) { + penv->fw_cal_exp_frag = 0; + goto unlock_exit; + } + mutex_unlock(&penv->dev_lock); + return; + } + + if (calhdr->frag_number == 0) { + if (calhdr->total_size > MAX_CALIBRATED_DATA_SIZE) { + wcnss_log(ERR, "Invalid cal data size %d", + calhdr->total_size); + goto unlock_exit; + } + kfree(penv->fw_cal_data); + penv->fw_cal_rcvd = 0; + penv->fw_cal_data = kmalloc(calhdr->total_size, + GFP_KERNEL); + if (!penv->fw_cal_data) { + wcnss_log(ERR, "cal data alloc failed"); + goto unlock_exit; + } + } + + if (penv->fw_cal_rcvd + calhdr->frag_size > + MAX_CALIBRATED_DATA_SIZE) { + wcnss_log(ERR, "calibrated data size is more than expected %d", + penv->fw_cal_rcvd + calhdr->frag_size); + penv->fw_cal_exp_frag = 0; + penv->fw_cal_rcvd = 0; + goto unlock_exit; + } + /* To Do: cross check if fragmet is getting copied corectly */ + memcpy(penv->fw_cal_data + penv->fw_cal_rcvd, + (unsigned char *)(buf + sizeof(struct cal_data_msg)), + calhdr->frag_size); + + penv->fw_cal_exp_frag++; + penv->fw_cal_rcvd += calhdr->frag_size; + + if (calhdr->msg_flags & LAST_FRAGMENT) { + penv->fw_cal_exp_frag = 0; + penv->fw_cal_available = true; + wcnss_log(INFO, "cal data collection completed\n"); + } + mutex_unlock(&penv->dev_lock); + wake_up(&penv->read_wait); + + if (penv->fw_cal_available) { + fw_status = WCNSS_RESP_SUCCESS; + wcnss_send_cal_rsp(fw_status); + } + return; + +unlock_exit: + mutex_unlock(&penv->dev_lock); + wcnss_send_cal_rsp(fw_status); +} + +static void wcnss_process_smd_msg(void *buf, int len) +{ + int rc = 0; + unsigned char *build; + struct smd_msg_hdr *phdr; + struct smd_msg_hdr smd_msg; + struct wcnss_version *pversion; + const struct wcnss_download_nv_resp *nvresp; + const struct wcnss_download_cal_data_resp *caldata_resp; + int hw_type; + + phdr = (struct smd_msg_hdr *)buf; + + switch (phdr->msg_type) { + case WCNSS_VERSION_RSP: + pversion = (struct wcnss_version *)buf; + penv->fw_major = pversion->major; + penv->fw_minor = pversion->minor; + snprintf(penv->wcnss_version, WCNSS_VERSION_LEN, + "%02x%02x%02x%02x", pversion->major, pversion->minor, + pversion->version, pversion->revision); + wcnss_log(INFO, "version %s\n", penv->wcnss_version); + /* schedule work to download nvbin to ccpu */ + hw_type = wcnss_hardware_type(); + switch (hw_type) { + case WCNSS_RIVA_HW: + /* supported only if riva major >= 1 and minor >= 4 */ + if ((pversion->major >= 1) && (pversion->minor >= 4)) { + wcnss_log(INFO, + "schedule download work for riva\n"); + schedule_work(&penv->wcnssctrl_nvbin_dnld_work); + } + break; + + case WCNSS_PRONTO_HW: + smd_msg.msg_type = WCNSS_BUILD_VER_REQ; + smd_msg.msg_len = sizeof(smd_msg); + rc = wcnss_smd_tx(penv->channel, &smd_msg, + smd_msg.msg_len); + if (rc < 0) + wcnss_log(ERR, "smd tx failed: %s\n", __func__); + + /* supported only if pronto major >= 1 and minor >= 4 */ + if ((pversion->major >= 1) && (pversion->minor >= 4)) { + wcnss_log(INFO, + "schedule dnld work for pronto\n"); + schedule_work(&penv->wcnssctrl_nvbin_dnld_work); + } + break; + + default: + wcnss_log(INFO, + "unknown hw type (%d) will not schedule dnld work\n", + hw_type); + break; + } + break; + + case WCNSS_BUILD_VER_RSP: + build = kmalloc(WCNSS_MAX_BUILD_VER_LEN + 1, GFP_ATOMIC); + if (!build) { + wcnss_log(ERR, + "%s: mem alloc failed for build ver resp\n", + __func__); + return; + } + + if (len > sizeof(struct smd_msg_hdr) + + WCNSS_MAX_BUILD_VER_LEN) { + wcnss_log(ERR, + "invalid build version:%d\n", len); + kfree(build); + return; + } + memcpy(build, buf + sizeof(struct smd_msg_hdr), + len - sizeof(struct smd_msg_hdr)); + build[len - sizeof(struct smd_msg_hdr)] = 0; + wcnss_log(INFO, "build version %s\n", build); + kfree(build); + break; + + case WCNSS_NVBIN_DNLD_RSP: + penv->nv_downloaded = true; + nvresp = buf; + wcnss_log(DBG, "received WCNSS_NVBIN_DNLD_RSP from ccpu %u\n", + nvresp->status); + if (nvresp->status != WAIT_FOR_CBC_IND) + penv->is_cbc_done = 1; + + penv->smd_channel_ready = 1; + + if (penv->ops) + penv->ops->driver_state(penv->ops->priv_data, + WCNSS_SMD_OPEN); + + wcnss_setup_vbat_monitoring(); + break; + + case WCNSS_CALDATA_DNLD_RSP: + penv->nv_downloaded = true; + caldata_resp = buf; + wcnss_log(DBG, "received WCNSS_CALDATA_DNLD_RSP from ccpu %u\n", + caldata_resp->status); + break; + case WCNSS_CBC_COMPLETE_IND: + penv->is_cbc_done = 1; + wcnss_log(DBG, "received WCNSS_CBC_COMPLETE_IND from FW\n"); + break; + + case WCNSS_CALDATA_UPLD_REQ: + extract_cal_data(buf, len); + break; + + default: + wcnss_log(ERR, "invalid message type %d\n", phdr->msg_type); + } +} + +static void wcnssctrl_rx_handler(struct work_struct *work) +{ + struct rpmsg_event *event; + unsigned long flags; + + spin_lock_irqsave(&penv->event_lock, flags); + while (!list_empty(&penv->event_list)) { + event = list_first_entry(&penv->event_list, + struct rpmsg_event, list); + list_del(&event->list); + spin_unlock_irqrestore(&penv->event_lock, flags); + wcnss_process_smd_msg(event->data, event->len); + kfree(event); + spin_lock_irqsave(&penv->event_lock, flags); + } + spin_unlock_irqrestore(&penv->event_lock, flags); +} + +static void wcnss_send_version_req(struct work_struct *worker) +{ + struct smd_msg_hdr smd_msg; + int ret = 0; + + smd_msg.msg_type = WCNSS_VERSION_REQ; + smd_msg.msg_len = sizeof(smd_msg); + ret = wcnss_smd_tx(penv->channel, &smd_msg, smd_msg.msg_len); + if (ret < 0) + wcnss_log(ERR, "smd tx failed\n"); +} + +static void wcnss_send_pm_config(struct work_struct *worker) +{ + struct smd_msg_hdr *hdr; + unsigned char *msg = NULL; + int rc, prop_len; + u32 *payload; + + if (!of_find_property(penv->pdev->dev.of_node, + "qcom,wcnss-pm", &prop_len)) + return; + + msg = kmalloc((sizeof(struct smd_msg_hdr) + prop_len), GFP_KERNEL); + if (!msg) + return; + + payload = (u32 *)(msg + sizeof(struct smd_msg_hdr)); + + prop_len /= sizeof(int); + + rc = of_property_read_u32_array(penv->pdev->dev.of_node, + "qcom,wcnss-pm", payload, prop_len); + if (rc < 0) { + wcnss_log(ERR, "property read failed\n"); + kfree(msg); + return; + } + + wcnss_log(DBG, "%s:size=%d: <%d, %d, %d, %d, %d %d>\n", __func__, + prop_len, *payload, *(payload + 1), *(payload + 2), + *(payload + 3), *(payload + 4), *(payload + 5)); + + hdr = (struct smd_msg_hdr *)msg; + hdr->msg_type = WCNSS_PM_CONFIG_REQ; + hdr->msg_len = sizeof(struct smd_msg_hdr) + (prop_len * sizeof(int)); + + rc = wcnss_smd_tx(penv->channel, msg, hdr->msg_len); + if (rc < 0) + wcnss_log(ERR, "smd tx failed\n"); + + kfree(msg); +} + +static void wcnss_pm_qos_enable_pc(struct work_struct *worker) +{ + wcnss_disable_pc_remove_req(); +} + +static DECLARE_RWSEM(wcnss_pm_sem); + +int wcnss_get_nv_name(char *nv_name) +{ + char variant[8] = {0}; + int ret; + + if (penv->multi_sku) { + ret = wcnss_get_iris_name(variant); + if (ret) { + wcnss_log(ERR, "Invalid IRIS name using default one\n"); + scnprintf(nv_name, NVBIN_FILE_SIZE, "%s.bin", + NVBIN_FILE); + return 0; + } + + scnprintf(nv_name, NVBIN_FILE_SIZE, "%s_%s.bin", + NVBIN_FILE, variant); + } else { + scnprintf(nv_name, NVBIN_FILE_SIZE, "%s.bin", + NVBIN_FILE); + } + + return 0; +} +EXPORT_SYMBOL(wcnss_get_nv_name); + +int wcnss_is_sw_pta_enabled(void) +{ + return penv->sw_pta; +} +EXPORT_SYMBOL(wcnss_is_sw_pta_enabled); + +static void wcnss_nvbin_dnld(void) +{ + int ret = 0; + struct nvbin_dnld_req_msg *dnld_req_msg; + unsigned short total_fragments = 0; + unsigned short count = 0; + unsigned short retry_count = 0; + unsigned short cur_frag_size = 0; + unsigned char *outbuffer = NULL; + const void *nv_blob_addr = NULL; + unsigned int nv_blob_size = 0; + const struct firmware *nv = NULL; + struct device *dev = &penv->pdev->dev; + + down_read(&wcnss_pm_sem); + + wcnss_get_nv_name(penv->nv_name); + + ret = request_firmware(&nv, penv->nv_name, dev); + + if (ret || !nv || !nv->data || !nv->size) { + wcnss_log(ERR, + "%s: request_firmware failed for %s (ret = %d)\n", + __func__, penv->nv_name, ret); + goto out; + } + + /* First 4 bytes in nv blob is validity bitmap. + * We cannot validate nv, so skip those 4 bytes. + */ + nv_blob_addr = nv->data + 4; + nv_blob_size = nv->size - 4; + + total_fragments = TOTALFRAGMENTS(nv_blob_size); + + wcnss_log(INFO, "NV bin size: %d, total_fragments: %d\n", + nv_blob_size, total_fragments); + + /* get buffer for nv bin dnld req message */ + outbuffer = kmalloc((sizeof(struct nvbin_dnld_req_msg) + + NV_FRAGMENT_SIZE), GFP_KERNEL); + if (!outbuffer) + goto err_free_nv; + + dnld_req_msg = (struct nvbin_dnld_req_msg *)outbuffer; + + dnld_req_msg->hdr.msg_type = WCNSS_NVBIN_DNLD_REQ; + dnld_req_msg->dnld_req_params.msg_flags = 0; + + for (count = 0; count < total_fragments; count++) { + dnld_req_msg->dnld_req_params.frag_number = count; + + if (count == (total_fragments - 1)) { + /* last fragment, take care of boundary condition */ + cur_frag_size = nv_blob_size % NV_FRAGMENT_SIZE; + if (!cur_frag_size) + cur_frag_size = NV_FRAGMENT_SIZE; + + dnld_req_msg->dnld_req_params.msg_flags |= + LAST_FRAGMENT; + dnld_req_msg->dnld_req_params.msg_flags |= + CAN_RECEIVE_CALDATA; + } else { + cur_frag_size = NV_FRAGMENT_SIZE; + dnld_req_msg->dnld_req_params.msg_flags &= + ~LAST_FRAGMENT; + } + + dnld_req_msg->dnld_req_params.nvbin_buffer_size = + cur_frag_size; + + dnld_req_msg->hdr.msg_len = + sizeof(struct nvbin_dnld_req_msg) + cur_frag_size; + + /* copy NV fragment */ + memcpy((outbuffer + sizeof(struct nvbin_dnld_req_msg)), + (nv_blob_addr + count * NV_FRAGMENT_SIZE), + cur_frag_size); + + ret = wcnss_smd_tx(penv->channel, outbuffer, + dnld_req_msg->hdr.msg_len); + + retry_count = 0; + while ((ret == -ENOSPC) && (retry_count <= 3)) { + wcnss_log(DBG, "%s: smd tx failed, ENOSPC\n", + __func__); + wcnss_log(DBG, "fragment %d, len: %d,", count, + dnld_req_msg->hdr.msg_len); + wcnss_log(DBG, "TotFragments: %d, retry_count: %d\n", + total_fragments, retry_count); + + /* wait and try again */ + msleep(20); + retry_count++; + ret = wcnss_smd_tx(penv->channel, outbuffer, + dnld_req_msg->hdr.msg_len); + } + + if (ret < 0) { + wcnss_log(ERR, "%s: smd tx failed\n", __func__); + wcnss_log(ERR, "fragment %d, len: %d,", count, + dnld_req_msg->hdr.msg_len); + wcnss_log(ERR, "TotFragments: %d, retry_count: %d\n", + total_fragments, retry_count); + goto err_dnld; + } + } + +err_dnld: + /* free buffer */ + kfree(outbuffer); + +err_free_nv: + /* release firmware */ + release_firmware(nv); + +out: + up_read(&wcnss_pm_sem); +} + +static void wcnss_caldata_dnld(const void *cal_data, + unsigned int cal_data_size, bool msg_to_follow) +{ + int ret = 0; + struct cal_data_msg *cal_msg; + unsigned short total_fragments = 0; + unsigned short count = 0; + unsigned short retry_count = 0; + unsigned short cur_frag_size = 0; + unsigned char *outbuffer = NULL; + + total_fragments = TOTALFRAGMENTS(cal_data_size); + + outbuffer = kmalloc((sizeof(struct cal_data_msg) + + NV_FRAGMENT_SIZE), GFP_KERNEL); + if (!outbuffer) + return; + + cal_msg = (struct cal_data_msg *)outbuffer; + + cal_msg->hdr.msg_type = WCNSS_CALDATA_DNLD_REQ; + cal_msg->cal_params.msg_flags = 0; + + for (count = 0; count < total_fragments; count++) { + cal_msg->cal_params.frag_number = count; + + if (count == (total_fragments - 1)) { + cur_frag_size = cal_data_size % NV_FRAGMENT_SIZE; + if (!cur_frag_size) + cur_frag_size = NV_FRAGMENT_SIZE; + + cal_msg->cal_params.msg_flags + |= LAST_FRAGMENT; + if (msg_to_follow) + cal_msg->cal_params.msg_flags |= + MESSAGE_TO_FOLLOW; + } else { + cur_frag_size = NV_FRAGMENT_SIZE; + cal_msg->cal_params.msg_flags &= + ~LAST_FRAGMENT; + } + + cal_msg->cal_params.total_size = cal_data_size; + cal_msg->cal_params.frag_size = + cur_frag_size; + + cal_msg->hdr.msg_len = + sizeof(struct cal_data_msg) + cur_frag_size; + + memcpy((outbuffer + sizeof(struct cal_data_msg)), + (cal_data + count * NV_FRAGMENT_SIZE), + cur_frag_size); + + ret = wcnss_smd_tx(penv->channel, outbuffer, + cal_msg->hdr.msg_len); + + + retry_count = 0; + while ((ret == -ENOSPC) && (retry_count <= 3)) { + wcnss_log(DBG, "%s: smd tx failed, ENOSPC\n", + __func__); + wcnss_log(DBG, "fragment: %d, len: %d", + count, cal_msg->hdr.msg_len); + wcnss_log(DBG, " TotFragments: %d, retry_count: %d\n", + total_fragments, retry_count); + + /* wait and try again */ + msleep(20); + retry_count++; + ret = wcnss_smd_tx(penv->channel, outbuffer, + cal_msg->hdr.msg_len); + } + + if (ret < 0) { + wcnss_log(ERR, "%s: smd tx failed: fragment %d, len:%d", + count, cal_msg->hdr.msg_len, __func__); + wcnss_log(ERR, " TotFragments: %d, retry_count: %d\n", + total_fragments, retry_count); + goto err_dnld; + } + } + +err_dnld: + /* free buffer */ + kfree(outbuffer); +} + +static void wcnss_nvbin_dnld_main(struct work_struct *worker) +{ + int retry = 0; + + if (!FW_CALDATA_CAPABLE()) + goto nv_download; + + if (!penv->fw_cal_available && IS_CAL_DATA_PRESENT + != has_calibrated_data && !penv->user_cal_available) { + while (!penv->user_cal_available && retry++ < 5) + msleep(500); + } + if (penv->fw_cal_available) { + wcnss_log(INFO, "cal download, using fw cal"); + wcnss_caldata_dnld(penv->fw_cal_data, penv->fw_cal_rcvd, true); + + } else if (penv->user_cal_available) { + wcnss_log(INFO, "cal download, using user cal"); + wcnss_caldata_dnld(penv->user_cal_data, + penv->user_cal_rcvd, true); + } + +nv_download: + wcnss_log(INFO, "NV download"); + wcnss_nvbin_dnld(); +} + +static int wcnss_pm_notify(struct notifier_block *b, + unsigned long event, void *p) +{ + switch (event) { + case PM_SUSPEND_PREPARE: + down_write(&wcnss_pm_sem); + if (penv->wake_state && penv->ops) + qcom_smem_state_update_bits(penv->wake_state, + AWAKE_BIT, 0); + break; + + case PM_POST_SUSPEND: + up_write(&wcnss_pm_sem); + if (penv->wake_state && penv->ops) + qcom_smem_state_update_bits(penv->wake_state, AWAKE_BIT, + AWAKE_BIT); + break; + } + + return NOTIFY_DONE; +} + +static struct notifier_block wcnss_pm_notifier = { + .notifier_call = wcnss_pm_notify, +}; + +static int wcnss_ctrl_open(struct inode *inode, struct file *file) +{ + int rc = 0; + + if (!penv || penv->ctrl_device_opened) + return -EFAULT; + + penv->ctrl_device_opened = 1; + + return rc; +} + +void process_usr_ctrl_cmd(u8 *buf, size_t len) +{ + u16 cmd = buf[0] << 8 | buf[1]; + + switch (cmd) { + case WCNSS_USR_HAS_CAL_DATA: + if (buf[2] > 1) + wcnss_log(ERR, "%s: Invalid data for cal %d\n", + __func__, buf[2]); + has_calibrated_data = buf[2]; + break; + case WCNSS_USR_WLAN_MAC_ADDR: + memcpy(&penv->wlan_nv_mac_addr, &buf[2], + sizeof(penv->wlan_nv_mac_addr)); + wcnss_log(DBG, "%s: MAC Addr:" MAC_ADDRESS_STR "\n", __func__, + penv->wlan_nv_mac_addr[0], penv->wlan_nv_mac_addr[1], + penv->wlan_nv_mac_addr[2], penv->wlan_nv_mac_addr[3], + penv->wlan_nv_mac_addr[4], penv->wlan_nv_mac_addr[5]); + break; + default: + wcnss_log(ERR, "%s: Invalid command %d\n", __func__, cmd); + break; + } +} + +static ssize_t wcnss_ctrl_write(struct file *fp, const char __user + *user_buffer, size_t count, loff_t *position) +{ + int rc = 0; + u8 buf[WCNSS_MAX_CMD_LEN]; + + if (!penv || !penv->ctrl_device_opened || WCNSS_MAX_CMD_LEN < count || + count < WCNSS_MIN_CMD_LEN) + return -EFAULT; + + mutex_lock(&penv->ctrl_lock); + rc = copy_from_user(buf, user_buffer, count); + if (rc == 0) + process_usr_ctrl_cmd(buf, count); + + mutex_unlock(&penv->ctrl_lock); + + return rc; +} + +static const struct file_operations wcnss_ctrl_fops = { + .owner = THIS_MODULE, + .open = wcnss_ctrl_open, + .write = wcnss_ctrl_write, +}; + +static int +wcnss_trigger_config(struct platform_device *pdev) +{ + int ret = 0; + int rc; + struct qcom_wcnss_opts *pdata; + struct resource *res; + int is_pronto_vadc; + int is_pronto_v3; + int pil_retry = 0; + struct device_node *node = (&pdev->dev)->of_node; + int has_pronto_hw = of_property_read_bool(node, "qcom,has-pronto-hw"); + + is_pronto_vadc = of_property_read_bool(node, "qcom,is-pronto-vadc"); + is_pronto_v3 = of_property_read_bool(node, "qcom,is-pronto-v3"); + + penv->is_vsys_adc_channel = + of_property_read_bool(node, "qcom,has-vsys-adc-channel"); + penv->is_a2xb_split_reg = + of_property_read_bool(node, "qcom,has-a2xb-split-reg"); + + if (of_property_read_u32(node, "qcom,wlan-rx-buff-count", + &penv->wlan_rx_buff_count)) { + penv->wlan_rx_buff_count = WCNSS_DEF_WLAN_RX_BUFF_COUNT; + } + + rc = wcnss_parse_voltage_regulator(&penv->wlan_config, &pdev->dev); + if (rc) { + wcnss_log(ERR, "Failed to parse voltage regulators\n"); + goto fail; + } + + /* make sure we are only triggered once */ + if (penv->triggered) + return 0; + penv->triggered = 1; + + /* initialize the WCNSS device configuration */ + pdata = pdev->dev.platform_data; + if (has_48mhz_xo == WCNSS_CONFIG_UNSPECIFIED) { + if (has_pronto_hw) { + has_48mhz_xo = + of_property_read_bool(node, "qcom,has-48mhz-xo"); + } else { + has_48mhz_xo = pdata->has_48mhz_xo; + } + } + penv->wcnss_hw_type = (has_pronto_hw) ? WCNSS_PRONTO_HW : WCNSS_RIVA_HW; + penv->wlan_config.use_48mhz_xo = has_48mhz_xo; + penv->wlan_config.is_pronto_vadc = is_pronto_vadc; + penv->wlan_config.is_pronto_v3 = is_pronto_v3; + + if (has_autodetect_xo == WCNSS_CONFIG_UNSPECIFIED && has_pronto_hw) { + has_autodetect_xo = + of_property_read_bool(node, "qcom,has-autodetect-xo"); + } + + penv->thermal_mitigation = 0; + strlcpy(penv->wcnss_version, "INVALID", WCNSS_VERSION_LEN); + + /* Configure 5 wire GPIOs */ + if (!has_pronto_hw) { + penv->gpios_5wire = platform_get_resource_byname(pdev, + IORESOURCE_IO, "wcnss_gpios_5wire"); + + /* allocate 5-wire GPIO resources */ + if (!penv->gpios_5wire) { + wcnss_log(ERR, "insufficient IO resources\n"); + ret = -ENOENT; + goto fail_gpio_res; + } + ret = wcnss_gpios_config(penv->gpios_5wire, true); + } else { + ret = wcnss_pronto_gpios_config(pdev, true); + } + + if (ret) { + wcnss_log(ERR, "gpios config failed.\n"); + goto fail_gpio_res; + } + + /* allocate resources */ + penv->mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "wcnss_mmio"); + penv->tx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "wcnss_wlantx_irq"); + penv->rx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "wcnss_wlanrx_irq"); + + if (!(penv->mmio_res && penv->tx_irq_res && penv->rx_irq_res)) { + wcnss_log(ERR, "insufficient resources\n"); + ret = -ENOENT; + goto fail_res; + } + INIT_WORK(&penv->wcnssctrl_rx_work, wcnssctrl_rx_handler); + INIT_WORK(&penv->wcnssctrl_version_work, wcnss_send_version_req); + INIT_WORK(&penv->wcnss_pm_config_work, wcnss_send_pm_config); + INIT_WORK(&penv->wcnssctrl_nvbin_dnld_work, wcnss_nvbin_dnld_main); + INIT_DELAYED_WORK(&penv->wcnss_pm_qos_del_req, wcnss_pm_qos_enable_pc); + + penv->wcnss_wake_lock = wakeup_source_register(&pdev->dev, "wcnss"); + /* Add pm_qos request to disable power collapse for DDR */ + wcnss_disable_pc_add_req(); + + if (wcnss_hardware_type() == WCNSS_PRONTO_HW) { + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "pronto_phy_base"); + if (!res) { + ret = -EIO; + wcnss_log(ERR, "%s: resource pronto_phy_base failed\n", + __func__); + goto fail_ioremap; + } + penv->msm_wcnss_base = + devm_ioremap_resource(&pdev->dev, res); + } else { + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "riva_phy_base"); + if (!res) { + ret = -EIO; + wcnss_log(ERR, "%s: resource riva_phy_base failed\n", + __func__); + goto fail_ioremap; + } + penv->msm_wcnss_base = + devm_ioremap_resource(&pdev->dev, res); + } + + if (!penv->msm_wcnss_base) { + ret = -ENOMEM; + wcnss_log(ERR, "%s: ioremap wcnss physical failed\n", __func__); + goto fail_ioremap; + } + + penv->wlan_config.msm_wcnss_base = penv->msm_wcnss_base; + if (wcnss_hardware_type() == WCNSS_RIVA_HW) { + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "riva_ccu_base"); + if (!res) { + ret = -EIO; + wcnss_log(ERR, "%s: resource riva_ccu_base failed\n", + __func__); + goto fail_ioremap; + } + penv->riva_ccu_base = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->riva_ccu_base) { + ret = -ENOMEM; + wcnss_log(ERR, "%s: ioremap riva ccu physical failed\n", + __func__); + goto fail_ioremap; + } + } else { + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "pronto_a2xb_base"); + if (!res) { + ret = -EIO; + wcnss_log(ERR, "%s: resource pronto_a2xb_base failed\n", + __func__); + goto fail_ioremap; + } + penv->pronto_a2xb_base = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->pronto_a2xb_base) { + ret = -ENOMEM; + wcnss_log(ERR, + "%s: ioremap pronto a2xb physical failed\n", __func__); + goto fail_ioremap; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "pronto_ccpu_base"); + if (!res) { + ret = -EIO; + wcnss_log(ERR, "%s: resource pronto_ccpu_base failed\n", + __func__); + goto fail_ioremap; + } + penv->pronto_ccpu_base = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->pronto_ccpu_base) { + ret = -ENOMEM; + wcnss_log(ERR, + "%s: ioremap pronto ccpu physical failed\n", __func__); + goto fail_ioremap; + } + + /* for reset FIQ */ + res = platform_get_resource_byname(penv->pdev, + IORESOURCE_MEM, "wcnss_fiq"); + if (!res) { + wcnss_log(ERR, "insufficient irq mem resources\n"); + ret = -ENOENT; + goto fail_ioremap; + } + penv->fiq_reg = ioremap_nocache(res->start, resource_size(res)); + if (!penv->fiq_reg) { + wcnss_log(ERR, "%s", __func__, + "ioremap_nocache() failed fiq_reg addr:%pr\n", + &res->start); + ret = -ENOMEM; + goto fail_ioremap; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "pronto_saw2_base"); + if (!res) { + ret = -EIO; + wcnss_log(ERR, "%s: resource pronto_saw2_base failed\n", + __func__); + goto fail_ioremap2; + } + penv->pronto_saw2_base = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->pronto_saw2_base) { + wcnss_log(ERR, + "%s: ioremap wcnss physical(saw2) failed\n", __func__); + ret = -ENOMEM; + goto fail_ioremap2; + } + + penv->pronto_pll_base = + penv->msm_wcnss_base + PRONTO_PLL_MODE_OFFSET; + if (!penv->pronto_pll_base) { + wcnss_log(ERR, + "%s: ioremap wcnss physical(pll) failed\n", __func__); + ret = -ENOMEM; + goto fail_ioremap2; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "wlan_tx_phy_aborts"); + if (!res) { + ret = -EIO; + wcnss_log(ERR, + "%s: resource wlan_tx_phy_aborts failed\n", __func__); + goto fail_ioremap2; + } + penv->wlan_tx_phy_aborts = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->wlan_tx_phy_aborts) { + ret = -ENOMEM; + wcnss_log(ERR, "%s: ioremap wlan TX PHY failed\n", + __func__); + goto fail_ioremap2; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "wlan_brdg_err_source"); + if (!res) { + ret = -EIO; + wcnss_log(ERR, + "%s: get wlan_brdg_err_source res failed\n", __func__); + goto fail_ioremap2; + } + penv->wlan_brdg_err_source = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->wlan_brdg_err_source) { + ret = -ENOMEM; + wcnss_log(ERR, "%s: ioremap wlan BRDG ERR failed\n", + __func__); + goto fail_ioremap2; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "wlan_tx_status"); + if (!res) { + ret = -EIO; + wcnss_log(ERR, "%s: resource wlan_tx_status failed\n", + __func__); + goto fail_ioremap2; + } + penv->wlan_tx_status = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->wlan_tx_status) { + ret = -ENOMEM; + wcnss_log(ERR, "%s: ioremap wlan TX STATUS failed\n", + __func__); + goto fail_ioremap2; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "alarms_txctl"); + if (!res) { + ret = -EIO; + wcnss_log(ERR, + "%s: resource alarms_txctl failed\n", __func__); + goto fail_ioremap2; + } + penv->alarms_txctl = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->alarms_txctl) { + ret = -ENOMEM; + wcnss_log(ERR, + "%s: ioremap alarms TXCTL failed\n", __func__); + goto fail_ioremap2; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "alarms_tactl"); + if (!res) { + ret = -EIO; + wcnss_log(ERR, + "%s: resource alarms_tactl failed\n", __func__); + goto fail_ioremap2; + } + penv->alarms_tactl = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->alarms_tactl) { + ret = -ENOMEM; + wcnss_log(ERR, + "%s: ioremap alarms TACTL failed\n", __func__); + goto fail_ioremap2; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "pronto_mcu_base"); + if (!res) { + ret = -EIO; + wcnss_log(ERR, + "%s: resource pronto_mcu_base failed\n", __func__); + goto fail_ioremap2; + } + penv->pronto_mcu_base = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->pronto_mcu_base) { + ret = -ENOMEM; + wcnss_log(ERR, + "%s: ioremap pronto mcu physical failed\n", __func__); + goto fail_ioremap2; + } + + if (of_property_read_bool(node, + "qcom,is-dual-band-disabled")) { + ret = wcnss_get_dual_band_capability_info(pdev); + if (ret) { + wcnss_log(ERR, + "%s: failed to get dual band info\n", __func__); + goto fail_ioremap2; + } + } + } + + penv->adc_tm_dev = get_adc_tm(&penv->pdev->dev, "wcnss"); + if (IS_ERR(penv->adc_tm_dev)) { + wcnss_log(ERR, "%s: adc get failed\n", __func__); + penv->adc_tm_dev = NULL; + } else { + INIT_DELAYED_WORK(&penv->vbatt_work, wcnss_update_vbatt); + penv->fw_vbatt_state = WCNSS_CONFIG_UNSPECIFIED; + } + + penv->snoc_wcnss = devm_clk_get(&penv->pdev->dev, "snoc_wcnss"); + if (IS_ERR(penv->snoc_wcnss)) { + wcnss_log(ERR, "%s: couldn't get snoc_wcnss\n", __func__); + penv->snoc_wcnss = NULL; + } else { + if (of_property_read_u32(pdev->dev.of_node, + "qcom,snoc-wcnss-clock-freq", + &penv->snoc_wcnss_clock_freq)) { + wcnss_log(DBG, + "%s: snoc clock frequency is not defined\n", __func__); + penv->snoc_wcnss = NULL; + } + } + + if (penv->wlan_config.is_pronto_vadc) { + penv->adc_channel = iio_channel_get(&penv->pdev->dev, "wcnss"); + + if (IS_ERR(penv->adc_channel)) { + wcnss_log(DBG, "%s: vadc get failed\n", __func__); + penv->adc_channel = NULL; + } else { + rc = wcnss_get_battery_volt(&penv->wlan_config.vbatt); + INIT_WORK(&penv->wcnss_vadc_work, + wcnss_send_vbatt_indication); + + if (rc < 0) + wcnss_log(ERR, + "battery voltage get failed:err=%d\n", rc); + } + } + + device_property_read_u32(&pdev->dev, "qcom,multi_sku", + &penv->multi_sku); + + do { + /* trigger initialization of the WCNSS */ + penv->pil = subsystem_get(WCNSS_PIL_DEVICE); + if (IS_ERR(penv->pil)) { + wcnss_log(ERR, "Peripheral Loader failed on WCNSS.\n"); + ret = PTR_ERR(penv->pil); + wcnss_disable_pc_add_req(); + wcnss_pronto_log_debug_regs(); + } + } while (pil_retry++ < WCNSS_MAX_PIL_RETRY && IS_ERR(penv->pil)); + + if (IS_ERR(penv->pil)) { + wcnss_reset_fiq(false); + if (penv->wcnss_notif_hdle) + subsys_notif_unregister_notifier(penv->wcnss_notif_hdle, + &wnb); + penv->pil = NULL; + goto fail_ioremap2; + } + /* Remove pm_qos request */ + wcnss_disable_pc_remove_req(); + + return 0; + +fail_ioremap2: + if (penv->fiq_reg) + iounmap(penv->fiq_reg); +fail_ioremap: + wakeup_source_unregister(penv->wcnss_wake_lock); +fail_res: + if (!has_pronto_hw) + wcnss_gpios_config(penv->gpios_5wire, false); + else if (penv->use_pinctrl) + wcnss_pinctrl_set_state(false); + else + wcnss_pronto_gpios_config(pdev, false); +fail_gpio_res: + wcnss_disable_pc_remove_req(); +fail: + if (penv->wcnss_notif_hdle) + subsys_notif_unregister_notifier(penv->wcnss_notif_hdle, &wnb); + penv = NULL; + return ret; +} + +/* Driver requires to directly vote the snoc clocks + * To enable and disable snoc clock, it call + * wcnss_snoc_vote function + */ +void wcnss_snoc_vote(bool clk_chk_en) +{ + int rc; + + if (!penv->snoc_wcnss) { + wcnss_log(ERR, "%s: couldn't get clk snoc_wcnss\n", __func__); + return; + } + + if (clk_chk_en) { + rc = clk_set_rate(penv->snoc_wcnss, + penv->snoc_wcnss_clock_freq); + if (rc) { + wcnss_log(ERR, + "%s: snoc_wcnss_clk-clk_set_rate failed=%d\n", + __func__, rc); + return; + } + + if (clk_prepare_enable(penv->snoc_wcnss)) { + wcnss_log(ERR, "%s: snoc_wcnss clk enable failed\n", + __func__); + return; + } + } else { + clk_disable_unprepare(penv->snoc_wcnss); + } +} +EXPORT_SYMBOL(wcnss_snoc_vote); + +/* wlan prop driver cannot invoke cancel_work_sync + * function directly, so to invoke this function it + * call wcnss_flush_work function + */ +void wcnss_flush_work(struct work_struct *work) +{ + struct work_struct *cnss_work = work; + + if (cnss_work) + cancel_work_sync(cnss_work); +} +EXPORT_SYMBOL(wcnss_flush_work); + +/* wlan prop driver cannot invoke show_stack + * function directly, so to invoke this function it + * call wcnss_dump_stack function + */ +void wcnss_dump_stack(struct task_struct *task) +{ + show_stack(task, NULL); +} +EXPORT_SYMBOL(wcnss_dump_stack); + +/* wlan prop driver cannot invoke cancel_delayed_work_sync + * function directly, so to invoke this function it call + * wcnss_flush_delayed_work function + */ +void wcnss_flush_delayed_work(struct delayed_work *dwork) +{ + struct delayed_work *cnss_dwork = dwork; + + if (cnss_dwork) + cancel_delayed_work_sync(cnss_dwork); +} +EXPORT_SYMBOL(wcnss_flush_delayed_work); + +/* wlan prop driver cannot invoke INIT_WORK function + * directly, so to invoke this function call + * wcnss_init_work function. + */ +void wcnss_init_work(struct work_struct *work, void *callbackptr) +{ + if (work && callbackptr) + INIT_WORK(work, callbackptr); +} +EXPORT_SYMBOL(wcnss_init_work); + +/* wlan prop driver cannot invoke INIT_DELAYED_WORK + * function directly, so to invoke this function + * call wcnss_init_delayed_work function. + */ +void wcnss_init_delayed_work(struct delayed_work *dwork, void *callbackptr) +{ + if (dwork && callbackptr) + INIT_DELAYED_WORK(dwork, callbackptr); +} +EXPORT_SYMBOL(wcnss_init_delayed_work); + +static int wcnss_node_open(struct inode *inode, struct file *file) +{ + struct platform_device *pdev; + int rc = 0; + + if (!penv) + return -EFAULT; + + if (!penv->triggered) { + wcnss_log(INFO, DEVICE " triggered by userspace\n"); + pdev = penv->pdev; + rc = wcnss_trigger_config(pdev); + if (rc) + return -EFAULT; + } + + return rc; +} + +static ssize_t wcnss_wlan_read(struct file *fp, char __user + *buffer, size_t count, loff_t *position) +{ + int rc = 0; + + if (!penv) + return -EFAULT; + + rc = wait_event_interruptible(penv->read_wait, penv->fw_cal_rcvd + > penv->user_cal_read || penv->fw_cal_available); + + if (rc < 0) + return rc; + + mutex_lock(&penv->dev_lock); + + if (penv->fw_cal_available && penv->fw_cal_rcvd + == penv->user_cal_read) { + rc = 0; + goto exit; + } + + if (count > penv->fw_cal_rcvd - penv->user_cal_read) + count = penv->fw_cal_rcvd - penv->user_cal_read; + + rc = copy_to_user(buffer, penv->fw_cal_data + + penv->user_cal_read, count); + if (rc == 0) { + penv->user_cal_read += count; + rc = count; + } + +exit: + mutex_unlock(&penv->dev_lock); + return rc; +} + +/* first (valid) write to this device should be 4 bytes cal file size */ +static ssize_t wcnss_wlan_write(struct file *fp, const char __user + *user_buffer, size_t count, loff_t *position) +{ + int rc = 0; + char *cal_data = NULL; + + if (!penv || penv->user_cal_available) + return -EFAULT; + + if (!penv->user_cal_rcvd && count >= 4 && !penv->user_cal_exp_size) { + mutex_lock(&penv->dev_lock); + rc = copy_from_user((void *)&penv->user_cal_exp_size, + user_buffer, 4); + if (!penv->user_cal_exp_size || + penv->user_cal_exp_size > MAX_CALIBRATED_DATA_SIZE) { + wcnss_log(ERR, DEVICE " invalid size to write %d\n", + penv->user_cal_exp_size); + penv->user_cal_exp_size = 0; + mutex_unlock(&penv->dev_lock); + return -EFAULT; + } + mutex_unlock(&penv->dev_lock); + return count; + } else if (!penv->user_cal_rcvd && count < 4) { + return -EFAULT; + } + + mutex_lock(&penv->dev_lock); + if ((UINT32_MAX - count < penv->user_cal_rcvd) || + (penv->user_cal_exp_size < count + penv->user_cal_rcvd)) { + wcnss_log(ERR, DEVICE " invalid size to write %zu\n", + count + penv->user_cal_rcvd); + mutex_unlock(&penv->dev_lock); + return -ENOMEM; + } + + cal_data = kmalloc(count, GFP_KERNEL); + if (!cal_data) { + mutex_unlock(&penv->dev_lock); + return -ENOMEM; + } + + rc = copy_from_user(cal_data, user_buffer, count); + if (!rc) { + memcpy(penv->user_cal_data + penv->user_cal_rcvd, + cal_data, count); + penv->user_cal_rcvd += count; + rc += count; + } + + kfree(cal_data); + if (penv->user_cal_rcvd == penv->user_cal_exp_size) { + penv->user_cal_available = true; + wcnss_log(INFO, "user cal written"); + } + mutex_unlock(&penv->dev_lock); + + return rc; +} + +static int wcnss_node_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static int wcnss_notif_cb(struct notifier_block *this, unsigned long code, + void *ss_handle) +{ + struct platform_device *pdev = wcnss_get_platform_device(); + struct wcnss_wlan_config *pwlanconfig = wcnss_get_wlan_config(); + struct notif_data *data = (struct notif_data *)ss_handle; + int ret, xo_mode; + void *priv; + + if (!(code >= SUBSYS_NOTIF_MIN_INDEX) && + (code <= SUBSYS_NOTIF_MAX_INDEX)) { + wcnss_log(DBG, "%s: Invaild subsystem notification code: %lu\n", + __func__, code); + return NOTIFY_DONE; + } + + wcnss_log(INFO, "%s: notification event: %lu : %s\n", + __func__, code, wcnss_subsys_notif_type[code]); + + if (code == SUBSYS_PROXY_VOTE) { + if (pdev && pwlanconfig) { + ret = wcnss_wlan_power(&pdev->dev, pwlanconfig, + WCNSS_WLAN_SWITCH_ON, &xo_mode); + wcnss_set_iris_xo_mode(xo_mode); + if (ret) + wcnss_log(ERR, "wcnss_wlan_power failed\n"); + } + } else if (code == SUBSYS_PROXY_UNVOTE) { + if (pdev && pwlanconfig) { + /* Temporary workaround as some pronto images have an + * issue of sending an interrupt that it is capable of + * voting for it's resources too early. + */ + msleep(20); + wcnss_wlan_power(&pdev->dev, pwlanconfig, + WCNSS_WLAN_SWITCH_OFF, NULL); + } + } else if ((code == SUBSYS_BEFORE_SHUTDOWN && data && data->crashed) || + code == SUBSYS_SOC_RESET) { + wcnss_disable_pc_add_req(); + schedule_delayed_work(&penv->wcnss_pm_qos_del_req, + msecs_to_jiffies(WCNSS_PM_QOS_TIMEOUT)); + penv->is_shutdown = 1; + + if (penv->ops) { + priv = penv->ops->priv_data; + penv->ops->driver_state(priv, WCNSS_SMD_CLOSE); + } + + wcnss_log_debug_regs_on_bite(); + } else if (code == SUBSYS_POWERUP_FAILURE) { + if (pdev && pwlanconfig) + wcnss_wlan_power(&pdev->dev, pwlanconfig, + WCNSS_WLAN_SWITCH_OFF, NULL); + wcnss_pronto_log_debug_regs(); + wcnss_disable_pc_remove_req(); + } else if (code == SUBSYS_BEFORE_SHUTDOWN) { + wcnss_disable_pc_add_req(); + schedule_delayed_work(&penv->wcnss_pm_qos_del_req, + msecs_to_jiffies(WCNSS_PM_QOS_TIMEOUT)); + penv->is_shutdown = 1; + } else if (code == SUBSYS_AFTER_POWERUP) { + penv->is_shutdown = 0; + } + + return NOTIFY_DONE; +} + +static const struct file_operations wcnss_node_fops = { + .owner = THIS_MODULE, + .open = wcnss_node_open, + .read = wcnss_wlan_read, + .write = wcnss_wlan_write, + .release = wcnss_node_release, +}; + +static int wcnss_cdev_register(struct platform_device *pdev) +{ + int ret = 0; + + ret = alloc_chrdev_region(&penv->dev_ctrl, 0, 1, CTRL_DEVICE); + if (ret < 0) { + wcnss_log(ERR, "CTRL Device Registration failed\n"); + goto alloc_region_ctrl; + } + ret = alloc_chrdev_region(&penv->dev_node, 0, 1, DEVICE); + if (ret < 0) { + wcnss_log(ERR, "NODE Device Registration failed\n"); + goto alloc_region_node; + } + + penv->node_class = class_create(THIS_MODULE, "wcnss"); + if (!penv->node_class) { + wcnss_log(ERR, "NODE Device Class Creation failed\n"); + goto class_create_node; + } + + if (device_create(penv->node_class, NULL, penv->dev_ctrl, NULL, + CTRL_DEVICE) == NULL) { + wcnss_log(ERR, "CTRL Device Creation failed\n"); + goto device_create_ctrl; + } + + if (device_create(penv->node_class, NULL, penv->dev_node, NULL, + DEVICE) == NULL) { + wcnss_log(ERR, "NODE Device Creation failed\n"); + goto device_create_node; + } + + cdev_init(&penv->ctrl_dev, &wcnss_ctrl_fops); + cdev_init(&penv->node_dev, &wcnss_node_fops); + + if (cdev_add(&penv->ctrl_dev, penv->dev_ctrl, 1) == -1) { + wcnss_log(ERR, "CTRL Device addition failed\n"); + goto cdev_add_ctrl; + } + if (cdev_add(&penv->node_dev, penv->dev_node, 1) == -1) { + wcnss_log(ERR, "NODE Device addition failed\n"); + goto cdev_add_node; + } + + return 0; + +cdev_add_node: + cdev_del(&penv->ctrl_dev); +cdev_add_ctrl: + device_destroy(penv->node_class, penv->dev_node); +device_create_node: + device_destroy(penv->node_class, penv->dev_ctrl); +device_create_ctrl: + class_destroy(penv->node_class); +class_create_node: + unregister_chrdev_region(penv->dev_node, 1); +alloc_region_node: + unregister_chrdev_region(penv->dev_ctrl, 1); +alloc_region_ctrl: + return -ENOMEM; +} + +static void wcnss_cdev_unregister(struct platform_device *pdev) +{ + wcnss_log(ERR, "Unregistering cdev devices\n"); + cdev_del(&penv->ctrl_dev); + cdev_del(&penv->node_dev); + device_destroy(penv->node_class, penv->dev_ctrl); + device_destroy(penv->node_class, penv->dev_node); + class_destroy(penv->node_class); + unregister_chrdev_region(penv->dev_ctrl, 1); + unregister_chrdev_region(penv->dev_node, 1); +} + +static int +wcnss_wlan_probe(struct platform_device *pdev) +{ + int ret = 0; + + /* verify we haven't been called more than once */ + if (penv) { + wcnss_log(ERR, "cannot handle multiple devices.\n"); + return -ENODEV; + } + + /* create an environment to track the device */ + penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL); + if (!penv) + return -ENOMEM; + + penv->pdev = pdev; + + penv->user_cal_data = + devm_kzalloc(&pdev->dev, MAX_CALIBRATED_DATA_SIZE, GFP_KERNEL); + if (!penv->user_cal_data) { + wcnss_log(ERR, "Failed to alloc memory for cal data.\n"); + return -ENOMEM; + } + + device_property_read_u32(&pdev->dev, "qcom,sw_pta", + &penv->sw_pta); + + /* register sysfs entries */ + ret = wcnss_create_sysfs(&pdev->dev); + if (ret) { + penv = NULL; + return -ENOENT; + } + + /* register wcnss event notification */ + penv->wcnss_notif_hdle = subsys_notif_register_notifier("wcnss", &wnb); + if (IS_ERR(penv->wcnss_notif_hdle)) { + wcnss_log(ERR, "register event notification failed!\n"); + return PTR_ERR(penv->wcnss_notif_hdle); + } + + mutex_init(&penv->dev_lock); + mutex_init(&penv->ctrl_lock); + mutex_init(&penv->vbat_monitor_mutex); + mutex_init(&penv->pm_qos_mutex); + init_waitqueue_head(&penv->read_wait); + + penv->user_cal_rcvd = 0; + penv->user_cal_read = 0; + penv->user_cal_exp_size = 0; + penv->user_cal_available = false; + + /* Since we were built into the kernel we'll be called as part + * of kernel initialization. We don't know if userspace + * applications are available to service PIL at this time + * (they probably are not), so we simply create a device node + * here. When userspace is available it should touch the + * device so that we know that WCNSS configuration can take + * place + */ + wcnss_log(INFO, DEVICE " probed in built-in mode\n"); + + penv->wake_state = qcom_smem_state_get(&pdev->dev, + "wake-state", + &penv->wake_state_bit); + if (IS_ERR(penv->wake_state)) { + penv->wake_state = NULL; + wcnss_log(WARN, "%s: qcom_smem_wake_state_get failed", + __func__); + } + + return wcnss_cdev_register(pdev); +} + +static int +wcnss_wlan_remove(struct platform_device *pdev) +{ + if (penv->wcnss_notif_hdle) + subsys_notif_unregister_notifier(penv->wcnss_notif_hdle, &wnb); + wcnss_cdev_unregister(pdev); + + if (penv->wake_state) + qcom_smem_state_put(penv->wake_state); + + wcnss_remove_sysfs(&pdev->dev); + penv = NULL; + return 0; +} + +static const struct dev_pm_ops wcnss_wlan_pm_ops = { + .suspend = wcnss_wlan_suspend, + .resume = wcnss_wlan_resume, + .suspend_noirq = wcnss_wlan_suspend_noirq, + .resume_noirq = wcnss_wlan_resume_noirq, +}; + +#ifdef CONFIG_WCNSS_CORE_PRONTO +static const struct of_device_id msm_wcnss_pronto_match[] = { + {.compatible = "qcom,wcnss_wlan"}, + {} +}; +#endif + +static struct platform_driver wcnss_wlan_driver = { + .driver = { + .name = DEVICE, + .pm = &wcnss_wlan_pm_ops, +#ifdef CONFIG_WCNSS_CORE_PRONTO + .of_match_table = msm_wcnss_pronto_match, +#endif + }, + .probe = wcnss_wlan_probe, + .remove = wcnss_wlan_remove, +}; + +static int __init wcnss_wlan_init(void) +{ + int ret; + + wcnss_ipc_log = ipc_log_context_create(IPC_NUM_LOG_PAGES, "wcnss", 0); + if (!wcnss_ipc_log) + wcnss_log(ERR, "Unable to create log context\n"); + + platform_driver_register(&wcnss_wlan_driver); + platform_driver_register(&wcnss_wlan_ctrl_driver); + ret = wcnss_rpmsg_resource_init(); + if (ret) { + pr_err("%s : register_rpmsg_driver failed with err %d\n", + __func__, ret); + goto resource_deinit; + } + ret = register_rpmsg_driver(&wcnss_rpmsg_client); + if (ret) { + pr_err("%s : register_rpmsg_driver failed with err %d\n", + __func__, ret); + goto register_bail; + } + register_pm_notifier(&wcnss_pm_notifier); + + return 0; + +register_bail: + wcnss_rpmsg_resource_deinit(); +resource_deinit: + platform_driver_unregister(&wcnss_wlan_ctrl_driver); + platform_driver_unregister(&wcnss_wlan_driver); + ipc_log_context_destroy(wcnss_ipc_log); + wcnss_ipc_log = NULL; + return ret; +} + +static void __exit wcnss_wlan_exit(void) +{ + if (penv) { + if (penv->pil) + subsystem_put(penv->pil); + penv = NULL; + } + + unregister_pm_notifier(&wcnss_pm_notifier); + unregister_rpmsg_driver(&wcnss_rpmsg_client); + wcnss_rpmsg_resource_deinit(); + platform_driver_unregister(&wcnss_wlan_ctrl_driver); + platform_driver_unregister(&wcnss_wlan_driver); + ipc_log_context_destroy(wcnss_ipc_log); + wcnss_ipc_log = NULL; +} + +module_init(wcnss_wlan_init); +module_exit(wcnss_wlan_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION(DEVICE "Driver"); diff --git a/drivers/spmi/spmi-pmic-arb-debug.c b/drivers/spmi/spmi-pmic-arb-debug.c index e23188d1de60..2b8d17e4494e 100644 --- a/drivers/spmi/spmi-pmic-arb-debug.c +++ b/drivers/spmi/spmi-pmic-arb-debug.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2012-2018, 2021, The Linux Foundation. All rights reserved. */ #include #include @@ -266,7 +266,8 @@ static int spmi_pmic_arb_debug_probe(struct platform_device *pdev) return -EINVAL; } - fuse_addr = devm_ioremap_resource(&pdev->dev, res); + fuse_addr = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); if (IS_ERR(fuse_addr)) return PTR_ERR(fuse_addr); diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index d2f4cce06bf4..17177f9a033f 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -35,8 +35,6 @@ #include -#define USE_LMH_DEV 0 - /* * Cooling state <-> CPUFreq frequency * @@ -56,12 +54,6 @@ * @time: previous reading of the absolute time that this cpu was idle * @timestamp: wall time of the last invocation of get_cpu_idle_time_us() */ - -struct freq_table { - u32 frequency; - u32 power; -}; - struct time_in_idle { u64 time; u64 timestamp; @@ -95,14 +87,12 @@ struct time_in_idle { */ struct cpufreq_cooling_device { int id; - int cpu_id; u32 last_load; unsigned int cpufreq_state; unsigned int clipped_freq; unsigned int cpufreq_floor_state; unsigned int floor_freq; unsigned int max_level; - struct freq_table *freq_table; struct em_perf_domain *em; struct thermal_cooling_device *cdev; struct cpufreq_policy *policy; @@ -135,7 +125,7 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb, unsigned long clipped_freq = ULONG_MAX, floor_freq = 0; struct cpufreq_cooling_device *cpufreq_cdev; - if (event != CPUFREQ_THERMAL) + if (event != CPUFREQ_INCOMPATIBLE) return NOTIFY_DONE; mutex_lock(&cooling_list_lock); @@ -161,49 +151,19 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb, * Similarly, if policy minimum set by the user is less than * the floor_frequency, then adjust the policy->min. */ - if (clipped_freq > cpufreq_cdev->clipped_freq) - { - //pr_info("__test__, %s,cpu is %d, clipperd_freq is %d, %d.\n", __func__, policy->cpu, clipped_freq, cpufreq_cdev->clipped_freq); - clipped_freq = cpufreq_cdev->clipped_freq; - - } + clipped_freq = cpufreq_cdev->clipped_freq; + floor_freq = cpufreq_cdev->floor_freq; + if (policy->max > clipped_freq || policy->min < floor_freq) + cpufreq_verify_within_limits(policy, floor_freq, + clipped_freq); + break; } - cpufreq_verify_within_limits(policy, floor_freq, clipped_freq); + mutex_unlock(&cooling_list_lock); return NOTIFY_OK; } - -void cpu_limits_set_level(unsigned int cpu, unsigned int max_freq) -{ - struct cpufreq_cooling_device *cpufreq_cdev; - struct thermal_cooling_device *cdev; - unsigned int cdev_cpu; - unsigned int level; - - list_for_each_entry(cpufreq_cdev, &cpufreq_cdev_list, node) { - sscanf(cpufreq_cdev->cdev->type, "thermal-cpufreq-%d", &cdev_cpu); - if (cdev_cpu == cpu) { - for (level = 0; level <= cpufreq_cdev->max_level; level++) { - int target_freq = cpufreq_cdev->em->table[level].frequency; - if (max_freq <= target_freq) { - cdev = cpufreq_cdev->cdev; - if (cdev) - { - //pr_info("__test__, %s, cpu is %d, max_freq is %d, target_freq is %d.\n", __func__, cpu, max_freq, target_freq); - cdev->ops->set_cur_state(cdev, cpufreq_cdev->max_level - level); - - } - break; - } - } - break; - } - } -} - - #ifdef CONFIG_ENERGY_MODEL /** * get_level: Find the level for a particular frequency @@ -445,7 +405,7 @@ static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, /* Request state should be less than max_level */ if (WARN_ON(state > cpufreq_cdev->max_level)) - return cpufreq_cdev->max_level; + return -EINVAL; /* Check if the old cooling action is same as new cooling action */ if (cpufreq_cdev->cpufreq_state == state) @@ -459,18 +419,12 @@ static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, * can handle the CPU freq mitigation, if not, notify cpufreq * framework. */ - if (USE_LMH_DEV && cpufreq_cdev->plat_ops) { - if (cpufreq_cdev->plat_ops->ceil_limit) - cpufreq_cdev->plat_ops->ceil_limit(cpufreq_cdev->policy->cpu, + if (cpufreq_cdev->plat_ops && + cpufreq_cdev->plat_ops->ceil_limit) + cpufreq_cdev->plat_ops->ceil_limit(cpufreq_cdev->policy->cpu, clip_freq); - get_online_cpus(); + else cpufreq_update_policy(cpufreq_cdev->policy->cpu); - put_online_cpus(); - } else { - get_online_cpus(); - cpufreq_update_policy(cpufreq_cdev->policy->cpu); - put_online_cpus(); - } return 0; } @@ -747,7 +701,7 @@ __cpufreq_cooling_register(struct device_node *np, list_add(&cpufreq_cdev->node, &cpufreq_cdev_list); mutex_unlock(&cooling_list_lock); - if (first) + if (first && !cpufreq_cdev->plat_ops) cpufreq_register_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); @@ -887,9 +841,10 @@ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) mutex_unlock(&cooling_list_lock); if (last) { - cpufreq_unregister_notifier( - &thermal_cpufreq_notifier_block, - CPUFREQ_POLICY_NOTIFIER); + if (!cpufreq_cdev->plat_ops) + cpufreq_unregister_notifier( + &thermal_cpufreq_notifier_block, + CPUFREQ_POLICY_NOTIFIER); } thermal_cooling_device_unregister(cpufreq_cdev->cdev); diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 70bb797c910e..6411e11200cc 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -22,11 +22,6 @@ #include #include #include -#include - -#ifdef CONFIG_DRM -#include -#endif #define CREATE_TRACE_POINTS #include @@ -40,8 +35,6 @@ MODULE_LICENSE("GPL v2"); #define THERMAL_MAX_ACTIVE 16 -#define CPU_LIMITS_PARAM_NUM 2 - static DEFINE_IDA(thermal_tz_ida); static DEFINE_IDA(thermal_cdev_ida); @@ -53,21 +46,6 @@ static DEFINE_MUTEX(thermal_list_lock); static DEFINE_MUTEX(thermal_governor_lock); static DEFINE_MUTEX(poweroff_lock); -#ifdef CONFIG_DRM -struct screen_monitor { - struct notifier_block thermal_notifier; - int screen_state; -}; - -struct screen_monitor sm; -#endif - -static atomic_t switch_mode = ATOMIC_INIT(-1); -static atomic_t temp_state = ATOMIC_INIT(0); -static char boost_buf[128]; -const char *board_sensor; -static char board_sensor_temp[128]; - static atomic_t in_suspend; static bool power_off_triggered; @@ -961,8 +939,6 @@ static struct class thermal_class = { .dev_release = thermal_release, }; -static struct device thermal_message_dev; - static inline void print_bind_err_msg(struct thermal_zone_device *tz, struct thermal_cooling_device *cdev, int ret) @@ -1682,252 +1658,14 @@ static struct notifier_block thermal_pm_nb = { .notifier_call = thermal_pm_notify, }; -#ifdef CONFIG_DRM -static ssize_t -thermal_screen_state_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return snprintf(buf, PAGE_SIZE, "%d\n", sm.screen_state); -} - -static DEVICE_ATTR(screen_state, 0664, - thermal_screen_state_show, NULL); -#endif - -static ssize_t -thermal_sconfig_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return snprintf(buf, PAGE_SIZE, "%d\n", atomic_read(&switch_mode)); -} - - -static ssize_t -thermal_sconfig_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t len) -{ - int val = -1; - - val = simple_strtol(buf, NULL, 10); - - atomic_set(&switch_mode, val); - - return len; -} - -static DEVICE_ATTR(sconfig, 0664, - thermal_sconfig_show, thermal_sconfig_store); - -static ssize_t -thermal_boost_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return snprintf(buf, PAGE_SIZE, boost_buf); -} - -static ssize_t -thermal_boost_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t len) -{ - int ret; - ret = snprintf(boost_buf, sizeof(boost_buf), buf); - return len; -} - -static DEVICE_ATTR(boost, 0644, - thermal_boost_show, thermal_boost_store); - -static ssize_t -thermal_temp_state_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return snprintf(buf, PAGE_SIZE, "%d\n", atomic_read(&temp_state)); -} - -static ssize_t -thermal_temp_state_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t len) -{ - int val = -1; - - val = simple_strtol(buf, NULL, 10); - - atomic_set(&temp_state, val); - - return len; -} - -static DEVICE_ATTR(temp_state, 0664, - thermal_temp_state_show, thermal_temp_state_store); - -static ssize_t -cpu_limits_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return 0; -} - -static ssize_t -cpu_limits_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t len) -{ - unsigned int cpu; - unsigned int max; - - if (sscanf(buf, "cpu%u %u", &cpu, &max) != CPU_LIMITS_PARAM_NUM) { - pr_err("input param error, can not prase param\n"); - return -EINVAL; - } - - cpu_limits_set_level(cpu, max); - - return len; -} - -static ssize_t -thermal_board_sensor_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - if (!board_sensor) - board_sensor = "invalid"; - - return snprintf(buf, PAGE_SIZE, "%s", board_sensor); -} - -static DEVICE_ATTR(board_sensor, 0664, - thermal_board_sensor_show, NULL); - -static ssize_t -thermal_board_sensor_temp_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return snprintf(buf, PAGE_SIZE, board_sensor_temp); -} - -static ssize_t -thermal_board_sensor_temp_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t len) -{ - snprintf(board_sensor_temp, sizeof(board_sensor_temp), buf); - - return len; -} - -static DEVICE_ATTR(board_sensor_temp, 0664, - thermal_board_sensor_temp_show, thermal_board_sensor_temp_store); - -static DEVICE_ATTR(cpu_limits, 0664, - cpu_limits_show, cpu_limits_store); - -static int create_thermal_message_node(void) -{ - int ret = 0; - - thermal_message_dev.class = &thermal_class; - - dev_set_name(&thermal_message_dev, "thermal_message"); - ret = device_register(&thermal_message_dev); - if (!ret) { -#ifdef CONFIG_DRM - ret = sysfs_create_file(&thermal_message_dev.kobj, &dev_attr_screen_state.attr); - if (ret < 0) - pr_warn("Thermal: create batt message node failed\n"); -#endif - ret = sysfs_create_file(&thermal_message_dev.kobj, &dev_attr_sconfig.attr); - if (ret < 0) - pr_warn("Thermal: create sconfig node failed\n"); - - ret = sysfs_create_file(&thermal_message_dev.kobj, &dev_attr_boost.attr); - if (ret < 0) - pr_warn("Thermal: create boost node failed\n"); - - ret = sysfs_create_file(&thermal_message_dev.kobj, &dev_attr_temp_state.attr); - if (ret < 0) - pr_warn("Thermal: create temp state node failed\n"); - - ret = sysfs_create_file(&thermal_message_dev.kobj, &dev_attr_cpu_limits.attr); - ret = sysfs_create_file(&thermal_message_dev.kobj, &dev_attr_board_sensor.attr); - if (ret < 0) - pr_warn("Thermal: create board sensor node failed\n"); - - ret = sysfs_create_file(&thermal_message_dev.kobj, &dev_attr_board_sensor_temp.attr); - if (ret < 0) - pr_warn("Thermal: create cpu limits node failed\n"); - } - - return ret; -} - -static void destroy_thermal_message_node(void) -{ - sysfs_remove_file(&thermal_message_dev.kobj, &dev_attr_cpu_limits.attr); - sysfs_remove_file(&thermal_message_dev.kobj, &dev_attr_temp_state.attr); - sysfs_remove_file(&thermal_message_dev.kobj, &dev_attr_boost.attr); - sysfs_remove_file(&thermal_message_dev.kobj, &dev_attr_sconfig.attr); - sysfs_remove_file(&thermal_message_dev.kobj, &dev_attr_board_sensor_temp.attr); - sysfs_remove_file(&thermal_message_dev.kobj, &dev_attr_board_sensor.attr); -#ifdef CONFIG_DRM - sysfs_remove_file(&thermal_message_dev.kobj, &dev_attr_screen_state.attr); -#endif - device_unregister(&thermal_message_dev); -} - -#ifdef CONFIG_DRM -static int screen_state_for_thermal_callback(struct notifier_block *nb, unsigned long val, void *data) -{ - struct drm_notify_data *evdata = data; - unsigned int blank; - - if (val != DRM_EVENT_BLANK || !evdata || !evdata->data) - return 0; - - blank = *(int *)(evdata->data); - switch (blank) { - case DRM_BLANK_LP1: - pr_warn("%s: DRM_BLANK_LP1\n", __func__); - case DRM_BLANK_LP2: - pr_warn("%s: DRM_BLANK_LP2\n", __func__); - case DRM_BLANK_POWERDOWN: - sm.screen_state = 0; - pr_warn("%s: DRM_BLANK_POWERDOWN\n", __func__); - break; - case DRM_BLANK_UNBLANK: - sm.screen_state = 1; - pr_warn("%s: DRM_BLANK_UNBLANK\n", __func__); - break; - default: - break; - } - - sysfs_notify(&thermal_message_dev.kobj, NULL, "screen_state"); - - return NOTIFY_OK; -} -#endif - -static int of_parse_thermal_message(void) -{ - struct device_node *np; - - np = of_find_node_by_name(NULL, "thermal-message"); - if (!np) - return -EINVAL; - - if (of_property_read_string(np, "board-sensor", &board_sensor)) - return -EINVAL; - - pr_info("%s board sensor: %s\n", __func__, board_sensor); - - return 0; -} - static int __init thermal_init(void) { int result; mutex_init(&poweroff_lock); thermal_passive_wq = alloc_workqueue("thermal_passive_wq", - WQ_UNBOUND | WQ_FREEZABLE, + WQ_HIGHPRI | WQ_UNBOUND + | WQ_FREEZABLE, THERMAL_MAX_ACTIVE); if (!thermal_passive_wq) { result = -ENOMEM; @@ -1951,22 +1689,6 @@ static int __init thermal_init(void) pr_warn("Thermal: Can not register suspend notifier, return %d\n", result); - result = create_thermal_message_node(); - if (result) - pr_warn("Thermal: create thermal message node failed, return %d\n", - result); - result = of_parse_thermal_message(); - if (result) - pr_warn("Thermal: Can not parse thermal message node, return %d\n", - result); - -#ifdef CONFIG_DRM - sm.thermal_notifier.notifier_call = screen_state_for_thermal_callback; - if (drm_register_client(&sm.thermal_notifier) < 0) { - pr_warn("Thermal: register screen state callback failed\n"); - } -#endif - return 0; exit_zone_parse: @@ -1986,14 +1708,10 @@ static int __init thermal_init(void) static void thermal_exit(void) { -#ifdef CONFIG_DRM - drm_unregister_client(&sm.thermal_notifier); -#endif unregister_pm_notifier(&thermal_pm_nb); of_thermal_destroy_zones(); destroy_workqueue(thermal_passive_wq); genetlink_exit(); - destroy_thermal_message_node(); class_unregister(&thermal_class); thermal_unregister_governors(); ida_destroy(&thermal_tz_ida); diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c index dc867546195f..9ca7eeb40fd1 100644 --- a/drivers/thermal/thermal_sysfs.c +++ b/drivers/thermal/thermal_sysfs.c @@ -903,7 +903,7 @@ void thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev, spin_lock(&stats->lock); - if (stats->state == new_state || new_state >= stats->max_states) + if (stats->state == new_state) goto unlock; update_time_in_state(stats); diff --git a/drivers/tty/serial/msm_geni_serial.c b/drivers/tty/serial/msm_geni_serial.c index 3719e31e780c..7ed40ca19637 100644 --- a/drivers/tty/serial/msm_geni_serial.c +++ b/drivers/tty/serial/msm_geni_serial.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ #include @@ -123,7 +123,9 @@ #define DATA_BYTES_PER_LINE (32) #define M_IRQ_BITS (M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN |\ - M_CMD_CANCEL_EN | M_CMD_ABORT_EN) + M_CMD_CANCEL_EN | M_CMD_ABORT_EN |\ + M_IO_DATA_ASSERT_EN | M_IO_DATA_DEASSERT_EN) + #define S_IRQ_BITS (S_RX_FIFO_WATERMARK_EN | S_RX_FIFO_LAST_EN |\ S_CMD_CANCEL_EN | S_CMD_ABORT_EN) #define DMA_TX_IRQ_BITS (TX_RESET_DONE | TX_DMA_DONE |\ @@ -208,6 +210,7 @@ struct msm_geni_serial_port { bool s_cmd; struct completion m_cmd_timeout; struct completion s_cmd_timeout; + spinlock_t rx_lock; }; static const struct uart_ops msm_geni_serial_pops; @@ -234,6 +237,7 @@ static int msm_geni_serial_runtime_suspend(struct device *dev); static int msm_geni_serial_get_ver_info(struct uart_port *uport); static void msm_geni_serial_set_manual_flow(bool enable, struct msm_geni_serial_port *port); +static bool handle_rx_dma_xfer(u32 s_irq_status, struct uart_port *uport); static int uart_line_id; #define GET_DEV_PORT(uport) \ @@ -1365,14 +1369,14 @@ static void start_rx_sequencer(struct uart_port *uport) msm_geni_serial_stop_rx(uport); } - /* Start RX with the RFR_OPEN to keep RFR in always ready state */ - msm_geni_serial_enable_interrupts(uport); - geni_setup_s_cmd(uport->membase, UART_START_READ, geni_se_param); - if (port->xfer_mode == SE_DMA) geni_se_rx_dma_start(uport->membase, DMA_RX_BUF_SIZE, &port->rx_dma); + /* Start RX with the RFR_OPEN to keep RFR in always ready state */ + geni_setup_s_cmd(uport->membase, UART_START_READ, geni_se_param); + msm_geni_serial_enable_interrupts(uport); + /* Ensure that the above writes go through */ mb(); geni_status = geni_read_reg_nolog(uport->membase, SE_GENI_STATUS); @@ -1436,6 +1440,7 @@ static int stop_rx_sequencer(struct uart_port *uport) unsigned long flags = 0; bool is_rx_active; unsigned int stale_delay; + u32 dma_rx_status, s_irq_status; IPC_LOG_MSG(port->ipc_log_misc, "%s\n", __func__); @@ -1460,6 +1465,25 @@ static int stop_rx_sequencer(struct uart_port *uport) stale_delay = (STALE_COUNT * SEC_TO_USEC) / port->cur_baud; stale_delay = (2 * stale_delay) + SYSTEM_DELAY; udelay(stale_delay); + + dma_rx_status = geni_read_reg_nolog(uport->membase, + SE_DMA_RX_IRQ_STAT); + /* The transfer is completed at HW level and the completion + * interrupt is delayed. So process the transfer completion + * before issuing the cancel command to resolve the race + * btw cancel RX and completion interrupt. + */ + if (dma_rx_status) { + s_irq_status = geni_read_reg_nolog(uport->membase, + SE_GENI_S_IRQ_STATUS); + geni_write_reg_nolog(s_irq_status, uport->membase, + SE_GENI_S_IRQ_CLEAR); + geni_se_dump_dbg_regs(&port->serial_rsc, + uport->membase, port->ipc_log_misc); + IPC_LOG_MSG(port->ipc_log_misc, "%s: Interrupt delay\n", + __func__); + handle_rx_dma_xfer(s_irq_status, uport); + } } IPC_LOG_MSG(port->ipc_log_misc, "%s: Start 0x%x\n", @@ -1861,6 +1885,77 @@ static int msm_geni_serial_handle_dma_tx(struct uart_port *uport) return 0; } +static bool handle_rx_dma_xfer(u32 s_irq_status, struct uart_port *uport) +{ + bool ret = false; + bool drop_rx = false; + struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport); + u32 dma_rx_status; + + spin_lock(&msm_port->rx_lock); + dma_rx_status = geni_read_reg_nolog(uport->membase, + SE_DMA_RX_IRQ_STAT); + + if (dma_rx_status) { + geni_write_reg_nolog(dma_rx_status, uport->membase, + SE_DMA_RX_IRQ_CLR); + + if (dma_rx_status & RX_RESET_DONE) { + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s.Reset done. 0x%x.\n", __func__, dma_rx_status); + ret = true; + goto exit; + } + + if (dma_rx_status & UART_DMA_RX_ERRS) { + if (dma_rx_status & UART_DMA_RX_PARITY_ERR) + uport->icount.parity++; + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s.Rx Errors. 0x%x parity:%d\n", + __func__, dma_rx_status, + uport->icount.parity); + drop_rx = true; + } else if (dma_rx_status & UART_DMA_RX_BREAK) { + uport->icount.brk++; + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s.Rx Errors. 0x%x break:%d\n", + __func__, dma_rx_status, + uport->icount.brk); + } + + if (dma_rx_status & RX_EOT || + dma_rx_status & RX_DMA_DONE) { + msm_geni_serial_handle_dma_rx(uport, + drop_rx); + if (!(dma_rx_status & RX_GENI_CANCEL_IRQ)) { + geni_se_rx_dma_start(uport->membase, + DMA_RX_BUF_SIZE, &msm_port->rx_dma); + } else { + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s. not mapping rx dma\n", + __func__); + } + } + if (dma_rx_status & RX_SBE) { + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s.Rx Errors. 0x%x\n", + __func__, dma_rx_status); + WARN_ON(1); + } + + if (dma_rx_status & (RX_EOT | RX_GENI_CANCEL_IRQ | RX_DMA_DONE)) + ret = true; + + } + + if (s_irq_status & (S_CMD_CANCEL_EN | S_CMD_ABORT_EN)) + ret = true; + +exit: + spin_unlock(&msm_port->rx_lock); + return ret; +} + static void msm_geni_serial_handle_isr(struct uart_port *uport, unsigned long *flags, bool is_irq_masked) @@ -1908,6 +2003,13 @@ static void msm_geni_serial_handle_isr(struct uart_port *uport, goto exit_geni_serial_isr; } + if (m_irq_status & (M_IO_DATA_ASSERT_EN | M_IO_DATA_DEASSERT_EN)) { + uport->icount.cts++; + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s. cts counter:%d\n", __func__, + uport->icount.cts); + } + if (s_irq_status & S_RX_FIFO_WR_ERR_EN) { uport->icount.overrun++; tty_insert_flip_char(tport, 0, TTY_OVERRUN); @@ -1973,7 +2075,7 @@ static void msm_geni_serial_handle_isr(struct uart_port *uport, if (m_irq_status || s_irq_status || dma_tx_status || dma_rx_status) IPC_LOG_MSG(msm_port->ipc_log_irqstatus, - "%s: sirq:0x%x mirq:0x%x dma_txirq:0x%x dma_rxirq:0x%x\n", + "%s: sirq:0x%x mirq:0x%x dma_txirq:0x%x dma_rxirq:0x%x\n", __func__, s_irq_status, m_irq_status, dma_tx_status, dma_rx_status); if (dma_tx_status) { @@ -1985,68 +2087,18 @@ static void msm_geni_serial_handle_isr(struct uart_port *uport, TX_GENI_CANCEL_IRQ)) m_cmd_done = true; - if (m_irq_status & (M_CMD_CANCEL_EN | M_CMD_ABORT_EN)) - m_cmd_done = true; - if ((dma_tx_status & TX_DMA_DONE) && !m_cmd_done) msm_geni_serial_handle_dma_tx(uport); } - if (dma_rx_status) { - geni_write_reg_nolog(dma_rx_status, uport->membase, - SE_DMA_RX_IRQ_CLR); + if (m_irq_status & (M_CMD_CANCEL_EN | M_CMD_ABORT_EN)) + m_cmd_done = true; - if (dma_rx_status & RX_RESET_DONE) { - IPC_LOG_MSG(msm_port->ipc_log_misc, - "%s.Reset done. 0x%x.\n", - __func__, dma_rx_status); - goto exit_geni_serial_isr; - } + if (dma_rx_status) + s_cmd_done = handle_rx_dma_xfer(s_irq_status, uport); - if (dma_rx_status & UART_DMA_RX_ERRS) { - if (dma_rx_status & UART_DMA_RX_PARITY_ERR) - uport->icount.parity++; - IPC_LOG_MSG(msm_port->ipc_log_misc, - "%s.Rx Errors. 0x%x parity:%d\n", - __func__, dma_rx_status, - uport->icount.parity); - drop_rx = true; - } else if (dma_rx_status & UART_DMA_RX_BREAK) { - uport->icount.brk++; - IPC_LOG_MSG(msm_port->ipc_log_misc, - "%s.Rx Errors. 0x%x break:%d\n", - __func__, dma_rx_status, - uport->icount.brk); - } - - if (dma_rx_status & RX_EOT || - dma_rx_status & RX_DMA_DONE) { - msm_geni_serial_handle_dma_rx(uport, - drop_rx); - if (!(dma_rx_status & RX_GENI_CANCEL_IRQ)) { - geni_se_rx_dma_start(uport->membase, - DMA_RX_BUF_SIZE, &msm_port->rx_dma); - } else { - IPC_LOG_MSG(msm_port->ipc_log_misc, - "%s. not mapping rx dma\n", - __func__); - } - } - - if (dma_rx_status & RX_SBE) { - IPC_LOG_MSG(msm_port->ipc_log_misc, - "%s.Rx Errors. 0x%x\n", - __func__, dma_rx_status); - WARN_ON(1); - } - - if (dma_rx_status & (RX_EOT | RX_GENI_CANCEL_IRQ | - RX_DMA_DONE)) - s_cmd_done = true; - - if (s_irq_status & (S_CMD_CANCEL_EN | S_CMD_ABORT_EN)) - s_cmd_done = true; - } + if (s_irq_status & (S_CMD_CANCEL_EN | S_CMD_ABORT_EN)) + s_cmd_done = true; } exit_geni_serial_isr: @@ -3376,6 +3428,9 @@ static int msm_geni_serial_probe(struct platform_device *pdev) if (ret) goto exit_wakeup_unregister; + if (!uart_console(uport)) + spin_lock_init(&dev_port->rx_lock); + IPC_LOG_MSG(dev_port->ipc_log_misc, "%s: port:%s irq:%d\n", __func__, uport->name, uport->irq); diff --git a/drivers/uio/msm_sharedmem/msm_sharedmem.c b/drivers/uio/msm_sharedmem/msm_sharedmem.c index 2cd101b9f550..64c20deada1c 100644 --- a/drivers/uio/msm_sharedmem/msm_sharedmem.c +++ b/drivers/uio/msm_sharedmem/msm_sharedmem.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. */ #define DRIVER_NAME "msm_sharedmem" @@ -186,11 +186,8 @@ static int msm_sharedmem_probe(struct platform_device *pdev) "qcom,vm-nav-path"); /* Set up the permissions for the shared ram that was allocated. */ - ret = setup_shared_ram_perms(client_id, shared_mem_pyhsical, + setup_shared_ram_perms(client_id, shared_mem_pyhsical, shared_mem_size, vm_nav_path); - if (ret) - goto out; - /* Setup device */ info->mmap = sharedmem_mmap; /* Custom mmap function. */ info->name = clnt_res->name; diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 9dc15d9b5c4a..17febe960b63 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -45,8 +45,6 @@ #define USB_TP_TRANSMISSION_DELAY 40 /* ns */ #define USB_TP_TRANSMISSION_DELAY_MAX 65535 /* ns */ -int deny_new_usb __read_mostly = 0; - /* Protect struct usb_device->state and ->children members * Note: Both are also protected by ->dev.sem, except that ->state can * change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */ @@ -4997,12 +4995,6 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus, goto done; return; } - - if (deny_new_usb) { - dev_err(&port_dev->dev, "denied insert of USB device on port %d\n", port1); - goto done; - } - if (hub_is_superspeed(hub->hdev)) unit_load = 150; else diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 2f65fd20e394..794344785e07 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1009,7 +1009,6 @@ int dwc3_core_init(struct dwc3 *dwc) */ if (!dwc3_is_usb31(dwc)) { reg |= DWC3_GUCTL1_PARKMODE_DISABLE_SS; - reg |= DWC3_GUCTL1_PARKMODE_DISABLE_HS; reg |= DWC3_GUCTL1_PARKMODE_DISABLE_FSLS; } @@ -1590,7 +1589,7 @@ static int dwc3_probe(struct platform_device *pdev) snprintf(dma_ipc_log_ctx_name, sizeof(dma_ipc_log_ctx_name), "%s.ep_events", dev_name(dwc->dev)); - dwc->dwc_dma_ipc_log_ctxt = ipc_log_context_create(NUM_LOG_PAGES, + dwc->dwc_dma_ipc_log_ctxt = ipc_log_context_create(2 * NUM_LOG_PAGES, dma_ipc_log_ctx_name, 0); if (!dwc->dwc_dma_ipc_log_ctxt) dev_err(dwc->dev, "Error getting ipc_log_ctxt for ep_events\n"); diff --git a/drivers/usb/dwc3/debug_ipc.c b/drivers/usb/dwc3/debug_ipc.c index e00bbfc4eada..1873b222dc8c 100644 --- a/drivers/usb/dwc3/debug_ipc.c +++ b/drivers/usb/dwc3/debug_ipc.c @@ -139,19 +139,39 @@ void dwc3_dbg_print_reg(struct dwc3 *dwc, const char *name, int reg) void dwc3_dbg_dma_unmap(struct dwc3 *dwc, u8 ep_num, struct dwc3_request *req) { - ipc_log_string(dwc->dwc_dma_ipc_log_ctxt, - "%02X-%-3.3s %-25.25s 0x%pK 0x%lx %u 0x%lx %d", ep_num >> 1, - ep_num & 1 ? "IN":"OUT", "UNMAP", &req->request, - req->request.dma, req->request.length, req->trb_dma, - req->trb->ctrl & DWC3_TRB_CTRL_HWO); + if (req->request.num_sgs > 0) { + ipc_log_string(dwc->dwc_dma_ipc_log_ctxt, + "%02X-%-3.3s %-25.25s 0x%pK 0x%lx %u 0x%lx mapped_sgs:%d queued_sgs:%d %d", + ep_num >> 1, ep_num & 1 ? "IN":"OUT", "UNMAP", + &req->request, sg_dma_address(req->start_sg), + sg_dma_len(req->start_sg), req->trb_dma, + req->request.num_mapped_sgs, req->num_queued_sgs, + req->trb->ctrl & DWC3_TRB_CTRL_HWO); + } else { + ipc_log_string(dwc->dwc_dma_ipc_log_ctxt, + "%02X-%-3.3s %-25.25s 0x%pK 0x%lx %u 0x%lx %d", + ep_num >> 1, ep_num & 1 ? "IN":"OUT", "UNMAP", + &req->request, req->request.dma, req->request.length, + req->trb_dma, req->trb->ctrl & DWC3_TRB_CTRL_HWO); + } } void dwc3_dbg_dma_map(struct dwc3 *dwc, u8 ep_num, struct dwc3_request *req) { - ipc_log_string(dwc->dwc_dma_ipc_log_ctxt, - "%02X-%-3.3s %-25.25s 0x%pK 0x%lx %u 0x%lx", ep_num >> 1, - ep_num & 1 ? "IN":"OUT", "MAP", &req->request, req->request.dma, - req->request.length, req->trb_dma); + if (req->request.num_sgs > 0) { + ipc_log_string(dwc->dwc_dma_ipc_log_ctxt, + "%02X-%-3.3s %-25.25s 0x%pK 0x%lx %u 0x%lx mapped_sgs:%d queued_sgs:%d", + ep_num >> 1, ep_num & 1 ? "IN":"OUT", "MAP", + &req->request, sg_dma_address(req->start_sg), + sg_dma_len(req->start_sg), req->trb_dma, + req->request.num_mapped_sgs, req->num_queued_sgs); + } else { + ipc_log_string(dwc->dwc_dma_ipc_log_ctxt, + "%02X-%-3.3s %-25.25s 0x%pK 0x%lx %u 0x%lx", + ep_num >> 1, ep_num & 1 ? "IN":"OUT", "MAP", + &req->request, req->request.dma, req->request.length, + req->trb_dma); + } } void dwc3_dbg_dma_dequeue(struct dwc3 *dwc, u8 ep_num, struct dwc3_request *req) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index e87f089d98ff..8955fc90c9e2 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -812,7 +812,7 @@ static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep) dbg_log_string("START for %s(%d)", dep->name, dep->number); dwc3_stop_active_transfer(dwc, dep->number, true); - if (dep->number == 0 && dwc->ep0state != EP0_SETUP_PHASE) { + if (dep->number == 0) { unsigned int dir; dbg_log_string("CTRLPEND(%d)", dwc->ep0state); @@ -1012,6 +1012,12 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, dep->name)) return 0; + if (pm_runtime_suspended(dwc->sysdev)) { + dev_err(dwc->dev, "fail ep_enable %s device is into LPM\n", + dep->name); + return -EINVAL; + } + spin_lock_irqsave(&dwc->lock, flags); ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); dbg_event(dep->number, "ENABLE", ret); @@ -1040,10 +1046,13 @@ static int dwc3_gadget_ep_disable(struct usb_ep *ep) dep->name)) return 0; + pm_runtime_get_sync(dwc->sysdev); spin_lock_irqsave(&dwc->lock, flags); ret = __dwc3_gadget_ep_disable(dep); dbg_event(dep->number, "DISABLE", ret); spin_unlock_irqrestore(&dwc->lock, flags); + pm_runtime_mark_last_busy(dwc->sysdev); + pm_runtime_put_sync_autosuspend(dwc->sysdev); return ret; } @@ -1805,11 +1814,19 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, */ list_for_each_entry_safe(r, t, &dep->started_list, list) dwc3_gadget_move_cancelled_request(r); + /* If GEN1 controller then cleanup the cancelled + * requests from here as check for + * DWC3_EP_END_TRANSFER_PENDING in EPCMDCMPLT + * will prevent the request on cancelled list from + * getting cleared there. + */ + if (!(dep->flags & DWC3_EP_END_TRANSFER_PENDING)) { + dbg_log_string("%s:giveback all request\n", + __func__); + dwc3_gadget_ep_cleanup_cancelled_requests(dep); + } - if (dep->flags & DWC3_EP_TRANSFER_STARTED) - goto out0; - else - goto out1; + goto out0; } dev_err_ratelimited(dwc->dev, "request %pK was not queued to %s\n", request, ep->name); @@ -1817,7 +1834,6 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, goto out0; } -out1: dbg_ep_dequeue(dep->number, req); dwc3_gadget_giveback(dep, req, -ECONNRESET); @@ -3239,8 +3255,12 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, case DWC3_DEPEVT_EPCMDCMPLT: dep->dbg_ep_events.epcmdcomplete++; cmd = DEPEVT_PARAMETER_CMD(event->parameters); - - if (cmd == DWC3_DEPCMD_ENDTRANSFER) { + /* Prevent GEN1 controllers to cleanup cancelled + * request twice (one from error path in kick_transfer + * another from here). + */ + if (cmd == DWC3_DEPCMD_ENDTRANSFER && + (dep->flags & DWC3_EP_END_TRANSFER_PENDING)) { dep->flags &= ~(DWC3_EP_END_TRANSFER_PENDING | DWC3_EP_TRANSFER_STARTED); dwc3_gadget_ep_cleanup_cancelled_requests(dep); @@ -3407,6 +3427,11 @@ int dwc3_stop_active_transfer_noioc(struct dwc3 *dwc, u32 epnum, bool force) dbg_log_string("%s(%d): endxfer ret:%d)", dep->name, dep->number, ret); + + /* Clear DWC3_EP_TRANSFER_STARTED if endxfer fails */ + if (ret) + dep->flags &= ~DWC3_EP_TRANSFER_STARTED; + return ret; } diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 130dad7130b6..47ced3cda111 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -11,3 +11,4 @@ libcomposite-y := usbstring.o config.o epautoconf.o libcomposite-y += composite.o functions.o configfs.o u_f.o obj-$(CONFIG_USB_GADGET) += udc/ function/ legacy/ +obj-$(CONFIG_USB_CI13XXX_MSM) += ci13xxx_msm.o diff --git a/drivers/usb/gadget/ci13xxx_msm.c b/drivers/usb/gadget/ci13xxx_msm.c new file mode 100644 index 000000000000..e128d0e4bbe0 --- /dev/null +++ b/drivers/usb/gadget/ci13xxx_msm.c @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2010-2019,2021 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 +#include +#include +#include +#include +#include +#include +#include + +#include "ci13xxx_udc.c" + +#define MSM_USB_BASE (udc->regs) + +#define CI13XXX_MSM_MAX_LOG2_ITC 7 + +struct ci13xxx_udc_context { + int irq; + void __iomem *regs; + int wake_gpio; + int wake_irq; + bool wake_irq_state; + struct pinctrl *ci13xxx_pinctrl; + struct timer_list irq_enable_timer; + bool irq_disabled; +}; + +static struct ci13xxx_udc_context _udc_ctxt; +#define IRQ_ENABLE_DELAY (jiffies + msecs_to_jiffies(1000)) + +static irqreturn_t msm_udc_irq(int irq, void *data) +{ + return udc_irq(); +} + +static void ci13xxx_msm_suspend(void) +{ + struct device *dev = _udc->gadget.dev.parent; + + dev_dbg(dev, "%s\n", __func__); + + if (_udc_ctxt.wake_irq && !_udc_ctxt.wake_irq_state) { + enable_irq_wake(_udc_ctxt.wake_irq); + enable_irq(_udc_ctxt.wake_irq); + _udc_ctxt.wake_irq_state = true; + } +} + +static void ci13xxx_msm_resume(void) +{ + struct device *dev = _udc->gadget.dev.parent; + + dev_dbg(dev, "%s\n", __func__); + + if (_udc_ctxt.wake_irq && _udc_ctxt.wake_irq_state) { + disable_irq_wake(_udc_ctxt.wake_irq); + disable_irq_nosync(_udc_ctxt.wake_irq); + _udc_ctxt.wake_irq_state = false; + } +} + +static void ci13xxx_msm_disconnect(void) +{ + struct ci13xxx *udc = _udc; + struct usb_phy *phy = udc->transceiver; + + if (phy && (phy->flags & ENABLE_DP_MANUAL_PULLUP)) { + usb_phy_io_write(phy, + ULPI_MISC_A_VBUSVLDEXT | + ULPI_MISC_A_VBUSVLDEXTSEL, + ULPI_CLR(ULPI_MISC_A)); + + /* + * Add memory barrier as it is must to complete + * above USB PHY and Link register writes before + * moving ahead with USB peripheral mode enumeration, + * otherwise USB peripheral mode may not work. + */ + mb(); + } +} + +/* Link power management will reduce power consumption by + * short time HW suspend/resume. + */ +static void ci13xxx_msm_set_l1(struct ci13xxx *udc) +{ + int temp; + struct device *dev = udc->gadget.dev.parent; + + dev_dbg(dev, "Enable link power management\n"); + + /* Enable remote wakeup and L1 for IN EPs */ + writel_relaxed(0xffff0000, USB_L1_EP_CTRL); + + temp = readl_relaxed(USB_L1_CONFIG); + temp |= L1_CONFIG_LPM_EN | L1_CONFIG_REMOTE_WAKEUP | + L1_CONFIG_GATE_SYS_CLK | L1_CONFIG_PHY_LPM | + L1_CONFIG_PLL; + writel_relaxed(temp, USB_L1_CONFIG); +} + +static void ci13xxx_msm_connect(void) +{ + struct ci13xxx *udc = _udc; + struct usb_phy *phy = udc->transceiver; + + if (phy && (phy->flags & ENABLE_DP_MANUAL_PULLUP)) { + int temp; + + usb_phy_io_write(phy, + ULPI_MISC_A_VBUSVLDEXT | + ULPI_MISC_A_VBUSVLDEXTSEL, + ULPI_SET(ULPI_MISC_A)); + + temp = readl_relaxed(USB_GENCONFIG_2); + temp |= GENCONFIG_2_SESS_VLD_CTRL_EN; + writel_relaxed(temp, USB_GENCONFIG_2); + + temp = readl_relaxed(USB_USBCMD); + temp |= USBCMD_SESS_VLD_CTRL; + writel_relaxed(temp, USB_USBCMD); + + /* + * Add memory barrier as it is must to complete + * above USB PHY and Link register writes before + * moving ahead with USB peripheral mode enumeration, + * otherwise USB peripheral mode may not work. + */ + mb(); + } +} + +static void ci13xxx_msm_reset(void) +{ + struct ci13xxx *udc = _udc; + struct usb_phy *phy = udc->transceiver; + struct device *dev = udc->gadget.dev.parent; + int temp; + + writel_relaxed(0, USB_AHBBURST); + writel_relaxed(0x08, USB_AHBMODE); + + /* workaround for rx buffer collision issue */ + temp = readl_relaxed(USB_GENCONFIG); + temp &= ~GENCONFIG_TXFIFO_IDLE_FORCE_DISABLE; + temp &= ~GENCONFIG_ULPI_SERIAL_EN; + writel_relaxed(temp, USB_GENCONFIG); + + if (udc->gadget.l1_supported) + ci13xxx_msm_set_l1(udc); + + if (phy && (phy->flags & ENABLE_SECONDARY_PHY)) { + int temp; + + dev_dbg(dev, "using secondary hsphy\n"); + temp = readl_relaxed(USB_PHY_CTRL2); + temp |= (1<<16); + writel_relaxed(temp, USB_PHY_CTRL2); + + /* + * Add memory barrier to make sure above LINK writes are + * complete before moving ahead with USB peripheral mode + * enumeration. + */ + mb(); + } +} + +static void ci13xxx_msm_mark_err_event(void) +{ + struct ci13xxx *udc = _udc; + struct msm_otg *otg; + + if (udc == NULL) + return; + + if (udc->transceiver == NULL) + return; + + otg = container_of(udc->transceiver, struct msm_otg, phy); + + /* This will trigger hardware reset before next connection */ + otg->err_event_seen = true; +} + +static void ci13xxx_msm_notify_event(struct ci13xxx *udc, unsigned int event) +{ + struct device *dev = udc->gadget.dev.parent; + + switch (event) { + case CI13XXX_CONTROLLER_RESET_EVENT: + dev_info(dev, "CI13XXX_CONTROLLER_RESET_EVENT received\n"); + ci13xxx_msm_reset(); + break; + case CI13XXX_CONTROLLER_DISCONNECT_EVENT: + dev_info(dev, "CI13XXX_CONTROLLER_DISCONNECT_EVENT received\n"); + ci13xxx_msm_disconnect(); + ci13xxx_msm_resume(); + break; + case CI13XXX_CONTROLLER_CONNECT_EVENT: + dev_info(dev, "CI13XXX_CONTROLLER_CONNECT_EVENT received\n"); + ci13xxx_msm_connect(); + break; + case CI13XXX_CONTROLLER_SUSPEND_EVENT: + dev_info(dev, "CI13XXX_CONTROLLER_SUSPEND_EVENT received\n"); + ci13xxx_msm_suspend(); + break; + case CI13XXX_CONTROLLER_RESUME_EVENT: + dev_info(dev, "CI13XXX_CONTROLLER_RESUME_EVENT received\n"); + ci13xxx_msm_resume(); + break; + case CI13XXX_CONTROLLER_ERROR_EVENT: + dev_info(dev, "CI13XXX_CONTROLLER_ERROR_EVENT received\n"); + ci13xxx_msm_mark_err_event(); + break; + case CI13XXX_CONTROLLER_UDC_STARTED_EVENT: + dev_info(dev, + "CI13XXX_CONTROLLER_UDC_STARTED_EVENT received\n"); + break; + default: + dev_dbg(dev, "unknown ci13xxx_udc event\n"); + break; + } +} + +static bool ci13xxx_msm_in_lpm(struct ci13xxx *udc) +{ + struct msm_otg *otg; + + if (udc == NULL) + return false; + + if (udc->transceiver == NULL) + return false; + + otg = container_of(udc->transceiver, struct msm_otg, phy); + + return (atomic_read(&otg->in_lpm) != 0); +} + + +static irqreturn_t ci13xxx_msm_resume_irq(int irq, void *data) +{ + struct ci13xxx *udc = _udc; + + if (udc->transceiver && udc->vbus_active && udc->suspended) + usb_phy_set_suspend(udc->transceiver, 0); + else if (!udc->suspended) + ci13xxx_msm_resume(); + + return IRQ_HANDLED; +} + +static struct ci13xxx_udc_driver ci13xxx_msm_udc_driver = { + .name = "ci13xxx_msm", + .flags = CI13XXX_REGS_SHARED | + CI13XXX_REQUIRE_TRANSCEIVER | + CI13XXX_PULLUP_ON_VBUS | + CI13XXX_ZERO_ITC | + CI13XXX_DISABLE_STREAMING, + .nz_itc = 0, + .notify_event = ci13xxx_msm_notify_event, + .in_lpm = ci13xxx_msm_in_lpm, +}; + +static int ci13xxx_msm_install_wake_gpio(struct platform_device *pdev, + struct resource *res) +{ + int wake_irq; + int ret; + struct pinctrl_state *set_state; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + _udc_ctxt.wake_gpio = res->start; + if (_udc_ctxt.ci13xxx_pinctrl) { + set_state = pinctrl_lookup_state(_udc_ctxt.ci13xxx_pinctrl, + "ci13xxx_active"); + if (IS_ERR(set_state)) { + pr_err("cannot get ci13xxx pinctrl active state\n"); + return PTR_ERR(set_state); + } + pinctrl_select_state(_udc_ctxt.ci13xxx_pinctrl, set_state); + } + gpio_request(_udc_ctxt.wake_gpio, "USB_RESUME"); + gpio_direction_input(_udc_ctxt.wake_gpio); + wake_irq = gpio_to_irq(_udc_ctxt.wake_gpio); + if (wake_irq < 0) { + dev_err(&pdev->dev, "could not register USB_RESUME GPIO.\n"); + return -ENXIO; + } + + dev_dbg(&pdev->dev, "_udc_ctxt.gpio_irq = %d and irq = %d\n", + _udc_ctxt.wake_gpio, wake_irq); + ret = request_irq(wake_irq, ci13xxx_msm_resume_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, "usb resume", NULL); + if (ret < 0) { + dev_err(&pdev->dev, "could not register USB_RESUME IRQ.\n"); + goto gpio_free; + } + disable_irq(wake_irq); + _udc_ctxt.wake_irq = wake_irq; + + return 0; + +gpio_free: + gpio_free(_udc_ctxt.wake_gpio); + if (_udc_ctxt.ci13xxx_pinctrl) { + set_state = pinctrl_lookup_state(_udc_ctxt.ci13xxx_pinctrl, + "ci13xxx_sleep"); + if (IS_ERR(set_state)) + pr_err("cannot get ci13xxx pinctrl sleep state\n"); + else + pinctrl_select_state(_udc_ctxt.ci13xxx_pinctrl, + set_state); + } + _udc_ctxt.wake_gpio = 0; + return ret; +} + +static void ci13xxx_msm_uninstall_wake_gpio(struct platform_device *pdev) +{ + struct pinctrl_state *set_state; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + if (_udc_ctxt.wake_gpio) { + gpio_free(_udc_ctxt.wake_gpio); + if (_udc_ctxt.ci13xxx_pinctrl) { + set_state = + pinctrl_lookup_state(_udc_ctxt.ci13xxx_pinctrl, + "ci13xxx_sleep"); + if (IS_ERR(set_state)) + pr_err("cannot get ci13xxx pinctrl sleep state\n"); + else + pinctrl_select_state(_udc_ctxt.ci13xxx_pinctrl, + set_state); + } + _udc_ctxt.wake_gpio = 0; + } +} + +static void enable_usb_irq_timer_func(struct timer_list *t); +static int ci13xxx_msm_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret; + struct ci13xxx_platform_data *pdata = pdev->dev.platform_data; + bool is_l1_supported = false; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + if (pdata) { + /* Acceptable values for nz_itc are: 0,1,2,4,8,16,32,64 */ + if (pdata->log2_itc > CI13XXX_MSM_MAX_LOG2_ITC || + pdata->log2_itc <= 0) + ci13xxx_msm_udc_driver.nz_itc = 0; + else + ci13xxx_msm_udc_driver.nz_itc = + 1 << (pdata->log2_itc-1); + + is_l1_supported = pdata->l1_supported; + /* Set ahb2ahb bypass flag if it is requested. */ + if (pdata->enable_ahb2ahb_bypass) + ci13xxx_msm_udc_driver.flags |= + CI13XXX_ENABLE_AHB2AHB_BYPASS; + + /* Clear disable streaming flag if is requested. */ + if (pdata->enable_streaming) + ci13xxx_msm_udc_driver.flags &= + ~CI13XXX_DISABLE_STREAMING; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get platform resource mem\n"); + return -ENXIO; + } + + _udc_ctxt.regs = ioremap(res->start, resource_size(res)); + if (!_udc_ctxt.regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + return -ENOMEM; + } + + ret = udc_probe(&ci13xxx_msm_udc_driver, &pdev->dev, _udc_ctxt.regs); + if (ret < 0) { + dev_err(&pdev->dev, "udc_probe failed\n"); + goto iounmap; + } + + _udc->gadget.l1_supported = is_l1_supported; + + _udc_ctxt.irq = platform_get_irq(pdev, 0); + if (_udc_ctxt.irq < 0) { + dev_err(&pdev->dev, "IRQ not found\n"); + ret = -ENXIO; + goto udc_remove; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, "USB_RESUME"); + /* Get pinctrl if target uses pinctrl */ + _udc_ctxt.ci13xxx_pinctrl = devm_pinctrl_get(&pdev->dev); + if (IS_ERR(_udc_ctxt.ci13xxx_pinctrl)) { + if (of_property_read_bool(pdev->dev.of_node, "pinctrl-names")) { + dev_err(&pdev->dev, "Error encountered while getting pinctrl\n"); + ret = PTR_ERR(_udc_ctxt.ci13xxx_pinctrl); + goto udc_remove; + } + dev_dbg(&pdev->dev, "Target does not use pinctrl\n"); + _udc_ctxt.ci13xxx_pinctrl = NULL; + } + if (res) { + ret = ci13xxx_msm_install_wake_gpio(pdev, res); + if (ret < 0) { + dev_err(&pdev->dev, "gpio irq install failed\n"); + goto udc_remove; + } + } + + ret = request_irq(_udc_ctxt.irq, msm_udc_irq, IRQF_SHARED, pdev->name, + pdev); + if (ret < 0) { + dev_err(&pdev->dev, "request_irq failed\n"); + goto gpio_uninstall; + } + + timer_setup(&_udc_ctxt.irq_enable_timer, enable_usb_irq_timer_func, 0); + + pm_runtime_no_callbacks(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + return 0; + +gpio_uninstall: + ci13xxx_msm_uninstall_wake_gpio(pdev); +udc_remove: + udc_remove(); +iounmap: + iounmap(_udc_ctxt.regs); + + return ret; +} + +int ci13xxx_msm_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + free_irq(_udc_ctxt.irq, pdev); + ci13xxx_msm_uninstall_wake_gpio(pdev); + udc_remove(); + iounmap(_udc_ctxt.regs); + return 0; +} + +void ci13xxx_msm_shutdown(struct platform_device *pdev) +{ + ci13xxx_pullup(&_udc->gadget, 0); +} + +void msm_hw_soft_reset(void) +{ + struct ci13xxx *udc = _udc; + + hw_device_reset(udc); +} + +void msm_hw_bam_disable(bool bam_disable) +{ + u32 val; + struct ci13xxx *udc = _udc; + + if (bam_disable) + val = readl_relaxed(USB_GENCONFIG) | GENCONFIG_BAM_DISABLE; + else + val = readl_relaxed(USB_GENCONFIG) & ~GENCONFIG_BAM_DISABLE; + + writel_relaxed(val, USB_GENCONFIG); +} + +void msm_usb_irq_disable(bool disable) +{ + struct ci13xxx *udc = _udc; + unsigned long flags; + + spin_lock_irqsave(udc->lock, flags); + + if (_udc_ctxt.irq_disabled == disable) { + pr_debug("Interrupt state already disable = %d\n", disable); + if (disable) + mod_timer(&_udc_ctxt.irq_enable_timer, + IRQ_ENABLE_DELAY); + spin_unlock_irqrestore(udc->lock, flags); + return; + } + + if (disable) { + disable_irq_nosync(_udc_ctxt.irq); + /* start timer here */ + pr_debug("%s: Disabling interrupts\n", __func__); + mod_timer(&_udc_ctxt.irq_enable_timer, IRQ_ENABLE_DELAY); + _udc_ctxt.irq_disabled = true; + + } else { + pr_debug("%s: Enabling interrupts\n", __func__); + del_timer(&_udc_ctxt.irq_enable_timer); + enable_irq(_udc_ctxt.irq); + _udc_ctxt.irq_disabled = false; + } + + spin_unlock_irqrestore(udc->lock, flags); +} + +static void enable_usb_irq_timer_func(struct timer_list *t) +{ + pr_debug("enabling interrupt from timer\n"); + msm_usb_irq_disable(false); +} + +static struct platform_driver ci13xxx_msm_driver = { + .probe = ci13xxx_msm_probe, + .driver = { + .name = "msm_hsusb", + }, + .remove = ci13xxx_msm_remove, + .shutdown = ci13xxx_msm_shutdown, +}; +MODULE_ALIAS("platform:msm_hsusb"); + +static int __init ci13xxx_msm_init(void) +{ + return platform_driver_register(&ci13xxx_msm_driver); +} +module_init(ci13xxx_msm_init); + +static void __exit ci13xxx_msm_exit(void) +{ + platform_driver_unregister(&ci13xxx_msm_driver); +} +module_exit(ci13xxx_msm_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/gadget/ci13xxx_udc.c b/drivers/usb/gadget/ci13xxx_udc.c new file mode 100644 index 000000000000..179e8595d1c5 --- /dev/null +++ b/drivers/usb/gadget/ci13xxx_udc.c @@ -0,0 +1,4115 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ci13xxx_udc.c - MIPS USB IP core family device controller + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * Description: MIPS USB IP core family device controller + * Currently it only supports IP part number CI13412 + * + * This driver is composed of several blocks: + * - HW: hardware interface + * - DBG: debug facilities (optional) + * - UTIL: utilities + * - ISR: interrupts handling + * - ENDPT: endpoint operations (Gadget API) + * - GADGET: gadget operations (Gadget API) + * - BUS: bus glue code, bus abstraction layer + * + * Compile Options + * - CONFIG_USB_GADGET_DEBUG_FILES: enable debug facilities + * - STALL_IN: non-empty bulk-in pipes cannot be halted + * if defined mass storage compliance succeeds but with warnings + * => case 4: Hi > Dn + * => case 5: Hi > Di + * => case 8: Hi <> Do + * if undefined usbtest 13 fails + * - TRACE: enable function tracing (depends on DEBUG) + * + * Main Features + * - Chapter 9 & Mass Storage Compliance with Gadget File Storage + * - Chapter 9 Compliance with Gadget Zero (STALL_IN undefined) + * - Normal & LPM support + * + * USBTEST Report + * - OK: 0-12, 13 (STALL_IN defined) & 14 + * - Not Supported: 15 & 16 (ISO) + * + * TODO List + * - OTG + * - Isochronous & Interrupt Traffic + * - Handle requests which spawns into several TDs + * - GET_STATUS(device) - always reports 0 + * - Gadget API (majority of optional features) + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci13xxx_udc.h" + +/****************************************************************************** + * DEFINE + *****************************************************************************/ + +#define USB_MAX_TIMEOUT 25 /* 25msec timeout */ +#define EP_PRIME_CHECK_DELAY (jiffies + msecs_to_jiffies(1000)) +#define MAX_PRIME_CHECK_RETRY 3 /*Wait for 3sec for EP prime failure */ +#define EXTRA_ALLOCATION_SIZE 256 + +/* ctrl register bank access */ +static DEFINE_SPINLOCK(udc_lock); + +/* control endpoint description */ +static const struct usb_endpoint_descriptor +ctrl_endpt_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(CTRL_PAYLOAD_MAX), +}; + +static const struct usb_endpoint_descriptor +ctrl_endpt_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(CTRL_PAYLOAD_MAX), +}; + +/* UDC descriptor */ +static struct ci13xxx *_udc; + +/* Interrupt statistics */ +#define ISR_MASK 0x1F +static struct { + u32 test; + u32 ui; + u32 uei; + u32 pci; + u32 uri; + u32 sli; + u32 none; + struct { + u32 cnt; + u32 buf[ISR_MASK+1]; + u32 idx; + } hndl; +} isr_statistics; + +/** + * ffs_nr: find first (least significant) bit set + * @x: the word to search + * + * This function returns bit number (instead of position) + */ +static int ffs_nr(u32 x) +{ + int n = ffs(x); + + return n ? n-1 : 32; +} + +/****************************************************************************** + * HW block + *****************************************************************************/ +/* register bank descriptor */ +static struct { + unsigned int lpm; /* is LPM? */ + void __iomem *abs; /* bus map offset */ + void __iomem *cap; /* bus map offset + CAP offset + CAP data */ + size_t size; /* bank size */ +} hw_bank; + +/* MSM specific */ +#define ABS_AHBBURST (0x0090UL) +#define ABS_AHBMODE (0x0098UL) +/* UDC register map */ +#define ABS_CAPLENGTH (0x100UL) +#define ABS_HCCPARAMS (0x108UL) +#define ABS_DCCPARAMS (0x124UL) +#define ABS_TESTMODE (hw_bank.lpm ? 0x0FCUL : 0x138UL) +/* offset to CAPLENTGH (addr + data) */ +#define CAP_USBCMD (0x000UL) +#define CAP_USBSTS (0x004UL) +#define CAP_USBINTR (0x008UL) +#define CAP_DEVICEADDR (0x014UL) +#define CAP_ENDPTLISTADDR (0x018UL) +#define CAP_PORTSC (0x044UL) +#define CAP_DEVLC (0x084UL) +#define CAP_ENDPTPIPEID (0x0BCUL) +#define CAP_USBMODE (hw_bank.lpm ? 0x0C8UL : 0x068UL) +#define CAP_ENDPTSETUPSTAT (hw_bank.lpm ? 0x0D8UL : 0x06CUL) +#define CAP_ENDPTPRIME (hw_bank.lpm ? 0x0DCUL : 0x070UL) +#define CAP_ENDPTFLUSH (hw_bank.lpm ? 0x0E0UL : 0x074UL) +#define CAP_ENDPTSTAT (hw_bank.lpm ? 0x0E4UL : 0x078UL) +#define CAP_ENDPTCOMPLETE (hw_bank.lpm ? 0x0E8UL : 0x07CUL) +#define CAP_ENDPTCTRL (hw_bank.lpm ? 0x0ECUL : 0x080UL) +#define CAP_LAST (hw_bank.lpm ? 0x12CUL : 0x0C0UL) + +#define REMOTE_WAKEUP_DELAY msecs_to_jiffies(200) + +/* maximum number of enpoints: valid only after hw_device_reset() */ +static unsigned int hw_ep_max; +static void dbg_usb_op_fail(u8 addr, const char *name, + const struct ci13xxx_ep *mep); +/** + * hw_ep_bit: calculates the bit number + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns bit number + */ +static inline int hw_ep_bit(int num, int dir) +{ + return num + (dir ? 16 : 0); +} + +static int ep_to_bit(int n) +{ + int fill = 16 - hw_ep_max / 2; + + if (n >= hw_ep_max / 2) + n += fill; + + return n; +} + +/** + * hw_aread: reads from register bitfield + * @addr: address relative to bus map + * @mask: bitfield mask + * + * This function returns register bitfield data + */ +static u32 hw_aread(u32 addr, u32 mask) +{ + return ioread32(addr + hw_bank.abs) & mask; +} + +/** + * hw_awrite: writes to register bitfield + * @addr: address relative to bus map + * @mask: bitfield mask + * @data: new data + */ +static void hw_awrite(u32 addr, u32 mask, u32 data) +{ + iowrite32(hw_aread(addr, ~mask) | (data & mask), + addr + hw_bank.abs); +} + +/** + * hw_cread: reads from register bitfield + * @addr: address relative to CAP offset plus content + * @mask: bitfield mask + * + * This function returns register bitfield data + */ +static u32 hw_cread(u32 addr, u32 mask) +{ + return ioread32(addr + hw_bank.cap) & mask; +} + +/** + * hw_cwrite: writes to register bitfield + * @addr: address relative to CAP offset plus content + * @mask: bitfield mask + * @data: new data + */ +static void hw_cwrite(u32 addr, u32 mask, u32 data) +{ + iowrite32(hw_cread(addr, ~mask) | (data & mask), + addr + hw_bank.cap); +} + +/** + * hw_ctest_and_clear: tests & clears register bitfield + * @addr: address relative to CAP offset plus content + * @mask: bitfield mask + * + * This function returns register bitfield data + */ +static u32 hw_ctest_and_clear(u32 addr, u32 mask) +{ + u32 reg = hw_cread(addr, mask); + + iowrite32(reg, addr + hw_bank.cap); + return reg; +} + +/** + * hw_ctest_and_write: tests & writes register bitfield + * @addr: address relative to CAP offset plus content + * @mask: bitfield mask + * @data: new data + * + * This function returns register bitfield data + */ +static u32 hw_ctest_and_write(u32 addr, u32 mask, u32 data) +{ + u32 reg = hw_cread(addr, ~0); + + iowrite32((reg & ~mask) | (data & mask), addr + hw_bank.cap); + return (reg & mask) >> ffs_nr(mask); +} + +static int hw_device_init(void __iomem *base) +{ + u32 reg; + + /* bank is a module variable */ + hw_bank.abs = base; + + hw_bank.cap = hw_bank.abs; + hw_bank.cap += ABS_CAPLENGTH; + hw_bank.cap += ioread8(hw_bank.cap); + + reg = hw_aread(ABS_HCCPARAMS, HCCPARAMS_LEN) >> ffs_nr(HCCPARAMS_LEN); + hw_bank.lpm = reg; + hw_bank.size = hw_bank.cap - hw_bank.abs; + hw_bank.size += CAP_LAST; + hw_bank.size /= sizeof(u32); + + reg = hw_aread(ABS_DCCPARAMS, DCCPARAMS_DEN) >> ffs_nr(DCCPARAMS_DEN); + hw_ep_max = reg * 2; /* cache hw ENDPT_MAX */ + + if (hw_ep_max == 0 || hw_ep_max > ENDPT_MAX) + return -ENODEV; + + /* setup lock mode ? */ + + /* ENDPTSETUPSTAT is '0' by default */ + + /* HCSPARAMS.bf.ppc SHOULD BE zero for device */ + + return 0; +} +/** + * hw_device_reset: resets chip (execute without interruption) + * @base: register base address + * + * This function returns an error code + */ +static int hw_device_reset(struct ci13xxx *udc) +{ + int delay_count = 25; /* 250 usec */ + + /* should flush & stop before reset */ + hw_cwrite(CAP_ENDPTFLUSH, ~0, ~0); + hw_cwrite(CAP_USBCMD, USBCMD_RS, 0); + + hw_cwrite(CAP_USBCMD, USBCMD_RST, USBCMD_RST); + while (delay_count-- && hw_cread(CAP_USBCMD, USBCMD_RST)) + udelay(10); + if (delay_count < 0) + pr_err("USB controller reset failed\n"); + + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_RESET_EVENT); + + /* USBMODE should be configured step by step */ + hw_cwrite(CAP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE); + hw_cwrite(CAP_USBMODE, USBMODE_CM, USBMODE_CM_DEVICE); + hw_cwrite(CAP_USBMODE, USBMODE_SLOM, USBMODE_SLOM); /* HW >= 2.3 */ + + /* + * ITC (Interrupt Threshold Control) field is to set the maximum + * rate at which the device controller will issue interrupts. + * The maximum interrupt interval measured in micro frames. + * Valid values are 0, 1, 2, 4, 8, 16, 32, 64. The default value is + * 8 micro frames. If CPU can handle interrupts at faster rate, ITC + * can be set to lesser value to gain performance. + */ + if (udc->udc_driver->nz_itc) + hw_cwrite(CAP_USBCMD, USBCMD_ITC_MASK, + USBCMD_ITC(udc->udc_driver->nz_itc)); + else if (udc->udc_driver->flags & CI13XXX_ZERO_ITC) + hw_cwrite(CAP_USBCMD, USBCMD_ITC_MASK, USBCMD_ITC(0)); + + if (hw_cread(CAP_USBMODE, USBMODE_CM) != USBMODE_CM_DEVICE) { + pr_err("cannot enter in device mode\n"); + pr_err("lpm = %i\n", hw_bank.lpm); + return -ENODEV; + } + + return 0; +} + +/** + * hw_device_state: enables/disables interrupts & starts/stops device (execute + * without interruption) + * @dma: 0 => disable, !0 => enable and set dma engine + * + * This function returns an error code + */ +static int hw_device_state(u32 dma) +{ + struct ci13xxx *udc = _udc; + + if (dma) { + if (!(udc->udc_driver->flags & CI13XXX_DISABLE_STREAMING)) { + hw_cwrite(CAP_USBMODE, USBMODE_SDIS, 0); + pr_debug("%s(): streaming mode is enabled. USBMODE:%x\n", + __func__, hw_cread(CAP_USBMODE, ~0)); + + } else { + hw_cwrite(CAP_USBMODE, USBMODE_SDIS, USBMODE_SDIS); + pr_debug("%s(): streaming mode is disabled. USBMODE:%x\n", + __func__, hw_cread(CAP_USBMODE, ~0)); + } + + hw_cwrite(CAP_ENDPTLISTADDR, ~0, dma); + + + /* Set BIT(31) to enable AHB2AHB Bypass functionality */ + if (udc->udc_driver->flags & CI13XXX_ENABLE_AHB2AHB_BYPASS) { + hw_awrite(ABS_AHBMODE, AHB2AHB_BYPASS, AHB2AHB_BYPASS); + pr_debug("%s(): ByPass Mode is enabled. AHBMODE:%x\n", + __func__, hw_aread(ABS_AHBMODE, ~0)); + } + + /* interrupt, error, port change, reset, sleep/suspend */ + hw_cwrite(CAP_USBINTR, ~0, + USBi_UI|USBi_UEI|USBi_PCI|USBi_URI|USBi_SLI); + hw_cwrite(CAP_USBCMD, USBCMD_RS, USBCMD_RS); + udc->transceiver->flags |= EUD_SPOOF_CONNECT; + } else { + udc->transceiver->flags &= ~EUD_SPOOF_CONNECT; + hw_cwrite(CAP_USBCMD, USBCMD_RS, 0); + hw_cwrite(CAP_USBINTR, ~0, 0); + /* Clear BIT(31) to disable AHB2AHB Bypass functionality */ + if (udc->udc_driver->flags & CI13XXX_ENABLE_AHB2AHB_BYPASS) { + hw_awrite(ABS_AHBMODE, AHB2AHB_BYPASS, 0); + pr_debug("%s(): ByPass Mode is disabled. AHBMODE:%x\n", + __func__, hw_aread(ABS_AHBMODE, ~0)); + } + } + return 0; +} + +static void debug_ept_flush_info(int ep_num, int dir) +{ + struct ci13xxx *udc = _udc; + struct ci13xxx_ep *mep; + + if (dir) + mep = &udc->ci13xxx_ep[ep_num + hw_ep_max/2]; + else + mep = &udc->ci13xxx_ep[ep_num]; + + pr_err_ratelimited("USB Registers\n"); + pr_err_ratelimited("USBCMD:%x\n", hw_cread(CAP_USBCMD, ~0)); + pr_err_ratelimited("USBSTS:%x\n", hw_cread(CAP_USBSTS, ~0)); + pr_err_ratelimited("ENDPTLISTADDR:%x\n", + hw_cread(CAP_ENDPTLISTADDR, ~0)); + pr_err_ratelimited("PORTSC:%x\n", hw_cread(CAP_PORTSC, ~0)); + pr_err_ratelimited("USBMODE:%x\n", hw_cread(CAP_USBMODE, ~0)); + pr_err_ratelimited("ENDPTSTAT:%x\n", hw_cread(CAP_ENDPTSTAT, ~0)); + + dbg_usb_op_fail(0xFF, "FLUSHF", mep); +} +/** + * hw_ep_flush: flush endpoint fifo (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns an error code + */ +static int hw_ep_flush(int num, int dir) +{ + ktime_t start, diff; + int n = hw_ep_bit(num, dir); + struct ci13xxx_ep *mEp = &_udc->ci13xxx_ep[n]; + + /* Flush ep0 even when queue is empty */ + if (_udc->skip_flush || (num && list_empty(&mEp->qh.queue))) + return 0; + + start = ktime_get(); + do { + /* flush any pending transfer */ + hw_cwrite(CAP_ENDPTFLUSH, BIT(n), BIT(n)); + while (hw_cread(CAP_ENDPTFLUSH, BIT(n))) { + cpu_relax(); + diff = ktime_sub(ktime_get(), start); + if (ktime_to_ms(diff) > USB_MAX_TIMEOUT) { + printk_ratelimited(KERN_ERR + "%s: Failed to flush ep#%d %s\n", + __func__, num, + dir ? "IN" : "OUT"); + debug_ept_flush_info(num, dir); + _udc->skip_flush = true; + /* Notify to trigger h/w reset recovery later */ + if (_udc->udc_driver->notify_event) + _udc->udc_driver->notify_event(_udc, + CI13XXX_CONTROLLER_ERROR_EVENT); + return 0; + } + } + } while (hw_cread(CAP_ENDPTSTAT, BIT(n))); + + return 0; +} + +/** + * hw_ep_disable: disables endpoint (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns an error code + */ +static int hw_ep_disable(int num, int dir) +{ + hw_cwrite(CAP_ENDPTCTRL + num * sizeof(u32), + dir ? ENDPTCTRL_TXE : ENDPTCTRL_RXE, 0); + return 0; +} + +/** + * hw_ep_enable: enables endpoint (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * @type: endpoint type + * + * This function returns an error code + */ +static int hw_ep_enable(int num, int dir, int type) +{ + u32 mask, data; + + if (dir) { + mask = ENDPTCTRL_TXT; /* type */ + data = type << ffs_nr(mask); + + mask |= ENDPTCTRL_TXS; /* unstall */ + mask |= ENDPTCTRL_TXR; /* reset data toggle */ + data |= ENDPTCTRL_TXR; + mask |= ENDPTCTRL_TXE; /* enable */ + data |= ENDPTCTRL_TXE; + } else { + mask = ENDPTCTRL_RXT; /* type */ + data = type << ffs_nr(mask); + + mask |= ENDPTCTRL_RXS; /* unstall */ + mask |= ENDPTCTRL_RXR; /* reset data toggle */ + data |= ENDPTCTRL_RXR; + mask |= ENDPTCTRL_RXE; /* enable */ + data |= ENDPTCTRL_RXE; + } + hw_cwrite(CAP_ENDPTCTRL + num * sizeof(u32), mask, data); + + /* make sure endpoint is enabled before returning */ + mb(); + + return 0; +} + +/** + * hw_ep_get_halt: return endpoint halt status + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns 1 if endpoint halted + */ +static int hw_ep_get_halt(int num, int dir) +{ + u32 mask = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; + + return hw_cread(CAP_ENDPTCTRL + num * sizeof(u32), mask) ? 1 : 0; +} + +/** + * hw_test_and_clear_setup_status: test & clear setup status (execute without + * interruption) + * @n: endpoint number + * + * This function returns setup status + */ +static int hw_test_and_clear_setup_status(int n) +{ + n = ep_to_bit(n); + return hw_ctest_and_clear(CAP_ENDPTSETUPSTAT, BIT(n)); +} + +/** + * hw_ep_prime: primes endpoint (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * @is_ctrl: true if control endpoint + * + * This function returns an error code + */ +static int hw_ep_prime(int num, int dir, int is_ctrl) +{ + int n = hw_ep_bit(num, dir); + + if (is_ctrl && dir == RX && hw_cread(CAP_ENDPTSETUPSTAT, BIT(num))) + return -EAGAIN; + + hw_cwrite(CAP_ENDPTPRIME, BIT(n), BIT(n)); + + if (is_ctrl && dir == RX && hw_cread(CAP_ENDPTSETUPSTAT, BIT(num))) + return -EAGAIN; + + /* status shoult be tested according with manual but it doesn't work */ + return 0; +} + +/** + * hw_ep_set_halt: configures ep halt & resets data toggle after clear (execute + * without interruption) + * @num: endpoint number + * @dir: endpoint direction + * @value: true => stall, false => unstall + * + * This function returns an error code + */ +static int hw_ep_set_halt(int num, int dir, int value) +{ + u32 addr, mask_xs, mask_xr; + + if (value != 0 && value != 1) + return -EINVAL; + + do { + if (hw_cread(CAP_ENDPTSETUPSTAT, BIT(num))) + return 0; + + addr = CAP_ENDPTCTRL + num * sizeof(u32); + mask_xs = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; + mask_xr = dir ? ENDPTCTRL_TXR : ENDPTCTRL_RXR; + + /* data toggle - reserved for EP0 but it's in ESS */ + hw_cwrite(addr, mask_xs|mask_xr, value ? mask_xs : mask_xr); + + } while (value != hw_ep_get_halt(num, dir)); + + return 0; +} + +/** + * hw_intr_clear: disables interrupt & clears interrupt status (execute without + * interruption) + * @n: interrupt bit + * + * This function returns an error code + */ +static int hw_intr_clear(int n) +{ + if (n >= REG_BITS) + return -EINVAL; + + hw_cwrite(CAP_USBINTR, BIT(n), 0); + hw_cwrite(CAP_USBSTS, BIT(n), BIT(n)); + return 0; +} + +/** + * hw_intr_force: enables interrupt & forces interrupt status (execute without + * interruption) + * @n: interrupt bit + * + * This function returns an error code + */ +static int hw_intr_force(int n) +{ + if (n >= REG_BITS) + return -EINVAL; + + hw_awrite(ABS_TESTMODE, TESTMODE_FORCE, TESTMODE_FORCE); + hw_cwrite(CAP_USBINTR, BIT(n), BIT(n)); + hw_cwrite(CAP_USBSTS, BIT(n), BIT(n)); + hw_awrite(ABS_TESTMODE, TESTMODE_FORCE, 0); + return 0; +} + +/** + * hw_is_port_high_speed: test if port is high speed + * + * This function returns true if high speed port + */ +static int hw_port_is_high_speed(void) +{ + return hw_bank.lpm ? hw_cread(CAP_DEVLC, DEVLC_PSPD) : + hw_cread(CAP_PORTSC, PORTSC_HSP); +} + +/** + * hw_port_test_get: reads port test mode value + * + * This function returns port test mode value + */ +static u8 hw_port_test_get(void) +{ + return hw_cread(CAP_PORTSC, PORTSC_PTC) >> ffs_nr(PORTSC_PTC); +} + +/** + * hw_port_test_set: writes port test mode (execute without interruption) + * @mode: new value + * + * This function returns an error code + */ +static int hw_port_test_set(u8 mode) +{ + const u8 TEST_MODE_MAX = 7; + + if (mode > TEST_MODE_MAX) + return -EINVAL; + + hw_cwrite(CAP_PORTSC, PORTSC_PTC, mode << ffs_nr(PORTSC_PTC)); + return 0; +} + +/** + * hw_read_intr_enable: returns interrupt enable register + * + * This function returns register data + */ +static u32 hw_read_intr_enable(void) +{ + return hw_cread(CAP_USBINTR, ~0); +} + +/** + * hw_read_intr_status: returns interrupt status register + * + * This function returns register data + */ +static u32 hw_read_intr_status(void) +{ + return hw_cread(CAP_USBSTS, ~0); +} + +/** + * hw_register_read: reads all device registers (execute without interruption) + * @buf: destination buffer + * @size: buffer size + * + * This function returns number of registers read + */ +static size_t hw_register_read(u32 *buf, size_t size) +{ + unsigned int i; + + if (size > hw_bank.size) + size = hw_bank.size; + + for (i = 0; i < size; i++) + buf[i] = hw_aread(i * sizeof(u32), ~0); + + return size; +} + +/** + * hw_register_write: writes to register + * @addr: register address + * @data: register value + * + * This function returns an error code + */ +static int hw_register_write(u16 addr, u32 data) +{ + /* align */ + addr /= sizeof(u32); + + if (addr >= hw_bank.size) + return -EINVAL; + + /* align */ + addr *= sizeof(u32); + + hw_awrite(addr, ~0, data); + return 0; +} + +/** + * hw_test_and_clear_complete: test & clear complete status (execute without + * interruption) + * @n: endpoint number + * + * This function returns complete status + */ +static int hw_test_and_clear_complete(int n) +{ + n = ep_to_bit(n); + return hw_ctest_and_clear(CAP_ENDPTCOMPLETE, BIT(n)); +} + +/** + * hw_test_and_clear_intr_active: test & clear active interrupts (execute + * without interruption) + * + * This function returns active interrutps + */ +static u32 hw_test_and_clear_intr_active(void) +{ + u32 reg = hw_read_intr_status() & hw_read_intr_enable(); + + hw_cwrite(CAP_USBSTS, ~0, reg); + return reg; +} + +/** + * hw_test_and_clear_setup_guard: test & clear setup guard (execute without + * interruption) + * + * This function returns guard value + */ +static int hw_test_and_clear_setup_guard(void) +{ + return hw_ctest_and_write(CAP_USBCMD, USBCMD_SUTW, 0); +} + +/** + * hw_test_and_set_setup_guard: test & set setup guard (execute without + * interruption) + * + * This function returns guard value + */ +static int hw_test_and_set_setup_guard(void) +{ + return hw_ctest_and_write(CAP_USBCMD, USBCMD_SUTW, USBCMD_SUTW); +} + +/** + * hw_usb_set_address: configures USB address (execute without interruption) + * @value: new USB address + * + * This function returns an error code + */ +static int hw_usb_set_address(u8 value) +{ + /* advance */ + hw_cwrite(CAP_DEVICEADDR, DEVICEADDR_USBADR | DEVICEADDR_USBADRA, + value << ffs_nr(DEVICEADDR_USBADR) | DEVICEADDR_USBADRA); + return 0; +} + +/** + * hw_usb_reset: restart device after a bus reset (execute without + * interruption) + * + * This function returns an error code + */ +static int hw_usb_reset(void) +{ + int delay_count = 10; /* 100 usec delay */ + + hw_usb_set_address(0); + + /* ESS flushes only at end?!? */ + hw_cwrite(CAP_ENDPTFLUSH, ~0, ~0); /* flush all EPs */ + + /* clear complete status */ + hw_cwrite(CAP_ENDPTCOMPLETE, 0, 0); /* writes its content */ + + /* wait until all bits cleared */ + while (delay_count-- && hw_cread(CAP_ENDPTPRIME, ~0)) + udelay(10); + if (delay_count < 0) + pr_err("ENDPTPRIME is not cleared during bus reset\n"); + + /* reset all endpoints ? */ + + /* + * reset internal status and wait for further instructions + * no need to verify the port reset status (ESS does it) + */ + + return 0; +} + +static ssize_t device_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return 0; +} + +/****************************************************************************** + * DBG block + *****************************************************************************/ +/** + * show_device: prints information about device capabilities and status + * + * Check "device.h" for details + */ +static ssize_t device_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + struct usb_gadget *gadget = &udc->gadget; + int n = 0; + + dbg_trace("[%s] %pK\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + n += scnprintf(buf + n, PAGE_SIZE - n, "speed = %d\n", + gadget->speed); + n += scnprintf(buf + n, PAGE_SIZE - n, "max_speed = %d\n", + gadget->max_speed); + /* TODO: Scheduled for removal in 3.8. */ + n += scnprintf(buf + n, PAGE_SIZE - n, "is_dualspeed = %d\n", + gadget_is_dualspeed(gadget)); + n += scnprintf(buf + n, PAGE_SIZE - n, "is_otg = %d\n", + gadget->is_otg); + n += scnprintf(buf + n, PAGE_SIZE - n, "is_a_peripheral = %d\n", + gadget->is_a_peripheral); + n += scnprintf(buf + n, PAGE_SIZE - n, "b_hnp_enable = %d\n", + gadget->b_hnp_enable); + n += scnprintf(buf + n, PAGE_SIZE - n, "a_hnp_support = %d\n", + gadget->a_hnp_support); + n += scnprintf(buf + n, PAGE_SIZE - n, "a_alt_hnp_support = %d\n", + gadget->a_alt_hnp_support); + n += scnprintf(buf + n, PAGE_SIZE - n, "name = %s\n", + (gadget->name ? gadget->name : "")); + + return n; +} +static DEVICE_ATTR_RW(device); + +static ssize_t driver_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return 0; +} + +/** + * show_driver: prints information about attached gadget (if any) + * + * Check "device.h" for details + */ +static ssize_t driver_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + struct usb_gadget_driver *driver = udc->driver; + int n = 0; + + dbg_trace("[%s] %pK\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + if (driver == NULL) + return scnprintf(buf, PAGE_SIZE, + "There is no gadget attached!\n"); + + n += scnprintf(buf + n, PAGE_SIZE - n, "function = %s\n", + (driver->function ? driver->function : "")); + n += scnprintf(buf + n, PAGE_SIZE - n, "max speed = %d\n", + driver->max_speed); + + return n; +} +static DEVICE_ATTR_RW(driver); + +/* Maximum event message length */ +#define DBG_DATA_MSG 64UL + +/* Maximum event messages */ +#define DBG_DATA_MAX 128UL + +/* Event buffer descriptor */ +static struct { + char (buf[DBG_DATA_MAX])[DBG_DATA_MSG]; /* buffer */ + unsigned int idx; /* index */ + unsigned int tty; /* print to console? */ + rwlock_t lck; /* lock */ +} dbg_data = { + .idx = 0, + .tty = 0, + .lck = __RW_LOCK_UNLOCKED(lck) +}; + +/** + * dbg_dec: decrements debug event index + * @idx: buffer index + */ +static void dbg_dec(unsigned int *idx) +{ + *idx = (*idx - 1) & (DBG_DATA_MAX-1); +} + +/** + * dbg_inc: increments debug event index + * @idx: buffer index + */ +static void dbg_inc(unsigned int *idx) +{ + *idx = (*idx + 1) & (DBG_DATA_MAX-1); +} + + +static unsigned int ep_addr_txdbg_mask; +static ssize_t ep_addr_txdbg_mask_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%u\n", ep_addr_txdbg_mask); +} + +static ssize_t ep_addr_txdbg_mask_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + ep_addr_txdbg_mask = val; + + return count; +} +static DEVICE_ATTR_RW(ep_addr_txdbg_mask_enable); + +static unsigned int ep_addr_rxdbg_mask; +static ssize_t ep_addr_rxdbg_mask_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%u\n", ep_addr_rxdbg_mask); +} + +static ssize_t ep_addr_rxdbg_mask_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + ep_addr_rxdbg_mask = val; + + return count; +} +static DEVICE_ATTR_RW(ep_addr_rxdbg_mask_enable); + +static int allow_dbg_print(u8 addr) +{ + int dir, num; + + /* allow bus wide events */ + if (addr == 0xff) + return 1; + + dir = addr & USB_ENDPOINT_DIR_MASK ? TX : RX; + num = addr & ~USB_ENDPOINT_DIR_MASK; + num = 1 << num; + + if ((dir == TX) && (num & ep_addr_txdbg_mask)) + return 1; + if ((dir == RX) && (num & ep_addr_rxdbg_mask)) + return 1; + + return 0; +} + +#define TIME_BUF_LEN 20 +/*get_timestamp - returns time of day in us */ +static char *get_timestamp(char *tbuf) +{ + unsigned long long t; + unsigned long nanosec_rem; + + t = cpu_clock(smp_processor_id()); + nanosec_rem = do_div(t, 1000000000)/1000; + scnprintf(tbuf, TIME_BUF_LEN, "[%5lu.%06lu] ", (unsigned long)t, + nanosec_rem); + return tbuf; +} + +/** + * dbg_print: prints the common part of the event + * @addr: endpoint address + * @name: event name + * @status: status + * @extra: extra information + */ +static void dbg_print(u8 addr, const char *name, int status, const char *extra) +{ + unsigned long flags; + char tbuf[TIME_BUF_LEN]; + + if (!allow_dbg_print(addr)) + return; + + write_lock_irqsave(&dbg_data.lck, flags); + + scnprintf(dbg_data.buf[dbg_data.idx], DBG_DATA_MSG, + "%s\t? %02X %-7.7s %4i ?\t%s\n", + get_timestamp(tbuf), addr, name, status, extra); + + dbg_inc(&dbg_data.idx); + + write_unlock_irqrestore(&dbg_data.lck, flags); + + if (dbg_data.tty != 0) + pr_notice("%s\t? %02X %-7.7s %4i ?\t%s\n", + get_timestamp(tbuf), addr, name, status, extra); +} + +/** + * dbg_done: prints a DONE event + * @addr: endpoint address + * @td: transfer descriptor + * @status: status + */ +static void dbg_done(u8 addr, const u32 token, int status) +{ + char msg[DBG_DATA_MSG]; + + scnprintf(msg, sizeof(msg), "%d %02X", + (int)(token & TD_TOTAL_BYTES) >> ffs_nr(TD_TOTAL_BYTES), + (int)(token & TD_STATUS) >> ffs_nr(TD_STATUS)); + dbg_print(addr, "DONE", status, msg); +} + +/** + * dbg_event: prints a generic event + * @addr: endpoint address + * @name: event name + * @status: status + */ +static void dbg_event(u8 addr, const char *name, int status) +{ + if (name != NULL) + dbg_print(addr, name, status, ""); +} + +/* + * dbg_queue: prints a QUEUE event + * @addr: endpoint address + * @req: USB request + * @status: status + */ +static void dbg_queue(u8 addr, const struct usb_request *req, int status) +{ + char msg[DBG_DATA_MSG]; + + if (req != NULL) { + scnprintf(msg, sizeof(msg), + "%d %d", !req->no_interrupt, req->length); + dbg_print(addr, "QUEUE", status, msg); + } +} + +/** + * dbg_setup: prints a SETUP event + * @addr: endpoint address + * @req: setup request + */ +static void dbg_setup(u8 addr, const struct usb_ctrlrequest *req) +{ + char msg[DBG_DATA_MSG]; + + if (req != NULL) { + scnprintf(msg, sizeof(msg), + "%02X %02X %04X %04X %d", req->bRequestType, + req->bRequest, le16_to_cpu(req->wValue), + le16_to_cpu(req->wIndex), le16_to_cpu(req->wLength)); + dbg_print(addr, "SETUP", 0, msg); + } +} + +/** + * dbg_usb_op_fail: prints USB Operation FAIL event + * @addr: endpoint address + * @mEp: endpoint structure + */ +static void dbg_usb_op_fail(u8 addr, const char *name, + const struct ci13xxx_ep *mep) +{ + char msg[DBG_DATA_MSG]; + struct ci13xxx_req *req; + struct list_head *ptr = NULL; + + if (mep != NULL) { + scnprintf(msg, sizeof(msg), + "%s Fail EP%d%s QH:%08X", + name, mep->num, + mep->dir ? "IN" : "OUT", mep->qh.ptr->cap); + dbg_print(addr, name, 0, msg); + scnprintf(msg, sizeof(msg), + "cap:%08X %08X %08X\n", + mep->qh.ptr->curr, mep->qh.ptr->td.next, + mep->qh.ptr->td.token); + dbg_print(addr, "QHEAD", 0, msg); + + list_for_each(ptr, &mep->qh.queue) { + req = list_entry(ptr, struct ci13xxx_req, queue); + scnprintf(msg, sizeof(msg), + "%pKa:%08X:%08X\n", + &req->dma, req->ptr->next, + req->ptr->token); + dbg_print(addr, "REQ", 0, msg); + scnprintf(msg, sizeof(msg), "%08X:%d\n", + req->ptr->page[0], + req->req.status); + dbg_print(addr, "REQPAGE", 0, msg); + } + } +} + +/** + * show_events: displays the event buffer + * + * Check "device.h" for details + */ +static ssize_t events_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + unsigned long flags; + unsigned int i, j, n = 0; + + dbg_trace("[%s] %pK\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + read_lock_irqsave(&dbg_data.lck, flags); + + i = dbg_data.idx; + for (dbg_dec(&i); i != dbg_data.idx; dbg_dec(&i)) { + n += strlen(dbg_data.buf[i]); + if (n >= PAGE_SIZE) { + n -= strlen(dbg_data.buf[i]); + break; + } + } + for (j = 0, dbg_inc(&i); j < n; dbg_inc(&i)) + j += scnprintf(buf + j, PAGE_SIZE - j, + "%s", dbg_data.buf[i]); + + read_unlock_irqrestore(&dbg_data.lck, flags); + + return n; +} + +/** + * store_events: configure if events are going to be also printed to console + * + * Check "device.h" for details + */ +static ssize_t events_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int tty; + + dbg_trace("[%s] %pK, %d\n", __func__, buf, count); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + goto done; + } + + if (kstrtouint(buf, 10, &tty) || tty > 1) { + dev_err(dev, "<1|0>: enable|disable console log\n"); + goto done; + } + + dbg_data.tty = tty; + dev_info(dev, "tty = %u\n", dbg_data.tty); + + done: + return count; +} +static DEVICE_ATTR_RW(events); + +/** + * show_inters: interrupt status, enable status and historic + * + * Check "device.h" for details + */ +static ssize_t inters_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + u32 intr; + unsigned int i, j, n = 0; + + dbg_trace("[%s] %pK\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + spin_lock_irqsave(udc->lock, flags); + + n += scnprintf(buf + n, PAGE_SIZE - n, + "status = %08x\n", hw_read_intr_status()); + n += scnprintf(buf + n, PAGE_SIZE - n, + "enable = %08x\n", hw_read_intr_enable()); + + n += scnprintf(buf + n, PAGE_SIZE - n, "*test = %d\n", + isr_statistics.test); + n += scnprintf(buf + n, PAGE_SIZE - n, "? ui = %d\n", + isr_statistics.ui); + n += scnprintf(buf + n, PAGE_SIZE - n, "? uei = %d\n", + isr_statistics.uei); + n += scnprintf(buf + n, PAGE_SIZE - n, "? pci = %d\n", + isr_statistics.pci); + n += scnprintf(buf + n, PAGE_SIZE - n, "? uri = %d\n", + isr_statistics.uri); + n += scnprintf(buf + n, PAGE_SIZE - n, "? sli = %d\n", + isr_statistics.sli); + n += scnprintf(buf + n, PAGE_SIZE - n, "*none = %d\n", + isr_statistics.none); + n += scnprintf(buf + n, PAGE_SIZE - n, "*hndl = %d\n", + isr_statistics.hndl.cnt); + + for (i = isr_statistics.hndl.idx, j = 0; j <= ISR_MASK; j++, i++) { + i &= ISR_MASK; + intr = isr_statistics.hndl.buf[i]; + + if (USBi_UI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "ui "); + intr &= ~USBi_UI; + if (USBi_UEI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "uei "); + intr &= ~USBi_UEI; + if (USBi_PCI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "pci "); + intr &= ~USBi_PCI; + if (USBi_URI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "uri "); + intr &= ~USBi_URI; + if (USBi_SLI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "sli "); + intr &= ~USBi_SLI; + if (intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "??? "); + if (isr_statistics.hndl.buf[i]) + n += scnprintf(buf + n, PAGE_SIZE - n, "\n"); + } + + spin_unlock_irqrestore(udc->lock, flags); + + return n; +} + +/** + * store_inters: enable & force or disable an individual interrutps + * (to be used for test purposes only) + * + * Check "device.h" for details + */ +static ssize_t inters_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + unsigned int en, bit; + + dbg_trace("[%s] %pK, %d\n", __func__, buf, count); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + goto done; + } + + if (sscanf(buf, "%u %u", &en, &bit) != 2 || en > 1) { + dev_err(dev, "<1|0> : enable|disable interrupt\n"); + goto done; + } + + spin_lock_irqsave(udc->lock, flags); + if (en) { + if (hw_intr_force(bit)) + dev_err(dev, "invalid bit number\n"); + else + isr_statistics.test++; + } else { + if (hw_intr_clear(bit)) + dev_err(dev, "invalid bit number\n"); + } + spin_unlock_irqrestore(udc->lock, flags); + + done: + return count; +} +static DEVICE_ATTR_RW(inters); + +/** + * show_port_test: reads port test mode + * + * Check "device.h" for details + */ +static ssize_t port_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + unsigned int mode; + + dbg_trace("[%s] %pK\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + spin_lock_irqsave(udc->lock, flags); + mode = hw_port_test_get(); + spin_unlock_irqrestore(udc->lock, flags); + + return scnprintf(buf, PAGE_SIZE, "mode = %u\n", mode); +} + +/** + * store_port_test: writes port test mode + * + * Check "device.h" for details + */ +static ssize_t port_test_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + unsigned int mode; + + dbg_trace("[%s] %pK, %d\n", __func__, buf, count); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + goto done; + } + + if (kstrtouint(buf, 10, &mode)) { + dev_err(dev, ": set port test mode\n"); + goto done; + } + + spin_lock_irqsave(udc->lock, flags); + if (hw_port_test_set(mode)) + dev_err(dev, "invalid mode\n"); + spin_unlock_irqrestore(udc->lock, flags); + + done: + return count; +} +static DEVICE_ATTR_RW(port_test); + +static ssize_t qheads_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return 0; +} + +/** + * show_qheads: DMA contents of all queue heads + * + * Check "device.h" for details + */ +static ssize_t qheads_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + unsigned int i, j, n = 0; + + dbg_trace("[%s] %pK\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + spin_lock_irqsave(udc->lock, flags); + for (i = 0; i < hw_ep_max/2; i++) { + struct ci13xxx_ep *mEpRx = &udc->ci13xxx_ep[i]; + struct ci13xxx_ep *mEpTx = &udc->ci13xxx_ep[i + hw_ep_max/2]; + + n += scnprintf(buf + n, PAGE_SIZE - n, + "EP=%02i: RX=%08X TX=%08X\n", + i, (u32)mEpRx->qh.dma, (u32)mEpTx->qh.dma); + for (j = 0; j < (sizeof(struct ci13xxx_qh)/sizeof(u32)); j++) { + n += scnprintf(buf + n, PAGE_SIZE - n, + " %04X: %08X %08X\n", j, + *((u32 *)mEpRx->qh.ptr + j), + *((u32 *)mEpTx->qh.ptr + j)); + } + } + spin_unlock_irqrestore(udc->lock, flags); + + return n; +} +static DEVICE_ATTR_RW(qheads); + +/** + * show_registers: dumps all registers + * + * Check "device.h" for details + */ +#define DUMP_ENTRIES 512 +static ssize_t registers_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + u32 *dump; + unsigned int i, k, n = 0; + + dbg_trace("[%s] %pK\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + dump = kmalloc(sizeof(u32) * DUMP_ENTRIES, GFP_KERNEL); + if (!dump) + return 0; + + spin_lock_irqsave(udc->lock, flags); + k = hw_register_read(dump, DUMP_ENTRIES); + spin_unlock_irqrestore(udc->lock, flags); + + for (i = 0; i < k; i++) { + n += scnprintf(buf + n, PAGE_SIZE - n, + "reg[0x%04X] = 0x%08X\n", + i * (unsigned int)sizeof(u32), dump[i]); + } + kfree(dump); + + return n; +} + +/** + * store_registers: writes value to register address + * + * Check "device.h" for details + */ +static ssize_t registers_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long addr, data, flags; + + dbg_trace("[%s] %pK, %d\n", __func__, buf, count); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + goto done; + } + + if (sscanf(buf, "%li %li", &addr, &data) != 2) { + dev_err(dev, " : write data to register address\n"); + goto done; + } + + spin_lock_irqsave(udc->lock, flags); + if (hw_register_write(addr, data)) + dev_err(dev, "invalid address range\n"); + spin_unlock_irqrestore(udc->lock, flags); + + done: + return count; +} +static DEVICE_ATTR_RW(registers); + +/** + * show_requests: DMA contents of all requests currently queued (all endpts) + * + * Check "device.h" for details + */ +static ssize_t requests_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + struct list_head *ptr = NULL; + struct ci13xxx_req *req = NULL; + unsigned int i, j, n = 0, qSize = sizeof(struct ci13xxx_td)/sizeof(u32); + + dbg_trace("[%s] %pK\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + spin_lock_irqsave(udc->lock, flags); + for (i = 0; i < hw_ep_max; i++) + list_for_each(ptr, &udc->ci13xxx_ep[i].qh.queue) + { + req = list_entry(ptr, struct ci13xxx_req, queue); + + n += scnprintf(buf + n, PAGE_SIZE - n, + "EP=%02i: TD=%08X %s\n", + i % hw_ep_max/2, (u32)req->dma, + ((i < hw_ep_max/2) ? "RX" : "TX")); + + for (j = 0; j < qSize; j++) + n += scnprintf(buf + n, PAGE_SIZE - n, + " %04X: %08X\n", j, + *((u32 *)req->ptr + j)); + } + spin_unlock_irqrestore(udc->lock, flags); + + return n; +} + +static ssize_t requests_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return 0; +} + +static DEVICE_ATTR_RW(requests); + +static ssize_t prime_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return 0; +} + +/* EP# and Direction */ +static ssize_t prime_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + struct ci13xxx_ep *mEp; + unsigned int ep_num, dir; + int n; + struct ci13xxx_req *mReq = NULL; + + if (sscanf(buf, "%u %u", &ep_num, &dir) != 2) { + dev_err(dev, "

: prime the ep\n"); + goto done; + } + + if (dir) + mEp = &udc->ci13xxx_ep[ep_num + hw_ep_max/2]; + else + mEp = &udc->ci13xxx_ep[ep_num]; + + n = hw_ep_bit(mEp->num, mEp->dir); + mReq = list_entry(mEp->qh.queue.next, struct ci13xxx_req, queue); + mEp->qh.ptr->td.next = mReq->dma; + mEp->qh.ptr->td.token &= ~TD_STATUS; + + /* Makes sure that above write goes through */ + wmb(); + + hw_cwrite(CAP_ENDPTPRIME, BIT(n), BIT(n)); + while (hw_cread(CAP_ENDPTPRIME, BIT(n))) + cpu_relax(); + + pr_info("%s: prime:%08x stat:%08x ep#%d dir:%s\n", __func__, + hw_cread(CAP_ENDPTPRIME, ~0), + hw_cread(CAP_ENDPTSTAT, ~0), + mEp->num, mEp->dir ? "IN" : "OUT"); +done: + return count; + +} +static DEVICE_ATTR_RW(prime); + +static ssize_t dtds_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return 0; +} + + +/* EP# and Direction */ +static ssize_t dtds_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + struct ci13xxx_ep *mEp; + unsigned int ep_num, dir; + int n; + struct list_head *ptr = NULL; + struct ci13xxx_req *req = NULL; + + if (sscanf(buf, "%u %u", &ep_num, &dir) != 2) { + dev_err(dev, " : to print dtds\n"); + goto done; + } + + if (dir) + mEp = &udc->ci13xxx_ep[ep_num + hw_ep_max/2]; + else + mEp = &udc->ci13xxx_ep[ep_num]; + + n = hw_ep_bit(mEp->num, mEp->dir); + pr_info("%s: prime:%08x stat:%08x ep#%d dir:%s dTD_update_fail_count: %lu mEp->dTD_update_fail_count: %lu mEp->dTD_active_re_q_count: %lu mEp->prime_fail_count: %lu\n", + __func__, + hw_cread(CAP_ENDPTPRIME, ~0), + hw_cread(CAP_ENDPTSTAT, ~0), + mEp->num, mEp->dir ? "IN" : "OUT", + udc->dTD_update_fail_count, + mEp->dTD_update_fail_count, + mEp->dTD_active_re_q_count, + mEp->prime_fail_count); + + pr_info("QH: cap:%08x cur:%08x next:%08x token:%08x\n", + mEp->qh.ptr->cap, mEp->qh.ptr->curr, + mEp->qh.ptr->td.next, mEp->qh.ptr->td.token); + + list_for_each(ptr, &mEp->qh.queue) { + req = list_entry(ptr, struct ci13xxx_req, queue); + + pr_info("\treq:%pKa next:%08x token:%08x page0:%08x status:%d\n", + &req->dma, req->ptr->next, req->ptr->token, + req->ptr->page[0], req->req.status); + } +done: + return count; + +} +static DEVICE_ATTR_RW(dtds); + +static int ci13xxx_wakeup(struct usb_gadget *_gadget) +{ + struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); + unsigned long flags; + int ret = 0; + + trace(); + + spin_lock_irqsave(udc->lock, flags); + if (!udc->gadget.remote_wakeup) { + ret = -EOPNOTSUPP; + pr_info("remote wakeup feature is not enabled\n"); + goto out; + } + spin_unlock_irqrestore(udc->lock, flags); + + pm_runtime_get_sync(&_gadget->dev); + + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_REMOTE_WAKEUP_EVENT); + + if (udc->transceiver) + usb_phy_set_suspend(udc->transceiver, 0); + + spin_lock_irqsave(udc->lock, flags); + if (!hw_cread(CAP_PORTSC, PORTSC_SUSP)) { + ret = -EINVAL; + pr_info("port is not suspended\n"); + pm_runtime_put(&_gadget->dev); + goto out; + } + hw_cwrite(CAP_PORTSC, PORTSC_FPR, PORTSC_FPR); + + pm_runtime_mark_last_busy(&_gadget->dev); + pm_runtime_put_autosuspend(&_gadget->dev); +out: + spin_unlock_irqrestore(udc->lock, flags); + return ret; +} + +static void usb_do_remote_wakeup(struct work_struct *w) +{ + struct ci13xxx *udc = _udc; + unsigned long flags; + bool do_wake; + + /* + * This work can not be canceled from interrupt handler. Check + * if wakeup conditions are still met. + */ + spin_lock_irqsave(udc->lock, flags); + do_wake = udc->suspended && udc->gadget.remote_wakeup; + spin_unlock_irqrestore(udc->lock, flags); + + if (do_wake) + ci13xxx_wakeup(&udc->gadget); +} + +static ssize_t wakeup_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static ssize_t wakeup_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + + ci13xxx_wakeup(&udc->gadget); + + return count; +} +static DEVICE_ATTR_RW(wakeup); + +/** + * dbg_create_files: initializes the attribute interface + * @dev: device + * + * This function returns an error code + */ +static int __maybe_unused dbg_create_files(struct device *dev) +{ + int retval = 0; + + if (dev == NULL) + return -EINVAL; + retval = device_create_file(dev, &dev_attr_device); + if (retval) + goto done; + retval = device_create_file(dev, &dev_attr_driver); + if (retval) + goto rm_device; + retval = device_create_file(dev, &dev_attr_events); + if (retval) + goto rm_driver; + retval = device_create_file(dev, &dev_attr_inters); + if (retval) + goto rm_events; + retval = device_create_file(dev, &dev_attr_port_test); + if (retval) + goto rm_inters; + retval = device_create_file(dev, &dev_attr_qheads); + if (retval) + goto rm_port_test; + retval = device_create_file(dev, &dev_attr_registers); + if (retval) + goto rm_qheads; + retval = device_create_file(dev, &dev_attr_requests); + if (retval) + goto rm_registers; + retval = device_create_file(dev, &dev_attr_wakeup); + if (retval) + goto rm_remote_wakeup; + retval = device_create_file(dev, &dev_attr_prime); + if (retval) + goto rm_prime; + retval = device_create_file(dev, &dev_attr_dtds); + if (retval) + goto rm_dtds; + retval = device_create_file(dev, &dev_attr_ep_addr_txdbg_mask_enable); + if (retval) + goto rm_txdbg; + retval = device_create_file(dev, &dev_attr_ep_addr_rxdbg_mask_enable); + if (retval) + goto rm_rxdbg; + + return 0; + +rm_dtds: + device_remove_file(dev, &dev_attr_dtds); +rm_prime: + device_remove_file(dev, &dev_attr_prime); +rm_remote_wakeup: + device_remove_file(dev, &dev_attr_wakeup); + rm_registers: + device_remove_file(dev, &dev_attr_registers); + rm_qheads: + device_remove_file(dev, &dev_attr_qheads); + rm_port_test: + device_remove_file(dev, &dev_attr_port_test); + rm_inters: + device_remove_file(dev, &dev_attr_inters); + rm_events: + device_remove_file(dev, &dev_attr_events); + rm_driver: + device_remove_file(dev, &dev_attr_driver); + rm_device: + device_remove_file(dev, &dev_attr_device); + rm_txdbg: + device_remove_file(dev, &dev_attr_ep_addr_txdbg_mask_enable); + rm_rxdbg: + device_remove_file(dev, &dev_attr_ep_addr_rxdbg_mask_enable); + done: + return retval; +} + +/** + * dbg_remove_files: destroys the attribute interface + * @dev: device + * + * This function returns an error code + */ +static int __maybe_unused dbg_remove_files(struct device *dev) +{ + if (dev == NULL) + return -EINVAL; + device_remove_file(dev, &dev_attr_requests); + device_remove_file(dev, &dev_attr_registers); + device_remove_file(dev, &dev_attr_qheads); + device_remove_file(dev, &dev_attr_port_test); + device_remove_file(dev, &dev_attr_inters); + device_remove_file(dev, &dev_attr_events); + device_remove_file(dev, &dev_attr_driver); + device_remove_file(dev, &dev_attr_device); + device_remove_file(dev, &dev_attr_wakeup); + device_remove_file(dev, &dev_attr_ep_addr_txdbg_mask_enable); + device_remove_file(dev, &dev_attr_ep_addr_rxdbg_mask_enable); + return 0; +} + +/****************************************************************************** + * UTIL block + *****************************************************************************/ +/** + * _usb_addr: calculates endpoint address from direction & number + * @ep: endpoint + */ +static inline u8 _usb_addr(struct ci13xxx_ep *ep) +{ + return ((ep->dir == TX) ? USB_ENDPOINT_DIR_MASK : 0) | ep->num; +} + +static void ep_prime_timer_func(struct timer_list *t) +{ + struct ci13xxx_ep *mep = from_timer(mep, t, prime_timer); + struct ci13xxx_req *req; + struct list_head *ptr = NULL; + int n = hw_ep_bit(mep->num, mep->dir); + unsigned long flags; + + + spin_lock_irqsave(mep->lock, flags); + + if (_udc && (!_udc->vbus_active || _udc->suspended)) { + pr_debug("ep%d%s prime timer when vbus_active=%d,suspend=%d\n", + mep->num, mep->dir ? "IN" : "OUT", + _udc->vbus_active, _udc->suspended); + goto out; + } + + if (!hw_cread(CAP_ENDPTPRIME, BIT(n))) + goto out; + + if (list_empty(&mep->qh.queue)) + goto out; + + req = list_entry(mep->qh.queue.next, struct ci13xxx_req, queue); + + /* clean speculative fetches on req->ptr->token */ + mb(); + if (!(TD_STATUS_ACTIVE & req->ptr->token)) + goto out; + + mep->prime_timer_count++; + if (mep->prime_timer_count == MAX_PRIME_CHECK_RETRY) { + mep->prime_timer_count = 0; + pr_info("ep%d dir:%s QH:cap:%08x cur:%08x next:%08x tkn:%08x\n", + mep->num, mep->dir ? "IN" : "OUT", + mep->qh.ptr->cap, mep->qh.ptr->curr, + mep->qh.ptr->td.next, mep->qh.ptr->td.token); + list_for_each(ptr, &mep->qh.queue) { + req = list_entry(ptr, struct ci13xxx_req, queue); + pr_info("\treq:%pKa:%08xtkn:%08xpage0:%08xsts:%d\n", + &req->dma, req->ptr->next, + req->ptr->token, req->ptr->page[0], + req->req.status); + } + dbg_usb_op_fail(0xFF, "PRIMEF", mep); + mep->prime_fail_count++; + } else { + mod_timer(&mep->prime_timer, EP_PRIME_CHECK_DELAY); + } + + spin_unlock_irqrestore(mep->lock, flags); + return; + +out: + mep->prime_timer_count = 0; + spin_unlock_irqrestore(mep->lock, flags); + +} + +/** + * _hardware_queue: configures a request at hardware level + * @gadget: gadget + * @mEp: endpoint + * + * This function returns an error code + */ +static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) +{ + unsigned int i; + int ret = 0; + unsigned int length = mReq->req.length; + struct ci13xxx *udc = _udc; + + trace("%pK, %pK", mEp, mReq); + + /* don't queue twice */ + if (mReq->req.status == -EALREADY) + return -EALREADY; + + mReq->req.status = -EALREADY; + if (length && mReq->req.dma == DMA_ERROR_CODE) { + mReq->req.dma = dma_map_single(mEp->device, mReq->req.buf, + length, mEp->dir ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + if (mReq->req.dma == 0) + return -ENOMEM; + + mReq->map = 1; + } + + if (mReq->req.zero && length && (length % mEp->ep.maxpacket == 0)) { + mReq->zptr = dma_pool_zalloc(mEp->td_pool, GFP_ATOMIC, + &mReq->zdma); + if (mReq->zptr == NULL) { + if (mReq->map) { + dma_unmap_single(mEp->device, mReq->req.dma, + length, mEp->dir ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + mReq->req.dma = DMA_ERROR_CODE; + mReq->map = 0; + } + return -ENOMEM; + } + memset(mReq->zptr, 0, sizeof(*mReq->zptr)); + mReq->zptr->next = TD_TERMINATE; + mReq->zptr->token = TD_STATUS_ACTIVE; + if (!mReq->req.no_interrupt) + mReq->zptr->token |= TD_IOC; + } + + /* + * TD configuration + * TODO - handle requests which spawns into several TDs + */ + memset(mReq->ptr, 0, sizeof(*mReq->ptr)); + mReq->ptr->token = length << ffs_nr(TD_TOTAL_BYTES); + mReq->ptr->token &= TD_TOTAL_BYTES; + mReq->ptr->token |= TD_STATUS_ACTIVE; + if (mReq->zptr) { + mReq->ptr->next = mReq->zdma; + } else { + mReq->ptr->next = TD_TERMINATE; + if (!mReq->req.no_interrupt) + mReq->ptr->token |= TD_IOC; + } + + /* MSM Specific: updating the request as required for + * SPS mode. Enable MSM DMA engine according + * to the UDC private data in the request. + */ + if (CI13XX_REQ_VENDOR_ID(mReq->req.udc_priv) == MSM_VENDOR_ID) { + if (mReq->req.udc_priv & MSM_SPS_MODE) { + mReq->ptr->token = TD_STATUS_ACTIVE; + if (mReq->req.udc_priv & MSM_IS_FINITE_TRANSFER) + mReq->ptr->next = TD_TERMINATE; + else + mReq->ptr->next = MSM_ETD_TYPE | mReq->dma; + if (!mReq->req.no_interrupt) + mReq->ptr->token |= MSM_ETD_IOC; + } + mReq->req.dma = 0; + } + + mReq->ptr->page[0] = mReq->req.dma; + for (i = 1; i < 5; i++) + mReq->ptr->page[i] = (mReq->req.dma + i * CI13XXX_PAGE_SIZE) & + ~TD_RESERVED_MASK; + /* Makes sure that above write goes through */ + wmb(); + + /* Remote Wakeup */ + if (udc->suspended) { + if (!udc->gadget.remote_wakeup) { + mReq->req.status = -EAGAIN; + + dev_dbg(mEp->device, "%s: queue failed (suspend).", + __func__); + dev_dbg(mEp->device, "%s: Remote wakeup is not supported. ept #%d\n", + __func__, mEp->num); + + return -EAGAIN; + } + + usb_phy_set_suspend(udc->transceiver, 0); + schedule_delayed_work(&udc->rw_work, REMOTE_WAKEUP_DELAY); + } + + if (!list_empty(&mEp->qh.queue)) { + struct ci13xxx_req *mReqPrev; + int n = hw_ep_bit(mEp->num, mEp->dir); + int tmp_stat; + ktime_t start, diff; + + mReqPrev = list_entry(mEp->qh.queue.prev, + struct ci13xxx_req, queue); + if (mReqPrev->zptr) + mReqPrev->zptr->next = mReq->dma & TD_ADDR_MASK; + else + mReqPrev->ptr->next = mReq->dma & TD_ADDR_MASK; + /* Makes sure that above write goes through */ + wmb(); + if (hw_cread(CAP_ENDPTPRIME, BIT(n))) + goto done; + start = ktime_get(); + do { + hw_cwrite(CAP_USBCMD, USBCMD_ATDTW, USBCMD_ATDTW); + tmp_stat = hw_cread(CAP_ENDPTSTAT, BIT(n)); + diff = ktime_sub(ktime_get(), start); + /* poll for max. 100ms */ + if (ktime_to_ms(diff) > USB_MAX_TIMEOUT) { + if (hw_cread(CAP_USBCMD, USBCMD_ATDTW)) + break; + printk_ratelimited(KERN_ERR + "%s:queue failed ep#%d %s\n", + __func__, mEp->num, mEp->dir ? "IN" : "OUT"); + return -EAGAIN; + } + } while (!hw_cread(CAP_USBCMD, USBCMD_ATDTW)); + hw_cwrite(CAP_USBCMD, USBCMD_ATDTW, 0); + if (tmp_stat) + goto done; + } + + /* Hardware may leave few TDs unprocessed, check and reprime with 1st */ + if (!list_empty(&mEp->qh.queue)) { + struct ci13xxx_req *mReq_active, *mReq_next; + u32 i = 0; + + /* Nothing to be done if hardware already finished this TD */ + if ((TD_STATUS_ACTIVE & mReq->ptr->token) == 0) + goto done; + + /* Iterate forward to find first TD with ACTIVE bit set */ + mReq_active = mReq; + list_for_each_entry(mReq_next, &mEp->qh.queue, queue) { + i++; + mEp->dTD_active_re_q_count++; + if (TD_STATUS_ACTIVE & mReq_next->ptr->token) { + mReq_active = mReq_next; + dbg_event(_usb_addr(mEp), "ReQUE", + mReq_next->ptr->token); + pr_debug("!!ReQ(%u-%u-%x)-%u!!\n", mEp->num, + mEp->dir, mReq_next->ptr->token, i); + break; + } + } + + /* QH configuration */ + mEp->qh.ptr->td.next = mReq_active->dma; + mEp->qh.ptr->td.token &= ~TD_STATUS; + goto prime; + } + + /* QH configuration */ + mEp->qh.ptr->td.next = mReq->dma; /* TERMINATE = 0 */ + + if (CI13XX_REQ_VENDOR_ID(mReq->req.udc_priv) == MSM_VENDOR_ID) { + if (mReq->req.udc_priv & MSM_SPS_MODE) { + mEp->qh.ptr->td.next |= MSM_ETD_TYPE; + i = hw_cread(CAP_ENDPTPIPEID + + mEp->num * sizeof(u32), ~0); + /* Read current value of this EPs pipe id */ + i = (mEp->dir == TX) ? + ((i >> MSM_TX_PIPE_ID_OFS) & MSM_PIPE_ID_MASK) : + (i & MSM_PIPE_ID_MASK); + /* + * If requested pipe id is different from current, + * then write it + */ + if (i != (mReq->req.udc_priv & MSM_PIPE_ID_MASK)) { + if (mEp->dir == TX) + hw_cwrite( + CAP_ENDPTPIPEID + + mEp->num * sizeof(u32), + MSM_PIPE_ID_MASK << + MSM_TX_PIPE_ID_OFS, + (mReq->req.udc_priv & + MSM_PIPE_ID_MASK) + << MSM_TX_PIPE_ID_OFS); + else + hw_cwrite( + CAP_ENDPTPIPEID + + mEp->num * sizeof(u32), + MSM_PIPE_ID_MASK, + mReq->req.udc_priv & + MSM_PIPE_ID_MASK); + } + } + } + + mEp->qh.ptr->td.token &= ~TD_STATUS; /* clear status */ + mEp->qh.ptr->cap |= QH_ZLT; + +prime: + /* Makes sure that above write goes through */ + wmb(); /* synchronize before ep prime */ + + ret = hw_ep_prime(mEp->num, mEp->dir, + mEp->type == USB_ENDPOINT_XFER_CONTROL); + if (!ret) + mod_timer(&mEp->prime_timer, EP_PRIME_CHECK_DELAY); + +done: + return ret; +} + +/** + * _hardware_dequeue: handles a request at hardware level + * @gadget: gadget + * @mEp: endpoint + * + * This function returns an error code + */ +static int _hardware_dequeue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) +{ + trace("%pK, %pK", mEp, mReq); + + if (mReq->req.status != -EALREADY) + return -EINVAL; + + /* clean speculative fetches on req->ptr->token */ + mb(); + + if ((TD_STATUS_ACTIVE & mReq->ptr->token) != 0) + return -EBUSY; + + if (CI13XX_REQ_VENDOR_ID(mReq->req.udc_priv) == MSM_VENDOR_ID) + if ((mReq->req.udc_priv & MSM_SPS_MODE) && + (mReq->req.udc_priv & MSM_IS_FINITE_TRANSFER)) + return -EBUSY; + if (mReq->zptr) { + if ((TD_STATUS_ACTIVE & mReq->zptr->token) != 0) + return -EBUSY; + + /* The controller may access this dTD one more time. + * Defer freeing this to next zero length dTD completion. + * It is safe to assume that controller will no longer + * access the previous dTD after next dTD completion. + */ + if (mEp->last_zptr) + dma_pool_free(mEp->td_pool, mEp->last_zptr, + mEp->last_zdma); + mEp->last_zptr = mReq->zptr; + mEp->last_zdma = mReq->zdma; + + mReq->zptr = NULL; + } + + mReq->req.status = 0; + + if (mReq->map) { + dma_unmap_single(mEp->device, mReq->req.dma, mReq->req.length, + mEp->dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + mReq->req.dma = DMA_ERROR_CODE; + mReq->map = 0; + } + + mReq->req.status = mReq->ptr->token & TD_STATUS; + if ((TD_STATUS_HALTED & mReq->req.status) != 0) + mReq->req.status = -1; + else if ((TD_STATUS_DT_ERR & mReq->req.status) != 0) + mReq->req.status = -1; + else if ((TD_STATUS_TR_ERR & mReq->req.status) != 0) + mReq->req.status = -1; + + mReq->req.actual = mReq->ptr->token & TD_TOTAL_BYTES; + mReq->req.actual >>= ffs_nr(TD_TOTAL_BYTES); + mReq->req.actual = mReq->req.length - mReq->req.actual; + mReq->req.actual = mReq->req.status ? 0 : mReq->req.actual; + + return mReq->req.actual; +} + +/** + * purge_rw_queue: Purge requests pending at the remote-wakeup + * queue and send them to the HW. + * + * Go over all of the endpoints and push any pending requests to + * the HW queue. + */ +static void purge_rw_queue(struct ci13xxx *udc) +{ + int i; + struct ci13xxx_ep *mEp = NULL; + struct ci13xxx_req *mReq = NULL; + + /* + * Go over all of the endpoints and push any pending requests to + * the HW queue. + */ + for (i = 0; i < hw_ep_max; i++) { + mEp = &udc->ci13xxx_ep[i]; + + while (!list_empty(&udc->ci13xxx_ep[i].rw_queue)) { + int retval; + + /* pop oldest request */ + mReq = list_entry(udc->ci13xxx_ep[i].rw_queue.next, + struct ci13xxx_req, queue); + + list_del_init(&mReq->queue); + + retval = _hardware_enqueue(mEp, mReq); + + if (retval != 0) { + dbg_event(_usb_addr(mEp), "QUEUE", retval); + mReq->req.status = retval; + if (mReq->req.complete != NULL) { + if (mEp->type == + USB_ENDPOINT_XFER_CONTROL) + mReq->req.complete( + &(_udc->ep0in.ep), + &mReq->req); + else + mReq->req.complete( + &mEp->ep, + &mReq->req); + } + retval = 0; + } + + if (!retval) + list_add_tail(&mReq->queue, &mEp->qh.queue); + else if (mEp->multi_req) + mEp->multi_req = false; + + } + } + + udc->rw_pending = false; +} + +/** + * restore_original_req: Restore original req's attributes + * @mReq: Request + * + * This function restores original req's attributes. Call + * this function before completing the large req (>16K). + */ +static void restore_original_req(struct ci13xxx_req *mReq) +{ + mReq->req.buf = mReq->multi.buf; + mReq->req.length = mReq->multi.len; + if (!mReq->req.status) + mReq->req.actual = mReq->multi.actual; + + mReq->multi.len = 0; + mReq->multi.actual = 0; + mReq->multi.buf = NULL; +} + +/** + * release_ep_request: Free and endpoint request and release + * resources + * @mReq: request + * @mEp: endpoint + * + */ +static void release_ep_request(struct ci13xxx_ep *mEp, + struct ci13xxx_req *mReq) +{ + struct ci13xxx_ep *mEpTemp = mEp; + + unsigned int val; + + /* MSM Specific: Clear end point specific register */ + if (CI13XX_REQ_VENDOR_ID(mReq->req.udc_priv) == MSM_VENDOR_ID) { + if (mReq->req.udc_priv & MSM_SPS_MODE) { + val = hw_cread(CAP_ENDPTPIPEID + + mEp->num * sizeof(u32), + ~0); + + if (val != MSM_EP_PIPE_ID_RESET_VAL) + hw_cwrite( + CAP_ENDPTPIPEID + + mEp->num * sizeof(u32), + ~0, MSM_EP_PIPE_ID_RESET_VAL); + } + } + mReq->req.status = -ESHUTDOWN; + + if (mReq->map) { + dma_unmap_single(mEp->device, mReq->req.dma, + mReq->req.length, + mEp->dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + mReq->req.dma = DMA_ERROR_CODE; + mReq->map = 0; + } + + if (mReq->zptr) { + dma_pool_free(mEp->td_pool, mReq->zptr, mReq->zdma); + mReq->zptr = NULL; + mReq->zdma = 0; + } + + if (mEp->multi_req) { + restore_original_req(mReq); + mEp->multi_req = false; + } + + if (mReq->req.complete != NULL) { + spin_unlock(mEp->lock); + if ((mEp->type == USB_ENDPOINT_XFER_CONTROL) && + mReq->req.length) + mEpTemp = &_udc->ep0in; + mReq->req.complete(&mEpTemp->ep, &mReq->req); + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + mReq->req.complete = NULL; + spin_lock(mEp->lock); + } +} + +/** + * _ep_nuke: dequeues all endpoint requests + * @mEp: endpoint + * + * This function returns an error code + * Caller must hold lock + */ +static int _ep_nuke(struct ci13xxx_ep *mEp) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + trace("%pK", mEp); + + if (mEp == NULL) + return -EINVAL; + + del_timer(&mEp->prime_timer); + mEp->prime_timer_count = 0; + + hw_ep_flush(mEp->num, mEp->dir); + + while (!list_empty(&mEp->qh.queue)) { + /* pop oldest request */ + struct ci13xxx_req *mReq = + list_entry(mEp->qh.queue.next, + struct ci13xxx_req, queue); + list_del_init(&mReq->queue); + + release_ep_request(mEp, mReq); + } + + /* Clear the requests pending at the remote-wakeup queue */ + while (!list_empty(&mEp->rw_queue)) { + + /* pop oldest request */ + struct ci13xxx_req *mReq = + list_entry(mEp->rw_queue.next, + struct ci13xxx_req, queue); + + list_del_init(&mReq->queue); + + release_ep_request(mEp, mReq); + } + + if (mEp->last_zptr) { + dma_pool_free(mEp->td_pool, mEp->last_zptr, mEp->last_zdma); + mEp->last_zptr = NULL; + mEp->last_zdma = 0; + } + + return 0; +} + +/** + * _gadget_stop_activity: stops all USB activity, flushes & disables all endpts + * @gadget: gadget + * + * This function returns an error code + */ +static int _gadget_stop_activity(struct usb_gadget *gadget) +{ + struct ci13xxx *udc = container_of(gadget, struct ci13xxx, gadget); + unsigned long flags; + + trace("%pK", gadget); + + if (gadget == NULL) + return -EINVAL; + + spin_lock_irqsave(udc->lock, flags); + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.remote_wakeup = 0; + udc->suspended = 0; + udc->configured = 0; + spin_unlock_irqrestore(udc->lock, flags); + + udc->driver->disconnect(gadget); + + spin_lock_irqsave(udc->lock, flags); + _ep_nuke(&udc->ep0out); + _ep_nuke(&udc->ep0in); + spin_unlock_irqrestore(udc->lock, flags); + + return 0; +} + +/****************************************************************************** + * ISR block + *****************************************************************************/ +/** + * isr_reset_handler: USB reset interrupt handler + * @udc: UDC device + * + * This function resets USB engine after a bus reset occurred + */ +static void isr_reset_handler(struct ci13xxx *udc) +__releases(udc->lock) +__acquires(udc->lock) +{ + int retval; + + trace("%pK", udc); + + if (udc == NULL) { + pr_err("EINVAL\n"); + + return; + } + + dbg_event(0xFF, "BUS RST", 0); + + spin_unlock(udc->lock); + + if (udc->suspended) { + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_RESUME_EVENT); + if (udc->transceiver) + usb_phy_set_suspend(udc->transceiver, 0); + udc->driver->resume(&udc->gadget); + udc->suspended = 0; + } + + /*stop charging upon reset */ + if (udc->transceiver) + usb_phy_set_power(udc->transceiver, 100); + + retval = _gadget_stop_activity(&udc->gadget); + if (retval) + goto done; + + if (udc->rw_pending) + purge_rw_queue(udc); + + _udc->skip_flush = false; + retval = hw_usb_reset(); + if (retval) + goto done; + + spin_lock(udc->lock); + + done: + if (retval) + pr_err("error: %i\n", retval); +} + +/** + * isr_resume_handler: USB PCI interrupt handler + * @udc: UDC device + * + */ +static void isr_resume_handler(struct ci13xxx *udc) +{ + udc->gadget.speed = hw_port_is_high_speed() ? + USB_SPEED_HIGH : USB_SPEED_FULL; + if (udc->suspended) { + spin_unlock(udc->lock); + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_RESUME_EVENT); + if (udc->transceiver) + usb_phy_set_suspend(udc->transceiver, 0); + udc->suspended = 0; + udc->driver->resume(&udc->gadget); + spin_lock(udc->lock); + + if (udc->rw_pending) + purge_rw_queue(udc); + + } +} + +/** + * isr_resume_handler: USB SLI interrupt handler + * @udc: UDC device + * + */ +static void isr_suspend_handler(struct ci13xxx *udc) +{ + if (udc->gadget.speed != USB_SPEED_UNKNOWN && + udc->vbus_active) { + if (udc->suspended == 0) { + spin_unlock(udc->lock); + udc->driver->suspend(&udc->gadget); + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_SUSPEND_EVENT); + if (udc->transceiver) + usb_phy_set_suspend(udc->transceiver, 1); + spin_lock(udc->lock); + udc->suspended = 1; + } + } +} + +/** + * isr_get_status_complete: get_status request complete function + * @ep: endpoint + * @req: request handled + * + * Caller must release lock + */ +static void isr_get_status_complete(struct usb_ep *ep, struct usb_request *req) +{ + trace("%pK, %pK", ep, req); + + if (ep == NULL || req == NULL) { + pr_err("EINVAL\n"); + + return; + } + + if (req->status) + pr_err("GET_STATUS failed\n"); +} + +/** + * isr_get_status_response: get_status request response + * @udc: udc struct + * @setup: setup request packet + * + * This function returns an error code + */ +static int isr_get_status_response(struct ci13xxx *udc, + struct usb_ctrlrequest *setup) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + struct ci13xxx_ep *mEp = &udc->ep0in; + struct usb_request *req = udc->status; + int dir, num, retval; + + trace("%pK, %pK", mEp, setup); + + if (mEp == NULL || setup == NULL) + return -EINVAL; + + req->complete = isr_get_status_complete; + req->length = 2; + req->buf = udc->status_buf; + + if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { + /* Assume that device is bus powered for now. */ + *((u16 *)req->buf) = _udc->gadget.remote_wakeup << 1; + retval = 0; + } else if ((setup->bRequestType & USB_RECIP_MASK) == + USB_RECIP_ENDPOINT) { + dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ? + TX : RX; + num = le16_to_cpu(setup->wIndex) & USB_ENDPOINT_NUMBER_MASK; + *((u16 *)req->buf) = hw_ep_get_halt(num, dir); + } + /* else do nothing; reserved for future use */ + + spin_unlock(mEp->lock); + retval = usb_ep_queue(&mEp->ep, req, GFP_ATOMIC); + spin_lock(mEp->lock); + return retval; +} + +/** + * isr_setup_status_complete: setup_status request complete function + * @ep: endpoint + * @req: request handled + * + * Caller must release lock. Put the port in test mode if test mode + * feature is selected. + */ +static void +isr_setup_status_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct ci13xxx *udc = req->context; + unsigned long flags; + + trace("%pK, %pK", ep, req); + + spin_lock_irqsave(udc->lock, flags); + if (udc->test_mode) + hw_port_test_set(udc->test_mode); + spin_unlock_irqrestore(udc->lock, flags); +} + +/** + * isr_setup_status_phase: queues the status phase of a setup transation + * @udc: udc struct + * + * This function returns an error code + */ +static int isr_setup_status_phase(struct ci13xxx *udc) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + int retval; + struct ci13xxx_ep *mEp; + + trace("%pK", udc); + + mEp = (udc->ep0_dir == TX) ? &udc->ep0out : &udc->ep0in; + udc->status->context = udc; + udc->status->complete = isr_setup_status_complete; + udc->status->length = 0; + + spin_unlock(mEp->lock); + retval = usb_ep_queue(&mEp->ep, udc->status, GFP_ATOMIC); + spin_lock(mEp->lock); + + return retval; +} + +/** + * isr_tr_complete_low: transaction complete low level handler + * @mEp: endpoint + * + * This function returns an error code + * Caller must hold lock + */ +static int isr_tr_complete_low(struct ci13xxx_ep *mEp) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + struct ci13xxx_req *mReq, *mReqTemp; + struct ci13xxx_ep *mEpTemp = mEp; + int retval = 0; + int req_dequeue = 1; + struct ci13xxx *udc = _udc; + + trace("%pK", mEp); + + if (list_empty(&mEp->qh.queue)) + return 0; + + del_timer(&mEp->prime_timer); + mEp->prime_timer_count = 0; + list_for_each_entry_safe(mReq, mReqTemp, &mEp->qh.queue, + queue) { +dequeue: + retval = _hardware_dequeue(mEp, mReq); + if (retval < 0) { + /* + * FIXME: don't know exact delay + * required for HW to update dTD status + * bits. This is a temporary workaround till + * HW designers come back on this. + */ + if (retval == -EBUSY && req_dequeue && + (mEp->dir == 0 || mEp->num == 0)) { + req_dequeue = 0; + udc->dTD_update_fail_count++; + mEp->dTD_update_fail_count++; + udelay(10); + goto dequeue; + } + break; + } + req_dequeue = 0; + + if (mEp->multi_req) { /* Large request in progress */ + unsigned int remain_len; + + mReq->multi.actual += mReq->req.actual; + remain_len = mReq->multi.len - mReq->multi.actual; + if (mReq->req.status || !remain_len || + (mReq->req.actual != mReq->req.length)) { + restore_original_req(mReq); + mEp->multi_req = false; + } else { + mReq->req.buf = mReq->multi.buf + + mReq->multi.actual; + mReq->req.length = min_t(unsigned int, + remain_len, + 4 * CI13XXX_PAGE_SIZE); + + mReq->req.status = -EINPROGRESS; + mReq->req.actual = 0; + list_del_init(&mReq->queue); + retval = _hardware_enqueue(mEp, mReq); + if (retval) { + pr_err("Large req failed in middle\n"); + mReq->req.status = retval; + restore_original_req(mReq); + mEp->multi_req = false; + goto done; + } else { + list_add_tail(&mReq->queue, + &mEp->qh.queue); + return 0; + } + } + } + list_del_init(&mReq->queue); +done: + + dbg_done(_usb_addr(mEp), mReq->ptr->token, retval); + + if (mReq->req.complete != NULL) { + spin_unlock(mEp->lock); + if ((mEp->type == USB_ENDPOINT_XFER_CONTROL) && + mReq->req.length) + mEpTemp = &_udc->ep0in; + mReq->req.complete(&mEpTemp->ep, &mReq->req); + spin_lock(mEp->lock); + } + } + + if (retval == -EBUSY) + retval = 0; + if (retval < 0) + dbg_event(_usb_addr(mEp), "DONE", retval); + + return retval; +} + +/** + * isr_tr_complete_handler: transaction complete interrupt handler + * @udc: UDC descriptor + * + * This function handles traffic events + */ +static void isr_tr_complete_handler(struct ci13xxx *udc) +__releases(udc->lock) +__acquires(udc->lock) +{ + unsigned int i; + u8 tmode = 0; + + trace("%pK", udc); + + if (udc == NULL) { + pr_err("EINVAL\n"); + + return; + } + + for (i = 0; i < hw_ep_max; i++) { + struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[i]; + int type, num, dir, err = -EINVAL; + struct usb_ctrlrequest req; + + if (mEp->desc == NULL) + continue; /* not configured */ + + if (hw_test_and_clear_complete(i)) { + err = isr_tr_complete_low(mEp); + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) { + if (err > 0) /* needs status phase */ + err = isr_setup_status_phase(udc); + if (err < 0) { + dbg_event(_usb_addr(mEp), + "ERROR", err); + spin_unlock(udc->lock); + if (usb_ep_set_halt(&mEp->ep)) + pr_err("error: ep_set_halt\n"); + spin_lock(udc->lock); + } + } + } + + if (mEp->type != USB_ENDPOINT_XFER_CONTROL || + !hw_test_and_clear_setup_status(i)) + continue; + + if (i != 0) { + pr_err("ctrl traffic received at endpoint\n"); + continue; + } + + /* + * Flush data and handshake transactions of previous + * setup packet. + */ + _ep_nuke(&udc->ep0out); + _ep_nuke(&udc->ep0in); + + /* read_setup_packet */ + do { + hw_test_and_set_setup_guard(); + memcpy(&req, &mEp->qh.ptr->setup, sizeof(req)); + /* Ensure buffer is read before acknowledging to h/w */ + mb(); + } while (!hw_test_and_clear_setup_guard()); + + type = req.bRequestType; + + udc->ep0_dir = (type & USB_DIR_IN) ? TX : RX; + + dbg_setup(_usb_addr(mEp), &req); + + switch (req.bRequest) { + case USB_REQ_CLEAR_FEATURE: + if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && + le16_to_cpu(req.wValue) == + USB_ENDPOINT_HALT) { + if (req.wLength != 0) + break; + num = le16_to_cpu(req.wIndex); + dir = num & USB_ENDPOINT_DIR_MASK; + num &= USB_ENDPOINT_NUMBER_MASK; + if (dir) /* TX */ + num += hw_ep_max/2; + if (!udc->ci13xxx_ep[num].wedge) { + spin_unlock(udc->lock); + err = usb_ep_clear_halt( + &udc->ci13xxx_ep[num].ep); + spin_lock(udc->lock); + if (err) + break; + } + err = isr_setup_status_phase(udc); + } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE) && + le16_to_cpu(req.wValue) == + USB_DEVICE_REMOTE_WAKEUP) { + if (req.wLength != 0) + break; + udc->gadget.remote_wakeup = 0; + err = isr_setup_status_phase(udc); + } else { + goto delegate; + } + break; + case USB_REQ_GET_STATUS: + if (type != (USB_DIR_IN|USB_RECIP_DEVICE) && + type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && + type != (USB_DIR_IN|USB_RECIP_INTERFACE)) + goto delegate; + if (le16_to_cpu(req.wLength) != 2 || + le16_to_cpu(req.wValue) != 0) + break; + err = isr_get_status_response(udc, &req); + break; + case USB_REQ_SET_ADDRESS: + if (type != (USB_DIR_OUT|USB_RECIP_DEVICE)) + goto delegate; + if (le16_to_cpu(req.wLength) != 0 || + le16_to_cpu(req.wIndex) != 0) + break; + err = hw_usb_set_address((u8)le16_to_cpu(req.wValue)); + if (err) + break; + err = isr_setup_status_phase(udc); + break; + case USB_REQ_SET_CONFIGURATION: + if (type == (USB_DIR_OUT|USB_TYPE_STANDARD)) + udc->configured = !!req.wValue; + goto delegate; + case USB_REQ_SET_FEATURE: + if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && + le16_to_cpu(req.wValue) == + USB_ENDPOINT_HALT) { + if (req.wLength != 0) + break; + num = le16_to_cpu(req.wIndex); + dir = num & USB_ENDPOINT_DIR_MASK; + num &= USB_ENDPOINT_NUMBER_MASK; + if (dir) /* TX */ + num += hw_ep_max/2; + + spin_unlock(udc->lock); + err = usb_ep_set_halt(&udc->ci13xxx_ep[num].ep); + spin_lock(udc->lock); + if (!err) + isr_setup_status_phase(udc); + } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE)) { + if (req.wLength != 0) + break; + switch (le16_to_cpu(req.wValue)) { + case USB_DEVICE_REMOTE_WAKEUP: + udc->gadget.remote_wakeup = 1; + err = isr_setup_status_phase(udc); + break; + case USB_DEVICE_TEST_MODE: + tmode = le16_to_cpu(req.wIndex) >> 8; + switch (tmode) { + case TEST_J: + case TEST_K: + case TEST_SE0_NAK: + case TEST_PACKET: + case TEST_FORCE_EN: + udc->test_mode = tmode; + err = isr_setup_status_phase( + udc); + break; + default: + break; + } + default: + goto delegate; + } + } else { + goto delegate; + } + break; + default: +delegate: + if (req.wLength == 0) /* no data phase */ + udc->ep0_dir = TX; + + spin_unlock(udc->lock); + err = udc->driver->setup(&udc->gadget, &req); + spin_lock(udc->lock); + break; + } + + if (err < 0) { + dbg_event(_usb_addr(mEp), "ERROR", err); + + spin_unlock(udc->lock); + if (usb_ep_set_halt(&mEp->ep)) + pr_err("error: ep_set_halt\n"); + spin_lock(udc->lock); + } + } +} + +/****************************************************************************** + * ENDPT block + *****************************************************************************/ +/** + * ep_enable: configure endpoint, making it usable + * + * Check usb_ep_enable() at "usb_gadget.h" for details + */ +static int ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + int retval = 0; + unsigned long flags; + unsigned int mult = 0; + + trace("ep = %pK, desc = %pK", ep, desc); + + if (ep == NULL || desc == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + + /* only internal SW should enable ctrl endpts */ + + mEp->desc = desc; + + if (!list_empty(&mEp->qh.queue)) + pr_err("enabling a non-empty endpoint!\n"); + + mEp->dir = usb_endpoint_dir_in(desc) ? TX : RX; + mEp->num = usb_endpoint_num(desc); + mEp->type = usb_endpoint_type(desc); + + mEp->ep.maxpacket = usb_endpoint_maxp(desc); + + dbg_event(_usb_addr(mEp), "ENABLE", 0); + + mEp->qh.ptr->cap = 0; + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) { + mEp->qh.ptr->cap |= QH_IOS; + } else if (mEp->type == USB_ENDPOINT_XFER_ISOC) { + mEp->qh.ptr->cap &= ~QH_MULT; + mult = ((mEp->ep.maxpacket >> QH_MULT_SHIFT) + 1) & 0x03; + mEp->qh.ptr->cap |= (mult << ffs_nr(QH_MULT)); + } else { + mEp->qh.ptr->cap |= QH_ZLT; + } + + mEp->qh.ptr->cap |= + (mEp->ep.maxpacket << ffs_nr(QH_MAX_PKT)) & QH_MAX_PKT; + mEp->qh.ptr->td.next |= TD_TERMINATE; /* needed? */ + + /* complete all the updates to ept->head before enabling endpoint*/ + mb(); + + /* + * Enable endpoints in the HW other than ep0 as ep0 + * is always enabled + */ + if (mEp->num) + retval |= hw_ep_enable(mEp->num, mEp->dir, mEp->type); + + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_disable: endpoint is no longer usable + * + * Check usb_ep_disable() at "usb_gadget.h" for details + */ +static int ep_disable(struct usb_ep *ep) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + int direction, retval = 0; + unsigned long flags; + + trace("%pK", ep); + + if (ep == NULL) + return -EINVAL; + else if (mEp->desc == NULL) + return -EBUSY; + + spin_lock_irqsave(mEp->lock, flags); + + /* only internal SW should disable ctrl endpts */ + + direction = mEp->dir; + do { + dbg_event(_usb_addr(mEp), "DISABLE", 0); + + retval |= _ep_nuke(mEp); + retval |= hw_ep_disable(mEp->num, mEp->dir); + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + mEp->dir = (mEp->dir == TX) ? RX : TX; + + } while (mEp->dir != direction); + + mEp->desc = NULL; + mEp->ep.desc = NULL; + mEp->ep.maxpacket = USHRT_MAX; + + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_alloc_request: allocate a request object to use with this endpoint + * + * Check usb_ep_alloc_request() at "usb_gadget.h" for details + */ +static struct usb_request *ep_alloc_request(struct usb_ep *ep, gfp_t gfp_flags) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = NULL; + + trace("%pK, %i", ep, gfp_flags); + + if (ep == NULL) { + pr_err("EINVAL\n"); + + return NULL; + } + + mReq = kzalloc(sizeof(struct ci13xxx_req), gfp_flags); + if (mReq != NULL) { + INIT_LIST_HEAD(&mReq->queue); + mReq->req.dma = DMA_ERROR_CODE; + + mReq->ptr = dma_pool_alloc(mEp->td_pool, gfp_flags, + &mReq->dma); + if (mReq->ptr == NULL) { + kfree(mReq); + mReq = NULL; + } + } + + dbg_event(_usb_addr(mEp), "ALLOC", mReq == NULL); + + return (mReq == NULL) ? NULL : &mReq->req; +} + +/** + * ep_free_request: frees a request object + * + * Check usb_ep_free_request() at "usb_gadget.h" for details + */ +static void ep_free_request(struct usb_ep *ep, struct usb_request *req) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); + unsigned long flags; + + trace("%pK, %pK", ep, req); + + if (ep == NULL || req == NULL) { + pr_err("EINVAL\n"); + + return; + } else if (!list_empty(&mReq->queue)) { + pr_err("EBUSY\n"); + + return; + } + + spin_lock_irqsave(mEp->lock, flags); + + if (mReq->ptr) + dma_pool_free(mEp->td_pool, mReq->ptr, mReq->dma); + kfree(mReq); + + dbg_event(_usb_addr(mEp), "FREE", 0); + + spin_unlock_irqrestore(mEp->lock, flags); +} + +/** + * ep_queue: queues (submits) an I/O request to an endpoint + * + * Check usb_ep_queue()* at usb_gadget.h" for details + */ +static int ep_queue(struct usb_ep *ep, struct usb_request *req, + gfp_t __maybe_unused gfp_flags) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); + int retval = 0; + unsigned long flags; + struct ci13xxx *udc = _udc; + + trace("%pK, %pK, %X", ep, req, gfp_flags); + + if (ep == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + if (req == NULL || mEp->desc == NULL) { + retval = -EINVAL; + goto done; + } + + if (!udc->softconnect) { + retval = -ENODEV; + goto done; + } + + if (!udc->configured && mEp->type != + USB_ENDPOINT_XFER_CONTROL) { + trace("usb is not configured ept #%d, ept name#%s\n", + mEp->num, mEp->ep.name); + retval = -ESHUTDOWN; + goto done; + } + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) { + if (req->length) + mEp = (_udc->ep0_dir == RX) ? + &_udc->ep0out : &_udc->ep0in; + if (!list_empty(&mEp->qh.queue)) { + _ep_nuke(mEp); + retval = -EOVERFLOW; + pr_err("endpoint ctrl %X nuked\n", _usb_addr(mEp)); + } + } + + if (ep->endless && udc->gadget.speed == USB_SPEED_FULL) { + pr_err("Queueing endless req is not supported for FS\n"); + retval = -EINVAL; + goto done; + } + + /* first nuke then test link, e.g. previous status has not sent */ + if (!list_empty(&mReq->queue)) { + retval = -EBUSY; + pr_err("request already in queue\n"); + goto done; + } + if (mEp->multi_req) { + retval = -EAGAIN; + pr_err("Large request is in progress. come again\n"); + goto done; + } + + if (req->length > (4 * CI13XXX_PAGE_SIZE)) { + if (!list_empty(&mEp->qh.queue)) { + retval = -EAGAIN; + pr_err("Queue is busy. Large req is not allowed\n"); + goto done; + } + if ((mEp->type != USB_ENDPOINT_XFER_BULK) || + (mEp->dir != RX)) { + retval = -EINVAL; + pr_err("Larger req is supported only for Bulk OUT\n"); + goto done; + } + mEp->multi_req = true; + mReq->multi.len = req->length; + mReq->multi.buf = req->buf; + req->length = (4 * CI13XXX_PAGE_SIZE); + } + + dbg_queue(_usb_addr(mEp), req, retval); + + /* push request */ + mReq->req.status = -EINPROGRESS; + mReq->req.actual = 0; + + if (udc->rw_pending) { + list_add_tail(&mReq->queue, &mEp->rw_queue); + retval = 0; + goto done; + } + + if (udc->suspended) { + /* Remote Wakeup */ + if (!udc->gadget.remote_wakeup) { + + dev_dbg(mEp->device, "%s: queue failed (suspend).\n", + __func__); + dev_dbg(mEp->device, "%s: Remote wakeup is not supported. ept #%d\n", + __func__, mEp->num); + mEp->multi_req = false; + + retval = -EAGAIN; + goto done; + } + + list_add_tail(&mReq->queue, &mEp->rw_queue); + + udc->rw_pending = true; + schedule_delayed_work(&udc->rw_work, + REMOTE_WAKEUP_DELAY); + + retval = 0; + goto done; + } + + retval = _hardware_enqueue(mEp, mReq); + + if (retval == -EALREADY) { + dbg_event(_usb_addr(mEp), "QUEUE", retval); + retval = 0; + } + if (!retval) + list_add_tail(&mReq->queue, &mEp->qh.queue); + else if (mEp->multi_req) + mEp->multi_req = false; + + done: + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_dequeue: dequeues (cancels, unlinks) an I/O request from an endpoint + * + * Check usb_ep_dequeue() at "usb_gadget.h" for details + */ +static int ep_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_ep *mEpTemp = mEp; + struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); + struct ci13xxx *udc = _udc; + unsigned long flags; + + trace("%pK, %pK", ep, req); + + if (udc->udc_driver->in_lpm && udc->udc_driver->in_lpm(udc)) { + dev_err(udc->transceiver->dev, + "%s: Unable to dequeue while in LPM\n", + __func__); + return -EAGAIN; + } + + if (ep == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + /* + * Only ep0 IN is exposed to composite. When a req is dequeued + * on ep0, check both ep0 IN and ep0 OUT queues. + */ + if (req == NULL || mReq->req.status != -EALREADY || + mEp->desc == NULL || list_empty(&mReq->queue) || + (list_empty(&mEp->qh.queue) && ((mEp->type != + USB_ENDPOINT_XFER_CONTROL) || + list_empty(&_udc->ep0out.qh.queue)))) { + spin_unlock_irqrestore(mEp->lock, flags); + return -EINVAL; + } + + dbg_event(_usb_addr(mEp), "DEQUEUE", 0); + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) { + hw_ep_flush(_udc->ep0out.num, RX); + hw_ep_flush(_udc->ep0in.num, TX); + } else { + hw_ep_flush(mEp->num, mEp->dir); + } + + /* pop request */ + list_del_init(&mReq->queue); + if (mReq->map) { + dma_unmap_single(mEp->device, mReq->req.dma, mReq->req.length, + mEp->dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + mReq->req.dma = DMA_ERROR_CODE; + mReq->map = 0; + } + req->status = -ECONNRESET; + + if (mEp->last_zptr) { + dma_pool_free(mEp->td_pool, mEp->last_zptr, mEp->last_zdma); + mEp->last_zptr = NULL; + mEp->last_zdma = 0; + } + + if (mReq->zptr) { + dma_pool_free(mEp->td_pool, mReq->zptr, mReq->zdma); + mReq->zptr = NULL; + mReq->zdma = 0; + } + + if (mEp->multi_req) { + restore_original_req(mReq); + mEp->multi_req = false; + } + + if (mReq->req.complete != NULL) { + spin_unlock(mEp->lock); + if ((mEp->type == USB_ENDPOINT_XFER_CONTROL) && + mReq->req.length) + mEpTemp = &_udc->ep0in; + mReq->req.complete(&mEpTemp->ep, &mReq->req); + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + mReq->req.complete = NULL; + spin_lock(mEp->lock); + } + + spin_unlock_irqrestore(mEp->lock, flags); + return 0; +} + +static int is_sps_req(struct ci13xxx_req *mReq) +{ + return (CI13XX_REQ_VENDOR_ID(mReq->req.udc_priv) == MSM_VENDOR_ID && + mReq->req.udc_priv & MSM_SPS_MODE); +} + +/** + * ep_set_halt: sets the endpoint halt feature + * + * Check usb_ep_set_halt() at "usb_gadget.h" for details + */ +static int ep_set_halt(struct usb_ep *ep, int value) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx *udc = _udc; + int direction, retval = 0; + unsigned long flags; + + trace("%pK, %i", ep, value); + + if (ep == NULL || mEp->desc == NULL) + return -EINVAL; + + if (udc->suspended) { + dev_err(udc->transceiver->dev, + "%s: Unable to halt EP while suspended\n", __func__); + return -EINVAL; + } + + spin_lock_irqsave(mEp->lock, flags); + +#ifndef STALL_IN + /* g_file_storage MS compliant but g_zero fails chapter 9 compliance */ + if (value && mEp->type == USB_ENDPOINT_XFER_BULK && mEp->dir == TX && + !list_empty(&mEp->qh.queue) && + !is_sps_req(list_entry(mEp->qh.queue.next, struct ci13xxx_req, + queue))){ + spin_unlock_irqrestore(mEp->lock, flags); + return -EAGAIN; + } +#endif + + direction = mEp->dir; + do { + dbg_event(_usb_addr(mEp), "HALT", value); + retval |= hw_ep_set_halt(mEp->num, mEp->dir, value); + + if (!value) + mEp->wedge = 0; + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + mEp->dir = (mEp->dir == TX) ? RX : TX; + + } while (mEp->dir != direction); + + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_set_wedge: sets the halt feature and ignores clear requests + * + * Check usb_ep_set_wedge() at "usb_gadget.h" for details + */ +static int ep_set_wedge(struct usb_ep *ep) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + unsigned long flags; + + trace("%pK", ep); + + if (ep == NULL || mEp->desc == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + + dbg_event(_usb_addr(mEp), "WEDGE", 0); + mEp->wedge = 1; + + spin_unlock_irqrestore(mEp->lock, flags); + + return usb_ep_set_halt(ep); +} + +/** + * ep_fifo_flush: flushes contents of a fifo + * + * Check usb_ep_fifo_flush() at "usb_gadget.h" for details + */ +static void ep_fifo_flush(struct usb_ep *ep) +{ + struct ci13xxx *udc = _udc; + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + unsigned long flags; + + trace("%pK", ep); + + if (ep == NULL) { + pr_err("%02X: -EINVAL\n", _usb_addr(mEp)); + return; + } + + if (udc->udc_driver->in_lpm && udc->udc_driver->in_lpm(udc)) { + dev_err(udc->transceiver->dev, + "%s: Unable to fifo_flush while in LPM\n", + __func__); + return; + } + + spin_lock_irqsave(mEp->lock, flags); + + dbg_event(_usb_addr(mEp), "FFLUSH", 0); + /* + * _ep_nuke() takes care of flushing the endpoint. + * some function drivers expect udc to retire all + * pending requests upon flushing an endpoint. There + * is no harm in doing it. + */ + _ep_nuke(mEp); + + spin_unlock_irqrestore(mEp->lock, flags); +} + +/** + * Endpoint-specific part of the API to the USB controller hardware + * Check "usb_gadget.h" for details + */ +static const struct usb_ep_ops usb_ep_ops = { + .enable = ep_enable, + .disable = ep_disable, + .alloc_request = ep_alloc_request, + .free_request = ep_free_request, + .queue = ep_queue, + .dequeue = ep_dequeue, + .set_halt = ep_set_halt, + .set_wedge = ep_set_wedge, + .fifo_flush = ep_fifo_flush, +}; + +/****************************************************************************** + * GADGET block + *****************************************************************************/ +static int ci13xxx_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); + unsigned long flags; + int gadget_ready = 0; + + if (!(udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS)) + return -EOPNOTSUPP; + + spin_lock_irqsave(udc->lock, flags); + udc->vbus_active = is_active; + if (udc->driver) + gadget_ready = 1; + spin_unlock_irqrestore(udc->lock, flags); + + if (!gadget_ready) + return 0; + + if (is_active) { + hw_device_reset(udc); + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_CONNECT_EVENT); + /* Enable BAM (if needed) before starting controller */ + if (udc->softconnect) { + dbg_event(0xFF, "BAM EN2", + _gadget->bam2bam_func_enabled); + msm_usb_bam_enable(CI_CTRL, + _gadget->bam2bam_func_enabled); + hw_device_state(udc->ep0out.qh.dma); + } + } else { + hw_device_state(0); + _gadget_stop_activity(&udc->gadget); + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_DISCONNECT_EVENT); + usb_gadget_set_state(&udc->gadget, USB_STATE_NOTATTACHED); + } + + return 0; +} + +#define VBUS_DRAW_BUF_LEN 10 +#define MAX_OVERRIDE_VBUS_ALLOWED 900 /* 900 mA */ +static char vbus_draw_mA[VBUS_DRAW_BUF_LEN]; +module_param_string(vbus_draw_mA, vbus_draw_mA, VBUS_DRAW_BUF_LEN, 0644); + +static int ci13xxx_vbus_draw(struct usb_gadget *_gadget, unsigned int mA) +{ + struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); + unsigned int override_mA = 0; + + /* override param to draw more current if battery draining faster */ + if ((mA == CONFIG_USB_GADGET_VBUS_DRAW) && + (vbus_draw_mA[0] != '\0')) { + if ((!kstrtoint(vbus_draw_mA, 10, &override_mA)) && + (override_mA <= MAX_OVERRIDE_VBUS_ALLOWED)) { + mA = override_mA; + } + } + + if (udc->transceiver) + return usb_phy_set_power(udc->transceiver, mA); + return -ENOTSUPP; +} + +static int ci13xxx_pullup(struct usb_gadget *_gadget, int is_active) +{ + struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); + unsigned long flags; + + spin_lock_irqsave(udc->lock, flags); + udc->softconnect = is_active; + if (((udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS) && + !udc->vbus_active) || !udc->driver) { + spin_unlock_irqrestore(udc->lock, flags); + return 0; + } + spin_unlock_irqrestore(udc->lock, flags); + + pm_runtime_get_sync(&_gadget->dev); + + /* Enable BAM (if needed) before starting controller */ + if (is_active) { + dbg_event(0xFF, "BAM EN1", _gadget->bam2bam_func_enabled); + msm_usb_bam_enable(CI_CTRL, _gadget->bam2bam_func_enabled); + } + + spin_lock_irqsave(udc->lock, flags); + if (!udc->vbus_active) { + spin_unlock_irqrestore(udc->lock, flags); + pm_runtime_put_sync(&_gadget->dev); + return 0; + } + if (is_active) { + spin_unlock(udc->lock); + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_CONNECT_EVENT); + spin_lock(udc->lock); + hw_device_state(udc->ep0out.qh.dma); + } else { + hw_device_state(0); + if (udc->suspended) { + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_RESUME_EVENT); + if (udc->transceiver) + usb_phy_set_suspend(udc->transceiver, 0); + udc->driver->resume(&udc->gadget); + udc->suspended = 0; + } + spin_unlock_irqrestore(udc->lock, flags); + _gadget_stop_activity(&udc->gadget); + spin_lock_irqsave(udc->lock, flags); + } + spin_unlock_irqrestore(udc->lock, flags); + + pm_runtime_mark_last_busy(&_gadget->dev); + pm_runtime_put_autosuspend(&_gadget->dev); + + return 0; +} + +static int ci13xxx_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static int ci13xxx_stop(struct usb_gadget *gadget); + +/** + * Device operations part of the API to the USB controller hardware, + * which don't involve endpoints (or i/o) + * Check "usb_gadget.h" for details + */ +static const struct usb_gadget_ops usb_gadget_ops = { + .vbus_session = ci13xxx_vbus_session, + .wakeup = ci13xxx_wakeup, + .vbus_draw = ci13xxx_vbus_draw, + .pullup = ci13xxx_pullup, + .udc_start = ci13xxx_start, + .udc_stop = ci13xxx_stop, +}; + +/** + * ci13xxx_start: register a gadget driver + * @gadget: our gadget + * @driver: the driver being registered + * + * Interrupts are enabled here. + */ +static int ci13xxx_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct ci13xxx *udc = _udc; + unsigned long flags; + int retval = -ENOMEM; + + trace("%pK", driver); + + if (driver == NULL || + driver->setup == NULL || + driver->disconnect == NULL) + return -EINVAL; + else if (udc == NULL) + return -ENODEV; + else if (udc->driver != NULL) + return -EBUSY; + + spin_lock_irqsave(udc->lock, flags); + + pr_info("hw_ep_max = %d\n", hw_ep_max); + + udc->gadget.dev.driver = NULL; + + spin_unlock_irqrestore(udc->lock, flags); + + pm_runtime_get_sync(&udc->gadget.dev); + + udc->ep0out.ep.desc = &ctrl_endpt_out_desc; + retval = usb_ep_enable(&udc->ep0out.ep); + if (retval) + goto pm_put; + + udc->ep0in.ep.desc = &ctrl_endpt_in_desc; + retval = usb_ep_enable(&udc->ep0in.ep); + if (retval) + goto pm_put; + udc->status = usb_ep_alloc_request(&udc->ep0in.ep, GFP_KERNEL); + if (!udc->status) { + retval = -ENOMEM; + goto pm_put; + } + + udc->status_buf = kzalloc(2 + udc->gadget.extra_buf_alloc, + GFP_KERNEL); /* for GET_STATUS */ + if (!udc->status_buf) { + usb_ep_free_request(&udc->ep0in.ep, udc->status); + retval = -ENOMEM; + goto pm_put; + } + spin_lock_irqsave(udc->lock, flags); + + udc->gadget.ep0 = &udc->ep0in.ep; + /* bind gadget */ + driver->driver.bus = NULL; + udc->gadget.dev.driver = &driver->driver; + + udc->driver = driver; + if (udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS) { + if (udc->vbus_active) { + if (udc->udc_driver->flags & CI13XXX_REGS_SHARED) + hw_device_reset(udc); + } else { + goto done; + } + } + + if (!udc->softconnect) + goto done; + + retval = hw_device_state(udc->ep0out.qh.dma); + +done: + spin_unlock_irqrestore(udc->lock, flags); + + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_UDC_STARTED_EVENT); +pm_put: + pm_runtime_put(&udc->gadget.dev); + + return retval; +} + +/** + * ci13xxx_stop: unregister a gadget driver + * + * Check usb_gadget_unregister_driver() at "usb_gadget.h" for details + */ +static int ci13xxx_stop(struct usb_gadget *gadget) +{ + struct ci13xxx *udc = _udc; + unsigned long flags; + + spin_lock_irqsave(udc->lock, flags); + + if (!(udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS) || + udc->vbus_active) { + hw_device_state(0); + spin_unlock_irqrestore(udc->lock, flags); + _gadget_stop_activity(&udc->gadget); + spin_lock_irqsave(udc->lock, flags); + } + + udc->driver = NULL; + spin_unlock_irqrestore(udc->lock, flags); + + usb_ep_free_request(&udc->ep0in.ep, udc->status); + kfree(udc->status_buf); + + return 0; +} + +/****************************************************************************** + * BUS block + *****************************************************************************/ +/** + * udc_irq: global interrupt handler + * + * This function returns IRQ_HANDLED if the IRQ has been handled + * It locks access to registers + */ +static irqreturn_t udc_irq(void) +{ + struct ci13xxx *udc = _udc; + irqreturn_t retval; + u32 intr; + + trace(); + + if (udc == NULL) { + pr_err("ENODEV\n"); + + return IRQ_HANDLED; + } + + spin_lock(udc->lock); + + if (udc->udc_driver->in_lpm && udc->udc_driver->in_lpm(udc)) { + spin_unlock(udc->lock); + return IRQ_NONE; + } + + if (udc->udc_driver->flags & CI13XXX_REGS_SHARED) { + if (hw_cread(CAP_USBMODE, USBMODE_CM) != + USBMODE_CM_DEVICE) { + spin_unlock(udc->lock); + return IRQ_NONE; + } + } + intr = hw_test_and_clear_intr_active(); + if (intr) { + isr_statistics.hndl.buf[isr_statistics.hndl.idx++] = intr; + isr_statistics.hndl.idx &= ISR_MASK; + isr_statistics.hndl.cnt++; + + /* order defines priority - do NOT change it */ + if (USBi_URI & intr) { + isr_statistics.uri++; + if (!hw_cread(CAP_PORTSC, PORTSC_PR)) + pr_info("%s: USB reset interrupt is delayed\n", + __func__); + isr_reset_handler(udc); + } + if (USBi_PCI & intr) { + isr_statistics.pci++; + isr_resume_handler(udc); + } + if (USBi_UEI & intr) + isr_statistics.uei++; + if (USBi_UI & intr) { + isr_statistics.ui++; + isr_tr_complete_handler(udc); + } + if (USBi_SLI & intr) { + isr_suspend_handler(udc); + isr_statistics.sli++; + } + retval = IRQ_HANDLED; + } else { + isr_statistics.none++; + retval = IRQ_NONE; + } + spin_unlock(udc->lock); + + return retval; +} + +static void destroy_eps(struct ci13xxx *ci) +{ + int i; + + for (i = 0; i < hw_ep_max; i++) { + struct ci13xxx_ep *mEp = &ci->ci13xxx_ep[i]; + + dma_pool_free(ci->qh_pool, mEp->qh.ptr, mEp->qh.dma); + } +} + +/** + * udc_probe: parent probe must call this to initialize UDC + * @dev: parent device + * @regs: registers base address + * @name: driver name + * + * This function returns an error code + * No interrupts active, the IRQ has not been requested yet + * Kernel assumes 32-bit DMA operations by default, no need to dma_set_mask + */ +static int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, + void __iomem *regs) +{ + struct ci13xxx *udc; + struct ci13xxx_platform_data *pdata; + int retval = 0, i, j; + + trace("%pK, %pK, %pK", dev, regs, driver->name); + + if (dev == NULL || regs == NULL || driver == NULL || + driver->name == NULL) + return -EINVAL; + + udc = kzalloc(sizeof(struct ci13xxx), GFP_KERNEL); + if (udc == NULL) + return -ENOMEM; + + udc->lock = &udc_lock; + udc->regs = regs; + udc->udc_driver = driver; + + udc->gadget.ops = &usb_gadget_ops; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->gadget.is_otg = 0; + udc->gadget.name = driver->name; + udc->gadget.is_chipidea = true; + + /* alloc resources */ + udc->qh_pool = dma_pool_create("ci13xxx_qh", dev, + sizeof(struct ci13xxx_qh), + 64, CI13XXX_PAGE_SIZE); + if (udc->qh_pool == NULL) { + retval = -ENOMEM; + goto free_udc; + } + + udc->td_pool = dma_pool_create("ci13xxx_td", dev, + sizeof(struct ci13xxx_td), + 64, CI13XXX_PAGE_SIZE); + if (udc->td_pool == NULL) { + retval = -ENOMEM; + goto free_qh_pool; + } + + INIT_DELAYED_WORK(&udc->rw_work, usb_do_remote_wakeup); + + retval = hw_device_init(regs); + if (retval < 0) + goto free_qh_pool; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + for (i = 0; i < hw_ep_max; i++) { + struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[i]; + + INIT_LIST_HEAD(&mEp->ep.ep_list); + INIT_LIST_HEAD(&mEp->rw_queue); + timer_setup(&mEp->prime_timer, ep_prime_timer_func, 0); + } + + arch_setup_dma_ops(&udc->gadget.dev, 0, DMA_BIT_MASK(32), NULL, false); + for (i = 0; i < hw_ep_max/2; i++) { + for (j = RX; j <= TX; j++) { + int k = i + j * hw_ep_max/2; + struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[k]; + + scnprintf(mEp->name, sizeof(mEp->name), "ep%i%s", i, + (j == TX) ? "in" : "out"); + + mEp->lock = udc->lock; + mEp->device = &udc->gadget.dev; + mEp->td_pool = udc->td_pool; + + mEp->ep.name = mEp->name; + mEp->ep.ops = &usb_ep_ops; + + if (i == 0) { + mEp->ep.caps.type_control = true; + } else { + mEp->ep.caps.type_iso = true; + mEp->ep.caps.type_bulk = true; + mEp->ep.caps.type_int = true; + } + + if (j == TX) + mEp->ep.caps.dir_in = true; + else + mEp->ep.caps.dir_out = true; + + usb_ep_set_maxpacket_limit(&mEp->ep, + k ? USHRT_MAX : CTRL_PAYLOAD_MAX); + + INIT_LIST_HEAD(&mEp->qh.queue); + mEp->qh.ptr = dma_pool_alloc(udc->qh_pool, GFP_KERNEL, + &mEp->qh.dma); + if (mEp->qh.ptr == NULL) + retval = -ENOMEM; + else + memset(mEp->qh.ptr, 0, sizeof(*mEp->qh.ptr)); + + /* skip ep0 out and in endpoints */ + if (i == 0) + continue; + + list_add_tail(&mEp->ep.ep_list, &udc->gadget.ep_list); + } + } + + if (retval) + goto free_dma_pools; + + udc->gadget.ep0 = &udc->ep0in.ep; + + pdata = dev->platform_data; + if (pdata) { + if (pdata->enable_axi_prefetch) + udc->gadget.extra_buf_alloc = EXTRA_ALLOCATION_SIZE; + } + + if (udc->udc_driver->flags & CI13XXX_REQUIRE_TRANSCEIVER) { + udc->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); + if (udc->transceiver == NULL) { + retval = -ENODEV; + goto destroy_eps; + } + } + + if (!(udc->udc_driver->flags & CI13XXX_REGS_SHARED)) { + retval = hw_device_reset(udc); + if (retval) + goto put_transceiver; + } + + if (udc->transceiver) { + retval = otg_set_peripheral(udc->transceiver->otg, + &udc->gadget); + if (retval) + goto put_transceiver; + } + + retval = usb_add_gadget_udc(dev, &udc->gadget); + if (retval) + goto remove_trans; + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + retval = dbg_create_files(&udc->gadget.dev); + if (retval) { + pr_err("Registering sysfs files for debug failed!!!!\n"); + goto del_udc; + } +#endif + + pm_runtime_no_callbacks(&udc->gadget.dev); + pm_runtime_set_active(&udc->gadget.dev); + pm_runtime_enable(&udc->gadget.dev); + + /* Use delayed LPM especially for composition-switch in LPM (suspend) */ + pm_runtime_set_autosuspend_delay(&udc->gadget.dev, 2000); + pm_runtime_use_autosuspend(&udc->gadget.dev); + + _udc = udc; + return retval; + +del_udc: + usb_del_gadget_udc(&udc->gadget); +remove_trans: + if (udc->transceiver) + otg_set_peripheral(udc->transceiver->otg, NULL); + + pr_err("error = %i\n", retval); +put_transceiver: + if (udc->transceiver) + usb_put_phy(udc->transceiver); +destroy_eps: + destroy_eps(udc); +free_dma_pools: + dma_pool_destroy(udc->td_pool); +free_qh_pool: + dma_pool_destroy(udc->qh_pool); +free_udc: + kfree(udc); + _udc = NULL; + return retval; +} + +/** + * udc_remove: parent remove must call this to remove UDC + * + * No interrupts active, the IRQ has been released + */ +static void udc_remove(void) +{ + struct ci13xxx *udc = _udc; + + if (udc == NULL) { + pr_err("EINVAL\n"); + + return; + } + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + dbg_remove_files(&udc->gadget.dev); +#endif + usb_del_gadget_udc(&udc->gadget); + + if (udc->transceiver) { + otg_set_peripheral(udc->transceiver->otg, NULL); + usb_put_phy(udc->transceiver); + } + destroy_eps(udc); + dma_pool_destroy(udc->td_pool); + dma_pool_destroy(udc->qh_pool); + + kfree(udc); + _udc = NULL; +} diff --git a/drivers/usb/gadget/ci13xxx_udc.h b/drivers/usb/gadget/ci13xxx_udc.h new file mode 100644 index 000000000000..331392955689 --- /dev/null +++ b/drivers/usb/gadget/ci13xxx_udc.h @@ -0,0 +1,263 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ci13xxx_udc.h - structures, registers, and macros MIPS USB IP core + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Description: MIPS USB IP core family device controller + * Structures, registers and logging macros + */ + +#ifndef _CI13XXX_h_ +#define _CI13XXX_h_ + +/****************************************************************************** + * DEFINE + *****************************************************************************/ +#define CI13XXX_PAGE_SIZE 4096ul /* page size for TD's */ +#define ENDPT_MAX (32) +#define CTRL_PAYLOAD_MAX (64) +#define RX (0) /* similar to USB_DIR_OUT but can be used as an index */ +#define TX (1) /* similar to USB_DIR_IN but can be used as an index */ + +/* UDC private data: + * 16MSb - Vendor ID | 16 LSb Vendor private data + */ +#define CI13XX_REQ_VENDOR_ID(id) (id & 0xFFFF0000UL) + +#define MSM_ETD_TYPE BIT(1) +#define MSM_EP_PIPE_ID_RESET_VAL 0x1F001F + +/****************************************************************************** + * STRUCTURES + *****************************************************************************/ +/* DMA layout of transfer descriptors */ +struct ci13xxx_td { + /* 0 */ + u32 next; +#define TD_TERMINATE BIT(0) +#define TD_ADDR_MASK (0xFFFFFFEUL << 5) + /* 1 */ + u32 token; +#define TD_STATUS (0x00FFUL << 0) +#define TD_STATUS_TR_ERR BIT(3) +#define TD_STATUS_DT_ERR BIT(5) +#define TD_STATUS_HALTED BIT(6) +#define TD_STATUS_ACTIVE BIT(7) +#define TD_MULTO (0x0003UL << 10) +#define TD_IOC BIT(15) +#define TD_TOTAL_BYTES (0x7FFFUL << 16) + /* 2 */ + u32 page[5]; +#define TD_CURR_OFFSET (0x0FFFUL << 0) +#define TD_FRAME_NUM (0x07FFUL << 0) +#define TD_RESERVED_MASK (0x0FFFUL << 0) +} __packed __aligned(4); + +/* DMA layout of queue heads */ +struct ci13xxx_qh { + /* 0 */ + u32 cap; +#define QH_IOS BIT(15) +#define QH_MAX_PKT (0x07FFUL << 16) +#define QH_ZLT BIT(29) +#define QH_MULT (0x0003UL << 30) +#define QH_MULT_SHIFT 11 + /* 1 */ + u32 curr; + /* 2 - 8 */ + struct ci13xxx_td td; + /* 9 */ + u32 RESERVED; + struct usb_ctrlrequest setup; +} __packed __aligned(4); + +/* cache of larger request's original attributes */ +struct ci13xxx_multi_req { + unsigned int len; + unsigned int actual; + void *buf; +}; + +/* Extension of usb_request */ +struct ci13xxx_req { + struct usb_request req; + unsigned int map; + struct list_head queue; + struct ci13xxx_td *ptr; + dma_addr_t dma; + struct ci13xxx_td *zptr; + dma_addr_t zdma; + struct ci13xxx_multi_req multi; +}; + +/* Extension of usb_ep */ +struct ci13xxx_ep { + struct usb_ep ep; + const struct usb_endpoint_descriptor *desc; + u8 dir; + u8 num; + u8 type; + char name[16]; + struct { + struct list_head queue; + struct ci13xxx_qh *ptr; + dma_addr_t dma; + } qh; + struct list_head rw_queue; + int wedge; + + /* global resources */ + spinlock_t *lock; + struct device *device; + struct dma_pool *td_pool; + struct ci13xxx_td *last_zptr; + dma_addr_t last_zdma; + unsigned long dTD_update_fail_count; + unsigned long dTD_active_re_q_count; + unsigned long prime_fail_count; + int prime_timer_count; + struct timer_list prime_timer; + + bool multi_req; +}; + +struct ci13xxx; +struct ci13xxx_udc_driver { + const char *name; + unsigned long flags; + unsigned int nz_itc; +#define CI13XXX_REGS_SHARED BIT(0) +#define CI13XXX_REQUIRE_TRANSCEIVER BIT(1) +#define CI13XXX_PULLUP_ON_VBUS BIT(2) +#define CI13XXX_DISABLE_STREAMING BIT(3) +#define CI13XXX_ZERO_ITC BIT(4) +#define CI13XXX_ENABLE_AHB2AHB_BYPASS BIT(6) + +#define CI13XXX_CONTROLLER_RESET_EVENT 0 +#define CI13XXX_CONTROLLER_CONNECT_EVENT 1 +#define CI13XXX_CONTROLLER_SUSPEND_EVENT 2 +#define CI13XXX_CONTROLLER_REMOTE_WAKEUP_EVENT 3 +#define CI13XXX_CONTROLLER_RESUME_EVENT 4 +#define CI13XXX_CONTROLLER_DISCONNECT_EVENT 5 +#define CI13XXX_CONTROLLER_UDC_STARTED_EVENT 6 +#define CI13XXX_CONTROLLER_ERROR_EVENT 7 + + void (*notify_event)(struct ci13xxx *udc, unsigned int event); + bool (*in_lpm)(struct ci13xxx *udc); +}; + +/* CI13XXX UDC descriptor & global resources */ +struct ci13xxx { + spinlock_t *lock; /* ctrl register bank access */ + void __iomem *regs; /* registers address space */ + + struct dma_pool *qh_pool; /* DMA pool for queue heads */ + struct dma_pool *td_pool; /* DMA pool for transfer descs */ + struct usb_request *status; /* ep0 status request */ + void *status_buf;/* GET_STATUS buffer */ + + struct usb_gadget gadget; /* USB slave device */ + struct ci13xxx_ep ci13xxx_ep[ENDPT_MAX]; /* extended endpts */ + u32 ep0_dir; /* ep0 direction */ +#define ep0out ci13xxx_ep[0] +#define ep0in ci13xxx_ep[hw_ep_max / 2] + u8 suspended; /* suspended by the host */ + u8 configured; /* is device configured */ + u8 test_mode; /* the selected test mode */ + bool rw_pending; /* Remote wakeup pending flag */ + struct delayed_work rw_work; /* remote wakeup delayed work */ + struct usb_gadget_driver *driver; /* 3rd party gadget driver */ + struct ci13xxx_udc_driver *udc_driver; /* device controller driver */ + int vbus_active; /* is VBUS active */ + int softconnect; /* is pull-up enable allowed */ + unsigned long dTD_update_fail_count; + struct usb_phy *transceiver; /* Transceiver struct */ + bool skip_flush; /* + * skip flushing remaining EP + * upon flush timeout for the + * first EP. + */ +}; + +/****************************************************************************** + * REGISTERS + *****************************************************************************/ +/* register size */ +#define REG_BITS (32) + +/* HCCPARAMS */ +#define HCCPARAMS_LEN BIT(17) + +/* DCCPARAMS */ +#define DCCPARAMS_DEN (0x1F << 0) +#define DCCPARAMS_DC BIT(7) + +/* TESTMODE */ +#define TESTMODE_FORCE BIT(0) + +/* AHB_MODE */ +#define AHB2AHB_BYPASS BIT(31) + +/* USBCMD */ +#define USBCMD_RS BIT(0) +#define USBCMD_RST BIT(1) +#define USBCMD_SUTW BIT(13) +#define USBCMD_ATDTW BIT(14) + +/* USBSTS & USBINTR */ +#define USBi_UI BIT(0) +#define USBi_UEI BIT(1) +#define USBi_PCI BIT(2) +#define USBi_URI BIT(6) +#define USBi_SLI BIT(8) + +/* DEVICEADDR */ +#define DEVICEADDR_USBADRA BIT(24) +#define DEVICEADDR_USBADR (0x7FUL << 25) + +/* PORTSC */ +#define PORTSC_FPR BIT(6) +#define PORTSC_SUSP BIT(7) +#define PORTSC_PR BIT(8) +#define PORTSC_HSP BIT(9) +#define PORTSC_PTC (0x0FUL << 16) + +/* DEVLC */ +#define DEVLC_PSPD (0x03UL << 25) +#define DEVLC_PSPD_HS (0x02UL << 25) + +/* USBMODE */ +#define USBMODE_CM (0x03UL << 0) +#define USBMODE_CM_IDLE (0x00UL << 0) +#define USBMODE_CM_DEVICE (0x02UL << 0) +#define USBMODE_CM_HOST (0x03UL << 0) +#define USBMODE_SLOM BIT(3) +#define USBMODE_SDIS BIT(4) +#define USBCMD_ITC(n) (n << 16) /* n = 0, 1, 2, 4, 8, 16, 32, 64 */ +#define USBCMD_ITC_MASK (0xFF << 16) + +/* ENDPTCTRL */ +#define ENDPTCTRL_RXS BIT(0) +#define ENDPTCTRL_RXT (0x03UL << 2) +#define ENDPTCTRL_RXR BIT(6) /* reserved for port 0 */ +#define ENDPTCTRL_RXE BIT(7) +#define ENDPTCTRL_TXS BIT(16) +#define ENDPTCTRL_TXT (0x03UL << 18) +#define ENDPTCTRL_TXR BIT(22) /* reserved for port 0 */ +#define ENDPTCTRL_TXE BIT(23) + +/****************************************************************************** + * LOGGING + *****************************************************************************/ + +#define trace(format, args...) do {} while (0) +#define dbg_trace(format, args...) dev_dbg(dev, format, ##args) + +#endif /* _CI13XXX_h_ */ diff --git a/drivers/usb/gadget/function/f_diag.c b/drivers/usb/gadget/function/f_diag.c index d93df9ef3bab..f2eb6e4be3c5 100644 --- a/drivers/usb/gadget/function/f_diag.c +++ b/drivers/usb/gadget/function/f_diag.c @@ -3,7 +3,7 @@ * Diag Function Device - Route ARM9 and ARM11 DIAG messages * between HOST and DEVICE. * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2008-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2008-2021, The Linux Foundation. All rights reserved. * Author: Brian Swetland */ #include @@ -510,7 +510,7 @@ int usb_diag_read(struct usb_diag_ch *ch, struct diag_request *d_req) if (list_empty(&ctxt->read_pool)) { spin_unlock_irqrestore(&ctxt->lock, flags); - ERROR(ctxt->cdev, "%s: no requests available\n", __func__); + pr_err("%s: no requests available\n", __func__); return -EAGAIN; } @@ -536,8 +536,7 @@ int usb_diag_read(struct usb_diag_ch *ch, struct diag_request *d_req) list_add_tail(&req->list, &ctxt->read_pool); /* 1 error message for every 10 sec */ if (__ratelimit(&rl)) - ERROR(ctxt->cdev, "%s: cannot queue read request\n", - __func__); + pr_err("%s: cannot queue read request\n", __func__); if (kref_put(&ctxt->kref, diag_context_release)) /* diag_context_release called spin_unlock already */ @@ -586,7 +585,7 @@ int usb_diag_write(struct usb_diag_ch *ch, struct diag_request *d_req) if (list_empty(&ctxt->write_pool)) { spin_unlock_irqrestore(&ctxt->lock, flags); - ERROR(ctxt->cdev, "%s: no requests available\n", __func__); + pr_err("%s: no requests available\n", __func__); return -EAGAIN; } @@ -614,8 +613,7 @@ int usb_diag_write(struct usb_diag_ch *ch, struct diag_request *d_req) ctxt->dpkts_tolaptop_pending--; /* 1 error message for every 10 sec */ if (__ratelimit(&rl)) - ERROR(ctxt->cdev, "%s: cannot queue read request\n", - __func__); + pr_err("%s: cannot queue read request\n", __func__); if (kref_put(&ctxt->kref, diag_context_release)) /* diag_context_release called spin_unlock already */ diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index 8bce7ce0eaaa..0b7b4d09785b 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -2229,8 +2229,6 @@ static int do_set_interface(struct fsg_common *common, struct fsg_dev *new_fsg) fsg->bulk_out_enabled = 0; } - /* allow usb LPM after eps are disabled */ - usb_gadget_autopm_put_async(common->gadget); common->fsg = NULL; wake_up(&common->fsg_wait); } @@ -2295,9 +2293,6 @@ static int fsg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct fsg_dev *fsg = fsg_from_func(f); - /* prevents usb LPM until thread runs to completion */ - usb_gadget_autopm_get_async(fsg->common->gadget); - __raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE, fsg); return USB_GADGET_DELAYED_STATUS; } diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 0a16cbd4e528..7967196aedb8 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -439,6 +439,21 @@ config USB_GADGET_XILINX dynamically linked module called "udc-xilinx" and force all gadget drivers to also be dynamically linked. +config USB_CI13XXX_MSM + tristate "MIPS USB CI13xxx for MSM" + select USB_MSM_OTG + help + MSM SoC has chipidea USB controller. This driver uses + ci13xxx_udc core. + This driver depends on OTG driver for PHY initialization, + clock management, powering up VBUS, and power management. + This driver is not supported on boards like trout which + has an external PHY. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "ci13xxx_msm" and force all + gadget drivers to also be dynamically linked. + source "drivers/usb/gadget/udc/aspeed-vhub/Kconfig" # diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 1a4ea98cac2a..b167aeffd118 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -782,6 +782,18 @@ config USB_HCD_SSB If unsure, say N. +config USB_EHCI_MSM + tristate "Support for Qualcomm Technologies, Inc. QSD/MSM on-chip EHCI USB controller" + depends on ARCH_QCOM + select USB_EHCI_ROOT_HUB_TT + help + Enables support for the USB Host controller present on the + Qualcomm Technologies, Inc. chipsets. Root Hub has inbuilt TT. + This driver depends on OTG driver for PHY initialization, + clock management, powering up VBUS, and power management. + This driver is not supported on boards like trout which + has an external PHY. + config USB_HCD_TEST_MODE bool "HCD test mode support" ---help--- diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index e6235269c151..dcff0ddcdbf6 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -50,6 +50,7 @@ obj-$(CONFIG_USB_EHCI_HCD_SPEAR) += ehci-spear.o obj-$(CONFIG_USB_EHCI_HCD_STI) += ehci-st.o obj-$(CONFIG_USB_EHCI_EXYNOS) += ehci-exynos.o obj-$(CONFIG_USB_EHCI_HCD_AT91) += ehci-atmel.o +obj-$(CONFIG_USB_EHCI_MSM) += ehci-msm.o obj-$(CONFIG_USB_EHCI_TEGRA) += ehci-tegra.o obj-$(CONFIG_USB_W90X900_EHCI) += ehci-w90x900.o diff --git a/drivers/usb/host/ehci-msm.c b/drivers/usb/host/ehci-msm.c new file mode 100644 index 000000000000..642d88033946 --- /dev/null +++ b/drivers/usb/host/ehci-msm.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * EHCI HSUSB Host Controller Driver Implementation + * + * Copyright (c) 2008-2011, 2021, The Linux Foundation. All rights reserved. + * + * Partly derived from ehci-fsl.c and ehci-hcd.c + * Copyright (c) 2000-2004 by David Brownell + * Copyright (c) 2005 MontaVista Software + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License 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. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define MSM_USB_BASE (hcd->regs) + +#define DRIVER_DESC "Qualcomm Technologies, Inc. On-Chip EHCI Host Controller" + +static const char hcd_name[] = "ehci-msm"; +static struct hc_driver __read_mostly msm_hc_driver; + +static int ehci_msm_reset(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int retval; + + ehci->caps = USB_CAPLENGTH; + hcd->has_tt = 1; + + retval = ehci_setup(hcd); + if (retval) + return retval; + + /* select ULPI phy and clear other status/control bits in PORTSC */ + writel_relaxed(PORTSC_PTS_ULPI, USB_PORTSC); + /* bursts of unspecified length. */ + writel_relaxed(0, USB_AHBBURST); + /* Use the AHB transactor, allow posted data writes */ + writel_relaxed(0x8, USB_AHBMODE); + /* Disable streaming mode and select host mode */ + writel_relaxed(0x13, USB_USBMODE); + /* Disable ULPI_TX_PKT_EN_CLR_FIX which is valid only for HSIC */ + writel_relaxed(readl_relaxed(USB_GENCONFIG_2) & ~ULPI_TX_PKT_EN_CLR_FIX, + USB_GENCONFIG_2); + + return 0; +} + +static int ehci_msm_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct resource *res; + struct usb_phy *phy; + int ret; + + dev_dbg(&pdev->dev, "ehci_msm proble\n"); + + hcd = usb_create_hcd(&msm_hc_driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + dev_err(&pdev->dev, "Unable to create HCD\n"); + return -ENOMEM; + } + + ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to get IRQ resource\n"); + goto put_hcd; + } + hcd->irq = ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Unable to get memory resource\n"); + ret = -ENODEV; + goto put_hcd; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = devm_ioremap(&pdev->dev, hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto put_hcd; + } + + /* + * If there is an OTG driver, let it take care of PHY initialization, + * clock management, powering up VBUS, mapping of registers address + * space and power management. + */ + if (pdev->dev.of_node) + phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0); + else + phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); + + if (IS_ERR(phy)) { + if (PTR_ERR(phy) == -EPROBE_DEFER) { + dev_err(&pdev->dev, "unable to find transceiver\n"); + ret = -EPROBE_DEFER; + goto put_hcd; + } + phy = NULL; + } + + hcd->usb_phy = phy; + device_init_wakeup(&pdev->dev, 1); + + if (phy && phy->otg) { + /* + * MSM OTG driver takes care of adding the HCD and + * placing hardware into low power mode via runtime PM. + */ + ret = otg_set_host(phy->otg, &hcd->self); + if (ret < 0) { + dev_err(&pdev->dev, "unable to register with transceiver\n"); + goto put_hcd; + } + + pm_runtime_no_callbacks(&pdev->dev); + pm_runtime_enable(&pdev->dev); + } else { + ret = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); + if (ret) + goto put_hcd; + } + + return 0; + +put_hcd: + usb_put_hcd(hcd); + + return ret; +} + +static int ehci_msm_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + device_init_wakeup(&pdev->dev, 0); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + + if (hcd->usb_phy && hcd->usb_phy->otg) + otg_set_host(hcd->usb_phy->otg, NULL); + else + usb_remove_hcd(hcd); + + usb_put_hcd(hcd); + + return 0; +} + +#ifdef CONFIG_PM +static int ehci_msm_runtime_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + bool do_wakeup = device_may_wakeup(dev); + + dev_dbg(dev, "ehci-msm PM suspend\n"); + + /* Only call ehci_suspend if ehci_setup has been done */ + if (ehci->sbrn) + return ehci_suspend(hcd, do_wakeup); + + return 0; +} + +static int ehci_msm_runtime_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + dev_dbg(dev, "ehci-msm PM resume\n"); + + /* Only call ehci_resume if ehci_setup has been done */ + if (ehci->sbrn) + ehci_resume(hcd, false); + + return 0; +} + +#else +#define ehci_msm_pm_suspend NULL +#define ehci_msm_pm_resume NULL +#endif + +static const struct dev_pm_ops ehci_msm_dev_pm_ops = { + SET_RUNTIME_PM_OPS(ehci_msm_runtime_suspend, ehci_msm_runtime_resume, + NULL) +}; + +static const struct acpi_device_id msm_ehci_acpi_ids[] = { + { "QCOM8040", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, msm_ehci_acpi_ids); + +static const struct of_device_id msm_ehci_dt_match[] = { + { .compatible = "qcom,ehci-host", }, + {} +}; +MODULE_DEVICE_TABLE(of, msm_ehci_dt_match); + +static struct platform_driver ehci_msm_driver = { + .probe = ehci_msm_probe, + .remove = ehci_msm_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "msm_hsusb_host", + .pm = &ehci_msm_dev_pm_ops, + .of_match_table = msm_ehci_dt_match, + .acpi_match_table = ACPI_PTR(msm_ehci_acpi_ids), + }, +}; + +static const struct ehci_driver_overrides msm_overrides __initconst = { + .reset = ehci_msm_reset, +}; + +static int __init ehci_msm_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + ehci_init_driver(&msm_hc_driver, &msm_overrides); + return platform_driver_register(&ehci_msm_driver); +} +module_init(ehci_msm_init); + +static void __exit ehci_msm_cleanup(void) +{ + platform_driver_unregister(&ehci_msm_driver); +} +module_exit(ehci_msm_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_ALIAS("platform:msm-ehci"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/ssusb-redriver-nb7vpq904m.c b/drivers/usb/misc/ssusb-redriver-nb7vpq904m.c index 3d75aa18fd4e..99cb899642a1 100644 --- a/drivers/usb/misc/ssusb-redriver-nb7vpq904m.c +++ b/drivers/usb/misc/ssusb-redriver-nb7vpq904m.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2018-2021, The Linux Foundation. All rights reserved. */ #include @@ -153,6 +153,8 @@ struct ssusb_redriver { u8 loss_match[CHAN_MODE_NUM][CHANNEL_NUM]; u8 flat_gain[CHAN_MODE_NUM][CHANNEL_NUM]; + u8 gen_dev_val; + struct dentry *debug_root; }; @@ -210,7 +212,7 @@ static void ssusb_redriver_gen_dev_set( struct ssusb_redriver *redriver, bool on) { int ret; - u8 val; + u8 val, oldval; val = 0; @@ -276,11 +278,18 @@ static void ssusb_redriver_gen_dev_set( } /* exit/enter deep-sleep power mode */ - if (on) + oldval = redriver->gen_dev_val; + if (on) { val |= CHIP_EN; - else - val &= ~CHIP_EN; + if (val == oldval) + return; + } else { + /* no operation if already disabled */ + if (oldval && !(oldval & CHIP_EN)) + return; + val &= ~CHIP_EN; + } ret = redriver_i2c_reg_set(redriver, GEN_DEV_SET_REG, val); if (ret < 0) goto err_exit; @@ -289,6 +298,7 @@ static void ssusb_redriver_gen_dev_set( "successfully (%s) the redriver chip, reg 0x00 = 0x%x\n", on ? "ENABLE":"DISABLE", val); + redriver->gen_dev_val = val; return; err_exit: @@ -1142,8 +1152,10 @@ static int __maybe_unused redriver_i2c_suspend(struct device *dev) __func__); /* Disable redriver chip when USB cable disconnected */ - if (!redriver->vbus_active && !redriver->host_active && - redriver->op_mode != OP_MODE_DP) + if ((!redriver->vbus_active && !redriver->host_active && + redriver->op_mode != OP_MODE_DP) || + (redriver->host_active && + redriver->op_mode == OP_MODE_USB_AND_DP)) ssusb_redriver_gen_dev_set(redriver, false); flush_workqueue(redriver->redriver_wq); @@ -1159,6 +1171,10 @@ static int __maybe_unused redriver_i2c_resume(struct device *dev) dev_dbg(redriver->dev, "%s: SS USB redriver resume.\n", __func__); + if (redriver->host_active && + redriver->op_mode == OP_MODE_USB_AND_DP) + ssusb_redriver_gen_dev_set(redriver, true); + flush_workqueue(redriver->redriver_wq); return 0; diff --git a/drivers/usb/pd/policy_engine.c b/drivers/usb/pd/policy_engine.c index 01e40091dfeb..885c9305e8b4 100644 --- a/drivers/usb/pd/policy_engine.c +++ b/drivers/usb/pd/policy_engine.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. */ #include @@ -381,6 +381,7 @@ struct usbpd { struct workqueue_struct *wq; struct work_struct sm_work; struct work_struct start_periph_work; + struct work_struct restart_host_work; struct hrtimer timer; bool sm_queued; @@ -406,6 +407,8 @@ struct usbpd { bool peer_usb_comm; bool peer_pr_swap; bool peer_dr_swap; + bool no_usb3dp_concurrency; + bool pd20_source_only; u32 sink_caps[7]; int num_sink_caps; @@ -472,6 +475,7 @@ struct usbpd { u8 src_cap_ext_db[PD_SRC_CAP_EXT_DB_LEN]; bool send_get_pps_status; u32 pps_status_db; + bool pps_disabled; bool send_get_status; u8 status_db[PD_STATUS_DB_LEN]; bool send_get_battery_cap; @@ -609,6 +613,26 @@ static void start_usb_peripheral_work(struct work_struct *w) } } +static void restart_usb_host_work(struct work_struct *w) +{ + struct usbpd *pd = container_of(w, struct usbpd, restart_host_work); + int ret; + + if (!pd->no_usb3dp_concurrency) + return; + + stop_usb_host(pd); + + /* blocks until USB host is completely stopped */ + ret = extcon_blocking_sync(pd->extcon, EXTCON_USB_HOST, STOP_USB_HOST); + if (ret) { + usbpd_err(&pd->dev, "err(%d) stopping host", ret); + return; + } + + start_usb_host(pd, false); +} + /** * This API allows client driver to request for releasing SS lanes. It should * not be called from atomic context. @@ -1564,6 +1588,7 @@ static void handle_vdm_rx(struct usbpd *pd, struct rx_msg *rx_msg) /* Set to USB and DP cocurrency mode */ extcon_blocking_sync(pd->extcon, EXTCON_DISP_DP, 2); + queue_work(pd->wq, &pd->restart_host_work); } /* if it's a supported SVID, pass the message to the handler */ @@ -2106,7 +2131,11 @@ static int usbpd_startup_common(struct usbpd *pd, * support up to PD 3.0; if peer is 2.0 * phy_msg_received() will handle the downgrade. */ - pd->spec_rev = USBPD_REV_30; + if ((pd->pd20_source_only) && + pd->current_state == PE_SRC_STARTUP) + pd->spec_rev = USBPD_REV_20; + else + pd->spec_rev = USBPD_REV_30; if (pd->pd_phy_opened) { pd_phy_close(); @@ -2234,7 +2263,10 @@ static void handle_state_src_startup_wait_for_vdm_resp(struct usbpd *pd, * Emarker may have negotiated down to rev 2.0. * Reset to 3.0 to begin SOP communication with sink */ - pd->spec_rev = USBPD_REV_30; + if (pd->pd20_source_only) + pd->spec_rev = USBPD_REV_20; + else + pd->spec_rev = USBPD_REV_30; pd->current_state = PE_SRC_SEND_CAPABILITIES; kick_sm(pd, ms); @@ -2669,6 +2701,7 @@ static void handle_state_snk_wait_for_capabilities(struct usbpd *pd, struct rx_msg *rx_msg) { union power_supply_propval val = {0}; + int i; pd->in_pr_swap = false; val.intval = 0; @@ -2687,6 +2720,14 @@ static void handle_state_snk_wait_for_capabilities(struct usbpd *pd, memcpy(&pd->received_pdos, rx_msg->payload, min_t(size_t, rx_msg->data_len, sizeof(pd->received_pdos))); + /* if pps is disabled clear all the received pdos */ + if (pd->pps_disabled) { + for (i = 0; i < ARRAY_SIZE(pd->received_pdos); i++) + if ((PD_SRC_PDO_TYPE(pd->received_pdos[i]) == + PD_SRC_PDO_TYPE_AUGMENTED)) + pd->received_pdos[i] = 0x00; + } + pd->src_cap_id++; usbpd_set_state(pd, PE_SNK_EVALUATE_CAPABILITY); @@ -2921,6 +2962,7 @@ static bool handle_ctrl_snk_ready(struct usbpd *pd, struct rx_msg *rx_msg) static bool handle_data_snk_ready(struct usbpd *pd, struct rx_msg *rx_msg) { u32 ado; + int i; switch (PD_MSG_HDR_TYPE(rx_msg->hdr)) { case MSG_SOURCE_CAPABILITIES: @@ -2930,6 +2972,14 @@ static bool handle_data_snk_ready(struct usbpd *pd, struct rx_msg *rx_msg) memcpy(&pd->received_pdos, rx_msg->payload, min_t(size_t, rx_msg->data_len, sizeof(pd->received_pdos))); + /* if pps is disabled clear all the received pdos */ + if (pd->pps_disabled) { + for (i = 0; i < ARRAY_SIZE(pd->received_pdos); i++) + if ((PD_SRC_PDO_TYPE(pd->received_pdos[i]) == + PD_SRC_PDO_TYPE_AUGMENTED)) + pd->received_pdos[i] = 0x00; + } + pd->src_cap_id++; usbpd_set_state(pd, PE_SNK_EVALUATE_CAPABILITY); @@ -4678,6 +4728,7 @@ struct usbpd *usbpd_create(struct device *parent) } INIT_WORK(&pd->sm_work, usbpd_sm); INIT_WORK(&pd->start_periph_work, start_usb_peripheral_work); + INIT_WORK(&pd->restart_host_work, restart_usb_host_work); hrtimer_init(&pd->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); pd->timer.function = pd_timeout; mutex_init(&pd->swap_lock); @@ -4733,6 +4784,9 @@ struct usbpd *usbpd_create(struct device *parent) extcon_set_property_capability(pd->extcon, EXTCON_USB_HOST, EXTCON_PROP_USB_SS); + if (device_property_read_bool(parent, "qcom,no-usb3-dp-concurrency")) + pd->no_usb3dp_concurrency = true; + pd->num_sink_caps = device_property_read_u32_array(parent, "qcom,default-sink-caps", NULL, 0); if (pd->num_sink_caps > 0) { @@ -4771,6 +4825,9 @@ struct usbpd *usbpd_create(struct device *parent) pd->num_sink_caps = ARRAY_SIZE(default_snk_caps); } + if (device_property_read_bool(parent, "qcom,pd-20-source-only")) + pd->pd20_source_only = true; + /* * Register a Type-C class instance (/sys/class/typec/portX). * Note this is different than the /sys/class/usbpd/ created above. @@ -4797,6 +4854,8 @@ struct usbpd *usbpd_create(struct device *parent) } } + pd->pps_disabled = device_property_read_bool(parent, + "qcom,pps-disabled"); pd->current_pr = PR_NONE; pd->current_dr = DR_NONE; list_add_tail(&pd->instance, &_usbpd); diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 13d33001038e..b8edc5a0e92d 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -137,6 +137,20 @@ config USB_ISP1301 To compile this driver as a module, choose M here: the module will be called phy-isp1301. +config USB_MSM_OTG + tristate "Qualcomm Technologies, Inc. USB OTG controller support" + depends on (USB || USB_GADGET) && (ARCH_QCOM || COMPILE_TEST) + depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y' + depends on RESET_CONTROLLER + select USB_PHY + help + Enable this to support the USB OTG transceiver on the chipset. + It handles PHY initialization, clock management, and workarounds required + after resetting the hardware and power management. + This driver is required even for peripheral only or host only + mode configurations. This driver is not supported on boards like trout which + has an external PHY. + config USB_MSM_SSPHY_QMP tristate "MSM SSUSB QMP PHY Driver" depends on ARCH_QCOM diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index fe5504d229c7..271403ac1822 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -28,3 +28,4 @@ obj-$(CONFIG_KEYSTONE_USB_PHY) += phy-keystone.o obj-$(CONFIG_MSM_QUSB_PHY) += phy-msm-qusb.o phy-msm-qusb-v2.o obj-$(CONFIG_USB_MSM_SSPHY_QMP) += phy-msm-ssusb-qmp.o obj-$(CONFIG_MSM_HSUSB_PHY) += phy-msm-snps-hs.o +obj-$(CONFIG_USB_MSM_OTG) += phy-msm-usb.o diff --git a/drivers/usb/phy/phy-msm-qusb-v2.c b/drivers/usb/phy/phy-msm-qusb-v2.c index aac61c53bcd1..47d49c92c671 100644 --- a/drivers/usb/phy/phy-msm-qusb-v2.c +++ b/drivers/usb/phy/phy-msm-qusb-v2.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2014-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2021, The Linux Foundation. All rights reserved. */ #include @@ -519,8 +519,7 @@ static void qusb_phy_host_init(struct usb_phy *phy) qphy->host_init_seq_len, 0); if (qphy->efuse_reg) { - if (!qphy->tune_val) - qusb_phy_get_tune1_param(qphy); + qusb_phy_get_tune1_param(qphy); } else { /* For non fused chips we need to write the TUNE1 param as * specified in DT otherwise we will end up writing 0 to @@ -621,8 +620,7 @@ static int qusb_phy_init(struct usb_phy *phy) qusb_phy_write_seq(qphy->base, qphy->qusb_phy_init_seq, qphy->init_seq_len, 0); if (qphy->efuse_reg) { - if (!qphy->tune_val) - qusb_phy_get_tune1_param(qphy); + qusb_phy_get_tune1_param(qphy); pr_debug("%s(): Programming TUNE1 parameter as:%x\n", __func__, qphy->tune_val); diff --git a/drivers/usb/phy/phy-msm-usb.c b/drivers/usb/phy/phy-msm-usb.c new file mode 100644 index 000000000000..5b8edf70124f --- /dev/null +++ b/drivers/usb/phy/phy-msm-usb.c @@ -0,0 +1,5049 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2009-2019, 2021, Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Requested USB votes for BUS bandwidth + * + * USB_NO_PERF_VOTE BUS Vote for inactive USB session or disconnect + * USB_MAX_PERF_VOTE Maximum BUS bandwidth vote + * USB_MIN_PERF_VOTE Minimum BUS bandwidth vote (for some hw same as NO_PERF) + * + */ +enum usb_bus_vote { + USB_NO_PERF_VOTE = 0, + USB_MAX_PERF_VOTE, + USB_MIN_PERF_VOTE, +}; + +/** + * Supported USB modes + * + * USB_PERIPHERAL Only peripheral mode is supported. + * USB_HOST Only host mode is supported. + * USB_OTG OTG mode is supported. + * + */ +enum usb_mode_type { + USB_NONE = 0, + USB_PERIPHERAL, + USB_HOST, + USB_OTG, +}; + +/** + * OTG control + * + * OTG_NO_CONTROL Id/VBUS notifications not required. Useful in host + * only configuration. + * OTG_PHY_CONTROL Id/VBUS notifications comes form USB PHY. + * OTG_PMIC_CONTROL Id/VBUS notifications comes from PMIC hardware. + * OTG_USER_CONTROL Id/VBUS notifcations comes from User via sysfs. + * + */ +enum otg_control_type { + OTG_NO_CONTROL = 0, + OTG_PHY_CONTROL, + OTG_PMIC_CONTROL, + OTG_USER_CONTROL, +}; + +/** + * PHY used in + * + * INVALID_PHY Unsupported PHY + * CI_PHY Chipidea PHY + * SNPS_PICO_PHY Synopsis Pico PHY + * SNPS_FEMTO_PHY Synopsis Femto PHY + * QUSB_ULPI_PHY + * + */ +enum msm_usb_phy_type { + INVALID_PHY = 0, + CI_PHY, /* not supported */ + SNPS_PICO_PHY, + SNPS_FEMTO_PHY, + QUSB_ULPI_PHY, +}; + +#define IDEV_CHG_MAX 1500 +#define IUNIT 100 +#define IDEV_HVDCP_CHG_MAX 1800 + +/** + * struct msm_otg_platform_data - platform device data + * for msm_otg driver. + * @phy_init_seq: PHY configuration sequence values. Value of -1 is reserved as + * "do not overwrite default value at this address". + * @power_budget: VBUS power budget in mA (0 will be treated as 500mA). + * @mode: Supported mode (OTG/peripheral/host). + * @otg_control: OTG switch controlled by user/Id pin + * @default_mode: Default operational mode. Applicable only if + * OTG switch is controller by user. + * @pmic_id_irq: IRQ number assigned for PMIC USB ID line. + * @disable_reset_on_disconnect: perform USB PHY and LINK reset + * on USB cable disconnection. + * @pnoc_errata_fix: workaround needed for PNOC hardware bug that + * affects USB performance. + * @enable_lpm_on_suspend: Enable the USB core to go into Low + * Power Mode, when USB bus is suspended but cable + * is connected. + * @core_clk_always_on_workaround: Don't disable core_clk when + * USB enters LPM. + * @delay_lpm_on_disconnect: Use a delay before entering LPM + * upon USB cable disconnection. + * @enable_sec_phy: Use second HSPHY with USB2 core + * @bus_scale_table: parameters for bus bandwidth requirements + * @log2_itc: value of 2^(log2_itc-1) will be used as the + * interrupt threshold (ITC), when log2_itc is + * between 1 to 7. + * @l1_supported: enable link power management support. + * @dpdm_pulldown_added: Indicates whether pull down resistors are + * connected on data lines or not. + * @vddmin_gpio: dedictaed gpio in the platform that is used for + * pullup the D+ line in case of bus suspend with + * phy retention. + * @enable_ahb2ahb_bypass: Indicates whether enable AHB2AHB BYPASS + * mode with controller in device mode. + * @bool disable_retention_with_vdd_min: Indicates whether to enable + allowing VDDmin without putting PHY into retention. + * @bool enable_phy_id_pullup: Indicates whether phy id pullup is + enabled or not. + * @usb_id_gpio: Gpio used for USB ID detection. + * @hub_reset_gpio: Gpio used for hub reset. + * @switch_sel_gpio: Gpio used for controlling switch that + routing D+/D- from the USB HUB to the USB jack type B + for peripheral mode. + * @bool phy_dvdd_always_on: PHY DVDD is supplied by always on PMIC LDO. + * @bool emulation: Indicates whether we are running on emulation platform. + * @bool enable_streaming: Indicates whether streaming to be enabled by default. + * @bool enable_axi_prefetch: Indicates whether AXI Prefetch interface is used + for improving data performance. + * @usbeth_reset_gpio: Gpio used for external usb-to-eth reset. + */ +struct msm_otg_platform_data { + int *phy_init_seq; + int phy_init_sz; + unsigned int power_budget; + enum usb_mode_type mode; + enum otg_control_type otg_control; + enum usb_mode_type default_mode; + enum msm_usb_phy_type phy_type; + int pmic_id_irq; + bool disable_reset_on_disconnect; + bool pnoc_errata_fix; + bool enable_lpm_on_dev_suspend; + bool core_clk_always_on_workaround; + bool delay_lpm_on_disconnect; + bool dp_manual_pullup; + bool enable_sec_phy; + struct msm_bus_scale_pdata *bus_scale_table; + int log2_itc; + bool l1_supported; + bool dpdm_pulldown_added; + int vddmin_gpio; + bool enable_ahb2ahb_bypass; + bool disable_retention_with_vdd_min; + bool enable_phy_id_pullup; + int usb_id_gpio; + int hub_reset_gpio; + int usbeth_reset_gpio; + int switch_sel_gpio; + bool phy_dvdd_always_on; + bool emulation; + bool enable_streaming; + bool enable_axi_prefetch; + bool vbus_low_as_hostmode; + bool phy_id_high_as_peripheral; +}; + +#define SDP_CHECK_DELAY_MS 10000 /* in ms */ +#define SDP_CHECK_BOOT_DELAY_MS 30000 /* in ms */ + +#define MSM_USB_BASE (motg->regs) +#define MSM_USB_PHY_CSR_BASE (motg->phy_csr_regs) + +#define DRIVER_NAME "msm_otg" + +#define ULPI_IO_TIMEOUT_USEC (10 * 1000) +#define USB_PHY_3P3_VOL_MIN 3050000 /* uV */ +#define USB_PHY_3P3_VOL_MAX 3300000 /* uV */ +#define USB_PHY_3P3_HPM_LOAD 50000 /* uA */ +#define USB_PHY_3P3_LPM_LOAD 4000 /* uA */ + +#define USB_PHY_1P8_VOL_MIN 1800000 /* uV */ +#define USB_PHY_1P8_VOL_MAX 1800000 /* uV */ +#define USB_PHY_1P8_HPM_LOAD 50000 /* uA */ +#define USB_PHY_1P8_LPM_LOAD 4000 /* uA */ + +#define USB_DEFAULT_SYSTEM_CLOCK 80000000 /* 80 MHz */ + +#define PM_QOS_SAMPLE_SEC 2 +#define PM_QOS_THRESHOLD 400 + +enum msm_otg_phy_reg_mode { + USB_PHY_REG_OFF, + USB_PHY_REG_ON, + USB_PHY_REG_LPM_ON, + USB_PHY_REG_LPM_OFF, + USB_PHY_REG_3P3_ON, + USB_PHY_REG_3P3_OFF, +}; + +static char *override_phy_init; + +static ssize_t override_phy_init_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%s\n", override_phy_init); +} + +static ssize_t override_phy_init_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + override_phy_init = kstrndup(buf, count, GFP_KERNEL); + if (!*override_phy_init) + return -ENOSPC; + + return count; +} +static DEVICE_ATTR_RW(override_phy_init_enable); + +unsigned int lpm_disconnect_thresh = 1000; +static ssize_t lpm_disconnect_thresh_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", lpm_disconnect_thresh); +} + +static ssize_t lpm_disconnect_thresh_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val = 1000; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + lpm_disconnect_thresh = val; + + return count; +} +static DEVICE_ATTR_RW(lpm_disconnect_thresh_enable); + +static bool floated_charger_enable; +static ssize_t floated_charger_enable_value_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", floated_charger_enable); +} + +static ssize_t floated_charger_enable_value_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + bool val; + + if (kstrtobool(buf, &val)) + return -EINVAL; + floated_charger_enable = val; + + return count; +} +static DEVICE_ATTR_RW(floated_charger_enable_value); + +/* by default debugging is enabled */ +static unsigned int enable_dbg_log = 1; +static ssize_t debug_log_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", enable_dbg_log); +} + +static ssize_t debug_log_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val = 1; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + enable_dbg_log = val; + + return count; +} +static DEVICE_ATTR_RW(debug_log_enable); + +/* Max current to be drawn for DCP charger */ +static int dcp_max_current = IDEV_CHG_MAX; +static ssize_t dcp_max_current_value_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", dcp_max_current); +} + +static ssize_t dcp_max_current_value_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + dcp_max_current = IDEV_CHG_MAX; + + return count; +} +static DEVICE_ATTR_RW(dcp_max_current_value); + + +static bool chg_detection_for_float_charger; +static ssize_t chg_detection_for_float_charger_value_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", + chg_detection_for_float_charger); +} + +static ssize_t chg_detection_for_float_charger_value_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + bool val; + + if (kstrtobool(buf, &val)) + return -EINVAL; + chg_detection_for_float_charger = val; + + return count; +} +static DEVICE_ATTR_RW(chg_detection_for_float_charger_value); + +static struct msm_otg *the_msm_otg; +static bool debug_bus_voting_enabled; + +static struct regulator *hsusb_3p3; +static struct regulator *hsusb_1p8; +static struct regulator *hsusb_vdd; +static struct regulator *vbus_otg; +static struct power_supply *psy; + +static int vdd_val[VDD_VAL_MAX]; +static u32 bus_freqs[USB_NOC_NUM_VOTE][USB_NUM_BUS_CLOCKS] /*bimc,snoc,pcnoc*/; +static char bus_clkname[USB_NUM_BUS_CLOCKS][20] = {"bimc_clk", "snoc_clk", + "pcnoc_clk"}; +static bool bus_clk_rate_set; + +static inline void dbg_inc(unsigned int *idx) +{ + *idx = (*idx + 1) & (DEBUG_MAX_MSG-1); +} + +static void +msm_otg_dbg_log_event(struct usb_phy *phy, char *event, int d1, int d2) +{ + struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + unsigned long flags; + unsigned long long t; + unsigned long nanosec; + + if (!enable_dbg_log) + return; + + write_lock_irqsave(&motg->dbg_lock, flags); + t = cpu_clock(smp_processor_id()); + nanosec = do_div(t, 1000000000)/1000; + scnprintf(motg->buf[motg->dbg_idx], DEBUG_MSG_LEN, + "[%5lu.%06lu]: %s :%d:%d", + (unsigned long)t, nanosec, event, d1, d2); + + motg->dbg_idx++; + motg->dbg_idx = motg->dbg_idx % DEBUG_MAX_MSG; + write_unlock_irqrestore(&motg->dbg_lock, flags); +} + +static int msm_hsusb_ldo_init(struct msm_otg *motg, int init) +{ + int rc = 0; + + if (init) { + hsusb_3p3 = devm_regulator_get(motg->phy.dev, "HSUSB_3p3"); + if (IS_ERR(hsusb_3p3)) { + dev_err(motg->phy.dev, "unable to get hsusb 3p3\n"); + return PTR_ERR(hsusb_3p3); + } + + rc = regulator_set_voltage(hsusb_3p3, USB_PHY_3P3_VOL_MIN, + USB_PHY_3P3_VOL_MAX); + if (rc) { + dev_err(motg->phy.dev, "unable to set voltage level for hsusb 3p3\n" + ); + return rc; + } + hsusb_1p8 = devm_regulator_get(motg->phy.dev, "HSUSB_1p8"); + if (IS_ERR(hsusb_1p8)) { + dev_err(motg->phy.dev, "unable to get hsusb 1p8\n"); + rc = PTR_ERR(hsusb_1p8); + goto put_3p3_lpm; + } + rc = regulator_set_voltage(hsusb_1p8, USB_PHY_1P8_VOL_MIN, + USB_PHY_1P8_VOL_MAX); + if (rc) { + dev_err(motg->phy.dev, "unable to set voltage level for hsusb 1p8\n" + ); + goto put_1p8; + } + + return 0; + } + +put_1p8: + regulator_set_voltage(hsusb_1p8, 0, USB_PHY_1P8_VOL_MAX); +put_3p3_lpm: + regulator_set_voltage(hsusb_3p3, 0, USB_PHY_3P3_VOL_MAX); + return rc; +} + +static int msm_hsusb_config_vddcx(int high) +{ + struct msm_otg *motg = the_msm_otg; + int max_vol = vdd_val[VDD_MAX]; + int min_vol; + int ret; + + min_vol = vdd_val[!!high]; + ret = regulator_set_voltage(hsusb_vdd, min_vol, max_vol); + if (ret) { + pr_err("%s: unable to set the voltage for regulator HSUSB_VDDCX\n", + __func__); + return ret; + } + + pr_debug("%s: min_vol:%d max_vol:%d\n", __func__, min_vol, max_vol); + msm_otg_dbg_log_event(&motg->phy, "CONFIG VDDCX", min_vol, max_vol); + + return ret; +} + +static int msm_hsusb_ldo_enable(struct msm_otg *motg, + enum msm_otg_phy_reg_mode mode) +{ + int ret = 0; + + if (IS_ERR(hsusb_1p8)) { + dev_err(motg->phy.dev, "%s: HSUSB_1p8 is not initialized\n", + __func__); + return -ENODEV; + } + + if (IS_ERR(hsusb_3p3)) { + dev_err(motg->phy.dev, "%s: HSUSB_3p3 is not initialized\n", + __func__); + return -ENODEV; + } + + switch (mode) { + case USB_PHY_REG_ON: + ret = regulator_set_load(hsusb_1p8, USB_PHY_1P8_HPM_LOAD); + if (ret < 0) { + dev_err(motg->phy.dev, + "%s: Unable to set HPM of the regulator HSUSB_1p8\n", + __func__); + return ret; + } + + ret = regulator_enable(hsusb_1p8); + if (ret) { + dev_err(motg->phy.dev, "%s: unable to enable the hsusb 1p8\n", + __func__); + regulator_set_load(hsusb_1p8, 0); + return ret; + } + + /* fall through */ + case USB_PHY_REG_3P3_ON: + ret = regulator_set_load(hsusb_3p3, USB_PHY_3P3_HPM_LOAD); + if (ret < 0) { + dev_err(motg->phy.dev, "%s: Unable to set HPM of the regulator HSUSB_3p3\n", + __func__); + if (mode == USB_PHY_REG_ON) { + regulator_set_load(hsusb_1p8, 0); + regulator_disable(hsusb_1p8); + } + return ret; + } + + ret = regulator_enable(hsusb_3p3); + if (ret) { + dev_err(motg->phy.dev, "%s: unable to enable the hsusb 3p3\n", + __func__); + regulator_set_load(hsusb_3p3, 0); + if (mode == USB_PHY_REG_ON) { + regulator_set_load(hsusb_1p8, 0); + regulator_disable(hsusb_1p8); + } + return ret; + } + + break; + + case USB_PHY_REG_OFF: + ret = regulator_disable(hsusb_1p8); + if (ret) { + dev_err(motg->phy.dev, "%s: unable to disable the hsusb 1p8\n", + __func__); + return ret; + } + + ret = regulator_set_load(hsusb_1p8, 0); + if (ret < 0) + dev_err(motg->phy.dev, "%s: Unable to set LPM of the regulator HSUSB_1p8\n", + __func__); + + /* fall through */ + case USB_PHY_REG_3P3_OFF: + ret = regulator_disable(hsusb_3p3); + if (ret) { + dev_err(motg->phy.dev, "%s: unable to disable the hsusb 3p3\n", + __func__); + return ret; + } + ret = regulator_set_load(hsusb_3p3, 0); + if (ret < 0) + dev_err(motg->phy.dev, "%s: Unable to set LPM of the regulator HSUSB_3p3\n", + __func__); + + break; + + case USB_PHY_REG_LPM_ON: + ret = regulator_set_load(hsusb_1p8, USB_PHY_1P8_LPM_LOAD); + if (ret < 0) { + dev_err(motg->phy.dev, "%s: Unable to set LPM of the regulator HSUSB_1p8\n", + __func__); + return ret; + } + + ret = regulator_set_load(hsusb_3p3, USB_PHY_3P3_LPM_LOAD); + if (ret < 0) { + dev_err(motg->phy.dev, "%s: Unable to set LPM of the regulator HSUSB_3p3\n", + __func__); + regulator_set_load(hsusb_1p8, USB_PHY_REG_ON); + return ret; + } + + break; + + case USB_PHY_REG_LPM_OFF: + ret = regulator_set_load(hsusb_1p8, USB_PHY_1P8_HPM_LOAD); + if (ret < 0) { + dev_err(motg->phy.dev, "%s: Unable to set HPM of the regulator HSUSB_1p8\n", + __func__); + return ret; + } + + ret = regulator_set_load(hsusb_3p3, USB_PHY_3P3_HPM_LOAD); + if (ret < 0) { + dev_err(motg->phy.dev, "%s: Unable to set HPM of the regulator HSUSB_3p3\n", + __func__); + regulator_set_load(hsusb_1p8, USB_PHY_REG_ON); + return ret; + } + + break; + + default: + dev_err(motg->phy.dev, "%s: Unsupported mode (%d).\n", + __func__, mode); + return -ENOTSUPP; + } + + dev_dbg(motg->phy.dev, "%s: USB reg mode (%d) (OFF/HPM/LPM)\n", + __func__, mode); + msm_otg_dbg_log_event(&motg->phy, "USB REG MODE", mode, ret); + return ret < 0 ? ret : 0; +} + +static int ulpi_read(struct usb_phy *phy, u32 reg) +{ + struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + int cnt = 0; + + if (motg->pdata->emulation) + return 0; + + if (motg->pdata->phy_type == QUSB_ULPI_PHY && reg > 0x3F) { + dev_dbg(phy->dev, + "%s: ULPI vendor-specific reg 0x%02x not supported\n", + __func__, reg); + return 0; + } + + /* initiate read operation */ + writel_relaxed(ULPI_RUN | ULPI_READ | ULPI_ADDR(reg), + USB_ULPI_VIEWPORT); + + /* wait for completion */ + while (cnt < ULPI_IO_TIMEOUT_USEC) { + if (!(readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_RUN)) + break; + udelay(1); + cnt++; + } + + if (cnt >= ULPI_IO_TIMEOUT_USEC) { + dev_err(phy->dev, "%s: timeout %08x\n", __func__, + readl_relaxed(USB_ULPI_VIEWPORT)); + dev_err(phy->dev, "PORTSC: %08x USBCMD: %08x\n", + readl_relaxed(USB_PORTSC), readl_relaxed(USB_USBCMD)); + return -ETIMEDOUT; + } + return ULPI_DATA_READ(readl_relaxed(USB_ULPI_VIEWPORT)); +} + +static int ulpi_write(struct usb_phy *phy, u32 val, u32 reg) +{ + struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + int cnt = 0; + + if (motg->pdata->emulation) + return 0; + + if (motg->pdata->phy_type == QUSB_ULPI_PHY && reg > 0x3F) { + dev_dbg(phy->dev, "%s: ULPI vendor-specific reg 0x%02x not supported\n", + __func__, reg); + return 0; + } + + /* initiate write operation */ + writel_relaxed(ULPI_RUN | ULPI_WRITE | + ULPI_ADDR(reg) | ULPI_DATA(val), + USB_ULPI_VIEWPORT); + + /* wait for completion */ + while (cnt < ULPI_IO_TIMEOUT_USEC) { + if (!(readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_RUN)) + break; + udelay(1); + cnt++; + } + + if (cnt >= ULPI_IO_TIMEOUT_USEC) { + dev_err(phy->dev, "%s: timeout\n", __func__); + dev_err(phy->dev, "PORTSC: %08x USBCMD: %08x\n", + readl_relaxed(USB_PORTSC), readl_relaxed(USB_USBCMD)); + return -ETIMEDOUT; + } + return 0; +} + +static struct usb_phy_io_ops msm_otg_io_ops = { + .read = ulpi_read, + .write = ulpi_write, +}; + +static void ulpi_init(struct msm_otg *motg) +{ + struct msm_otg_platform_data *pdata = motg->pdata; + int aseq[10]; + int *seq = NULL; + + if (override_phy_init) { + pr_debug("%s(): HUSB PHY Init:%s\n", __func__, + override_phy_init); + get_options(override_phy_init, ARRAY_SIZE(aseq), aseq); + seq = &aseq[1]; + } else { + seq = pdata->phy_init_seq; + } + + if (!seq) + return; + + while (seq[0] >= 0) { + if (override_phy_init) + pr_debug("ulpi: write 0x%02x to 0x%02x\n", + seq[0], seq[1]); + + dev_vdbg(motg->phy.dev, "ulpi: write 0x%02x to 0x%02x\n", + seq[0], seq[1]); + msm_otg_dbg_log_event(&motg->phy, "ULPI WRITE", seq[0], seq[1]); + ulpi_write(&motg->phy, seq[0], seq[1]); + seq += 2; + } +} + +static int msm_otg_phy_clk_reset(struct msm_otg *motg) +{ + int ret; + + if (!motg->phy_reset_clk && !motg->phy_reset) + return 0; + + if (motg->sleep_clk) + clk_disable_unprepare(motg->sleep_clk); + if (motg->phy_csr_clk) + clk_disable_unprepare(motg->phy_csr_clk); + + ret = reset_control_assert(motg->phy_reset); + if (ret) { + pr_err("phy_reset_clk assert failed %d\n", ret); + return ret; + } + /* + * As per databook, 10 usec delay is required between + * PHY POR assert and de-assert. + */ + usleep_range(10, 15); + ret = reset_control_deassert(motg->phy_reset); + if (ret) { + pr_err("phy_reset_clk de-assert failed %d\n", ret); + return ret; + } + /* + * As per databook, it takes 75 usec for PHY to stabilize + * after the reset. + */ + usleep_range(80, 100); + + if (motg->phy_csr_clk) + clk_prepare_enable(motg->phy_csr_clk); + if (motg->sleep_clk) + clk_prepare_enable(motg->sleep_clk); + + return 0; +} + +static int msm_otg_link_clk_reset(struct msm_otg *motg, bool assert) +{ + int ret; + + if (assert) { + /* Using asynchronous block reset to the hardware */ + dev_dbg(motg->phy.dev, "block_reset ASSERT\n"); + clk_disable_unprepare(motg->pclk); + clk_disable_unprepare(motg->core_clk); + ret = reset_control_assert(motg->core_reset); + if (ret) + dev_err(motg->phy.dev, "usb hs_clk assert failed\n"); + } else { + dev_dbg(motg->phy.dev, "block_reset DEASSERT\n"); + ret = reset_control_deassert(motg->core_reset); + ndelay(200); + ret = clk_prepare_enable(motg->core_clk); + WARN(ret, "USB core_clk enable failed\n"); + ret = clk_prepare_enable(motg->pclk); + WARN(ret, "USB pclk enable failed\n"); + if (ret) + dev_err(motg->phy.dev, "usb hs_clk deassert failed\n"); + } + return ret; +} + +static int msm_otg_phy_reset(struct msm_otg *motg) +{ + u32 val; + int ret; + struct msm_otg_platform_data *pdata = motg->pdata; + + /* + * AHB2AHB Bypass mode shouldn't be enable before doing + * async clock reset. If it is enable, disable the same. + */ + val = readl_relaxed(USB_AHBMODE); + if (val & AHB2AHB_BYPASS) { + pr_err("%s(): AHB2AHB_BYPASS SET: AHBMODE:%x\n", + __func__, val); + val &= ~AHB2AHB_BYPASS_BIT_MASK; + writel_relaxed(val | AHB2AHB_BYPASS_CLEAR, USB_AHBMODE); + pr_err("%s(): AHBMODE: %x\n", __func__, + readl_relaxed(USB_AHBMODE)); + } + + ret = msm_otg_link_clk_reset(motg, 1); + if (ret) + return ret; + + msm_otg_phy_clk_reset(motg); + + /* wait for 1ms delay as suggested in HPG. */ + usleep_range(1000, 1200); + + ret = msm_otg_link_clk_reset(motg, 0); + if (ret) + return ret; + + if (pdata && pdata->enable_sec_phy) + writel_relaxed(readl_relaxed(USB_PHY_CTRL2) | (1<<16), + USB_PHY_CTRL2); + val = readl_relaxed(USB_PORTSC) & ~PORTSC_PTS_MASK; + writel_relaxed(val | PORTSC_PTS_ULPI, USB_PORTSC); + + dev_info(motg->phy.dev, "phy_reset: success\n"); + msm_otg_dbg_log_event(&motg->phy, "PHY RESET SUCCESS", + motg->inputs, motg->phy.otg->state); + return 0; +} + +#define LINK_RESET_TIMEOUT_USEC (250 * 1000) +static int msm_otg_link_reset(struct msm_otg *motg) +{ + int cnt = 0; + struct msm_otg_platform_data *pdata = motg->pdata; + + writel_relaxed(USBCMD_RESET, USB_USBCMD); + while (cnt < LINK_RESET_TIMEOUT_USEC) { + if (!(readl_relaxed(USB_USBCMD) & USBCMD_RESET)) + break; + udelay(1); + cnt++; + } + if (cnt >= LINK_RESET_TIMEOUT_USEC) + return -ETIMEDOUT; + + /* select ULPI phy */ + writel_relaxed(0x80000000, USB_PORTSC); + writel_relaxed(0x0, USB_AHBBURST); + writel_relaxed(0x08, USB_AHBMODE); + + if (pdata && pdata->enable_sec_phy) + writel_relaxed(readl_relaxed(USB_PHY_CTRL2) | (1<<16), + USB_PHY_CTRL2); + return 0; +} + +#define QUSB2PHY_PORT_POWERDOWN 0xB4 +#define QUSB2PHY_PORT_UTMI_CTRL2 0xC4 + +static void msm_usb_phy_reset(struct msm_otg *motg) +{ + u32 val; + int ret, *seq; + + switch (motg->pdata->phy_type) { + case SNPS_PICO_PHY: + /* Assert USB PHY_PON */ + val = readl_relaxed(motg->usb_phy_ctrl_reg); + val &= ~PHY_POR_BIT_MASK; + val |= PHY_POR_ASSERT; + writel_relaxed(val, motg->usb_phy_ctrl_reg); + + /* wait for minimum 10 microseconds as + * suggested in HPG. + */ + usleep_range(10, 15); + + /* Deassert USB PHY_PON */ + val = readl_relaxed(motg->usb_phy_ctrl_reg); + val &= ~PHY_POR_BIT_MASK; + val |= PHY_POR_DEASSERT; + writel_relaxed(val, motg->usb_phy_ctrl_reg); + break; + case QUSB_ULPI_PHY: + ret = reset_control_assert(motg->phy_reset); + if (ret) { + pr_err("phy_reset_clk assert failed %d\n", ret); + break; + } + + /* need to delay 10us for PHY to reset */ + usleep_range(10, 20); + + ret = reset_control_deassert(motg->phy_reset); + if (ret) { + pr_err("phy_reset_clk de-assert failed %d\n", ret); + break; + } + + /* Ensure that RESET operation is completed. */ + mb(); + + writel_relaxed(0x23, + motg->phy_csr_regs + QUSB2PHY_PORT_POWERDOWN); + writel_relaxed(0x0, + motg->phy_csr_regs + QUSB2PHY_PORT_UTMI_CTRL2); + + /* Program tuning parameters for PHY */ + seq = motg->pdata->phy_init_seq; + if (seq) { + while (seq[0] >= 0) { + writel_relaxed(seq[1], + motg->phy_csr_regs + seq[0]); + seq += 2; + } + } + + /* ensure above writes are completed before re-enabling PHY */ + wmb(); + writel_relaxed(0x22, + motg->phy_csr_regs + QUSB2PHY_PORT_POWERDOWN); + break; + case SNPS_FEMTO_PHY: + if (!motg->phy_por_clk && !motg->phy_por_reset) { + pr_err("phy_por_clk missing\n"); + break; + } + ret = reset_control_assert(motg->phy_por_reset); + if (ret) { + pr_err("phy_por_clk assert failed %d\n", ret); + break; + } + /* + * The Femto PHY is POR reset in the following scenarios. + * + * 1. After overriding the parameter registers. + * 2. Low power mode exit from PHY retention. + * + * Ensure that SIDDQ is cleared before bringing the PHY + * out of reset. + * + */ + + val = readb_relaxed(USB_PHY_CSR_PHY_CTRL_COMMON0); + val &= ~SIDDQ; + writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL_COMMON0); + + /* + * As per databook, 10 usec delay is required between + * PHY POR assert and de-assert. + */ + usleep_range(10, 20); + ret = reset_control_deassert(motg->phy_por_reset); + if (ret) { + pr_err("phy_por_clk de-assert failed %d\n", ret); + break; + } + /* + * As per databook, it takes 75 usec for PHY to stabilize + * after the reset. + */ + usleep_range(80, 100); + break; + default: + break; + } + /* Ensure that RESET operation is completed. */ + mb(); +} + +static void msm_chg_block_on(struct msm_otg *); + +static int msm_otg_reset(struct usb_phy *phy) +{ + struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + struct msm_otg_platform_data *pdata = motg->pdata; + int ret; + u32 val = 0; + u32 ulpi_val = 0; + + msm_otg_dbg_log_event(&motg->phy, "USB RESET", phy->otg->state, + get_pm_runtime_counter(phy->dev)); + /* + * USB PHY and Link reset also reset the USB BAM. + * Thus perform reset operation only once to avoid + * USB BAM reset on other cases e.g. USB cable disconnections. + * If hardware reported error then it must be reset for recovery. + */ + if (motg->err_event_seen) + dev_info(phy->dev, "performing USB h/w reset for recovery\n"); + else if (pdata->disable_reset_on_disconnect && motg->reset_counter) + return 0; + + motg->reset_counter++; + + disable_irq(motg->irq); + if (motg->phy_irq) + disable_irq(motg->phy_irq); + + ret = msm_otg_phy_reset(motg); + if (ret) { + dev_err(phy->dev, "phy_reset failed\n"); + if (motg->phy_irq) + enable_irq(motg->phy_irq); + + enable_irq(motg->irq); + return ret; + } + + if (motg->phy_irq) + enable_irq(motg->phy_irq); + + enable_irq(motg->irq); + ret = msm_otg_link_reset(motg); + if (ret) { + dev_err(phy->dev, "link reset failed\n"); + return ret; + } + + msleep(100); + + /* Reset USB PHY after performing USB Link RESET */ + msm_usb_phy_reset(motg); + + /* Program USB PHY Override registers. */ + ulpi_init(motg); + + /* + * It is required to reset USB PHY after programming + * the USB PHY Override registers to get the new + * values into effect. + */ + msm_usb_phy_reset(motg); + + if (pdata->otg_control == OTG_PHY_CONTROL) { + val = readl_relaxed(USB_OTGSC); + if (pdata->mode == USB_OTG) { + ulpi_val = ULPI_INT_IDGRD | ULPI_INT_SESS_VALID; + val |= OTGSC_IDIE | OTGSC_BSVIE; + } else if (pdata->mode == USB_PERIPHERAL) { + ulpi_val = ULPI_INT_SESS_VALID; + val |= OTGSC_BSVIE; + } + writel_relaxed(val, USB_OTGSC); + ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_RISE); + ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_FALL); + } else if (pdata->otg_control == OTG_PMIC_CONTROL) { + ulpi_write(phy, OTG_COMP_DISABLE, + ULPI_SET(ULPI_PWR_CLK_MNG_REG)); + if (motg->phy_irq) + writeb_relaxed(USB_PHY_ID_MASK, + USB2_PHY_USB_PHY_INTERRUPT_MASK1); + } + + if (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED) + writel_relaxed(readl_relaxed(USB_OTGSC) & ~(OTGSC_IDPU), + USB_OTGSC); + + msm_otg_dbg_log_event(&motg->phy, "USB RESET DONE", phy->otg->state, + get_pm_runtime_counter(phy->dev)); + + if (pdata->enable_axi_prefetch) + writel_relaxed(readl_relaxed(USB_HS_APF_CTRL) | (APF_CTRL_EN), + USB_HS_APF_CTRL); + + /* + * Disable USB BAM as block reset resets USB BAM registers. + */ + msm_usb_bam_enable(CI_CTRL, false); + + if (phy->otg->state == OTG_STATE_UNDEFINED && motg->rm_pulldown) + msm_chg_block_on(motg); + + return 0; +} + +static void msm_otg_kick_sm_work(struct msm_otg *motg) +{ + if (atomic_read(&motg->in_lpm)) + motg->resume_pending = true; + + /* For device mode, resume now. Let pm_resume handle other cases */ + if (atomic_read(&motg->pm_suspended) && + motg->phy.otg->state != OTG_STATE_B_SUSPEND) { + motg->sm_work_pending = true; + } else if (!motg->sm_work_pending) { + /* process event only if previous one is not pending */ + queue_work(motg->otg_wq, &motg->sm_work); + } +} + +/* + * UDC calls usb_phy_set_suspend() to notify during bus suspend/resume. + * Update relevant state-machine inputs and queue sm_work. + * LPM enter/exit doesn't happen directly from this routine. + */ + +static int msm_otg_set_suspend(struct usb_phy *phy, int suspend) +{ + struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + + pr_debug("%s(%d) in %s state\n", __func__, suspend, + usb_otg_state_string(phy->otg->state)); + msm_otg_dbg_log_event(phy, "SET SUSPEND", suspend, phy->otg->state); + + if (!(motg->caps & ALLOW_LPM_ON_DEV_SUSPEND)) + return 0; + + if (suspend) { + /* called in suspend interrupt context */ + pr_debug("peripheral bus suspend\n"); + msm_otg_dbg_log_event(phy, "PERIPHERAL BUS SUSPEND", + motg->inputs, phy->otg->state); + + set_bit(A_BUS_SUSPEND, &motg->inputs); + } else { + /* host resume or remote-wakeup */ + pr_debug("peripheral bus resume\n"); + msm_otg_dbg_log_event(phy, "PERIPHERAL BUS RESUME", + motg->inputs, phy->otg->state); + + clear_bit(A_BUS_SUSPEND, &motg->inputs); + } + /* use kick_sm_work to handle race with pm_resume */ + msm_otg_kick_sm_work(motg); + + return 0; +} + +static int msm_otg_bus_freq_set(struct msm_otg *motg, enum usb_noc_mode mode) +{ + int i, ret; + long rate; + + for (i = 0; i < USB_NUM_BUS_CLOCKS; i++) { + rate = bus_freqs[mode][i]; + if (!rate) { + pr_debug("%s rate not available\n", bus_clkname[i]); + continue; + } + + ret = clk_set_rate(motg->bus_clks[i], rate); + if (ret) { + pr_err("%s set rate failed: %d\n", bus_clkname[i], ret); + return ret; + } + pr_debug("%s set to %lu Hz\n", bus_clkname[i], + clk_get_rate(motg->bus_clks[i])); + msm_otg_dbg_log_event(&motg->phy, "OTG BUS FREQ SET", i, rate); + } + + bus_clk_rate_set = true; + + return 0; +} + +static int msm_otg_bus_freq_get(struct msm_otg *motg) +{ + struct device *dev = motg->phy.dev; + struct device_node *np = dev->of_node; + int len = 0, i, count = USB_NUM_BUS_CLOCKS; + + if (!np) + return -EINVAL; + + /* SVS requires extra set of frequencies for perf_mode sysfs node */ + if (motg->default_noc_mode == USB_NOC_SVS_VOTE) + count *= 2; + + len = of_property_count_elems_of_size(np, "qcom,bus-clk-rate", + sizeof(len)); + if (!len || (len != count)) { + pr_err("Invalid bus rate:%d %u\n", len, motg->default_noc_mode); + return -EINVAL; + } + of_property_read_u32_array(np, "qcom,bus-clk-rate", bus_freqs[0], + count); + for (i = 0; i < USB_NUM_BUS_CLOCKS; i++) { + if (bus_freqs[0][i] == 0) { + motg->bus_clks[i] = NULL; + pr_debug("%s not available\n", bus_clkname[i]); + continue; + } + + motg->bus_clks[i] = devm_clk_get(dev, bus_clkname[i]); + if (IS_ERR(motg->bus_clks[i])) { + pr_err("%s get failed\n", bus_clkname[i]); + return PTR_ERR(motg->bus_clks[i]); + } + } + return 0; +} + +static void msm_otg_bus_clks_enable(struct msm_otg *motg) +{ + int i; + int ret; + + if (!bus_clk_rate_set || motg->bus_clks_enabled) + return; + + for (i = 0; i < USB_NUM_BUS_CLOCKS; i++) { + if (motg->bus_clks[i] == NULL) + continue; + ret = clk_prepare_enable(motg->bus_clks[i]); + if (ret) { + pr_err("%s enable rate failed: %d\n", bus_clkname[i], + ret); + goto err_clk_en; + } + } + motg->bus_clks_enabled = true; + return; +err_clk_en: + for (--i; i >= 0; --i) { + if (motg->bus_clks[i] != NULL) + clk_disable_unprepare(motg->bus_clks[i]); + } +} + +static void msm_otg_bus_clks_disable(struct msm_otg *motg) +{ + int i; + + if (!bus_clk_rate_set || !motg->bus_clks_enabled) + return; + + for (i = 0; i < USB_NUM_BUS_CLOCKS; i++) { + if (motg->bus_clks[i] != NULL) + clk_disable_unprepare(motg->bus_clks[i]); + } + motg->bus_clks_enabled = false; +} + +static void msm_otg_bus_vote(struct msm_otg *motg, enum usb_bus_vote vote) +{ + int ret; + struct msm_otg_platform_data *pdata = motg->pdata; + + msm_otg_dbg_log_event(&motg->phy, "BUS VOTE", vote, + motg->phy.otg->state); + /* Check if target allows min_vote to be same as no_vote */ + if (pdata->bus_scale_table && + vote >= pdata->bus_scale_table->num_usecases) + vote = USB_NO_PERF_VOTE; + + if (motg->bus_perf_client) { + ret = msm_bus_scale_client_update_request( + motg->bus_perf_client, vote); + if (ret) + dev_err(motg->phy.dev, "%s: Failed to vote (%d)\n" + "for bus bw %d\n", __func__, vote, ret); + } + + if (vote == USB_MAX_PERF_VOTE) + msm_otg_bus_clks_enable(motg); + else + msm_otg_bus_clks_disable(motg); +} + +static void msm_otg_enable_phy_hv_int(struct msm_otg *motg) +{ + bool bsv_id_hv_int = false; + bool dp_dm_hv_int = false; + u32 val; + + if (motg->pdata->otg_control == OTG_PHY_CONTROL || + motg->phy_irq) + bsv_id_hv_int = true; + if (motg->host_bus_suspend || motg->device_bus_suspend) + dp_dm_hv_int = true; + + if (!bsv_id_hv_int && !dp_dm_hv_int) + return; + + switch (motg->pdata->phy_type) { + case SNPS_PICO_PHY: + val = readl_relaxed(motg->usb_phy_ctrl_reg); + if (bsv_id_hv_int) + val |= (PHY_IDHV_INTEN | PHY_OTGSESSVLDHV_INTEN); + if (dp_dm_hv_int) + val |= PHY_CLAMP_DPDMSE_EN; + writel_relaxed(val, motg->usb_phy_ctrl_reg); + break; + case SNPS_FEMTO_PHY: + if (bsv_id_hv_int) { + val = readb_relaxed(USB_PHY_CSR_PHY_CTRL1); + val |= ID_HV_CLAMP_EN_N; + writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL1); + } + + if (dp_dm_hv_int) { + val = readb_relaxed(USB_PHY_CSR_PHY_CTRL3); + val |= CLAMP_MPM_DPSE_DMSE_EN_N; + writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL3); + } + break; + default: + break; + } + pr_debug("%s: bsv_id_hv = %d dp_dm_hv_int = %d\n", + __func__, bsv_id_hv_int, dp_dm_hv_int); + msm_otg_dbg_log_event(&motg->phy, "PHY HV INTR ENABLED", + bsv_id_hv_int, dp_dm_hv_int); +} + +static void msm_otg_disable_phy_hv_int(struct msm_otg *motg) +{ + bool bsv_id_hv_int = false; + bool dp_dm_hv_int = false; + u32 val; + + if (motg->pdata->otg_control == OTG_PHY_CONTROL || + motg->phy_irq) + bsv_id_hv_int = true; + if (motg->host_bus_suspend || motg->device_bus_suspend) + dp_dm_hv_int = true; + + if (!bsv_id_hv_int && !dp_dm_hv_int) + return; + + switch (motg->pdata->phy_type) { + case SNPS_PICO_PHY: + val = readl_relaxed(motg->usb_phy_ctrl_reg); + if (bsv_id_hv_int) + val &= ~(PHY_IDHV_INTEN | PHY_OTGSESSVLDHV_INTEN); + if (dp_dm_hv_int) + val &= ~PHY_CLAMP_DPDMSE_EN; + writel_relaxed(val, motg->usb_phy_ctrl_reg); + break; + case SNPS_FEMTO_PHY: + if (bsv_id_hv_int) { + val = readb_relaxed(USB_PHY_CSR_PHY_CTRL1); + val &= ~ID_HV_CLAMP_EN_N; + writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL1); + } + + if (dp_dm_hv_int) { + val = readb_relaxed(USB_PHY_CSR_PHY_CTRL3); + val &= ~CLAMP_MPM_DPSE_DMSE_EN_N; + writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL3); + } + break; + default: + break; + } + pr_debug("%s: bsv_id_hv = %d dp_dm_hv_int = %d\n", + __func__, bsv_id_hv_int, dp_dm_hv_int); + msm_otg_dbg_log_event(&motg->phy, "PHY HV INTR DISABLED", + bsv_id_hv_int, dp_dm_hv_int); +} + +static void msm_otg_enter_phy_retention(struct msm_otg *motg) +{ + u32 val; + + switch (motg->pdata->phy_type) { + case SNPS_PICO_PHY: + val = readl_relaxed(motg->usb_phy_ctrl_reg); + val &= ~PHY_RETEN; + writel_relaxed(val, motg->usb_phy_ctrl_reg); + break; + case SNPS_FEMTO_PHY: + /* Retention is supported via SIDDQ */ + val = readb_relaxed(USB_PHY_CSR_PHY_CTRL_COMMON0); + val |= SIDDQ; + writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL_COMMON0); + break; + default: + break; + } + pr_debug("USB PHY is in retention\n"); + msm_otg_dbg_log_event(&motg->phy, "USB PHY ENTER RETENTION", + motg->pdata->phy_type, 0); +} + +static void msm_otg_exit_phy_retention(struct msm_otg *motg) +{ + int val; + + switch (motg->pdata->phy_type) { + case SNPS_PICO_PHY: + val = readl_relaxed(motg->usb_phy_ctrl_reg); + val |= PHY_RETEN; + writel_relaxed(val, motg->usb_phy_ctrl_reg); + break; + case SNPS_FEMTO_PHY: + /* + * It is required to do USB block reset to bring Femto PHY out + * of retention. + */ + msm_otg_reset(&motg->phy); + break; + default: + break; + } + pr_debug("USB PHY is exited from retention\n"); + msm_otg_dbg_log_event(&motg->phy, "USB PHY EXIT RETENTION", + motg->pdata->phy_type, 0); +} + +static void msm_id_status_w(struct work_struct *w); +static irqreturn_t msm_otg_phy_irq_handler(int irq, void *data) +{ + struct msm_otg *motg = data; + + msm_otg_dbg_log_event(&motg->phy, "PHY ID IRQ", + atomic_read(&motg->in_lpm), motg->phy.otg->state); + if (atomic_read(&motg->in_lpm)) { + pr_debug("PHY ID IRQ in LPM\n"); + motg->phy_irq_pending = true; + msm_otg_kick_sm_work(motg); + } else { + pr_debug("PHY ID IRQ outside LPM\n"); + msm_id_status_w(&motg->id_status_work.work); + } + + return IRQ_HANDLED; +} + +#define PHY_SUSPEND_TIMEOUT_USEC (5 * 1000) +#define PHY_DEVICE_BUS_SUSPEND_TIMEOUT_USEC 100 +#define PHY_RESUME_TIMEOUT_USEC (100 * 1000) +#define PHY_SUSPEND_RETRIES_MAX 3 + +static void msm_otg_set_vbus_state(int online); +static void msm_otg_perf_vote_update(struct msm_otg *motg, bool perf_mode); + +#ifdef CONFIG_PM_SLEEP +static int msm_otg_suspend(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + struct usb_bus *bus = phy->otg->host; + struct msm_otg_platform_data *pdata = motg->pdata; + int cnt; + bool host_bus_suspend, device_bus_suspend, sm_work_busy; + bool host_pc_charger; + u32 cmd_val; + u32 portsc, config2; + u32 func_ctrl; + int phcd_retry_cnt = 0, ret; + unsigned int phy_suspend_timeout; + + cnt = 0; + msm_otg_dbg_log_event(phy, "LPM ENTER START", + motg->inputs, phy->otg->state); + + if (atomic_read(&motg->in_lpm)) + return 0; + + cancel_delayed_work_sync(&motg->perf_vote_work); + + disable_irq(motg->irq); + if (motg->phy_irq) + disable_irq(motg->phy_irq); +lpm_start: + host_bus_suspend = phy->otg->host && !test_bit(ID, &motg->inputs); + device_bus_suspend = phy->otg->gadget && test_bit(ID, &motg->inputs) && + test_bit(A_BUS_SUSPEND, &motg->inputs) && + motg->caps & ALLOW_LPM_ON_DEV_SUSPEND; + + if (host_bus_suspend) + msm_otg_perf_vote_update(motg, false); + + host_pc_charger = (motg->chg_type == USB_SDP_CHARGER) || + (motg->chg_type == USB_CDP_CHARGER); + + /* !BSV, but its handling is in progress by otg sm_work */ + sm_work_busy = !test_bit(B_SESS_VLD, &motg->inputs) && + phy->otg->state == OTG_STATE_B_PERIPHERAL; + + /* Perform block reset to recover from UDC error events on disconnect */ + if (motg->err_event_seen) + msm_otg_reset(phy); + + /* Enable line state difference wakeup fix for only device and host + * bus suspend scenarios. Otherwise PHY can not be suspended when + * a charger that pulls DP/DM high is connected. + */ + config2 = readl_relaxed(USB_GENCONFIG_2); + if (device_bus_suspend) + config2 |= GENCONFIG_2_LINESTATE_DIFF_WAKEUP_EN; + else + config2 &= ~GENCONFIG_2_LINESTATE_DIFF_WAKEUP_EN; + writel_relaxed(config2, USB_GENCONFIG_2); + + /* + * Abort suspend when, + * 1. host mode activation in progress due to Micro-A cable insertion + * 2. !BSV, but its handling is in progress by otg sm_work + * Don't abort suspend in case of dcp detected by PMIC + */ + + if ((test_bit(B_SESS_VLD, &motg->inputs) && !device_bus_suspend && + host_pc_charger) || sm_work_busy) { + msm_otg_dbg_log_event(phy, "LPM ENTER ABORTED", + motg->inputs, 0); + enable_irq(motg->irq); + if (motg->phy_irq) + enable_irq(motg->phy_irq); + return -EBUSY; + } + + if (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED) { + /* put the controller in non-driving mode */ + func_ctrl = ulpi_read(phy, ULPI_FUNC_CTRL); + func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK; + func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; + ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL); + ulpi_write(phy, ULPI_IFC_CTRL_AUTORESUME, + ULPI_CLR(ULPI_IFC_CTRL)); + } + + /* + * PHY suspend sequence as mentioned in the databook. + * + * Device bus suspend: The controller may abort PHY suspend if + * there is an incoming reset or resume from the host. If PHCD + * is not set within 100 usec. Abort the LPM sequence. + * + * Host bus suspend: If the peripheral is attached, PHY is already + * put into suspend along with the peripheral bus suspend. poll for + * PHCD upto 5 msec. If the peripheral is not attached i.e entering + * LPM with Micro-A cable, set the PHCD and poll for it for 5 msec. + * + * No cable connected: Set the PHCD to suspend the PHY. Poll for PHCD + * upto 5 msec. + * + * The controller aborts PHY suspend only in device bus suspend case. + * In other cases, it is observed that PHCD may not get set within + * the timeout. If so, set the PHCD again and poll for it before + * reset recovery. + */ + +phcd_retry: + if (device_bus_suspend) + phy_suspend_timeout = PHY_DEVICE_BUS_SUSPEND_TIMEOUT_USEC; + else + phy_suspend_timeout = PHY_SUSPEND_TIMEOUT_USEC; + + cnt = 0; + portsc = readl_relaxed(USB_PORTSC); + if (!(portsc & PORTSC_PHCD)) { + writel_relaxed(portsc | PORTSC_PHCD, + USB_PORTSC); + while (cnt < phy_suspend_timeout) { + if (readl_relaxed(USB_PORTSC) & PORTSC_PHCD) + break; + udelay(1); + cnt++; + } + } + + if (cnt >= phy_suspend_timeout) { + if (phcd_retry_cnt > PHY_SUSPEND_RETRIES_MAX) { + msm_otg_dbg_log_event(phy, "PHY SUSPEND FAILED", + phcd_retry_cnt, phy->otg->state); + dev_err(phy->dev, "PHY suspend failed\n"); + ret = -EBUSY; + goto phy_suspend_fail; + } + + if (device_bus_suspend) { + dev_dbg(phy->dev, "PHY suspend aborted\n"); + ret = -EBUSY; + goto phy_suspend_fail; + } else { + if (phcd_retry_cnt++ < PHY_SUSPEND_RETRIES_MAX) { + dev_dbg(phy->dev, "PHY suspend retry\n"); + goto phcd_retry; + } else { + dev_err(phy->dev, "reset attempt during PHY suspend\n"); + phcd_retry_cnt++; + motg->reset_counter = 0; + msm_otg_reset(phy); + goto lpm_start; + } + } + } + + /* + * PHY has capability to generate interrupt asynchronously in low + * power mode (LPM). This interrupt is level triggered. So USB IRQ + * line must be disabled till async interrupt enable bit is cleared + * in USBCMD register. Assert STP (ULPI interface STOP signal) to + * block data communication from PHY. + * + * PHY retention mode is disallowed while entering to LPM with wall + * charger connected. But PHY is put into suspend mode. Hence + * enable asynchronous interrupt to detect charger disconnection when + * PMIC notifications are unavailable. + */ + cmd_val = readl_relaxed(USB_USBCMD); + if (host_bus_suspend || device_bus_suspend || + (motg->pdata->otg_control == OTG_PHY_CONTROL)) + cmd_val |= ASYNC_INTR_CTRL | ULPI_STP_CTRL; + else + cmd_val |= ULPI_STP_CTRL; + writel_relaxed(cmd_val, USB_USBCMD); + + /* + * BC1.2 spec mandates PD to enable VDP_SRC when charging from DCP. + * PHY retention and collapse can not happen with VDP_SRC enabled. + */ + + + /* + * We come here in 3 scenarios. + * + * (1) No cable connected (out of session): + * - BSV/ID HV interrupts are enabled for PHY based detection. + * - PHY is put in retention. + * - If allowed (PMIC based detection), PHY is power collapsed. + * - DVDD (CX/MX) minimization and XO shutdown are allowed. + * - The wakeup is through VBUS/ID interrupt from PHY/PMIC/user. + * (2) USB wall charger: + * - BSV/ID HV interrupts are enabled for PHY based detection. + * - For BC1.2 compliant charger, retention is not allowed to + * keep VDP_SRC on. XO shutdown is allowed. + * - The wakeup is through VBUS/ID interrupt from PHY/PMIC/user. + * (3) Device/Host Bus suspend (if LPM is enabled): + * - BSV/ID HV interrupts are enabled for PHY based detection. + * - D+/D- MPM pin are configured to wakeup from line state + * change through PHY HV interrupts. PHY HV interrupts are + * also enabled. If MPM pins are not available, retention and + * XO is not allowed. + * - PHY is put into retention only if a gpio is used to keep + * the D+ pull-up. ALLOW_BUS_SUSPEND_WITHOUT_REWORK capability + * is set means, PHY can enable D+ pull-up or D+/D- pull-down + * without any re-work and PHY should not be put into retention. + * - DVDD (CX/MX) minimization and XO shutdown is allowed if + * ALLOW_BUS_SUSPEND_WITHOUT_REWORK is set (PHY DVDD is supplied + * via PMIC LDO) or board level re-work is present. + * - The wakeup is through VBUS/ID interrupt from PHY/PMIC/user + * or USB link asynchronous interrupt for line state change. + * + */ + motg->host_bus_suspend = host_bus_suspend; + motg->device_bus_suspend = device_bus_suspend; + + if (motg->caps & ALLOW_PHY_RETENTION && !device_bus_suspend && + (!host_bus_suspend || (motg->caps & + ALLOW_BUS_SUSPEND_WITHOUT_REWORK) || + ((motg->caps & ALLOW_HOST_PHY_RETENTION) + && (pdata->dpdm_pulldown_added || !(portsc & PORTSC_CCS))))) { + msm_otg_enable_phy_hv_int(motg); + if ((!host_bus_suspend || !(motg->caps & + ALLOW_BUS_SUSPEND_WITHOUT_REWORK)) && + !(motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED)) { + msm_otg_enter_phy_retention(motg); + motg->lpm_flags |= PHY_RETENTIONED; + } + } else if (device_bus_suspend) { + /* DP DM HV interrupts are used for bus resume from XO off */ + msm_otg_enable_phy_hv_int(motg); + if (motg->caps & ALLOW_PHY_RETENTION && pdata->vddmin_gpio) { + + /* + * This is HW WA needed when PHY_CLAMP_DPDMSE_EN is + * enabled and we put the phy in retention mode. + * Without this WA, the async_irq will be fired right + * after suspending whithout any bus resume. + */ + config2 = readl_relaxed(USB_GENCONFIG_2); + config2 &= ~GENCONFIG_2_DPSE_DMSE_HV_INTR_EN; + writel_relaxed(config2, USB_GENCONFIG_2); + + msm_otg_enter_phy_retention(motg); + motg->lpm_flags |= PHY_RETENTIONED; + gpio_direction_output(pdata->vddmin_gpio, 1); + } + } + + /* Ensure that above operation is completed before turning off clocks */ + mb(); + /* Consider clocks on workaround flag only in case of bus suspend */ + if (!(phy->otg->state == OTG_STATE_B_PERIPHERAL && + test_bit(A_BUS_SUSPEND, &motg->inputs)) || + !motg->pdata->core_clk_always_on_workaround) { + clk_disable_unprepare(motg->pclk); + clk_disable_unprepare(motg->core_clk); + if (motg->phy_csr_clk) + clk_disable_unprepare(motg->phy_csr_clk); + motg->lpm_flags |= CLOCKS_DOWN; + } + + /* usb phy no more require TCXO clock, hence vote for TCXO disable */ + if (!host_bus_suspend || (motg->caps & + ALLOW_BUS_SUSPEND_WITHOUT_REWORK) || + ((motg->caps & ALLOW_HOST_PHY_RETENTION) && + (pdata->dpdm_pulldown_added || !(portsc & PORTSC_CCS)))) { + if (motg->xo_clk) { + clk_disable_unprepare(motg->xo_clk); + motg->lpm_flags |= XO_SHUTDOWN; + } + } + + if (motg->caps & ALLOW_PHY_POWER_COLLAPSE && + !host_bus_suspend && !device_bus_suspend) { + msm_hsusb_ldo_enable(motg, USB_PHY_REG_OFF); + motg->lpm_flags |= PHY_PWR_COLLAPSED; + } else if (motg->caps & ALLOW_PHY_REGULATORS_LPM && + !host_bus_suspend && !device_bus_suspend) { + msm_hsusb_ldo_enable(motg, USB_PHY_REG_LPM_ON); + motg->lpm_flags |= PHY_REGULATORS_LPM; + } + + if (motg->lpm_flags & PHY_RETENTIONED || + (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED)) { + regulator_disable(hsusb_vdd); + msm_hsusb_config_vddcx(0); + } + + if (device_may_wakeup(phy->dev)) { + if (host_bus_suspend || device_bus_suspend) { + enable_irq_wake(motg->async_irq); + enable_irq_wake(motg->irq); + } + + if (motg->phy_irq) + enable_irq_wake(motg->phy_irq); + if (motg->pdata->pmic_id_irq) + enable_irq_wake(motg->pdata->pmic_id_irq); + if (motg->ext_id_irq) + enable_irq_wake(motg->ext_id_irq); + } + if (bus) + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); + + msm_otg_bus_vote(motg, USB_NO_PERF_VOTE); + + atomic_set(&motg->in_lpm, 1); + + if (host_bus_suspend || device_bus_suspend) { + /* Enable ASYNC IRQ during LPM */ + enable_irq(motg->async_irq); + enable_irq(motg->irq); + } + if (motg->phy_irq) + enable_irq(motg->phy_irq); + + pm_relax(&motg->pdev->dev); + + dev_dbg(phy->dev, "LPM caps = %lu flags = %lu\n", + motg->caps, motg->lpm_flags); + dev_info(phy->dev, "USB in low power mode\n"); + msm_otg_dbg_log_event(phy, "LPM ENTER DONE", + motg->caps, motg->lpm_flags); + + if (motg->err_event_seen) { + motg->err_event_seen = false; + if (motg->vbus_state != test_bit(B_SESS_VLD, &motg->inputs)) + msm_otg_set_vbus_state(motg->vbus_state); + if (motg->id_state != test_bit(ID, &motg->inputs)) + msm_id_status_w(&motg->id_status_work.work); + } + + return 0; + +phy_suspend_fail: + enable_irq(motg->irq); + if (motg->phy_irq) + enable_irq(motg->phy_irq); + return ret; +} + +static int msm_otg_resume(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + struct usb_bus *bus = phy->otg->host; + struct usb_hcd *hcd = bus_to_hcd(phy->otg->host); + struct msm_otg_platform_data *pdata = motg->pdata; + int cnt = 0; + unsigned int temp; + unsigned int ret; + u32 func_ctrl; + + msm_otg_dbg_log_event(phy, "LPM EXIT START", motg->inputs, + phy->otg->state); + if (!atomic_read(&motg->in_lpm)) { + msm_otg_dbg_log_event(phy, "USB NOT IN LPM", + atomic_read(&motg->in_lpm), phy->otg->state); + return 0; + } + + pm_stay_awake(&motg->pdev->dev); + if (motg->phy_irq) + disable_irq(motg->phy_irq); + + if (motg->host_bus_suspend || motg->device_bus_suspend) + disable_irq(motg->irq); + + /* + * If we are resuming from the device bus suspend, restore + * the max performance bus vote. Otherwise put a minimum + * bus vote to satisfy the requirement for enabling clocks. + */ + + if (motg->device_bus_suspend && debug_bus_voting_enabled) + msm_otg_bus_vote(motg, USB_MAX_PERF_VOTE); + else + msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE); + + /* Vote for TCXO when waking up the phy */ + if (motg->lpm_flags & XO_SHUTDOWN) { + if (motg->xo_clk) + clk_prepare_enable(motg->xo_clk); + motg->lpm_flags &= ~XO_SHUTDOWN; + } + + if (motg->lpm_flags & CLOCKS_DOWN) { + if (motg->phy_csr_clk) { + ret = clk_prepare_enable(motg->phy_csr_clk); + WARN(ret, "USB phy_csr_clk enable failed\n"); + } + ret = clk_prepare_enable(motg->core_clk); + WARN(ret, "USB core_clk enable failed\n"); + ret = clk_prepare_enable(motg->pclk); + WARN(ret, "USB pclk enable failed\n"); + motg->lpm_flags &= ~CLOCKS_DOWN; + } + + if (motg->lpm_flags & PHY_PWR_COLLAPSED) { + msm_hsusb_ldo_enable(motg, USB_PHY_REG_ON); + motg->lpm_flags &= ~PHY_PWR_COLLAPSED; + } else if (motg->lpm_flags & PHY_REGULATORS_LPM) { + msm_hsusb_ldo_enable(motg, USB_PHY_REG_LPM_OFF); + motg->lpm_flags &= ~PHY_REGULATORS_LPM; + } + + if (motg->lpm_flags & PHY_RETENTIONED || + (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED)) { + msm_hsusb_config_vddcx(1); + ret = regulator_enable(hsusb_vdd); + WARN(ret, "hsusb_vdd LDO enable failed\n"); + msm_otg_disable_phy_hv_int(motg); + msm_otg_exit_phy_retention(motg); + motg->lpm_flags &= ~PHY_RETENTIONED; + if (pdata->vddmin_gpio && motg->device_bus_suspend) + gpio_direction_input(pdata->vddmin_gpio); + } else if (motg->device_bus_suspend) { + msm_otg_disable_phy_hv_int(motg); + } + + temp = readl_relaxed(USB_USBCMD); + temp &= ~ASYNC_INTR_CTRL; + temp &= ~ULPI_STP_CTRL; + writel_relaxed(temp, USB_USBCMD); + + /* + * PHY comes out of low power mode (LPM) in case of wakeup + * from asynchronous interrupt. + */ + if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD)) + goto skip_phy_resume; + + writel_relaxed(readl_relaxed(USB_PORTSC) & ~PORTSC_PHCD, USB_PORTSC); + + while (cnt < PHY_RESUME_TIMEOUT_USEC) { + if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD)) + break; + udelay(1); + cnt++; + } + + if (cnt >= PHY_RESUME_TIMEOUT_USEC) { + /* + * This is a fatal error. Reset the link and + * PHY. USB state can not be restored. Re-insertion + * of USB cable is the only way to get USB working. + */ + dev_err(phy->dev, "Unable to resume USB. Re-plugin the cable\n" + ); + msm_otg_reset(phy); + } + +skip_phy_resume: + if (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED) { + /* put the controller in normal mode */ + func_ctrl = ulpi_read(phy, ULPI_FUNC_CTRL); + func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK; + func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NORMAL; + ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL); + } + + if (device_may_wakeup(phy->dev)) { + if (motg->host_bus_suspend || motg->device_bus_suspend) { + disable_irq_wake(motg->async_irq); + disable_irq_wake(motg->irq); + } + + if (motg->phy_irq) + disable_irq_wake(motg->phy_irq); + if (motg->pdata->pmic_id_irq) + disable_irq_wake(motg->pdata->pmic_id_irq); + if (motg->ext_id_irq) + disable_irq_wake(motg->ext_id_irq); + } + if (bus) + set_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); + + atomic_set(&motg->in_lpm, 0); + + if (motg->async_int) { + /* Match the disable_irq call from ISR */ + enable_irq(motg->async_int); + motg->async_int = 0; + } + if (motg->phy_irq) + enable_irq(motg->phy_irq); + enable_irq(motg->irq); + + /* Enable ASYNC_IRQ only during LPM */ + if (motg->host_bus_suspend || motg->device_bus_suspend) + disable_irq(motg->async_irq); + + if (motg->phy_irq_pending) { + motg->phy_irq_pending = false; + msm_id_status_w(&motg->id_status_work.work); + } + + if (motg->host_bus_suspend) { + usb_hcd_resume_root_hub(hcd); + schedule_delayed_work(&motg->perf_vote_work, + msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC)); + } + + dev_info(phy->dev, "USB exited from low power mode\n"); + msm_otg_dbg_log_event(phy, "LPM EXIT DONE", + motg->caps, motg->lpm_flags); + + return 0; +} +#endif + +static int get_psy_type(struct msm_otg *motg) +{ + union power_supply_propval pval = {0}; + + if (!psy) { + psy = power_supply_get_by_name("usb"); + if (!psy) { + dev_err(motg->phy.dev, "Could not get usb power_supply\n"); + return -ENODEV; + } + } + + power_supply_get_property(psy, POWER_SUPPLY_PROP_REAL_TYPE, &pval); + + return pval.intval; +} + +static int msm_otg_notify_chg_type(struct msm_otg *motg) +{ + static int charger_type; + union power_supply_propval propval; + int ret = 0; + + if (charger_type == motg->chg_type) + return 0; + + if (motg->chg_type == USB_SDP_CHARGER) + charger_type = POWER_SUPPLY_TYPE_USB; + else if (motg->chg_type == USB_CDP_CHARGER) + charger_type = POWER_SUPPLY_TYPE_USB_CDP; + else if (motg->chg_type == USB_DCP_CHARGER || + motg->chg_type == USB_NONCOMPLIANT_CHARGER) + charger_type = POWER_SUPPLY_TYPE_USB_DCP; + else if (motg->chg_type == USB_FLOATED_CHARGER) + charger_type = POWER_SUPPLY_TYPE_USB_FLOAT; + else + charger_type = POWER_SUPPLY_TYPE_UNKNOWN; + + pr_debug("Trying to set usb power supply type %d\n", charger_type); + + propval.intval = charger_type; + ret = power_supply_set_property(psy, POWER_SUPPLY_PROP_REAL_TYPE, + &propval); + if (ret) + dev_dbg(motg->phy.dev, "power supply error when setting property\n"); + + msm_otg_dbg_log_event(&motg->phy, "SET USB PWR SUPPLY TYPE", + motg->chg_type, charger_type); + return ret; +} + +static void msm_otg_notify_charger(struct msm_otg *motg, unsigned int mA) +{ + struct usb_gadget *g = motg->phy.otg->gadget; + union power_supply_propval pval = {0}; + int psy_type; + + if (g && g->is_a_peripheral) + return; + + dev_dbg(motg->phy.dev, "Requested curr from USB = %u\n", mA); + + psy_type = get_psy_type(motg); + if (psy_type == -ENODEV) + return; + + if (msm_otg_notify_chg_type(motg)) + dev_dbg(motg->phy.dev, "Failed notifying %d charger type to PMIC\n", + motg->chg_type); + + psy_type = get_psy_type(motg); + if (psy_type == POWER_SUPPLY_TYPE_USB_FLOAT || + (psy_type == POWER_SUPPLY_TYPE_USB && + motg->enable_sdp_check_timer)) { + if (!mA) { + pval.intval = -ETIMEDOUT; + goto set_prop; + } + } + + if (motg->cur_power == mA) + return; + + dev_info(motg->phy.dev, "Avail curr from USB = %u\n", mA); + msm_otg_dbg_log_event(&motg->phy, "AVAIL CURR FROM USB", mA, 0); + + /* Set max current limit in uA */ + pval.intval = 1000 * mA; + +set_prop: + if (power_supply_set_property(psy, POWER_SUPPLY_PROP_SDP_CURRENT_MAX, + &pval)) { + dev_dbg(motg->phy.dev, "power supply error when setting property\n"); + return; + } + + motg->cur_power = mA; +} + +static void msm_otg_notify_charger_work(struct work_struct *w) +{ + struct msm_otg *motg = container_of(w, + struct msm_otg, notify_charger_work); + + msm_otg_notify_charger(motg, motg->notify_current_mA); +} + +static int msm_otg_set_power(struct usb_phy *phy, unsigned int mA) +{ + struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + + motg->notify_current_mA = mA; + /* + * Gadget driver uses set_power method to notify about the + * available current based on suspend/configured states. + */ + if (motg->chg_type == USB_SDP_CHARGER || + get_psy_type(motg) == POWER_SUPPLY_TYPE_USB || + get_psy_type(motg) == POWER_SUPPLY_TYPE_USB_FLOAT) + queue_work(motg->otg_wq, &motg->notify_charger_work); + + return 0; +} + +static void msm_hsusb_vbus_power(struct msm_otg *motg, bool on); + +static void msm_otg_perf_vote_update(struct msm_otg *motg, bool perf_mode) +{ + static bool curr_perf_mode; + int ret, latency = motg->pm_qos_latency; + long clk_rate; + + if (curr_perf_mode == perf_mode) + return; + + if (perf_mode) { + if (latency) + pm_qos_update_request(&motg->pm_qos_req_dma, latency); + msm_otg_bus_vote(motg, USB_MAX_PERF_VOTE); + clk_rate = motg->core_clk_rate; + } else { + if (latency) + pm_qos_update_request(&motg->pm_qos_req_dma, + PM_QOS_DEFAULT_VALUE); + msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE); + clk_rate = motg->core_clk_svs_rate; + } + + if (clk_rate) { + ret = clk_set_rate(motg->core_clk, clk_rate); + if (ret) + dev_err(motg->phy.dev, "sys_clk set_rate fail:%d %ld\n", + ret, clk_rate); + } + curr_perf_mode = perf_mode; + pr_debug("%s: latency updated to: %d, core_freq to: %ld\n", __func__, + latency, clk_rate); +} + +static void msm_otg_perf_vote_work(struct work_struct *w) +{ + struct msm_otg *motg = container_of(w, struct msm_otg, + perf_vote_work.work); + unsigned int curr_sample_int_count; + bool in_perf_mode = false; + + curr_sample_int_count = motg->usb_irq_count; + motg->usb_irq_count = 0; + + if (curr_sample_int_count >= PM_QOS_THRESHOLD) + in_perf_mode = true; + + msm_otg_perf_vote_update(motg, in_perf_mode); + pr_debug("%s: in_perf_mode:%u, interrupts in last sample:%u\n", + __func__, in_perf_mode, curr_sample_int_count); + + schedule_delayed_work(&motg->perf_vote_work, + msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC)); +} + +static void msm_otg_start_host(struct usb_otg *otg, int on) +{ + struct msm_otg *motg = container_of(otg->usb_phy, struct msm_otg, phy); + struct msm_otg_platform_data *pdata = motg->pdata; + struct usb_hcd *hcd; + u32 val; + + if (!otg->host) + return; + + hcd = bus_to_hcd(otg->host); + + msm_otg_dbg_log_event(&motg->phy, "PM RT: StartHost GET", + get_pm_runtime_counter(motg->phy.dev), 0); + pm_runtime_get_sync(otg->usb_phy->dev); + if (on) { + dev_dbg(otg->usb_phy->dev, "host on\n"); + msm_otg_dbg_log_event(&motg->phy, "HOST ON", + motg->inputs, otg->state); + msm_hsusb_vbus_power(motg, 1); + msm_otg_reset(&motg->phy); + + if (pdata->otg_control == OTG_PHY_CONTROL) + ulpi_write(otg->usb_phy, OTG_COMP_DISABLE, + ULPI_SET(ULPI_PWR_CLK_MNG_REG)); + + if (pdata->enable_axi_prefetch) { + val = readl_relaxed(USB_HS_APF_CTRL); + val &= ~APF_CTRL_EN; + writel_relaxed(val, USB_HS_APF_CTRL); + } + usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); +#ifdef CONFIG_SMP + motg->pm_qos_req_dma.type = PM_QOS_REQ_AFFINE_IRQ; + motg->pm_qos_req_dma.irq = motg->irq; +#endif + pm_qos_add_request(&motg->pm_qos_req_dma, + PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); + /* start in perf mode for better performance initially */ + msm_otg_perf_vote_update(motg, true); + schedule_delayed_work(&motg->perf_vote_work, + msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC)); + } else { + dev_dbg(otg->usb_phy->dev, "host off\n"); + msm_otg_dbg_log_event(&motg->phy, "HOST OFF", + motg->inputs, otg->state); + msm_hsusb_vbus_power(motg, 0); + + cancel_delayed_work_sync(&motg->perf_vote_work); + msm_otg_perf_vote_update(motg, false); + pm_qos_remove_request(&motg->pm_qos_req_dma); + + pm_runtime_disable(&hcd->self.root_hub->dev); + pm_runtime_barrier(&hcd->self.root_hub->dev); + usb_remove_hcd(hcd); + msm_otg_reset(&motg->phy); + + if (pdata->enable_axi_prefetch) + writel_relaxed(readl_relaxed(USB_HS_APF_CTRL) + | (APF_CTRL_EN), USB_HS_APF_CTRL); + + /* HCD core reset all bits of PORTSC. select ULPI phy */ + writel_relaxed(0x80000000, USB_PORTSC); + + if (pdata->otg_control == OTG_PHY_CONTROL) + ulpi_write(otg->usb_phy, OTG_COMP_DISABLE, + ULPI_CLR(ULPI_PWR_CLK_MNG_REG)); + } + msm_otg_dbg_log_event(&motg->phy, "PM RT: StartHost PUT", + get_pm_runtime_counter(motg->phy.dev), 0); + + pm_runtime_mark_last_busy(otg->usb_phy->dev); + pm_runtime_put_autosuspend(otg->usb_phy->dev); +} + +static void msm_hsusb_vbus_power(struct msm_otg *motg, bool on) +{ + int ret; + static bool vbus_is_on; + + msm_otg_dbg_log_event(&motg->phy, "VBUS POWER", on, vbus_is_on); + if (vbus_is_on == on) + return; + + if (!vbus_otg) { + pr_err("vbus_otg is NULL.\n"); + return; + } + + /* + * if entering host mode tell the charger to not draw any current + * from usb before turning on the boost. + * if exiting host mode disable the boost before enabling to draw + * current from the source. + */ + if (on) { + ret = regulator_enable(vbus_otg); + if (ret) { + pr_err("unable to enable vbus_otg\n"); + return; + } + vbus_is_on = true; + } else { + ret = regulator_disable(vbus_otg); + if (ret) { + pr_err("unable to disable vbus_otg\n"); + return; + } + vbus_is_on = false; + } +} + +static int msm_otg_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + struct msm_otg *motg = container_of(otg->usb_phy, struct msm_otg, phy); + struct usb_hcd *hcd; + + /* + * Fail host registration if this board can support + * only peripheral configuration. + */ + if (motg->pdata->mode == USB_PERIPHERAL) { + dev_info(otg->usb_phy->dev, "Host mode is not supported\n"); + return -ENODEV; + } + + if (host) { + vbus_otg = devm_regulator_get(motg->phy.dev, "vbus_otg"); + if (IS_ERR(vbus_otg)) { + msm_otg_dbg_log_event(&motg->phy, + "UNABLE TO GET VBUS_OTG", + otg->state, 0); + dev_err(otg->usb_phy->dev, "Unable to get vbus_otg\n"); + return PTR_ERR(vbus_otg); + } + } else { + if (otg->state == OTG_STATE_A_HOST) { + msm_otg_start_host(otg, 0); + otg->host = NULL; + otg->state = OTG_STATE_UNDEFINED; + queue_work(motg->otg_wq, &motg->sm_work); + } else { + otg->host = NULL; + } + + return 0; + } + + hcd = bus_to_hcd(host); + hcd->power_budget = motg->pdata->power_budget; + + otg->host = host; + dev_dbg(otg->usb_phy->dev, "host driver registered w/ tranceiver\n"); + msm_otg_dbg_log_event(&motg->phy, "HOST DRIVER REGISTERED", + hcd->power_budget, motg->pdata->mode); + + /* + * Kick the state machine work, if peripheral is not supported + * or peripheral is already registered with us. + */ + if (motg->pdata->mode == USB_HOST || otg->gadget) + queue_work(motg->otg_wq, &motg->sm_work); + + return 0; +} + +static void msm_otg_start_peripheral(struct usb_otg *otg, int on) +{ + struct msm_otg *motg = container_of(otg->usb_phy, struct msm_otg, phy); + struct msm_otg_platform_data *pdata = motg->pdata; + struct pinctrl_state *set_state; + int ret; + + if (!otg->gadget) + return; + + msm_otg_dbg_log_event(&motg->phy, "PM RT: StartPeri GET", + get_pm_runtime_counter(motg->phy.dev), 0); + pm_runtime_get_sync(otg->usb_phy->dev); + if (on) { + dev_dbg(otg->usb_phy->dev, "gadget on\n"); + msm_otg_dbg_log_event(&motg->phy, "GADGET ON", + motg->inputs, otg->state); + + /* Configure BUS performance parameters for MAX bandwidth */ + if (debug_bus_voting_enabled) + msm_otg_bus_vote(motg, USB_MAX_PERF_VOTE); + /* bump up usb core_clk to default */ + clk_set_rate(motg->core_clk, motg->core_clk_rate); + + usb_gadget_vbus_connect(otg->gadget); + + /* + * Request VDD min gpio, if need to support VDD + * minimazation during peripheral bus suspend. + */ + if (pdata->vddmin_gpio) { + if (motg->phy_pinctrl) { + set_state = + pinctrl_lookup_state(motg->phy_pinctrl, + "hsusb_active"); + if (IS_ERR(set_state)) + pr_err("cannot get phy pinctrl active state\n"); + else + pinctrl_select_state(motg->phy_pinctrl, + set_state); + } + + ret = gpio_request(pdata->vddmin_gpio, + "MSM_OTG_VDD_MIN_GPIO"); + if (ret < 0) { + dev_err(otg->usb_phy->dev, "gpio req failed for vdd min:%d\n", + ret); + pdata->vddmin_gpio = 0; + } + } + } else { + dev_dbg(otg->usb_phy->dev, "gadget off\n"); + msm_otg_dbg_log_event(&motg->phy, "GADGET OFF", + motg->inputs, otg->state); + usb_gadget_vbus_disconnect(otg->gadget); + clear_bit(A_BUS_SUSPEND, &motg->inputs); + /* Configure BUS performance parameters to default */ + msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE); + + if (pdata->vddmin_gpio) { + gpio_free(pdata->vddmin_gpio); + if (motg->phy_pinctrl) { + set_state = + pinctrl_lookup_state(motg->phy_pinctrl, + "hsusb_sleep"); + if (IS_ERR(set_state)) + pr_err("cannot get phy pinctrl sleep state\n"); + else + pinctrl_select_state(motg->phy_pinctrl, + set_state); + } + } + } + msm_otg_dbg_log_event(&motg->phy, "PM RT: StartPeri PUT", + get_pm_runtime_counter(motg->phy.dev), 0); + pm_runtime_mark_last_busy(otg->usb_phy->dev); + pm_runtime_put_autosuspend(otg->usb_phy->dev); +} + +static int msm_otg_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + struct msm_otg *motg = container_of(otg->usb_phy, struct msm_otg, phy); + + /* + * Fail peripheral registration if this board can support + * only host configuration. + */ + if (motg->pdata->mode == USB_HOST) { + dev_err(otg->usb_phy->dev, "Peripheral mode is not supported\n"); + return -ENODEV; + } + + if (!gadget) { + if (otg->state == OTG_STATE_B_PERIPHERAL) { + msm_otg_dbg_log_event(&motg->phy, + "PM RUNTIME: PERIPHERAL GET1", + get_pm_runtime_counter(otg->usb_phy->dev), 0); + msm_otg_start_peripheral(otg, 0); + otg->gadget = NULL; + otg->state = OTG_STATE_UNDEFINED; + queue_work(motg->otg_wq, &motg->sm_work); + } else { + otg->gadget = NULL; + } + + return 0; + } + otg->gadget = gadget; + dev_dbg(otg->usb_phy->dev, "peripheral driver registered w/ tranceiver\n"); + msm_otg_dbg_log_event(&motg->phy, "PERIPHERAL DRIVER REGISTERED", + otg->state, motg->pdata->mode); + + /* + * Kick the state machine work, if host is not supported + * or host is already registered with us. + */ + if (motg->pdata->mode == USB_PERIPHERAL || otg->host) + queue_work(motg->otg_wq, &motg->sm_work); + + return 0; +} + +static bool msm_otg_read_pmic_id_state(struct msm_otg *motg) +{ + unsigned long flags; + bool id; + int ret; + + if (!motg->pdata->pmic_id_irq) + return -ENODEV; + + local_irq_save(flags); + ret = irq_get_irqchip_state(motg->pdata->pmic_id_irq, + IRQCHIP_STATE_LINE_LEVEL, &id); + local_irq_restore(flags); + + /* + * If we can not read ID line state for some reason, treat + * it as float. This would prevent MHL discovery and kicking + * host mode unnecessarily. + */ + if (ret < 0) + return true; + + return !!id; +} + +static bool msm_otg_read_phy_id_state(struct msm_otg *motg) +{ + u8 val; + + /* + * clear the pending/outstanding interrupts and + * read the ID status from the SRC_STATUS register. + */ + writeb_relaxed(USB_PHY_ID_MASK, USB2_PHY_USB_PHY_INTERRUPT_CLEAR1); + + writeb_relaxed(0x1, USB2_PHY_USB_PHY_IRQ_CMD); + /* + * Databook says 200 usec delay is required for + * clearing the interrupts. + */ + udelay(200); + writeb_relaxed(0x0, USB2_PHY_USB_PHY_IRQ_CMD); + + val = readb_relaxed(USB2_PHY_USB_PHY_INTERRUPT_SRC_STATUS); + if (val & USB_PHY_IDDIG_1_0) + return false; /* ID is grounded */ + + return true; +} + +static bool msm_chg_check_secondary_det(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 chg_det; + + chg_det = ulpi_read(phy, 0x87); + + return (chg_det & 1); +} + +static void msm_chg_enable_secondary_det(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + + /* + * Configure DM as current source, DP as current sink + * and enable battery charging comparators. + */ + ulpi_write(phy, 0x8, 0x85); + ulpi_write(phy, 0x2, 0x85); + ulpi_write(phy, 0x1, 0x85); +} + +static bool msm_chg_check_primary_det(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 chg_det; + bool ret = false; + + chg_det = ulpi_read(phy, 0x87); + ret = chg_det & 1; + /* Turn off VDP_SRC */ + ulpi_write(phy, 0x3, 0x86); + msleep(20); + + return ret; +} + +static void msm_chg_enable_primary_det(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + + /* + * Configure DP as current source, DM as current sink + * and enable battery charging comparators. + */ + ulpi_write(phy, 0x2, 0x85); + ulpi_write(phy, 0x1, 0x85); +} + +static bool msm_chg_check_dcd(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 line_state; + + line_state = ulpi_read(phy, 0x87); + + return line_state & 2; +} + +static void msm_chg_disable_dcd(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + + ulpi_write(phy, 0x10, 0x86); + /* + * Disable the Rdm_down after + * the DCD is completed. + */ + ulpi_write(phy, 0x04, 0x0C); +} + +static void msm_chg_enable_dcd(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + + /* + * Idp_src and Rdm_down are de-coupled + * on Femto PHY. If Idp_src alone is + * enabled, DCD timeout is observed with + * wall charger. But a genuine DCD timeout + * may be incorrectly interpreted. Also + * BC1.2 compliance testers expect Rdm_down + * to enabled during DCD. Enable Rdm_down + * explicitly before enabling the DCD. + */ + ulpi_write(phy, 0x04, 0x0B); + ulpi_write(phy, 0x10, 0x85); +} + +static void msm_chg_block_on(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 func_ctrl; + + /* put the controller in non-driving mode */ + msm_otg_dbg_log_event(&motg->phy, "PHY NON DRIVE", 0, 0); + func_ctrl = ulpi_read(phy, ULPI_FUNC_CTRL); + func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK; + func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; + ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL); + + /* disable DP and DM pull down resistors */ + ulpi_write(phy, 0x6, 0xC); + /* Clear charger detecting control bits */ + ulpi_write(phy, 0x1F, 0x86); + /* Clear alt interrupt latch and enable bits */ + ulpi_write(phy, 0x1F, 0x92); + ulpi_write(phy, 0x1F, 0x95); + udelay(100); +} + +static void msm_chg_block_off(struct msm_otg *motg) +{ + struct usb_phy *phy = &motg->phy; + u32 func_ctrl; + + /* Clear charger detecting control bits */ + ulpi_write(phy, 0x3F, 0x86); + /* Clear alt interrupt latch and enable bits */ + ulpi_write(phy, 0x1F, 0x92); + ulpi_write(phy, 0x1F, 0x95); + /* re-enable DP and DM pull down resistors */ + ulpi_write(phy, 0x6, 0xB); + + /* put the controller in normal mode */ + msm_otg_dbg_log_event(&motg->phy, "PHY MODE NORMAL", 0, 0); + func_ctrl = ulpi_read(phy, ULPI_FUNC_CTRL); + func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK; + func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NORMAL; + ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL); +} + +static void msm_otg_set_mode_nondriving(struct msm_otg *motg, + bool mode_nondriving) +{ + clk_prepare_enable(motg->xo_clk); + clk_prepare_enable(motg->phy_csr_clk); + clk_prepare_enable(motg->core_clk); + clk_prepare_enable(motg->pclk); + + msm_otg_exit_phy_retention(motg); + + if (mode_nondriving) + msm_chg_block_on(motg); + else + msm_chg_block_off(motg); + + msm_otg_enter_phy_retention(motg); + + clk_disable_unprepare(motg->pclk); + clk_disable_unprepare(motg->core_clk); + clk_disable_unprepare(motg->phy_csr_clk); + clk_disable_unprepare(motg->xo_clk); +} + +#define MSM_CHG_DCD_TIMEOUT (750 * HZ/1000) /* 750 msec */ +#define MSM_CHG_DCD_POLL_TIME (50 * HZ/1000) /* 50 msec */ +#define MSM_CHG_PRIMARY_DET_TIME (50 * HZ/1000) /* TVDPSRC_ON */ +#define MSM_CHG_SECONDARY_DET_TIME (50 * HZ/1000) /* TVDMSRC_ON */ + +static void msm_chg_detect_work(struct work_struct *w) +{ + struct msm_otg *motg = container_of(w, struct msm_otg, chg_work.work); + struct usb_phy *phy = &motg->phy; + bool is_dcd = false, tmout, vout, queue_sm_work = false; + static bool dcd; + u32 line_state, dm_vlgc; + unsigned long delay = 0; + + dev_dbg(phy->dev, "chg detection work\n"); + msm_otg_dbg_log_event(phy, "CHG DETECTION WORK", + motg->chg_state, get_pm_runtime_counter(phy->dev)); + + switch (motg->chg_state) { + case USB_CHG_STATE_UNDEFINED: + pm_runtime_get_sync(phy->dev); + msm_chg_block_on(motg); + case USB_CHG_STATE_IN_PROGRESS: + if (!motg->vbus_state) { + motg->chg_state = USB_CHG_STATE_UNDEFINED; + motg->chg_type = USB_INVALID_CHARGER; + msm_chg_block_off(motg); + pm_runtime_put_sync(phy->dev); + return; + } + + msm_chg_enable_dcd(motg); + motg->chg_state = USB_CHG_STATE_WAIT_FOR_DCD; + motg->dcd_time = 0; + delay = MSM_CHG_DCD_POLL_TIME; + break; + case USB_CHG_STATE_WAIT_FOR_DCD: + if (!motg->vbus_state) { + motg->chg_state = USB_CHG_STATE_IN_PROGRESS; + break; + } + + is_dcd = msm_chg_check_dcd(motg); + motg->dcd_time += MSM_CHG_DCD_POLL_TIME; + tmout = motg->dcd_time >= MSM_CHG_DCD_TIMEOUT; + if (is_dcd || tmout) { + dcd = is_dcd ? true : false; + msm_chg_disable_dcd(motg); + msm_chg_enable_primary_det(motg); + delay = MSM_CHG_PRIMARY_DET_TIME; + motg->chg_state = USB_CHG_STATE_DCD_DONE; + } else { + delay = MSM_CHG_DCD_POLL_TIME; + } + break; + case USB_CHG_STATE_DCD_DONE: + if (!motg->vbus_state) { + motg->chg_state = USB_CHG_STATE_IN_PROGRESS; + break; + } + + vout = msm_chg_check_primary_det(motg); + line_state = readl_relaxed(USB_PORTSC) & PORTSC_LS; + dm_vlgc = line_state & PORTSC_LS_DM; + if (vout && !dm_vlgc) { /* VDAT_REF < DM < VLGC */ + if (line_state) { /* DP > VLGC */ + motg->chg_type = USB_NONCOMPLIANT_CHARGER; + motg->chg_state = USB_CHG_STATE_DETECTED; + } else { + msm_chg_enable_secondary_det(motg); + delay = MSM_CHG_SECONDARY_DET_TIME; + motg->chg_state = USB_CHG_STATE_PRIMARY_DONE; + } + } else { /* DM < VDAT_REF || DM > VLGC */ + if (line_state) /* DP > VLGC or/and DM > VLGC */ + motg->chg_type = USB_NONCOMPLIANT_CHARGER; + else if (!dcd && floated_charger_enable) + motg->chg_type = USB_FLOATED_CHARGER; + else + motg->chg_type = USB_SDP_CHARGER; + + motg->chg_state = USB_CHG_STATE_DETECTED; + } + break; + case USB_CHG_STATE_PRIMARY_DONE: + if (!motg->vbus_state) { + motg->chg_state = USB_CHG_STATE_IN_PROGRESS; + break; + } + + vout = msm_chg_check_secondary_det(motg); + if (vout) + motg->chg_type = USB_DCP_CHARGER; + else + motg->chg_type = USB_CDP_CHARGER; + motg->chg_state = USB_CHG_STATE_SECONDARY_DONE; + /* fall through */ + case USB_CHG_STATE_SECONDARY_DONE: + motg->chg_state = USB_CHG_STATE_DETECTED; + case USB_CHG_STATE_DETECTED: + if (!motg->vbus_state) { + motg->chg_state = USB_CHG_STATE_IN_PROGRESS; + break; + } + + msm_chg_block_off(motg); + + /* Enable VDP_SRC in case of DCP charger */ + if (motg->chg_type == USB_DCP_CHARGER) { + ulpi_write(phy, 0x2, 0x85); + msm_otg_notify_charger(motg, dcp_max_current); + } else if (motg->chg_type == USB_NONCOMPLIANT_CHARGER) + msm_otg_notify_charger(motg, dcp_max_current); + else if (motg->chg_type == USB_FLOATED_CHARGER || + motg->chg_type == USB_CDP_CHARGER) + msm_otg_notify_charger(motg, IDEV_CHG_MAX); + + msm_otg_dbg_log_event(phy, "CHG WORK PUT: CHG_TYPE", + motg->chg_type, get_pm_runtime_counter(phy->dev)); + /* to match _get at the start of chg_det_work */ + pm_runtime_mark_last_busy(phy->dev); + pm_runtime_put_autosuspend(phy->dev); + motg->chg_state = USB_CHG_STATE_QUEUE_SM_WORK; + break; + case USB_CHG_STATE_QUEUE_SM_WORK: + if (!motg->vbus_state) { + pm_runtime_get_sync(phy->dev); + /* Turn off VDP_SRC if charger is DCP type */ + if (motg->chg_type == USB_DCP_CHARGER) + ulpi_write(phy, 0x2, 0x86); + + motg->chg_state = USB_CHG_STATE_UNDEFINED; + if (motg->chg_type == USB_SDP_CHARGER || + motg->chg_type == USB_CDP_CHARGER) + queue_sm_work = true; + + motg->chg_type = USB_INVALID_CHARGER; + msm_otg_notify_charger(motg, 0); + motg->cur_power = 0; + msm_chg_block_off(motg); + pm_runtime_mark_last_busy(phy->dev); + pm_runtime_put_autosuspend(phy->dev); + if (queue_sm_work) + queue_work(motg->otg_wq, &motg->sm_work); + else + return; + } + + if (motg->chg_type == USB_CDP_CHARGER || + motg->chg_type == USB_SDP_CHARGER) + queue_work(motg->otg_wq, &motg->sm_work); + + return; + default: + return; + } + + msm_otg_dbg_log_event(phy, "CHG WORK: QUEUE", motg->chg_type, delay); + queue_delayed_work(motg->otg_wq, &motg->chg_work, delay); +} + +/* + * We support OTG, Peripheral only and Host only configurations. In case + * of OTG, mode switch (host-->peripheral/peripheral-->host) can happen + * via Id pin status or user request (debugfs). Id/BSV interrupts are not + * enabled when switch is controlled by user and default mode is supplied + * by board file, which can be changed by userspace later. + */ +static void msm_otg_init_sm(struct msm_otg *motg) +{ + struct msm_otg_platform_data *pdata = motg->pdata; + u32 otgsc = readl_relaxed(USB_OTGSC); + + switch (pdata->mode) { + case USB_OTG: + if (pdata->otg_control == OTG_USER_CONTROL) { + if (pdata->default_mode == USB_HOST) { + clear_bit(ID, &motg->inputs); + } else if (pdata->default_mode == USB_PERIPHERAL) { + set_bit(ID, &motg->inputs); + set_bit(B_SESS_VLD, &motg->inputs); + } else { + set_bit(ID, &motg->inputs); + clear_bit(B_SESS_VLD, &motg->inputs); + } + } else if (pdata->otg_control == OTG_PHY_CONTROL) { + if (otgsc & OTGSC_ID) + set_bit(ID, &motg->inputs); + else + clear_bit(ID, &motg->inputs); + if (otgsc & OTGSC_BSV) + set_bit(B_SESS_VLD, &motg->inputs); + else + clear_bit(B_SESS_VLD, &motg->inputs); + } else if (pdata->otg_control == OTG_PMIC_CONTROL) { + if (pdata->pmic_id_irq) { + if (msm_otg_read_pmic_id_state(motg)) + set_bit(ID, &motg->inputs); + else + clear_bit(ID, &motg->inputs); + } else if (motg->ext_id_irq) { + if (gpio_get_value(pdata->usb_id_gpio)) + set_bit(ID, &motg->inputs); + else + clear_bit(ID, &motg->inputs); + } else if (motg->phy_irq) { + if (msm_otg_read_phy_id_state(motg)) { + set_bit(ID, &motg->inputs); + if (pdata->phy_id_high_as_peripheral) + set_bit(B_SESS_VLD, + &motg->inputs); + } else { + clear_bit(ID, &motg->inputs); + if (pdata->phy_id_high_as_peripheral) + clear_bit(B_SESS_VLD, + &motg->inputs); + } + } + } + break; + case USB_HOST: + clear_bit(ID, &motg->inputs); + break; + case USB_PERIPHERAL: + set_bit(ID, &motg->inputs); + if (pdata->otg_control == OTG_PHY_CONTROL) { + if (otgsc & OTGSC_BSV) + set_bit(B_SESS_VLD, &motg->inputs); + else + clear_bit(B_SESS_VLD, &motg->inputs); + } else if (pdata->otg_control == OTG_USER_CONTROL) { + set_bit(ID, &motg->inputs); + set_bit(B_SESS_VLD, &motg->inputs); + } + break; + default: + break; + } + msm_otg_dbg_log_event(&motg->phy, "SM INIT", pdata->mode, motg->inputs); + if (motg->id_state != USB_ID_GROUND) + motg->id_state = (test_bit(ID, &motg->inputs)) ? USB_ID_FLOAT : + USB_ID_GROUND; +} + +static void check_for_sdp_connection(struct work_struct *w) +{ + struct msm_otg *motg = container_of(w, struct msm_otg, sdp_check.work); + + /* Cable disconnected or device enumerated as SDP */ + if (!motg->vbus_state || motg->phy.otg->gadget->state > + USB_STATE_DEFAULT) + return; + + /* floating D+/D- lines detected */ + motg->vbus_state = 0; + msm_otg_dbg_log_event(&motg->phy, "Q RW SDP CHK", motg->vbus_state, 0); + msm_otg_set_vbus_state(motg->vbus_state); +} + +static void msm_otg_sm_work(struct work_struct *w) +{ + struct msm_otg *motg = container_of(w, struct msm_otg, sm_work); + struct usb_phy *phy = &motg->phy; + struct usb_otg *otg = motg->phy.otg; + struct device *dev = otg->usb_phy->dev; + bool work = false; + int ret; + + pr_debug("%s work\n", usb_otg_state_string(otg->state)); + msm_otg_dbg_log_event(phy, "SM WORK:", otg->state, motg->inputs); + + /* Just resume h/w if reqd, pm_count is handled based on state/inputs */ + if (motg->resume_pending) { + pm_runtime_get_sync(dev); + if (atomic_read(&motg->in_lpm)) { + dev_err(dev, "SM WORK: USB is in LPM\n"); + msm_otg_dbg_log_event(phy, "SM WORK: USB IS IN LPM", + otg->state, motg->inputs); + msm_otg_resume(motg); + } + motg->resume_pending = false; + pm_runtime_put_noidle(dev); + } + + switch (otg->state) { + case OTG_STATE_UNDEFINED: + pm_runtime_get_sync(dev); + msm_otg_reset(otg->usb_phy); + /* Add child device only after block reset */ + ret = of_platform_populate(motg->pdev->dev.of_node, NULL, NULL, + &motg->pdev->dev); + if (ret) + dev_dbg(&motg->pdev->dev, "failed to add BAM core\n"); + + msm_otg_init_sm(motg); + otg->state = OTG_STATE_B_IDLE; + if (!test_bit(B_SESS_VLD, &motg->inputs) && + test_bit(ID, &motg->inputs)) { + msm_otg_dbg_log_event(phy, "PM RUNTIME: UNDEF PUT", + get_pm_runtime_counter(dev), 0); + pm_runtime_put_sync(dev); + break; + } + pm_runtime_put(dev); + /* FALL THROUGH */ + case OTG_STATE_B_IDLE: + if (!test_bit(ID, &motg->inputs) && otg->host) { + pr_debug("!id\n"); + msm_otg_dbg_log_event(phy, "!ID", + motg->inputs, otg->state); + if (!otg->host) { + msm_otg_dbg_log_event(phy, + "SM WORK: Host Not Set", + otg->state, motg->inputs); + break; + } + + msm_otg_start_host(otg, 1); + otg->state = OTG_STATE_A_HOST; + } else if (test_bit(B_SESS_VLD, &motg->inputs)) { + pr_debug("b_sess_vld\n"); + msm_otg_dbg_log_event(phy, "B_SESS_VLD", + motg->inputs, otg->state); + if (!otg->gadget) { + msm_otg_dbg_log_event(phy, + "SM WORK: Gadget Not Set", + otg->state, motg->inputs); + break; + } + + pm_runtime_get_sync(otg->usb_phy->dev); + msm_otg_start_peripheral(otg, 1); + if (get_psy_type(motg) == POWER_SUPPLY_TYPE_USB_FLOAT || + (get_psy_type(motg) == POWER_SUPPLY_TYPE_USB && + motg->enable_sdp_check_timer)) { + queue_delayed_work(motg->otg_wq, + &motg->sdp_check, + msecs_to_jiffies( + (phy->flags & EUD_SPOOF_CONNECT) ? + SDP_CHECK_DELAY_MS : + SDP_CHECK_BOOT_DELAY_MS)); + } + otg->state = OTG_STATE_B_PERIPHERAL; + } else { + pr_debug("Cable disconnected\n"); + msm_otg_dbg_log_event(phy, "RT: Cable DISC", + get_pm_runtime_counter(dev), 0); + msm_otg_notify_charger(motg, 0); + } + break; + case OTG_STATE_B_PERIPHERAL: + if (!test_bit(B_SESS_VLD, &motg->inputs)) { + cancel_delayed_work_sync(&motg->sdp_check); + msm_otg_start_peripheral(otg, 0); + msm_otg_dbg_log_event(phy, "RT PM: B_PERI A PUT", + get_pm_runtime_counter(dev), 0); + /* Schedule work to finish cable disconnect processing*/ + otg->state = OTG_STATE_B_IDLE; + /* _put for _get done on cable connect in B_IDLE */ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + work = true; + } else if (test_bit(A_BUS_SUSPEND, &motg->inputs)) { + pr_debug("a_bus_suspend\n"); + msm_otg_dbg_log_event(phy, "BUS_SUSPEND: PM RT PUT", + get_pm_runtime_counter(dev), 0); + otg->state = OTG_STATE_B_SUSPEND; + /* _get on connect in B_IDLE or host resume in B_SUSP */ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + } + break; + case OTG_STATE_B_SUSPEND: + if (!test_bit(B_SESS_VLD, &motg->inputs)) { + cancel_delayed_work_sync(&motg->sdp_check); + msm_otg_start_peripheral(otg, 0); + otg->state = OTG_STATE_B_IDLE; + /* Schedule work to finish cable disconnect processing*/ + work = true; + } else if (!test_bit(A_BUS_SUSPEND, &motg->inputs)) { + pr_debug("!a_bus_suspend\n"); + otg->state = OTG_STATE_B_PERIPHERAL; + msm_otg_dbg_log_event(phy, "BUS_RESUME: PM RT GET", + get_pm_runtime_counter(dev), 0); + pm_runtime_get_sync(dev); + } + break; + case OTG_STATE_A_HOST: + if (test_bit(ID, &motg->inputs)) { + msm_otg_start_host(otg, 0); + otg->state = OTG_STATE_B_IDLE; + work = true; + } + break; + default: + break; + } + + if (work) + queue_work(motg->otg_wq, &motg->sm_work); +} + +static irqreturn_t msm_otg_irq(int irq, void *data) +{ + struct msm_otg *motg = data; + struct usb_otg *otg = motg->phy.otg; + u32 otgsc = 0; + bool work = false; + + if (atomic_read(&motg->in_lpm)) { + pr_debug("OTG IRQ: %d in LPM\n", irq); + msm_otg_dbg_log_event(&motg->phy, "OTG IRQ IS IN LPM", + irq, otg->state); + /*Ignore interrupt if one interrupt already seen in LPM*/ + if (motg->async_int) + return IRQ_HANDLED; + + disable_irq_nosync(irq); + motg->async_int = irq; + msm_otg_kick_sm_work(motg); + + return IRQ_HANDLED; + } + motg->usb_irq_count++; + + otgsc = readl_relaxed(USB_OTGSC); + if (!(otgsc & (OTGSC_IDIS | OTGSC_BSVIS))) + return IRQ_NONE; + + if ((otgsc & OTGSC_IDIS) && (otgsc & OTGSC_IDIE)) { + if (otgsc & OTGSC_ID) { + dev_dbg(otg->usb_phy->dev, "ID set\n"); + msm_otg_dbg_log_event(&motg->phy, "ID SET", + motg->inputs, otg->state); + set_bit(ID, &motg->inputs); + } else { + dev_dbg(otg->usb_phy->dev, "ID clear\n"); + msm_otg_dbg_log_event(&motg->phy, "ID CLEAR", + motg->inputs, otg->state); + clear_bit(ID, &motg->inputs); + } + work = true; + } else if ((otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) { + if (otgsc & OTGSC_BSV) { + dev_dbg(otg->usb_phy->dev, "BSV set\n"); + msm_otg_dbg_log_event(&motg->phy, "BSV SET", + motg->inputs, otg->state); + set_bit(B_SESS_VLD, &motg->inputs); + } else { + dev_dbg(otg->usb_phy->dev, "BSV clear\n"); + msm_otg_dbg_log_event(&motg->phy, "BSV CLEAR", + motg->inputs, otg->state); + clear_bit(B_SESS_VLD, &motg->inputs); + clear_bit(A_BUS_SUSPEND, &motg->inputs); + } + work = true; + } + if (work) + queue_work(motg->otg_wq, &motg->sm_work); + + writel_relaxed(otgsc, USB_OTGSC); + + return IRQ_HANDLED; +} + +static int +msm_otg_phy_drive_dp_pulse(struct msm_otg *motg, unsigned int pulse_width) +{ + int ret = 0; + u32 val; + + msm_otg_dbg_log_event(&motg->phy, "DRIVE DP PULSE", + motg->inputs, 0); + ret = msm_hsusb_ldo_enable(motg, USB_PHY_REG_ON); + if (ret) + return ret; + msm_hsusb_config_vddcx(1); + ret = regulator_enable(hsusb_vdd); + WARN(ret, "hsusb_vdd LDO enable failed for driving pulse\n"); + clk_prepare_enable(motg->xo_clk); + clk_prepare_enable(motg->phy_csr_clk); + clk_prepare_enable(motg->core_clk); + clk_prepare_enable(motg->pclk); + + msm_otg_exit_phy_retention(motg); + + val = readb_relaxed(USB_PHY_CSR_PHY_CTRL2); + val |= USB2_SUSPEND_N; + writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL2); + + val = readb_relaxed(USB_PHY_CSR_PHY_UTMI_CTRL1); + val &= ~XCVR_SEL_MASK; + val |= (DM_PULLDOWN | DP_PULLDOWN | 0x1); + writeb_relaxed(val, USB_PHY_CSR_PHY_UTMI_CTRL1); + + val = readb_relaxed(USB_PHY_CSR_PHY_UTMI_CTRL0); + val &= ~OP_MODE_MASK; + val |= (TERM_SEL | SLEEP_M | 0x20); + writeb_relaxed(val, USB_PHY_CSR_PHY_UTMI_CTRL0); + + val = readb_relaxed(USB_PHY_CSR_PHY_CFG0); + writeb_relaxed(0x6, USB_PHY_CSR_PHY_CFG0); + + writeb_relaxed( + (readb_relaxed(USB_PHY_CSR_PHY_UTMI_CTRL0) | PORT_SELECT), + USB_PHY_CSR_PHY_UTMI_CTRL0); + + usleep_range(10, 20); + + val = readb_relaxed(USB_PHY_CSR_PHY_UTMI_CTRL0); + val &= ~PORT_SELECT; + writeb_relaxed(val, USB_PHY_CSR_PHY_UTMI_CTRL0); + + val = readb_relaxed(USB_PHY_CSR_PHY_UTMI_CTRL2); + writeb_relaxed(0xFF, USB_PHY_CSR_PHY_UTMI_CTRL2); + + val = readb_relaxed(USB_PHY_CSR_PHY_UTMI_CTRL4); + val |= TX_VALID; + writeb_relaxed(val, USB_PHY_CSR_PHY_UTMI_CTRL4); + + msleep(pulse_width); + + val = readb_relaxed(USB_PHY_CSR_PHY_UTMI_CTRL4); + val &= ~TX_VALID; + writeb_relaxed(val, USB_PHY_CSR_PHY_UTMI_CTRL4); + + /* Make sure above writes are completed before clks off */ + mb(); + clk_disable_unprepare(motg->pclk); + clk_disable_unprepare(motg->core_clk); + clk_disable_unprepare(motg->phy_csr_clk); + clk_disable_unprepare(motg->xo_clk); + regulator_disable(hsusb_vdd); + msm_hsusb_config_vddcx(0); + msm_hsusb_ldo_enable(motg, USB_PHY_REG_OFF); + + msm_otg_dbg_log_event(&motg->phy, "DP PULSE DRIVEN", + motg->inputs, 0); + return 0; +} + +#define DP_PULSE_WIDTH_MSEC 200 + +static void msm_otg_set_vbus_state(int online) +{ + struct msm_otg *motg = the_msm_otg; + + motg->vbus_state = online; + + if (motg->err_event_seen) + return; + + if (online) { + pr_debug("EXTCON: BSV set\n"); + msm_otg_dbg_log_event(&motg->phy, "EXTCON: BSV SET", + motg->inputs, 0); + if (test_and_set_bit(B_SESS_VLD, &motg->inputs)) + return; + if (get_psy_type(motg) == POWER_SUPPLY_TYPE_USB_CDP) { + pr_debug("Connected to CDP, pull DP up\n"); + msm_otg_phy_drive_dp_pulse(motg, DP_PULSE_WIDTH_MSEC); + } + } else { + pr_debug("EXTCON: BSV clear\n"); + msm_otg_dbg_log_event(&motg->phy, "EXTCON: BSV CLEAR", + motg->inputs, 0); + if (!test_and_clear_bit(B_SESS_VLD, &motg->inputs)) + return; + } + + msm_otg_dbg_log_event(&motg->phy, "CHECK VBUS EVENT DURING SUSPEND", + atomic_read(&motg->pm_suspended), + motg->sm_work_pending); + + /* Move to host mode on vbus low if required */ + if (motg->pdata->vbus_low_as_hostmode) { + if (!test_bit(B_SESS_VLD, &motg->inputs)) + clear_bit(ID, &motg->inputs); + else + set_bit(ID, &motg->inputs); + } + + /* + * Enable PHY based charger detection in 2 cases: + * 1. PMI not capable of doing charger detection and provides VBUS + * notification with UNKNOWN psy type. + * 2. Data lines have been cut off from PMI, in which case it provides + * VBUS notification with FLOAT psy type and we want to do PHY based + * charger detection by setting 'chg_detection_for_float_charger'. + */ + if (test_bit(B_SESS_VLD, &motg->inputs) && !motg->chg_detection) { + if ((get_psy_type(motg) == POWER_SUPPLY_TYPE_UNKNOWN) || + (get_psy_type(motg) == POWER_SUPPLY_TYPE_USB_FLOAT && + chg_detection_for_float_charger)) + motg->chg_detection = true; + } + + if (motg->chg_detection) + queue_delayed_work(motg->otg_wq, &motg->chg_work, 0); + else + msm_otg_kick_sm_work(motg); +} + +static void msm_id_status_w(struct work_struct *w) +{ + struct msm_otg *motg = container_of(w, struct msm_otg, + id_status_work.work); + int work = 0; + + dev_dbg(motg->phy.dev, "ID status_w\n"); + + if (motg->pdata->pmic_id_irq) + motg->id_state = msm_otg_read_pmic_id_state(motg); + else if (motg->ext_id_irq) + motg->id_state = gpio_get_value(motg->pdata->usb_id_gpio); + else if (motg->phy_irq) + motg->id_state = msm_otg_read_phy_id_state(motg); + + if (motg->err_event_seen) + return; + + if (motg->id_state) { + if (gpio_is_valid(motg->pdata->switch_sel_gpio)) + gpio_direction_input(motg->pdata->switch_sel_gpio); + if (!test_and_set_bit(ID, &motg->inputs)) { + pr_debug("ID set\n"); + if (motg->pdata->phy_id_high_as_peripheral) + set_bit(B_SESS_VLD, &motg->inputs); + msm_otg_dbg_log_event(&motg->phy, "ID SET", + motg->inputs, motg->phy.otg->state); + work = 1; + } + } else { + if (gpio_is_valid(motg->pdata->switch_sel_gpio)) + gpio_direction_output(motg->pdata->switch_sel_gpio, 1); + if (test_and_clear_bit(ID, &motg->inputs)) { + pr_debug("ID clear\n"); + if (motg->pdata->phy_id_high_as_peripheral) + clear_bit(B_SESS_VLD, &motg->inputs); + msm_otg_dbg_log_event(&motg->phy, "ID CLEAR", + motg->inputs, motg->phy.otg->state); + work = 1; + } + } + + if (work && (motg->phy.otg->state != OTG_STATE_UNDEFINED)) { + msm_otg_dbg_log_event(&motg->phy, + "CHECK ID EVENT DURING SUSPEND", + atomic_read(&motg->pm_suspended), + motg->sm_work_pending); + msm_otg_kick_sm_work(motg); + } +} + +#define MSM_ID_STATUS_DELAY 5 /* 5msec */ +static irqreturn_t msm_id_irq(int irq, void *data) +{ + struct msm_otg *motg = data; + + /*schedule delayed work for 5msec for ID line state to settle*/ + queue_delayed_work(motg->otg_wq, &motg->id_status_work, + msecs_to_jiffies(MSM_ID_STATUS_DELAY)); + + return IRQ_HANDLED; +} + +int msm_otg_pm_notify(struct notifier_block *notify_block, + unsigned long mode, void *unused) +{ + struct msm_otg *motg = container_of( + notify_block, struct msm_otg, pm_notify); + + dev_dbg(motg->phy.dev, "OTG PM notify:%lx, sm_pending:%u\n", mode, + motg->sm_work_pending); + msm_otg_dbg_log_event(&motg->phy, "PM NOTIFY", + mode, motg->sm_work_pending); + + switch (mode) { + case PM_POST_SUSPEND: + /* OTG sm_work can be armed now */ + atomic_set(&motg->pm_suspended, 0); + + /* Handle any deferred wakeup events from USB during suspend */ + if (motg->sm_work_pending) { + motg->sm_work_pending = false; + queue_work(motg->otg_wq, &motg->sm_work); + } + break; + + default: + break; + } + + return NOTIFY_OK; +} + +static int msm_otg_mode_show(struct seq_file *s, void *unused) +{ + struct msm_otg *motg = s->private; + struct usb_otg *otg = motg->phy.otg; + + switch (otg->state) { + case OTG_STATE_A_HOST: + seq_puts(s, "host\n"); + break; + case OTG_STATE_B_IDLE: + case OTG_STATE_B_PERIPHERAL: + case OTG_STATE_B_SUSPEND: + seq_puts(s, "peripheral\n"); + break; + default: + seq_puts(s, "none\n"); + break; + } + + return 0; +} + +static int msm_otg_mode_open(struct inode *inode, struct file *file) +{ + return single_open(file, msm_otg_mode_show, inode->i_private); +} + +static ssize_t msm_otg_mode_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct msm_otg *motg = s->private; + char buf[16]; + struct usb_phy *phy = &motg->phy; + int status = count; + enum usb_mode_type req_mode; + + memset(buf, 0x00, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) { + status = -EFAULT; + goto out; + } + + if (!strcmp(buf, "host")) { + req_mode = USB_HOST; + } else if (!strcmp(buf, "peripheral")) { + req_mode = USB_PERIPHERAL; + } else if (!strcmp(buf, "none")) { + req_mode = USB_NONE; + } else { + status = -EINVAL; + goto out; + } + + switch (req_mode) { + case USB_NONE: + switch (phy->otg->state) { + case OTG_STATE_A_HOST: + case OTG_STATE_B_PERIPHERAL: + case OTG_STATE_B_SUSPEND: + set_bit(ID, &motg->inputs); + clear_bit(B_SESS_VLD, &motg->inputs); + break; + default: + goto out; + } + break; + case USB_PERIPHERAL: + switch (phy->otg->state) { + case OTG_STATE_B_IDLE: + case OTG_STATE_A_HOST: + set_bit(ID, &motg->inputs); + set_bit(B_SESS_VLD, &motg->inputs); + break; + default: + goto out; + } + break; + case USB_HOST: + switch (phy->otg->state) { + case OTG_STATE_B_IDLE: + case OTG_STATE_B_PERIPHERAL: + case OTG_STATE_B_SUSPEND: + clear_bit(ID, &motg->inputs); + break; + default: + goto out; + } + break; + default: + goto out; + } + + motg->id_state = (test_bit(ID, &motg->inputs)) ? USB_ID_FLOAT : + USB_ID_GROUND; + queue_work(motg->otg_wq, &motg->sm_work); +out: + return status; +} + +const struct file_operations msm_otg_mode_fops = { + .open = msm_otg_mode_open, + .read = seq_read, + .write = msm_otg_mode_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static int msm_otg_show_otg_state(struct seq_file *s, void *unused) +{ + struct msm_otg *motg = s->private; + struct usb_phy *phy = &motg->phy; + + seq_printf(s, "%s\n", usb_otg_state_string(phy->otg->state)); + return 0; +} + +static int msm_otg_otg_state_open(struct inode *inode, struct file *file) +{ + return single_open(file, msm_otg_show_otg_state, inode->i_private); +} + +const struct file_operations msm_otg_state_fops = { + .open = msm_otg_otg_state_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int msm_otg_bus_show(struct seq_file *s, void *unused) +{ + if (debug_bus_voting_enabled) + seq_puts(s, "enabled\n"); + else + seq_puts(s, "disabled\n"); + + return 0; +} + +static int msm_otg_bus_open(struct inode *inode, struct file *file) +{ + return single_open(file, msm_otg_bus_show, inode->i_private); +} + +static ssize_t msm_otg_bus_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + char buf[8]; + struct seq_file *s = file->private_data; + struct msm_otg *motg = s->private; + + memset(buf, 0x00, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strcmp(buf, "enable")) { + /* Do not vote here. Let OTG statemachine decide when to vote */ + debug_bus_voting_enabled = true; + } else { + debug_bus_voting_enabled = false; + msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE); + } + + return count; +} + +static int msm_otg_dbg_buff_show(struct seq_file *s, void *unused) +{ + struct msm_otg *motg = s->private; + unsigned long flags; + unsigned int i; + + read_lock_irqsave(&motg->dbg_lock, flags); + + i = motg->dbg_idx; + if (strnlen(motg->buf[i], DEBUG_MSG_LEN)) + seq_printf(s, "%s\n", motg->buf[i]); + for (dbg_inc(&i); i != motg->dbg_idx; dbg_inc(&i)) { + if (!strnlen(motg->buf[i], DEBUG_MSG_LEN)) + continue; + seq_printf(s, "%s\n", motg->buf[i]); + } + read_unlock_irqrestore(&motg->dbg_lock, flags); + + return 0; +} + +static int msm_otg_dbg_buff_open(struct inode *inode, struct file *file) +{ + return single_open(file, msm_otg_dbg_buff_show, inode->i_private); +} + +const struct file_operations msm_otg_dbg_buff_fops = { + .open = msm_otg_dbg_buff_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int msm_otg_dpdm_regulator_enable(struct regulator_dev *rdev) +{ + int ret = 0; + struct msm_otg *motg = rdev_get_drvdata(rdev); + struct usb_phy *phy = &motg->phy; + + if (!motg->rm_pulldown) { + msm_otg_dbg_log_event(&motg->phy, "Disable Pulldown", + motg->rm_pulldown, 0); + ret = msm_hsusb_ldo_enable(motg, USB_PHY_REG_3P3_ON); + if (ret) + return ret; + + motg->rm_pulldown = true; + /* Don't reset h/w if previous disconnect handling is pending */ + if (phy->otg->state == OTG_STATE_B_IDLE || + phy->otg->state == OTG_STATE_UNDEFINED) + msm_otg_set_mode_nondriving(motg, true); + else + msm_otg_dbg_log_event(&motg->phy, "NonDrv err", + motg->rm_pulldown, 0); + } + + return ret; +} + +static int msm_otg_dpdm_regulator_disable(struct regulator_dev *rdev) +{ + int ret = 0; + struct msm_otg *motg = rdev_get_drvdata(rdev); + struct usb_phy *phy = &motg->phy; + + if (motg->rm_pulldown) { + /* Let sm_work handle it if USB core is active */ + if (phy->otg->state == OTG_STATE_B_IDLE || + phy->otg->state == OTG_STATE_UNDEFINED) + msm_otg_set_mode_nondriving(motg, false); + + ret = msm_hsusb_ldo_enable(motg, USB_PHY_REG_3P3_OFF); + if (ret) + return ret; + + motg->rm_pulldown = false; + msm_otg_dbg_log_event(&motg->phy, "EN Pulldown", + motg->rm_pulldown, 0); + } + + return ret; +} + +static int msm_otg_dpdm_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct msm_otg *motg = rdev_get_drvdata(rdev); + + return motg->rm_pulldown; +} + +static struct regulator_ops msm_otg_dpdm_regulator_ops = { + .enable = msm_otg_dpdm_regulator_enable, + .disable = msm_otg_dpdm_regulator_disable, + .is_enabled = msm_otg_dpdm_regulator_is_enabled, +}; + +static int usb_phy_regulator_init(struct msm_otg *motg) +{ + struct device *dev = motg->phy.dev; + struct regulator_config cfg = {}; + struct regulator_init_data *init_data; + + init_data = devm_kzalloc(dev, sizeof(*init_data), GFP_KERNEL); + if (!init_data) + return -ENOMEM; + + init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_STATUS; + motg->dpdm_rdesc.owner = THIS_MODULE; + motg->dpdm_rdesc.type = REGULATOR_VOLTAGE; + motg->dpdm_rdesc.ops = &msm_otg_dpdm_regulator_ops; + motg->dpdm_rdesc.name = kbasename(dev->of_node->full_name); + + cfg.dev = dev; + cfg.init_data = init_data; + cfg.driver_data = motg; + cfg.of_node = dev->of_node; + + motg->dpdm_rdev = devm_regulator_register(dev, &motg->dpdm_rdesc, &cfg); + if (IS_ERR(motg->dpdm_rdev)) + return PTR_ERR_OR_ZERO(motg->dpdm_rdev); + + return 0; +} + +const struct file_operations msm_otg_bus_fops = { + .open = msm_otg_bus_open, + .read = seq_read, + .write = msm_otg_bus_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry *msm_otg_dbg_root; + +static int msm_otg_debugfs_init(struct msm_otg *motg) +{ + struct dentry *msm_otg_dentry; + struct msm_otg_platform_data *pdata = motg->pdata; + + msm_otg_dbg_root = debugfs_create_dir("msm_otg", NULL); + + if (!msm_otg_dbg_root || IS_ERR(msm_otg_dbg_root)) + return -ENODEV; + + if ((pdata->mode == USB_OTG || pdata->mode == USB_PERIPHERAL) && + pdata->otg_control == OTG_USER_CONTROL) { + + msm_otg_dentry = debugfs_create_file("mode", 0644, + msm_otg_dbg_root, motg, &msm_otg_mode_fops); + + if (!msm_otg_dentry) { + debugfs_remove(msm_otg_dbg_root); + msm_otg_dbg_root = NULL; + return -ENODEV; + } + } + + msm_otg_dentry = debugfs_create_file("bus_voting", 0644, + msm_otg_dbg_root, motg, &msm_otg_bus_fops); + + if (!msm_otg_dentry) { + debugfs_remove_recursive(msm_otg_dbg_root); + return -ENODEV; + } + + msm_otg_dentry = debugfs_create_file("otg_state", 0444, + msm_otg_dbg_root, motg, &msm_otg_state_fops); + + if (!msm_otg_dentry) { + debugfs_remove_recursive(msm_otg_dbg_root); + return -ENODEV; + } + + msm_otg_dentry = debugfs_create_file("dbg_buff", 0444, + msm_otg_dbg_root, motg, &msm_otg_dbg_buff_fops); + + if (!msm_otg_dentry) { + debugfs_remove_recursive(msm_otg_dbg_root); + return -ENODEV; + } + return 0; +} + +static void msm_otg_debugfs_cleanup(void) +{ + debugfs_remove_recursive(msm_otg_dbg_root); +} + +static long clk_perf_rate; + +static ssize_t perf_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%ld\n", clk_perf_rate); +} + +static ssize_t +perf_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct msm_otg *motg = the_msm_otg; + int ret; + + pr_debug("%s: enable:%d\n", __func__, !strncasecmp(buf, "enable", 6)); + + if (!strncasecmp(buf, "enable", 6)) { + clk_perf_rate = motg->core_clk_nominal_rate; + msm_otg_bus_freq_set(motg, USB_NOC_NOM_VOTE); + } else { + clk_perf_rate = motg->core_clk_svs_rate; + msm_otg_bus_freq_set(motg, USB_NOC_SVS_VOTE); + } + + if (clk_perf_rate) { + pr_debug("Set usb sys_clk rate:%ld\n", clk_perf_rate); + ret = clk_set_rate(motg->core_clk, clk_perf_rate); + if (ret) + pr_err("sys_clk set_rate fail:%d %ld\n", + ret, clk_perf_rate); + msm_otg_dbg_log_event(&motg->phy, "OTG PERF SET", + clk_perf_rate, ret); + } else { + pr_err("usb sys_clk rate is undefined\n"); + } + + return count; +} + +static DEVICE_ATTR_RW(perf_mode); + +/** + * dbg_create_files: initializes the attribute interface + * @pdev: Platform device + * + * This function returns an error code + */ + +static int __maybe_unused dbg_create_files(struct platform_device *pdev) +{ + + int retval = 0; + + if (pdev == NULL) + return -EINVAL; + + retval = device_create_file(&pdev->dev, + &dev_attr_override_phy_init_enable); + if (retval) + goto rm_phy; + + retval = device_create_file(&pdev->dev, + &dev_attr_lpm_disconnect_thresh_enable); + if (retval) + goto rm_lpm; + + retval = device_create_file(&pdev->dev, + &dev_attr_floated_charger_enable_value); + if (retval) + goto rm_charger; + + retval = device_create_file(&pdev->dev, + &dev_attr_debug_log_enable); + if (retval) + goto rm_log; + + retval = device_create_file(&pdev->dev, + &dev_attr_dcp_max_current_value); + if (retval) + goto rm_dcp; + + retval = device_create_file(&pdev->dev, + &dev_attr_chg_detection_for_float_charger_value); + if (retval) + goto rm_float; + + return 0; + +rm_phy: + device_remove_file(&pdev->dev, &dev_attr_override_phy_init_enable); +rm_lpm: + device_remove_file(&pdev->dev, &dev_attr_lpm_disconnect_thresh_enable); +rm_charger: + device_remove_file(&pdev->dev, &dev_attr_floated_charger_enable_value); +rm_log: + device_remove_file(&pdev->dev, &dev_attr_debug_log_enable); +rm_dcp: + device_remove_file(&pdev->dev, &dev_attr_dcp_max_current_value); +rm_float: + device_remove_file(&pdev->dev, + &dev_attr_chg_detection_for_float_charger_value); + + return retval; +} + +/** + * dbg_remove_files: destroys the attribute interface + * @pdev: Platform device + * + * This function returns an error code + */ + +static int __maybe_unused dbg_remove_files(struct platform_device *pdev) +{ + if (pdev == NULL) + return -EINVAL; + + device_remove_file(&pdev->dev, &dev_attr_override_phy_init_enable); + device_remove_file(&pdev->dev, &dev_attr_lpm_disconnect_thresh_enable); + device_remove_file(&pdev->dev, &dev_attr_floated_charger_enable_value); + device_remove_file(&pdev->dev, &dev_attr_debug_log_enable); + device_remove_file(&pdev->dev, &dev_attr_dcp_max_current_value); + device_remove_file(&pdev->dev, + &dev_attr_chg_detection_for_float_charger_value); + + return 0; +} + + +#define MSM_OTG_CMD_ID 0x09 +#define MSM_OTG_DEVICE_ID 0x04 +#define MSM_OTG_VMID_IDX 0xFF +#define MSM_OTG_MEM_TYPE 0x02 +struct msm_otg_scm_cmd_buf { + unsigned int device_id; + unsigned int vmid_idx; + unsigned int mem_type; +} __attribute__ ((__packed__)); + +static void msm_otg_pnoc_errata_fix(struct msm_otg *motg) +{ + int ret; + struct msm_otg_platform_data *pdata = motg->pdata; + struct scm_desc desc = {0}; + + if (!pdata->pnoc_errata_fix) + return; + + dev_dbg(motg->phy.dev, "applying fix for pnoc h/w issue\n"); + + desc.args[0] = MSM_OTG_DEVICE_ID; + desc.args[1] = MSM_OTG_VMID_IDX; + desc.args[2] = MSM_OTG_MEM_TYPE; + desc.arginfo = SCM_ARGS(2); + + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_MP, MSM_OTG_CMD_ID), &desc); + + if (ret) + dev_err(motg->phy.dev, "scm command failed to update VMIDMT\n"); +} + +static u64 msm_otg_dma_mask = DMA_BIT_MASK(32); +static struct platform_device *msm_otg_add_pdev( + struct platform_device *ofdev, const char *name) +{ + struct platform_device *pdev; + const struct resource *res = ofdev->resource; + unsigned int num = ofdev->num_resources; + int retval; + struct ci13xxx_platform_data ci_pdata; + struct msm_otg_platform_data *otg_pdata; + struct msm_otg *motg; + + pdev = platform_device_alloc(name, -1); + if (!pdev) { + retval = -ENOMEM; + goto error; + } + + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + pdev->dev.dma_mask = &msm_otg_dma_mask; + pdev->dev.parent = &ofdev->dev; + + if (num) { + retval = platform_device_add_resources(pdev, res, num); + if (retval) + goto error; + } + + if (!strcmp(name, "msm_hsusb")) { + otg_pdata = + (struct msm_otg_platform_data *) + ofdev->dev.platform_data; + motg = platform_get_drvdata(ofdev); + ci_pdata.log2_itc = otg_pdata->log2_itc; + ci_pdata.usb_core_id = 0; + ci_pdata.l1_supported = otg_pdata->l1_supported; + ci_pdata.enable_ahb2ahb_bypass = + otg_pdata->enable_ahb2ahb_bypass; + ci_pdata.enable_streaming = otg_pdata->enable_streaming; + ci_pdata.enable_axi_prefetch = otg_pdata->enable_axi_prefetch; + retval = platform_device_add_data(pdev, &ci_pdata, + sizeof(ci_pdata)); + if (retval) + goto error; + } + + arch_setup_dma_ops(&pdev->dev, 0, DMA_BIT_MASK(32), NULL, false); + retval = platform_device_add(pdev); + if (retval) + goto error; + + return pdev; + +error: + platform_device_put(pdev); + return ERR_PTR(retval); +} + +static int msm_otg_setup_devices(struct platform_device *ofdev, + enum usb_mode_type mode, bool init) +{ + const char *gadget_name = "msm_hsusb"; + const char *host_name = "msm_hsusb_host"; + static struct platform_device *gadget_pdev; + static struct platform_device *host_pdev; + int retval = 0; + + if (!init) { + if (gadget_pdev) { + device_remove_file(&gadget_pdev->dev, + &dev_attr_perf_mode); + platform_device_unregister(gadget_pdev); + } + if (host_pdev) + platform_device_unregister(host_pdev); + return 0; + } + + switch (mode) { + case USB_OTG: + /* fall through */ + case USB_PERIPHERAL: + gadget_pdev = msm_otg_add_pdev(ofdev, gadget_name); + if (IS_ERR(gadget_pdev)) { + retval = PTR_ERR(gadget_pdev); + break; + } + if (device_create_file(&gadget_pdev->dev, &dev_attr_perf_mode)) + dev_err(&gadget_pdev->dev, "perf_mode file failed\n"); + if (mode == USB_PERIPHERAL) + break; + /* fall through */ + case USB_HOST: + host_pdev = msm_otg_add_pdev(ofdev, host_name); + if (IS_ERR(host_pdev)) { + retval = PTR_ERR(host_pdev); + if (mode == USB_OTG) { + platform_device_unregister(gadget_pdev); + device_remove_file(&gadget_pdev->dev, + &dev_attr_perf_mode); + } + } + break; + default: + break; + } + + return retval; +} + +static ssize_t dpdm_pulldown_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct msm_otg *motg = the_msm_otg; + struct msm_otg_platform_data *pdata = motg->pdata; + + return scnprintf(buf, PAGE_SIZE, "%s\n", pdata->dpdm_pulldown_added ? + "enabled" : "disabled"); +} + +static ssize_t dpdm_pulldown_enable_store(struct device *dev, + struct device_attribute *attr, const char + *buf, size_t size) +{ + struct msm_otg *motg = the_msm_otg; + struct msm_otg_platform_data *pdata = motg->pdata; + + if (!strncasecmp(buf, "enable", 6)) { + pdata->dpdm_pulldown_added = true; + return size; + } else if (!strncasecmp(buf, "disable", 7)) { + pdata->dpdm_pulldown_added = false; + return size; + } + + return -EINVAL; +} + +static DEVICE_ATTR_RW(dpdm_pulldown_enable); + +static int msm_otg_vbus_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + msm_otg_set_vbus_state(!!event); + + return NOTIFY_DONE; +} + +static int msm_otg_id_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct usb_phy *phy = container_of(nb, struct usb_phy, id_nb); + struct msm_otg *motg = container_of(phy, struct msm_otg, phy); + + if (event) + motg->id_state = USB_ID_GROUND; + else + motg->id_state = USB_ID_FLOAT; + + msm_id_status_w(&motg->id_status_work.work); + + return NOTIFY_DONE; +} + +struct msm_otg_platform_data *msm_otg_dt_to_pdata(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct msm_otg_platform_data *pdata; + int len = 0; + int res_gpio; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + len = of_property_count_elems_of_size(node, + "qcom,hsusb-otg-phy-init-seq", sizeof(len)); + if (len > 0) { + pdata->phy_init_seq = devm_kzalloc(&pdev->dev, + len * sizeof(len), GFP_KERNEL); + if (!pdata->phy_init_seq) + return NULL; + of_property_read_u32_array(node, "qcom,hsusb-otg-phy-init-seq", + pdata->phy_init_seq, len); + } + of_property_read_u32(node, "qcom,hsusb-otg-power-budget", + &pdata->power_budget); + of_property_read_u32(node, "qcom,hsusb-otg-mode", + &pdata->mode); + of_property_read_u32(node, "qcom,hsusb-otg-otg-control", + &pdata->otg_control); + of_property_read_u32(node, "qcom,hsusb-otg-default-mode", + &pdata->default_mode); + of_property_read_u32(node, "qcom,hsusb-otg-phy-type", + &pdata->phy_type); + pdata->disable_reset_on_disconnect = of_property_read_bool(node, + "qcom,hsusb-otg-disable-reset"); + pdata->pnoc_errata_fix = of_property_read_bool(node, + "qcom,hsusb-otg-pnoc-errata-fix"); + pdata->enable_lpm_on_dev_suspend = of_property_read_bool(node, + "qcom,hsusb-otg-lpm-on-dev-suspend"); + pdata->core_clk_always_on_workaround = of_property_read_bool(node, + "qcom,hsusb-otg-clk-always-on-workaround"); + pdata->delay_lpm_on_disconnect = of_property_read_bool(node, + "qcom,hsusb-otg-delay-lpm"); + pdata->dp_manual_pullup = of_property_read_bool(node, + "qcom,dp-manual-pullup"); + pdata->enable_sec_phy = of_property_read_bool(node, + "qcom,usb2-enable-hsphy2"); + of_property_read_u32(node, "qcom,hsusb-log2-itc", + &pdata->log2_itc); + + pdata->pmic_id_irq = platform_get_irq_byname(pdev, "pmic_id_irq"); + if (pdata->pmic_id_irq < 0) + pdata->pmic_id_irq = 0; + + pdata->hub_reset_gpio = of_get_named_gpio( + node, "qcom,hub-reset-gpio", 0); + if (!gpio_is_valid(pdata->hub_reset_gpio)) + pr_debug("hub_reset_gpio is not available\n"); + + pdata->usbeth_reset_gpio = of_get_named_gpio( + node, "qcom,usbeth-reset-gpio", 0); + if (!gpio_is_valid(pdata->usbeth_reset_gpio)) + pr_debug("usbeth_reset_gpio is not available\n"); + + pdata->switch_sel_gpio = + of_get_named_gpio(node, "qcom,sw-sel-gpio", 0); + if (!gpio_is_valid(pdata->switch_sel_gpio)) + pr_debug("switch_sel_gpio is not available\n"); + + pdata->usb_id_gpio = + of_get_named_gpio(node, "qcom,usbid-gpio", 0); + if (!gpio_is_valid(pdata->usb_id_gpio)) + pr_debug("usb_id_gpio is not available\n"); + + pdata->l1_supported = of_property_read_bool(node, + "qcom,hsusb-l1-supported"); + pdata->enable_ahb2ahb_bypass = of_property_read_bool(node, + "qcom,ahb-async-bridge-bypass"); + pdata->disable_retention_with_vdd_min = of_property_read_bool(node, + "qcom,disable-retention-with-vdd-min"); + pdata->enable_phy_id_pullup = of_property_read_bool(node, + "qcom,enable-phy-id-pullup"); + pdata->phy_dvdd_always_on = of_property_read_bool(node, + "qcom,phy-dvdd-always-on"); + + res_gpio = of_get_named_gpio(node, "qcom,hsusb-otg-vddmin-gpio", 0); + if (!gpio_is_valid(res_gpio)) + res_gpio = 0; + pdata->vddmin_gpio = res_gpio; + + pdata->emulation = of_property_read_bool(node, + "qcom,emulation"); + + pdata->enable_streaming = of_property_read_bool(node, + "qcom,boost-sysclk-with-streaming"); + + pdata->enable_axi_prefetch = of_property_read_bool(node, + "qcom,axi-prefetch-enable"); + + pdata->vbus_low_as_hostmode = of_property_read_bool(node, + "qcom,vbus-low-as-hostmode"); + + pdata->phy_id_high_as_peripheral = of_property_read_bool(node, + "qcom,phy-id-high-as-peripheral"); + + return pdata; +} + +static int msm_otg_probe(struct platform_device *pdev) +{ + int ret = 0; + int len = 0; + u32 tmp[3]; + struct resource *res; + struct msm_otg *motg; + struct usb_phy *phy; + struct msm_otg_platform_data *pdata; + void __iomem *tcsr; + int id_irq = 0; + + dev_info(&pdev->dev, "msm_otg probe\n"); + + motg = kzalloc(sizeof(struct msm_otg), GFP_KERNEL); + if (!motg) + return -ENOMEM; + + /* + * USB Core is running its protocol engine based on CORE CLK, + * CORE CLK must be running at >55Mhz for correct HSUSB + * operation and USB core cannot tolerate frequency changes on + * CORE CLK. For such USB cores, vote for maximum clk frequency + * on pclk source + */ + motg->core_clk = clk_get(&pdev->dev, "core_clk"); + if (IS_ERR(motg->core_clk)) { + ret = PTR_ERR(motg->core_clk); + motg->core_clk = NULL; + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "failed to get core_clk\n"); + goto free_motg; + } + + motg->core_reset = devm_reset_control_get(&pdev->dev, "core_reset"); + if (IS_ERR(motg->core_reset)) { + dev_err(&pdev->dev, "failed to get core_reset\n"); + ret = PTR_ERR(motg->core_reset); + goto put_core_clk; + } + + /* + * USB Core CLK can run at max freq if streaming is enabled. Hence, + * get Max supported clk frequency for USB Core CLK and request to set + * the same. Otherwise set USB Core CLK to defined default value. + */ + if (of_property_read_u32(pdev->dev.of_node, + "qcom,max-nominal-sysclk-rate", &ret)) { + ret = -EINVAL; + goto put_core_clk; + } else { + motg->core_clk_nominal_rate = clk_round_rate(motg->core_clk, + ret); + } + + if (of_property_read_u32(pdev->dev.of_node, + "qcom,max-svs-sysclk-rate", &ret)) { + dev_dbg(&pdev->dev, "core_clk svs freq not specified\n"); + } else { + motg->core_clk_svs_rate = clk_round_rate(motg->core_clk, ret); + } + + motg->default_noc_mode = USB_NOC_NOM_VOTE; + if (of_property_read_bool(pdev->dev.of_node, "qcom,default-mode-svs")) { + motg->core_clk_rate = motg->core_clk_svs_rate; + motg->default_noc_mode = USB_NOC_SVS_VOTE; + } else if (of_property_read_bool(pdev->dev.of_node, + "qcom,boost-sysclk-with-streaming")) { + motg->core_clk_rate = motg->core_clk_nominal_rate; + } else { + motg->core_clk_rate = clk_round_rate(motg->core_clk, + USB_DEFAULT_SYSTEM_CLOCK); + } + + if (IS_ERR_VALUE(motg->core_clk_rate)) { + dev_err(&pdev->dev, "fail to get core clk max freq.\n"); + } else { + ret = clk_set_rate(motg->core_clk, motg->core_clk_rate); + if (ret) + dev_err(&pdev->dev, "fail to set core_clk freq:%d\n", + ret); + } + + motg->pclk = clk_get(&pdev->dev, "iface_clk"); + if (IS_ERR(motg->pclk)) { + ret = PTR_ERR(motg->pclk); + motg->pclk = NULL; + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "failed to get iface_clk\n"); + goto put_core_clk; + } + + motg->xo_clk = clk_get(&pdev->dev, "xo"); + if (IS_ERR(motg->xo_clk)) { + ret = PTR_ERR(motg->xo_clk); + motg->xo_clk = NULL; + if (ret == -EPROBE_DEFER) + goto put_pclk; + } + + /* + * On few platforms USB PHY is fed with sleep clk. + * Hence don't fail probe. + */ + motg->sleep_clk = devm_clk_get(&pdev->dev, "sleep_clk"); + if (IS_ERR(motg->sleep_clk)) { + ret = PTR_ERR(motg->sleep_clk); + motg->sleep_clk = NULL; + if (ret == -EPROBE_DEFER) + goto put_xo_clk; + else + dev_dbg(&pdev->dev, "failed to get sleep_clk\n"); + } else { + ret = clk_prepare_enable(motg->sleep_clk); + if (ret) { + dev_err(&pdev->dev, "%s failed to vote sleep_clk%d\n", + __func__, ret); + goto put_xo_clk; + } + } + + /* + * If present, phy_reset_clk is used to reset the PHY, ULPI bridge + * and CSR Wrapper. This is a reset only clock. + */ + + if (of_property_match_string(pdev->dev.of_node, + "clock-names", "phy_reset_clk") >= 0) { + motg->phy_reset_clk = devm_clk_get(&pdev->dev, "phy_reset_clk"); + if (IS_ERR(motg->phy_reset_clk)) { + ret = PTR_ERR(motg->phy_reset_clk); + goto disable_sleep_clk; + } + } + + motg->phy_reset = devm_reset_control_get(&pdev->dev, + "phy_reset"); + if (IS_ERR(motg->phy_reset)) { + dev_err(&pdev->dev, "failed to get phy_reset\n"); + ret = PTR_ERR(motg->phy_reset); + goto disable_sleep_clk; + } + + /* + * If present, phy_por_clk is used to assert/de-assert phy POR + * input. This is a reset only clock. phy POR must be asserted + * after overriding the parameter registers via CSR wrapper or + * ULPI bridge. + */ + if (of_property_match_string(pdev->dev.of_node, + "clock-names", "phy_por_clk") >= 0) { + motg->phy_por_clk = devm_clk_get(&pdev->dev, "phy_por_clk"); + if (IS_ERR(motg->phy_por_clk)) { + ret = PTR_ERR(motg->phy_por_clk); + goto disable_sleep_clk; + } + } + + motg->phy_por_reset = devm_reset_control_get(&pdev->dev, + "phy_por_reset"); + if (IS_ERR(motg->phy_por_reset)) { + dev_err(&pdev->dev, "failed to get phy_por_reset\n"); + ret = PTR_ERR(motg->phy_por_reset); + goto disable_sleep_clk; + } + + /* + * If present, phy_csr_clk is required for accessing PHY + * CSR registers via AHB2PHY interface. + */ + if (of_property_match_string(pdev->dev.of_node, + "clock-names", "phy_csr_clk") >= 0) { + motg->phy_csr_clk = devm_clk_get(&pdev->dev, "phy_csr_clk"); + if (IS_ERR(motg->phy_csr_clk)) { + ret = PTR_ERR(motg->phy_csr_clk); + goto disable_sleep_clk; + } else { + ret = clk_prepare_enable(motg->phy_csr_clk); + if (ret) { + dev_err(&pdev->dev, + "fail to enable phy csr clk %d\n", ret); + goto disable_sleep_clk; + } + } + } + + of_property_read_u32(pdev->dev.of_node, "qcom,pm-qos-latency", + &motg->pm_qos_latency); + + motg->enable_sdp_check_timer = of_property_read_bool(pdev->dev.of_node, + "qcom,enumeration-check-for-sdp"); + + pdata = msm_otg_dt_to_pdata(pdev); + if (!pdata) { + ret = -ENOMEM; + goto disable_phy_csr_clk; + } + pdev->dev.platform_data = pdata; + + pdata->bus_scale_table = msm_bus_cl_get_pdata(pdev); + if (!pdata->bus_scale_table) + dev_dbg(&pdev->dev, "bus scaling is disabled\n"); + + if (pdata->phy_type == QUSB_ULPI_PHY) { + if (of_property_match_string(pdev->dev.of_node, + "clock-names", "phy_ref_clk") >= 0) { + motg->phy_ref_clk = devm_clk_get(&pdev->dev, + "phy_ref_clk"); + if (IS_ERR(motg->phy_ref_clk)) { + ret = PTR_ERR(motg->phy_ref_clk); + goto disable_phy_csr_clk; + } else { + ret = clk_prepare_enable(motg->phy_ref_clk); + if (ret) { + dev_err(&pdev->dev, + "fail to enable phy ref clk %d\n", + ret); + goto disable_phy_csr_clk; + } + } + } + } + + motg->phy.otg = devm_kzalloc(&pdev->dev, sizeof(struct usb_otg), + GFP_KERNEL); + if (!motg->phy.otg) { + ret = -ENOMEM; + goto disable_phy_csr_clk; + } + + the_msm_otg = motg; + motg->pdata = pdata; + phy = &motg->phy; + phy->dev = &pdev->dev; + motg->pdev = pdev; + motg->dbg_idx = 0; + motg->dbg_lock = __RW_LOCK_UNLOCKED(lck); + + if (motg->pdata->bus_scale_table) { + motg->bus_perf_client = + msm_bus_scale_register_client(motg->pdata->bus_scale_table); + if (!motg->bus_perf_client) { + dev_err(motg->phy.dev, "%s: Failed to register BUS\n" + "scaling client!!\n", __func__); + } else { + debug_bus_voting_enabled = true; + /* Some platforms require BUS vote to control clocks */ + msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE); + } + } + + ret = msm_otg_bus_freq_get(motg); + if (ret) { + pr_err("failed to get noc clocks: %d\n", ret); + } else { + ret = msm_otg_bus_freq_set(motg, motg->default_noc_mode); + if (ret) + pr_err("failed to vote explicit noc rates: %d\n", ret); + } + + /* initialize reset counter */ + motg->reset_counter = 0; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); + if (!res) { + dev_err(&pdev->dev, "failed to get core iomem resource\n"); + ret = -ENODEV; + goto devote_bus_bw; + } + + motg->io_res = res; + motg->regs = ioremap(res->start, resource_size(res)); + if (!motg->regs) { + dev_err(&pdev->dev, "core iomem ioremap failed\n"); + ret = -ENOMEM; + goto devote_bus_bw; + } + dev_info(&pdev->dev, "OTG regs = %pK\n", motg->regs); + + if (pdata->enable_sec_phy) { + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "tcsr"); + if (!res) { + dev_dbg(&pdev->dev, "missing TCSR memory resource\n"); + } else { + tcsr = devm_ioremap_nocache(&pdev->dev, res->start, + resource_size(res)); + if (!tcsr) { + dev_dbg(&pdev->dev, "tcsr ioremap failed\n"); + } else { + /* Enable USB2 on secondary HSPHY. */ + writel_relaxed(0x1, tcsr); + /* + * Ensure that TCSR write is completed before + * USB registers initialization. + */ + mb(); + } + } + } + + if (pdata->enable_sec_phy) + motg->usb_phy_ctrl_reg = USB_PHY_CTRL2; + else + motg->usb_phy_ctrl_reg = USB_PHY_CTRL; + + /* + * The USB PHY wrapper provides a register interface + * through AHB2PHY for performing PHY related operations + * like retention, HV interrupts and overriding parameter + * registers etc. The registers start at 4 byte boundary + * but only the first byte is valid and remaining are not + * used. Relaxed versions of readl/writel should be used. + * + * The link does not have any PHY specific registers. + * Hence set motg->usb_phy_ctrl_reg to. + */ + if (motg->pdata->phy_type == SNPS_FEMTO_PHY || + pdata->phy_type == QUSB_ULPI_PHY) { + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "phy_csr"); + if (!res) { + dev_err(&pdev->dev, "PHY CSR IOMEM missing!\n"); + ret = -ENODEV; + goto free_regs; + } + motg->phy_csr_regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(motg->phy_csr_regs)) { + ret = PTR_ERR(motg->phy_csr_regs); + dev_err(&pdev->dev, "PHY CSR ioremap failed!\n"); + goto free_regs; + } + motg->usb_phy_ctrl_reg = 0; + } + + motg->irq = platform_get_irq(pdev, 0); + if (!motg->irq) { + dev_err(&pdev->dev, "platform_get_irq failed\n"); + ret = -ENODEV; + goto free_regs; + } + + motg->async_irq = platform_get_irq_byname(pdev, "async_irq"); + if (motg->async_irq < 0) { + dev_err(&pdev->dev, "platform_get_irq for async_int failed\n"); + motg->async_irq = 0; + goto free_regs; + } + + if (motg->xo_clk) { + ret = clk_prepare_enable(motg->xo_clk); + if (ret) { + dev_err(&pdev->dev, + "%s failed to vote for TCXO %d\n", + __func__, ret); + goto free_xo_handle; + } + } + + + clk_prepare_enable(motg->pclk); + + hsusb_vdd = devm_regulator_get(motg->phy.dev, "hsusb_vdd_dig"); + if (IS_ERR(hsusb_vdd)) { + hsusb_vdd = devm_regulator_get(motg->phy.dev, "HSUSB_VDDCX"); + if (IS_ERR(hsusb_vdd)) { + dev_err(motg->phy.dev, "unable to get hsusb vddcx\n"); + ret = PTR_ERR(hsusb_vdd); + goto devote_xo_handle; + } + } + + len = of_property_count_elems_of_size(pdev->dev.of_node, + "qcom,vdd-voltage-level", sizeof(len)); + if (len > 0) { + if (len == sizeof(tmp) / sizeof(len)) { + of_property_read_u32_array(pdev->dev.of_node, + "qcom,vdd-voltage-level", + tmp, len); + vdd_val[0] = tmp[0]; + vdd_val[1] = tmp[1]; + vdd_val[2] = tmp[2]; + } else { + dev_dbg(&pdev->dev, + "Using default hsusb vdd config.\n"); + goto devote_xo_handle; + } + } else { + goto devote_xo_handle; + } + + ret = msm_hsusb_config_vddcx(1); + if (ret) { + dev_err(&pdev->dev, "hsusb vddcx configuration failed\n"); + goto devote_xo_handle; + } + + ret = regulator_enable(hsusb_vdd); + if (ret) { + dev_err(&pdev->dev, "unable to enable the hsusb vddcx\n"); + goto free_config_vddcx; + } + + ret = msm_hsusb_ldo_init(motg, 1); + if (ret) { + dev_err(&pdev->dev, "hsusb vreg configuration failed\n"); + goto free_hsusb_vdd; + } + + /* Get pinctrl if target uses pinctrl */ + motg->phy_pinctrl = devm_pinctrl_get(&pdev->dev); + if (IS_ERR(motg->phy_pinctrl)) { + if (of_property_read_bool(pdev->dev.of_node, "pinctrl-names")) { + dev_err(&pdev->dev, "Error encountered while getting pinctrl\n"); + ret = PTR_ERR(motg->phy_pinctrl); + goto free_ldo_init; + } + dev_dbg(&pdev->dev, "Target does not use pinctrl\n"); + motg->phy_pinctrl = NULL; + } + + ret = msm_hsusb_ldo_enable(motg, USB_PHY_REG_ON); + if (ret) { + dev_err(&pdev->dev, "hsusb vreg enable failed\n"); + goto free_ldo_init; + } + clk_prepare_enable(motg->core_clk); + + /* Check if USB mem_type change is needed to workaround PNOC hw issue */ + msm_otg_pnoc_errata_fix(motg); + + writel_relaxed(0, USB_USBINTR); + writel_relaxed(0, USB_OTGSC); + /* Ensure that above STOREs are completed before enabling interrupts */ + mb(); + + motg->id_state = USB_ID_FLOAT; + set_bit(ID, &motg->inputs); + INIT_WORK(&motg->sm_work, msm_otg_sm_work); + INIT_DELAYED_WORK(&motg->chg_work, msm_chg_detect_work); + INIT_DELAYED_WORK(&motg->id_status_work, msm_id_status_w); + INIT_DELAYED_WORK(&motg->perf_vote_work, msm_otg_perf_vote_work); + INIT_DELAYED_WORK(&motg->sdp_check, check_for_sdp_connection); + INIT_WORK(&motg->notify_charger_work, msm_otg_notify_charger_work); + motg->otg_wq = alloc_ordered_workqueue("k_otg", WQ_FREEZABLE); + if (!motg->otg_wq) { + pr_err("%s: Unable to create workqueue otg_wq\n", + __func__); + goto disable_core_clk; + } + + ret = devm_request_irq(&pdev->dev, motg->irq, msm_otg_irq, IRQF_SHARED, + "msm_otg", motg); + if (ret) { + dev_err(&pdev->dev, "request irq failed\n"); + goto destroy_wq; + } + + motg->phy_irq = platform_get_irq_byname(pdev, "phy_irq"); + if (motg->phy_irq < 0) { + dev_dbg(&pdev->dev, "phy_irq is not present\n"); + motg->phy_irq = 0; + } else { + + /* clear all interrupts before enabling the IRQ */ + writeb_relaxed(0xFF, USB2_PHY_USB_PHY_INTERRUPT_CLEAR0); + writeb_relaxed(0xFF, USB2_PHY_USB_PHY_INTERRUPT_CLEAR1); + + writeb_relaxed(0x1, USB2_PHY_USB_PHY_IRQ_CMD); + /* + * Databook says 200 usec delay is required for + * clearing the interrupts. + */ + udelay(200); + writeb_relaxed(0x0, USB2_PHY_USB_PHY_IRQ_CMD); + + ret = devm_request_irq(&pdev->dev, motg->phy_irq, + msm_otg_phy_irq_handler, IRQF_TRIGGER_RISING, + "msm_otg_phy_irq", motg); + if (ret < 0) { + dev_err(&pdev->dev, "phy_irq request fail %d\n", ret); + goto destroy_wq; + } + } + + ret = devm_request_irq(&pdev->dev, motg->async_irq, msm_otg_irq, + IRQF_TRIGGER_RISING, "msm_otg", motg); + if (ret) { + dev_err(&pdev->dev, "request irq failed (ASYNC INT)\n"); + goto destroy_wq; + } + disable_irq(motg->async_irq); + + phy->init = msm_otg_reset; + phy->set_power = msm_otg_set_power; + phy->set_suspend = msm_otg_set_suspend; + phy->dbg_event = msm_otg_dbg_log_event; + + phy->io_ops = &msm_otg_io_ops; + + phy->otg->usb_phy = &motg->phy; + phy->otg->set_host = msm_otg_set_host; + phy->otg->set_peripheral = msm_otg_set_peripheral; + if (pdata->dp_manual_pullup) + phy->flags |= ENABLE_DP_MANUAL_PULLUP; + + if (pdata->enable_sec_phy) + phy->flags |= ENABLE_SECONDARY_PHY; + + phy->vbus_nb.notifier_call = msm_otg_vbus_notifier; + phy->id_nb.notifier_call = msm_otg_id_notifier; + ret = usb_add_phy(&motg->phy, USB_PHY_TYPE_USB2); + if (ret) { + dev_err(&pdev->dev, "usb_add_phy failed\n"); + goto destroy_wq; + } + + ret = usb_phy_regulator_init(motg); + if (ret) { + dev_err(&pdev->dev, "usb_phy_regulator_init failed\n"); + goto remove_phy; + } + + if (motg->pdata->mode == USB_OTG && + motg->pdata->otg_control == OTG_PMIC_CONTROL && + !motg->phy_irq) { + + if (gpio_is_valid(motg->pdata->usb_id_gpio)) { + /* usb_id_gpio request */ + ret = devm_gpio_request(&pdev->dev, + motg->pdata->usb_id_gpio, + "USB_ID_GPIO"); + if (ret < 0) { + dev_err(&pdev->dev, "gpio req failed for id\n"); + goto phy_reg_deinit; + } + + /* + * The following code implements switch between the HOST + * mode to device mode when used different HW components + * on the same port: USB HUB and the usb jack type B + * for device mode In this case HUB should be gone + * only once out of reset at the boot time and after + * that always stay on + */ + if (gpio_is_valid(motg->pdata->hub_reset_gpio)) { + ret = devm_gpio_request(&pdev->dev, + motg->pdata->hub_reset_gpio, + "qcom,hub-reset-gpio"); + if (ret < 0) { + dev_err(&pdev->dev, "gpio req failed for hub reset\n"); + goto phy_reg_deinit; + } + gpio_direction_output( + motg->pdata->hub_reset_gpio, 1); + } + + if (gpio_is_valid(motg->pdata->switch_sel_gpio)) { + ret = devm_gpio_request(&pdev->dev, + motg->pdata->switch_sel_gpio, + "qcom,sw-sel-gpio"); + if (ret < 0) { + dev_err(&pdev->dev, "gpio req failed for switch sel\n"); + goto phy_reg_deinit; + } + if (gpio_get_value(motg->pdata->usb_id_gpio)) + gpio_direction_input( + motg->pdata->switch_sel_gpio); + + else + gpio_direction_output( + motg->pdata->switch_sel_gpio, + 1); + } + + /* usb_id_gpio to irq */ + id_irq = gpio_to_irq(motg->pdata->usb_id_gpio); + motg->ext_id_irq = id_irq; + } else if (motg->pdata->pmic_id_irq) { + id_irq = motg->pdata->pmic_id_irq; + } + + if (id_irq) { + ret = devm_request_irq(&pdev->dev, id_irq, + msm_id_irq, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "msm_otg", motg); + if (ret) { + dev_err(&pdev->dev, "request irq failed for ID\n"); + goto phy_reg_deinit; + } + } else { + /* PMIC does USB ID detection and notifies through + * USB_OTG property of USB powersupply. + */ + dev_dbg(&pdev->dev, "PMIC does ID detection\n"); + } + } + + platform_set_drvdata(pdev, motg); + device_init_wakeup(&pdev->dev, 1); + + ret = msm_otg_debugfs_init(motg); + if (ret) + dev_dbg(&pdev->dev, "mode debugfs file is not available\n"); + + if (motg->pdata->otg_control == OTG_PMIC_CONTROL && + (!(motg->pdata->mode == USB_OTG) || + motg->pdata->pmic_id_irq || motg->ext_id_irq || + !motg->phy_irq)) + motg->caps = ALLOW_PHY_POWER_COLLAPSE | ALLOW_PHY_RETENTION; + + if (motg->pdata->otg_control == OTG_PHY_CONTROL || motg->phy_irq || + motg->pdata->enable_phy_id_pullup) + motg->caps = ALLOW_PHY_RETENTION | ALLOW_PHY_REGULATORS_LPM; + + motg->caps |= ALLOW_HOST_PHY_RETENTION; + + device_create_file(&pdev->dev, &dev_attr_dpdm_pulldown_enable); + + if (motg->pdata->enable_lpm_on_dev_suspend) + motg->caps |= ALLOW_LPM_ON_DEV_SUSPEND; + + if (motg->pdata->disable_retention_with_vdd_min) + motg->caps |= ALLOW_VDD_MIN_WITH_RETENTION_DISABLED; + + /* + * PHY DVDD is supplied by a always on PMIC LDO (unlike + * vddcx/vddmx). PHY can keep D+ pull-up and D+/D- + * pull-down during suspend without any additional + * hardware re-work. + */ + if (motg->pdata->phy_type == SNPS_FEMTO_PHY) + motg->caps |= ALLOW_BUS_SUSPEND_WITHOUT_REWORK; + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + ret = dbg_create_files(pdev); + if (ret) { + pr_err("Registering sysfs files for debug failed!!!!\n"); + goto remove_cdev; + } +#endif + pm_stay_awake(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + if (motg->pdata->delay_lpm_on_disconnect) { + pm_runtime_set_autosuspend_delay(&pdev->dev, + lpm_disconnect_thresh); + pm_runtime_use_autosuspend(&pdev->dev); + } + + if (pdev->dev.of_node) { + ret = msm_otg_setup_devices(pdev, pdata->mode, true); + if (ret) { + dev_err(&pdev->dev, "devices setup failed\n"); + goto remove_cdev; + } + } + + if (gpio_is_valid(motg->pdata->hub_reset_gpio)) { + ret = devm_gpio_request(&pdev->dev, + motg->pdata->hub_reset_gpio, + "HUB_RESET"); + if (ret < 0) { + dev_err(&pdev->dev, "gpio req failed for hub_reset\n"); + } else { + gpio_direction_output( + motg->pdata->hub_reset_gpio, 0); + /* 5 microsecs reset signaling to usb hub */ + usleep_range(5, 10); + gpio_direction_output( + motg->pdata->hub_reset_gpio, 1); + } + } + + if (gpio_is_valid(motg->pdata->usbeth_reset_gpio)) { + ret = devm_gpio_request(&pdev->dev, + motg->pdata->usbeth_reset_gpio, + "ETH_RESET"); + if (ret < 0) { + dev_err(&pdev->dev, "gpio req failed for usbeth_reset\n"); + } else { + gpio_direction_output( + motg->pdata->usbeth_reset_gpio, 0); + /* 100 microsecs reset signaling to usb-to-eth */ + usleep_range(100, 110); + gpio_direction_output( + motg->pdata->usbeth_reset_gpio, 1); + } + } + + if (of_property_read_bool(pdev->dev.of_node, "extcon")) { + if (extcon_get_state(motg->phy.edev, EXTCON_USB_HOST)) { + msm_otg_id_notifier(&motg->phy.id_nb, + 1, motg->phy.edev); + } else if (extcon_get_state(motg->phy.edev, EXTCON_USB)) { + msm_otg_vbus_notifier(&motg->phy.vbus_nb, + 1, motg->phy.edev); + } + } + + motg->pm_notify.notifier_call = msm_otg_pm_notify; + register_pm_notifier(&motg->pm_notify); + msm_otg_dbg_log_event(phy, "OTG PROBE", motg->caps, motg->lpm_flags); + + return 0; + +remove_cdev: + pm_runtime_disable(&pdev->dev); + device_remove_file(&pdev->dev, &dev_attr_dpdm_pulldown_enable); + msm_otg_debugfs_cleanup(); +phy_reg_deinit: + devm_regulator_unregister(motg->phy.dev, motg->dpdm_rdev); +remove_phy: + usb_remove_phy(&motg->phy); +destroy_wq: + destroy_workqueue(motg->otg_wq); +disable_core_clk: + clk_disable_unprepare(motg->core_clk); + msm_hsusb_ldo_enable(motg, USB_PHY_REG_OFF); +free_ldo_init: + msm_hsusb_ldo_init(motg, 0); +free_hsusb_vdd: + regulator_disable(hsusb_vdd); +free_config_vddcx: + regulator_set_voltage(hsusb_vdd, + vdd_val[VDD_NONE], + vdd_val[VDD_MAX]); +devote_xo_handle: + clk_disable_unprepare(motg->pclk); + if (motg->xo_clk) + clk_disable_unprepare(motg->xo_clk); +free_xo_handle: + if (motg->xo_clk) { + clk_put(motg->xo_clk); + motg->xo_clk = NULL; + } +free_regs: + iounmap(motg->regs); +devote_bus_bw: + if (motg->bus_perf_client) { + msm_otg_bus_vote(motg, USB_NO_PERF_VOTE); + msm_bus_scale_unregister_client(motg->bus_perf_client); + } +disable_phy_csr_clk: + if (motg->phy_csr_clk) + clk_disable_unprepare(motg->phy_csr_clk); +disable_sleep_clk: + if (motg->sleep_clk) + clk_disable_unprepare(motg->sleep_clk); +put_xo_clk: + if (motg->xo_clk) + clk_put(motg->xo_clk); +put_pclk: + if (motg->pclk) + clk_put(motg->pclk); +put_core_clk: + if (motg->core_clk) + clk_put(motg->core_clk); +free_motg: + kfree(motg); + return ret; +} + +static int msm_otg_remove(struct platform_device *pdev) +{ + struct msm_otg *motg = platform_get_drvdata(pdev); + struct usb_phy *phy = &motg->phy; + int cnt = 0; + + if (phy->otg->host || phy->otg->gadget) + return -EBUSY; + + unregister_pm_notifier(&motg->pm_notify); + + if (pdev->dev.of_node) + msm_otg_setup_devices(pdev, motg->pdata->mode, false); + if (psy) + power_supply_put(psy); + msm_otg_debugfs_cleanup(); + cancel_delayed_work_sync(&motg->chg_work); + cancel_delayed_work_sync(&motg->sdp_check); + cancel_delayed_work_sync(&motg->id_status_work); + cancel_delayed_work_sync(&motg->perf_vote_work); + msm_otg_perf_vote_update(motg, false); + cancel_work_sync(&motg->sm_work); + cancel_work_sync(&motg->notify_charger_work); + destroy_workqueue(motg->otg_wq); + + pm_runtime_resume(&pdev->dev); + + device_init_wakeup(&pdev->dev, 0); + pm_runtime_disable(&pdev->dev); + + usb_remove_phy(phy); + + device_remove_file(&pdev->dev, &dev_attr_dpdm_pulldown_enable); + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + dbg_remove_files(pdev); +#endif + + /* + * Put PHY in low power mode. + */ + ulpi_read(phy, 0x14); + ulpi_write(phy, 0x08, 0x09); + + writel_relaxed(readl_relaxed(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); + while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { + if (readl_relaxed(USB_PORTSC) & PORTSC_PHCD) + break; + udelay(1); + cnt++; + } + if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) + dev_err(phy->dev, "Unable to suspend PHY\n"); + + clk_disable_unprepare(motg->pclk); + clk_disable_unprepare(motg->core_clk); + if (motg->phy_csr_clk) + clk_disable_unprepare(motg->phy_csr_clk); + if (motg->xo_clk) { + clk_disable_unprepare(motg->xo_clk); + clk_put(motg->xo_clk); + } + + if (!IS_ERR(motg->sleep_clk)) + clk_disable_unprepare(motg->sleep_clk); + + msm_hsusb_ldo_enable(motg, USB_PHY_REG_OFF); + msm_hsusb_ldo_init(motg, 0); + regulator_disable(hsusb_vdd); + regulator_set_voltage(hsusb_vdd, + vdd_val[VDD_NONE], + vdd_val[VDD_MAX]); + + iounmap(motg->regs); + pm_runtime_set_suspended(&pdev->dev); + + clk_put(motg->pclk); + clk_put(motg->core_clk); + + if (motg->bus_perf_client) { + msm_otg_bus_vote(motg, USB_NO_PERF_VOTE); + msm_bus_scale_unregister_client(motg->bus_perf_client); + } + + return 0; +} + +static void msm_otg_shutdown(struct platform_device *pdev) +{ + struct msm_otg *motg = platform_get_drvdata(pdev); + + dev_dbg(&pdev->dev, "OTG shutdown\n"); + msm_hsusb_vbus_power(motg, 0); +} + +#ifdef CONFIG_PM +static int msm_otg_runtime_idle(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + struct usb_phy *phy = &motg->phy; + + dev_dbg(dev, "OTG runtime idle\n"); + msm_otg_dbg_log_event(phy, "RUNTIME IDLE", phy->otg->state, 0); + + if (phy->otg->state == OTG_STATE_UNDEFINED) + return -EAGAIN; + + return 0; +} + +static int msm_otg_runtime_suspend(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + + dev_dbg(dev, "OTG runtime suspend\n"); + msm_otg_dbg_log_event(&motg->phy, "RUNTIME SUSPEND", + get_pm_runtime_counter(dev), 0); + return msm_otg_suspend(motg); +} + +static int msm_otg_runtime_resume(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + + dev_dbg(dev, "OTG runtime resume\n"); + msm_otg_dbg_log_event(&motg->phy, "RUNTIME RESUME", + get_pm_runtime_counter(dev), 0); + + return msm_otg_resume(motg); +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int msm_otg_pm_suspend(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + + dev_dbg(dev, "OTG PM suspend\n"); + msm_otg_dbg_log_event(&motg->phy, "PM SUSPEND START", + get_pm_runtime_counter(dev), + atomic_read(&motg->pm_suspended)); + + /* flush any pending sm_work first */ + flush_work(&motg->sm_work); + if (!atomic_read(&motg->in_lpm)) { + dev_err(dev, "Abort PM suspend!! (USB is outside LPM)\n"); + return -EBUSY; + } + atomic_set(&motg->pm_suspended, 1); + + return 0; +} + +static int msm_otg_pm_resume(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + + dev_dbg(dev, "OTG PM resume\n"); + msm_otg_dbg_log_event(&motg->phy, "PM RESUME START", + get_pm_runtime_counter(dev), pm_runtime_suspended(dev)); + + if (motg->resume_pending || motg->phy_irq_pending) { + msm_otg_dbg_log_event(&motg->phy, "PM RESUME BY USB", + motg->async_int, motg->resume_pending); + /* sm work if pending will start in pm notify to exit LPM */ + } + + return 0; +} +#endif + +#ifdef CONFIG_PM +static const struct dev_pm_ops msm_otg_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(msm_otg_pm_suspend, msm_otg_pm_resume) + SET_RUNTIME_PM_OPS(msm_otg_runtime_suspend, msm_otg_runtime_resume, + msm_otg_runtime_idle) +}; +#endif + +static const struct of_device_id msm_otg_dt_match[] = { + { .compatible = "qcom,hsusb-otg", }, + {} +}; + +static struct platform_driver msm_otg_driver = { + .probe = msm_otg_probe, + .remove = msm_otg_remove, + .shutdown = msm_otg_shutdown, + .driver = { + .name = DRIVER_NAME, +#ifdef CONFIG_PM + .pm = &msm_otg_dev_pm_ops, +#endif + .of_match_table = msm_otg_dt_match, + }, +}; + +module_platform_driver(msm_otg_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM USB transceiver driver"); diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 353cbb53ed33..80950e3616ae 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -60,4 +60,3 @@ obj-$(CONFIG_BACKLIGHT_TPS65217) += tps65217_bl.o obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o obj-$(CONFIG_BACKLIGHT_ARCXCNN) += arcxcnn_bl.o obj-$(CONFIG_BACKLIGHT_RAVE_SP) += rave-sp-backlight.o -obj-y += sgm37603a.o diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c index 13517553e5ff..2792ae6f658d 100644 --- a/drivers/video/backlight/backlight.c +++ b/drivers/video/backlight/backlight.c @@ -2,7 +2,6 @@ * Backlight Lowlevel Control Abstraction * * Copyright (C) 2003,2004 Hewlett-Packard Company - * Copyright (C) 2020 XiaoMi, Inc. * */ @@ -181,7 +180,7 @@ int backlight_device_set_brightness(struct backlight_device *bd, if (brightness > bd->props.max_brightness) rc = -EINVAL; else { - pr_info("set brightness to %lu\n", brightness); + pr_debug("set brightness to %lu\n", brightness); bd->props.brightness = brightness; rc = backlight_update_status(bd); } @@ -336,9 +335,8 @@ static int bd_cdev_get_cur_brightness(struct thermal_cooling_device *cdev, { struct backlight_device *bd = (struct backlight_device *)cdev->devdata; - *state = bd->thermal_brightness_limit > 0 ? - bd->thermal_brightness_limit: - bd->props.max_brightness; + *state = bd->props.max_brightness - bd->thermal_brightness_limit; + return 0; } @@ -351,12 +349,11 @@ static int bd_cdev_set_cur_brightness(struct thermal_cooling_device *cdev, if (state > bd->props.max_brightness) return -EINVAL; - brightness_lvl = state; + brightness_lvl = bd->props.max_brightness - state; if (brightness_lvl == bd->thermal_brightness_limit) return 0; - bd->thermal_brightness_limit = (state == 0) ? - bd->props.max_brightness : state; + bd->thermal_brightness_limit = brightness_lvl; brightness_lvl = (bd->usr_brightness_req <= bd->thermal_brightness_limit) ? bd->usr_brightness_req : diff --git a/drivers/video/backlight/qcom-spmi-wled.c b/drivers/video/backlight/qcom-spmi-wled.c index 6f383d37a10c..43134e2518e4 100644 --- a/drivers/video/backlight/qcom-spmi-wled.c +++ b/drivers/video/backlight/qcom-spmi-wled.c @@ -3,7 +3,7 @@ * Copyright (c) 2015, Sony Mobile Communications, AB. */ /* - * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2018-2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "WLED: %s: " fmt, __func__ @@ -85,6 +85,7 @@ #define WLED_SINK_REG_STR_MOD_EN BIT(7) #define WLED_SINK_SYNC_DLY_REG(n) (0x51 + (n * 0x10)) +#define WLED_SINK_SYNC_DLY_MASK GENMASK(2, 0) #define WLED_SINK_FS_CURR_REG(n) (0x52 + (n * 0x10)) #define WLED_SINK_FS_MASK GENMASK(3, 0) @@ -203,6 +204,7 @@ struct wled_config { int string_cfg; int mod_sel; int cabc_sel; + int sync_dly; bool en_cabc; bool ext_pfet_sc_pro_en; bool auto_calib_enabled; @@ -359,15 +361,13 @@ static int wled_sync_toggle(struct wled *wled) rc = regmap_update_bits(wled->regmap, wled->sink_addr + WLED_SINK_SYNC, - WLED_SINK_SYNC_MASK, WLED_SINK_SYNC_MASK); + WLED_SINK_SYNC_MASK, WLED_SINK_SYNC_CLEAR); if (rc < 0) return rc; - rc = regmap_update_bits(wled->regmap, + return regmap_update_bits(wled->regmap, wled->sink_addr + WLED_SINK_SYNC, - WLED_SINK_SYNC_MASK, WLED_SINK_SYNC_CLEAR); - - return rc; + WLED_SINK_SYNC_MASK, WLED_SINK_SYNC_MASK); } static int wled5_sample_hold_control(struct wled *wled, u16 brightness, @@ -441,20 +441,19 @@ static int wled5_set_brightness(struct wled *wled, u16 brightness) if (rc < 0) return rc; - /* Update brightness values to modulator in WLED5 */ - val = (wled->cfg.mod_sel == MOD_A) ? WLED5_SINK_SYNC_MODA_BIT : - WLED5_SINK_SYNC_MODB_BIT; - rc = regmap_update_bits(wled->regmap, - wled->sink_addr + WLED5_SINK_MOD_SYNC_BIT_REG, - WLED5_SINK_SYNC_MASK, val); - if (rc < 0) - return rc; - val = 0; rc = regmap_update_bits(wled->regmap, wled->sink_addr + WLED5_SINK_MOD_SYNC_BIT_REG, WLED_SINK_SYNC_MASK, val); - return rc; + /* Update brightness values to modulator in WLED5 */ + if (rc < 0) + return rc; + + val = (wled->cfg.mod_sel == MOD_A) ? WLED5_SINK_SYNC_MODA_BIT : + WLED5_SINK_SYNC_MODB_BIT; + return regmap_update_bits(wled->regmap, + wled->sink_addr + WLED5_SINK_MOD_SYNC_BIT_REG, + WLED5_SINK_SYNC_MASK, val); } static int wled4_set_brightness(struct wled *wled, u16 brightness) @@ -1268,6 +1267,14 @@ static int wled4_setup(struct wled *wled) if (rc < 0) return rc; + addr = wled->sink_addr + + WLED_SINK_SYNC_DLY_REG(i); + rc = regmap_update_bits(wled->regmap, addr, + WLED_SINK_SYNC_DLY_MASK, + wled->cfg.sync_dly); + if (rc < 0) + return rc; + temp = i + WLED_SINK_CURR_SINK_SHFT; sink_en |= 1 << temp; } @@ -1352,6 +1359,7 @@ static const struct wled_config wled4_config_defaults = { .fs_current = 10, .ovp = 1, .switch_freq = 11, + .sync_dly = 2, .string_cfg = 0xf, .mod_sel = -EINVAL, .cabc_sel = -EINVAL, @@ -1417,6 +1425,15 @@ static const struct wled_var_cfg wled4_ovp_cfg = { .size = ARRAY_SIZE(wled4_ovp_values), }; +static const u32 wled4_sync_dly_values[] = { + 0, 200, 400, 600, 800, 1000, 1200, 1400, +}; + +static const struct wled_var_cfg wled4_sync_dly_cfg = { + .values = wled4_sync_dly_values, + .size = ARRAY_SIZE(wled4_sync_dly_values), +}; + static inline u32 wled5_ovp_values_fn(u32 idx) { /* @@ -2134,6 +2151,11 @@ static int wled_configure(struct wled *wled, struct device *dev) .val_ptr = &cfg->string_cfg, .cfg = &wled_string_cfg, }, + { + .name = "qcom,sync-dly", + .val_ptr = &cfg->sync_dly, + .cfg = &wled4_sync_dly_cfg, + }, }; const struct wled_u32_opts wled5_opts[] = { diff --git a/drivers/video/fbdev/msm/Makefile b/drivers/video/fbdev/msm/Makefile index 4f9cff8989fa..309c6d2fe2f9 100644 --- a/drivers/video/fbdev/msm/Makefile +++ b/drivers/video/fbdev/msm/Makefile @@ -39,6 +39,7 @@ mdss-dsi-objs += mdss_dsi_panel.o mdss-dsi-objs += msm_mdss_io_8974.o mdss-dsi-objs += mdss_dsi_phy.o mdss-dsi-objs += mdss_dsi_phy_v3.o +mdss-dsi-objs += mdss_dsi_phy_12nm.o mdss-dsi-objs += mdss_dsi_clk.o obj-$(CONFIG_FB_MSM_MDSS) += mdss-dsi.o obj-$(CONFIG_FB_MSM_MDSS) += mdss_panel.o diff --git a/drivers/video/fbdev/msm/mdss_dsi.c b/drivers/video/fbdev/msm/mdss_dsi.c index ffe8b12bc402..b5352f8ea786 100644 --- a/drivers/video/fbdev/msm/mdss_dsi.c +++ b/drivers/video/fbdev/msm/mdss_dsi.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. */ #include #include @@ -3317,10 +3317,10 @@ static int mdss_dsi_ctrl_clock_init(struct platform_device *ctrl_pdev, info.core_clks.mmss_misc_ahb_clk = ctrl_pdata->shared_data->mmss_misc_ahb_clk; - info.link_clks.esc_clk = ctrl_pdata->esc_clk; - info.link_clks.byte_clk = ctrl_pdata->byte_clk; - info.link_clks.pixel_clk = ctrl_pdata->pixel_clk; - info.link_clks.byte_intf_clk = ctrl_pdata->byte_intf_clk; + info.link_lp_clks.esc_clk = ctrl_pdata->esc_clk; + info.link_hs_clks.byte_clk = ctrl_pdata->byte_clk; + info.link_hs_clks.pixel_clk = ctrl_pdata->pixel_clk; + info.link_hs_clks.byte_intf_clk = ctrl_pdata->byte_intf_clk; info.pre_clkoff_cb = mdss_dsi_pre_clkoff_cb; info.post_clkon_cb = mdss_dsi_post_clkon_cb; @@ -4394,11 +4394,11 @@ static int mdss_dsi_parse_ctrl_params(struct platform_device *ctrl_pdev, if (!data) { pr_err("%s:%d, Unable to read Phy Strength ctrl settings\n", __func__, __LINE__); - return -EINVAL; + } else { + pinfo->mipi.dsi_phy_db.strength_len = len; + for (i = 0; i < len; i++) + pinfo->mipi.dsi_phy_db.strength[i] = data[i]; } - pinfo->mipi.dsi_phy_db.strength_len = len; - for (i = 0; i < len; i++) - pinfo->mipi.dsi_phy_db.strength[i] = data[i]; pinfo->mipi.dsi_phy_db.reg_ldo_mode = of_property_read_bool( ctrl_pdev->dev.of_node, "qcom,regulator-ldo-mode"); @@ -4429,11 +4429,11 @@ static int mdss_dsi_parse_ctrl_params(struct platform_device *ctrl_pdev, if (!data) { pr_err("%s:%d, Unable to read Phy lane configure settings\n", __func__, __LINE__); - return -EINVAL; + } else { + pinfo->mipi.dsi_phy_db.lanecfg_len = len; + for (i = 0; i < len; i++) + pinfo->mipi.dsi_phy_db.lanecfg[i] = data[i]; } - pinfo->mipi.dsi_phy_db.lanecfg_len = len; - for (i = 0; i < len; i++) - pinfo->mipi.dsi_phy_db.lanecfg[i] = data[i]; ctrl_pdata->timing_db_mode = of_property_read_bool( ctrl_pdev->dev.of_node, "qcom,timing-db-mode"); diff --git a/drivers/video/fbdev/msm/mdss_dsi.h b/drivers/video/fbdev/msm/mdss_dsi.h index 2c77c16b9818..7b462ab40bb2 100644 --- a/drivers/video/fbdev/msm/mdss_dsi.h +++ b/drivers/video/fbdev/msm/mdss_dsi.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. */ #ifndef MDSS_DSI_H #define MDSS_DSI_H @@ -350,6 +350,7 @@ struct dsi_panel_timing { struct mdss_panel_timing timing; uint32_t phy_timing[12]; uint32_t phy_timing_8996[40]; + uint32_t phy_timing_12nm[8]; /* DSI_CLKOUT_TIMING_CTRL */ char t_clk_post; char t_clk_pre; @@ -646,15 +647,19 @@ void mdss_dsi_shadow_clk_deinit(struct device *dev, struct mdss_dsi_ctrl_pdata *ctrl_pdata); int mdss_dsi_pre_clkoff_cb(void *priv, enum mdss_dsi_clk_type clk_type, + enum mdss_dsi_lclk_type l_type, enum mdss_dsi_clk_state new_state); int mdss_dsi_post_clkoff_cb(void *priv, enum mdss_dsi_clk_type clk_type, + enum mdss_dsi_lclk_type l_type, enum mdss_dsi_clk_state curr_state); int mdss_dsi_post_clkon_cb(void *priv, enum mdss_dsi_clk_type clk_type, + enum mdss_dsi_lclk_type l_type, enum mdss_dsi_clk_state curr_state); int mdss_dsi_pre_clkon_cb(void *priv, enum mdss_dsi_clk_type clk_type, + enum mdss_dsi_lclk_type l_type, enum mdss_dsi_clk_state new_state); int mdss_dsi_panel_reset(struct mdss_panel_data *pdata, int enable); void mdss_dsi_phy_disable(struct mdss_dsi_ctrl_pdata *ctrl); @@ -709,6 +714,7 @@ void mdss_dsi_set_reg(struct mdss_dsi_ctrl_pdata *ctrl, int off, u32 mask, u32 val); int mdss_dsi_phy_pll_reset_status(struct mdss_dsi_ctrl_pdata *ctrl); int mdss_dsi_check_panel_status(struct mdss_dsi_ctrl_pdata *ctrl, void *arg); +void mdss_dsi_ctrl_phy_reset(struct mdss_dsi_ctrl_pdata *ctrl); void mdss_dsi_debug_bus_init(struct mdss_dsi_data *sdata); int mdss_dsi_get_dt_vreg_data(struct device *dev, diff --git a/drivers/video/fbdev/msm/mdss_dsi_clk.c b/drivers/video/fbdev/msm/mdss_dsi_clk.c index 2d10c25bb215..60f9a072746e 100644 --- a/drivers/video/fbdev/msm/mdss_dsi_clk.c +++ b/drivers/video/fbdev/msm/mdss_dsi_clk.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2015-2016, 2018-2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2015-2016, 2018-2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "mdss-dsi-clk:[%s] " fmt, __func__ #include @@ -17,11 +17,9 @@ struct dsi_core_clks { }; struct dsi_link_clks { - struct mdss_dsi_link_clk_info clks; + struct mdss_dsi_link_hs_clk_info hs_clks; + struct mdss_dsi_link_lp_clk_info lp_clks; u32 current_clk_state; - u32 byte_clk_rate; - u32 pix_clk_rate; - u32 esc_clk_rate; }; struct mdss_dsi_clk_mngr { @@ -63,8 +61,7 @@ static int dsi_core_clk_start(struct dsi_core_clks *c_clks) rc = clk_prepare_enable(c_clks->clks.mdp_core_clk); if (rc) { - pr_err("%s: failed to enable mdp_core_clock. rc=%d\n", - __func__, rc); + pr_err("failed to enable mdp_core_clock. rc=%d\n", rc); goto error; } @@ -84,15 +81,15 @@ static int dsi_core_clk_start(struct dsi_core_clks *c_clks) rc = clk_prepare_enable(c_clks->clks.axi_clk); if (rc) { - pr_err("%s: failed to enable ahb clock. rc=%d\n", __func__, rc); + pr_err("failed to enable ahb clock. rc=%d\n", rc); goto disable_ahb_clk; } if (c_clks->clks.mmss_misc_ahb_clk) { rc = clk_prepare_enable(c_clks->clks.mmss_misc_ahb_clk); if (rc) { - pr_err("%s: failed to enable mmss misc ahb clk.rc=%d\n", - __func__, rc); + pr_err("failed to enable mmss misc ahb clk.rc=%d\n", + rc); goto disable_axi_clk; } } @@ -142,12 +139,15 @@ static int dsi_core_clk_stop(struct dsi_core_clks *c_clks) return 0; } -static int dsi_link_clk_set_rate(struct dsi_link_clks *l_clks) +static int dsi_link_hs_clk_set_rate( + struct mdss_dsi_link_hs_clk_info *link_hs_clks) { int rc = 0; struct mdss_dsi_clk_mngr *mngr; + struct dsi_link_clks *l_clks; struct mdss_dsi_ctrl_pdata *ctrl; + l_clks = container_of(link_hs_clks, struct dsi_link_clks, hs_clks); mngr = container_of(l_clks, struct mdss_dsi_clk_mngr, link_clks); /* @@ -162,19 +162,13 @@ static int dsi_link_clk_set_rate(struct dsi_link_clks *l_clks) if (ctrl->panel_data.panel_info.cont_splash_enabled) return 0; - rc = clk_set_rate(l_clks->clks.esc_clk, l_clks->esc_clk_rate); - if (rc) { - pr_err("clk_set_rate failed for esc_clk rc = %d\n", rc); - goto error; - } - - rc = clk_set_rate(l_clks->clks.byte_clk, l_clks->byte_clk_rate); + rc = clk_set_rate(link_hs_clks->byte_clk, link_hs_clks->byte_clk_rate); if (rc) { pr_err("clk_set_rate failed for byte_clk rc = %d\n", rc); goto error; } - rc = clk_set_rate(l_clks->clks.pixel_clk, l_clks->pix_clk_rate); + rc = clk_set_rate(link_hs_clks->pixel_clk, link_hs_clks->pix_clk_rate); if (rc) { pr_err("clk_set_rate failed for pixel_clk rc = %d\n", rc); goto error; @@ -186,9 +180,9 @@ static int dsi_link_clk_set_rate(struct dsi_link_clks *l_clks) * byte_intf_clk_rate = byte_clk_rate / 2 * todo: this needs to be revisited when support for CPHY is added */ - if (l_clks->clks.byte_intf_clk) { - rc = clk_set_rate(l_clks->clks.byte_intf_clk, - l_clks->byte_clk_rate / 2); + if (link_hs_clks->byte_intf_clk) { + rc = clk_set_rate(link_hs_clks->byte_intf_clk, + link_hs_clks->byte_clk_rate / 2); if (rc) { pr_err("set rate failed for byte intf clk rc=%d\n", rc); goto error; @@ -199,30 +193,25 @@ static int dsi_link_clk_set_rate(struct dsi_link_clks *l_clks) return rc; } -static int dsi_link_clk_prepare(struct dsi_link_clks *l_clks) +static int dsi_link_hs_clk_prepare( + struct mdss_dsi_link_hs_clk_info *link_hs_clks) { int rc = 0; - rc = clk_prepare(l_clks->clks.esc_clk); + rc = clk_prepare(link_hs_clks->byte_clk); if (rc) { - pr_err("%s: Failed to prepare dsi esc clk\n", __func__); - goto esc_clk_err; - } - - rc = clk_prepare(l_clks->clks.byte_clk); - if (rc) { - pr_err("%s: Failed to prepare dsi byte clk\n", __func__); + pr_err("Failed to prepare dsi byte clk\n"); goto byte_clk_err; } - rc = clk_prepare(l_clks->clks.pixel_clk); + rc = clk_prepare(link_hs_clks->pixel_clk); if (rc) { - pr_err("%s: Failed to prepare dsi pixel clk\n", __func__); + pr_err("Failed to prepare dsi pixel_clk\n"); goto pixel_clk_err; } - if (l_clks->clks.byte_intf_clk) { - rc = clk_prepare(l_clks->clks.byte_intf_clk); + if (link_hs_clks->byte_intf_clk) { + rc = clk_prepare(link_hs_clks->byte_intf_clk); if (rc) { pr_err("%s: Failed to prepare dsi byte_intf clk\n", __func__); @@ -233,50 +222,43 @@ static int dsi_link_clk_prepare(struct dsi_link_clks *l_clks) return rc; byte_intf_clk_err: - clk_unprepare(l_clks->clks.pixel_clk); + clk_unprepare(link_hs_clks->pixel_clk); pixel_clk_err: - clk_unprepare(l_clks->clks.byte_clk); + clk_unprepare(link_hs_clks->byte_clk); byte_clk_err: - clk_unprepare(l_clks->clks.esc_clk); -esc_clk_err: return rc; } -static int dsi_link_clk_unprepare(struct dsi_link_clks *l_clks) +static int dsi_link_hs_clk_unprepare( + struct mdss_dsi_link_hs_clk_info *link_hs_clks) { - if (l_clks->clks.byte_intf_clk) - clk_unprepare(l_clks->clks.byte_intf_clk); - clk_unprepare(l_clks->clks.pixel_clk); - clk_unprepare(l_clks->clks.byte_clk); - clk_unprepare(l_clks->clks.esc_clk); + if (link_hs_clks->byte_intf_clk) + clk_unprepare(link_hs_clks->byte_intf_clk); + clk_unprepare(link_hs_clks->pixel_clk); + clk_unprepare(link_hs_clks->byte_clk); return 0; } -static int dsi_link_clk_enable(struct dsi_link_clks *l_clks) +static int dsi_link_hs_clk_enable( + struct mdss_dsi_link_hs_clk_info *link_hs_clks) { int rc = 0; - rc = clk_enable(l_clks->clks.esc_clk); + rc = clk_enable(link_hs_clks->byte_clk); if (rc) { - pr_err("%s: Failed to enable dsi esc clk\n", __func__); - goto esc_clk_err; - } - - rc = clk_enable(l_clks->clks.byte_clk); - if (rc) { - pr_err("%s: Failed to enable dsi byte clk\n", __func__); + pr_err("Failed to enable dsi byte clk\n"); goto byte_clk_err; } - rc = clk_enable(l_clks->clks.pixel_clk); + rc = clk_enable(link_hs_clks->pixel_clk); if (rc) { - pr_err("%s: Failed to enable dsi pixel clk\n", __func__); + pr_err("Failed to enable dsi pixel_clk\n"); goto pixel_clk_err; } - if (l_clks->clks.byte_intf_clk) { - rc = clk_enable(l_clks->clks.byte_intf_clk); + if (link_hs_clks->byte_intf_clk) { + rc = clk_enable(link_hs_clks->byte_intf_clk); if (rc) { pr_err("%s: Failed to enable dsi byte_intf clk\n", __func__); @@ -287,74 +269,148 @@ static int dsi_link_clk_enable(struct dsi_link_clks *l_clks) return rc; byte_intf_clk_err: - clk_disable(l_clks->clks.pixel_clk); + clk_disable(link_hs_clks->pixel_clk); pixel_clk_err: - clk_disable(l_clks->clks.byte_clk); + clk_disable(link_hs_clks->byte_clk); byte_clk_err: - clk_disable(l_clks->clks.esc_clk); -esc_clk_err: return rc; } -static int dsi_link_clk_disable(struct dsi_link_clks *l_clks) +static int dsi_link_hs_clk_disable( + struct mdss_dsi_link_hs_clk_info *link_hs_clks) { - if (l_clks->clks.byte_intf_clk) - clk_disable(l_clks->clks.byte_intf_clk); - clk_disable(l_clks->clks.esc_clk); - clk_disable(l_clks->clks.pixel_clk); - clk_disable(l_clks->clks.byte_clk); + if (link_hs_clks->byte_intf_clk) + clk_disable(link_hs_clks->byte_intf_clk); + clk_disable(link_hs_clks->pixel_clk); + clk_disable(link_hs_clks->byte_clk); return 0; } -static int dsi_link_clk_start(struct dsi_link_clks *l_clks) +static int dsi_link_hs_clk_start( + struct mdss_dsi_link_hs_clk_info *link_hs_clks, + enum mdss_dsi_link_clk_op_type op_type) { int rc = 0; + struct dsi_link_clks *l_clks; struct mdss_dsi_clk_mngr *mngr; + l_clks = container_of(link_hs_clks, struct dsi_link_clks, hs_clks); mngr = container_of(l_clks, struct mdss_dsi_clk_mngr, link_clks); - rc = dsi_link_clk_set_rate(l_clks); - if (rc) { - pr_err("failed to set clk rates, rc = %d\n", rc); - goto error; + if (op_type & MDSS_DSI_LINK_CLK_SET_RATE) { + rc = dsi_link_hs_clk_set_rate(link_hs_clks); + if (rc) { + pr_err("failed to set HS clk rates, rc = %d\n", rc); + goto error; + } } - rc = dsi_link_clk_prepare(l_clks); - if (rc) { - pr_err("failed to prepare link clks, rc = %d\n", rc); - goto error; + if (op_type & MDSS_DSI_LINK_CLK_PREPARE) { + rc = dsi_link_hs_clk_prepare(link_hs_clks); + if (rc) { + pr_err("failed to prepare link HS clks, rc = %d\n", rc); + goto error; + } } - rc = dsi_link_clk_enable(l_clks); - if (rc) { - pr_err("failed to enable link clks, rc = %d\n", rc); - goto error_unprepare; + if (op_type & MDSS_DSI_LINK_CLK_ENABLE) { + rc = dsi_link_hs_clk_enable(link_hs_clks); + if (rc) { + pr_err("failed to enable link HS clks, rc = %d\n", rc); + goto error_unprepare; + } } - pr_debug("%s: LINK CLOCK IS ON\n", mngr->name); + pr_debug("%s: LINK HS CLOCK IS ON\n", mngr->name); return rc; error_unprepare: - dsi_link_clk_unprepare(l_clks); + dsi_link_hs_clk_unprepare(link_hs_clks); error: return rc; } -static int dsi_link_clk_stop(struct dsi_link_clks *l_clks) +static int dsi_link_lp_clk_start( + struct mdss_dsi_link_lp_clk_info *link_lp_clks) { + int rc = 0; + struct mdss_dsi_clk_mngr *mngr; + struct dsi_link_clks *l_clks; + struct mdss_dsi_ctrl_pdata *ctrl; + + l_clks = container_of(link_lp_clks, struct dsi_link_clks, lp_clks); + mngr = container_of(l_clks, struct mdss_dsi_clk_mngr, link_clks); + /* + * In an ideal world, cont_splash_enabled should not be required inside + * the clock manager. But, in the current driver cont_splash_enabled + * flag is set inside mdp driver and there is no interface event + * associated with this flag setting. Also, set rate for clock need not + * be called for every enable call. It should be done only once when + * coming out of suspend. + */ + ctrl = mngr->priv_data; + if (ctrl->panel_data.panel_info.cont_splash_enabled) + goto prepare; + + rc = clk_set_rate(link_lp_clks->esc_clk, link_lp_clks->esc_clk_rate); + if (rc) { + pr_err("clk_set_rate failed for esc_clk rc = %d\n", rc); + goto error; + } + +prepare: + rc = clk_prepare(link_lp_clks->esc_clk); + if (rc) { + pr_err("Failed to prepare dsi esc clk\n"); + goto error; + } + + rc = clk_enable(link_lp_clks->esc_clk); + if (rc) { + pr_err("Failed to enable dsi esc clk\n"); + clk_unprepare(l_clks->lp_clks.esc_clk); + goto error; + } +error: + pr_debug("%s: LINK LP CLOCK IS ON\n", mngr->name); + return rc; +} + +static int dsi_link_hs_clk_stop( + struct mdss_dsi_link_hs_clk_info *link_hs_clks) +{ + struct dsi_link_clks *l_clks; struct mdss_dsi_clk_mngr *mngr; + l_clks = container_of(link_hs_clks, struct dsi_link_clks, hs_clks); mngr = container_of(l_clks, struct mdss_dsi_clk_mngr, link_clks); - (void)dsi_link_clk_disable(l_clks); + (void)dsi_link_hs_clk_disable(link_hs_clks); - (void)dsi_link_clk_unprepare(l_clks); - pr_debug("%s: LINK CLOCK IS OFF\n", mngr->name); + (void)dsi_link_hs_clk_unprepare(link_hs_clks); + pr_debug("%s: LINK HS CLOCK IS OFF\n", mngr->name); return 0; } +static int dsi_link_lp_clk_stop( + struct mdss_dsi_link_lp_clk_info *link_lp_clks) +{ + struct dsi_link_clks *l_clks; + struct mdss_dsi_clk_mngr *mngr; + + l_clks = container_of(link_lp_clks, struct dsi_link_clks, lp_clks); + mngr = container_of(l_clks, struct mdss_dsi_clk_mngr, link_clks); + + clk_disable(l_clks->lp_clks.esc_clk); + clk_unprepare(l_clks->lp_clks.esc_clk); + + pr_debug("%s: LINK LP CLOCK IS OFF\n", mngr->name); + return 0; +} + + static int dsi_update_clk_state(struct dsi_core_clks *c_clks, u32 c_state, struct dsi_link_clks *l_clks, u32 l_state) { @@ -385,8 +441,8 @@ static int dsi_update_clk_state(struct dsi_core_clks *c_clks, u32 c_state, if (c_clks && (c_state == MDSS_DSI_CLK_ON)) { if (c_clks->current_clk_state == MDSS_DSI_CLK_OFF) { rc = mngr->pre_clkon_cb(mngr->priv_data, - MDSS_DSI_CORE_CLK, - MDSS_DSI_CLK_ON); + MDSS_DSI_CORE_CLK, MDSS_DSI_LINK_NONE, + MDSS_DSI_CLK_ON); if (rc) { pr_err("failed to turn on MDP FS rc= %d\n", rc); goto error; @@ -400,8 +456,8 @@ static int dsi_update_clk_state(struct dsi_core_clks *c_clks, u32 c_state, if (mngr->post_clkon_cb) { rc = mngr->post_clkon_cb(mngr->priv_data, - MDSS_DSI_CORE_CLK, - MDSS_DSI_CLK_ON); + MDSS_DSI_CORE_CLK, MDSS_DSI_LINK_NONE, + MDSS_DSI_CLK_ON); if (rc) pr_err("post clk on cb failed, rc = %d\n", rc); } @@ -413,21 +469,50 @@ static int dsi_update_clk_state(struct dsi_core_clks *c_clks, u32 c_state, if (l_state == MDSS_DSI_CLK_ON) { if (mngr->pre_clkon_cb) { rc = mngr->pre_clkon_cb(mngr->priv_data, - MDSS_DSI_LINK_CLK, l_state); + MDSS_DSI_LINK_CLK, MDSS_DSI_LINK_LP_CLK, + l_state); if (rc) - pr_err("pre link clk on cb failed\n"); + pr_err("pre link LP clk on cb failed\n"); } - rc = dsi_link_clk_start(l_clks); + rc = dsi_link_lp_clk_start(&l_clks->lp_clks); if (rc) { - pr_err("failed to start link clk rc= %d\n", rc); + pr_err("failed to start LP link clk clk\n"); goto error; } if (mngr->post_clkon_cb) { rc = mngr->post_clkon_cb(mngr->priv_data, - MDSS_DSI_LINK_CLK, - l_state); + MDSS_DSI_LINK_CLK, MDSS_DSI_LINK_LP_CLK, + l_state); if (rc) - pr_err("post link clk on cb failed\n"); + pr_err("post LP clk on cb failed\n"); + } + + if (mngr->pre_clkon_cb) { + rc = mngr->pre_clkon_cb(mngr->priv_data, + MDSS_DSI_LINK_CLK, MDSS_DSI_LINK_HS_CLK, + l_state); + if (rc) + pr_err("pre HS clk on cb failed\n"); + } + rc = dsi_link_hs_clk_start(&l_clks->hs_clks, + (MDSS_DSI_LINK_CLK_SET_RATE | + MDSS_DSI_LINK_CLK_PREPARE)); + if (rc) { + pr_err("failed to prepare HS clk rc= %d\n", rc); + goto error; + } + if (mngr->post_clkon_cb) { + rc = mngr->post_clkon_cb(mngr->priv_data, + MDSS_DSI_LINK_CLK, MDSS_DSI_LINK_HS_CLK, + l_state); + if (rc) + pr_err("post HS clk on cb failed\n"); + } + rc = dsi_link_hs_clk_start(&l_clks->hs_clks, + MDSS_DSI_LINK_CLK_ENABLE); + if (rc) { + pr_err("failed to enable HS clk rc= %d\n", rc); + goto error; } } else { /* @@ -456,9 +541,16 @@ static int dsi_update_clk_state(struct dsi_core_clks *c_clks, u32 c_state, goto error; } - rc = dsi_link_clk_start(l_clks); + rc = dsi_link_lp_clk_start(&l_clks->lp_clks); if (rc) { - pr_err("Link clks did not start\n"); + pr_err("LP Link clks did not start\n"); + goto error; + } + + rc = dsi_link_hs_clk_start(&l_clks->hs_clks, + MDSS_DSI_LINK_CLK_START); + if (rc) { + pr_err("HS Link clks did not start\n"); goto error; } l_c_on = true; @@ -467,24 +559,50 @@ static int dsi_update_clk_state(struct dsi_core_clks *c_clks, u32 c_state, if (mngr->pre_clkoff_cb) { rc = mngr->pre_clkoff_cb(mngr->priv_data, - MDSS_DSI_LINK_CLK, l_state); + MDSS_DSI_LINK_CLK, MDSS_DSI_LINK_HS_CLK, + l_state); if (rc) - pr_err("pre link clk off cb failed\n"); + pr_err("pre HS clk off cb failed\n"); } - rc = dsi_link_clk_stop(l_clks); + rc = dsi_link_hs_clk_stop(&l_clks->hs_clks); if (rc) { - pr_err("failed to stop link clk, rc = %d\n", + pr_err("failed to stop HS clk, rc = %d\n", rc); goto error; } if (mngr->post_clkoff_cb) { rc = mngr->post_clkoff_cb(mngr->priv_data, - MDSS_DSI_LINK_CLK, l_state); + MDSS_DSI_LINK_CLK, MDSS_DSI_LINK_HS_CLK, + l_state); if (rc) - pr_err("post link clk off cb failed\n"); + pr_err("post HS clk off cb failed\n"); } + + if (mngr->pre_clkoff_cb) { + rc = mngr->pre_clkoff_cb(mngr->priv_data, + MDSS_DSI_LINK_CLK, MDSS_DSI_LINK_LP_CLK, + l_state); + if (rc) + pr_err("pre LP clk off cb failed\n"); + } + + rc = dsi_link_lp_clk_stop(&l_clks->lp_clks); + if (rc) { + pr_err("failed to stop LP link clk, rc = %d\n", + rc); + goto error; + } + + if (mngr->post_clkoff_cb) { + rc = mngr->post_clkoff_cb(mngr->priv_data, + MDSS_DSI_LINK_CLK, MDSS_DSI_LINK_LP_CLK, + l_state); + if (rc) + pr_err("post LP clk off cb failed\n"); + } + /* * This check is to save unnecessary clock state * change when going from EARLY_GATE to OFF. In the @@ -540,8 +658,8 @@ static int dsi_update_clk_state(struct dsi_core_clks *c_clks, u32 c_state, if (mngr->pre_clkoff_cb) { rc = mngr->pre_clkoff_cb(mngr->priv_data, - MDSS_DSI_CORE_CLK, - c_state); + MDSS_DSI_CORE_CLK, MDSS_DSI_LINK_NONE, + c_state); if (rc) pr_err("pre core clk off cb failed\n"); } @@ -555,7 +673,7 @@ static int dsi_update_clk_state(struct dsi_core_clks *c_clks, u32 c_state, if (c_state == MDSS_DSI_CLK_OFF) { if (mngr->post_clkoff_cb) { rc = mngr->post_clkoff_cb(mngr->priv_data, - MDSS_DSI_CORE_CLK, + MDSS_DSI_CORE_CLK, MDSS_DSI_LINK_NONE, MDSS_DSI_CLK_OFF); if (rc) pr_err("post clkoff cb fail, rc = %d\n", @@ -648,18 +766,20 @@ static int dsi_set_clk_rate(struct mdss_dsi_clk_mngr *mngr, int clk, u32 rate, MDSS_XLOG(clk, rate, flags); switch (clk) { case MDSS_DSI_LINK_ESC_CLK: - mngr->link_clks.esc_clk_rate = rate; + mngr->link_clks.lp_clks.esc_clk_rate = rate; if (!flags) { - rc = clk_set_rate(mngr->link_clks.clks.esc_clk, rate); + rc = clk_set_rate(mngr->link_clks.lp_clks.esc_clk, + rate); if (rc) pr_err("set rate failed for esc clk rc=%d\n", rc); } break; case MDSS_DSI_LINK_BYTE_CLK: - mngr->link_clks.byte_clk_rate = rate; + mngr->link_clks.hs_clks.byte_clk_rate = rate; if (!flags) { - rc = clk_set_rate(mngr->link_clks.clks.byte_clk, rate); + rc = clk_set_rate(mngr->link_clks.hs_clks.byte_clk, + rate); if (rc) { pr_err("set rate failed for byte clk rc=%d\n", rc); @@ -673,9 +793,9 @@ static int dsi_set_clk_rate(struct mdss_dsi_clk_mngr *mngr, int clk, u32 rate, * todo: this needs to be revisited when support for * CPHY is added. */ - if (mngr->link_clks.clks.byte_intf_clk) { + if (mngr->link_clks.hs_clks.byte_intf_clk) { rc = clk_set_rate( - mngr->link_clks.clks.byte_intf_clk, + mngr->link_clks.hs_clks.byte_intf_clk, rate / 2); if (rc) pr_err("set rate failed for byte intf clk rc=%d\n", @@ -684,9 +804,10 @@ static int dsi_set_clk_rate(struct mdss_dsi_clk_mngr *mngr, int clk, u32 rate, } break; case MDSS_DSI_LINK_PIX_CLK: - mngr->link_clks.pix_clk_rate = rate; + mngr->link_clks.hs_clks.pix_clk_rate = rate; if (!flags) { - rc = clk_set_rate(mngr->link_clks.clks.pixel_clk, rate); + rc = clk_set_rate(mngr->link_clks.hs_clks.pixel_clk, + rate); if (rc) pr_err("failed to set rate for pix clk rc=%d\n", rc); @@ -945,8 +1066,10 @@ void *mdss_dsi_clk_init(struct mdss_dsi_clk_info *info) mutex_init(&mngr->clk_mutex); memcpy(&mngr->core_clks.clks, &info->core_clks, sizeof(struct mdss_dsi_core_clk_info)); - memcpy(&mngr->link_clks.clks, &info->link_clks, sizeof(struct - mdss_dsi_link_clk_info)); + memcpy(&mngr->link_clks.hs_clks, &info->link_hs_clks, sizeof(struct + mdss_dsi_link_hs_clk_info)); + memcpy(&mngr->link_clks.lp_clks, &info->link_lp_clks, sizeof(struct + mdss_dsi_link_lp_clk_info)); INIT_LIST_HEAD(&mngr->client_list); mngr->pre_clkon_cb = info->pre_clkon_cb; @@ -1038,15 +1161,26 @@ int mdss_dsi_clk_force_toggle(void *client, u32 clk) if ((clk & MDSS_DSI_LINK_CLK) && (mngr->link_clks.current_clk_state == MDSS_DSI_CLK_ON)) { - rc = dsi_link_clk_stop(&mngr->link_clks); + rc = dsi_link_hs_clk_stop(&mngr->link_clks.hs_clks); if (rc) { - pr_err("failed to stop link clks\n"); + pr_err("failed to stop HS link clks\n"); goto error; } - rc = dsi_link_clk_start(&mngr->link_clks); + rc = dsi_link_lp_clk_stop(&mngr->link_clks.lp_clks); + if (rc) { + pr_err("failed to stop LP link clks\n"); + goto error; + } + + rc = dsi_link_lp_clk_start(&mngr->link_clks.lp_clks); if (rc) - pr_err("failed to start link clks\n"); + pr_err("failed to start LP link clks\n"); + + rc = dsi_link_hs_clk_start(&mngr->link_clks.hs_clks, + MDSS_DSI_LINK_CLK_START); + if (rc) + pr_err("failed to start HS link clks\n"); } else if (clk & MDSS_DSI_LINK_CLK) { pr_err("cannot reset, link clock is off\n"); diff --git a/drivers/video/fbdev/msm/mdss_dsi_clk.h b/drivers/video/fbdev/msm/mdss_dsi_clk.h index a1b8c207f6f1..d6c56a055f76 100644 --- a/drivers/video/fbdev/msm/mdss_dsi_clk.h +++ b/drivers/video/fbdev/msm/mdss_dsi_clk.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (c) 2015-2016, 2018, 2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2015-2016, 2018, 2020-2021, The Linux Foundation. All rights reserved. */ #ifndef _MDSS_DSI_CLK_H_ #define _MDSS_DSI_CLK_H_ @@ -30,6 +30,13 @@ enum mdss_dsi_link_clk_type { MDSS_DSI_LINK_CLK_MAX, }; +enum mdss_dsi_link_clk_op_type { + MDSS_DSI_LINK_CLK_SET_RATE = BIT(0), + MDSS_DSI_LINK_CLK_PREPARE = BIT(1), + MDSS_DSI_LINK_CLK_ENABLE = BIT(2), + MDSS_DSI_LINK_CLK_START = BIT(0) | BIT(1) | BIT(2), +}; + enum mdss_dsi_clk_type { MDSS_DSI_CORE_CLK = BIT(0), MDSS_DSI_LINK_CLK = BIT(1), @@ -37,53 +44,67 @@ enum mdss_dsi_clk_type { MDSS_DSI_CLKS_MAX = BIT(2), }; +enum mdss_dsi_lclk_type { + MDSS_DSI_LINK_NONE = 0, + MDSS_DSI_LINK_LP_CLK = BIT(0), + MDSS_DSI_LINK_HS_CLK = BIT(1), +}; + /** * typedef *pre_clockoff_cb() - Callback before clock is turned off * @priv: private data pointer. * @clk_type: clock which is being turned off. + * @l_type: specifies if the clock is HS or LP type. Valid only for link clocks. * @new_state: next state for the clock. * * @return: error code. */ typedef int (*pre_clockoff_cb)(void *priv, - enum mdss_dsi_clk_type clk_type, - enum mdss_dsi_clk_state new_state); + enum mdss_dsi_clk_type clk_type, + enum mdss_dsi_lclk_type l_type, + enum mdss_dsi_clk_state new_state); /** * typedef *post_clockoff_cb() - Callback after clock is turned off * @priv: private data pointer. * @clk_type: clock which was turned off. + * @l_type: specifies if the clock is HS or LP type. Valid only for link clocks. * @curr_state: current state for the clock. * * @return: error code. */ typedef int (*post_clockoff_cb)(void *priv, enum mdss_dsi_clk_type clk_type, + enum mdss_dsi_lclk_type l_type, enum mdss_dsi_clk_state curr_state); /** * typedef *post_clockon_cb() - Callback after clock is turned on * @priv: private data pointer. * @clk_type: clock which was turned on. + * @l_type: specifies if the clock is HS or LP type. Valid only for link clocks. * @curr_state: current state for the clock. * * @return: error code. */ typedef int (*post_clockon_cb)(void *priv, - enum mdss_dsi_clk_type clk_type, - enum mdss_dsi_clk_state curr_state); + enum mdss_dsi_clk_type clk_type, + enum mdss_dsi_lclk_type l_type, + enum mdss_dsi_clk_state curr_state); /** * typedef *pre_clockon_cb() - Callback before clock is turned on * @priv: private data pointer. * @clk_type: clock which is being turned on. + * @l_type: specifies if the clock is HS or LP type. Valid only for link clocks. * @new_state: next state for the clock. * * @return: error code. */ typedef int (*pre_clockon_cb)(void *priv, - enum mdss_dsi_clk_type clk_type, - enum mdss_dsi_clk_state new_state); + enum mdss_dsi_clk_type clk_type, + enum mdss_dsi_lclk_type l_type, + enum mdss_dsi_clk_state new_state); struct mdss_dsi_core_clk_info { struct clk *mdp_core_clk; @@ -93,11 +114,17 @@ struct mdss_dsi_core_clk_info { struct clk *mmss_misc_ahb_clk; }; -struct mdss_dsi_link_clk_info { - struct clk *esc_clk; +struct mdss_dsi_link_hs_clk_info { struct clk *byte_clk; struct clk *pixel_clk; struct clk *byte_intf_clk; + u32 byte_clk_rate; + u32 pix_clk_rate; +}; + +struct mdss_dsi_link_lp_clk_info { + struct clk *esc_clk; + u32 esc_clk_rate; }; struct dsi_panel_clk_ctrl { @@ -119,7 +146,8 @@ struct dsi_panel_clk_ctrl { struct mdss_dsi_clk_info { char name[DSI_CLK_NAME_LEN]; struct mdss_dsi_core_clk_info core_clks; - struct mdss_dsi_link_clk_info link_clks; + struct mdss_dsi_link_hs_clk_info link_hs_clks; + struct mdss_dsi_link_lp_clk_info link_lp_clks; pre_clockoff_cb pre_clkoff_cb; post_clockoff_cb post_clkoff_cb; post_clockon_cb post_clkon_cb; diff --git a/drivers/video/fbdev/msm/mdss_dsi_host.c b/drivers/video/fbdev/msm/mdss_dsi_host.c index f08d02a02030..29275e86902e 100644 --- a/drivers/video/fbdev/msm/mdss_dsi_host.c +++ b/drivers/video/fbdev/msm/mdss_dsi_host.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2012-2018, 2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2012-2018, 2020-2021, The Linux Foundation. All rights reserved. */ #include #include @@ -77,6 +77,8 @@ struct mdss_dsi_event { static struct mdss_dsi_event dsi_event; static int dsi_event_thread(void *data); +static void dsi_send_events(struct mdss_dsi_ctrl_pdata *ctrl, + u32 events, u32 arg); void mdss_dsi_ctrl_init(struct device *ctrl_dev, struct mdss_dsi_ctrl_pdata *ctrl) @@ -296,6 +298,30 @@ void mdss_dsi_read_phy_revision(struct mdss_dsi_ctrl_pdata *ctrl) return; reg_val = MIPI_INP(ctrl->phy_io.base); + if (!reg_val) { + /* + * DSI_0_PHY_DSIPHY_REVISION_ID3 for phy 1.0 + * reset value = 0x10 + * 7:4 Major + * 3:0 Minor + */ + reg_val = MIPI_INP(ctrl->phy_io.base + 0x20c); + reg_val = reg_val >> 4; + if (!reg_val) { + /* + * DSI_0_PHY_DSIPHY_REVISION_ID3 for 12nm PHY + * reset value = 0x20 + * 7:4 Major + * 3:0 Minor + */ + reg_val = MIPI_INP(ctrl->phy_io.base + 0x3dc); + reg_val = reg_val >> 4; + if (reg_val == 0x2) { + ctrl->shared_data->phy_rev = DSI_PHY_REV_12NM; + return; + } + } + } if (reg_val == DSI_PHY_REV_30) ctrl->shared_data->phy_rev = DSI_PHY_REV_30; @@ -418,6 +444,9 @@ void mdss_dsi_host_init(struct mdss_panel_data *pdata) mdss_dsi_config_data_lane_swap(ctrl_pdata); + if (ctrl_pdata->shared_data->phy_rev == DSI_PHY_REV_12NM) + goto next; + /* clock out ctrl */ data = pinfo->t_clk_post & 0x3f; /* 6 bits */ data <<= 8; @@ -425,6 +454,7 @@ void mdss_dsi_host_init(struct mdss_panel_data *pdata) /* DSI_CLKOUT_TIMING_CTRL */ MIPI_OUTP((ctrl_pdata->ctrl_base) + 0xc4, data); +next: data = 0; if (pinfo->rx_eot_ignore) data |= BIT(4); @@ -801,8 +831,10 @@ static void mdss_dsi_ctl_phy_reset(struct mdss_dsi_ctrl_pdata *ctrl, u32 event) * Disable PHY contention detection and receive. * Configure the strength ctrl 1 register. */ - MIPI_OUTP((ctrl0->phy_io.base) + 0x0188, 0); - MIPI_OUTP((ctrl1->phy_io.base) + 0x0188, 0); + if (ctrl0->shared_data->phy_rev != DSI_PHY_REV_12NM) { + MIPI_OUTP((ctrl0->phy_io.base) + 0x0188, 0); + MIPI_OUTP((ctrl1->phy_io.base) + 0x0188, 0); + } data0 = MIPI_INP(ctrl0->ctrl_base + 0x0004); data1 = MIPI_INP(ctrl1->ctrl_base + 0x0004); @@ -857,6 +889,18 @@ static void mdss_dsi_ctl_phy_reset(struct mdss_dsi_ctrl_pdata *ctrl, u32 event) udelay(u_dly); } if (i == loop) { + if ((ctrl0->shared_data->phy_rev == DSI_PHY_REV_12NM) && + (event == DSI_EV_LP_RX_TIMEOUT)) { + struct mdss_panel_info *pinfo = + &ctrl0->panel_data.panel_info; + /* If ESD is not enabled, report panel dead */ + if (!pinfo->esd_check_enabled && + ctrl0->recovery) + ctrl0->recovery->fxn( + ctrl0->recovery->data, + MDP_INTF_DSI_PANEL_DEAD); + return; + } MDSS_XLOG(ctrl0->ndx, ln0, 0x1f1f); MDSS_XLOG(ctrl1->ndx, ln1, 0x1f1f); pr_err("%s: Clock lane still in stop state\n", @@ -886,16 +930,24 @@ static void mdss_dsi_ctl_phy_reset(struct mdss_dsi_ctrl_pdata *ctrl, u32 event) * Enable PHY contention detection and receive. * Configure the strength ctrl 1 register. */ - MIPI_OUTP((ctrl0->phy_io.base) + 0x0188, 0x6); - MIPI_OUTP((ctrl1->phy_io.base) + 0x0188, 0x6); + if (ctrl0->shared_data->phy_rev != DSI_PHY_REV_12NM) { + MIPI_OUTP((ctrl0->phy_io.base) + 0x0188, 0x6); + MIPI_OUTP((ctrl1->phy_io.base) + 0x0188, 0x6); + } /* * Add sufficient delay to make sure * pixel transmission as started */ udelay(200); + /* Un-mask LP_RX_TIMEOUT error if recovery successful */ + if (event == DSI_EV_LP_RX_TIMEOUT) { + mdss_dsi_set_reg(ctrl0, 0x10c, BIT(5), 0); + mdss_dsi_set_reg(ctrl1, 0x10c, BIT(5), 0); + } } else { /* Disable PHY contention detection and receive */ - MIPI_OUTP((ctrl->phy_io.base) + 0x0188, 0); + if (ctrl->shared_data->phy_rev != DSI_PHY_REV_12NM) + MIPI_OUTP((ctrl->phy_io.base) + 0x0188, 0); data0 = MIPI_INP(ctrl->ctrl_base + 0x0004); /* Disable DSI video mode */ @@ -936,6 +988,17 @@ static void mdss_dsi_ctl_phy_reset(struct mdss_dsi_ctrl_pdata *ctrl, u32 event) udelay(u_dly); } if (i == loop) { + if ((ctrl->shared_data->phy_rev == DSI_PHY_REV_12NM) && + (event == DSI_EV_LP_RX_TIMEOUT)) { + struct mdss_panel_info *pinfo = + &ctrl->panel_data.panel_info; + /* If ESD is not enabled, report panel dead */ + if (!pinfo->esd_check_enabled && ctrl->recovery) + ctrl->recovery->fxn( + ctrl->recovery->data, + MDP_INTF_DSI_PANEL_DEAD); + return; + } MDSS_XLOG(ctrl->ndx, ln0, 0x1f1f); pr_err("%s: Clock lane still in stop state\n", __func__); @@ -958,12 +1021,16 @@ static void mdss_dsi_ctl_phy_reset(struct mdss_dsi_ctrl_pdata *ctrl, u32 event) /* Enable Video mode for DSI controller */ MIPI_OUTP(ctrl->ctrl_base + 0x004, data0); /* Enable PHY contention detection and receiver */ - MIPI_OUTP((ctrl->phy_io.base) + 0x0188, 0x6); + if (ctrl->shared_data->phy_rev != DSI_PHY_REV_12NM) + MIPI_OUTP((ctrl->phy_io.base) + 0x0188, 0x6); /* * Add sufficient delay to make sure * pixel transmission as started */ udelay(200); + /* Un-mask LP_RX_TIMEOUT error if recovery successful */ + if (event == DSI_EV_LP_RX_TIMEOUT) + mdss_dsi_set_reg(ctrl, 0x10c, BIT(5), 0); } pr_debug("Recovery done\n"); } @@ -1616,15 +1683,45 @@ int mdss_dsi_bta_status_check(struct mdss_dsi_ctrl_pdata *ctrl_pdata) ret = wait_for_completion_killable_timeout(&ctrl_pdata->bta_comp, DSI_BTA_EVENT_TIMEOUT); if (ret <= 0) { - mdss_dsi_disable_irq(ctrl_pdata, DSI_BTA_TERM); - pr_err("%s: DSI BTA error: %i\n", __func__, ret); + u32 reg_val, status; + + reg_val = MIPI_INP(ctrl_pdata->ctrl_base + 0x0110); + status = reg_val & DSI_INTR_BTA_DONE; + if (status) { + reg_val &= DSI_INTR_MASK_ALL; + /* clear BTA_DONE isr only */ + reg_val |= DSI_INTR_BTA_DONE; + MIPI_OUTP(ctrl_pdata->ctrl_base + 0x0110, reg_val); + mdss_dsi_disable_irq(ctrl_pdata, DSI_BTA_TERM); + complete(&ctrl_pdata->bta_comp); + ret = 1; + pr_warn("%s: bta done but irq not triggered\n", + __func__); + } else { + pr_err("%s: DSI BTA error: %i\n", __func__, ret); + /* + * For 12nm DSI PHY, BTA_TO interrupt may not trigger. + * Treat software timer timeout as BTA_TO. + */ + if (ctrl_pdata->shared_data->phy_rev == + DSI_PHY_REV_12NM) { + /* Mask BTA_TIMEOUT/LP_RX_TIMEOUT error */ + mdss_dsi_set_reg(ctrl_pdata, 0x10c, + (BIT(5) | BIT(7)), (BIT(5) | BIT(7))); + dsi_send_events(ctrl_pdata, + DSI_EV_LP_RX_TIMEOUT, 0); + } + ret = -ETIMEDOUT; + } } if (ignore_underflow) { + u32 data = MIPI_INP((ctrl_pdata->ctrl_base) + 0x10c); /* clear pending overflow status */ mdss_dsi_set_reg(ctrl_pdata, 0xc, 0xffffffff, 0x44440000); - /* restore overflow isr */ - mdss_dsi_set_reg(ctrl_pdata, 0x10c, 0x0f0000, 0); + /* restore overflow isr if LP_RX_TO not masked*/ + if (!(data & BIT(5))) + mdss_dsi_set_reg(ctrl_pdata, 0x10c, 0x0f0000, 0); } mdss_dsi_clk_ctrl(ctrl_pdata, ctrl_pdata->dsi_clk_handle, @@ -2271,10 +2368,12 @@ static int mdss_dsi_cmd_dma_tx(struct mdss_dsi_ctrl_pdata *ctrl, if (mctrl && mctrl->dma_addr) { if (ignored) { + u32 data = MIPI_INP((mctrl->ctrl_base) + 0x10c); /* clear pending overflow status */ mdss_dsi_set_reg(mctrl, 0xc, 0xffffffff, 0x44440000); - /* restore overflow isr */ - mdss_dsi_set_reg(mctrl, 0x10c, 0x0f0000, 0); + /* restore overflow isr if LP_RX_TO not masked*/ + if (!(data & BIT(5))) + mdss_dsi_set_reg(mctrl, 0x10c, 0x0f0000, 0); } if (mctrl->dmap_iommu_map) { mdss_smmu_dsi_unmap_buffer(mctrl->dma_addr, domain, @@ -2292,10 +2391,12 @@ static int mdss_dsi_cmd_dma_tx(struct mdss_dsi_ctrl_pdata *ctrl, } if (ignored) { + u32 data = MIPI_INP((ctrl->ctrl_base) + 0x10c); /* clear pending overflow status */ mdss_dsi_set_reg(ctrl, 0xc, 0xffffffff, 0x44440000); - /* restore overflow isr */ - mdss_dsi_set_reg(ctrl, 0x10c, 0x0f0000, 0); + /* restore overflow isr if LP_RX_TO/BTA_TO not masked*/ + if (!(data & BIT(5))) + mdss_dsi_set_reg(ctrl, 0x10c, 0x0f0000, 0); } ctrl->dma_addr = 0; ctrl->dma_size = 0; @@ -3103,8 +3204,12 @@ static bool mdss_dsi_timeout_status(struct mdss_dsi_ctrl_pdata *ctrl) if (status & 0x0111) { MIPI_OUTP(base + 0x00c0, status); - if (status & 0x0110) + if (status & 0x0110) { + /* Mask BTA_TIMEOUT/LP_RX_TIMEOUT error */ + mdss_dsi_set_reg(ctrl, 0x10c, + (BIT(5) | BIT(7)), (BIT(5) | BIT(7))); dsi_send_events(ctrl, DSI_EV_LP_RX_TIMEOUT, 0); + } pr_err("%s: status=%x\n", __func__, status); ret = true; } diff --git a/drivers/video/fbdev/msm/mdss_dsi_panel.c b/drivers/video/fbdev/msm/mdss_dsi_panel.c index b856a709c945..decf051db214 100644 --- a/drivers/video/fbdev/msm/mdss_dsi_panel.c +++ b/drivers/video/fbdev/msm/mdss_dsi_panel.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. */ #include #include @@ -38,6 +38,7 @@ void mdss_dsi_panel_pwm_cfg(struct mdss_dsi_ctrl_pdata *ctrl) if (ctrl->pwm_bl == NULL || IS_ERR(ctrl->pwm_bl)) { pr_err("%s: Error: lpg_chan=%d pwm request failed\n", __func__, ctrl->pwm_lpg_chan); + ctrl->pwm_bl = NULL; } ctrl->pwm_enabled = 0; } @@ -2181,13 +2182,13 @@ static int mdss_dsi_parse_panel_features(struct device_node *np, pinfo->dcs_cmd_by_left = of_property_read_bool(np, "qcom,dcs-cmd-by-left"); - pinfo->ulps_feature_enabled = true; - + pinfo->ulps_feature_enabled = of_property_read_bool(np, + "qcom,ulps-enabled"); pr_info("%s: ulps feature %s\n", __func__, (pinfo->ulps_feature_enabled ? "enabled" : "disabled")); - pinfo->ulps_suspend_enabled = true; - + pinfo->ulps_suspend_enabled = of_property_read_bool(np, + "qcom,suspend-ulps-enabled"); pr_info("%s: ulps during suspend feature %s\n", __func__, (pinfo->ulps_suspend_enabled ? "enabled" : "disabled")); @@ -2196,7 +2197,8 @@ static int mdss_dsi_parse_panel_features(struct device_node *np, pinfo->panel_ack_disabled = pinfo->sim_panel_mode ? 1 : of_property_read_bool(np, "qcom,panel-ack-disabled"); - pinfo->allow_phy_power_off = true; + pinfo->allow_phy_power_off = of_property_read_bool(np, + "qcom,panel-allow-phy-poweroff"); mdss_dsi_parse_esd_params(np, ctrl); @@ -2503,6 +2505,9 @@ int mdss_dsi_panel_timing_switch(struct mdss_dsi_ctrl_pdata *ctrl, for (i = 0; i < ARRAY_SIZE(pt->phy_timing_8996); i++) pinfo->mipi.dsi_phy_db.timing_8996[i] = pt->phy_timing_8996[i]; + for (i = 0; i < ARRAY_SIZE(pt->phy_timing_12nm); i++) + pinfo->mipi.dsi_phy_db.timing_12nm[i] = pt->phy_timing_12nm[i]; + ctrl->on_cmds = pt->on_cmds; ctrl->post_panel_on_cmds = pt->post_panel_on_cmds; @@ -2616,6 +2621,18 @@ static int mdss_dsi_panel_timing_from_dt(struct device_node *np, pt->phy_timing_8996[i] = data[i]; phy_timings_present = true; } + + data = of_get_property(np, + "qcom,mdss-dsi-panel-timings-phy-12nm", &len); + if ((!data) || (len != 8)) { + pr_debug("%s:%d,Unable to read 12nm Phy lane timing settings\n", + __func__, __LINE__); + } else { + for (i = 0; i < len; i++) + pt->phy_timing_12nm[i] = data[i]; + phy_timings_present = true; + } + if (!phy_timings_present) { pr_err("%s: phy timing settings not present\n", __func__); return -EINVAL; diff --git a/drivers/video/fbdev/msm/mdss_dsi_phy.h b/drivers/video/fbdev/msm/mdss_dsi_phy.h index ca88c621c6b1..d6d370aa643a 100644 --- a/drivers/video/fbdev/msm/mdss_dsi_phy.h +++ b/drivers/video/fbdev/msm/mdss_dsi_phy.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (c) 2015-2018, 2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2015-2018, 2020-2021, The Linux Foundation. All rights reserved. */ #ifndef MDSS_DSI_PHY_H #define MDSS_DSI_PHY_H @@ -14,6 +14,7 @@ enum phy_rev { DSI_PHY_REV_10 = 0x01, /* REV 1.0 - 20nm, 28nm */ DSI_PHY_REV_20 = 0x02, /* REV 2.0 - 14nm */ DSI_PHY_REV_30 = 0x03, /* REV 3.0 */ + DSI_PHY_REV_12NM = 0x04, /* 12nm PHY */ DSI_PHY_REV_MAX, }; @@ -115,4 +116,54 @@ int mdss_dsi_phy_v3_ulps_config(struct mdss_dsi_ctrl_pdata *ctrl, bool enable); * can perform any sequence required after the Idle PC exit. */ void mdss_dsi_phy_v3_idle_pc_exit(struct mdss_dsi_ctrl_pdata *ctrl); + +/** + * mdss_dsi_12nm_phy_regulator_enable() - enable lane reg for DSI 12nm PHY + * + * @ctrl: pointer to DSI controller structure + */ +int mdss_dsi_12nm_phy_regulator_enable(struct mdss_dsi_ctrl_pdata *ctrl); + +/* + * mdss_dsi_12nm_phy_regulator_disable() - disable lane reg for DSI 12nm PHY + * + * @ctrl: pointer to DSI controller structure + */ +int mdss_dsi_12nm_phy_regulator_disable(struct mdss_dsi_ctrl_pdata *ctrl); + +/* + * mdss_dsi_12nm_phy_config() - initialization sequence for DSI 12nm PHY + * + * @ctrl: pointer to DSI controller structure + * + * This function performs a sequence of register writes to initialize DSI + * 12nm phy. This function assumes that the DSI bus clocks are turned on. + * This function should only be called prior to enabling the DSI link clocks. + */ +int mdss_dsi_12nm_phy_config(struct mdss_dsi_ctrl_pdata *ctrl); + +/* + * mdss_dsi_12nm_phy_shutdown() - shutdown sequence for DSI 12nm PHY + * + * @ctrl: pointer to DSI controller structure + * + * Perform a sequence of register writes to completely shut down DSI 12nm PHY. + * This function assumes that the DSI bus clocks are turned on. + */ +int mdss_dsi_12nm_phy_shutdown(struct mdss_dsi_ctrl_pdata *ctrl); + +/* + * mdss_dsi_12nm_phy_hstx_drv_ctrl() - enable/disable HSTX drivers + * + * @ctrl: pointer to DSI controller structure + * @enable: boolean to specify enable/disable the HSTX drivers + * + * Perform a sequence of register writes to enable/disable HSTX drivers. + * This function assumes that the DSI bus clocks are turned on. + */ + +void mdss_dsi_12nm_phy_hstx_drv_ctrl( + struct mdss_dsi_ctrl_pdata *ctrl, bool enable); + + #endif /* MDSS_DSI_PHY_H */ diff --git a/drivers/video/fbdev/msm/mdss_dsi_phy_12nm.c b/drivers/video/fbdev/msm/mdss_dsi_phy_12nm.c index e2a3b41a5e74..a24e7eb85e86 100644 --- a/drivers/video/fbdev/msm/mdss_dsi_phy_12nm.c +++ b/drivers/video/fbdev/msm/mdss_dsi_phy_12nm.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2018, 2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2018, 2020-2021, The Linux Foundation. All rights reserved. */ #include #include "mdss_dsi_phy.h" @@ -15,6 +15,7 @@ #define HSTX_CLKLANE_REQSTATE_TIM_CTRL 0x180 #define HSTX_CLKLANE_HS0STATE_TIM_CTRL 0x188 #define HSTX_CLKLANE_TRALSTATE_TIM_CTRL 0x18c +#define HSTX_CLKLANE_EXITSTATE_TIM_CTRL 0x190 #define HSTX_CLKLANE_CLKPOSTSTATE_TIM_CTRL 0x194 #define HSTX_DATALANE_REQSTATE_TIM_CTRL 0x1c0 #define HSTX_DATALANE_HS0STATE_TIM_CTRL 0x1c8 @@ -60,6 +61,8 @@ int mdss_dsi_12nm_phy_config(struct mdss_dsi_ctrl_pdata *ctrl) (pd->timing_12nm[2] | BIT(6))); DSI_PHY_W32(ctrl->phy_io.base, HSTX_CLKLANE_REQSTATE_TIM_CTRL, pd->timing_12nm[3]); + DSI_PHY_W32(ctrl->phy_io.base, HSTX_CLKLANE_EXITSTATE_TIM_CTRL, + (pd->timing_12nm[7] | BIT(6) | BIT(7))); /* DSI PHY data lane timings */ DSI_PHY_W32(ctrl->phy_io.base, HSTX_DATALANE_HS0STATE_TIM_CTRL, @@ -94,6 +97,7 @@ int mdss_dsi_12nm_phy_shutdown(struct mdss_dsi_ctrl_pdata *ctrl) { DSI_PHY_W32(ctrl->phy_io.base, SYS_CTRL, BIT(0) | BIT(3)); wmb(); /* make sure DSI PHY is disabled */ + mdss_dsi_ctrl_phy_reset(ctrl); return 0; } diff --git a/drivers/video/fbdev/msm/mdss_dsi_status.c b/drivers/video/fbdev/msm/mdss_dsi_status.c index 0bf6e242661e..863b596e053b 100644 --- a/drivers/video/fbdev/msm/mdss_dsi_status.c +++ b/drivers/video/fbdev/msm/mdss_dsi_status.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2013-2018, 2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2013-2018, 2020-2021, The Linux Foundation. All rights reserved. */ #include #include @@ -150,14 +150,16 @@ static int fb_event_callback(struct notifier_block *self, struct mdss_dsi_ctrl_pdata *ctrl_pdata = NULL; struct mdss_panel_info *pinfo; struct msm_fb_data_type *mfd; + char fb_id[7] = {'\0'}; if (!evdata) { pr_err("%s: event data not available\n", __func__); return NOTIFY_BAD; } + strlcpy(fb_id, evdata->info->fix.id, 7); /* handle only mdss fb device */ - if (strcmp("mdssfb", evdata->info->fix.id)) + if (strcmp("mdssfb", fb_id)) return NOTIFY_DONE; mfd = evdata->info->par; diff --git a/drivers/video/fbdev/msm/mdss_mdp_intf_cmd.c b/drivers/video/fbdev/msm/mdss_mdp_intf_cmd.c index 4705191e84b9..8ee33a2536d8 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_intf_cmd.c +++ b/drivers/video/fbdev/msm/mdss_mdp_intf_cmd.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2013-2018, 2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2013-2018, 2020-2021, The Linux Foundation. All rights reserved. */ #include #include @@ -1322,16 +1322,21 @@ static int mdss_mdp_cmd_intf_recovery(void *data, int event) return -EINVAL; /* - * Currently, only intf_fifo_underflow is + * Currently, intf_fifo_overflow is not * supported for recovery sequence for command * mode DSI interface */ - if (event != MDP_INTF_DSI_CMD_FIFO_UNDERFLOW) { + if (event == MDP_INTF_DSI_VIDEO_FIFO_OVERFLOW) { pr_warn("%s: unsupported recovery event:%d\n", __func__, event); return -EPERM; } + if (event == MDP_INTF_DSI_PANEL_DEAD) { + mdss_fb_report_panel_dead(ctx->ctl->mfd); + return 0; + } + if (atomic_read(&ctx->koff_cnt)) { mdss_mdp_ctl_reset(ctx->ctl, true); reset_done = true; diff --git a/drivers/video/fbdev/msm/mdss_mdp_intf_video.c b/drivers/video/fbdev/msm/mdss_mdp_intf_video.c index f1f7de5e7590..3bf0f2211a35 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_intf_video.c +++ b/drivers/video/fbdev/msm/mdss_mdp_intf_video.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2012-2018, 2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2012-2018, 2020-2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "%s: " fmt, __func__ @@ -367,11 +367,11 @@ static int mdss_mdp_video_intf_recovery(void *data, int event) } /* - * Currently, only intf_fifo_overflow is + * Currently, intf_fifo_underflow is not * supported for recovery sequence for video * mode DSI interface */ - if (event != MDP_INTF_DSI_VIDEO_FIFO_OVERFLOW) { + if (event == MDP_INTF_DSI_CMD_FIFO_UNDERFLOW) { pr_warn("%s: unsupported recovery event:%d\n", __func__, event); return -EPERM; @@ -381,6 +381,11 @@ static int mdss_mdp_video_intf_recovery(void *data, int event) pr_debug("%s: ctl num = %d, event = %d\n", __func__, ctl->num, event); + if (event == MDP_INTF_DSI_PANEL_DEAD) { + mdss_fb_report_panel_dead(ctx->ctl->mfd); + return 0; + } + pinfo = &ctl->panel_data->panel_info; clk_rate = ((ctl->intf_type == MDSS_INTF_DSI) ? pinfo->mipi.dsi_pclk_rate : diff --git a/drivers/video/fbdev/msm/mdss_panel.h b/drivers/video/fbdev/msm/mdss_panel.h index ea4fa0acccaf..dc925c05196d 100644 --- a/drivers/video/fbdev/msm/mdss_panel.h +++ b/drivers/video/fbdev/msm/mdss_panel.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (c) 2008-2018, 2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2008-2018, 2020-2021, The Linux Foundation. All rights reserved. */ #ifndef MDSS_PANEL_H #define MDSS_PANEL_H @@ -180,6 +180,7 @@ struct mdss_panel_cfg { #define MDP_INTF_DSI_CMD_FIFO_UNDERFLOW 0x0001 #define MDP_INTF_DSI_VIDEO_FIFO_OVERFLOW 0x0002 +#define MDP_INTF_DSI_PANEL_DEAD 0x0003 enum { @@ -419,6 +420,7 @@ struct mdss_dsi_phy_ctrl { bool reg_ldo_mode; char timing_8996[40];/* 8996, 8 * 5 */ + char timing_12nm[14]; /* 12nm PHY */ char regulator_len; char strength_len; char lanecfg_len; diff --git a/drivers/video/fbdev/msm/mdss_rgb.c b/drivers/video/fbdev/msm/mdss_rgb.c index 71dc87b03794..d34ef7c2d524 100644 --- a/drivers/video/fbdev/msm/mdss_rgb.c +++ b/drivers/video/fbdev/msm/mdss_rgb.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2018, 2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2018, 2020-2021, The Linux Foundation. All rights reserved. */ #include @@ -449,8 +449,8 @@ static int mdss_rgb_ctrl_clock_init(struct platform_device *ctrl_pdev, info.core_clks.mdp_core_clk = rgb_data->mdp_core_clk; info.core_clks.mmss_misc_ahb_clk = rgb_data->mmss_misc_ahb_clk; - info.link_clks.byte_clk = rgb_data->byte_clk_rgb; - info.link_clks.pixel_clk = rgb_data->pixel_clk_rgb; + info.link_hs_clks.byte_clk = rgb_data->byte_clk_rgb; + info.link_hs_clks.pixel_clk = rgb_data->pixel_clk_rgb; info.priv_data = rgb_data; snprintf(info.name, sizeof(info.name), "DSI0"); diff --git a/drivers/video/fbdev/msm/msm_mdss_io_8974.c b/drivers/video/fbdev/msm/msm_mdss_io_8974.c index 4dfdc550ca35..54c481e92bcd 100644 --- a/drivers/video/fbdev/msm/msm_mdss_io_8974.c +++ b/drivers/video/fbdev/msm/msm_mdss_io_8974.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (c) 2012-2018, 2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2012-2018, 2020-2021, The Linux Foundation. All rights reserved. */ #include #include #include @@ -433,7 +433,7 @@ void mdss_dsi_dfps_config_8996(struct mdss_dsi_ctrl_pdata *ctrl) wmb(); /* make sure phy timings are updated*/ } -static void mdss_dsi_ctrl_phy_reset(struct mdss_dsi_ctrl_pdata *ctrl) +void mdss_dsi_ctrl_phy_reset(struct mdss_dsi_ctrl_pdata *ctrl) { /* start phy sw reset */ MIPI_OUTP(ctrl->ctrl_base + 0x12c, 0x0001); @@ -580,6 +580,9 @@ static void mdss_dsi_phy_regulator_disable(struct mdss_dsi_ctrl_pdata *ctrl) if (ctrl->shared_data->phy_rev == DSI_PHY_REV_30) return; + if (ctrl->shared_data->phy_rev == DSI_PHY_REV_12NM) + return; + MIPI_OUTP(ctrl->phy_regulator_io.base + 0x018, 0x000); } @@ -596,6 +599,8 @@ static void mdss_dsi_phy_shutdown(struct mdss_dsi_ctrl_pdata *ctrl) MIPI_OUTP(ctrl->phy_io.base + DSIPHY_CMN_CTRL_0, 0); } else if (ctrl->shared_data->phy_rev == DSI_PHY_REV_30) { mdss_dsi_phy_v3_shutdown(ctrl); + } else if (ctrl->shared_data->phy_rev == DSI_PHY_REV_12NM) { + mdss_dsi_12nm_phy_shutdown(ctrl); } else { MIPI_OUTP(ctrl->phy_io.base + MDSS_DSI_DSIPHY_CTRL_0, 0x000); } @@ -617,7 +622,8 @@ void mdss_dsi_lp_cd_rx(struct mdss_dsi_ctrl_pdata *ctrl) return; } - if (ctrl->shared_data->phy_rev == DSI_PHY_REV_20) + if ((ctrl->shared_data->phy_rev == DSI_PHY_REV_20) || + (ctrl->shared_data->phy_rev == DSI_PHY_REV_12NM)) return; pd = &(((ctrl->panel_data).panel_info.mipi).dsi_phy_db); @@ -1308,6 +1314,8 @@ static void mdss_dsi_phy_regulator_ctrl(struct mdss_dsi_ctrl_pdata *ctrl, mdss_dsi_8996_phy_regulator_enable(ctrl); } else if (ctrl->shared_data->phy_rev == DSI_PHY_REV_30) { mdss_dsi_phy_v3_regulator_enable(ctrl); + } else if (ctrl->shared_data->phy_rev == DSI_PHY_REV_12NM) { + mdss_dsi_12nm_phy_regulator_enable(ctrl); } else { switch (ctrl->shared_data->hw_rev) { case MDSS_DSI_HW_REV_103: @@ -1360,6 +1368,8 @@ static void mdss_dsi_phy_ctrl(struct mdss_dsi_ctrl_pdata *ctrl, bool enable) mdss_dsi_8996_phy_config(ctrl); } else if (ctrl->shared_data->phy_rev == DSI_PHY_REV_30) { mdss_dsi_phy_v3_init(ctrl, DSI_PHY_MODE_DPHY); + } else if (ctrl->shared_data->phy_rev == DSI_PHY_REV_12NM) { + mdss_dsi_12nm_phy_config(ctrl); } else { switch (ctrl->shared_data->hw_rev) { case MDSS_DSI_HW_REV_103: @@ -1435,6 +1445,13 @@ void mdss_dsi_phy_init(struct mdss_dsi_ctrl_pdata *ctrl) } } +static void mdss_dsi_phy_hstx_drv_ctrl( + struct mdss_dsi_ctrl_pdata *ctrl, bool enable) +{ + if (ctrl->shared_data->phy_rev == DSI_PHY_REV_12NM) + mdss_dsi_12nm_phy_hstx_drv_ctrl(ctrl, enable); +} + void mdss_dsi_core_clk_deinit(struct device *dev, struct dsi_shared_data *sdata) { } @@ -1866,7 +1883,7 @@ u32 mdss_dsi_get_pclk_rate(struct mdss_panel_info *panel_info, u64 clk_rate) } static bool mdss_dsi_is_ulps_req_valid(struct mdss_dsi_ctrl_pdata *ctrl, - int enable) + int enable, bool reconfig) { struct mdss_dsi_ctrl_pdata *octrl = NULL; struct mdss_panel_data *pdata = &ctrl->panel_data; @@ -1897,11 +1914,11 @@ static bool mdss_dsi_is_ulps_req_valid(struct mdss_dsi_ctrl_pdata *ctrl, * However, this should be allowed in following usecases: * 1. If ULPS during suspend feature is enabled, where we * configure the lanes in ULPS after turning off the panel. - * 2. When coming out of idle PC with clamps enabled, where we - * transition the controller HW state back to ULPS prior to + * 2. When coming out of idle PC with ULPS enabled, where we need to + * reconfigure the controller HW state again to ULPS prior to * disabling ULPS. */ - if (enable && !ctrl->mmss_clamp && + if (enable && !reconfig && !(ctrl->ctrl_state & CTRL_STATE_PANEL_INIT) && !pdata->panel_info.ulps_suspend_enabled) { pr_debug("%s: panel not yet initialized\n", __func__); @@ -2028,6 +2045,7 @@ static int mdss_dsi_ulps_config_default(struct mdss_dsi_ctrl_pdata *ctrl, * mdss_dsi_ulps_config() - Program DSI lanes to enter/exit ULPS mode * @ctrl: pointer to DSI controller structure * @enable: 1 to enter ULPS, 0 to exit ULPS + * @reconfig: boolean to specify if DSI controller is reconfigured to enter ULPS * * Execute the necessary programming sequence to enter/exit DSI Ultra-Low Power * State (ULPS). This function the validity of the ULPS config request and @@ -2035,7 +2053,7 @@ static int mdss_dsi_ulps_config_default(struct mdss_dsi_ctrl_pdata *ctrl, * This function assumes that the link and core clocks are already on. */ static int mdss_dsi_ulps_config(struct mdss_dsi_ctrl_pdata *ctrl, - int enable) + int enable, bool reconfig) { int ret = 0; @@ -2044,15 +2062,15 @@ static int mdss_dsi_ulps_config(struct mdss_dsi_ctrl_pdata *ctrl, return -EINVAL; } - if (!mdss_dsi_is_ulps_req_valid(ctrl, enable)) { + if (!mdss_dsi_is_ulps_req_valid(ctrl, enable, reconfig)) { pr_debug("%s: skiping ULPS config for ctrl%d, enable=%d\n", __func__, ctrl->ndx, enable); return 0; } - pr_debug("%s: configuring ulps (%s) for ctrl%d, clamps=%s\n", + pr_debug("%s: configuring ulps (%s) for ctrl%d, reconfig=%s\n", __func__, (enable ? "on" : "off"), ctrl->ndx, - ctrl->mmss_clamp ? "enabled" : "disabled"); + reconfig ? "true" : "false"); if (enable && !ctrl->ulps) { /* @@ -2065,7 +2083,7 @@ static int mdss_dsi_ulps_config(struct mdss_dsi_ctrl_pdata *ctrl, * power collapse and just restoring the controller state to * ULPS with the clamps still in place. */ - if (!ctrl->mmss_clamp) { + if (!reconfig) { ret = mdss_dsi_wait_for_lane_idle(ctrl); if (ret) { pr_warn_ratelimited("%s: lanes not idle, skip ulps\n", @@ -2425,6 +2443,7 @@ int mdss_dsi_clk_ctrl(struct mdss_dsi_ctrl_pdata *ctrl, void *clk_handle, int mdss_dsi_pre_clkoff_cb(void *priv, enum mdss_dsi_clk_type clk, + enum mdss_dsi_lclk_type l_type, enum mdss_dsi_clk_state new_state) { int rc = 0; @@ -2433,7 +2452,14 @@ int mdss_dsi_pre_clkoff_cb(void *priv, pdata = &ctrl->panel_data; - if ((clk & MDSS_DSI_LINK_CLK) && (new_state == MDSS_DSI_CLK_OFF)) { + if ((clk & MDSS_DSI_LINK_CLK) && (l_type == MDSS_DSI_LINK_HS_CLK) && + (new_state == MDSS_DSI_CLK_OFF)) { + /* Disable HS TX driver in DSI PHY if applicable */ + mdss_dsi_phy_hstx_drv_ctrl(ctrl, false); + } + + if ((clk & MDSS_DSI_LINK_CLK) && (l_type == MDSS_DSI_LINK_LP_CLK) && + (new_state == MDSS_DSI_CLK_OFF)) { if (pdata->panel_info.mipi.force_clk_lane_hs) mdss_dsi_cfg_lane_ctrl(ctrl, BIT(28), 0); /* @@ -2443,9 +2469,9 @@ int mdss_dsi_pre_clkoff_cb(void *priv, */ if (!(ctrl->ctrl_state & CTRL_STATE_PANEL_INIT)) { if (pdata->panel_info.ulps_suspend_enabled) - mdss_dsi_ulps_config(ctrl, 1); + mdss_dsi_ulps_config(ctrl, 1, false); } else if (mdss_dsi_ulps_feature_enabled(pdata)) { - rc = mdss_dsi_ulps_config(ctrl, 1); + rc = mdss_dsi_ulps_config(ctrl, 1, false); } if (rc) { pr_err("%s: failed enable ulps, rc = %d\n", @@ -2470,7 +2496,7 @@ int mdss_dsi_pre_clkoff_cb(void *priv, * Make sure that controller is not in ULPS state when * the DSI link is not active. */ - rc = mdss_dsi_ulps_config(ctrl, 0); + rc = mdss_dsi_ulps_config(ctrl, 0, false); if (rc) pr_err("%s: failed to disable ulps. rc=%d\n", __func__, rc); @@ -2508,6 +2534,7 @@ static void mdss_dsi_split_link_clk_cfg(struct mdss_dsi_ctrl_pdata *ctrl, int mdss_dsi_post_clkon_cb(void *priv, enum mdss_dsi_clk_type clk, + enum mdss_dsi_lclk_type l_type, enum mdss_dsi_clk_state curr_state) { int rc = 0; @@ -2526,7 +2553,23 @@ int mdss_dsi_post_clkon_cb(void *priv, if (mmss_clamp) mdss_dsi_ctrl_setup(ctrl); - if (ctrl->ulps && mmss_clamp) { + rc = mdss_dsi_clamp_ctrl(ctrl, 0); + if (rc) { + pr_err("%s: Failed to disable dsi clamps. rc=%d\n", + __func__, rc); + goto error; + } + + /* + * Phy setup is needed if coming out of idle + * power collapse with clamps enabled. + */ + if (ctrl->phy_power_off || mmss_clamp) + mdss_dsi_phy_power_on(ctrl, mmss_clamp); + } + + if ((clk & MDSS_DSI_LINK_CLK) && (l_type == MDSS_DSI_LINK_LP_CLK)) { + if (ctrl->ulps) { /* * ULPS Entry Request. This is needed if the lanes were * in ULPS prior to power collapse, since after @@ -2542,54 +2585,46 @@ int mdss_dsi_post_clkon_cb(void *priv, * ULPS. */ ctrl->ulps = false; - rc = mdss_dsi_ulps_config(ctrl, 1); + rc = mdss_dsi_ulps_config(ctrl, 1, true); if (rc) { pr_err("%s: Failed to enter ULPS. rc=%d\n", __func__, rc); goto error; } - } - rc = mdss_dsi_clamp_ctrl(ctrl, 0); - if (rc) { - pr_err("%s: Failed to disable dsi clamps. rc=%d\n", - __func__, rc); - goto error; - } + /* toggle the resync FIFO everytime clock changes */ + if ((ctrl->shared_data->phy_rev == DSI_PHY_REV_30) && + !pdata->panel_info.cont_splash_enabled) + mdss_dsi_phy_v3_toggle_resync_fifo(ctrl); - /* - * Phy setup is needed if coming out of idle - * power collapse with clamps enabled. - */ - if (ctrl->phy_power_off || mmss_clamp) - mdss_dsi_phy_power_on(ctrl, mmss_clamp); - } - if (clk & MDSS_DSI_LINK_CLK) { - /* toggle the resync FIFO everytime clock changes */ - if ((ctrl->shared_data->phy_rev == DSI_PHY_REV_30) && - !pdata->panel_info.cont_splash_enabled) - mdss_dsi_phy_v3_toggle_resync_fifo(ctrl); - - if (ctrl->ulps) { - rc = mdss_dsi_ulps_config(ctrl, 0); + rc = mdss_dsi_ulps_config(ctrl, 0, false); if (rc) { pr_err("%s: failed to disable ulps, rc= %d\n", __func__, rc); goto error; } } + if (pdata->panel_info.mipi.force_clk_lane_hs) mdss_dsi_cfg_lane_ctrl(ctrl, BIT(28), 1); /* enable split link for cmn clk cfg1 */ mdss_dsi_split_link_clk_cfg(ctrl, 1); + + /* Enable HS TX driver in DSI PHY if applicable */ + if ((clk & MDSS_DSI_LINK_CLK) && + (l_type == MDSS_DSI_LINK_HS_CLK)) + mdss_dsi_phy_hstx_drv_ctrl(ctrl, true); } + + error: return rc; } int mdss_dsi_post_clkoff_cb(void *priv, enum mdss_dsi_clk_type clk_type, + enum mdss_dsi_lclk_type l_type, enum mdss_dsi_clk_state curr_state) { int rc = 0; @@ -2643,6 +2678,7 @@ int mdss_dsi_post_clkoff_cb(void *priv, int mdss_dsi_pre_clkon_cb(void *priv, enum mdss_dsi_clk_type clk_type, + enum mdss_dsi_lclk_type l_type, enum mdss_dsi_clk_state new_state) { int rc = 0; @@ -2695,6 +2731,30 @@ int mdss_dsi_pre_clkon_cb(void *priv, } } + /* Disable dynamic clock gating*/ + if (ctrl->mdss_util->dyn_clk_gating_ctrl) + ctrl->mdss_util->dyn_clk_gating_ctrl(0); + + if ((clk_type & MDSS_DSI_LINK_CLK) && + (l_type == MDSS_DSI_LINK_HS_CLK)) { + u32 data = 0; + + data = MIPI_INP((ctrl->ctrl_io.base) + 0x0120); + /* + * For 12nm PHY, the PLL unlock bit in DSI_CLK_STATUS gets set + * when PLL is turned off. When device comes out of static + * screen without the DSI controller getting power collapsed, + * the bit might not clear sometimes. Clear the bit before + * turning ON the PLL. This avoids false error interrupt due to + * PLL unlocked bit after PLL is turned ON. + */ + if (data & BIT(16)) { + pr_debug("pll unlocked: 0x%x\n", data); + MIPI_OUTP((ctrl->ctrl_io.base) + 0x120, BIT(16)); + } + + } + if ((clk_type & MDSS_DSI_LINK_CLK) && (new_state == MDSS_DSI_CLK_ON) && !ctrl->panel_data.panel_info.cont_splash_enabled) diff --git a/fs/Kconfig b/fs/Kconfig index 1389e6da197a..b8d003f02b76 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -136,7 +136,6 @@ if BLOCK menu "DOS/FAT/NT Filesystems" source "fs/fat/Kconfig" -source "fs/exfat/Kconfig" source "fs/ntfs/Kconfig" endmenu diff --git a/fs/Makefile b/fs/Makefile index c62198e859d7..9d1caea038f6 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -80,7 +80,6 @@ obj-$(CONFIG_HUGETLBFS) += hugetlbfs/ obj-$(CONFIG_CODA_FS) += coda/ obj-$(CONFIG_MINIX_FS) += minix/ obj-$(CONFIG_FAT_FS) += fat/ -obj-$(CONFIG_EXFAT_FS) += exfat/ obj-$(CONFIG_BFS_FS) += bfs/ obj-$(CONFIG_ISO9660_FS) += isofs/ obj-$(CONFIG_HFSPLUS_FS) += hfsplus/ # Before hfs to find wrapped HFS+ diff --git a/fs/crypto/Kconfig b/fs/crypto/Kconfig index 97c0a113f4cc..fbf7b094703b 100644 --- a/fs/crypto/Kconfig +++ b/fs/crypto/Kconfig @@ -29,3 +29,12 @@ config FS_ENCRYPTION_INLINE_CRYPT depends on FS_ENCRYPTION && BLK_INLINE_ENCRYPTION help Enable fscrypt to use inline encryption hardware if available. + +config ENABLE_LEGACY_PFK + bool "Legacy method to generate per file key" + default n + help + Enable legacy method to generate aes keys derived + from nonce and master key. In private mode the keys + will be used by inline crypto hardware to encrypt the + file content. diff --git a/fs/crypto/keysetup_v1.c b/fs/crypto/keysetup_v1.c index 59ffa3c64324..0dc04c55ad55 100644 --- a/fs/crypto/keysetup_v1.c +++ b/fs/crypto/keysetup_v1.c @@ -302,7 +302,7 @@ static int setup_v1_file_key_direct(struct fscrypt_info *ci, static int setup_v1_file_key_derived(struct fscrypt_info *ci, const u8 *raw_master_key) { - u8 *derived_key; + u8 *derived_key = NULL; int err; int i; union { @@ -334,7 +334,21 @@ static int setup_v1_file_key_derived(struct fscrypt_info *ci, ci->ci_hashed_ino = siphash_1u64(ci->ci_inode->i_ino, &ino_hash_key.k); } + +#if IS_ENABLED(CONFIG_ENABLE_LEGACY_PFK) + derived_key = kmalloc(ci->ci_mode->keysize, GFP_NOFS); + if (!derived_key) + return -ENOMEM; + + err = derive_key_aes(raw_master_key, ci->ci_nonce, + derived_key, ci->ci_mode->keysize); + if (err) + goto out; + + memcpy(key_new.bytes, derived_key, ci->ci_mode->keysize); +#else memcpy(key_new.bytes, raw_master_key, ci->ci_mode->keysize); +#endif for (i = 0; i < ARRAY_SIZE(key_new.words); i++) __cpu_to_be32s(&key_new.words[i]); @@ -344,6 +358,9 @@ static int setup_v1_file_key_derived(struct fscrypt_info *ci, ci->ci_mode->keysize, false, ci); + if (derived_key) + kzfree(derived_key); + return err; } /* @@ -361,7 +378,9 @@ static int setup_v1_file_key_derived(struct fscrypt_info *ci, err = fscrypt_set_per_file_enc_key(ci, derived_key); out: - kzfree(derived_key); + if (derived_key) + kzfree(derived_key); + return err; } diff --git a/fs/namespace.c b/fs/namespace.c index b55e2e846656..e876362ac9c3 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -1378,7 +1378,7 @@ static void namespace_unlock(void) if (likely(hlist_empty(&head))) return; - synchronize_rcu_expedited(); + synchronize_rcu(); group_pin_kill(&head); } diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index 2e8ba4ca0908..b4a6e797dea4 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -1686,7 +1686,7 @@ static void proc_reclaim_notify(unsigned long pid, void *rp) } int reclaim_address_space(struct address_space *mapping, - struct reclaim_param *rp, struct vm_area_struct *vma) + struct reclaim_param *rp) { struct radix_tree_iter iter; void __rcu **slot; @@ -1727,7 +1727,7 @@ int reclaim_address_space(struct address_space *mapping, } } rcu_read_unlock(); - reclaimed = reclaim_pages_from_list(&page_list, vma); + reclaimed = reclaim_pages_from_list(&page_list, NULL); rp->nr_reclaimed += reclaimed; if (rp->nr_scanned >= rp->nr_to_reclaim) @@ -1821,7 +1821,7 @@ struct reclaim_param reclaim_task_nomap(struct task_struct *task, goto out; down_read(&mm->mmap_sem); - proc_reclaim_notify(task_tgid_nr(task), (void *)&rp); + proc_reclaim_notify((unsigned long)task_pid(task), (void *)&rp); up_read(&mm->mmap_sem); mmput(mm); diff --git a/fs/pstore/inode.c b/fs/pstore/inode.c index e4f86ccab9d0..6f51c6d7b965 100644 --- a/fs/pstore/inode.c +++ b/fs/pstore/inode.c @@ -2,7 +2,6 @@ * Persistent Storage - ramfs parts. * * Copyright (C) 2010 Intel Corporation - * Copyright (C) 2020 XiaoMi, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -291,7 +290,7 @@ static const struct super_operations pstore_ops = { .show_options = pstore_show_options, }; -struct super_block *pstore_sb; +static struct super_block *pstore_sb; bool pstore_is_mounted(void) { diff --git a/gen_headers_arm.bp b/gen_headers_arm.bp index 4d800e071645..b5a546f124ac 100644 --- a/gen_headers_arm.bp +++ b/gen_headers_arm.bp @@ -975,27 +975,6 @@ gen_headers_out_arm = [ // From techpack/*/include/uapi/*/**/*.h - "linux/avtimer.h", - "linux/mfd/wcd9xxx/wcd9xxx_registers.h", - "linux/mfd/wcd9xxx/wcd9320_registers.h", - "linux/msm_audio.h", - "linux/msm_audio_aac.h", - "linux/msm_audio_ac3.h", - "linux/msm_audio_alac.h", - "linux/msm_audio_amrnb.h", - "linux/msm_audio_amrwb.h", - "linux/msm_audio_amrwbplus.h", - "linux/msm_audio_ape.h", - "linux/msm_audio_calibration.h", - "linux/msm_audio_g711.h", - "linux/msm_audio_g711_dec.h", - "linux/msm_audio_mvs.h", - "linux/msm_audio_qcp.h", - "linux/msm_audio_sbc.h", - "linux/msm_audio_voicememo.h", - "linux/msm_audio_wma.h", - "linux/msm_audio_wmapro.h", - "linux/wcd-spi-ac-params.h", "media/cam_cpas.h", "media/cam_custom.h", "media/cam_defs.h", @@ -1012,13 +991,6 @@ gen_headers_out_arm = [ "media/cam_sensor.h", "media/cam_sync.h", "media/cam_tfe.h", - "sound/audio_effects.h", - "sound/audio_slimslave.h", - "sound/devdep_params.h", - "sound/lsm_params.h", - "sound/msmcal-hwdep.h", - "sound/voice_params.h", - "sound/wcd-dsp-glink.h", ] genrule { diff --git a/gen_headers_arm64.bp b/gen_headers_arm64.bp index 5985d8013583..17616f395cf2 100644 --- a/gen_headers_arm64.bp +++ b/gen_headers_arm64.bp @@ -969,27 +969,6 @@ gen_headers_out_arm64 = [ // From techpack/*/include/uapi/*/**/*.h - "linux/avtimer.h", - "linux/mfd/wcd9xxx/wcd9xxx_registers.h", - "linux/mfd/wcd9xxx/wcd9320_registers.h", - "linux/msm_audio.h", - "linux/msm_audio_aac.h", - "linux/msm_audio_ac3.h", - "linux/msm_audio_alac.h", - "linux/msm_audio_amrnb.h", - "linux/msm_audio_amrwb.h", - "linux/msm_audio_amrwbplus.h", - "linux/msm_audio_ape.h", - "linux/msm_audio_calibration.h", - "linux/msm_audio_g711.h", - "linux/msm_audio_g711_dec.h", - "linux/msm_audio_mvs.h", - "linux/msm_audio_qcp.h", - "linux/msm_audio_sbc.h", - "linux/msm_audio_voicememo.h", - "linux/msm_audio_wma.h", - "linux/msm_audio_wmapro.h", - "linux/wcd-spi-ac-params.h", "media/cam_cpas.h", "media/cam_custom.h", "media/cam_defs.h", @@ -1006,13 +985,6 @@ gen_headers_out_arm64 = [ "media/cam_sensor.h", "media/cam_sync.h", "media/cam_tfe.h", - "sound/audio_effects.h", - "sound/audio_slimslave.h", - "sound/devdep_params.h", - "sound/lsm_params.h", - "sound/msmcal-hwdep.h", - "sound/voice_params.h", - "sound/wcd-dsp-glink.h", ] genrule { diff --git a/include/crypto/ice.h b/include/crypto/ice.h index 7c1f92e04ff7..907bbd46a433 100644 --- a/include/crypto/ice.h +++ b/include/crypto/ice.h @@ -30,7 +30,7 @@ enum ice_crpto_key_mode { ICE_CRYPTO_USE_LUT_SW_KEY = 0x3 }; -#define QCOM_ICE_TYPE_NAME_LEN 8 +#define QCOM_ICE_TYPE_NAME_LEN 12 typedef void (*ice_error_cb)(void *, u32 error); diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h index 1e0e273e1f88..fa117e11458a 100644 --- a/include/drm/drm_device.h +++ b/include/drm/drm_device.h @@ -219,8 +219,6 @@ struct drm_device { struct drm_vma_offset_manager *vma_offset_manager; /*@} */ int switch_power_state; - int doze_state; - int pre_state; /** * @fb_helper: diff --git a/include/dt-bindings/clock/mdss-12nm-pll-clk.h b/include/dt-bindings/clock/mdss-12nm-pll-clk.h new file mode 100644 index 000000000000..46bda2120382 --- /dev/null +++ b/include/dt-bindings/clock/mdss-12nm-pll-clk.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. */ + +#ifndef __MDSS_12NM_PLL_CLK_H +#define __MDSS_12NM_PLL_CLK_H + +/* DSI PLL clocks */ +#define VCO_CLK_0 0 +#define POST_DIV1_0_CLK 1 +#define POST_DIV2_0_CLK 2 +#define POST_DIV4_0_CLK 3 +#define POST_DIV8_0_CLK 4 +#define POST_DIV16_0_CLK 5 +#define POST_DIV32_0_CLK 6 +#define POST_DIV_MUX_0_CLK 7 +#define GP_DIV1_0_CLK 8 +#define GP_DIV2_0_CLK 9 +#define GP_DIV4_0_CLK 10 +#define GP_DIV8_0_CLK 11 +#define GP_DIV16_0_CLK 12 +#define GP_DIV32_0_CLK 13 +#define GP_DIV_MUX_0_CLK 14 +#define PCLK_SRC_MUX_0_CLK 15 +#define BYTE_CLK_SRC_0_CLK 16 + +#define VCO_CLK_1 17 +#define POST_DIV1_1_CLK 18 +#define POST_DIV2_1_CLK 19 +#define POST_DIV4_1_CLK 20 +#define POST_DIV8_1_CLK 21 +#define POST_DIV16_1_CLK 22 +#define POST_DIV32_1_CLK 23 +#define POST_DIV_MUX_1_CLK 24 +#define GP_DIV1_1_CLK 25 +#define GP_DIV2_1_CLK 26 +#define GP_DIV4_1_CLK 27 +#define GP_DIV8_1_CLK 28 +#define GP_DIV16_1_CLK 29 +#define GP_DIV32_1_CLK 30 +#define GP_DIV_MUX_1_CLK 31 +#define PCLK_SRC_MUX_1_CLK 32 +#define BYTE_CLK_SRC_1_CLK 33 + +#endif diff --git a/include/dt-bindings/clock/qcom,cpu-sdm.h b/include/dt-bindings/clock/qcom,cpu-sdm.h new file mode 100644 index 000000000000..638941ffbf9a --- /dev/null +++ b/include/dt-bindings/clock/qcom,cpu-sdm.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef _DT_BINDINGS_CLK_QCOM_CPU_SDM_H +#define _DT_BINDINGS_CLK_QCOM_CPU_SDM_H + +#define APCS_CPU_PLL 0 +#define APCS_MUX_C1_CLK 1 +#define APCS_MUX_CCI_CLK 2 + +#endif diff --git a/include/dt-bindings/clock/qcom,gcc-sdm429w.h b/include/dt-bindings/clock/qcom,gcc-sdm429w.h new file mode 100644 index 000000000000..a858af9cc2c2 --- /dev/null +++ b/include/dt-bindings/clock/qcom,gcc-sdm429w.h @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef _DT_BINDINGS_CLK_QCOM_GCC_SDM429W_H +#define _DT_BINDINGS_CLK_QCOM_GCC_SDM429W_H + +#define GPLL0_OUT_MAIN 0 +#define GPLL0_OUT_AUX 1 +#define GPLL0_AO_CLK_SRC 2 +#define GPLL1_OUT_MAIN 3 +#define GPLL3_OUT_MAIN 4 +#define GPLL4_OUT_MAIN 5 +#define GPLL0_AO_OUT_MAIN 6 +#define GPLL0_SLEEP_CLK_SRC 7 +#define GPLL6 8 +#define GPLL6_OUT_MAIN 9 +#define GPLL6_OUT_AUX 10 +#define APSS_AHB_CLK_SRC 11 +#define BLSP1_QUP1_I2C_APPS_CLK_SRC 12 +#define BLSP1_QUP1_SPI_APPS_CLK_SRC 13 +#define BLSP1_QUP2_I2C_APPS_CLK_SRC 14 +#define BLSP1_QUP2_SPI_APPS_CLK_SRC 15 +#define BLSP1_QUP3_I2C_APPS_CLK_SRC 16 +#define BLSP1_QUP3_SPI_APPS_CLK_SRC 17 +#define BLSP1_QUP4_I2C_APPS_CLK_SRC 18 +#define BLSP1_QUP4_SPI_APPS_CLK_SRC 19 +#define BLSP1_UART1_APPS_CLK_SRC 20 +#define BLSP1_UART2_APPS_CLK_SRC 21 +#define BLSP2_QUP1_I2C_APPS_CLK_SRC 22 +#define BLSP2_QUP1_SPI_APPS_CLK_SRC 23 +#define BLSP2_QUP2_I2C_APPS_CLK_SRC 24 +#define BLSP2_QUP2_SPI_APPS_CLK_SRC 25 +#define BLSP2_QUP3_I2C_APPS_CLK_SRC 26 +#define BLSP2_QUP3_SPI_APPS_CLK_SRC 27 +#define BLSP2_QUP4_I2C_APPS_CLK_SRC 28 +#define BLSP2_QUP4_SPI_APPS_CLK_SRC 29 +#define BLSP2_UART1_APPS_CLK_SRC 30 +#define BLSP2_UART2_APPS_CLK_SRC 31 +#define BYTE0_CLK_SRC 32 +#define BYTE1_CLK_SRC 33 +#define CAMSS_TOP_AHB_CLK_SRC 34 +#define CCI_CLK_SRC 35 +#define CPP_CLK_SRC 36 +#define CRYPTO_CLK_SRC 37 +#define CSI0_CLK_SRC 38 +#define CSI0PHYTIMER_CLK_SRC 39 +#define CSI1_CLK_SRC 40 +#define CSI1PHYTIMER_CLK_SRC 41 +#define CSI2_CLK_SRC 42 +#define ESC0_CLK_SRC 43 +#define ESC1_CLK_SRC 44 +#define GCC_BIMC_GFX_CLK 45 +#define GCC_BIMC_GPU_CLK 46 +#define GCC_BLSP1_AHB_CLK 47 +#define GCC_BLSP1_QUP1_I2C_APPS_CLK 48 +#define GCC_BLSP1_QUP1_SPI_APPS_CLK 49 +#define GCC_BLSP1_QUP2_I2C_APPS_CLK 50 +#define GCC_BLSP1_QUP2_SPI_APPS_CLK 51 +#define GCC_BLSP1_QUP3_I2C_APPS_CLK 52 +#define GCC_BLSP1_QUP3_SPI_APPS_CLK 53 +#define GCC_BLSP1_QUP4_I2C_APPS_CLK 54 +#define GCC_BLSP1_QUP4_SPI_APPS_CLK 55 +#define GCC_BLSP1_UART1_APPS_CLK 56 +#define GCC_BLSP1_UART2_APPS_CLK 57 +#define GCC_BLSP2_QUP1_I2C_APPS_CLK 58 +#define GCC_BLSP2_QUP1_SPI_APPS_CLK 59 +#define GCC_BLSP2_QUP2_I2C_APPS_CLK 60 +#define GCC_BLSP2_QUP2_SPI_APPS_CLK 61 +#define GCC_BLSP2_QUP3_I2C_APPS_CLK 62 +#define GCC_BLSP2_QUP3_SPI_APPS_CLK 63 +#define GCC_BLSP2_QUP4_I2C_APPS_CLK 64 +#define GCC_BLSP2_QUP4_SPI_APPS_CLK 65 +#define GCC_BLSP2_UART1_APPS_CLK 66 +#define GCC_BLSP2_UART2_APPS_CLK 67 +#define GCC_BLSP2_AHB_CLK 68 +#define GCC_BOOT_ROM_AHB_CLK 69 +#define GCC_CAMSS_AHB_CLK 70 +#define GCC_CAMSS_CCI_AHB_CLK 71 +#define GCC_CAMSS_CCI_CLK 72 +#define GCC_CAMSS_CPP_AHB_CLK 73 +#define GCC_CAMSS_CPP_AXI_CLK 74 +#define GCC_CAMSS_CPP_CLK 75 +#define GCC_CAMSS_CSI0_AHB_CLK 76 +#define GCC_CAMSS_CSI0_CLK 77 +#define GCC_CAMSS_CSI0PHY_CLK 78 +#define GCC_CAMSS_CSI0PIX_CLK 79 +#define GCC_CAMSS_CSI0RDI_CLK 80 +#define GCC_CAMSS_CSI1_AHB_CLK 81 +#define GCC_CAMSS_CSI1_CLK 82 +#define GCC_CAMSS_CSI1PHY_CLK 83 +#define GCC_CAMSS_CSI1PIX_CLK 84 +#define GCC_CAMSS_CSI1RDI_CLK 85 +#define GCC_CAMSS_CSI2_AHB_CLK 86 +#define GCC_CAMSS_CSI2_CLK 87 +#define GCC_CAMSS_CSI2PHY_CLK 88 +#define GCC_CAMSS_CSI2PIX_CLK 89 +#define GCC_CAMSS_CSI2RDI_CLK 90 +#define GCC_CAMSS_CSI_VFE0_CLK 91 +#define GCC_CAMSS_GP0_CLK_SRC 92 +#define GCC_CAMSS_GP1_CLK_SRC 93 +#define GCC_CAMSS_CSI_VFE1_CLK 94 +#define GCC_CAMSS_CSI0PHYTIMER_CLK 95 +#define GCC_CAMSS_CSI1PHYTIMER_CLK 96 +#define GCC_CAMSS_GP0_CLK 97 +#define GCC_CAMSS_GP1_CLK 98 +#define GCC_CAMSS_ISPIF_AHB_CLK 99 +#define GCC_CAMSS_JPEG0_CLK 100 +#define GCC_CAMSS_JPEG_AHB_CLK 101 +#define GCC_CAMSS_JPEG_AXI_CLK 102 +#define GCC_CAMSS_MCLK0_CLK 103 +#define GCC_CAMSS_MCLK1_CLK 104 +#define GCC_CAMSS_MCLK2_CLK 105 +#define GCC_CAMSS_MICRO_AHB_CLK 106 +#define GCC_CAMSS_TOP_AHB_CLK 107 +#define GCC_CAMSS_VFE0_CLK 108 +#define GCC_CAMSS_VFE1_AHB_CLK 109 +#define GCC_CAMSS_VFE1_AXI_CLK 110 +#define GCC_CAMSS_VFE1_CLK 111 +#define GCC_CAMSS_VFE_AHB_CLK 112 +#define GCC_CAMSS_VFE_AXI_CLK 113 +#define GCC_CRYPTO_AHB_CLK 114 +#define GCC_CRYPTO_AXI_CLK 115 +#define GCC_CRYPTO_CLK 116 +#define GCC_DCC_CLK 117 +#define GCC_GP1_CLK 118 +#define GCC_GP2_CLK 119 +#define GCC_GP3_CLK 120 +#define GCC_MDSS_AHB_CLK 121 +#define GCC_MDSS_AXI_CLK 122 +#define GCC_MDSS_BYTE0_CLK 123 +#define GCC_MDSS_BYTE1_CLK 124 +#define GCC_MDSS_ESC0_CLK 125 +#define GCC_MDSS_ESC1_CLK 126 +#define GCC_MDSS_MDP_CLK 127 +#define GCC_MDSS_PCLK0_CLK 128 +#define GCC_MDSS_PCLK1_CLK 129 +#define GCC_MDSS_VSYNC_CLK 130 +#define GCC_MSS_CFG_AHB_CLK 131 +#define GCC_MSS_Q6_BIMC_AXI_CLK 132 +#define GCC_OXILI_AHB_CLK 133 +#define GCC_OXILI_AON_CLK 134 +#define GFX3D_CLK_SRC 135 +#define GCC_OXILI_TIMER_CLK 136 +#define GCC_PDM2_CLK 137 +#define GCC_PDM_AHB_CLK 138 +#define GCC_PRNG_AHB_CLK 139 +#define GCC_SDCC1_AHB_CLK 140 +#define GCC_SDCC1_APPS_CLK 141 +#define GCC_SDCC1_ICE_CORE_CLK 142 +#define GCC_SDCC2_AHB_CLK 143 +#define GCC_SDCC2_APPS_CLK 144 +#define GCC_USB2A_PHY_SLEEP_CLK 145 +#define GCC_USB_HS_AHB_CLK 146 +#define GCC_USB_HS_PHY_CFG_AHB_CLK 147 +#define GCC_USB_HS_SYSTEM_CLK 148 +#define GCC_VENUS0_AHB_CLK 149 +#define GCC_VENUS0_AXI_CLK 150 +#define GCC_VENUS0_CORE0_VCODEC0_CLK 151 +#define GCC_VENUS0_VCODEC0_CLK 152 +#define GCC_XO_CLK_SRC 153 +#define GCC_OXILI_GFX3D_CLK 154 +#define GP1_CLK_SRC 155 +#define GP2_CLK_SRC 156 +#define GP3_CLK_SRC 157 +#define JPEG0_CLK_SRC 158 +#define MCLK0_CLK_SRC 159 +#define MCLK1_CLK_SRC 160 +#define MCLK2_CLK_SRC 161 +#define MDP_CLK_SRC 162 +#define MDSS_MDP_VOTE_CLK 163 +#define MDSS_ROTATOR_VOTE_CLK 164 +#define PCLK0_CLK_SRC 165 +#define PCLK1_CLK_SRC 166 +#define PDM2_CLK_SRC 167 +#define SDCC1_APPS_CLK_SRC 168 +#define SDCC1_ICE_CORE_CLK_SRC 169 +#define SDCC2_APPS_CLK_SRC 170 +#define USB_HS_SYSTEM_CLK_SRC 171 +#define VCODEC0_CLK_SRC 172 +#define VFE0_CLK_SRC 173 +#define VFE1_CLK_SRC 174 +#define VSYNC_CLK_SRC 175 +#define GCC_APSS_TCU_CLK 176 +#define GCC_CPP_TBU_CLK 177 +#define GCC_JPEG_TBU_CLK 178 +#define GCC_MDP_TBU_CLK 179 +#define GCC_SMMU_CFG_CLK 180 +#define GCC_VENUS_TBU_CLK 181 +#define GCC_VFE_TBU_CLK 182 +#define GCC_VFE1_TBU_CLK 183 +#define GCC_QDSS_DAP_CLK 184 +#define GCC_GFX_TCU_CLK 185 +#define GCC_GFX_TBU_CLK 186 +#define GCC_GTCU_AHB_CLK 187 +#define GCC_OXILI_GMEM_CLK 188 + +/* GCC resets */ +#define GCC_CAMSS_MICRO_BCR 0 +#define GCC_USB_FS_BCR 1 +#define GCC_USB_HS_BCR 2 +#define GCC_USB2_HS_PHY_ONLY_BCR 3 +#define GCC_QUSB2_PHY_BCR 4 + +#endif diff --git a/include/dt-bindings/clock/qcom,rpmcc.h b/include/dt-bindings/clock/qcom,rpmcc.h index 01a9f7d42641..74d8d89131cf 100644 --- a/include/dt-bindings/clock/qcom,rpmcc.h +++ b/include/dt-bindings/clock/qcom,rpmcc.h @@ -165,71 +165,75 @@ #define RPM_SMD_MMSSNOC_AXI_A_CLK 113 #define RPM_SMD_CPUSS_GNOC_CLK 114 #define RPM_SMD_CPUSS_GNOC_A_CLK 115 -#define PNOC_MSMBUS_CLK 116 -#define PNOC_MSMBUS_A_CLK 117 -#define PNOC_KEEPALIVE_A_CLK 118 -#define SNOC_MSMBUS_CLK 119 -#define SNOC_MSMBUS_A_CLK 120 -#define BIMC_MSMBUS_CLK 121 -#define BIMC_MSMBUS_A_CLK 122 -#define PNOC_USB_CLK 123 -#define PNOC_USB_A_CLK 124 -#define SNOC_USB_CLK 125 -#define SNOC_USB_A_CLK 126 -#define BIMC_USB_CLK 127 -#define BIMC_USB_A_CLK 128 -#define SNOC_WCNSS_A_CLK 129 -#define BIMC_WCNSS_A_CLK 130 -#define MCD_CE1_CLK 131 -#define QCEDEV_CE1_CLK 132 -#define QCRYPTO_CE1_CLK 133 -#define QSEECOM_CE1_CLK 134 -#define SCM_CE1_CLK 135 -#define CXO_SMD_OTG_CLK 136 -#define CXO_SMD_LPM_CLK 137 -#define CXO_SMD_PIL_PRONTO_CLK 138 -#define CXO_SMD_PIL_MSS_CLK 139 -#define CXO_SMD_WLAN_CLK 140 -#define CXO_SMD_PIL_LPASS_CLK 141 -#define CXO_SMD_PIL_CDSP_CLK 142 -#define CXO_DWC3_CLK 143 -#define CNOC_MSMBUS_CLK 144 -#define CNOC_MSMBUS_A_CLK 145 -#define CNOC_KEEPALIVE_A_CLK 146 -#define SNOC_KEEPALIVE_A_CLK 147 -#define CPP_MMNRT_MSMBUS_CLK 148 -#define CPP_MMNRT_MSMBUS_A_CLK 149 -#define JPEG_MMNRT_MSMBUS_CLK 150 -#define JPEG_MMNRT_MSMBUS_A_CLK 151 -#define VENUS_MMNRT_MSMBUS_CLK 152 -#define VENUS_MMNRT_MSMBUS_A_CLK 153 -#define ARM9_MMNRT_MSMBUS_CLK 154 -#define ARM9_MMNRT_MSMBUS_A_CLK 155 -#define MDP_MMRT_MSMBUS_CLK 156 -#define MDP_MMRT_MSMBUS_A_CLK 157 -#define VFE_MMRT_MSMBUS_CLK 158 -#define VFE_MMRT_MSMBUS_A_CLK 159 -#define QUP0_MSMBUS_SNOC_PERIPH_CLK 160 -#define QUP0_MSMBUS_SNOC_PERIPH_A_CLK 161 -#define QUP1_MSMBUS_SNOC_PERIPH_CLK 162 -#define QUP1_MSMBUS_SNOC_PERIPH_A_CLK 163 -#define QUP2_MSMBUS_SNOC_PERIPH_CLK 164 -#define QUP2_MSMBUS_SNOC_PERIPH_A_CLK 165 -#define DAP_MSMBUS_SNOC_PERIPH_CLK 166 -#define DAP_MSMBUS_SNOC_PERIPH_A_CLK 167 -#define SDC1_MSMBUS_SNOC_PERIPH_CLK 168 -#define SDC1_MSMBUS_SNOC_PERIPH_A_CLK 169 -#define SDC2_MSMBUS_SNOC_PERIPH_CLK 170 -#define SDC2_MSMBUS_SNOC_PERIPH_A_CLK 171 -#define CRYPTO_MSMBUS_SNOC_PERIPH_CLK 172 -#define CRYPTO_MSMBUS_SNOC_PERIPH_A_CLK 173 -#define SDC1_SLV_MSMBUS_SNOC_PERIPH_CLK 174 -#define SDC1_SLV_MSMBUS_SNOC_PERIPH_A_CLK 175 -#define SDC2_SLV_MSMBUS_SNOC_PERIPH_CLK 176 -#define SDC2_SLV_MSMBUS_SNOC_PERIPH_A_CLK 177 -#define AGGR2_NOC_MSMBUS_CLK 178 -#define AGGR2_NOC_MSMBUS_A_CLK 179 -#define AGGR2_NOC_SMMU_CLK 180 -#define AGGR2_NOC_USB_CLK 181 +#define RPM_SMD_SYSMMNOC_CLK 116 +#define RPM_SMD_SYSMMNOC_A_CLK 117 +#define PNOC_MSMBUS_CLK 118 +#define PNOC_MSMBUS_A_CLK 119 +#define PNOC_KEEPALIVE_A_CLK 120 +#define SNOC_MSMBUS_CLK 121 +#define SNOC_MSMBUS_A_CLK 122 +#define BIMC_MSMBUS_CLK 123 +#define BIMC_MSMBUS_A_CLK 124 +#define PNOC_USB_CLK 125 +#define PNOC_USB_A_CLK 126 +#define SNOC_USB_CLK 127 +#define SNOC_USB_A_CLK 128 +#define BIMC_USB_CLK 129 +#define BIMC_USB_A_CLK 130 +#define SNOC_WCNSS_A_CLK 131 +#define BIMC_WCNSS_A_CLK 132 +#define MCD_CE1_CLK 133 +#define QCEDEV_CE1_CLK 134 +#define QCRYPTO_CE1_CLK 135 +#define QSEECOM_CE1_CLK 136 +#define SCM_CE1_CLK 137 +#define CXO_SMD_OTG_CLK 138 +#define CXO_SMD_LPM_CLK 139 +#define CXO_SMD_PIL_PRONTO_CLK 140 +#define CXO_SMD_PIL_MSS_CLK 141 +#define CXO_SMD_WLAN_CLK 142 +#define CXO_SMD_PIL_LPASS_CLK 143 +#define CXO_SMD_PIL_CDSP_CLK 144 +#define CXO_DWC3_CLK 145 +#define CNOC_MSMBUS_CLK 146 +#define CNOC_MSMBUS_A_CLK 147 +#define CNOC_KEEPALIVE_A_CLK 148 +#define SNOC_KEEPALIVE_A_CLK 149 +#define CPP_MMNRT_MSMBUS_CLK 150 +#define CPP_MMNRT_MSMBUS_A_CLK 151 +#define JPEG_MMNRT_MSMBUS_CLK 152 +#define JPEG_MMNRT_MSMBUS_A_CLK 153 +#define VENUS_MMNRT_MSMBUS_CLK 154 +#define VENUS_MMNRT_MSMBUS_A_CLK 155 +#define ARM9_MMNRT_MSMBUS_CLK 156 +#define ARM9_MMNRT_MSMBUS_A_CLK 157 +#define MDP_MMRT_MSMBUS_CLK 158 +#define MDP_MMRT_MSMBUS_A_CLK 159 +#define VFE_MMRT_MSMBUS_CLK 160 +#define VFE_MMRT_MSMBUS_A_CLK 161 +#define QUP0_MSMBUS_SNOC_PERIPH_CLK 162 +#define QUP0_MSMBUS_SNOC_PERIPH_A_CLK 163 +#define QUP1_MSMBUS_SNOC_PERIPH_CLK 164 +#define QUP1_MSMBUS_SNOC_PERIPH_A_CLK 165 +#define QUP2_MSMBUS_SNOC_PERIPH_CLK 166 +#define QUP2_MSMBUS_SNOC_PERIPH_A_CLK 167 +#define DAP_MSMBUS_SNOC_PERIPH_CLK 168 +#define DAP_MSMBUS_SNOC_PERIPH_A_CLK 169 +#define SDC1_MSMBUS_SNOC_PERIPH_CLK 170 +#define SDC1_MSMBUS_SNOC_PERIPH_A_CLK 171 +#define SDC2_MSMBUS_SNOC_PERIPH_CLK 172 +#define SDC2_MSMBUS_SNOC_PERIPH_A_CLK 173 +#define CRYPTO_MSMBUS_SNOC_PERIPH_CLK 174 +#define CRYPTO_MSMBUS_SNOC_PERIPH_A_CLK 175 +#define SDC1_SLV_MSMBUS_SNOC_PERIPH_CLK 176 +#define SDC1_SLV_MSMBUS_SNOC_PERIPH_A_CLK 177 +#define SDC2_SLV_MSMBUS_SNOC_PERIPH_CLK 178 +#define SDC2_SLV_MSMBUS_SNOC_PERIPH_A_CLK 179 +#define AGGR2_NOC_MSMBUS_CLK 180 +#define AGGR2_NOC_MSMBUS_A_CLK 181 +#define AGGR2_NOC_SMMU_CLK 182 +#define AGGR2_NOC_USB_CLK 183 +#define SYSMMNOC_MSMBUS_CLK 184 +#define SYSMMNOC_MSMBUS_A_CLK 185 #endif diff --git a/include/dt-bindings/iio/qcom,spmi-vadc.h b/include/dt-bindings/iio/qcom,spmi-vadc.h index cec095f6d194..837f1246081f 100644 --- a/include/dt-bindings/iio/qcom,spmi-vadc.h +++ b/include/dt-bindings/iio/qcom,spmi-vadc.h @@ -320,7 +320,8 @@ #define ADC7_VBAT_2S_MID 0x96 /* VADC scale function index */ -#define ADC_SCALE_HW_CALIB_THERM_100K_PU_PM7 0x10 -#define ADC_SCALE_HW_CALIB_PMIC_THERM_PM7 0x11 +#define ADC_SCALE_HW_CALIB_THERM_100K_PU_PM7 0x11 +#define ADC_SCALE_HW_CALIB_PMIC_THERM_PM7 0x12 +#define ADC_SCALE_BATT_THERM_QRD_215 0x13 #endif /* _DT_BINDINGS_QCOM_SPMI_VADC_H */ diff --git a/include/linux/audit.h b/include/linux/audit.h index 2858d233ac39..9334fbef7bae 100644 --- a/include/linux/audit.h +++ b/include/linux/audit.h @@ -205,10 +205,6 @@ static inline int audit_log_task_context(struct audit_buffer *ab) static inline void audit_log_task_info(struct audit_buffer *ab, struct task_struct *tsk) { } -static inline int audit_update_lsm_rules(void) -{ - return 0; -} #define audit_enabled AUDIT_OFF #endif /* CONFIG_AUDIT */ diff --git a/include/linux/backlight.h b/include/linux/backlight.h index 65f0ffd2a85f..a5a50e784548 100644 --- a/include/linux/backlight.h +++ b/include/linux/backlight.h @@ -3,7 +3,6 @@ * Backlight Lowlevel Control Abstraction * * Copyright (C) 2003,2004 Hewlett-Packard Company - * Copyright (C) 2020 XiaoMi, Inc. * */ @@ -48,14 +47,6 @@ enum backlight_notification { BACKLIGHT_UNREGISTERED, }; -enum backlight_hbm_mode { - HBM_MODE_UN_SET, - HBM_MODE_DEFAULT = 1, - HBM_MODE_LEVEL1, //CURRENT = HBM_MODE_DEFAULT*112.5% - HBM_MODE_LEVEL2, //CURRENT = HBM_MODE_DEFAULT*125% - HBM_MODE_LEVEL_MAX -}; - struct backlight_device; struct fb_info; diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 9480ce905701..26f0a62c303c 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -708,10 +708,12 @@ struct request_queue { #define QUEUE_FLAG_SCSI_PASSTHROUGH 27 /* queue supports SCSI commands */ #define QUEUE_FLAG_QUIESCED 28 /* queue has been quiesced */ -#define QUEUE_FLAG_DEFAULT ((1 << QUEUE_FLAG_SAME_COMP) | \ +#define QUEUE_FLAG_DEFAULT ((1 << QUEUE_FLAG_IO_STAT) | \ + (1 << QUEUE_FLAG_SAME_COMP) | \ (1 << QUEUE_FLAG_ADD_RANDOM)) -#define QUEUE_FLAG_MQ_DEFAULT ((1 << QUEUE_FLAG_SAME_COMP) | \ +#define QUEUE_FLAG_MQ_DEFAULT ((1 << QUEUE_FLAG_IO_STAT) | \ + (1 << QUEUE_FLAG_SAME_COMP) | \ (1 << QUEUE_FLAG_POLL)) void blk_queue_flag_set(unsigned int flag, struct request_queue *q); diff --git a/include/linux/clk/qcom.h b/include/linux/clk/qcom.h index dc8316ea109e..3cb4648b8337 100644 --- a/include/linux/clk/qcom.h +++ b/include/linux/clk/qcom.h @@ -1,12 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2016, 2020 The Linux Foundation. All rights reserved. + * Copyright (c) 2016, 2020-2021 The Linux Foundation. All rights reserved. */ #ifndef __LINUX_CLK_QCOM_H_ #define __LINUX_CLK_QCOM_H_ #include +#include enum branch_mem_flags { CLKFLAG_RETAIN_PERIPH, @@ -17,8 +18,9 @@ enum branch_mem_flags { CLKFLAG_PERIPH_OFF_CLEAR, }; -void qcom_clk_dump(struct clk *clk, bool calltrace); -void qcom_clk_bulk_dump(int num_clks, struct clk_bulk_data *clks, +void qcom_clk_dump(struct clk *clk, struct regulator *regulator, bool calltrace); +void qcom_clk_bulk_dump(int num_clks, struct clk_bulk_data *clks, + struct regulator *regulator, bool calltrace); #endif /* __LINUX_CLK_QCOM_H_ */ diff --git a/include/linux/coresight.h b/include/linux/coresight.h index 05f83505a108..ec296134755d 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2012,2017-2018 The Linux Foundation. All rights reserved. + * Copyright (c) 2012,2017-2018,2021, The Linux Foundation. All rights reserved. */ #ifndef _LINUX_CORESIGHT_H @@ -325,8 +325,8 @@ static inline void coresight_abort(void) {} static inline void coresight_disable_reg_clk(struct coresight_device *csdev) {} static inline int coresight_enable_reg_clk(struct coresight_device *csdev) { return -EINVAL; } -static void coresight_disable_all_source_link(void) {}; -static void coresight_enable_all_source_link(void) {}; +static inline void coresight_disable_all_source_link(void) {}; +static inline void coresight_enable_all_source_link(void) {}; static inline int coresight_claim_device_unlocked(void __iomem *base) { return -EINVAL; diff --git a/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h index dab7b1e4c837..6a35863be4ec 100644 --- a/include/linux/cpu_cooling.h +++ b/include/linux/cpu_cooling.h @@ -56,8 +56,6 @@ struct thermal_cooling_device * cpufreq_platform_cooling_register(struct cpufreq_policy *policy, struct cpu_cooling_ops *ops); -void cpu_limits_set_level(unsigned int cpu, unsigned int max_freq); - /** * cpufreq_cooling_unregister - function to remove cpufreq cooling device. * @cdev: thermal cooling device pointer. diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index 9afa69469b23..12cec9cd4dc0 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -2,7 +2,6 @@ * linux/include/linux/cpufreq.h * * Copyright (C) 2001 Russell King - * Copyright (C) 2020 XiaoMi, Inc. * (C) 2002 - 2003 Dominik Brodowski * * This program is free software; you can redistribute it and/or modify @@ -431,7 +430,6 @@ static inline void cpufreq_resume(void) {} #define CPUFREQ_ADJUST (0) #define CPUFREQ_NOTIFY (1) #define CPUFREQ_INCOMPATIBLE (6) -#define CPUFREQ_THERMAL (2) #ifdef CONFIG_CPU_FREQ int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list); diff --git a/include/linux/diagchar.h b/include/linux/diagchar.h index 3b4f3d83782e..babac5692398 100644 --- a/include/linux/diagchar.h +++ b/include/linux/diagchar.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0 */ -/* Copyright (c) 2008-2020, The Linux Foundation. All rights reserved. +/* Copyright (c) 2008-2021, The Linux Foundation. All rights reserved. */ #ifndef DIAGCHAR_SHARED @@ -143,7 +143,7 @@ * a new RANGE of SSIDs to the msg_mask_tbl. */ #define MSG_MASK_TBL_CNT 26 -#define APPS_EVENT_LAST_ID 0xCCD +#define APPS_EVENT_LAST_ID 0xCED #define MSG_SSID_0 0 #define MSG_SSID_0_LAST 134 @@ -922,7 +922,7 @@ static const uint32_t msg_bld_masks_25[] = { /* LOG CODES */ static const uint32_t log_code_last_tbl[] = { 0x0, /* EQUIP ID 0 */ - 0x1CE8, /* EQUIP ID 1 */ + 0x1D1E, /* EQUIP ID 1 */ 0x0, /* EQUIP ID 2 */ 0x0, /* EQUIP ID 3 */ 0x4910, /* EQUIP ID 4 */ diff --git a/include/linux/init.h b/include/linux/init.h index 586dd187e22f..bc719c7da735 100644 --- a/include/linux/init.h +++ b/include/linux/init.h @@ -322,6 +322,8 @@ void __init parse_early_options(char *cmdline); /* Data marked not to be saved by software suspend */ #define __nosavedata __section(.data..nosave) +#define __rticdata __attribute__((section(".bss.rtic"))) + #ifdef MODULE #define __exit_p(x) x #else diff --git a/include/linux/input/qpnp-power-on.h b/include/linux/input/qpnp-power-on.h index 6b1eb81abda7..d80ab2b31d43 100644 --- a/include/linux/input/qpnp-power-on.h +++ b/include/linux/input/qpnp-power-on.h @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (c) 2012-2015, 2017-2019, The Linux Foundation. - * Copyright (C) 2020 XiaoMi, Inc. * All rights reserved. */ @@ -55,11 +54,6 @@ enum pon_restart_reason { PON_RESTART_REASON_DMVERITY_CORRUPTED = 0x04, PON_RESTART_REASON_DMVERITY_ENFORCE = 0x05, PON_RESTART_REASON_KEYS_CLEAR = 0x06, - /* 32 ~ 63 for OEMs/ODMs secific features */ - PON_RESTART_REASON_OEM_MIN = 0x20, - PON_RESTART_REASON_PANIC = 0x21, - PON_RESTART_REASON_NORMAL = 0x22, - PON_RESTART_REASON_OEM_MAX = 0x3f, }; #ifdef CONFIG_INPUT_QPNP_POWER_ON @@ -69,8 +63,6 @@ int qpnp_pon_trigger_config(enum pon_trigger_source pon_src, bool enable); int qpnp_pon_wd_config(bool enable); int qpnp_pon_set_restart_reason(enum pon_restart_reason reason); bool qpnp_pon_check_hard_reset_stored(void); -int qpnp_pon_is_lpk(void); -int qpnp_pon_is_ps_hold_reset(void); int qpnp_pon_modem_pwr_off(enum pon_power_off_type type); #else @@ -106,16 +98,6 @@ static inline bool qpnp_pon_check_hard_reset_stored(void) return false; } -static inline int qpnp_pon_is_lpk(void) -{ - return -ENODEV; -} - -static inline int qpnp_pon_is_ps_hold_reset(void) -{ - return -ENODEV; -} - static inline int qpnp_pon_modem_pwr_off(enum pon_power_off_type type) { return -ENODEV; diff --git a/include/linux/kernel.h b/include/linux/kernel.h index 5ca1666388e0..7a4e8e20ef17 100644 --- a/include/linux/kernel.h +++ b/include/linux/kernel.h @@ -686,9 +686,6 @@ do { \ * let gcc optimize the rest. */ -#ifdef CONFIG_DISABLE_TRACE_PRINTK -#define trace_printk pr_debug -#else #define trace_printk(fmt, ...) \ do { \ char _______STR[] = __stringify((__VA_ARGS__)); \ @@ -711,7 +708,6 @@ do { \ else \ __trace_printk(_THIS_IP_, fmt, ##args); \ } while (0) -#endif extern __printf(2, 3) int __trace_bprintk(unsigned long ip, const char *fmt, ...); diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 7ae9dcf89578..915330abf6e5 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -117,16 +117,8 @@ int ipv4_skb_to_auditdata(struct sk_buff *skb, int ipv6_skb_to_auditdata(struct sk_buff *skb, struct common_audit_data *ad, u8 *proto); -#ifdef CONFIG_AUDIT void common_lsm_audit(struct common_audit_data *a, void (*pre_audit)(struct audit_buffer *, void *), void (*post_audit)(struct audit_buffer *, void *)); -#else -static inline void common_lsm_audit(struct common_audit_data *a, - void (*pre_audit)(struct audit_buffer *, void *), - void (*post_audit)(struct audit_buffer *, void *)) -{ -} -#endif #endif diff --git a/include/linux/mm.h b/include/linux/mm.h index ac4819fe36c0..d7cca734feb5 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -3058,7 +3058,7 @@ extern struct reclaim_param reclaim_task_anon(struct task_struct *task, extern struct reclaim_param reclaim_task_nomap(struct task_struct *task, int nr_to_reclaim); extern int reclaim_address_space(struct address_space *mapping, - struct reclaim_param *rp, struct vm_area_struct *vma); + struct reclaim_param *rp); extern int proc_reclaim_notifier_register(struct notifier_block *nb); extern int proc_reclaim_notifier_unregister(struct notifier_block *nb); #endif diff --git a/include/linux/msm_pcie.h b/include/linux/msm_pcie.h index ee88dfbe56f0..415b60dc5cf2 100644 --- a/include/linux/msm_pcie.h +++ b/include/linux/msm_pcie.h @@ -32,6 +32,7 @@ enum msm_pcie_event { MSM_PCIE_EVENT_L1SS_TIMEOUT = BIT(3), MSM_PCIE_EVENT_DRV_CONNECT = BIT(4), MSM_PCIE_EVENT_DRV_DISCONNECT = BIT(5), + MSM_PCIE_EVENT_LINK_RECOVER = BIT(6), }; enum msm_pcie_trigger { diff --git a/include/linux/msm_smd_pkt.h b/include/linux/msm_smd_pkt.h new file mode 100644 index 000000000000..1c19ab2fef12 --- /dev/null +++ b/include/linux/msm_smd_pkt.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (c) 2010,2017,2019, 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. + * + */ +#ifndef __LINUX_MSM_SMD_PKT_H +#define __LINUX_MSM_SMD_PKT_H + +#include + +#define SMD_PKT_IOCTL_MAGIC (0xC2) + +#define SMD_PKT_IOCTL_BLOCKING_WRITE \ + _IOR(SMD_PKT_IOCTL_MAGIC, 0, unsigned int) + +#endif /* __LINUX_MSM_SMD_PKT_H */ diff --git a/include/linux/platform_data/qcom_wcnss_device.h b/include/linux/platform_data/qcom_wcnss_device.h new file mode 100644 index 000000000000..e8cc338efbaf --- /dev/null +++ b/include/linux/platform_data/qcom_wcnss_device.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2011, 2018, 2020-2021, The Linux Foundation. All rights reserved. + */ +#ifndef __QCOM_WCNSS_DEVICE__H +#define __QCOM_WCNSS_DEVICE__H + +struct qcom_wcnss_opts { + bool has_48mhz_xo; +}; + +#endif /* __QCOM_WCNSS_DEVICE__H */ diff --git a/include/linux/pm_wakeup.h b/include/linux/pm_wakeup.h index b520a92ebaee..6e6a25220c56 100644 --- a/include/linux/pm_wakeup.h +++ b/include/linux/pm_wakeup.h @@ -100,9 +100,7 @@ static inline void device_set_wakeup_path(struct device *dev) } /* drivers/base/power/wakeup.c */ -extern void wakeup_source_prepare(struct wakeup_source *ws, const char *name); extern struct wakeup_source *wakeup_source_create(const char *name); -extern void wakeup_source_drop(struct wakeup_source *ws); extern void wakeup_source_destroy(struct wakeup_source *ws); extern void wakeup_source_add(struct wakeup_source *ws); extern void wakeup_source_remove(struct wakeup_source *ws); @@ -133,14 +131,10 @@ static inline bool device_can_wakeup(struct device *dev) return dev->power.can_wakeup; } -static inline void wakeup_source_prepare(struct wakeup_source *ws, - const char *name) {} - static inline struct wakeup_source *wakeup_source_create(const char *name) { return NULL; } -static inline void wakeup_source_drop(struct wakeup_source *ws) {} static inline void wakeup_source_destroy(struct wakeup_source *ws) {} @@ -204,19 +198,6 @@ static inline void pm_wakeup_dev_event(struct device *dev, unsigned int msec, #endif /* !CONFIG_PM_SLEEP */ -static inline void wakeup_source_trash(struct wakeup_source *ws) -{ - wakeup_source_remove(ws); - wakeup_source_drop(ws); -} - -static inline void wakeup_source_init(struct wakeup_source *ws, - const char *name) -{ - wakeup_source_prepare(ws, name); - wakeup_source_add(ws); -} - static inline void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec) { return pm_wakeup_ws_event(ws, msec, false); diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 45a5424ae160..72fe67bf5efc 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -227,8 +227,6 @@ enum power_supply_property { POWER_SUPPLY_PROP_PRECHARGE_CURRENT, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, POWER_SUPPLY_PROP_CALIBRATE, - POWER_SUPPLY_PROP_OTG_ONLINE, - POWER_SUPPLY_PROP_BATTERY_ID, /* Local extensions */ POWER_SUPPLY_PROP_USB_HC, POWER_SUPPLY_PROP_USB_OTG, @@ -312,6 +310,7 @@ enum power_supply_property { POWER_SUPPLY_PROP_PD_VOLTAGE_MAX, POWER_SUPPLY_PROP_PD_VOLTAGE_MIN, POWER_SUPPLY_PROP_SDP_CURRENT_MAX, + POWER_SUPPLY_PROP_FG_RESET_CLOCK, POWER_SUPPLY_PROP_CONNECTOR_TYPE, POWER_SUPPLY_PROP_PARALLEL_BATFET_MODE, POWER_SUPPLY_PROP_PARALLEL_FCC_MAX, diff --git a/include/linux/qpnp/qpnp-revid.h b/include/linux/qpnp/qpnp-revid.h index ef2fdf14d4b6..02d7251e409c 100644 --- a/include/linux/qpnp/qpnp-revid.h +++ b/include/linux/qpnp/qpnp-revid.h @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #ifndef __QPNP_REVID @@ -364,8 +363,6 @@ struct pmic_revid_data { int tp_rev; }; -#define PMIC_STRING_MAXLENGTH 80 -extern char hq_pmic_string[PMIC_STRING_MAXLENGTH]; #ifdef CONFIG_QPNP_REVID struct pmic_revid_data *get_revid_data(struct device_node *dev_node); #else diff --git a/include/linux/regulator/cpr-regulator.h b/include/linux/regulator/cpr-regulator.h new file mode 100644 index 000000000000..c6c7a4d4226a --- /dev/null +++ b/include/linux/regulator/cpr-regulator.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __REGULATOR_CPR_REGULATOR_H__ +#define __REGULATOR_CPR_REGULATOR_H__ + +#include + +#ifdef CONFIG_REGULATOR_CPR + +int __init cpr_regulator_init(void); + +#else + +static inline int __init cpr_regulator_init(void) +{ + return -ENODEV; +} + +#endif /* CONFIG_REGULATOR_CPR */ + +#endif /* __REGULATOR_CPR_REGULATOR_H__ */ diff --git a/include/linux/sync_file.h b/include/linux/sync_file.h index a022809009ac..790ca021203a 100644 --- a/include/linux/sync_file.h +++ b/include/linux/sync_file.h @@ -34,6 +34,14 @@ */ struct sync_file { struct file *file; + /** + * @user_name: + * + * Name of the sync file provided by userspace, for merged fences. + * Otherwise generated through driver callbacks (in which case the + * entire array is 0). + */ + char user_name[32]; #ifdef CONFIG_DEBUG_FS struct list_head sync_file_list; #endif @@ -49,5 +57,6 @@ struct sync_file { struct sync_file *sync_file_create(struct dma_fence *fence); struct dma_fence *sync_file_get_fence(int fd); +char *sync_file_get_name(struct sync_file *sync_file, char *buf, int len); #endif /* _LINUX_SYNC_H */ diff --git a/include/linux/usb/msm_hsusb.h b/include/linux/usb/msm_hsusb.h new file mode 100644 index 000000000000..c88146965449 --- /dev/null +++ b/include/linux/usb/msm_hsusb.h @@ -0,0 +1,346 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* include/linux/usb/msm_hsusb.h + * + * Copyright (C) 2008 Google, Inc. + * Author: Brian Swetland + * Copyright (c) 2009-2021 The Linux Foundation. All rights reserved. + * + */ + +#ifndef __ASM_ARCH_MSM_HSUSB_H +#define __ASM_ARCH_MSM_HSUSB_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Used different VDDCX voltage values + */ +enum usb_vdd_value { + VDD_NONE = 0, + VDD_MIN, + VDD_MAX, + VDD_VAL_MAX, +}; + +/** + * Requested USB votes for NOC frequency + * + * USB_NOC_NOM_VOTE Vote for NOM set of NOC frequencies + * USB_NOC_SVS_VOTE Vote for SVS set of NOC frequencies + * + */ +enum usb_noc_mode { + USB_NOC_NOM_VOTE = 0, + USB_NOC_SVS_VOTE, + USB_NOC_NUM_VOTE, +}; + +/** + * Different states involved in USB charger detection. + * + * USB_CHG_STATE_UNDEFINED USB charger is not connected or detection + * process is not yet started. + * USB_CHG_STATE_IN_PROGRESS Charger detection in progress + * USB_CHG_STATE_WAIT_FOR_DCD Waiting for Data pins contact. + * USB_CHG_STATE_DCD_DONE Data pin contact is detected. + * USB_CHG_STATE_PRIMARY_DONE Primary detection is completed (Detects + * between SDP and DCP/CDP). + * USB_CHG_STATE_SECONDARY_DONE Secondary detection is completed (Detects + * between DCP and CDP). + * USB_CHG_STATE_DETECTED USB charger type is determined. + * USB_CHG_STATE_QUEUE_SM_WORK SM work to start/stop gadget is queued. + * + */ +enum usb_chg_state { + USB_CHG_STATE_UNDEFINED = 0, + USB_CHG_STATE_IN_PROGRESS, + USB_CHG_STATE_WAIT_FOR_DCD, + USB_CHG_STATE_DCD_DONE, + USB_CHG_STATE_PRIMARY_DONE, + USB_CHG_STATE_SECONDARY_DONE, + USB_CHG_STATE_DETECTED, + USB_CHG_STATE_QUEUE_SM_WORK, +}; + +/** + * USB charger types + * + * USB_INVALID_CHARGER Invalid USB charger. + * USB_SDP_CHARGER Standard downstream port. Refers to a downstream port + * on USB2.0 compliant host/hub. + * USB_DCP_CHARGER Dedicated charger port (AC charger/ Wall charger). + * USB_CDP_CHARGER Charging downstream port. Enumeration can happen and + * IDEV_CHG_MAX can be drawn irrespective of USB state. + * USB_NONCOMPLIANT_CHARGER A non-compliant charger pull DP and DM to specific + * voltages between 2.0-3.3v for identification. + * + */ +enum usb_chg_type { + USB_INVALID_CHARGER = 0, + USB_SDP_CHARGER, + USB_DCP_CHARGER, + USB_CDP_CHARGER, + USB_NONCOMPLIANT_CHARGER, + USB_FLOATED_CHARGER, +}; + +/** + * Maintain state for hvdcp external charger status + * DEFAULT This is used when DCP is detected + * ACTIVE This is used when ioctl is called to block LPM + * INACTIVE This is used when ioctl is called to unblock LPM + */ + +enum usb_ext_chg_status { + DEFAULT = 1, + ACTIVE, + INACTIVE, +}; + +/** + * USB ID state + */ +enum usb_id_state { + USB_ID_GROUND = 0, + USB_ID_FLOAT, +}; + +#define USB_NUM_BUS_CLOCKS 3 + +/** + * struct msm_otg: OTG driver data. Shared by HCD and DCD. + * @otg: USB OTG Transceiver structure. + * @pdata: otg device platform data. + * @irq: IRQ number assigned for HSUSB controller. + * @async_irq: IRQ number used by some controllers during low power state + * @phy_irq: IRQ number assigned for PHY to notify events like id and line + state changes. + * @pclk: clock struct of iface_clk. + * @core_clk: clock struct of core_bus_clk. + * @sleep_clk: clock struct of sleep_clk for USB PHY. + * @phy_reset_clk: clock struct of phy_reset_clk for USB PHY. This clock is + a reset only clock and resets the PHY, ULPI bridge and + CSR wrapper. + * @phy_por_clk: clock struct of phy_por_clk for USB PHY. This clock is + a reset only clock and resets only the PHY (POR). + * @phy_csr_clk: clock struct of phy_csr_clk for USB PHY. This clock is + required to access PHY CSR registers via AHB2PHY interface. + * @bus_clks: bimc/snoc/pcnoc clock struct. + * @core_reset: Reset control for core_clk + * @phy_reset: Reset control for phy_reset_clk + * @phy_por_reset: Reset control for phy_por_clk + * @default_noc_mode: default frequency for NOC clocks - SVS or NOM + * @core_clk_rate: core clk max frequency + * @regs: ioremapped register base address. + * @usb_phy_ctrl_reg: relevant PHY_CTRL_REG register base address. + * @inputs: OTG state machine inputs(Id, SessValid etc). + * @sm_work: OTG state machine work. + * @sm_work_pending: OTG state machine work is pending, queued post pm_resume + * @resume_pending: USB h/w lpm_exit pending. Done on next sm_work run + * @pm_suspended: OTG device is system(PM) suspended. + * @pm_notify: Notifier to receive system wide PM transition events. + It is used to defer wakeup events processing until + system is RESUMED. + * @in_lpm: indicates low power mode (LPM) state. + * @async_int: IRQ line on which ASYNC interrupt arrived in LPM. + * @cur_power: The amount of mA available from downstream port. + * @otg_wq: Strict order otg workqueue for OTG works (SM/ID/SUSPEND). + * @chg_work: Charger detection work. + * @chg_state: The state of charger detection process. + * @chg_type: The type of charger attached. + * @chg_detection: True if PHY is doing charger type detection. + * @bus_perf_client: Bus performance client handle to request BUS bandwidth + * @host_bus_suspend: indicates host bus suspend or not. + * @device_bus_suspend: indicates device bus suspend or not. + * @bus_clks_enabled: indicates pcnoc/snoc/bimc clocks are on or not. + * @is_ext_chg_dcp: To indicate whether charger detected by external entity + SMB hardware is DCP charger or not. + * @ext_id_irq: IRQ for ID interrupt. + * @phy_irq_pending: Gets set when PHY IRQ arrives in LPM. + * @id_state: Indicates USBID line status. + * @rm_pulldown: Indicates pulldown status on D+ and D- data lines. + * @dpdm_desc: Regulator descriptor for D+ and D- voting. + * @dpdm_rdev: Regulator class device for dpdm regulator. + * @dbg_idx: Dynamic debug buffer Index. + * @dbg_lock: Dynamic debug buffer Lock. + * @buf: Dynamic Debug Buffer. + * @max_nominal_system_clk_rate: max freq at which system clock can run in + nominal mode. + * @sdp_check: SDP detection work in case of USB_FLOAT power supply + * @notify_charger_work: Charger notification work. + * @enable_sdp_check_timer: Timer for SDP charger to check enumeration. + */ +struct msm_otg { + struct usb_phy phy; + struct msm_otg_platform_data *pdata; + struct platform_device *pdev; + int irq; + int async_irq; + int phy_irq; + struct clk *xo_clk; + struct clk *pclk; + struct clk *core_clk; + struct clk *sleep_clk; + struct clk *phy_reset_clk; + struct clk *phy_por_clk; + struct clk *phy_csr_clk; + struct clk *bus_clks[USB_NUM_BUS_CLOCKS]; + struct clk *phy_ref_clk; + struct reset_control *core_reset; + struct reset_control *phy_reset; + struct reset_control *phy_por_reset; + long core_clk_rate; + long core_clk_svs_rate; + long core_clk_nominal_rate; + enum usb_noc_mode default_noc_mode; + struct resource *io_res; + void __iomem *regs; + void __iomem *phy_csr_regs; + void __iomem *usb_phy_ctrl_reg; +#define ID 0 +#define B_SESS_VLD 1 +#define A_BUS_SUSPEND 14 + unsigned long inputs; + struct work_struct sm_work; + bool sm_work_pending; + bool resume_pending; + atomic_t pm_suspended; + struct notifier_block pm_notify; + atomic_t in_lpm; + bool err_event_seen; + int async_int; + unsigned int cur_power; + struct workqueue_struct *otg_wq; + struct delayed_work chg_work; + struct delayed_work id_status_work; + enum usb_chg_state chg_state; + enum usb_chg_type chg_type; + bool chg_detection; + unsigned int dcd_time; + unsigned long caps; + uint32_t bus_perf_client; + bool host_bus_suspend; + bool device_bus_suspend; + bool bus_clks_enabled; + /* + * Allowing PHY power collpase turns off the HSUSB 3.3v and 1.8v + * analog regulators while going to low power mode. + * Currently only 28nm PHY has the support to allowing PHY + * power collapse since it doesn't have leakage currents while + * turning off the power rails. + */ +#define ALLOW_PHY_POWER_COLLAPSE BIT(0) + /* + * Allow PHY RETENTION mode before turning off the digital + * voltage regulator(VDDCX). + */ +#define ALLOW_PHY_RETENTION BIT(1) + /* + * Allow putting the core in Low Power mode, when + * USB bus is suspended but cable is connected. + */ +#define ALLOW_LPM_ON_DEV_SUSPEND BIT(2) + /* + * Allowing PHY regulators LPM puts the HSUSB 3.3v and 1.8v + * analog regulators into LPM while going to USB low power mode. + */ +#define ALLOW_PHY_REGULATORS_LPM BIT(3) + /* + * Allow PHY RETENTION mode before turning off the digital + * voltage regulator(VDDCX) during host mode. + */ +#define ALLOW_HOST_PHY_RETENTION BIT(4) + /* + * Allow VDD minimization without putting PHY into retention + * for fixing PHY current leakage issue when LDOs ar turned off. + */ +#define ALLOW_VDD_MIN_WITH_RETENTION_DISABLED BIT(5) + + /* + * PHY can keep D+ pull-up during peripheral bus suspend and + * D+/D- pull-down during host bus suspend without any + * re-work. This is possible only when PHY DVDD is supplied + * by a PMIC LDO (unlike VDDCX/VDDMX). + */ +#define ALLOW_BUS_SUSPEND_WITHOUT_REWORK BIT(6) + unsigned long lpm_flags; +#define PHY_PWR_COLLAPSED BIT(0) +#define PHY_RETENTIONED BIT(1) +#define XO_SHUTDOWN BIT(2) +#define CLOCKS_DOWN BIT(3) +#define PHY_REGULATORS_LPM BIT(4) + int reset_counter; + unsigned int online; + + dev_t ext_chg_dev; + struct pinctrl *phy_pinctrl; + bool is_ext_chg_dcp; + struct qpnp_vadc_chip *vadc_dev; + int ext_id_irq; + bool phy_irq_pending; + enum usb_id_state id_state; + bool rm_pulldown; + struct regulator_desc dpdm_rdesc; + struct regulator_dev *dpdm_rdev; +/* Maximum debug message length */ +#define DEBUG_MSG_LEN 128UL +/* Maximum number of messages */ +#define DEBUG_MAX_MSG 256UL + unsigned int dbg_idx; + rwlock_t dbg_lock; + + char (buf[DEBUG_MAX_MSG])[DEBUG_MSG_LEN]; /* buffer */ + unsigned int vbus_state; + unsigned int usb_irq_count; + int pm_qos_latency; + unsigned int notify_current_mA; + struct pm_qos_request pm_qos_req_dma; + struct delayed_work perf_vote_work; + struct delayed_work sdp_check; + struct work_struct notify_charger_work; + bool enable_sdp_check_timer; +}; + +struct ci13xxx_platform_data { + u8 usb_core_id; + int *tlmm_init_seq; + int tlmm_seq_count; + /* + * value of 2^(log2_itc-1) will be used as the interrupt threshold + * (ITC), when log2_itc is between 1 to 7. + */ + int log2_itc; + bool l1_supported; + bool enable_ahb2ahb_bypass; + bool enable_streaming; + bool enable_axi_prefetch; +}; + +#ifdef CONFIG_USB_BAM +void msm_bam_set_usb_host_dev(struct device *dev); +int msm_do_bam_disable_enable(enum usb_ctrl ctrl); +#else +static inline void msm_bam_set_usb_host_dev(struct device *dev) {} +static inline int msm_do_bam_disable_enable(enum usb_ctrl ctrl) +{ return true; } +#endif +#ifdef CONFIG_USB_CI13XXX_MSM +void msm_hw_soft_reset(void); +#else +static inline void msm_hw_soft_reset(void) +{ +} +#endif + +#endif diff --git a/include/linux/usb/msm_hsusb_hw.h b/include/linux/usb/msm_hsusb_hw.h new file mode 100644 index 000000000000..4d349ddda2a0 --- /dev/null +++ b/include/linux/usb/msm_hsusb_hw.h @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2007 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __LINUX_USB_GADGET_MSM72K_UDC_H__ +#define __LINUX_USB_GADGET_MSM72K_UDC_H__ + +/* USB phy selector - in TCSR address range */ +#define USB2_PHY_SEL 0xfd4ab000 + +#define USB_AHBBURST (MSM_USB_BASE + 0x0090) +#define USB_AHBMODE (MSM_USB_BASE + 0x0098) +#define USB_GENCONFIG (MSM_USB_BASE + 0x009C) +#define USB_GENCONFIG_2 (MSM_USB_BASE + 0x00a0) +#define USB_HS_GPTIMER_BASE (MSM_USB_BASE + 0x80) +#define ULPI_TX_PKT_EN_CLR_FIX BIT(19) + +#define USB_CAPLENGTH (MSM_USB_BASE + 0x0100) /* 8 bit */ +#define USB_HS_APF_CTRL (MSM_USB_BASE + 0x0380) + +#define APF_CTRL_EN BIT(0) + +#define USB_USBCMD (MSM_USB_BASE + 0x0140) +#define USB_USBSTS (MSM_USB_BASE + 0x0144) +#define USB_PORTSC (MSM_USB_BASE + 0x0184) +#define USB_OTGSC (MSM_USB_BASE + 0x01A4) +#define USB_USBMODE (MSM_USB_BASE + 0x01A8) +#define USB_PHY_CTRL (MSM_USB_BASE + 0x0240) +#define USB_PHY_CTRL2 (MSM_USB_BASE + 0x0278) + +#define GENCONFIG_2_SESS_VLD_CTRL_EN BIT(7) +#define GENCONFIG_2_LINESTATE_DIFF_WAKEUP_EN BIT(12) +#define GENCONFIG_2_SYS_CLK_HOST_DEV_GATE_EN BIT(13) +#define GENCONFIG_2_DPSE_DMSE_HV_INTR_EN BIT(15) +#define USBCMD_SESS_VLD_CTRL BIT(25) + +#define USBCMD_RESET 2 +#define USB_USBINTR (MSM_USB_BASE + 0x0148) +#define USB_FRINDEX (MSM_USB_BASE + 0x014C) + +#define AHB2AHB_BYPASS BIT(31) +#define AHB2AHB_BYPASS_BIT_MASK BIT(31) +#define AHB2AHB_BYPASS_CLEAR (0 << 31) +#define USB_L1_EP_CTRL (MSM_USB_BASE + 0x0250) +#define USB_L1_CONFIG (MSM_USB_BASE + 0x0254) + +#define L1_CONFIG_LPM_EN BIT(4) +#define L1_CONFIG_REMOTE_WAKEUP BIT(5) +#define L1_CONFIG_GATE_SYS_CLK BIT(7) +#define L1_CONFIG_PHY_LPM BIT(10) +#define L1_CONFIG_PLL BIT(11) + +#define PORTSC_PHCD (1 << 23) /* phy suspend mode */ +#define PORTSC_PTS_MASK (3 << 30) +#define PORTSC_PTS_ULPI (2 << 30) +#define PORTSC_PTS_SERIAL (3 << 30) +#define PORTSC_LS (3 << 10) +#define PORTSC_LS_DM (1 << 10) +#define PORTSC_CCS (1 << 0) + +#define USB_ULPI_VIEWPORT (MSM_USB_BASE + 0x0170) +#define ULPI_RUN (1 << 30) +#define ULPI_WRITE (1 << 29) +#define ULPI_READ (0 << 29) +#define ULPI_SYNC_STATE (1 << 27) +#define ULPI_ADDR(n) (((n) & 255) << 16) +#define ULPI_DATA(n) ((n) & 255) +#define ULPI_DATA_READ(n) (((n) >> 8) & 255) + +#define GENCONFIG_BAM_DISABLE (1 << 13) +#define GENCONFIG_TXFIFO_IDLE_FORCE_DISABLE (1 << 4) +#define GENCONFIG_ULPI_SERIAL_EN (1 << 5) + +/* synopsys 28nm phy registers */ +#define ULPI_PWR_CLK_MNG_REG 0x88 +#define OTG_COMP_DISABLE BIT(0) + +#define ULPI_MISC_A 0x96 +#define ULPI_MISC_A_VBUSVLDEXTSEL BIT(1) +#define ULPI_MISC_A_VBUSVLDEXT BIT(0) + +#define ASYNC_INTR_CTRL (1 << 29) /* Enable async interrupt */ +#define ULPI_STP_CTRL (1 << 30) /* Block communication with PHY */ +#define PHY_RETEN (1 << 1) /* PHY retention enable/disable */ +#define PHY_IDHV_INTEN (1 << 8) /* PHY ID HV interrupt */ +#define PHY_OTGSESSVLDHV_INTEN (1 << 9) /* PHY Session Valid HV int. */ +#define PHY_CLAMP_DPDMSE_EN (1 << 21) /* PHY mpm DP DM clamp enable */ +#define PHY_POR_BIT_MASK BIT(0) +#define PHY_POR_ASSERT (1 << 0) /* USB2 28nm PHY POR ASSERT */ +#define PHY_POR_DEASSERT (0 << 0) /* USB2 28nm PHY POR DEASSERT */ + +/* OTG definitions */ +#define OTGSC_INTSTS_MASK (0x7f << 16) +#define OTGSC_IDPU (1 << 5) +#define OTGSC_ID (1 << 8) +#define OTGSC_BSV (1 << 11) +#define OTGSC_IDIS (1 << 16) +#define OTGSC_BSVIS (1 << 19) +#define OTGSC_IDIE (1 << 24) +#define OTGSC_BSVIE (1 << 27) + +/* USB PHY CSR registers and bit definitions */ + +#define USB_PHY_CSR_PHY_UTMI_CTRL0 (MSM_USB_PHY_CSR_BASE + 0x060) +#define TERM_SEL BIT(6) +#define SLEEP_M BIT(1) +#define PORT_SELECT BIT(2) +#define OP_MODE_MASK 0x30 + +#define USB_PHY_CSR_PHY_UTMI_CTRL1 (MSM_USB_PHY_CSR_BASE + 0x064) +#define DM_PULLDOWN BIT(3) +#define DP_PULLDOWN BIT(2) +#define XCVR_SEL_MASK 0x3 + +#define USB_PHY_CSR_PHY_UTMI_CTRL2 (MSM_USB_PHY_CSR_BASE + 0x068) + +#define USB_PHY_CSR_PHY_UTMI_CTRL3 (MSM_USB_PHY_CSR_BASE + 0x06c) + +#define USB_PHY_CSR_PHY_UTMI_CTRL4 (MSM_USB_PHY_CSR_BASE + 0x070) +#define TX_VALID BIT(0) + +#define USB_PHY_CSR_PHY_CTRL_COMMON0 (MSM_USB_PHY_CSR_BASE + 0x078) +#define SIDDQ BIT(2) + +#define USB_PHY_CSR_PHY_CTRL1 (MSM_USB_PHY_CSR_BASE + 0x08C) +#define ID_HV_CLAMP_EN_N BIT(1) + +#define USB_PHY_CSR_PHY_CTRL2 (MSM_USB_PHY_CSR_BASE + 0x090) +#define USB2_SUSPEND_N BIT(6) + +#define USB_PHY_CSR_PHY_CTRL3 (MSM_USB_PHY_CSR_BASE + 0x094) +#define CLAMP_MPM_DPSE_DMSE_EN_N BIT(2) + +#define USB_PHY_CSR_PHY_CFG0 (MSM_USB_PHY_CSR_BASE + 0x0c4) + +#define USB2_PHY_USB_PHY_IRQ_CMD (MSM_USB_PHY_CSR_BASE + 0x0D0) +#define USB2_PHY_USB_PHY_INTERRUPT_SRC_STATUS (MSM_USB_PHY_CSR_BASE + 0x05C) + +#define USB2_PHY_USB_PHY_INTERRUPT_CLEAR0 (MSM_USB_PHY_CSR_BASE + 0x0DC) +#define USB2_PHY_USB_PHY_DPDM_CLEAR_MASK 0x1E + +#define USB2_PHY_USB_PHY_INTERRUPT_CLEAR1 (MSM_USB_PHY_CSR_BASE + 0x0E0) + +#define USB2_PHY_USB_PHY_INTERRUPT_MASK0 (MSM_USB_PHY_CSR_BASE + 0x0D4) +#define USB2_PHY_USB_PHY_DP_1_0_MASK BIT(4) +#define USB2_PHY_USB_PHY_DP_0_1_MASK BIT(3) +#define USB2_PHY_USB_PHY_DM_1_0_MASK BIT(2) +#define USB2_PHY_USB_PHY_DM_0_1_MASK BIT(1) + +#define USB2_PHY_USB_PHY_INTERRUPT_MASK1 (MSM_USB_PHY_CSR_BASE + 0x0D8) + +#define USB_PHY_IDDIG_1_0 BIT(7) + +#define USB_PHY_IDDIG_RISE_MASK BIT(0) +#define USB_PHY_IDDIG_FALL_MASK BIT(1) +#define USB_PHY_ID_MASK (USB_PHY_IDDIG_RISE_MASK | USB_PHY_IDDIG_FALL_MASK) + +#endif /* __LINUX_USB_GADGET_MSM72K_UDC_H__ */ diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h index b863726c7c5b..7b2a7c313391 100644 --- a/include/linux/usb/phy.h +++ b/include/linux/usb/phy.h @@ -177,6 +177,11 @@ struct usb_phy { /* reset the PHY clocks */ int (*reset)(struct usb_phy *x); int (*drive_dp_pulse)(struct usb_phy *x, unsigned int pulse_width); + + /* for notification of usb_phy_dbg_events */ + void (*dbg_event)(struct usb_phy *x, + char *event, int msg1, int msg2); + }; /* for board-specific init logic */ diff --git a/include/linux/usb_bam.h b/include/linux/usb_bam.h index 3e25cb5f5204..ad462f49bf66 100644 --- a/include/linux/usb_bam.h +++ b/include/linux/usb_bam.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2011-2017, 2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2011-2017, 2020-2021, The Linux Foundation. All rights reserved. */ #ifndef _USB_BAM_H_ @@ -313,6 +313,12 @@ int usb_bam_alloc_fifos(enum usb_ctrl cur_bam, u8 idx); int usb_bam_free_fifos(enum usb_ctrl cur_bam, u8 idx); int get_qdss_bam_info(enum usb_ctrl cur_bam, u8 idx, phys_addr_t *p_addr, u32 *bam_size); +bool msm_usb_bam_enable(enum usb_ctrl ctrl, bool bam_enable); +void msm_bam_set_hsic_host_dev(struct device *dev); +bool msm_bam_hsic_lpm_ok(void); +void msm_bam_hsic_host_notify_on_resume(void); +void msm_bam_wait_for_hsic_host_prod_granted(void); +bool msm_bam_hsic_host_pipe_empty(void); #else static inline int usb_bam_connect(enum usb_ctrl bam, u8 idx, u32 *bam_pipe_idx, unsigned long iova) @@ -409,6 +415,28 @@ static inline int get_qdss_bam_info(enum usb_ctrl cur_bam, u8 idx, { return false; } + +static inline bool msm_usb_bam_enable(enum usb_ctrl ctrl, bool bam_enable) +{ + return false; +} + +static inline void msm_bam_set_hsic_host_dev(struct device *dev) {} + +static inline bool msm_bam_hsic_lpm_ok(void) +{ + return false; +} + +static inline void msm_bam_hsic_host_notify_on_resume(void) {} + +static inline void msm_bam_wait_for_hsic_host_prod_granted(void) {} + +static inline bool msm_bam_hsic_host_pipe_empty(void) +{ + return false; +} + #endif /* CONFIG_PM */ diff --git a/include/linux/wcnss_wlan.h b/include/linux/wcnss_wlan.h new file mode 100644 index 000000000000..6be08a009157 --- /dev/null +++ b/include/linux/wcnss_wlan.h @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2011-2021, The Linux Foundation. All rights reserved. + */ +#ifndef _WCNSS_WLAN_H_ +#define _WCNSS_WLAN_H_ + +#include +#include +#include + +#define IRIS_REGULATORS 4 +#define PRONTO_REGULATORS 3 + +enum wcnss_opcode { + WCNSS_WLAN_SWITCH_OFF = 0, + WCNSS_WLAN_SWITCH_ON, +}; + +enum wcnss_hw_type { + WCNSS_RIVA_HW = 0, + WCNSS_PRONTO_HW, +}; + +struct vregs_level { + int nominal_min; + int low_power_min; + int max_voltage; + int uA_load; +}; + +struct wcnss_wlan_config { + int use_48mhz_xo; + int is_pronto_vadc; + int is_pronto_v3; + void __iomem *msm_wcnss_base; + unsigned int iris_id; + u32 vbatt; + struct vregs_level pronto_vlevel[PRONTO_REGULATORS]; + struct vregs_level iris_vlevel[IRIS_REGULATORS]; +}; + +struct bt_profile_state { + bool bt_enabled; + bool bt_ble; + bool bt_adv; + bool bt_a2dp; + bool bt_sco; +}; + +enum { + WCNSS_XO_48MHZ = 1, + WCNSS_XO_19MHZ, + WCNSS_XO_INVALID, +}; + +enum { + WCNSS_WLAN_DATA2, + WCNSS_WLAN_DATA1, + WCNSS_WLAN_DATA0, + WCNSS_WLAN_SET, + WCNSS_WLAN_CLK, + WCNSS_WLAN_MAX_GPIO, +}; + +enum wcnss_log_type { + ERR, + WARN, + INFO, + DBG, +}; + +#define WCNSS_VBATT_THRESHOLD 3500000 +#define WCNSS_VBATT_GUARD 20000 +#define WCNSS_VBATT_HIGH 3700000 +#define WCNSS_VBATT_LOW 3300000 +#define WCNSS_VBATT_INITIAL 3000000 +#define WCNSS_WLAN_IRQ_INVALID -1 +#define HAVE_WCNSS_SUSPEND_RESUME_NOTIFY 1 +#define HAVE_WCNSS_RESET_INTR 1 +#define HAVE_WCNSS_CAL_DOWNLOAD 1 +#define HAVE_CBC_DONE 1 +#define HAVE_WCNSS_RX_BUFF_COUNT 1 +#define HAVE_WCNSS_SNOC_HIGH_FREQ_VOTING 1 +#define HAVE_WCNSS_5G_DISABLE 1 +#define WLAN_MAC_ADDR_SIZE (6) +#define WLAN_RF_REG_ADDR_START_OFFSET 0x3 +#define WLAN_RF_REG_DATA_START_OFFSET 0xf +#define WLAN_RF_READ_REG_CMD 0x3 +#define WLAN_RF_WRITE_REG_CMD 0x2 +#define WLAN_RF_READ_CMD_MASK 0x3fff +#define WLAN_RF_CLK_WAIT_CYCLE 2 +#define WLAN_RF_PREPARE_CMD_DATA 5 +#define WLAN_RF_READ_DATA 6 +#define WLAN_RF_DATA_LEN 3 +#define WLAN_RF_DATA0_SHIFT 0 +#define WLAN_RF_DATA1_SHIFT 1 +#define WLAN_RF_DATA2_SHIFT 2 +#define PRONTO_PMU_OFFSET 0x1004 +#define WCNSS_PMU_CFG_GC_BUS_MUX_SEL_TOP BIT(5) + +enum wcnss_driver_state { + WCNSS_SMD_OPEN, + WCNSS_SMD_CLOSE, +}; + +struct wcnss_driver_ops { + char *name; + void *priv_data; + int (*driver_state)(void *priv, enum wcnss_driver_state state); + int (*bt_profile_state)(void *priv, struct bt_profile_state *state); +}; + +struct device *wcnss_wlan_get_device(void); +void wcnss_get_monotonic_boottime(struct timespec *ts); +struct resource *wcnss_wlan_get_memory_map(struct device *dev); +int wcnss_wlan_get_dxe_tx_irq(struct device *dev); +int wcnss_wlan_get_dxe_rx_irq(struct device *dev); +int wcnss_register_driver(struct wcnss_driver_ops *ops, void *priv); +int wcnss_unregister_driver(struct wcnss_driver_ops *ops); +void wcnss_update_bt_profile(void); +void wcnss_wlan_register_pm_ops(struct device *dev, + const struct dev_pm_ops *pm_ops); +void wcnss_wlan_unregister_pm_ops(struct device *dev, + const struct dev_pm_ops *pm_ops); +void wcnss_register_thermal_mitigation(struct device *dev, + void (*tm_notify)(struct device *dev, + int)); +void wcnss_unregister_thermal_mitigation(void (*tm_notify)(struct device *dev, + int)); +struct platform_device *wcnss_get_platform_device(void); +struct wcnss_wlan_config *wcnss_get_wlan_config(void); +void wcnss_set_iris_xo_mode(int iris_xo_mode_set); +int wcnss_wlan_power(struct device *dev, + struct wcnss_wlan_config *cfg, + enum wcnss_opcode opcode, + int *iris_xo_mode_set); +int wcnss_req_power_on_lock(char *driver_name); +int wcnss_free_power_on_lock(char *driver_name); +unsigned int wcnss_get_serial_number(void); +int wcnss_get_wlan_mac_address(char mac_addr[WLAN_MAC_ADDR_SIZE]); +void wcnss_allow_suspend(void); +void wcnss_prevent_suspend(void); +int wcnss_hardware_type(void); +void *wcnss_prealloc_get(size_t size); +int wcnss_prealloc_put(void *ptr); +void wcnss_reset_fiq(bool clk_chk_en); +void wcnss_suspend_notify(void); +void wcnss_resume_notify(void); +void wcnss_riva_log_debug_regs(void); +void wcnss_pronto_log_debug_regs(void); +int wcnss_is_hw_pronto_ver3(void); +int wcnss_device_ready(void); +bool wcnss_cbc_complete(void); +int wcnss_device_is_shutdown(void); +void wcnss_riva_dump_pmic_regs(void); +int wcnss_xo_auto_detect_enabled(void); +u32 wcnss_get_wlan_rx_buff_count(void); +int wcnss_wlan_iris_xo_mode(void); +int wcnss_wlan_dual_band_disabled(void); +void wcnss_flush_work(struct work_struct *work); +void wcnss_flush_delayed_work(struct delayed_work *dwork); +void wcnss_init_work(struct work_struct *work, void *callbackptr); +void wcnss_init_delayed_work(struct delayed_work *dwork, void *callbackptr); +int wcnss_get_iris_name(char *iris_version); +void wcnss_dump_stack(struct task_struct *task); +void wcnss_snoc_vote(bool clk_chk_en); +int wcnss_parse_voltage_regulator(struct wcnss_wlan_config *wlan_config, + struct device *dev); +void wcnss_log(enum wcnss_log_type type, const char *_fmt, ...); + +#ifdef CONFIG_WCNSS_REGISTER_DUMP_ON_BITE +void wcnss_log_debug_regs_on_bite(void); +#else +static inline void wcnss_log_debug_regs_on_bite(void) +{ +} +#endif +int wcnss_set_wlan_unsafe_channel( + u16 *unsafe_ch_list, u16 ch_count); +int wcnss_get_wlan_unsafe_channel( + u16 *unsafe_ch_list, u16 buffer_size, + u16 *ch_count); +struct rpmsg_endpoint *wcnss_open_channel(const char *name, + rpmsg_rx_cb_t cb, void *priv); +void wcnss_close_channel(struct rpmsg_endpoint *channel); +int wcnss_smd_tx(struct rpmsg_endpoint *channel, void *data, int len); +int wcnss_get_nv_name(char *nv_name); +int wcnss_is_sw_pta_enabled(void); +#define wcnss_wlan_get_drvdata(dev) dev_get_drvdata(dev) +#define wcnss_wlan_set_drvdata(dev, data) dev_set_drvdata((dev), (data)) +/* WLAN driver uses these names */ +#define req_riva_power_on_lock(name) wcnss_req_power_on_lock(name) +#define free_riva_power_on_lock(name) wcnss_free_power_on_lock(name) + +#endif /* _WCNSS_WLAN_H_ */ diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h index c6998d5944fb..342f37479228 100644 --- a/include/linux/workqueue.h +++ b/include/linux/workqueue.h @@ -617,7 +617,7 @@ static inline void flush_scheduled_work(void) static inline bool schedule_delayed_work_on(int cpu, struct delayed_work *dwork, unsigned long delay) { - return queue_delayed_work_on(cpu, system_power_efficient_wq, dwork, delay); + return queue_delayed_work_on(cpu, system_wq, dwork, delay); } /** @@ -631,7 +631,7 @@ static inline bool schedule_delayed_work_on(int cpu, struct delayed_work *dwork, static inline bool schedule_delayed_work(struct delayed_work *dwork, unsigned long delay) { - return queue_delayed_work(system_power_efficient_wq, dwork, delay); + return queue_delayed_work(system_wq, dwork, delay); } #ifndef CONFIG_SMP diff --git a/include/media/radio-iris.h b/include/media/radio-iris.h new file mode 100644 index 000000000000..3bf21608bec6 --- /dev/null +++ b/include/media/radio-iris.h @@ -0,0 +1,307 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2011-2016 The Linux Foundation. All rights reserved. + * + * Written 2000,2001 by Maxim Krasnyansky + * + */ + +#ifndef __RADIO_IRIS_H +#define __RADIO_IRIS_H + +#include + +#include +#include +#include +#include + +#define RDS_PS_SIMPLE_OFFSET 2 +extern struct mutex fm_smd_enable; + +struct radio_hci_dev { + char name[8]; + unsigned long flags; + __u16 id; + __u8 bus; + __u8 dev_type; + __u8 dev_name[248]; + __u8 dev_class[3]; + __u8 features[8]; + __u8 commands[64]; + + unsigned int data_block_len; + unsigned long cmd_last_tx; + + struct sk_buff *sent_cmd; + + __u32 req_status; + __u32 req_result; + atomic_t cmd_cnt; + + struct tasklet_struct cmd_task; + struct tasklet_struct rx_task; + struct tasklet_struct tx_task; + + struct sk_buff_head rx_q; + struct sk_buff_head raw_q; + struct sk_buff_head cmd_q; + + struct mutex req_lock; + wait_queue_head_t req_wait_q; + + int (*open)(struct radio_hci_dev *hdev); + int (*close)(struct radio_hci_dev *hdev); + int (*flush)(struct radio_hci_dev *hdev); + int (*send)(struct sk_buff *skb); + void (*destruct)(struct radio_hci_dev *hdev); + void (*notify)(struct radio_hci_dev *hdev, unsigned int evt); + void (*close_smd)(void); +}; + +int radio_hci_register_dev(struct radio_hci_dev *hdev); +int radio_hci_unregister_dev(void); +int radio_hci_recv_frame(struct sk_buff *skb); +int radio_hci_send_cmd(struct radio_hci_dev *hdev, __u16 opcode, __u32 plen, + void *param); +void radio_hci_event_packet(struct radio_hci_dev *hdev, struct sk_buff *skb); + +#define hci_req_lock(d) mutex_lock(&d->req_lock) +#define hci_req_unlock(d) mutex_unlock(&d->req_lock) + +#undef FMDBG +#ifdef FM_DEBUG +#define FMDBG(fmt, args...) pr_info("iris_radio: " fmt, ##args) +#else +#define FMDBG(fmt, args...) +#endif + +#undef FMDERR +#define FMDERR(fmt, args...) pr_err("iris_radio: " fmt, ##args) + +/* HCI timeouts */ +#define RADIO_HCI_TIMEOUT (10000) /* 10 seconds */ + +int hci_def_data_read(struct hci_fm_def_data_rd_req *arg, + struct radio_hci_dev *hdev); +int hci_def_data_write(struct hci_fm_def_data_wr_req *arg, + struct radio_hci_dev *hdev); +int hci_fm_do_calibration(__u8 *arg, struct radio_hci_dev *hdev); +int hci_fm_do_calibration(__u8 *arg, struct radio_hci_dev *hdev); + +static inline int is_valid_tone(int tone) +{ + if ((tone >= MIN_TX_TONE_VAL) && + (tone <= MAX_TX_TONE_VAL)) + return 1; + else + return 0; +} + +static inline int is_valid_hard_mute(int hard_mute) +{ + if ((hard_mute >= MIN_HARD_MUTE_VAL) && + (hard_mute <= MAX_HARD_MUTE_VAL)) + return 1; + else + return 0; +} + +static inline int is_valid_srch_mode(int srch_mode) +{ + if ((srch_mode >= MIN_SRCH_MODE) && + (srch_mode <= MAX_SRCH_MODE)) + return 1; + else + return 0; +} + +static inline int is_valid_scan_dwell_prd(int scan_dwell_prd) +{ + if ((scan_dwell_prd >= MIN_SCAN_DWELL) && + (scan_dwell_prd <= MAX_SCAN_DWELL)) + return 1; + else + return 0; +} + +static inline int is_valid_sig_th(int sig_th) +{ + if ((sig_th >= MIN_SIG_TH) && + (sig_th <= MAX_SIG_TH)) + return 1; + else + return 0; +} + +static inline int is_valid_pty(int pty) +{ + if ((pty >= MIN_PTY) && + (pty <= MAX_PTY)) + return 1; + else + return 0; +} + +static inline int is_valid_pi(int pi) +{ + if ((pi >= MIN_PI) && + (pi <= MAX_PI)) + return 1; + else + return 0; +} + +static inline int is_valid_srch_station_cnt(int cnt) +{ + if ((cnt >= MIN_SRCH_STATIONS_CNT) && + (cnt <= MAX_SRCH_STATIONS_CNT)) + return 1; + else + return 0; +} + +static inline int is_valid_chan_spacing(int spacing) +{ + if ((spacing >= MIN_CHAN_SPACING) && + (spacing <= MAX_CHAN_SPACING)) + return 1; + else + return 0; +} + +static inline int is_valid_emphasis(int emphasis) +{ + if ((emphasis >= MIN_EMPHASIS) && + (emphasis <= MAX_EMPHASIS)) + return 1; + else + return 0; +} + +static inline int is_valid_rds_std(int rds_std) +{ + if ((rds_std >= MIN_RDS_STD) && + (rds_std <= MAX_RDS_STD)) + return 1; + else + return 0; +} + +static inline int is_valid_antenna(int antenna_type) +{ + if ((antenna_type >= MIN_ANTENNA_VAL) && + (antenna_type <= MAX_ANTENNA_VAL)) + return 1; + else + return 0; +} + +static inline int is_valid_ps_repeat_cnt(int cnt) +{ + if ((cnt >= MIN_TX_PS_REPEAT_CNT) && + (cnt <= MAX_TX_PS_REPEAT_CNT)) + return 1; + else + return 0; +} + +static inline int is_valid_soft_mute(int soft_mute) +{ + if ((soft_mute >= MIN_SOFT_MUTE) && + (soft_mute <= MAX_SOFT_MUTE)) + return 1; + else + return 0; +} + +static inline int is_valid_peek_len(int len) +{ + if ((len >= MIN_PEEK_ACCESS_LEN) && + (len <= MAX_PEEK_ACCESS_LEN)) + return 1; + else + return 0; +} + +static inline int is_valid_reset_cntr(int cntr) +{ + if ((cntr >= MIN_RESET_CNTR) && + (cntr <= MAX_RESET_CNTR)) + return 1; + else + return 0; +} + +static inline int is_valid_hlsi(int hlsi) +{ + if ((hlsi >= MIN_HLSI) && + (hlsi <= MAX_HLSI)) + return 1; + else + return 0; +} + +static inline int is_valid_notch_filter(int filter) +{ + if ((filter >= MIN_NOTCH_FILTER) && + (filter <= MAX_NOTCH_FILTER)) + return 1; + else + return 0; +} + +static inline int is_valid_intf_det_low_th(int th) +{ + if ((th >= MIN_INTF_DET_OUT_LW_TH) && + (th <= MAX_INTF_DET_OUT_LW_TH)) + return 1; + else + return 0; +} + +static inline int is_valid_intf_det_hgh_th(int th) +{ + if ((th >= MIN_INTF_DET_OUT_HG_TH) && + (th <= MAX_INTF_DET_OUT_HG_TH)) + return 1; + else + return 0; +} + +static inline int is_valid_sinr_th(int th) +{ + if ((th >= MIN_SINR_TH) && + (th <= MAX_SINR_TH)) + return 1; + else + return 0; +} + +static inline int is_valid_sinr_samples(int samples_cnt) +{ + if ((samples_cnt >= MIN_SINR_SAMPLES) && + (samples_cnt <= MAX_SINR_SAMPLES)) + return 1; + else + return 0; +} + +static inline int is_valid_fm_state(int state) +{ + if ((state >= 0) && (state < FM_MAX_NO_STATES)) + return 1; + else + return 0; +} + +static inline int is_valid_blend_value(int val) +{ + if ((val >= MIN_BLEND_HI) && (val <= MAX_BLEND_HI)) + return 1; + else + return 0; +} + +#endif + diff --git a/include/media/rc-core.h b/include/media/rc-core.h index fb77d5076968..61571773a98d 100644 --- a/include/media/rc-core.h +++ b/include/media/rc-core.h @@ -2,7 +2,6 @@ * Remote Controller core header * * Copyright (C) 2009-2010 by Mauro Carvalho Chehab - * Copyright (C) 2020 XiaoMi, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -292,20 +291,6 @@ int devm_rc_register_device(struct device *parent, struct rc_dev *dev); */ void rc_unregister_device(struct rc_dev *dev); -/** - * rc_open - Opens a RC device - * - * @rdev: pointer to struct rc_dev. - */ -int rc_open(struct rc_dev *rdev); - -/** - * rc_close - Closes a RC device - * - * @rdev: pointer to struct rc_dev. - */ -void rc_close(struct rc_dev *rdev); - void rc_repeat(struct rc_dev *dev); void rc_keydown(struct rc_dev *dev, enum rc_proto protocol, u32 scancode, u8 toggle); diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 0f72c3cf3994..30907af5c156 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5230,6 +5230,32 @@ int regulatory_set_wiphy_regd(struct wiphy *wiphy, int regulatory_set_wiphy_regd_sync_rtnl(struct wiphy *wiphy, struct ieee80211_regdomain *rd); +/** + * regulatory_hint_user - hint to the wireless core a regulatory domain + * which the driver has received from an application + * @alpha2: the ISO/IEC 3166 alpha2 the driver claims its regulatory domain + * should be in. If @rd is set this should be NULL. Note that if you + * set this to NULL you should still set rd->alpha2 to some accepted + * alpha2. + * @user_reg_hint_type: the type of user regulatory hint. + * + * Wireless drivers can use this function to hint to the wireless core + * the current regulatory domain as specified by trusted applications, + * it is the driver's responsibilty to estbalish which applications it + * trusts. + * + * The wiphy should be registered to cfg80211 prior to this call. + * For cfg80211 drivers this means you must first use wiphy_register(), + * for mac80211 drivers you must first use ieee80211_register_hw(). + * + * Drivers should check the return value, its possible you can get + * an -ENOMEM or an -EINVAL. + * + * Return: 0 on success. -ENOMEM, -EINVAL. + */ +int regulatory_hint_user(const char *alpha2, + enum nl80211_user_reg_hint_type user_reg_hint_type); + /** * wiphy_apply_custom_regulatory - apply a custom driver regulatory domain * @wiphy: the wireless device we want to process the regulatory domain on diff --git a/include/net/pkt_sched.h b/include/net/pkt_sched.h index fb8284f6df88..543b6e765060 100644 --- a/include/net/pkt_sched.h +++ b/include/net/pkt_sched.h @@ -117,7 +117,13 @@ void __qdisc_run(struct Qdisc *q); static inline void qdisc_run(struct Qdisc *q) { if (qdisc_run_begin(q)) { - __qdisc_run(q); + /* NOLOCK qdisc must check 'state' under the qdisc seqlock + * to avoid racing with dev_qdisc_reset() + */ + if (!(q->flags & TCQ_F_NOLOCK) || + likely(!test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) + __qdisc_run(q); + qdisc_run_end(q); } } diff --git a/include/soc/qcom/mpm.h b/include/soc/qcom/mpm.h index 236033511bb8..8209ebef6849 100644 --- a/include/soc/qcom/mpm.h +++ b/include/soc/qcom/mpm.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2018-2021, The Linux Foundation. All rights reserved. */ #ifndef __QCOM_MPM_H__ @@ -17,4 +17,7 @@ struct mpm_pin { extern const struct mpm_pin mpm_bengal_gic_chip_data[]; extern const struct mpm_pin mpm_scuba_gic_chip_data[]; extern const struct mpm_pin mpm_sdm660_gic_chip_data[]; +extern const struct mpm_pin mpm_msm8937_gic_chip_data[]; +extern const struct mpm_pin mpm_msm8953_gic_chip_data[]; + #endif /* __QCOM_MPM_H__ */ diff --git a/include/soc/qcom/msm_tz_smmu.h b/include/soc/qcom/msm_tz_smmu.h index 783b3cb1d35e..27f1882e88ef 100644 --- a/include/soc/qcom/msm_tz_smmu.h +++ b/include/soc/qcom/msm_tz_smmu.h @@ -1,12 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2015-2018,2021, The Linux Foundation. All rights reserved. */ #ifndef __MSM_TZ_SMMU_H__ #define __MSM_TZ_SMMU_H__ #include +#include enum tz_smmu_device_id { TZ_DEVICE_START = 0, @@ -49,6 +50,21 @@ enum tz_smmu_device_id msm_dev_to_device_id(struct device *dev); int msm_tz_set_cb_format(enum tz_smmu_device_id sec_id, int cbndx); int msm_iommu_sec_pgtbl_init(void); int register_iommu_sec_ptbl(void); +bool arm_smmu_skip_write(void __iomem *addr); +extern void *get_smmu_from_addr(struct iommu_device *iommu, void __iomem *addr); +extern void *arm_smmu_get_by_addr(void __iomem *addr); +/* Donot write to smmu global space with CONFIG_MSM_TZ_SMMU */ +#undef writel_relaxed +#undef writeq_relaxed +#define writel_relaxed(v, c) do { \ + if (!arm_smmu_skip_write(c)) \ + ((void)__raw_writel((u32)cpu_to_le32(v), (c))); \ + } while (0) + +#define writeq_relaxed(v, c) do { \ + if (!arm_smmu_skip_write(c)) \ + ((void)__raw_writeq((u64)cpu_to_le64(v), (c))); \ + } while (0) #else static inline int msm_tz_smmu_atos_start(struct device *dev, int cb_num) diff --git a/include/soc/qcom/socinfo.h b/include/soc/qcom/socinfo.h index d7f88abb5541..682380fc30f2 100644 --- a/include/soc/qcom/socinfo.h +++ b/include/soc/qcom/socinfo.h @@ -94,6 +94,12 @@ of_flat_dt_is_compatible(of_get_flat_dt_root(), "qcom,sdm439") #define early_machine_is_sdm429() \ of_flat_dt_is_compatible(of_get_flat_dt_root(), "qcom,sdm429") +#define early_machine_is_qm215() \ + of_flat_dt_is_compatible(of_get_flat_dt_root(), "qcom,qm215") +#define early_machine_is_msm8953() \ + of_flat_dt_is_compatible(of_get_flat_dt_root(), "qcom,msm8953") +#define early_machine_is_sdm450() \ + of_flat_dt_is_compatible(of_get_flat_dt_root(), "qcom,sdm450") #else #define of_board_is_sim() 0 #define of_board_is_rumi() 0 @@ -135,6 +141,9 @@ #define early_machine_is_msm8917() 0 #define early_machine_is_sdm439() 0 #define early_machine_is_sdm429() 0 +#define early_machine_is_qm215() 0 +#define early_machine_is_msm8953() 0 +#define early_machine_is_sdm450() 0 #endif #define PLATFORM_SUBTYPE_MDM 1 @@ -177,7 +186,10 @@ enum msm_cpu { MSM_CPU_8937, MSM_CPU_8917, MSM_CPU_SDM439, - MSM_CPU_SDM429 + MSM_CPU_SDM429, + MSM_CPU_QM215, + MSM_CPU_8953, + MSM_CPU_SDM450, }; struct msm_soc_info { diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index 8876c981ca2e..b63fa80a5f82 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -337,6 +337,8 @@ #define KEY_MICMUTE 248 /* Mute / unmute the microphone */ +#define KEY_SYM 249 + /* Code 255 is reserved for special needs of AT keyboard driver */ #define BTN_MISC 0x100 diff --git a/init/Kconfig b/init/Kconfig index 54b62c1c92a0..98f695e0e808 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1353,6 +1353,8 @@ config BPF menuconfig EXPERT bool "Configure standard kernel features (expert users)" + # Unhide debug options, to make the on-by-default options visible + select DEBUG_KERNEL help This option allows certain base kernel options and settings to be disabled or tweaked. This is for specialized diff --git a/kernel/Makefile b/kernel/Makefile index e1c484f94ec0..02f3b2d19b8d 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -129,7 +129,7 @@ obj-$(CONFIG_RSEQ) += rseq.o $(obj)/configs.o: $(obj)/config_data.h targets += config_data.gz -$(obj)/config_data.gz: arch/arm64/configs/vendor/citrus-stock-perf_defconfig FORCE +$(obj)/config_data.gz: $(KCONFIG_CONFIG) FORCE $(call if_changed,gzip) filechk_ikconfiggz = (echo "static const char kernel_config_data[] __used = MAGIC_START"; cat $< | scripts/bin2c; echo "MAGIC_END;") diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index 6b86b7916e0c..f8fe57d1022e 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -313,8 +313,3 @@ config ENERGY_MODEL The exact usage of the energy model is subsystem-dependent. If in doubt, say N. - -config BOEFFLA_WL_BLOCKER - bool "Boeffla generic wakelock blocker driver" - depends on PM - default y diff --git a/kernel/power/process.c b/kernel/power/process.c index 994a9c4c2e5e..d76e61606f51 100644 --- a/kernel/power/process.c +++ b/kernel/power/process.c @@ -26,7 +26,7 @@ /* * Timeout for stopping processes */ -unsigned int __read_mostly freeze_timeout_msecs = 2 * MSEC_PER_SEC; +unsigned int __read_mostly freeze_timeout_msecs = 20 * MSEC_PER_SEC; static int try_to_freeze_tasks(bool user_only) { @@ -146,7 +146,6 @@ int freeze_processes(void) pr_cont("\n"); BUG_ON(in_atomic()); -#ifndef CONFIG_ANDROID /* * Now that the whole userspace is frozen we need to disbale * the OOM killer to disallow any further interference with @@ -155,7 +154,6 @@ int freeze_processes(void) */ if (!error && !oom_killer_disable(msecs_to_jiffies(freeze_timeout_msecs))) error = -EBUSY; -#endif if (error) thaw_processes(); @@ -200,9 +198,7 @@ void thaw_processes(void) pm_freezing = false; pm_nosig_freezing = false; -#ifndef CONFIG_ANDROID oom_killer_enable(); -#endif pr_info("Restarting tasks ... "); diff --git a/kernel/power/wakelock.c b/kernel/power/wakelock.c index 10d0be057ef9..105df4dfc783 100644 --- a/kernel/power/wakelock.c +++ b/kernel/power/wakelock.c @@ -241,7 +241,7 @@ int pm_wake_lock(const char *buf) do_div(timeout_ms, NSEC_PER_MSEC); __pm_wakeup_event(wl->ws, timeout_ms); } else { - __pm_wakeup_event(wl->ws, 500); + __pm_stay_awake(wl->ws); } wakelocks_lru_most_recent(wl); diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c index a30531841894..329a13e71c68 100644 --- a/kernel/rcu/tree.c +++ b/kernel/rcu/tree.c @@ -4177,9 +4177,9 @@ void __init rcu_init(void) } /* Create workqueue for expedited GPs and for Tree SRCU. */ - rcu_gp_wq = alloc_workqueue("rcu_gp", WQ_POWER_EFFICIENT | WQ_MEM_RECLAIM, 0); + rcu_gp_wq = alloc_workqueue("rcu_gp", WQ_MEM_RECLAIM, 0); WARN_ON(!rcu_gp_wq); - rcu_par_gp_wq = alloc_workqueue("rcu_par_gp", WQ_POWER_EFFICIENT | WQ_MEM_RECLAIM, 0); + rcu_par_gp_wq = alloc_workqueue("rcu_par_gp", WQ_MEM_RECLAIM, 0); WARN_ON(!rcu_par_gp_wq); } diff --git a/kernel/sched/core_ctl.c b/kernel/sched/core_ctl.c index c7c79022f373..dc695b0db063 100644 --- a/kernel/sched/core_ctl.c +++ b/kernel/sched/core_ctl.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2014-2020, The Linux Foundation. All rights reserved. - * Copyright (C) 2020 XiaoMi, Inc. */ #define pr_fmt(fmt) "core_ctl: " fmt diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index a1c8de809088..20dcf11e090b 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -98,7 +98,7 @@ unsigned int sysctl_sched_cstate_aware = 1; * * (default SCHED_TUNABLESCALING_LOG = *(1+ilog(ncpus)) */ -enum sched_tunable_scaling sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_LINEAR; +enum sched_tunable_scaling sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_LOG; /* * Minimal preemption granularity for CPU-bound tasks: diff --git a/kernel/sysctl.c b/kernel/sysctl.c index fa85ee4ebda2..61e40b7c5d75 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -100,9 +100,6 @@ #if defined(CONFIG_SYSCTL) /* External variables not in a header file. */ -#ifdef CONFIG_USB -extern int deny_new_usb; -#endif extern int suid_dumpable; #ifdef CONFIG_COREDUMP extern int core_uses_pid; @@ -1201,17 +1198,6 @@ static struct ctl_table kern_table[] = { .extra1 = &zero, .extra2 = &two, }, -#endif -#ifdef CONFIG_USB - { - .procname = "deny_new_usb", - .data = &deny_new_usb, - .maxlen = sizeof(int), - .mode = 0644, - .proc_handler = proc_dointvec_minmax_sysadmin, - .extra1 = &zero, - .extra2 = &one, - }, #endif { .procname = "ngroups_max", diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig index b3d90c8769fd..75b31bc13823 100644 --- a/kernel/trace/Kconfig +++ b/kernel/trace/Kconfig @@ -146,20 +146,6 @@ config GENERIC_TRACER bool select TRACING -if TRACING - -config DISABLE_TRACE_PRINTK - bool "Force disable trace_printk() usage" - default y - help - When trace_printk() is used in any of the kernel source, it enables - debugging functions which are not desired for production kernel. - Enabling this option will replace trace_printk() with pr_debug(). - - If in doubt, say Y. - -endif - # # Minimum requirements an architecture has to meet for us to # be able to offer generic tracing facilities: diff --git a/mm/cma.c b/mm/cma.c index 35016b56b246..f20d9b9b68e5 100644 --- a/mm/cma.c +++ b/mm/cma.c @@ -561,7 +561,7 @@ struct page *cma_alloc(struct cma *cma, size_t count, unsigned int align, if (ret && !no_warn) { pr_err("%s: %s: alloc failed, req-size: %zu pages, ret: %d\n", - __func__, cma->name, cma->count, ret); + __func__, cma->name, count, ret); cma_debug_show_areas(cma); } diff --git a/net/core/dev.c b/net/core/dev.c index e696349f9e19..c7ee9db77b1e 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -3449,13 +3449,8 @@ static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q, qdisc_calculate_pkt_len(skb, q); if (q->flags & TCQ_F_NOLOCK) { - if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) { - __qdisc_drop(skb, &to_free); - rc = NET_XMIT_DROP; - } else { - rc = q->enqueue(skb, q, &to_free) & NET_XMIT_MASK; - qdisc_run(q); - } + rc = q->enqueue(skb, q, &to_free) & NET_XMIT_MASK; + qdisc_run(q); if (unlikely(to_free)) kfree_skb_list(to_free); diff --git a/net/netfilter/xt_qtaguid.c b/net/netfilter/xt_qtaguid.c index d261932ee595..a61f43674519 100644 --- a/net/netfilter/xt_qtaguid.c +++ b/net/netfilter/xt_qtaguid.c @@ -2424,15 +2424,20 @@ int qtaguid_untag(struct socket *el_socket, bool kernel) * At first, we want to catch user-space code that is not * opening the /dev/xt_qtaguid. */ - if (IS_ERR_OR_NULL(pqd_entry) || !sock_tag_entry->list.next) { + if (IS_ERR_OR_NULL(pqd_entry)) pr_warn_once("qtaguid: %s(): " "User space forgot to open /dev/xt_qtaguid? " "pid=%u tgid=%u sk_pid=%u, uid=%u\n", __func__, current->pid, current->tgid, sock_tag_entry->pid, from_kuid(&init_user_ns, current_fsuid())); - } else { + /* + * This check is needed because tagging from a process that + * didn’t open /dev/xt_qtaguid still adds the sock_tag_entry + * to sock_tag_tree. + */ + if (sock_tag_entry->list.next) list_del(&sock_tag_entry->list); - } + spin_unlock_bh(&uid_tag_data_tree_lock); /* * We don't free tag_ref from the utd_entry here, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 47dc496f49aa..2949944eca3c 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -2412,12 +2412,14 @@ static int nl80211_parse_chandef(struct cfg80211_registered_device *rdev, struct genl_info *info, struct cfg80211_chan_def *chandef) { + struct netlink_ext_ack *extack = info->extack; + struct nlattr **attrs = info->attrs; u32 control_freq; - if (!info->attrs[NL80211_ATTR_WIPHY_FREQ]) + if (!attrs[NL80211_ATTR_WIPHY_FREQ]) return -EINVAL; - control_freq = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_FREQ]); + control_freq = nla_get_u32(attrs[NL80211_ATTR_WIPHY_FREQ]); memset(chandef, 0, sizeof(*chandef)); @@ -2427,14 +2429,16 @@ static int nl80211_parse_chandef(struct cfg80211_registered_device *rdev, chandef->center_freq2 = 0; /* Primary channel not allowed */ - if (!chandef->chan || chandef->chan->flags & IEEE80211_CHAN_DISABLED) + if (!chandef->chan || chandef->chan->flags & IEEE80211_CHAN_DISABLED) { + NL_SET_ERR_MSG_ATTR(extack, attrs[NL80211_ATTR_WIPHY_FREQ], + "Channel is disabled"); return -EINVAL; + } - if (info->attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) { + if (attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) { enum nl80211_channel_type chantype; - chantype = nla_get_u32( - info->attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]); + chantype = nla_get_u32(attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]); switch (chantype) { case NL80211_CHAN_NO_HT: @@ -2444,29 +2448,37 @@ static int nl80211_parse_chandef(struct cfg80211_registered_device *rdev, cfg80211_chandef_create(chandef, chandef->chan, chantype); /* user input for center_freq is incorrect */ - if (info->attrs[NL80211_ATTR_CENTER_FREQ1] && - chandef->center_freq1 != nla_get_u32( - info->attrs[NL80211_ATTR_CENTER_FREQ1])) + if (attrs[NL80211_ATTR_CENTER_FREQ1] && + chandef->center_freq1 != nla_get_u32(attrs[NL80211_ATTR_CENTER_FREQ1])) { + NL_SET_ERR_MSG_ATTR(extack, + attrs[NL80211_ATTR_CENTER_FREQ1], + "bad center frequency 1"); return -EINVAL; + } /* center_freq2 must be zero */ - if (info->attrs[NL80211_ATTR_CENTER_FREQ2] && - nla_get_u32(info->attrs[NL80211_ATTR_CENTER_FREQ2])) + if (attrs[NL80211_ATTR_CENTER_FREQ2] && + nla_get_u32(attrs[NL80211_ATTR_CENTER_FREQ2])) { + NL_SET_ERR_MSG_ATTR(extack, + attrs[NL80211_ATTR_CENTER_FREQ2], + "center frequency 2 can't be used"); return -EINVAL; + } break; default: + NL_SET_ERR_MSG_ATTR(extack, + attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE], + "invalid channel type"); return -EINVAL; } - } else if (info->attrs[NL80211_ATTR_CHANNEL_WIDTH]) { + } else if (attrs[NL80211_ATTR_CHANNEL_WIDTH]) { chandef->width = - nla_get_u32(info->attrs[NL80211_ATTR_CHANNEL_WIDTH]); - if (info->attrs[NL80211_ATTR_CENTER_FREQ1]) + nla_get_u32(attrs[NL80211_ATTR_CHANNEL_WIDTH]); + if (attrs[NL80211_ATTR_CENTER_FREQ1]) chandef->center_freq1 = - nla_get_u32( - info->attrs[NL80211_ATTR_CENTER_FREQ1]); - if (info->attrs[NL80211_ATTR_CENTER_FREQ2]) + nla_get_u32(attrs[NL80211_ATTR_CENTER_FREQ1]); + if (attrs[NL80211_ATTR_CENTER_FREQ2]) chandef->center_freq2 = - nla_get_u32( - info->attrs[NL80211_ATTR_CENTER_FREQ2]); + nla_get_u32(attrs[NL80211_ATTR_CENTER_FREQ2]); } if (info->attrs[NL80211_ATTR_WIPHY_EDMG_CHANNELS]) { @@ -2481,17 +2493,23 @@ static int nl80211_parse_chandef(struct cfg80211_registered_device *rdev, chandef->edmg.channels = 0; } - if (!cfg80211_chandef_valid(chandef)) + if (!cfg80211_chandef_valid(chandef)) { + NL_SET_ERR_MSG(extack, "invalid channel definition"); return -EINVAL; + } if (!cfg80211_chandef_usable(&rdev->wiphy, chandef, - IEEE80211_CHAN_DISABLED)) + IEEE80211_CHAN_DISABLED)) { + NL_SET_ERR_MSG(extack, "(extension) channel is disabled"); return -EINVAL; + } if ((chandef->width == NL80211_CHAN_WIDTH_5 || chandef->width == NL80211_CHAN_WIDTH_10) && - !(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_5_10_MHZ)) + !(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_5_10_MHZ)) { + NL_SET_ERR_MSG(extack, "5/10 MHz not supported"); return -EINVAL; + } return 0; } diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 6d21a93a53fe..54eec91a5d2c 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -3004,6 +3004,7 @@ int regulatory_hint_user(const char *alpha2, return 0; } +EXPORT_SYMBOL(regulatory_hint_user); int regulatory_hint_indoor(bool is_indoor, u32 portid) { diff --git a/net/wireless/reg.h b/net/wireless/reg.h index 9ceeb5f3a7cb..92de47dec466 100644 --- a/net/wireless/reg.h +++ b/net/wireless/reg.h @@ -31,9 +31,6 @@ bool is_world_regdom(const char *alpha2); bool reg_supported_dfs_region(enum nl80211_dfs_regions dfs_region); enum nl80211_dfs_regions reg_get_dfs_region(struct wiphy *wiphy); -int regulatory_hint_user(const char *alpha2, - enum nl80211_user_reg_hint_type user_reg_hint_type); - /** * regulatory_hint_indoor - hint operation in indoor env. or not * @is_indoor: if true indicates that user space thinks that the diff --git a/net/wireless/util.c b/net/wireless/util.c index 1e92c8a783be..96e06c574130 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -117,11 +117,13 @@ int ieee80211_freq_khz_to_channel(u32 freq) return (freq - 2407) / 5; else if (freq >= 4910 && freq <= 4980) return (freq - 4000) / 5; - else if (freq < 5940) + else if (freq < 5925) return (freq - 5000) / 5; + else if (freq == 5935) + return 2; else if (freq <= 45000) /* DMG band lower limit */ - /* see 802.11ax D4.1 27.3.22.2 */ - return (freq - 5940) / 5; + /* see 802.11ax D6.1 27.3.23.2 */ + return (freq - 5950) / 5; else if (freq >= 58320 && freq <= 70200) return (freq - 56160) / 2160; else diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 7388886b2128..064bdab60513 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -255,9 +255,6 @@ cmd_gzip = (cat $(filter-out FORCE,$^) | gzip -n -f -9 > $@) || \ # --------------------------------------------------------------------------- DTC ?= $(objtree)/scripts/dtc/dtc -# Silence all DTC warnings by default. -DTC_FLAGS += -q - # Disable noisy checks by default ifeq ($(findstring 1,$(KBUILD_ENABLE_EXTRA_GCC_CHECKS)),) DTC_FLAGS += -Wno-unit_address_vs_reg @@ -271,9 +268,7 @@ DTC_FLAGS += -Wno-simple_bus_reg \ -Wno-graph_port \ -Wno-simple_bus_reg \ -Wno-unique_unit_address \ - -Wno-pci_device_reg \ - -Wno-avoid_default_addr_size \ - -Wno-reg_format + -Wno-pci_device_reg endif ifneq ($(findstring 2,$(KBUILD_ENABLE_EXTRA_GCC_CHECKS)),) @@ -320,11 +315,6 @@ $(obj)/%.dtb: $(src)/%.dts $(DTC) FORCE dtc-tmp = $(subst $(comma),_,$(dot-target).dts.tmp) -# mkdtimg -#---------------------------------------------------------------------------- -quiet_cmd_mkdtimg = DTBOIMG $@ -cmd_mkdtimg = python2 $(srctree)/scripts/dtc/libfdt/mkdtboimg.py create $@ --page_size=4096 $(filter-out FORCE,$^) - # cat # --------------------------------------------------------------------------- # Concatentate multiple files together diff --git a/scripts/dtc/Makefile b/scripts/dtc/Makefile index 377d05fa0cf5..28631ed596ab 100644 --- a/scripts/dtc/Makefile +++ b/scripts/dtc/Makefile @@ -13,13 +13,6 @@ dtc-objs += dtc-lexer.lex.o dtc-parser.tab.o # Source files need to get at the userspace version of libfdt_env.h to compile HOST_EXTRACFLAGS := -I$(src)/libfdt -ifeq ($(wildcard /usr/include/yaml.h),) -HOST_EXTRACFLAGS += -DNO_YAML -else -dtc-objs += yamltree.o -HOSTLDLIBS_dtc := -lyaml -endif - # Generated files need one more search path to include headers in source tree HOSTCFLAGS_dtc-lexer.lex.o := -I$(src) HOSTCFLAGS_dtc-parser.tab.o := -I$(src) diff --git a/scripts/dtc/Makefile.dtc b/scripts/dtc/Makefile.dtc index 9c467b096f03..bece49b35535 100644 --- a/scripts/dtc/Makefile.dtc +++ b/scripts/dtc/Makefile.dtc @@ -1,4 +1,3 @@ -# SPDX-License-Identifier: GPL-2.0-or-later # Makefile.dtc # # This is not a complete Makefile of itself. Instead, it is designed to @@ -15,9 +14,5 @@ DTC_SRCS = \ treesource.c \ util.c -ifneq ($(NO_YAML),1) -DTC_SRCS += yamltree.c -endif - DTC_GEN_SRCS = dtc-lexer.lex.c dtc-parser.tab.c DTC_OBJS = $(DTC_SRCS:%.c=%.o) $(DTC_GEN_SRCS:%.c=%.o) diff --git a/scripts/dtc/checks.c b/scripts/dtc/checks.c index d7986ee18012..a2cc1036c915 100644 --- a/scripts/dtc/checks.c +++ b/scripts/dtc/checks.c @@ -1,10 +1,24 @@ -// SPDX-License-Identifier: GPL-2.0-or-later /* * (C) Copyright David Gibson , IBM Corporation. 2007. + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA */ #include "dtc.h" -#include "srcpos.h" #ifdef TRACE_CHECKS #define TRACE(c, ...) \ @@ -64,56 +78,23 @@ static inline void PRINTF(5, 6) check_msg(struct check *c, struct dt_info *dti, const char *fmt, ...) { va_list ap; - char *str = NULL; - struct srcpos *pos = NULL; - char *file_str; - - if (!(c->warn && (quiet < 1)) && !(c->error && (quiet < 2))) - return; - - if (prop && prop->srcpos) - pos = prop->srcpos; - else if (node && node->srcpos) - pos = node->srcpos; - - if (pos) { - file_str = srcpos_string(pos); - xasprintf(&str, "%s", file_str); - free(file_str); - } else if (streq(dti->outname, "-")) { - xasprintf(&str, ""); - } else { - xasprintf(&str, "%s", dti->outname); - } - - xasprintf_append(&str, ": %s (%s): ", - (c->error) ? "ERROR" : "Warning", c->name); - - if (node) { - if (prop) - xasprintf_append(&str, "%s:%s: ", node->fullpath, prop->name); - else - xasprintf_append(&str, "%s: ", node->fullpath); - } - va_start(ap, fmt); - xavsprintf_append(&str, fmt, ap); - va_end(ap); - xasprintf_append(&str, "\n"); - - if (!prop && pos) { - pos = node->srcpos; - while (pos->next) { - pos = pos->next; - - file_str = srcpos_string(pos); - xasprintf_append(&str, " also defined at %s\n", file_str); - free(file_str); + if ((c->warn && (quiet < 1)) + || (c->error && (quiet < 2))) { + fprintf(stderr, "%s: %s (%s): ", + strcmp(dti->outname, "-") ? dti->outname : "", + (c->error) ? "ERROR" : "Warning", c->name); + if (node) { + fprintf(stderr, "%s", node->fullpath); + if (prop) + fprintf(stderr, ":%s", prop->name); + fputs(": ", stderr); } + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); } - - fputs(str, stderr); + va_end(ap); } #define FAIL(c, dti, node, ...) \ @@ -645,8 +626,6 @@ ERROR(path_references, fixup_path_references, NULL, &duplicate_node_names); static void fixup_omit_unused_nodes(struct check *c, struct dt_info *dti, struct node *node) { - if (generate_symbols && node->labels) - return; if (node->omit_if_unused && !node->is_referenced) delete_node(node); } @@ -931,7 +910,7 @@ static bool node_is_compatible(struct node *node, const char *compat) for (str = prop->val.val, end = str + prop->val.len; str < end; str += strnlen(str, end - str) + 1) { - if (streq(str, compat)) + if (strprefixeq(str, end - str, compat)) return true; } return false; @@ -942,8 +921,7 @@ static void check_simple_bus_bridge(struct check *c, struct dt_info *dti, struct if (node_is_compatible(node, "simple-bus")) node->bus = &simple_bus; } -WARNING(simple_bus_bridge, check_simple_bus_bridge, NULL, - &addr_size_cells, &compatible_is_string_list); +WARNING(simple_bus_bridge, check_simple_bus_bridge, NULL, &addr_size_cells); static void check_simple_bus_reg(struct check *c, struct dt_info *dti, struct node *node) { @@ -984,149 +962,6 @@ static void check_simple_bus_reg(struct check *c, struct dt_info *dti, struct no } WARNING(simple_bus_reg, check_simple_bus_reg, NULL, ®_format, &simple_bus_bridge); -static const struct bus_type i2c_bus = { - .name = "i2c-bus", -}; - -static void check_i2c_bus_bridge(struct check *c, struct dt_info *dti, struct node *node) -{ - if (strprefixeq(node->name, node->basenamelen, "i2c-bus") || - strprefixeq(node->name, node->basenamelen, "i2c-arb")) { - node->bus = &i2c_bus; - } else if (strprefixeq(node->name, node->basenamelen, "i2c")) { - struct node *child; - for_each_child(node, child) { - if (strprefixeq(child->name, node->basenamelen, "i2c-bus")) - return; - } - node->bus = &i2c_bus; - } else - return; - - if (!node->children) - return; - - if (node_addr_cells(node) != 1) - FAIL(c, dti, node, "incorrect #address-cells for I2C bus"); - if (node_size_cells(node) != 0) - FAIL(c, dti, node, "incorrect #size-cells for I2C bus"); - -} -WARNING(i2c_bus_bridge, check_i2c_bus_bridge, NULL, &addr_size_cells); - -static void check_i2c_bus_reg(struct check *c, struct dt_info *dti, struct node *node) -{ - struct property *prop; - const char *unitname = get_unitname(node); - char unit_addr[17]; - uint32_t reg = 0; - int len; - cell_t *cells = NULL; - - if (!node->parent || (node->parent->bus != &i2c_bus)) - return; - - prop = get_property(node, "reg"); - if (prop) - cells = (cell_t *)prop->val.val; - - if (!cells) { - FAIL(c, dti, node, "missing or empty reg property"); - return; - } - - reg = fdt32_to_cpu(*cells); - snprintf(unit_addr, sizeof(unit_addr), "%x", reg); - if (!streq(unitname, unit_addr)) - FAIL(c, dti, node, "I2C bus unit address format error, expected \"%s\"", - unit_addr); - - for (len = prop->val.len; len > 0; len -= 4) { - reg = fdt32_to_cpu(*(cells++)); - if (reg > 0x3ff) - FAIL_PROP(c, dti, node, prop, "I2C address must be less than 10-bits, got \"0x%x\"", - reg); - - } -} -WARNING(i2c_bus_reg, check_i2c_bus_reg, NULL, ®_format, &i2c_bus_bridge); - -static const struct bus_type spi_bus = { - .name = "spi-bus", -}; - -static void check_spi_bus_bridge(struct check *c, struct dt_info *dti, struct node *node) -{ - int spi_addr_cells = 1; - - if (strprefixeq(node->name, node->basenamelen, "spi")) { - node->bus = &spi_bus; - } else { - /* Try to detect SPI buses which don't have proper node name */ - struct node *child; - - if (node_addr_cells(node) != 1 || node_size_cells(node) != 0) - return; - - for_each_child(node, child) { - struct property *prop; - for_each_property(child, prop) { - if (strprefixeq(prop->name, 4, "spi-")) { - node->bus = &spi_bus; - break; - } - } - if (node->bus == &spi_bus) - break; - } - - if (node->bus == &spi_bus && get_property(node, "reg")) - FAIL(c, dti, node, "node name for SPI buses should be 'spi'"); - } - if (node->bus != &spi_bus || !node->children) - return; - - if (get_property(node, "spi-slave")) - spi_addr_cells = 0; - if (node_addr_cells(node) != spi_addr_cells) - FAIL(c, dti, node, "incorrect #address-cells for SPI bus"); - if (node_size_cells(node) != 0) - FAIL(c, dti, node, "incorrect #size-cells for SPI bus"); - -} -WARNING(spi_bus_bridge, check_spi_bus_bridge, NULL, &addr_size_cells); - -static void check_spi_bus_reg(struct check *c, struct dt_info *dti, struct node *node) -{ - struct property *prop; - const char *unitname = get_unitname(node); - char unit_addr[9]; - uint32_t reg = 0; - cell_t *cells = NULL; - - if (!node->parent || (node->parent->bus != &spi_bus)) - return; - - if (get_property(node->parent, "spi-slave")) - return; - - prop = get_property(node, "reg"); - if (prop) - cells = (cell_t *)prop->val.val; - - if (!cells) { - FAIL(c, dti, node, "missing or empty reg property"); - return; - } - - reg = fdt32_to_cpu(*cells); - snprintf(unit_addr, sizeof(unit_addr), "%x", reg); - if (!streq(unitname, unit_addr)) - FAIL(c, dti, node, "SPI bus unit address format error, expected \"%s\"", - unit_addr); -} -WARNING(spi_bus_reg, check_spi_bus_reg, NULL, ®_format, &spi_bus_bridge); - static void check_unit_address_format(struct check *c, struct dt_info *dti, struct node *node) { @@ -1199,24 +1034,8 @@ static void check_avoid_unnecessary_addr_size(struct check *c, struct dt_info *d } WARNING(avoid_unnecessary_addr_size, check_avoid_unnecessary_addr_size, NULL, &avoid_default_addr_size); -static bool node_is_disabled(struct node *node) -{ - struct property *prop; - - prop = get_property(node, "status"); - if (prop) { - char *str = prop->val.val; - if (streq("disabled", str)) - return true; - } - - return false; -} - -static void check_unique_unit_address_common(struct check *c, - struct dt_info *dti, - struct node *node, - bool disable_check) +static void check_unique_unit_address(struct check *c, struct dt_info *dti, + struct node *node) { struct node *childa; @@ -1233,38 +1052,18 @@ static void check_unique_unit_address_common(struct check *c, if (!strlen(addr_a)) continue; - if (disable_check && node_is_disabled(childa)) - continue; - for_each_child(node, childb) { const char *addr_b = get_unitname(childb); if (childa == childb) break; - if (disable_check && node_is_disabled(childb)) - continue; - if (streq(addr_a, addr_b)) FAIL(c, dti, childb, "duplicate unit-address (also used in node %s)", childa->fullpath); } } } - -static void check_unique_unit_address(struct check *c, struct dt_info *dti, - struct node *node) -{ - check_unique_unit_address_common(c, dti, node, false); -} WARNING(unique_unit_address, check_unique_unit_address, NULL, &avoid_default_addr_size); -static void check_unique_unit_address_if_enabled(struct check *c, struct dt_info *dti, - struct node *node) -{ - check_unique_unit_address_common(c, dti, node, true); -} -CHECK_ENTRY(unique_unit_address_if_enabled, check_unique_unit_address_if_enabled, - NULL, false, false, &avoid_default_addr_size); - static void check_obsolete_chosen_interrupt_controller(struct check *c, struct dt_info *dti, struct node *node) @@ -1565,14 +1364,10 @@ static void check_interrupts_property(struct check *c, prop = get_property(parent, "interrupt-parent"); if (prop) { phandle = propval_cell(prop); - if ((phandle == 0) || (phandle == -1)) { - /* Give up if this is an overlay with - * external references */ - if (dti->dtsflags & DTSF_PLUGIN) + /* Give up if this is an overlay with external references */ + if ((phandle == 0 || phandle == -1) && + (dti->dtsflags & DTSF_PLUGIN)) return; - FAIL_PROP(c, dti, parent, prop, "Invalid phandle"); - continue; - } irq_node = get_node_by_phandle(root, phandle); if (!irq_node) { @@ -1741,7 +1536,7 @@ static void check_graph_endpoint(struct check *c, struct dt_info *dti, return; if (!strprefixeq(node->name, node->basenamelen, "endpoint")) - FAIL(c, dti, node, "graph endpoint node name should be 'endpoint'"); + FAIL(c, dti, node, "graph endpont node name should be 'endpoint'"); check_graph_reg(c, dti, node); @@ -1787,16 +1582,9 @@ static struct check *check_table[] = { &simple_bus_bridge, &simple_bus_reg, - &i2c_bus_bridge, - &i2c_bus_reg, - - &spi_bus_bridge, - &spi_bus_reg, - &avoid_default_addr_size, &avoid_unnecessary_addr_size, &unique_unit_address, - &unique_unit_address_if_enabled, &obsolete_chosen_interrupt_controller, &chosen_node_is_root, &chosen_node_bootargs, &chosen_node_stdout_path, diff --git a/scripts/dtc/data.c b/scripts/dtc/data.c index 0a43b6de3264..aa37a16c8891 100644 --- a/scripts/dtc/data.c +++ b/scripts/dtc/data.c @@ -1,6 +1,21 @@ -// SPDX-License-Identifier: GPL-2.0-or-later /* * (C) Copyright David Gibson , IBM Corporation. 2005. + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA */ #include "dtc.h" @@ -59,8 +74,7 @@ struct data data_copy_escape_string(const char *s, int len) struct data d; char *q; - d = data_add_marker(empty_data, TYPE_STRING, NULL); - d = data_grow_for(d, len + 1); + d = data_grow_for(empty_data, len + 1); q = d.val; while (i < len) { @@ -80,7 +94,6 @@ struct data data_copy_file(FILE *f, size_t maxlen) { struct data d = empty_data; - d = data_add_marker(d, TYPE_NONE, NULL); while (!feof(f) && (d.len < maxlen)) { size_t chunksize, ret; diff --git a/scripts/dtc/dtc-lexer.l b/scripts/dtc/dtc-lexer.l index b3b7270300de..d3694d6cf202 100644 --- a/scripts/dtc/dtc-lexer.l +++ b/scripts/dtc/dtc-lexer.l @@ -1,6 +1,21 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * (C) Copyright David Gibson , IBM Corporation. 2005. + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA */ %option noyywrap nounput noinput never-interactive @@ -197,14 +212,14 @@ static void PRINTF(1, 2) lexical_error(const char *fmt, ...); <*>\&{LABEL} { /* label reference */ DPRINT("Ref: %s\n", yytext+1); yylval.labelref = xstrdup(yytext+1); - return DT_LABEL_REF; + return DT_REF; } <*>"&{/"{PATHCHAR}*\} { /* new-style path reference */ yytext[yyleng-1] = '\0'; DPRINT("Ref: %s\n", yytext+2); yylval.labelref = xstrdup(yytext+2); - return DT_PATH_REF; + return DT_REF; } [0-9a-fA-F]{2} { diff --git a/scripts/dtc/dtc-parser.y b/scripts/dtc/dtc-parser.y index 2ed4dc1f07fd..011a5b25539a 100644 --- a/scripts/dtc/dtc-parser.y +++ b/scripts/dtc/dtc-parser.y @@ -1,6 +1,21 @@ -// SPDX-License-Identifier: GPL-2.0-or-later /* * (C) Copyright David Gibson , IBM Corporation. 2005. + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA */ %{ #include @@ -55,8 +70,7 @@ extern bool treesource_error; %token DT_BYTE %token DT_STRING %token DT_LABEL -%token DT_LABEL_REF -%token DT_PATH_REF +%token DT_REF %token DT_INCBIN %type propdata @@ -69,7 +83,6 @@ extern bool treesource_error; %type bytestring %type propdef %type proplist -%type dt_ref %type devicetree %type nodedef @@ -145,8 +158,6 @@ memreserve: } ; -dt_ref: DT_LABEL_REF | DT_PATH_REF; - devicetree: '/' nodedef { @@ -156,7 +167,7 @@ devicetree: { $$ = merge_nodes($1, $3); } - | dt_ref nodedef + | DT_REF nodedef { /* * We rely on the rule being always: @@ -165,12 +176,9 @@ devicetree: */ if (!($-1 & DTSF_PLUGIN)) ERROR(&@2, "Label or path %s not found", $1); - $$ = add_orphan_node( - name_node(build_node(NULL, NULL, NULL), - ""), - $2, $1); + $$ = add_orphan_node(name_node(build_node(NULL, NULL), ""), $2, $1); } - | devicetree DT_LABEL dt_ref nodedef + | devicetree DT_LABEL DT_REF nodedef { struct node *target = get_node_by_ref($1, $3); @@ -181,7 +189,7 @@ devicetree: ERROR(&@3, "Label or path %s not found", $3); $$ = $1; } - | devicetree DT_PATH_REF nodedef + | devicetree DT_REF nodedef { /* * We rely on the rule being always: @@ -200,26 +208,7 @@ devicetree: } $$ = $1; } - | devicetree DT_LABEL_REF nodedef - { - struct node *target = get_node_by_ref($1, $2); - - if (target) { - merge_nodes(target, $3); - } else { - /* - * We rely on the rule being always: - * versioninfo plugindecl memreserves devicetree - * so $-1 is what we want (plugindecl) - */ - if ($-1 & DTSF_PLUGIN) - add_orphan_node($1, $3, $2); - else - ERROR(&@2, "Label or path %s not found", $2); - } - $$ = $1; - } - | devicetree DT_DEL_NODE dt_ref ';' + | devicetree DT_DEL_NODE DT_REF ';' { struct node *target = get_node_by_ref($1, $3); @@ -231,7 +220,7 @@ devicetree: $$ = $1; } - | devicetree DT_OMIT_NO_REF dt_ref ';' + | devicetree DT_OMIT_NO_REF DT_REF ';' { struct node *target = get_node_by_ref($1, $3); @@ -248,7 +237,7 @@ devicetree: nodedef: '{' proplist subnodes '}' ';' { - $$ = build_node($2, $3, &@$); + $$ = build_node($2, $3); } ; @@ -266,11 +255,11 @@ proplist: propdef: DT_PROPNODENAME '=' propdata ';' { - $$ = build_property($1, $3, &@$); + $$ = build_property($1, $3); } | DT_PROPNODENAME ';' { - $$ = build_property($1, empty_data, &@$); + $$ = build_property($1, empty_data); } | DT_DEL_PROP DT_PROPNODENAME ';' { @@ -296,9 +285,8 @@ propdata: { $$ = data_merge($1, $3); } - | propdataprefix dt_ref + | propdataprefix DT_REF { - $1 = data_add_marker($1, TYPE_STRING, $2); $$ = data_add_marker($1, REF_PATH, $2); } | propdataprefix DT_INCBIN '(' DT_STRING ',' integer_prim ',' integer_prim ')' @@ -352,27 +340,22 @@ arrayprefix: DT_BITS DT_LITERAL '<' { unsigned long long bits; - enum markertype type = TYPE_UINT32; bits = $2; - switch (bits) { - case 8: type = TYPE_UINT8; break; - case 16: type = TYPE_UINT16; break; - case 32: type = TYPE_UINT32; break; - case 64: type = TYPE_UINT64; break; - default: + if ((bits != 8) && (bits != 16) && + (bits != 32) && (bits != 64)) { ERROR(&@2, "Array elements must be" " 8, 16, 32 or 64-bits"); bits = 32; } - $$.data = data_add_marker(empty_data, type, NULL); + $$.data = empty_data; $$.bits = bits; } | '<' { - $$.data = data_add_marker(empty_data, TYPE_UINT32, NULL); + $$.data = empty_data; $$.bits = 32; } | arrayprefix integer_prim @@ -394,7 +377,7 @@ arrayprefix: $$.data = data_append_integer($1.data, $2, $1.bits); } - | arrayprefix dt_ref + | arrayprefix DT_REF { uint64_t val = ~0ULL >> (64 - $1.bits); @@ -516,7 +499,7 @@ integer_unary: bytestring: /* empty */ { - $$ = data_add_marker(empty_data, TYPE_UINT8, NULL); + $$ = empty_data; } | bytestring DT_BYTE { @@ -551,7 +534,7 @@ subnode: } | DT_DEL_NODE DT_PROPNODENAME ';' { - $$ = name_node(build_node_delete(&@$), $2); + $$ = name_node(build_node_delete(), $2); } | DT_OMIT_NO_REF subnode { diff --git a/scripts/dtc/dtc.c b/scripts/dtc/dtc.c index bdb3f5945699..c36994e6eac5 100644 --- a/scripts/dtc/dtc.c +++ b/scripts/dtc/dtc.c @@ -1,6 +1,21 @@ -// SPDX-License-Identifier: GPL-2.0-or-later /* * (C) Copyright David Gibson , IBM Corporation. 2005. + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA */ #include @@ -20,8 +35,6 @@ int phandle_format = PHANDLE_EPAPR; /* Use linux,phandle or phandle properties * int generate_symbols; /* enable symbols & fixup support */ int generate_fixups; /* suppress generation of fixups on symbol support */ int auto_label_aliases; /* auto generate labels -> aliases */ -int annotate; /* Level of annotation: 1 for input source location - >1 for full input source location. */ static int is_power_of_2(int x) { @@ -47,7 +60,7 @@ static void fill_fullpaths(struct node *tree, const char *prefix) /* Usage related data. */ static const char usage_synopsis[] = "dtc [options] "; -static const char usage_short_opts[] = "qI:O:o:V:d:R:S:p:a:fb:i:H:sW:E:@AThv"; +static const char usage_short_opts[] = "qI:O:o:V:d:R:S:p:a:fb:i:H:sW:E:@Ahv"; static struct option const usage_long_opts[] = { {"quiet", no_argument, NULL, 'q'}, {"in-format", a_argument, NULL, 'I'}, @@ -68,7 +81,6 @@ static struct option const usage_long_opts[] = { {"error", a_argument, NULL, 'E'}, {"symbols", no_argument, NULL, '@'}, {"auto-alias", no_argument, NULL, 'A'}, - {"annotate", no_argument, NULL, 'T'}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {NULL, no_argument, NULL, 0x0}, @@ -83,9 +95,6 @@ static const char * const usage_opts_help[] = { "\n\tOutput formats are:\n" "\t\tdts - device tree source text\n" "\t\tdtb - device tree blob\n" -#ifndef NO_YAML - "\t\tyaml - device tree encoded as YAML\n" -#endif "\t\tasm - assembler source", "\n\tBlob version to produce, defaults to "stringify(DEFAULT_FDT_VERSION)" (for dtb and asm output)", "\n\tOutput dependency file", @@ -105,7 +114,6 @@ static const char * const usage_opts_help[] = { "\n\tEnable/disable errors (prefix with \"no-\")", "\n\tEnable generation of symbols", "\n\tEnable auto-alias of labels", - "\n\tAnnotate output .dts with input source file and line (-T -T for more details)", "\n\tPrint this help and exit", "\n\tPrint version and exit", NULL, @@ -120,8 +128,6 @@ static const char *guess_type_by_name(const char *fname, const char *fallback) return fallback; if (!strcasecmp(s, ".dts")) return "dts"; - if (!strcasecmp(s, ".yaml")) - return "yaml"; if (!strcasecmp(s, ".dtb")) return "dtb"; return fallback; @@ -253,9 +259,6 @@ int main(int argc, char *argv[]) case 'A': auto_label_aliases = 1; break; - case 'T': - annotate++; - break; case 'h': usage(NULL); @@ -294,8 +297,6 @@ int main(int argc, char *argv[]) outform = "dts"; } } - if (annotate && (!streq(inform, "dts") || !streq(outform, "dts"))) - die("--annotate requires -I dts -O dts\n"); if (streq(inform, "dts")) dti = dt_from_source(arg); else if (streq(inform, "fs")) @@ -349,12 +350,6 @@ int main(int argc, char *argv[]) if (streq(outform, "dts")) { dt_to_source(outf, dti); -#ifndef NO_YAML - } else if (streq(outform, "yaml")) { - if (!streq(inform, "dts")) - die("YAML output format requires dts input format\n"); - dt_to_yaml(outf, dti); -#endif } else if (streq(outform, "dtb")) { dt_to_blob(outf, dti, outversion); } else if (streq(outform, "asm")) { diff --git a/scripts/dtc/dtc.h b/scripts/dtc/dtc.h index 6e74ecea55a3..6d667701ab6a 100644 --- a/scripts/dtc/dtc.h +++ b/scripts/dtc/dtc.h @@ -1,9 +1,24 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef DTC_H #define DTC_H /* * (C) Copyright David Gibson , IBM Corporation. 2005. + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA */ #include @@ -43,7 +58,6 @@ extern int phandle_format; /* Use linux,phandle or phandle properties */ extern int generate_symbols; /* generate symbols for nodes with labels */ extern int generate_fixups; /* generate fixups */ extern int auto_label_aliases; /* auto generate labels -> aliases */ -extern int annotate; /* annotate .dts with input source location */ #define PHANDLE_LEGACY 0x1 #define PHANDLE_EPAPR 0x2 @@ -60,17 +74,10 @@ typedef uint32_t cell_t; /* Data blobs */ enum markertype { - TYPE_NONE, REF_PHANDLE, REF_PATH, LABEL, - TYPE_UINT8, - TYPE_UINT16, - TYPE_UINT32, - TYPE_UINT64, - TYPE_STRING, }; -extern const char *markername(enum markertype markertype); struct marker { enum markertype type; @@ -94,8 +101,6 @@ struct data { for_each_marker(m) \ if ((m)->type == (t)) -size_t type_marker_length(struct marker *m); - void data_free(struct data d); struct data data_grow_for(struct data d, int xlen); @@ -144,7 +149,6 @@ struct property { struct property *next; struct label *labels; - struct srcpos *srcpos; }; struct node { @@ -164,7 +168,6 @@ struct node { struct label *labels; const struct bus_type *bus; - struct srcpos *srcpos; bool omit_if_unused, is_referenced; }; @@ -193,15 +196,13 @@ struct node { void add_label(struct label **labels, char *label); void delete_labels(struct label **labels); -struct property *build_property(char *name, struct data val, - struct srcpos *srcpos); +struct property *build_property(char *name, struct data val); struct property *build_property_delete(char *name); struct property *chain_property(struct property *first, struct property *list); struct property *reverse_properties(struct property *first); -struct node *build_node(struct property *proplist, struct node *children, - struct srcpos *srcpos); -struct node *build_node_delete(struct srcpos *srcpos); +struct node *build_node(struct property *proplist, struct node *children); +struct node *build_node_delete(void); struct node *name_node(struct node *node, char *name); struct node *omit_node_if_unused(struct node *node); struct node *reference_node(struct node *node); @@ -216,8 +217,7 @@ void add_child(struct node *parent, struct node *child); void delete_node_by_name(struct node *parent, char *name); void delete_node(struct node *node); void append_to_property(struct node *node, - char *name, const void *data, int len, - enum markertype type); + char *name, const void *data, int len); const char *get_unitname(struct node *node); struct property *get_property(struct node *node, const char *propname); @@ -290,10 +290,6 @@ struct dt_info *dt_from_blob(const char *fname); void dt_to_source(FILE *f, struct dt_info *dti); struct dt_info *dt_from_source(const char *f); -/* YAML source */ - -void dt_to_yaml(FILE *f, struct dt_info *dti); - /* FS trees */ struct dt_info *dt_from_fs(const char *dirname); diff --git a/scripts/dtc/flattree.c b/scripts/dtc/flattree.c index bd6977eedcb8..8d268fb785db 100644 --- a/scripts/dtc/flattree.c +++ b/scripts/dtc/flattree.c @@ -1,6 +1,21 @@ -// SPDX-License-Identifier: GPL-2.0-or-later /* * (C) Copyright David Gibson , IBM Corporation. 2005. + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA */ #include "dtc.h" @@ -378,7 +393,7 @@ void dt_to_blob(FILE *f, struct dt_info *dti, int version) padlen = 0; if (quiet < 1) fprintf(stderr, - "Warning: blob size %"PRIu32" >= minimum size %d\n", + "Warning: blob size %d >= minimum size %d\n", fdt32_to_cpu(fdt.totalsize), minsize); } } @@ -510,7 +525,7 @@ void dt_to_asm(FILE *f, struct dt_info *dti, int version) fprintf(f, "/* Memory reserve map from source file */\n"); /* - * Use .long on high and low halves of u64s to avoid .quad + * Use .long on high and low halfs of u64s to avoid .quad * as it appears .quad isn't available in some assemblers. */ for (re = dti->reservelist; re; re = re->next) { @@ -677,7 +692,7 @@ static struct property *flat_read_property(struct inbuf *dtbuf, val = flat_read_data(dtbuf, proplen); - return build_property(name, val, NULL); + return build_property(name, val); } @@ -735,7 +750,7 @@ static struct node *unflatten_tree(struct inbuf *dtbuf, char *flatname; uint32_t val; - node = build_node(NULL, NULL, NULL); + node = build_node(NULL, NULL); flatname = flat_read_string(dtbuf); diff --git a/scripts/dtc/fstree.c b/scripts/dtc/fstree.c index 9871689b4afb..ae7d06c3c492 100644 --- a/scripts/dtc/fstree.c +++ b/scripts/dtc/fstree.c @@ -1,6 +1,21 @@ -// SPDX-License-Identifier: GPL-2.0-or-later /* * (C) Copyright David Gibson , IBM Corporation. 2005. + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA */ #include "dtc.h" @@ -19,7 +34,7 @@ static struct node *read_fstree(const char *dirname) if (!d) die("Couldn't opendir() \"%s\": %s\n", dirname, strerror(errno)); - tree = build_node(NULL, NULL, NULL); + tree = build_node(NULL, NULL); while ((de = readdir(d)) != NULL) { char *tmpname; @@ -45,8 +60,7 @@ static struct node *read_fstree(const char *dirname) } else { prop = build_property(xstrdup(de->d_name), data_copy_file(pfile, - st.st_size), - NULL); + st.st_size)); add_property(tree, prop); fclose(pfile); } diff --git a/scripts/dtc/libfdt/Makefile.libfdt b/scripts/dtc/libfdt/Makefile.libfdt index e54639738c8e..098b3f36e668 100644 --- a/scripts/dtc/libfdt/Makefile.libfdt +++ b/scripts/dtc/libfdt/Makefile.libfdt @@ -1,4 +1,3 @@ -# SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) # Makefile.libfdt # # This is not a complete Makefile of itself. Instead, it is designed to @@ -10,9 +9,3 @@ LIBFDT_VERSION = version.lds LIBFDT_SRCS = fdt.c fdt_ro.c fdt_wip.c fdt_sw.c fdt_rw.c fdt_strerror.c fdt_empty_tree.c \ fdt_addresses.c fdt_overlay.c LIBFDT_OBJS = $(LIBFDT_SRCS:%.c=%.o) -LIBFDT_LIB = libfdt-$(DTC_VERSION).$(SHAREDLIB_EXT) - -libfdt_clean: - @$(VECHO) CLEAN "(libfdt)" - rm -f $(STD_CLEANFILES:%=$(LIBFDT_dir)/%) - rm -f $(LIBFDT_dir)/$(LIBFDT_soname) diff --git a/scripts/dtc/libfdt/fdt.c b/scripts/dtc/libfdt/fdt.c index 16a0bba3746a..9af0733d1837 100644 --- a/scripts/dtc/libfdt/fdt.c +++ b/scripts/dtc/libfdt/fdt.c @@ -1,7 +1,52 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) /* * libfdt - Flat Device Tree manipulation * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "libfdt_env.h" @@ -10,12 +55,7 @@ #include "libfdt_internal.h" -/* - * Minimal sanity check for a read-only tree. fdt_ro_probe_() checks - * that the given buffer contains what appears to be a flattened - * device tree with sane information in its header. - */ -int fdt_ro_probe_(const void *fdt) +int fdt_check_header(const void *fdt) { if (fdt_magic(fdt) == FDT_MAGIC) { /* Complete tree */ @@ -48,78 +88,6 @@ int fdt_ro_probe_(const void *fdt) return 0; } -static int check_off_(uint32_t hdrsize, uint32_t totalsize, uint32_t off) -{ - return (off >= hdrsize) && (off <= totalsize); -} - -static int check_block_(uint32_t hdrsize, uint32_t totalsize, - uint32_t base, uint32_t size) -{ - if (!check_off_(hdrsize, totalsize, base)) - return 0; /* block start out of bounds */ - if ((base + size) < base) - return 0; /* overflow */ - if (!check_off_(hdrsize, totalsize, base + size)) - return 0; /* block end out of bounds */ - return 1; -} - -size_t fdt_header_size_(uint32_t version) -{ - if (version <= 1) - return FDT_V1_SIZE; - else if (version <= 2) - return FDT_V2_SIZE; - else if (version <= 3) - return FDT_V3_SIZE; - else if (version <= 16) - return FDT_V16_SIZE; - else - return FDT_V17_SIZE; -} - -int fdt_check_header(const void *fdt) -{ - size_t hdrsize; - - if (fdt_magic(fdt) != FDT_MAGIC) - return -FDT_ERR_BADMAGIC; - hdrsize = fdt_header_size(fdt); - if ((fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) - || (fdt_last_comp_version(fdt) > FDT_LAST_SUPPORTED_VERSION)) - return -FDT_ERR_BADVERSION; - if (fdt_version(fdt) < fdt_last_comp_version(fdt)) - return -FDT_ERR_BADVERSION; - - if ((fdt_totalsize(fdt) < hdrsize) - || (fdt_totalsize(fdt) > INT_MAX)) - return -FDT_ERR_TRUNCATED; - - /* Bounds check memrsv block */ - if (!check_off_(hdrsize, fdt_totalsize(fdt), fdt_off_mem_rsvmap(fdt))) - return -FDT_ERR_TRUNCATED; - - /* Bounds check structure block */ - if (fdt_version(fdt) < 17) { - if (!check_off_(hdrsize, fdt_totalsize(fdt), - fdt_off_dt_struct(fdt))) - return -FDT_ERR_TRUNCATED; - } else { - if (!check_block_(hdrsize, fdt_totalsize(fdt), - fdt_off_dt_struct(fdt), - fdt_size_dt_struct(fdt))) - return -FDT_ERR_TRUNCATED; - } - - /* Bounds check strings block */ - if (!check_block_(hdrsize, fdt_totalsize(fdt), - fdt_off_dt_strings(fdt), fdt_size_dt_strings(fdt))) - return -FDT_ERR_TRUNCATED; - - return 0; -} - const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len) { unsigned absoffset = offset + fdt_off_dt_struct(fdt); @@ -290,7 +258,7 @@ const char *fdt_find_string_(const char *strtab, int tabsize, const char *s) int fdt_move(const void *fdt, void *buf, int bufsize) { - FDT_RO_PROBE(fdt); + FDT_CHECK_HEADER(fdt); if (fdt_totalsize(fdt) > bufsize) return -FDT_ERR_NOSPACE; diff --git a/scripts/dtc/libfdt/fdt.h b/scripts/dtc/libfdt/fdt.h index f2e68807f277..74961f9026d1 100644 --- a/scripts/dtc/libfdt/fdt.h +++ b/scripts/dtc/libfdt/fdt.h @@ -1,10 +1,55 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ #ifndef FDT_H #define FDT_H /* * libfdt - Flat Device Tree manipulation * Copyright (C) 2006 David Gibson, IBM Corporation. * Copyright 2012 Kim Phillips, Freescale Semiconductor. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __ASSEMBLY__ diff --git a/scripts/dtc/libfdt/fdt_addresses.c b/scripts/dtc/libfdt/fdt_addresses.c index d8ba8ec60c6c..eff4dbcc729d 100644 --- a/scripts/dtc/libfdt/fdt_addresses.c +++ b/scripts/dtc/libfdt/fdt_addresses.c @@ -1,8 +1,52 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) /* * libfdt - Flat Device Tree manipulation * Copyright (C) 2014 David Gibson - * Copyright (C) 2018 embedded brains GmbH + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "libfdt_env.h" @@ -11,89 +55,42 @@ #include "libfdt_internal.h" -static int fdt_cells(const void *fdt, int nodeoffset, const char *name) +int fdt_address_cells(const void *fdt, int nodeoffset) { - const fdt32_t *c; + const fdt32_t *ac; int val; int len; - c = fdt_getprop(fdt, nodeoffset, name, &len); - if (!c) - return len; + ac = fdt_getprop(fdt, nodeoffset, "#address-cells", &len); + if (!ac) + return 2; - if (len != sizeof(*c)) + if (len != sizeof(*ac)) return -FDT_ERR_BADNCELLS; - val = fdt32_to_cpu(*c); + val = fdt32_to_cpu(*ac); if ((val <= 0) || (val > FDT_MAX_NCELLS)) return -FDT_ERR_BADNCELLS; return val; } -int fdt_address_cells(const void *fdt, int nodeoffset) -{ - int val; - - val = fdt_cells(fdt, nodeoffset, "#address-cells"); - if (val == -FDT_ERR_NOTFOUND) - return 2; - return val; -} - int fdt_size_cells(const void *fdt, int nodeoffset) { + const fdt32_t *sc; int val; + int len; + + sc = fdt_getprop(fdt, nodeoffset, "#size-cells", &len); + if (!sc) + return 2; + + if (len != sizeof(*sc)) + return -FDT_ERR_BADNCELLS; + + val = fdt32_to_cpu(*sc); + if ((val < 0) || (val > FDT_MAX_NCELLS)) + return -FDT_ERR_BADNCELLS; - val = fdt_cells(fdt, nodeoffset, "#size-cells"); - if (val == -FDT_ERR_NOTFOUND) - return 1; return val; } - -/* This function assumes that [address|size]_cells is 1 or 2 */ -int fdt_appendprop_addrrange(void *fdt, int parent, int nodeoffset, - const char *name, uint64_t addr, uint64_t size) -{ - int addr_cells, size_cells, ret; - uint8_t data[sizeof(fdt64_t) * 2], *prop; - - ret = fdt_address_cells(fdt, parent); - if (ret < 0) - return ret; - addr_cells = ret; - - ret = fdt_size_cells(fdt, parent); - if (ret < 0) - return ret; - size_cells = ret; - - /* check validity of address */ - prop = data; - if (addr_cells == 1) { - if ((addr > UINT32_MAX) || ((UINT32_MAX + 1 - addr) < size)) - return -FDT_ERR_BADVALUE; - - fdt32_st(prop, (uint32_t)addr); - } else if (addr_cells == 2) { - fdt64_st(prop, addr); - } else { - return -FDT_ERR_BADNCELLS; - } - - /* check validity of size */ - prop += addr_cells * sizeof(fdt32_t); - if (size_cells == 1) { - if (size > UINT32_MAX) - return -FDT_ERR_BADVALUE; - - fdt32_st(prop, (uint32_t)size); - } else if (size_cells == 2) { - fdt64_st(prop, size); - } else { - return -FDT_ERR_BADNCELLS; - } - - return fdt_appendprop(fdt, nodeoffset, name, data, - (addr_cells + size_cells) * sizeof(fdt32_t)); -} diff --git a/scripts/dtc/libfdt/fdt_empty_tree.c b/scripts/dtc/libfdt/fdt_empty_tree.c index 49d54d44b8e7..f2ae9b77c285 100644 --- a/scripts/dtc/libfdt/fdt_empty_tree.c +++ b/scripts/dtc/libfdt/fdt_empty_tree.c @@ -1,7 +1,52 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) /* * libfdt - Flat Device Tree manipulation * Copyright (C) 2012 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "libfdt_env.h" diff --git a/scripts/dtc/libfdt/fdt_overlay.c b/scripts/dtc/libfdt/fdt_overlay.c index e97f12b1a780..bf75388ec9a2 100644 --- a/scripts/dtc/libfdt/fdt_overlay.c +++ b/scripts/dtc/libfdt/fdt_overlay.c @@ -1,8 +1,53 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) /* * libfdt - Flat Device Tree manipulation * Copyright (C) 2016 Free Electrons * Copyright (C) 2016 NextThing Co. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "libfdt_env.h" @@ -48,11 +93,11 @@ static uint32_t overlay_get_target_phandle(const void *fdto, int fragment) * @pathp: pointer which receives the path of the target (or NULL) * * overlay_get_target() retrieves the target offset in the base - * device tree of a fragment, no matter how the actual targeting is + * device tree of a fragment, no matter how the actual targetting is * done (through a phandle or a path) * * returns: - * the targeted node offset in the base device tree + * the targetted node offset in the base device tree * Negative error code on error */ static int overlay_get_target(const void *fdt, const void *fdto, @@ -652,7 +697,7 @@ static int get_path_len(const void *fdt, int nodeoffset) int len = 0, namelen; const char *name; - FDT_RO_PROBE(fdt); + FDT_CHECK_HEADER(fdt); for (;;) { name = fdt_get_name(fdt, nodeoffset, &namelen); @@ -818,15 +863,11 @@ static int overlay_symbol_update(void *fdt, void *fdto) int fdt_overlay_apply(void *fdt, void *fdto) { - uint32_t delta; + uint32_t delta = fdt_get_max_phandle(fdt); int ret; - FDT_RO_PROBE(fdt); - FDT_RO_PROBE(fdto); - - ret = fdt_find_max_phandle(fdt, &delta); - if (ret) - goto err; + FDT_CHECK_HEADER(fdt); + FDT_CHECK_HEADER(fdto); ret = overlay_adjust_local_phandles(fdto, delta); if (ret) diff --git a/scripts/dtc/libfdt/fdt_ro.c b/scripts/dtc/libfdt/fdt_ro.c index 6fd9ec170dbe..dfb3236da388 100644 --- a/scripts/dtc/libfdt/fdt_ro.c +++ b/scripts/dtc/libfdt/fdt_ro.c @@ -1,7 +1,52 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) /* * libfdt - Flat Device Tree manipulation * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "libfdt_env.h" @@ -31,156 +76,60 @@ static int fdt_nodename_eq_(const void *fdt, int offset, return 0; } -const char *fdt_get_string(const void *fdt, int stroffset, int *lenp) -{ - uint32_t absoffset = stroffset + fdt_off_dt_strings(fdt); - size_t len; - int err; - const char *s, *n; - - err = fdt_ro_probe_(fdt); - if (err != 0) - goto fail; - - err = -FDT_ERR_BADOFFSET; - if (absoffset >= fdt_totalsize(fdt)) - goto fail; - len = fdt_totalsize(fdt) - absoffset; - - if (fdt_magic(fdt) == FDT_MAGIC) { - if (stroffset < 0) - goto fail; - if (fdt_version(fdt) >= 17) { - if (stroffset >= fdt_size_dt_strings(fdt)) - goto fail; - if ((fdt_size_dt_strings(fdt) - stroffset) < len) - len = fdt_size_dt_strings(fdt) - stroffset; - } - } else if (fdt_magic(fdt) == FDT_SW_MAGIC) { - if ((stroffset >= 0) - || (stroffset < -fdt_size_dt_strings(fdt))) - goto fail; - if ((-stroffset) < len) - len = -stroffset; - } else { - err = -FDT_ERR_INTERNAL; - goto fail; - } - - s = (const char *)fdt + absoffset; - n = memchr(s, '\0', len); - if (!n) { - /* missing terminating NULL */ - err = -FDT_ERR_TRUNCATED; - goto fail; - } - - if (lenp) - *lenp = n - s; - return s; - -fail: - if (lenp) - *lenp = err; - return NULL; -} - const char *fdt_string(const void *fdt, int stroffset) { - return fdt_get_string(fdt, stroffset, NULL); + return (const char *)fdt + fdt_off_dt_strings(fdt) + stroffset; } static int fdt_string_eq_(const void *fdt, int stroffset, const char *s, int len) { - int slen; - const char *p = fdt_get_string(fdt, stroffset, &slen); + const char *p = fdt_string(fdt, stroffset); - return p && (slen == len) && (memcmp(p, s, len) == 0); + return (strlen(p) == len) && (memcmp(p, s, len) == 0); } -int fdt_find_max_phandle(const void *fdt, uint32_t *phandle) +uint32_t fdt_get_max_phandle(const void *fdt) { - uint32_t max = 0; - int offset = -1; + uint32_t max_phandle = 0; + int offset; - while (true) { - uint32_t value; + for (offset = fdt_next_node(fdt, -1, NULL);; + offset = fdt_next_node(fdt, offset, NULL)) { + uint32_t phandle; - offset = fdt_next_node(fdt, offset, NULL); - if (offset < 0) { - if (offset == -FDT_ERR_NOTFOUND) - break; + if (offset == -FDT_ERR_NOTFOUND) + return max_phandle; - return offset; - } + if (offset < 0) + return (uint32_t)-1; - value = fdt_get_phandle(fdt, offset); + phandle = fdt_get_phandle(fdt, offset); + if (phandle == (uint32_t)-1) + continue; - if (value > max) - max = value; + if (phandle > max_phandle) + max_phandle = phandle; } - if (phandle) - *phandle = max; - return 0; } -int fdt_generate_phandle(const void *fdt, uint32_t *phandle) -{ - uint32_t max; - int err; - - err = fdt_find_max_phandle(fdt, &max); - if (err < 0) - return err; - - if (max == FDT_MAX_PHANDLE) - return -FDT_ERR_NOPHANDLES; - - if (phandle) - *phandle = max + 1; - - return 0; -} - -static const struct fdt_reserve_entry *fdt_mem_rsv(const void *fdt, int n) -{ - int offset = n * sizeof(struct fdt_reserve_entry); - int absoffset = fdt_off_mem_rsvmap(fdt) + offset; - - if (absoffset < fdt_off_mem_rsvmap(fdt)) - return NULL; - if (absoffset > fdt_totalsize(fdt) - sizeof(struct fdt_reserve_entry)) - return NULL; - return fdt_mem_rsv_(fdt, n); -} - int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size) { - const struct fdt_reserve_entry *re; - - FDT_RO_PROBE(fdt); - re = fdt_mem_rsv(fdt, n); - if (!re) - return -FDT_ERR_BADOFFSET; - - *address = fdt64_ld(&re->address); - *size = fdt64_ld(&re->size); + FDT_CHECK_HEADER(fdt); + *address = fdt64_to_cpu(fdt_mem_rsv_(fdt, n)->address); + *size = fdt64_to_cpu(fdt_mem_rsv_(fdt, n)->size); return 0; } int fdt_num_mem_rsv(const void *fdt) { - int i; - const struct fdt_reserve_entry *re; + int i = 0; - for (i = 0; (re = fdt_mem_rsv(fdt, i)) != NULL; i++) { - if (fdt64_ld(&re->size) == 0) - return i; - } - return -FDT_ERR_TRUNCATED; + while (fdt64_to_cpu(fdt_mem_rsv_(fdt, i)->size) != 0) + i++; + return i; } static int nextprop_(const void *fdt, int offset) @@ -212,7 +161,7 @@ int fdt_subnode_offset_namelen(const void *fdt, int offset, { int depth; - FDT_RO_PROBE(fdt); + FDT_CHECK_HEADER(fdt); for (depth = 0; (offset >= 0) && (depth >= 0); @@ -238,7 +187,7 @@ int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen) const char *p = path; int offset = 0; - FDT_RO_PROBE(fdt); + FDT_CHECK_HEADER(fdt); /* see if we have an alias */ if (*path != '/') { @@ -288,7 +237,7 @@ const char *fdt_get_name(const void *fdt, int nodeoffset, int *len) const char *nameptr; int err; - if (((err = fdt_ro_probe_(fdt)) != 0) + if (((err = fdt_check_header(fdt)) != 0) || ((err = fdt_check_node_offset_(fdt, nodeoffset)) < 0)) goto fail; @@ -354,7 +303,7 @@ static const struct fdt_property *fdt_get_property_by_offset_(const void *fdt, prop = fdt_offset_ptr_(fdt, offset); if (lenp) - *lenp = fdt32_ld(&prop->len); + *lenp = fdt32_to_cpu(prop->len); return prop; } @@ -391,7 +340,7 @@ static const struct fdt_property *fdt_get_property_namelen_(const void *fdt, offset = -FDT_ERR_INTERNAL; break; } - if (fdt_string_eq_(fdt, fdt32_ld(&prop->nameoff), + if (fdt_string_eq_(fdt, fdt32_to_cpu(prop->nameoff), name, namelen)) { if (poffset) *poffset = offset; @@ -444,7 +393,7 @@ const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, /* Handle realignment */ if (fdt_version(fdt) < 0x10 && (poffset + sizeof(*prop)) % 8 && - fdt32_ld(&prop->len) >= 8) + fdt32_to_cpu(prop->len) >= 8) return prop->data + 4; return prop->data; } @@ -457,22 +406,12 @@ const void *fdt_getprop_by_offset(const void *fdt, int offset, prop = fdt_get_property_by_offset_(fdt, offset, lenp); if (!prop) return NULL; - if (namep) { - const char *name; - int namelen; - name = fdt_get_string(fdt, fdt32_ld(&prop->nameoff), - &namelen); - if (!name) { - if (lenp) - *lenp = namelen; - return NULL; - } - *namep = name; - } + if (namep) + *namep = fdt_string(fdt, fdt32_to_cpu(prop->nameoff)); /* Handle realignment */ if (fdt_version(fdt) < 0x10 && (offset + sizeof(*prop)) % 8 && - fdt32_ld(&prop->len) >= 8) + fdt32_to_cpu(prop->len) >= 8) return prop->data + 4; return prop->data; } @@ -497,7 +436,7 @@ uint32_t fdt_get_phandle(const void *fdt, int nodeoffset) return 0; } - return fdt32_ld(php); + return fdt32_to_cpu(*php); } const char *fdt_get_alias_namelen(const void *fdt, @@ -523,7 +462,7 @@ int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen) int offset, depth, namelen; const char *name; - FDT_RO_PROBE(fdt); + FDT_CHECK_HEADER(fdt); if (buflen < 2) return -FDT_ERR_NOSPACE; @@ -575,7 +514,7 @@ int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, int offset, depth; int supernodeoffset = -FDT_ERR_INTERNAL; - FDT_RO_PROBE(fdt); + FDT_CHECK_HEADER(fdt); if (supernodedepth < 0) return -FDT_ERR_NOTFOUND; @@ -634,7 +573,7 @@ int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, const void *val; int len; - FDT_RO_PROBE(fdt); + FDT_CHECK_HEADER(fdt); /* FIXME: The algorithm here is pretty horrible: we scan each * property of a node in fdt_getprop(), then if that didn't @@ -660,7 +599,7 @@ int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle) if ((phandle == 0) || (phandle == -1)) return -FDT_ERR_BADPHANDLE; - FDT_RO_PROBE(fdt); + FDT_CHECK_HEADER(fdt); /* FIXME: The algorithm here is pretty horrible: we * potentially scan each property of a node in @@ -813,7 +752,7 @@ int fdt_node_offset_by_compatible(const void *fdt, int startoffset, { int offset, err; - FDT_RO_PROBE(fdt); + FDT_CHECK_HEADER(fdt); /* FIXME: The algorithm here is pretty horrible: we scan each * property of a node in fdt_node_check_compatible(), then if @@ -832,66 +771,3 @@ int fdt_node_offset_by_compatible(const void *fdt, int startoffset, return offset; /* error from fdt_next_node() */ } - -int fdt_check_full(const void *fdt, size_t bufsize) -{ - int err; - int num_memrsv; - int offset, nextoffset = 0; - uint32_t tag; - unsigned depth = 0; - const void *prop; - const char *propname; - - if (bufsize < FDT_V1_SIZE) - return -FDT_ERR_TRUNCATED; - err = fdt_check_header(fdt); - if (err != 0) - return err; - if (bufsize < fdt_totalsize(fdt)) - return -FDT_ERR_TRUNCATED; - - num_memrsv = fdt_num_mem_rsv(fdt); - if (num_memrsv < 0) - return num_memrsv; - - while (1) { - offset = nextoffset; - tag = fdt_next_tag(fdt, offset, &nextoffset); - - if (nextoffset < 0) - return nextoffset; - - switch (tag) { - case FDT_NOP: - break; - - case FDT_END: - if (depth != 0) - return -FDT_ERR_BADSTRUCTURE; - return 0; - - case FDT_BEGIN_NODE: - depth++; - if (depth > INT_MAX) - return -FDT_ERR_BADSTRUCTURE; - break; - - case FDT_END_NODE: - if (depth == 0) - return -FDT_ERR_BADSTRUCTURE; - depth--; - break; - - case FDT_PROP: - prop = fdt_getprop_by_offset(fdt, offset, &propname, - &err); - if (!prop) - return err; - break; - - default: - return -FDT_ERR_INTERNAL; - } - } -} diff --git a/scripts/dtc/libfdt/fdt_rw.c b/scripts/dtc/libfdt/fdt_rw.c index d67c2cc3d72f..26d5755a97d3 100644 --- a/scripts/dtc/libfdt/fdt_rw.c +++ b/scripts/dtc/libfdt/fdt_rw.c @@ -1,7 +1,52 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) /* * libfdt - Flat Device Tree manipulation * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "libfdt_env.h" @@ -22,9 +67,9 @@ static int fdt_blocks_misordered_(const void *fdt, (fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt))); } -static int fdt_rw_probe_(void *fdt) +static int fdt_rw_check_header_(void *fdt) { - FDT_RO_PROBE(fdt); + FDT_CHECK_HEADER(fdt); if (fdt_version(fdt) < 17) return -FDT_ERR_BADVERSION; @@ -37,10 +82,10 @@ static int fdt_rw_probe_(void *fdt) return 0; } -#define FDT_RW_PROBE(fdt) \ +#define FDT_RW_CHECK_HEADER(fdt) \ { \ int err_; \ - if ((err_ = fdt_rw_probe_(fdt)) != 0) \ + if ((err_ = fdt_rw_check_header_(fdt)) != 0) \ return err_; \ } @@ -91,14 +136,6 @@ static int fdt_splice_struct_(void *fdt, void *p, return 0; } -/* Must only be used to roll back in case of error */ -static void fdt_del_last_string_(void *fdt, const char *s) -{ - int newlen = strlen(s) + 1; - - fdt_set_size_dt_strings(fdt, fdt_size_dt_strings(fdt) - newlen); -} - static int fdt_splice_string_(void *fdt, int newlen) { void *p = (char *)fdt @@ -112,7 +149,7 @@ static int fdt_splice_string_(void *fdt, int newlen) return 0; } -static int fdt_find_add_string_(void *fdt, const char *s, int *allocated) +static int fdt_find_add_string_(void *fdt, const char *s) { char *strtab = (char *)fdt + fdt_off_dt_strings(fdt); const char *p; @@ -120,8 +157,6 @@ static int fdt_find_add_string_(void *fdt, const char *s, int *allocated) int len = strlen(s) + 1; int err; - *allocated = 0; - p = fdt_find_string_(strtab, fdt_size_dt_strings(fdt), s); if (p) /* found it */ @@ -132,8 +167,6 @@ static int fdt_find_add_string_(void *fdt, const char *s, int *allocated) if (err) return err; - *allocated = 1; - memcpy(new, s, len); return (new - strtab); } @@ -143,7 +176,7 @@ int fdt_add_mem_rsv(void *fdt, uint64_t address, uint64_t size) struct fdt_reserve_entry *re; int err; - FDT_RW_PROBE(fdt); + FDT_RW_CHECK_HEADER(fdt); re = fdt_mem_rsv_w_(fdt, fdt_num_mem_rsv(fdt)); err = fdt_splice_mem_rsv_(fdt, re, 0, 1); @@ -159,7 +192,7 @@ int fdt_del_mem_rsv(void *fdt, int n) { struct fdt_reserve_entry *re = fdt_mem_rsv_w_(fdt, n); - FDT_RW_PROBE(fdt); + FDT_RW_CHECK_HEADER(fdt); if (n >= fdt_num_mem_rsv(fdt)) return -FDT_ERR_NOTFOUND; @@ -192,12 +225,11 @@ static int fdt_add_property_(void *fdt, int nodeoffset, const char *name, int nextoffset; int namestroff; int err; - int allocated; if ((nextoffset = fdt_check_node_offset_(fdt, nodeoffset)) < 0) return nextoffset; - namestroff = fdt_find_add_string_(fdt, name, &allocated); + namestroff = fdt_find_add_string_(fdt, name); if (namestroff < 0) return namestroff; @@ -205,11 +237,8 @@ static int fdt_add_property_(void *fdt, int nodeoffset, const char *name, proplen = sizeof(**prop) + FDT_TAGALIGN(len); err = fdt_splice_struct_(fdt, *prop, 0, proplen); - if (err) { - if (allocated) - fdt_del_last_string_(fdt, name); + if (err) return err; - } (*prop)->tag = cpu_to_fdt32(FDT_PROP); (*prop)->nameoff = cpu_to_fdt32(namestroff); @@ -223,7 +252,7 @@ int fdt_set_name(void *fdt, int nodeoffset, const char *name) int oldlen, newlen; int err; - FDT_RW_PROBE(fdt); + FDT_RW_CHECK_HEADER(fdt); namep = (char *)(uintptr_t)fdt_get_name(fdt, nodeoffset, &oldlen); if (!namep) @@ -246,7 +275,7 @@ int fdt_setprop_placeholder(void *fdt, int nodeoffset, const char *name, struct fdt_property *prop; int err; - FDT_RW_PROBE(fdt); + FDT_RW_CHECK_HEADER(fdt); err = fdt_resize_property_(fdt, nodeoffset, name, len, &prop); if (err == -FDT_ERR_NOTFOUND) @@ -279,7 +308,7 @@ int fdt_appendprop(void *fdt, int nodeoffset, const char *name, struct fdt_property *prop; int err, oldlen, newlen; - FDT_RW_PROBE(fdt); + FDT_RW_CHECK_HEADER(fdt); prop = fdt_get_property_w(fdt, nodeoffset, name, &oldlen); if (prop) { @@ -305,7 +334,7 @@ int fdt_delprop(void *fdt, int nodeoffset, const char *name) struct fdt_property *prop; int len, proplen; - FDT_RW_PROBE(fdt); + FDT_RW_CHECK_HEADER(fdt); prop = fdt_get_property_w(fdt, nodeoffset, name, &len); if (!prop) @@ -325,7 +354,7 @@ int fdt_add_subnode_namelen(void *fdt, int parentoffset, uint32_t tag; fdt32_t *endtag; - FDT_RW_PROBE(fdt); + FDT_RW_CHECK_HEADER(fdt); offset = fdt_subnode_offset_namelen(fdt, parentoffset, name, namelen); if (offset >= 0) @@ -365,7 +394,7 @@ int fdt_del_node(void *fdt, int nodeoffset) { int endoffset; - FDT_RW_PROBE(fdt); + FDT_RW_CHECK_HEADER(fdt); endoffset = fdt_node_end_offset_(fdt, nodeoffset); if (endoffset < 0) @@ -406,7 +435,7 @@ int fdt_open_into(const void *fdt, void *buf, int bufsize) const char *fdtend = fdtstart + fdt_totalsize(fdt); char *tmp; - FDT_RO_PROBE(fdt); + FDT_CHECK_HEADER(fdt); mem_rsv_size = (fdt_num_mem_rsv(fdt)+1) * sizeof(struct fdt_reserve_entry); @@ -465,7 +494,7 @@ int fdt_pack(void *fdt) { int mem_rsv_size; - FDT_RW_PROBE(fdt); + FDT_RW_CHECK_HEADER(fdt); mem_rsv_size = (fdt_num_mem_rsv(fdt)+1) * sizeof(struct fdt_reserve_entry); diff --git a/scripts/dtc/libfdt/fdt_strerror.c b/scripts/dtc/libfdt/fdt_strerror.c index 768db66eada5..9677a1887e57 100644 --- a/scripts/dtc/libfdt/fdt_strerror.c +++ b/scripts/dtc/libfdt/fdt_strerror.c @@ -1,7 +1,51 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) /* * libfdt - Flat Device Tree manipulation * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "libfdt_env.h" @@ -38,7 +82,6 @@ static struct fdt_errtabent fdt_errtable[] = { FDT_ERRTABENT(FDT_ERR_BADVALUE), FDT_ERRTABENT(FDT_ERR_BADOVERLAY), FDT_ERRTABENT(FDT_ERR_NOPHANDLES), - FDT_ERRTABENT(FDT_ERR_BADFLAGS), }; #define FDT_ERRTABSIZE (sizeof(fdt_errtable) / sizeof(fdt_errtable[0])) diff --git a/scripts/dtc/libfdt/fdt_sw.c b/scripts/dtc/libfdt/fdt_sw.c index 76bea22f734f..6d33cc29d022 100644 --- a/scripts/dtc/libfdt/fdt_sw.c +++ b/scripts/dtc/libfdt/fdt_sw.c @@ -1,7 +1,52 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) /* * libfdt - Flat Device Tree manipulation * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "libfdt_env.h" @@ -10,83 +55,21 @@ #include "libfdt_internal.h" -static int fdt_sw_probe_(void *fdt) +static int fdt_sw_check_header_(void *fdt) { - if (fdt_magic(fdt) == FDT_MAGIC) - return -FDT_ERR_BADSTATE; - else if (fdt_magic(fdt) != FDT_SW_MAGIC) + if (fdt_magic(fdt) != FDT_SW_MAGIC) return -FDT_ERR_BADMAGIC; + /* FIXME: should check more details about the header state */ return 0; } -#define FDT_SW_PROBE(fdt) \ +#define FDT_SW_CHECK_HEADER(fdt) \ { \ int err; \ - if ((err = fdt_sw_probe_(fdt)) != 0) \ + if ((err = fdt_sw_check_header_(fdt)) != 0) \ return err; \ } -/* 'memrsv' state: Initial state after fdt_create() - * - * Allowed functions: - * fdt_add_reservmap_entry() - * fdt_finish_reservemap() [moves to 'struct' state] - */ -static int fdt_sw_probe_memrsv_(void *fdt) -{ - int err = fdt_sw_probe_(fdt); - if (err) - return err; - - if (fdt_off_dt_strings(fdt) != 0) - return -FDT_ERR_BADSTATE; - return 0; -} - -#define FDT_SW_PROBE_MEMRSV(fdt) \ - { \ - int err; \ - if ((err = fdt_sw_probe_memrsv_(fdt)) != 0) \ - return err; \ - } - -/* 'struct' state: Enter this state after fdt_finish_reservemap() - * - * Allowed functions: - * fdt_begin_node() - * fdt_end_node() - * fdt_property*() - * fdt_finish() [moves to 'complete' state] - */ -static int fdt_sw_probe_struct_(void *fdt) -{ - int err = fdt_sw_probe_(fdt); - if (err) - return err; - - if (fdt_off_dt_strings(fdt) != fdt_totalsize(fdt)) - return -FDT_ERR_BADSTATE; - return 0; -} - -#define FDT_SW_PROBE_STRUCT(fdt) \ - { \ - int err; \ - if ((err = fdt_sw_probe_struct_(fdt)) != 0) \ - return err; \ - } - -static inline uint32_t sw_flags(void *fdt) -{ - /* assert: (fdt_magic(fdt) == FDT_SW_MAGIC) */ - return fdt_last_comp_version(fdt); -} - -/* 'complete' state: Enter this state after fdt_finish() - * - * Allowed functions: none - */ - static void *fdt_grab_space_(void *fdt, size_t len) { int offset = fdt_size_dt_struct(fdt); @@ -102,58 +85,38 @@ static void *fdt_grab_space_(void *fdt, size_t len) return fdt_offset_ptr_w_(fdt, offset); } -int fdt_create_with_flags(void *buf, int bufsize, uint32_t flags) +int fdt_create(void *buf, int bufsize) { - const size_t hdrsize = FDT_ALIGN(sizeof(struct fdt_header), - sizeof(struct fdt_reserve_entry)); void *fdt = buf; - if (bufsize < hdrsize) + if (bufsize < sizeof(struct fdt_header)) return -FDT_ERR_NOSPACE; - if (flags & ~FDT_CREATE_FLAGS_ALL) - return -FDT_ERR_BADFLAGS; - memset(buf, 0, bufsize); - /* - * magic and last_comp_version keep intermediate state during the fdt - * creation process, which is replaced with the proper FDT format by - * fdt_finish(). - * - * flags should be accessed with sw_flags(). - */ fdt_set_magic(fdt, FDT_SW_MAGIC); fdt_set_version(fdt, FDT_LAST_SUPPORTED_VERSION); - fdt_set_last_comp_version(fdt, flags); - + fdt_set_last_comp_version(fdt, FDT_FIRST_SUPPORTED_VERSION); fdt_set_totalsize(fdt, bufsize); - fdt_set_off_mem_rsvmap(fdt, hdrsize); + fdt_set_off_mem_rsvmap(fdt, FDT_ALIGN(sizeof(struct fdt_header), + sizeof(struct fdt_reserve_entry))); fdt_set_off_dt_struct(fdt, fdt_off_mem_rsvmap(fdt)); - fdt_set_off_dt_strings(fdt, 0); + fdt_set_off_dt_strings(fdt, bufsize); return 0; } -int fdt_create(void *buf, int bufsize) -{ - return fdt_create_with_flags(buf, bufsize, 0); -} - int fdt_resize(void *fdt, void *buf, int bufsize) { size_t headsize, tailsize; char *oldtail, *newtail; - FDT_SW_PROBE(fdt); + FDT_SW_CHECK_HEADER(fdt); - headsize = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); + headsize = fdt_off_dt_struct(fdt); tailsize = fdt_size_dt_strings(fdt); - if ((headsize + tailsize) > fdt_totalsize(fdt)) - return -FDT_ERR_INTERNAL; - if ((headsize + tailsize) > bufsize) return -FDT_ERR_NOSPACE; @@ -170,9 +133,8 @@ int fdt_resize(void *fdt, void *buf, int bufsize) memmove(buf, fdt, headsize); } + fdt_set_off_dt_strings(buf, bufsize); fdt_set_totalsize(buf, bufsize); - if (fdt_off_dt_strings(buf)) - fdt_set_off_dt_strings(buf, bufsize); return 0; } @@ -182,7 +144,10 @@ int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size) struct fdt_reserve_entry *re; int offset; - FDT_SW_PROBE_MEMRSV(fdt); + FDT_SW_CHECK_HEADER(fdt); + + if (fdt_size_dt_struct(fdt)) + return -FDT_ERR_BADSTATE; offset = fdt_off_dt_struct(fdt); if ((offset + sizeof(*re)) > fdt_totalsize(fdt)) @@ -199,23 +164,16 @@ int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size) int fdt_finish_reservemap(void *fdt) { - int err = fdt_add_reservemap_entry(fdt, 0, 0); - - if (err) - return err; - - fdt_set_off_dt_strings(fdt, fdt_totalsize(fdt)); - return 0; + return fdt_add_reservemap_entry(fdt, 0, 0); } int fdt_begin_node(void *fdt, const char *name) { struct fdt_node_header *nh; - int namelen; + int namelen = strlen(name) + 1; - FDT_SW_PROBE_STRUCT(fdt); + FDT_SW_CHECK_HEADER(fdt); - namelen = strlen(name) + 1; nh = fdt_grab_space_(fdt, sizeof(*nh) + FDT_TAGALIGN(namelen)); if (! nh) return -FDT_ERR_NOSPACE; @@ -229,7 +187,7 @@ int fdt_end_node(void *fdt) { fdt32_t *en; - FDT_SW_PROBE_STRUCT(fdt); + FDT_SW_CHECK_HEADER(fdt); en = fdt_grab_space_(fdt, FDT_TAGSIZE); if (! en) @@ -239,13 +197,19 @@ int fdt_end_node(void *fdt) return 0; } -static int fdt_add_string_(void *fdt, const char *s) +static int fdt_find_add_string_(void *fdt, const char *s) { char *strtab = (char *)fdt + fdt_totalsize(fdt); + const char *p; int strtabsize = fdt_size_dt_strings(fdt); int len = strlen(s) + 1; int struct_top, offset; + p = fdt_find_string_(strtab - strtabsize, strtabsize, s); + if (p) + return p - strtab; + + /* Add it */ offset = -strtabsize - len; struct_top = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); if (fdt_totalsize(fdt) + offset < struct_top) @@ -256,56 +220,20 @@ static int fdt_add_string_(void *fdt, const char *s) return offset; } -/* Must only be used to roll back in case of error */ -static void fdt_del_last_string_(void *fdt, const char *s) -{ - int strtabsize = fdt_size_dt_strings(fdt); - int len = strlen(s) + 1; - - fdt_set_size_dt_strings(fdt, strtabsize - len); -} - -static int fdt_find_add_string_(void *fdt, const char *s, int *allocated) -{ - char *strtab = (char *)fdt + fdt_totalsize(fdt); - int strtabsize = fdt_size_dt_strings(fdt); - const char *p; - - *allocated = 0; - - p = fdt_find_string_(strtab - strtabsize, strtabsize, s); - if (p) - return p - strtab; - - *allocated = 1; - - return fdt_add_string_(fdt, s); -} - int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp) { struct fdt_property *prop; int nameoff; - int allocated; - FDT_SW_PROBE_STRUCT(fdt); + FDT_SW_CHECK_HEADER(fdt); - /* String de-duplication can be slow, _NO_NAME_DEDUP skips it */ - if (sw_flags(fdt) & FDT_CREATE_FLAG_NO_NAME_DEDUP) { - allocated = 1; - nameoff = fdt_add_string_(fdt, name); - } else { - nameoff = fdt_find_add_string_(fdt, name, &allocated); - } + nameoff = fdt_find_add_string_(fdt, name); if (nameoff == 0) return -FDT_ERR_NOSPACE; prop = fdt_grab_space_(fdt, sizeof(*prop) + FDT_TAGALIGN(len)); - if (! prop) { - if (allocated) - fdt_del_last_string_(fdt, name); + if (! prop) return -FDT_ERR_NOSPACE; - } prop->tag = cpu_to_fdt32(FDT_PROP); prop->nameoff = cpu_to_fdt32(nameoff); @@ -334,7 +262,7 @@ int fdt_finish(void *fdt) uint32_t tag; int offset, nextoffset; - FDT_SW_PROBE_STRUCT(fdt); + FDT_SW_CHECK_HEADER(fdt); /* Add terminator */ end = fdt_grab_space_(fdt, sizeof(*end)); @@ -367,10 +295,6 @@ int fdt_finish(void *fdt) /* Finally, adjust the header */ fdt_set_totalsize(fdt, newstroffset + fdt_size_dt_strings(fdt)); - - /* And fix up fields that were keeping intermediate state. */ - fdt_set_last_comp_version(fdt, FDT_FIRST_SUPPORTED_VERSION); fdt_set_magic(fdt, FDT_MAGIC); - return 0; } diff --git a/scripts/dtc/libfdt/fdt_wip.c b/scripts/dtc/libfdt/fdt_wip.c index f64139e0b3dc..534c1cbbb2f3 100644 --- a/scripts/dtc/libfdt/fdt_wip.c +++ b/scripts/dtc/libfdt/fdt_wip.c @@ -1,7 +1,52 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) /* * libfdt - Flat Device Tree manipulation * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "libfdt_env.h" diff --git a/scripts/dtc/libfdt/libfdt.h b/scripts/dtc/libfdt/libfdt.h index 7b5ffd13a887..1e27780e1185 100644 --- a/scripts/dtc/libfdt/libfdt.h +++ b/scripts/dtc/libfdt/libfdt.h @@ -1,9 +1,54 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ #ifndef LIBFDT_H #define LIBFDT_H /* * libfdt - Flat Device Tree manipulation * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "libfdt_env.h" @@ -45,9 +90,8 @@ /* Error codes: codes for bad device tree blobs */ #define FDT_ERR_TRUNCATED 8 - /* FDT_ERR_TRUNCATED: FDT or a sub-block is improperly - * terminated (overflows, goes outside allowed bounds, or - * isn't properly terminated). */ + /* FDT_ERR_TRUNCATED: Structure block of the given device tree + * ends without an FDT_END tag. */ #define FDT_ERR_BADMAGIC 9 /* FDT_ERR_BADMAGIC: Given "device tree" appears not to be a * device tree at all - it is missing the flattened device @@ -93,15 +137,7 @@ /* FDT_ERR_NOPHANDLES: The device tree doesn't have any * phandle available anymore without causing an overflow */ -#define FDT_ERR_BADFLAGS 18 - /* FDT_ERR_BADFLAGS: The function was passed a flags field that - * contains invalid flags or an invalid combination of flags. */ - -#define FDT_ERR_MAX 18 - -/* constants */ -#define FDT_MAX_PHANDLE 0xfffffffe - /* Valid values for phandles range from 1 to 2^32-2. */ +#define FDT_ERR_MAX 17 /**********************************************************************/ /* Low-level functions (you probably don't need these) */ @@ -117,61 +153,6 @@ static inline void *fdt_offset_ptr_w(void *fdt, int offset, int checklen) uint32_t fdt_next_tag(const void *fdt, int offset, int *nextoffset); -/* - * Alignment helpers: - * These helpers access words from a device tree blob. They're - * built to work even with unaligned pointers on platforms (ike - * ARM) that don't like unaligned loads and stores - */ - -static inline uint32_t fdt32_ld(const fdt32_t *p) -{ - const uint8_t *bp = (const uint8_t *)p; - - return ((uint32_t)bp[0] << 24) - | ((uint32_t)bp[1] << 16) - | ((uint32_t)bp[2] << 8) - | bp[3]; -} - -static inline void fdt32_st(void *property, uint32_t value) -{ - uint8_t *bp = property; - - bp[0] = value >> 24; - bp[1] = (value >> 16) & 0xff; - bp[2] = (value >> 8) & 0xff; - bp[3] = value & 0xff; -} - -static inline uint64_t fdt64_ld(const fdt64_t *p) -{ - const uint8_t *bp = (const uint8_t *)p; - - return ((uint64_t)bp[0] << 56) - | ((uint64_t)bp[1] << 48) - | ((uint64_t)bp[2] << 40) - | ((uint64_t)bp[3] << 32) - | ((uint64_t)bp[4] << 24) - | ((uint64_t)bp[5] << 16) - | ((uint64_t)bp[6] << 8) - | bp[7]; -} - -static inline void fdt64_st(void *property, uint64_t value) -{ - uint8_t *bp = property; - - bp[0] = value >> 56; - bp[1] = (value >> 48) & 0xff; - bp[2] = (value >> 40) & 0xff; - bp[3] = (value >> 32) & 0xff; - bp[4] = (value >> 24) & 0xff; - bp[5] = (value >> 16) & 0xff; - bp[6] = (value >> 8) & 0xff; - bp[7] = value & 0xff; -} - /**********************************************************************/ /* Traversal functions */ /**********************************************************************/ @@ -214,7 +195,7 @@ int fdt_next_subnode(const void *fdt, int offset); * ... * } * - * if ((node < 0) && (node != -FDT_ERR_NOTFOUND)) { + * if ((node < 0) && (node != -FDT_ERR_NOT_FOUND)) { * Error handling * } * @@ -232,7 +213,7 @@ int fdt_next_subnode(const void *fdt, int offset); /* General functions */ /**********************************************************************/ #define fdt_get_header(fdt, field) \ - (fdt32_ld(&((const struct fdt_header *)(fdt))->field)) + (fdt32_to_cpu(((const struct fdt_header *)(fdt))->field)) #define fdt_magic(fdt) (fdt_get_header(fdt, magic)) #define fdt_totalsize(fdt) (fdt_get_header(fdt, totalsize)) #define fdt_off_dt_struct(fdt) (fdt_get_header(fdt, off_dt_struct)) @@ -263,31 +244,18 @@ fdt_set_hdr_(size_dt_struct); #undef fdt_set_hdr_ /** - * fdt_header_size - return the size of the tree's header - * @fdt: pointer to a flattened device tree - */ -size_t fdt_header_size_(uint32_t version); -static inline size_t fdt_header_size(const void *fdt) -{ - return fdt_header_size_(fdt_version(fdt)); -} - -/** - * fdt_check_header - sanity check a device tree header - + * fdt_check_header - sanity check a device tree or possible device tree * @fdt: pointer to data which might be a flattened device tree * * fdt_check_header() checks that the given buffer contains what - * appears to be a flattened device tree, and that the header contains - * valid information (to the extent that can be determined from the - * header alone). + * appears to be a flattened device tree with sane information in its + * header. * * returns: * 0, if the buffer appears to contain a valid device tree * -FDT_ERR_BADMAGIC, * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_TRUNCATED, standard meanings, as above + * -FDT_ERR_BADSTATE, standard meanings, as above */ int fdt_check_header(const void *fdt); @@ -316,24 +284,6 @@ int fdt_move(const void *fdt, void *buf, int bufsize); /* Read-only functions */ /**********************************************************************/ -int fdt_check_full(const void *fdt, size_t bufsize); - -/** - * fdt_get_string - retrieve a string from the strings block of a device tree - * @fdt: pointer to the device tree blob - * @stroffset: offset of the string within the strings block (native endian) - * @lenp: optional pointer to return the string's length - * - * fdt_get_string() retrieves a pointer to a single string from the - * strings block of the device tree blob at fdt, and optionally also - * returns the string's length in *lenp. - * - * returns: - * a pointer to the string, on success - * NULL, if stroffset is out of bounds, or doesn't point to a valid string - */ -const char *fdt_get_string(const void *fdt, int stroffset, int *lenp); - /** * fdt_string - retrieve a string from the strings block of a device tree * @fdt: pointer to the device tree blob @@ -344,24 +294,10 @@ const char *fdt_get_string(const void *fdt, int stroffset, int *lenp); * * returns: * a pointer to the string, on success - * NULL, if stroffset is out of bounds, or doesn't point to a valid string + * NULL, if stroffset is out of bounds */ const char *fdt_string(const void *fdt, int stroffset); -/** - * fdt_find_max_phandle - find and return the highest phandle in a tree - * @fdt: pointer to the device tree blob - * @phandle: return location for the highest phandle value found in the tree - * - * fdt_find_max_phandle() finds the highest phandle value in the given device - * tree. The value returned in @phandle is only valid if the function returns - * success. - * - * returns: - * 0 on success or a negative error code on failure - */ -int fdt_find_max_phandle(const void *fdt, uint32_t *phandle); - /** * fdt_get_max_phandle - retrieves the highest phandle in a tree * @fdt: pointer to the device tree blob @@ -370,39 +306,12 @@ int fdt_find_max_phandle(const void *fdt, uint32_t *phandle); * device tree. This will ignore badly formatted phandles, or phandles * with a value of 0 or -1. * - * This function is deprecated in favour of fdt_find_max_phandle(). - * * returns: * the highest phandle on success * 0, if no phandle was found in the device tree * -1, if an error occurred */ -static inline uint32_t fdt_get_max_phandle(const void *fdt) -{ - uint32_t phandle; - int err; - - err = fdt_find_max_phandle(fdt, &phandle); - if (err < 0) - return (uint32_t)-1; - - return phandle; -} - -/** - * fdt_generate_phandle - return a new, unused phandle for a device tree blob - * @fdt: pointer to the device tree blob - * @phandle: return location for the new phandle - * - * Walks the device tree blob and looks for the highest phandle value. On - * success, the new, unused phandle value (one higher than the previously - * highest phandle value in the device tree blob) will be returned in the - * @phandle parameter. - * - * Returns: - * 0 on success or a negative error-code on failure - */ -int fdt_generate_phandle(const void *fdt, uint32_t *phandle); +uint32_t fdt_get_max_phandle(const void *fdt); /** * fdt_num_mem_rsv - retrieve the number of memory reserve map entries @@ -594,7 +503,7 @@ int fdt_next_property_offset(const void *fdt, int offset); * ... * } * - * if ((property < 0) && (property != -FDT_ERR_NOTFOUND)) { + * if ((property < 0) && (property != -FDT_ERR_NOT_FOUND)) { * Error handling * } * @@ -697,7 +606,7 @@ static inline struct fdt_property *fdt_get_property_w(void *fdt, int nodeoffset, /** * fdt_getprop_by_offset - retrieve the value of a property at a given offset * @fdt: pointer to the device tree blob - * @offset: offset of the property to read + * @ffset: offset of the property to read * @namep: pointer to a string variable (will be overwritten) or NULL * @lenp: pointer to an integer variable (will be overwritten) or NULL * @@ -1181,7 +1090,7 @@ int fdt_address_cells(const void *fdt, int nodeoffset); * * returns: * 0 <= n < FDT_MAX_NCELLS, on success - * 1, if the node has no #size-cells property + * 2, if the node has no #address-cells property * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid * #size-cells property * -FDT_ERR_BADMAGIC, @@ -1388,45 +1297,7 @@ int fdt_nop_node(void *fdt, int nodeoffset); /* Sequential write functions */ /**********************************************************************/ -/* fdt_create_with_flags flags */ -#define FDT_CREATE_FLAG_NO_NAME_DEDUP 0x1 - /* FDT_CREATE_FLAG_NO_NAME_DEDUP: Do not try to de-duplicate property - * names in the fdt. This can result in faster creation times, but - * a larger fdt. */ - -#define FDT_CREATE_FLAGS_ALL (FDT_CREATE_FLAG_NO_NAME_DEDUP) - -/** - * fdt_create_with_flags - begin creation of a new fdt - * @fdt: pointer to memory allocated where fdt will be created - * @bufsize: size of the memory space at fdt - * @flags: a valid combination of FDT_CREATE_FLAG_ flags, or 0. - * - * fdt_create_with_flags() begins the process of creating a new fdt with - * the sequential write interface. - * - * fdt creation process must end with fdt_finished() to produce a valid fdt. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, bufsize is insufficient for a minimal fdt - * -FDT_ERR_BADFLAGS, flags is not valid - */ -int fdt_create_with_flags(void *buf, int bufsize, uint32_t flags); - -/** - * fdt_create - begin creation of a new fdt - * @fdt: pointer to memory allocated where fdt will be created - * @bufsize: size of the memory space at fdt - * - * fdt_create() is equivalent to fdt_create_with_flags() with flags=0. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, bufsize is insufficient for a minimal fdt - */ int fdt_create(void *buf, int bufsize); - int fdt_resize(void *fdt, void *buf, int bufsize); int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size); int fdt_finish_reservemap(void *fdt); @@ -1442,13 +1313,10 @@ static inline int fdt_property_u64(void *fdt, const char *name, uint64_t val) fdt64_t tmp = cpu_to_fdt64(val); return fdt_property(fdt, name, &tmp, sizeof(tmp)); } - -#ifndef SWIG /* Not available in Python */ static inline int fdt_property_cell(void *fdt, const char *name, uint32_t val) { return fdt_property_u32(fdt, name, val); } -#endif /** * fdt_property_placeholder - add a new property and return a ptr to its value @@ -1897,43 +1765,6 @@ static inline int fdt_appendprop_cell(void *fdt, int nodeoffset, #define fdt_appendprop_string(fdt, nodeoffset, name, str) \ fdt_appendprop((fdt), (nodeoffset), (name), (str), strlen(str)+1) -/** - * fdt_appendprop_addrrange - append a address range property - * @fdt: pointer to the device tree blob - * @parent: offset of the parent node - * @nodeoffset: offset of the node to add a property at - * @name: name of property - * @addr: start address of a given range - * @size: size of a given range - * - * fdt_appendprop_addrrange() appends an address range value (start - * address and size) to the value of the named property in the given - * node, or creates a new property with that value if it does not - * already exist. - * If "name" is not specified, a default "reg" is used. - * Cell sizes are determined by parent's #address-cells and #size-cells. - * - * This function may insert data into the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid - * #address-cells property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADVALUE, addr or size doesn't fit to respective cells size - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain a new property - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_appendprop_addrrange(void *fdt, int parent, int nodeoffset, - const char *name, uint64_t addr, uint64_t size); - /** * fdt_delprop - delete a property * @fdt: pointer to the device tree blob diff --git a/scripts/dtc/libfdt/libfdt_env.h b/scripts/dtc/libfdt/libfdt_env.h index 73b6d40450ac..bd2474628775 100644 --- a/scripts/dtc/libfdt/libfdt_env.h +++ b/scripts/dtc/libfdt/libfdt_env.h @@ -1,18 +1,61 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ #ifndef LIBFDT_ENV_H #define LIBFDT_ENV_H /* * libfdt - Flat Device Tree manipulation * Copyright (C) 2006 David Gibson, IBM Corporation. * Copyright 2012 Kim Phillips, Freescale Semiconductor. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include #include #include #include #include -#include #ifdef __CHECKER__ #define FDT_FORCE __attribute__((force)) diff --git a/scripts/dtc/libfdt/libfdt_internal.h b/scripts/dtc/libfdt/libfdt_internal.h index 7830e550c37a..7681e192295b 100644 --- a/scripts/dtc/libfdt/libfdt_internal.h +++ b/scripts/dtc/libfdt/libfdt_internal.h @@ -1,20 +1,64 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ #ifndef LIBFDT_INTERNAL_H #define LIBFDT_INTERNAL_H /* * libfdt - Flat Device Tree manipulation * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #define FDT_ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1)) #define FDT_TAGALIGN(x) (FDT_ALIGN((x), FDT_TAGSIZE)) -int fdt_ro_probe_(const void *fdt); -#define FDT_RO_PROBE(fdt) \ +#define FDT_CHECK_HEADER(fdt) \ { \ int err_; \ - if ((err_ = fdt_ro_probe_(fdt)) != 0) \ + if ((err_ = fdt_check_header(fdt)) != 0) \ return err_; \ } diff --git a/scripts/dtc/livetree.c b/scripts/dtc/livetree.c index 0c039993953a..6e4c367f54b3 100644 --- a/scripts/dtc/livetree.c +++ b/scripts/dtc/livetree.c @@ -1,10 +1,24 @@ -// SPDX-License-Identifier: GPL-2.0-or-later /* * (C) Copyright David Gibson , IBM Corporation. 2005. + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA */ #include "dtc.h" -#include "srcpos.h" /* * Tree building functions @@ -36,8 +50,7 @@ void delete_labels(struct label **labels) label->deleted = 1; } -struct property *build_property(char *name, struct data val, - struct srcpos *srcpos) +struct property *build_property(char *name, struct data val) { struct property *new = xmalloc(sizeof(*new)); @@ -45,7 +58,6 @@ struct property *build_property(char *name, struct data val, new->name = name; new->val = val; - new->srcpos = srcpos_copy(srcpos); return new; } @@ -85,8 +97,7 @@ struct property *reverse_properties(struct property *first) return head; } -struct node *build_node(struct property *proplist, struct node *children, - struct srcpos *srcpos) +struct node *build_node(struct property *proplist, struct node *children) { struct node *new = xmalloc(sizeof(*new)); struct node *child; @@ -95,7 +106,6 @@ struct node *build_node(struct property *proplist, struct node *children, new->proplist = reverse_properties(proplist); new->children = children; - new->srcpos = srcpos_copy(srcpos); for_each_child(new, child) { child->parent = new; @@ -104,14 +114,13 @@ struct node *build_node(struct property *proplist, struct node *children, return new; } -struct node *build_node_delete(struct srcpos *srcpos) +struct node *build_node_delete(void) { struct node *new = xmalloc(sizeof(*new)); memset(new, 0, sizeof(*new)); new->deleted = 1; - new->srcpos = srcpos_copy(srcpos); return new; } @@ -174,8 +183,6 @@ struct node *merge_nodes(struct node *old_node, struct node *new_node) old_prop->val = new_prop->val; old_prop->deleted = 0; - free(old_prop->srcpos); - old_prop->srcpos = new_prop->srcpos; free(new_prop); new_prop = NULL; break; @@ -216,8 +223,6 @@ struct node *merge_nodes(struct node *old_node, struct node *new_node) add_child(old_node, new_child); } - old_node->srcpos = srcpos_extend(old_node->srcpos, new_node->srcpos); - /* The new node contents are now merged into the old node. Free * the new node. */ free(new_node); @@ -234,21 +239,20 @@ struct node * add_orphan_node(struct node *dt, struct node *new_node, char *ref) char *name; if (ref[0] == '/') { - d = data_add_marker(d, TYPE_STRING, ref); d = data_append_data(d, ref, strlen(ref) + 1); - p = build_property("target-path", d, NULL); + p = build_property("target-path", d); } else { d = data_add_marker(d, REF_PHANDLE, ref); d = data_append_integer(d, 0xffffffff, 32); - p = build_property("target", d, NULL); + p = build_property("target", d); } xasprintf(&name, "fragment@%u", next_orphan_fragment++); name_node(new_node, "__overlay__"); - node = build_node(p, new_node, NULL); + node = build_node(p, new_node); name_node(node, name); add_child(dt, node); @@ -336,21 +340,18 @@ void delete_node(struct node *node) } void append_to_property(struct node *node, - char *name, const void *data, int len, - enum markertype type) + char *name, const void *data, int len) { struct data d; struct property *p; p = get_property(node, name); if (p) { - d = data_add_marker(p->val, type, name); - d = data_append_data(d, data, len); + d = data_append_data(p->val, data, len); p->val = d; } else { - d = data_add_marker(empty_data, type, name); - d = data_append_data(d, data, len); - p = build_property(name, d, NULL); + d = data_append_data(empty_data, data, len); + p = build_property(name, d); add_property(node, p); } } @@ -593,7 +594,6 @@ struct node *get_node_by_ref(struct node *tree, const char *ref) cell_t get_node_phandle(struct node *root, struct node *node) { static cell_t phandle = 1; /* FIXME: ick, static local */ - struct data d = empty_data; if ((node->phandle != 0) && (node->phandle != -1)) return node->phandle; @@ -603,16 +603,17 @@ cell_t get_node_phandle(struct node *root, struct node *node) node->phandle = phandle; - d = data_add_marker(d, TYPE_UINT32, NULL); - d = data_append_cell(d, phandle); - if (!get_property(node, "linux,phandle") && (phandle_format & PHANDLE_LEGACY)) - add_property(node, build_property("linux,phandle", d, NULL)); + add_property(node, + build_property("linux,phandle", + data_append_cell(empty_data, phandle))); if (!get_property(node, "phandle") && (phandle_format & PHANDLE_EPAPR)) - add_property(node, build_property("phandle", d, NULL)); + add_property(node, + build_property("phandle", + data_append_cell(empty_data, phandle))); /* If the node *does* have a phandle property, we must * be dealing with a self-referencing phandle, which will be @@ -786,7 +787,7 @@ static struct node *build_and_name_child_node(struct node *parent, char *name) { struct node *node; - node = build_node(NULL, NULL, NULL); + node = build_node(NULL, NULL); name_node(node, xstrdup(name)); add_child(parent, node); @@ -847,9 +848,8 @@ static void generate_label_tree_internal(struct dt_info *dti, /* insert it */ p = build_property(l->label, - data_copy_escape_string(node->fullpath, - strlen(node->fullpath)), - NULL); + data_copy_mem(node->fullpath, + strlen(node->fullpath) + 1)); add_property(an, p); } @@ -899,7 +899,7 @@ static void add_fixup_entry(struct dt_info *dti, struct node *fn, xasprintf(&entry, "%s:%s:%u", node->fullpath, prop->name, m->offset); - append_to_property(fn, m->ref, entry, strlen(entry) + 1, TYPE_STRING); + append_to_property(fn, m->ref, entry, strlen(entry) + 1); free(entry); } @@ -959,7 +959,7 @@ static void add_local_fixup_entry(struct dt_info *dti, char **compp; int i, depth; - /* walk back retrieving depth */ + /* walk back retreiving depth */ depth = 0; for (wn = node; wn; wn = wn->parent) depth++; @@ -982,7 +982,7 @@ static void add_local_fixup_entry(struct dt_info *dti, free(compp); value_32 = cpu_to_fdt32(m->offset); - append_to_property(wn, prop->name, &value_32, sizeof(value_32), TYPE_UINT32); + append_to_property(wn, prop->name, &value_32, sizeof(value_32)); } static void generate_local_fixups_tree_internal(struct dt_info *dti, diff --git a/scripts/dtc/srcpos.c b/scripts/dtc/srcpos.c index f5205fb9c1ff..cb6ed0e3e5e4 100644 --- a/scripts/dtc/srcpos.c +++ b/scripts/dtc/srcpos.c @@ -1,6 +1,20 @@ -// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright 2007 Jon Loeliger, Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA */ #define _GNU_SOURCE @@ -19,9 +33,6 @@ struct search_path { /* This is the list of directories that we search for source files */ static struct search_path *search_path_head, **search_path_tail; -/* Detect infinite include recursion. */ -#define MAX_SRCFILE_DEPTH (100) -static int srcfile_depth; /* = 0 */ static char *get_dirname(const char *path) { @@ -40,51 +51,11 @@ static char *get_dirname(const char *path) FILE *depfile; /* = NULL */ struct srcfile_state *current_srcfile; /* = NULL */ -static char *initial_path; /* = NULL */ -static int initial_pathlen; /* = 0 */ -static bool initial_cpp = true; -static void set_initial_path(char *fname) -{ - int i, len = strlen(fname); +/* Detect infinite include recursion. */ +#define MAX_SRCFILE_DEPTH (100) +static int srcfile_depth; /* = 0 */ - xasprintf(&initial_path, "%s", fname); - initial_pathlen = 0; - for (i = 0; i != len; i++) - if (initial_path[i] == '/') - initial_pathlen++; -} - -static char *shorten_to_initial_path(char *fname) -{ - char *p1, *p2, *prevslash1 = NULL; - int slashes = 0; - - for (p1 = fname, p2 = initial_path; *p1 && *p2; p1++, p2++) { - if (*p1 != *p2) - break; - if (*p1 == '/') { - prevslash1 = p1; - slashes++; - } - } - p1 = prevslash1 + 1; - if (prevslash1) { - int diff = initial_pathlen - slashes, i, j; - int restlen = strlen(fname) - (p1 - fname); - char *res; - - res = xmalloc((3 * diff) + restlen + 1); - for (i = 0, j = 0; i != diff; i++) { - res[j++] = '.'; - res[j++] = '.'; - res[j++] = '/'; - } - strcpy(res + j, p1); - return res; - } - return NULL; -} /** * Try to open a file in a given directory. @@ -186,9 +157,6 @@ void srcfile_push(const char *fname) srcfile->colno = 1; current_srcfile = srcfile; - - if (srcfile_depth == 1) - set_initial_path(srcfile->name); } bool srcfile_pop(void) @@ -229,6 +197,18 @@ void srcfile_add_search_path(const char *dirname) search_path_tail = &node->next; } +/* + * The empty source position. + */ + +struct srcpos srcpos_empty = { + .first_line = 0, + .first_column = 0, + .last_line = 0, + .last_column = 0, + .file = NULL, +}; + void srcpos_update(struct srcpos *pos, const char *text, int len) { int i; @@ -254,35 +234,13 @@ struct srcpos * srcpos_copy(struct srcpos *pos) { struct srcpos *pos_new; - struct srcfile_state *srcfile_state; - - if (!pos) - return NULL; pos_new = xmalloc(sizeof(struct srcpos)); - assert(pos->next == NULL); memcpy(pos_new, pos, sizeof(struct srcpos)); - /* allocate without free */ - srcfile_state = xmalloc(sizeof(struct srcfile_state)); - memcpy(srcfile_state, pos->file, sizeof(struct srcfile_state)); - pos_new->file = srcfile_state; - return pos_new; } -struct srcpos *srcpos_extend(struct srcpos *pos, struct srcpos *newtail) -{ - struct srcpos *p; - - if (!pos) - return newtail; - - for (p = pos; p->next != NULL; p = p->next); - p->next = newtail; - return pos; -} - char * srcpos_string(struct srcpos *pos) { @@ -308,68 +266,6 @@ srcpos_string(struct srcpos *pos) return pos_str; } -static char * -srcpos_string_comment(struct srcpos *pos, bool first_line, int level) -{ - char *pos_str, *fname, *first, *rest; - bool fresh_fname = false; - - if (!pos) { - if (level > 1) { - xasprintf(&pos_str, ":"); - return pos_str; - } else { - return NULL; - } - } - - if (!pos->file) - fname = ""; - else if (!pos->file->name) - fname = ""; - else if (level > 1) - fname = pos->file->name; - else { - fname = shorten_to_initial_path(pos->file->name); - if (fname) - fresh_fname = true; - else - fname = pos->file->name; - } - - if (level > 1) - xasprintf(&first, "%s:%d:%d-%d:%d", fname, - pos->first_line, pos->first_column, - pos->last_line, pos->last_column); - else - xasprintf(&first, "%s:%d", fname, - first_line ? pos->first_line : pos->last_line); - - if (fresh_fname) - free(fname); - - if (pos->next != NULL) { - rest = srcpos_string_comment(pos->next, first_line, level); - xasprintf(&pos_str, "%s, %s", first, rest); - free(first); - free(rest); - } else { - pos_str = first; - } - - return pos_str; -} - -char *srcpos_string_first(struct srcpos *pos, int level) -{ - return srcpos_string_comment(pos, true, level); -} - -char *srcpos_string_last(struct srcpos *pos, int level) -{ - return srcpos_string_comment(pos, false, level); -} - void srcpos_verror(struct srcpos *pos, const char *prefix, const char *fmt, va_list va) { @@ -398,9 +294,4 @@ void srcpos_set_line(char *f, int l) { current_srcfile->name = f; current_srcfile->lineno = l; - - if (initial_cpp) { - initial_cpp = false; - set_initial_path(f); - } } diff --git a/scripts/dtc/srcpos.h b/scripts/dtc/srcpos.h index 4318d7ad34d9..9ded12a3830a 100644 --- a/scripts/dtc/srcpos.h +++ b/scripts/dtc/srcpos.h @@ -1,6 +1,20 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright 2007 Jon Loeliger, Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA */ #ifndef SRCPOS_H @@ -60,7 +74,6 @@ struct srcpos { int last_line; int last_column; struct srcfile_state *file; - struct srcpos *next; }; #define YYLTYPE struct srcpos @@ -80,18 +93,19 @@ struct srcpos { YYRHSLOC(Rhs, 0).last_column; \ (Current).file = YYRHSLOC (Rhs, 0).file; \ } \ - (Current).next = NULL; \ } while (0) +/* + * Fictional source position used for IR nodes that are + * created without otherwise knowing a true source position. + * For example,constant definitions from the command line. + */ +extern struct srcpos srcpos_empty; + extern void srcpos_update(struct srcpos *pos, const char *text, int len); extern struct srcpos *srcpos_copy(struct srcpos *pos); -extern struct srcpos *srcpos_extend(struct srcpos *new_srcpos, - struct srcpos *old_srcpos); extern char *srcpos_string(struct srcpos *pos); -extern char *srcpos_string_first(struct srcpos *pos, int level); -extern char *srcpos_string_last(struct srcpos *pos, int level); - extern void PRINTF(3, 0) srcpos_verror(struct srcpos *pos, const char *prefix, const char *fmt, va_list va); diff --git a/scripts/dtc/treesource.c b/scripts/dtc/treesource.c index c9d980c8abfc..2461a3d068a0 100644 --- a/scripts/dtc/treesource.c +++ b/scripts/dtc/treesource.c @@ -1,6 +1,21 @@ -// SPDX-License-Identifier: GPL-2.0-or-later /* * (C) Copyright David Gibson , IBM Corporation. 2005. + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA */ #include "dtc.h" @@ -46,18 +61,24 @@ static bool isstring(char c) || strchr("\a\b\t\n\v\f\r", c)); } -static void write_propval_string(FILE *f, const char *s, size_t len) +static void write_propval_string(FILE *f, struct data val) { - const char *end = s + len - 1; + const char *str = val.val; + int i; + struct marker *m = val.markers; - if (!len) - return; - - assert(*end == '\0'); + assert(str[val.len-1] == '\0'); + while (m && (m->offset == 0)) { + if (m->type == LABEL) + fprintf(f, "%s: ", m->ref); + m = m->next; + } fprintf(f, "\""); - while (s < end) { - char c = *s++; + + for (i = 0; i < (val.len-1); i++) { + char c = str[i]; + switch (c) { case '\a': fprintf(f, "\\a"); @@ -87,80 +108,91 @@ static void write_propval_string(FILE *f, const char *s, size_t len) fprintf(f, "\\\""); break; case '\0': - fprintf(f, "\\0"); + fprintf(f, "\", "); + while (m && (m->offset <= (i + 1))) { + if (m->type == LABEL) { + assert(m->offset == (i+1)); + fprintf(f, "%s: ", m->ref); + } + m = m->next; + } + fprintf(f, "\""); break; default: if (isprint((unsigned char)c)) fprintf(f, "%c", c); else - fprintf(f, "\\x%02"PRIx8, c); + fprintf(f, "\\x%02hhx", c); } } fprintf(f, "\""); -} -static void write_propval_int(FILE *f, const char *p, size_t len, size_t width) -{ - const char *end = p + len; - assert(len % width == 0); - - for (; p < end; p += width) { - switch (width) { - case 1: - fprintf(f, "%02"PRIx8, *(const uint8_t*)p); - break; - case 2: - fprintf(f, "0x%02"PRIx16, fdt16_to_cpu(*(const fdt16_t*)p)); - break; - case 4: - fprintf(f, "0x%02"PRIx32, fdt32_to_cpu(*(const fdt32_t*)p)); - break; - case 8: - fprintf(f, "0x%02"PRIx64, fdt64_to_cpu(*(const fdt64_t*)p)); - break; - } - if (p + width < end) - fputc(' ', f); + /* Wrap up any labels at the end of the value */ + for_each_marker_of_type(m, LABEL) { + assert (m->offset == val.len); + fprintf(f, " %s:", m->ref); } } -static bool has_data_type_information(struct marker *m) +static void write_propval_cells(FILE *f, struct data val) { - return m->type >= TYPE_UINT8; + void *propend = val.val + val.len; + fdt32_t *cp = (fdt32_t *)val.val; + struct marker *m = val.markers; + + fprintf(f, "<"); + for (;;) { + while (m && (m->offset <= ((char *)cp - val.val))) { + if (m->type == LABEL) { + assert(m->offset == ((char *)cp - val.val)); + fprintf(f, "%s: ", m->ref); + } + m = m->next; + } + + fprintf(f, "0x%x", fdt32_to_cpu(*cp++)); + if ((void *)cp >= propend) + break; + fprintf(f, " "); + } + + /* Wrap up any labels at the end of the value */ + for_each_marker_of_type(m, LABEL) { + assert (m->offset == val.len); + fprintf(f, " %s:", m->ref); + } + fprintf(f, ">"); } -static struct marker *next_type_marker(struct marker *m) +static void write_propval_bytes(FILE *f, struct data val) { - while (m && !has_data_type_information(m)) - m = m->next; - return m; + void *propend = val.val + val.len; + const char *bp = val.val; + struct marker *m = val.markers; + + fprintf(f, "["); + for (;;) { + while (m && (m->offset == (bp-val.val))) { + if (m->type == LABEL) + fprintf(f, "%s: ", m->ref); + m = m->next; + } + + fprintf(f, "%02hhx", (unsigned char)(*bp++)); + if ((const void *)bp >= propend) + break; + fprintf(f, " "); + } + + /* Wrap up any labels at the end of the value */ + for_each_marker_of_type(m, LABEL) { + assert (m->offset == val.len); + fprintf(f, " %s:", m->ref); + } + fprintf(f, "]"); } -size_t type_marker_length(struct marker *m) -{ - struct marker *next = next_type_marker(m->next); - - if (next) - return next->offset - m->offset; - return 0; -} - -static const char *delim_start[] = { - [TYPE_UINT8] = "[", - [TYPE_UINT16] = "/bits/ 16 <", - [TYPE_UINT32] = "<", - [TYPE_UINT64] = "/bits/ 64 <", - [TYPE_STRING] = "", -}; -static const char *delim_end[] = { - [TYPE_UINT8] = "]", - [TYPE_UINT16] = ">", - [TYPE_UINT32] = ">", - [TYPE_UINT64] = ">", - [TYPE_STRING] = "", -}; - -static enum markertype guess_value_type(struct property *prop) +static void write_propval(FILE *f, struct property *prop) { int len = prop->val.len; const char *p = prop->val.val; @@ -169,6 +201,11 @@ static enum markertype guess_value_type(struct property *prop) int nnotstringlbl = 0, nnotcelllbl = 0; int i; + if (len == 0) { + fprintf(f, ";\n"); + return; + } + for (i = 0; i < len; i++) { if (! isstring(p[i])) nnotstring++; @@ -183,99 +220,17 @@ static enum markertype guess_value_type(struct property *prop) nnotcelllbl++; } + fprintf(f, " = "); if ((p[len-1] == '\0') && (nnotstring == 0) && (nnul < (len-nnul)) && (nnotstringlbl == 0)) { - return TYPE_STRING; + write_propval_string(f, prop->val); } else if (((len % sizeof(cell_t)) == 0) && (nnotcelllbl == 0)) { - return TYPE_UINT32; + write_propval_cells(f, prop->val); + } else { + write_propval_bytes(f, prop->val); } - return TYPE_UINT8; -} - -static void write_propval(FILE *f, struct property *prop) -{ - size_t len = prop->val.len; - struct marker *m = prop->val.markers; - struct marker dummy_marker; - enum markertype emit_type = TYPE_NONE; - char *srcstr; - - if (len == 0) { - fprintf(f, ";"); - if (annotate) { - srcstr = srcpos_string_first(prop->srcpos, annotate); - if (srcstr) { - fprintf(f, " /* %s */", srcstr); - free(srcstr); - } - } - fprintf(f, "\n"); - return; - } - - fprintf(f, " ="); - - if (!next_type_marker(m)) { - /* data type information missing, need to guess */ - dummy_marker.type = guess_value_type(prop); - dummy_marker.next = prop->val.markers; - dummy_marker.offset = 0; - dummy_marker.ref = NULL; - m = &dummy_marker; - } - - for_each_marker(m) { - size_t chunk_len = (m->next ? m->next->offset : len) - m->offset; - size_t data_len = type_marker_length(m) ? : len - m->offset; - const char *p = &prop->val.val[m->offset]; - - if (has_data_type_information(m)) { - emit_type = m->type; - fprintf(f, " %s", delim_start[emit_type]); - } else if (m->type == LABEL) - fprintf(f, " %s:", m->ref); - else if (m->offset) - fputc(' ', f); - - if (emit_type == TYPE_NONE) { - assert(chunk_len == 0); - continue; - } - - switch(emit_type) { - case TYPE_UINT16: - write_propval_int(f, p, chunk_len, 2); - break; - case TYPE_UINT32: - write_propval_int(f, p, chunk_len, 4); - break; - case TYPE_UINT64: - write_propval_int(f, p, chunk_len, 8); - break; - case TYPE_STRING: - write_propval_string(f, p, chunk_len); - break; - default: - write_propval_int(f, p, chunk_len, 1); - } - - if (chunk_len == data_len) { - size_t pos = m->offset + chunk_len; - fprintf(f, pos == len ? "%s" : "%s,", - delim_end[emit_type] ? : ""); - emit_type = TYPE_NONE; - } - } - fprintf(f, ";"); - if (annotate) { - srcstr = srcpos_string_first(prop->srcpos, annotate); - if (srcstr) { - fprintf(f, " /* %s */", srcstr); - free(srcstr); - } - } - fprintf(f, "\n"); + fprintf(f, ";\n"); } static void write_tree_source_node(FILE *f, struct node *tree, int level) @@ -283,24 +238,14 @@ static void write_tree_source_node(FILE *f, struct node *tree, int level) struct property *prop; struct node *child; struct label *l; - char *srcstr; write_prefix(f, level); for_each_label(tree->labels, l) fprintf(f, "%s: ", l->label); if (tree->name && (*tree->name)) - fprintf(f, "%s {", tree->name); + fprintf(f, "%s {\n", tree->name); else - fprintf(f, "/ {"); - - if (annotate) { - srcstr = srcpos_string_first(tree->srcpos, annotate); - if (srcstr) { - fprintf(f, " /* %s */", srcstr); - free(srcstr); - } - } - fprintf(f, "\n"); + fprintf(f, "/ {\n"); for_each_property(tree, prop) { write_prefix(f, level+1); @@ -314,17 +259,10 @@ static void write_tree_source_node(FILE *f, struct node *tree, int level) write_tree_source_node(f, child, level+1); } write_prefix(f, level); - fprintf(f, "};"); - if (annotate) { - srcstr = srcpos_string_last(tree->srcpos, annotate); - if (srcstr) { - fprintf(f, " /* %s */", srcstr); - free(srcstr); - } - } - fprintf(f, "\n"); + fprintf(f, "};\n"); } + void dt_to_source(FILE *f, struct dt_info *dti) { struct reserve_info *re; @@ -343,3 +281,4 @@ void dt_to_source(FILE *f, struct dt_info *dti) write_tree_source_node(f, dti->dt, 0); } + diff --git a/scripts/dtc/update-dtc-source.sh b/scripts/dtc/update-dtc-source.sh index 7dd29a0362b8..1a009fd195d0 100755 --- a/scripts/dtc/update-dtc-source.sh +++ b/scripts/dtc/update-dtc-source.sh @@ -32,7 +32,7 @@ DTC_UPSTREAM_PATH=`pwd`/../dtc DTC_LINUX_PATH=`pwd`/scripts/dtc DTC_SOURCE="checks.c data.c dtc.c dtc.h flattree.c fstree.c livetree.c srcpos.c \ - srcpos.h treesource.c util.c util.h version_gen.h yamltree.c Makefile.dtc \ + srcpos.h treesource.c util.c util.h version_gen.h Makefile.dtc \ dtc-lexer.l dtc-parser.y" LIBFDT_SOURCE="Makefile.libfdt fdt.c fdt.h fdt_addresses.c fdt_empty_tree.c \ fdt_overlay.c fdt_ro.c fdt_rw.c fdt_strerror.c fdt_sw.c \ diff --git a/scripts/dtc/util.c b/scripts/dtc/util.c index 48af961dcc8c..9953c32a0244 100644 --- a/scripts/dtc/util.c +++ b/scripts/dtc/util.c @@ -1,10 +1,24 @@ -// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright 2011 The Chromium Authors, All Rights Reserved. * Copyright 2008 Jon Loeliger, Freescale Semiconductor, Inc. * * util_is_printable_string contributed by * Pantelis Antoniou + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA */ #include @@ -32,52 +46,34 @@ char *xstrdup(const char *s) return d; } -int xavsprintf_append(char **strp, const char *fmt, va_list ap) -{ - int n, size = 0; /* start with 128 bytes */ - char *p; - va_list ap_copy; - - p = *strp; - if (p) - size = strlen(p); - - va_copy(ap_copy, ap); - n = vsnprintf(NULL, 0, fmt, ap_copy) + 1; - va_end(ap_copy); - - p = xrealloc(p, size + n); - - n = vsnprintf(p + size, n, fmt, ap); - - *strp = p; - return strlen(p); -} - -int xasprintf_append(char **strp, const char *fmt, ...) -{ - int n; - va_list ap; - - va_start(ap, fmt); - n = xavsprintf_append(strp, fmt, ap); - va_end(ap); - - return n; -} - +/* based in part from (3) vsnprintf */ int xasprintf(char **strp, const char *fmt, ...) { - int n; + int n, size = 128; /* start with 128 bytes */ + char *p; va_list ap; - *strp = NULL; + /* initial pointer is NULL making the fist realloc to be malloc */ + p = NULL; + while (1) { + p = xrealloc(p, size); - va_start(ap, fmt); - n = xavsprintf_append(strp, fmt, ap); - va_end(ap); + /* Try to print in the allocated space. */ + va_start(ap, fmt); + n = vsnprintf(p, size, fmt, ap); + va_end(ap); - return n; + /* If that worked, return the string. */ + if (n > -1 && n < size) + break; + /* Else try again with more space. */ + if (n > -1) /* glibc 2.1 */ + size = n + 1; /* precisely what is needed */ + else /* glibc 2.0 */ + size *= 2; /* twice the old size */ + } + *strp = p; + return strlen(p); } char *join_path(const char *path, const char *name) @@ -231,11 +227,11 @@ char get_escape_char(const char *s, int *i) return val; } -int utilfdt_read_err(const char *filename, char **buffp, size_t *len) +int utilfdt_read_err_len(const char *filename, char **buffp, off_t *len) { int fd = 0; /* assume stdin */ char *buf = NULL; - size_t bufsize = 1024, offset = 0; + off_t bufsize = 1024, offset = 0; int ret = 0; *buffp = NULL; @@ -268,15 +264,20 @@ int utilfdt_read_err(const char *filename, char **buffp, size_t *len) free(buf); else *buffp = buf; - if (len) - *len = bufsize; + *len = bufsize; return ret; } -char *utilfdt_read(const char *filename, size_t *len) +int utilfdt_read_err(const char *filename, char **buffp) +{ + off_t len; + return utilfdt_read_err_len(filename, buffp, &len); +} + +char *utilfdt_read_len(const char *filename, off_t *len) { char *buff; - int ret = utilfdt_read_err(filename, &buff, len); + int ret = utilfdt_read_err_len(filename, &buff, len); if (ret) { fprintf(stderr, "Couldn't open blob from '%s': %s\n", filename, @@ -287,6 +288,12 @@ char *utilfdt_read(const char *filename, size_t *len) return buff; } +char *utilfdt_read(const char *filename) +{ + off_t len; + return utilfdt_read_len(filename, &len); +} + int utilfdt_write_err(const char *filename, const void *blob) { int fd = 1; /* assume stdout */ diff --git a/scripts/dtc/util.h b/scripts/dtc/util.h index ca5cb52928e3..66fba8ea709b 100644 --- a/scripts/dtc/util.h +++ b/scripts/dtc/util.h @@ -1,4 +1,3 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef UTIL_H #define UTIL_H @@ -9,6 +8,21 @@ /* * Copyright 2011 The Chromium Authors, All Rights Reserved. * Copyright 2008 Jon Loeliger, Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA */ #ifdef __GNUC__ @@ -58,8 +72,6 @@ static inline void *xrealloc(void *p, size_t len) extern char *xstrdup(const char *s); extern int PRINTF(2, 3) xasprintf(char **strp, const char *fmt, ...); -extern int PRINTF(2, 3) xasprintf_append(char **strp, const char *fmt, ...); -extern int xavsprintf_append(char **strp, const char *fmt, va_list ap); extern char *join_path(const char *path, const char *name); /** @@ -86,10 +98,16 @@ char get_escape_char(const char *s, int *i); * stderr. * * @param filename The filename to read, or - for stdin - * @param len If non-NULL, the amount of data we managed to read * @return Pointer to allocated buffer containing fdt, or NULL on error */ -char *utilfdt_read(const char *filename, size_t *len); +char *utilfdt_read(const char *filename); + +/** + * Like utilfdt_read(), but also passes back the size of the file read. + * + * @param len If non-NULL, the amount of data we managed to read + */ +char *utilfdt_read_len(const char *filename, off_t *len); /** * Read a device tree file into a buffer. Does not report errors, but only @@ -98,17 +116,23 @@ char *utilfdt_read(const char *filename, size_t *len); * * @param filename The filename to read, or - for stdin * @param buffp Returns pointer to buffer containing fdt - * @param len If non-NULL, the amount of data we managed to read * @return 0 if ok, else an errno value representing the error */ -int utilfdt_read_err(const char *filename, char **buffp, size_t *len); +int utilfdt_read_err(const char *filename, char **buffp); + +/** + * Like utilfdt_read_err(), but also passes back the size of the file read. + * + * @param len If non-NULL, the amount of data we managed to read + */ +int utilfdt_read_err_len(const char *filename, char **buffp, off_t *len); /** * Write a device tree buffer to a file. This will report any errors on * stderr. * * @param filename The filename to write, or - for stdout - * @param blob Pointer to buffer containing fdt + * @param blob Poiner to buffer containing fdt * @return 0 if ok, -1 on error */ int utilfdt_write(const char *filename, const void *blob); @@ -119,7 +143,7 @@ int utilfdt_write(const char *filename, const void *blob); * an error message for the user. * * @param filename The filename to write, or - for stdout - * @param blob Pointer to buffer containing fdt + * @param blob Poiner to buffer containing fdt * @return 0 if ok, else an errno value representing the error */ int utilfdt_write_err(const char *filename, const void *blob); diff --git a/scripts/dtc/version_gen.h b/scripts/dtc/version_gen.h index f2761e24cf40..b00f14ff7a17 100644 --- a/scripts/dtc/version_gen.h +++ b/scripts/dtc/version_gen.h @@ -1 +1 @@ -#define DTC_VERSION "DTC 1.5.0-g702c1b6c" +#define DTC_VERSION "DTC 1.4.6-g84e414b0" diff --git a/scripts/setlocalversion b/scripts/setlocalversion index e02959536644..c2e8455b3aeb 100755 --- a/scripts/setlocalversion +++ b/scripts/setlocalversion @@ -204,6 +204,15 @@ res="${res}${CONFIG_LOCALVERSION}${LOCALVERSION}" if test "$CONFIG_LOCALVERSION_AUTO" = "y"; then # full scm version string res="$res$(scm_version)" +else + # append a plus sign if the repository is not in a clean + # annotated or signed tagged state (as git describe only + # looks at signed or annotated tags - git tag -a/-s) and + # LOCALVERSION= is not specified + if test "${LOCALVERSION+set}" != "set"; then + scm=$(scm_version --short) + res="$res${scm:++}" + fi fi echo "$res" diff --git a/security/Kconfig.hardening b/security/Kconfig.hardening index 059fc4dc2312..234250ce0b6c 100644 --- a/security/Kconfig.hardening +++ b/security/Kconfig.hardening @@ -25,22 +25,71 @@ config CC_HAS_AUTO_VAR_INIT_PATTERN config CC_HAS_AUTO_VAR_INIT_ZERO def_bool $(cc-option,-ftrivial-auto-var-init=zero -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang) -config INIT_STACK_NONE - bool "no automatic initialization (weakest)" - default y +choice + prompt "Initialize kernel stack variables at function entry" + default GCC_PLUGIN_STRUCTLEAK_BYREF_ALL if COMPILE_TEST && GCC_PLUGINS + default INIT_STACK_ALL_PATTERN if COMPILE_TEST && CC_HAS_AUTO_VAR_INIT_PATTERN + default INIT_STACK_NONE help - Disable automatic stack variable initialization. - This leaves the kernel vulnerable to the standard - classes of uninitialized stack variable exploits - and information exposures. + This option enables initialization of stack variables at + function entry time. This has the possibility to have the + greatest coverage (since all functions can have their + variables initialized), but the performance impact depends + on the function calling complexity of a given workload's + syscalls. -config GCC_PLUGIN_STRUCTLEAK_BYREF_ALL - bool "Force initialize all struct type variables passed by reference" - depends on GCC_PLUGIN_STRUCTLEAK - depends on !COMPILE_TEST - help - Zero initialize any struct type local variable that may - be passed by reference without having been initialized. + This chooses the level of coverage over classes of potentially + uninitialized variables. The selected class will be + initialized before use in a function. + + config INIT_STACK_NONE + bool "no automatic initialization (weakest)" + help + Disable automatic stack variable initialization. + This leaves the kernel vulnerable to the standard + classes of uninitialized stack variable exploits + and information exposures. + + config GCC_PLUGIN_STRUCTLEAK_BYREF_ALL + bool "zero-init anything passed by reference (very strong)" + depends on GCC_PLUGINS + select GCC_PLUGIN_STRUCTLEAK + help + Zero-initialize any stack variables that may be passed + by reference and had not already been explicitly + initialized. This is intended to eliminate all classes + of uninitialized stack variable exploits and information + exposures. + + config INIT_STACK_ALL_PATTERN + bool "0xAA-init everything on the stack (strongest)" + depends on CC_HAS_AUTO_VAR_INIT_PATTERN + help + Initializes everything on the stack with a 0xAA + pattern. This is intended to eliminate all classes + of uninitialized stack variable exploits and information + exposures, even variables that were warned to have been + left uninitialized. + + Pattern initialization is known to provoke many existing bugs + related to uninitialized locals, e.g. pointers receive + non-NULL values, buffer sizes and indices are very big. + + config INIT_STACK_ALL_ZERO + bool "zero-init everything on the stack (strongest and safest)" + depends on CC_HAS_AUTO_VAR_INIT_ZERO + help + Initializes everything on the stack with a zero + value. This is intended to eliminate all classes + of uninitialized stack variable exploits and information + exposures, even variables that were warned to have been + left uninitialized. + + Zero initialization provides safe defaults for strings, + pointers, indices and sizes, and is therefore + more suitable as a security mitigation measure. + +endchoice config GCC_PLUGIN_STRUCTLEAK_VERBOSE bool "Report forcefully initialized variables" diff --git a/security/selinux/Kconfig b/security/selinux/Kconfig index 2728517339a1..8297e48a283d 100644 --- a/security/selinux/Kconfig +++ b/security/selinux/Kconfig @@ -1,6 +1,6 @@ config SECURITY_SELINUX bool "NSA SELinux Support" - depends on SECURITY_NETWORK && NET && INET + depends on SECURITY_NETWORK && AUDIT && NET && INET select NETWORK_SECMARK default n help diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index d4e1e63a55d6..305890cab97b 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -100,7 +100,7 @@ #include "audit.h" #include "avc_ss.h" -struct selinux_state selinux_state; +struct selinux_state selinux_state __rticdata; /* SECMARK reference count */ static atomic_t selinux_secmark_refcount = ATOMIC_INIT(0); diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index b4949bc343dc..76ac88f51468 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -40,8 +40,6 @@ static const struct snd_pcm_hardware no_host_hardware = { SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE | - SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE, .period_bytes_min = PAGE_SIZE >> 2, .period_bytes_max = PAGE_SIZE >> 1, diff --git a/techpack/camera/drivers/cam_cdm/cam_cdm_hw_core.c b/techpack/camera/drivers/cam_cdm/cam_cdm_hw_core.c index 4be9e62f261b..55209a62f859 100644 --- a/techpack/camera/drivers/cam_cdm/cam_cdm_hw_core.c +++ b/techpack/camera/drivers/cam_cdm/cam_cdm_hw_core.c @@ -1587,6 +1587,11 @@ int cam_hw_cdm_handle_error_info( set_bit(CAM_CDM_RESET_HW_STATUS, &cdm_core->cdm_status); set_bit(CAM_CDM_FLUSH_HW_STATUS, &cdm_core->cdm_status); + if (cdm_hw->hw_state == CAM_HW_STATE_POWER_DOWN) { + CAM_WARN(CAM_CDM, "CDM is in power down state"); + goto end; + } + /* First pause CDM, If it fails still proceed to dump debug info */ cam_hw_cdm_pause_core(cdm_hw, true); diff --git a/techpack/camera/drivers/cam_ope/ope_hw_mgr/cam_ope_hw_mgr.c b/techpack/camera/drivers/cam_ope/ope_hw_mgr/cam_ope_hw_mgr.c index 0cac5a0a7d0a..dbbd3c89053c 100644 --- a/techpack/camera/drivers/cam_ope/ope_hw_mgr/cam_ope_hw_mgr.c +++ b/techpack/camera/drivers/cam_ope/ope_hw_mgr/cam_ope_hw_mgr.c @@ -707,6 +707,7 @@ static int32_t cam_ope_process_request_timer(void *priv, void *data) task = cam_req_mgr_workq_get_task(ope_hw_mgr->msg_work); if (!task) { CAM_ERR(CAM_OPE, "no empty task"); + mutex_unlock(&ctx_data->ctx_mutex); return 0; } task_data = (struct ope_msg_work_data *)task->payload; @@ -1956,6 +1957,14 @@ static int cam_ope_mgr_process_cmd_io_buf_req(struct cam_ope_hw_mgr *hw_mgr, alignment = in_res->alignment; unpack_format = in_res->unpacker_format; pack_format = 0; + if (in_io_buf->pix_pattern > + PIXEL_PATTERN_CRYCBY) { + CAM_ERR(CAM_OPE, + "Invalid pix pattern = %u", + in_io_buf->pix_pattern); + return -EINVAL; + } + io_buf->pix_pattern = in_io_buf->pix_pattern; } else if (in_io_buf->direction == CAM_BUF_OUTPUT) { out_res = &ctx_data->ope_acquire.out_res[rsc_idx]; @@ -3532,7 +3541,7 @@ static int cam_ope_mgr_hw_open_u(void *hw_priv, void *fw_download_args) return rc; } -static int cam_ope_mgr_hw_close_u(void *hw_priv, void *hw_close_args) +static cam_ope_mgr_hw_close_u(void *hw_priv, void *hw_close_args) { struct cam_ope_hw_mgr *hw_mgr; int rc = 0; diff --git a/techpack/camera/drivers/cam_ope/ope_hw_mgr/cam_ope_hw_mgr.h b/techpack/camera/drivers/cam_ope/ope_hw_mgr/cam_ope_hw_mgr.h index 3d3d11598701..3bc09be0b88c 100644 --- a/techpack/camera/drivers/cam_ope/ope_hw_mgr/cam_ope_hw_mgr.h +++ b/techpack/camera/drivers/cam_ope/ope_hw_mgr/cam_ope_hw_mgr.h @@ -363,6 +363,7 @@ struct ope_io_buf { uint32_t format; uint32_t fence; uint32_t num_planes; + uint32_t pix_pattern; uint32_t num_stripes[OPE_MAX_PLANES]; struct ope_stripe_io s_io[OPE_MAX_PLANES][OPE_MAX_STRIPES]; }; diff --git a/techpack/camera/drivers/cam_ope/ope_hw_mgr/ope_hw/bus_rd/ope_bus_rd.c b/techpack/camera/drivers/cam_ope/ope_hw_mgr/ope_hw/bus_rd/ope_bus_rd.c index 0042675f1821..033df2b6666d 100644 --- a/techpack/camera/drivers/cam_ope/ope_hw_mgr/ope_hw/bus_rd/ope_bus_rd.c +++ b/techpack/camera/drivers/cam_ope/ope_hw_mgr/ope_hw/bus_rd/ope_bus_rd.c @@ -244,7 +244,7 @@ static uint32_t *cam_ope_bus_rd_update(struct ope_hw *ope_hw_info, temp = 0; temp |= stripe_io->s_location & rd_res_val_client->stripe_location_mask; - temp |= (io_port_info->pixel_pattern[rsc_type] & + temp |= (io_buf->pix_pattern & rd_res_val_client->pix_pattern_mask) << rd_res_val_client->pix_pattern_shift; temp_reg[count++] = temp; diff --git a/techpack/camera/drivers/cam_req_mgr/cam_req_mgr_core.c b/techpack/camera/drivers/cam_req_mgr/cam_req_mgr_core.c index 38cfef654cdd..0001a0e5c6ec 100644 --- a/techpack/camera/drivers/cam_req_mgr/cam_req_mgr_core.c +++ b/techpack/camera/drivers/cam_req_mgr/cam_req_mgr_core.c @@ -198,10 +198,16 @@ static void __cam_req_mgr_find_dev_name( if (masked_val & (1 << dev->dev_bit)) continue; - CAM_INFO(CAM_CRM, - "Skip Frame: req: %lld not ready on link: 0x%x for pd: %d dev: %s open_req count: %d", - req_id, link->link_hdl, pd, dev->dev_info.name, - link->open_req_cnt); + if (link->wq_congestion) + CAM_INFO_RATE_LIMIT(CAM_CRM, + "WQ congestion, Skip Frame: req: %lld not ready on link: 0x%x for pd: %d dev: %s open_req count: %d", + req_id, link->link_hdl, pd, + dev->dev_info.name, link->open_req_cnt); + else + CAM_INFO(CAM_CRM, + "Skip Frame: req: %lld not ready on link: 0x%x for pd: %d dev: %s open_req count: %d", + req_id, link->link_hdl, pd, + dev->dev_info.name, link->open_req_cnt); } } } @@ -1371,18 +1377,18 @@ static int __cam_req_mgr_process_req(struct cam_req_mgr_core_link *link, if (slot->status == CRM_SLOT_STATUS_NO_REQ) { CAM_DBG(CAM_CRM, "No Pending req"); rc = 0; - goto error; + goto end; } if ((trigger != CAM_TRIGGER_POINT_SOF) && (trigger != CAM_TRIGGER_POINT_EOF)) - goto error; + goto end; if ((trigger == CAM_TRIGGER_POINT_EOF) && (!(link->trigger_mask & CAM_TRIGGER_POINT_SOF))) { CAM_DBG(CAM_CRM, "Applying for last SOF fails"); rc = -EINVAL; - goto error; + goto end; } if (trigger == CAM_TRIGGER_POINT_SOF) { @@ -1393,11 +1399,19 @@ static int __cam_req_mgr_process_req(struct cam_req_mgr_core_link *link, link->prev_sof_timestamp = link->sof_timestamp; link->sof_timestamp = trigger_data->sof_timestamp_val; + /* Check for WQ congestion */ + if (jiffies_to_msecs(jiffies - + link->last_sof_trigger_jiffies) < + MINIMUM_WORKQUEUE_SCHED_TIME_IN_MS) + link->wq_congestion = true; + else + link->wq_congestion = false; + if (link->trigger_mask) { CAM_ERR_RATE_LIMIT(CAM_CRM, "Applying for last EOF fails"); rc = -EINVAL; - goto error; + goto end; } if ((slot->sync_mode == CAM_REQ_MGR_SYNC_MODE_SYNC) && @@ -1457,7 +1471,7 @@ static int __cam_req_mgr_process_req(struct cam_req_mgr_core_link *link, rc = -EPERM; } spin_unlock_bh(&link->link_state_spin_lock); - goto error; + goto end; } } @@ -1466,24 +1480,30 @@ static int __cam_req_mgr_process_req(struct cam_req_mgr_core_link *link, /* Apply req failed retry at next sof */ slot->status = CRM_SLOT_STATUS_REQ_PENDING; - link->retry_cnt++; - if (link->retry_cnt == MAXIMUM_RETRY_ATTEMPTS) { - CAM_DBG(CAM_CRM, - "Max retry attempts reached on link[0x%x] for req [%lld]", - link->link_hdl, - in_q->slot[in_q->rd_idx].req_id); + if (!link->wq_congestion && dev) { + link->retry_cnt++; + if (link->retry_cnt == MAXIMUM_RETRY_ATTEMPTS) { + CAM_DBG(CAM_CRM, + "Max retry attempts reached on link[0x%x] for req [%lld]", + link->link_hdl, + in_q->slot[in_q->rd_idx].req_id); - cam_req_mgr_debug_delay_detect(); - trace_cam_delay_detect("CRM", - "Max retry attempts reached", - in_q->slot[in_q->rd_idx].req_id, - CAM_DEFAULT_VALUE, - link->link_hdl, - CAM_DEFAULT_VALUE, rc); + cam_req_mgr_debug_delay_detect(); + trace_cam_delay_detect("CRM", + "Max retry attempts reached", + in_q->slot[in_q->rd_idx].req_id, + CAM_DEFAULT_VALUE, + link->link_hdl, + CAM_DEFAULT_VALUE, rc); - __cam_req_mgr_notify_error_on_link(link, dev); - link->retry_cnt = 0; - } + __cam_req_mgr_notify_error_on_link(link, dev); + link->retry_cnt = 0; + } + } else + CAM_WARN_RATE_LIMIT(CAM_CRM, + "workqueue congestion, last applied idx:%d rd idx:%d", + in_q->last_applied_idx, + in_q->rd_idx); } else { if (link->retry_cnt) link->retry_cnt = 0; @@ -1531,10 +1551,9 @@ static int __cam_req_mgr_process_req(struct cam_req_mgr_core_link *link, link->open_req_cnt--; } } - - mutex_unlock(&session->lock); - return rc; -error: +end: + if (trigger == CAM_TRIGGER_POINT_SOF) + link->last_sof_trigger_jiffies = jiffies; mutex_unlock(&session->lock); return rc; } diff --git a/techpack/camera/drivers/cam_req_mgr/cam_req_mgr_core.h b/techpack/camera/drivers/cam_req_mgr/cam_req_mgr_core.h index 09f95e7072ed..b7677ed36ca9 100644 --- a/techpack/camera/drivers/cam_req_mgr/cam_req_mgr_core.h +++ b/techpack/camera/drivers/cam_req_mgr/cam_req_mgr_core.h @@ -36,6 +36,8 @@ #define MAXIMUM_RETRY_ATTEMPTS 2 +#define MINIMUM_WORKQUEUE_SCHED_TIME_IN_MS 5 + #define VERSION_1 1 #define VERSION_2 2 @@ -346,6 +348,8 @@ struct cam_req_mgr_connected_device { * as part of shutdown. * @sof_timestamp_value : SOF timestamp value * @prev_sof_timestamp : Previous SOF timestamp value + * @last_sof_trigger_jiffies : Record the jiffies of last sof trigger jiffies + * @wq_congestion : Indicates if WQ congestion is detected or not */ struct cam_req_mgr_core_link { int32_t link_hdl; @@ -376,6 +380,8 @@ struct cam_req_mgr_core_link { bool is_shutdown; uint64_t sof_timestamp; uint64_t prev_sof_timestamp; + uint64_t last_sof_trigger_jiffies; + bool wq_congestion; }; /** diff --git a/techpack/camera/drivers/cam_req_mgr/cam_req_mgr_dev.c b/techpack/camera/drivers/cam_req_mgr/cam_req_mgr_dev.c index 7552b7348374..72a34c5bae20 100644 --- a/techpack/camera/drivers/cam_req_mgr/cam_req_mgr_dev.c +++ b/techpack/camera/drivers/cam_req_mgr/cam_req_mgr_dev.c @@ -25,7 +25,7 @@ #include "cam_debug_util.h" #include "cam_common_util.h" -#define CAM_REQ_MGR_EVENT_MAX 60 +#define CAM_REQ_MGR_EVENT_MAX 30 static struct cam_req_mgr_device g_dev; struct kmem_cache *g_cam_req_mgr_timer_cachep; diff --git a/techpack/camera/drivers/cam_sensor_module/cam_flash/cam_flash_core.c b/techpack/camera/drivers/cam_sensor_module/cam_flash/cam_flash_core.c index 2caeecc80014..e4f896d886c9 100644 --- a/techpack/camera/drivers/cam_sensor_module/cam_flash/cam_flash_core.c +++ b/techpack/camera/drivers/cam_sensor_module/cam_flash/cam_flash_core.c @@ -11,6 +11,8 @@ #include "cam_common_util.h" #include "cam_packet_util.h" +static struct led_trigger *m_bctorch_trigger = NULL; + static int cam_flash_prepare(struct cam_flash_ctrl *flash_ctrl, bool regulator_enable) { @@ -469,11 +471,13 @@ static int cam_flash_ops(struct cam_flash_ctrl *flash_ctrl, curr = flash_data->led_current_ma[i]; else curr = max_current; - } + CAM_DBG(CAM_FLASH, "Led_Torch[%d]: Current: %d", i, curr); + cam_res_mgr_led_trigger_event( flash_ctrl->torch_trigger[i], curr); + } } } else if (op == CAMERA_SENSOR_FLASH_OP_FIREHIGH) { for (i = 0; i < flash_ctrl->flash_num_sources; i++) { @@ -520,7 +524,7 @@ int cam_flash_off(struct cam_flash_ctrl *flash_ctrl) static int cam_flash_low( struct cam_flash_ctrl *flash_ctrl, - struct cam_flash_frame_setting *flash_data) + struct cam_flash_frame_setting *flash_data,int req_id) { int i = 0, rc = 0; @@ -535,10 +539,27 @@ static int cam_flash_low( flash_ctrl->flash_trigger[i], LED_OFF); - rc = cam_flash_ops(flash_ctrl, flash_data, - CAMERA_SENSOR_FLASH_OP_FIRELOW); - if (rc) - CAM_ERR(CAM_FLASH, "Fire Torch failed: %d", rc); + //hjc++ disable torch_0 when use torch only + if ( flash_ctrl->torch_trigger[0] && req_id == 0 && + flash_ctrl->nrt_info.cmn_attr.cmd_type == CAMERA_SENSOR_FLASH_CMD_TYPE_WIDGET ) + { + pr_err("using torch\n"); + m_bctorch_trigger = flash_ctrl->torch_trigger[0]; + flash_ctrl->torch_trigger[0] = NULL; + } + + rc = cam_flash_ops(flash_ctrl, flash_data, CAMERA_SENSOR_FLASH_OP_FIRELOW); + if (rc) + CAM_ERR(CAM_FLASH, "Fire Torch failed: %d", rc); + + //hjc++ enable torch_0 when use torch only + if(m_bctorch_trigger && req_id == 0 && + flash_ctrl->nrt_info.cmn_attr.cmd_type == CAMERA_SENSOR_FLASH_CMD_TYPE_WIDGET) + { + pr_err("recovery torch paparm\n"); + flash_ctrl->torch_trigger[0] = m_bctorch_trigger; + m_bctorch_trigger = NULL; + } return rc; } @@ -804,7 +825,7 @@ int cam_flash_pmic_apply_setting(struct cam_flash_ctrl *fctrl, return -EINVAL; } - rc = cam_flash_low(fctrl, flash_data); + rc = cam_flash_low(fctrl, flash_data,0); if (rc) CAM_ERR(CAM_FLASH, "TORCH ON failed : %d", rc); @@ -828,7 +849,8 @@ int cam_flash_pmic_apply_setting(struct cam_flash_ctrl *fctrl, if (flash_data->opcode == CAMERA_SENSOR_FLASH_OP_FIRELOW) { - rc = cam_flash_low(fctrl, flash_data); + + rc = cam_flash_low(fctrl, flash_data,0); if (rc) { CAM_ERR(CAM_FLASH, "Torch ON failed : %d", @@ -862,7 +884,7 @@ int cam_flash_pmic_apply_setting(struct cam_flash_ctrl *fctrl, /* Turn On Torch */ if (fctrl->flash_state == CAM_FLASH_STATE_START) { - rc = cam_flash_low(fctrl, flash_data); + rc = cam_flash_low(fctrl, flash_data,0); if (rc) { CAM_ERR(CAM_FLASH, "Fire Torch Failed"); @@ -912,7 +934,7 @@ int cam_flash_pmic_apply_setting(struct cam_flash_ctrl *fctrl, (flash_data->cmn_attr.request_id == req_id)) { /* Turn On Torch */ if (fctrl->flash_state == CAM_FLASH_STATE_START) { - rc = cam_flash_low(fctrl, flash_data); + rc = cam_flash_low(fctrl, flash_data,req_id); if (rc) { CAM_ERR(CAM_FLASH, "Torch ON failed: rc= %d", diff --git a/techpack/camera/drivers/cam_sensor_module/cam_sensor/cam_sensor_core.c b/techpack/camera/drivers/cam_sensor_module/cam_sensor/cam_sensor_core.c index 61d33c5cbaad..746e4d063de1 100644 --- a/techpack/camera/drivers/cam_sensor_module/cam_sensor/cam_sensor_core.c +++ b/techpack/camera/drivers/cam_sensor_module/cam_sensor/cam_sensor_core.c @@ -12,6 +12,9 @@ #include "cam_common_util.h" #include "cam_packet_util.h" +//add by hzt 2021-7-1 for debug +//#undef CAM_DBG +//#define CAM_DBG(fmt,args...) CAM_ERR(fmt,##args) static void cam_sensor_update_req_mgr( struct cam_sensor_ctrl_t *s_ctrl, @@ -1128,7 +1131,10 @@ int cam_sensor_power_up(struct cam_sensor_ctrl_t *s_ctrl) rc = 0; } } - + //add by hzt 2021-9-4 for control external gpio + power_info->imx582_avdd18_gpio=s_ctrl->imx582_avdd18_gpio; + //add by hzt 2021-9-4 for control external gpio + rc = cam_sensor_core_power_up(power_info, soc_info); if (rc < 0) { CAM_ERR(CAM_SENSOR, "power up the core is failed:%d", rc); diff --git a/techpack/camera/drivers/cam_sensor_module/cam_sensor/cam_sensor_dev.h b/techpack/camera/drivers/cam_sensor_module/cam_sensor/cam_sensor_dev.h index b1963e15eb59..135a284b5fd4 100644 --- a/techpack/camera/drivers/cam_sensor_module/cam_sensor/cam_sensor_dev.h +++ b/techpack/camera/drivers/cam_sensor_module/cam_sensor/cam_sensor_dev.h @@ -111,6 +111,9 @@ struct cam_sensor_ctrl_t { bool bob_pwm_switch; uint32_t last_flush_req; uint16_t pipeline_delay; + //add by hzt 2021-9-4 for control external gpio + int imx582_avdd18_gpio; + //add by hzt 2021-9-4 for control external gpio }; #endif /* _CAM_SENSOR_DEV_H_ */ diff --git a/techpack/camera/drivers/cam_sensor_module/cam_sensor/cam_sensor_soc.c b/techpack/camera/drivers/cam_sensor_module/cam_sensor/cam_sensor_soc.c index 2c25ee0aa6f0..0df88cc5b781 100644 --- a/techpack/camera/drivers/cam_sensor_module/cam_sensor/cam_sensor_soc.c +++ b/techpack/camera/drivers/cam_sensor_module/cam_sensor/cam_sensor_soc.c @@ -254,6 +254,14 @@ int32_t cam_sensor_parse_dt(struct cam_sensor_ctrl_t *s_ctrl) return rc; } + //add by hzt 2021-9-4 for control external gpio + + s_ctrl->imx582_avdd18_gpio = of_get_named_gpio(s_ctrl->of_node, "imx582_avdd18,pwr-gpio", 0); + + //add by hzt 2021-9-4 for control external gpio + + + /* Initialize mutex */ mutex_init(&(s_ctrl->cam_sensor_mutex)); diff --git a/techpack/camera/drivers/cam_sensor_module/cam_sensor_utils/cam_sensor_cmn_header.h b/techpack/camera/drivers/cam_sensor_module/cam_sensor_utils/cam_sensor_cmn_header.h index 563414e640cc..017b56036d3d 100644 --- a/techpack/camera/drivers/cam_sensor_module/cam_sensor_utils/cam_sensor_cmn_header.h +++ b/techpack/camera/drivers/cam_sensor_module/cam_sensor_utils/cam_sensor_cmn_header.h @@ -316,6 +316,9 @@ struct cam_sensor_power_ctrl_t { struct msm_camera_gpio_num_info *gpio_num_info; struct msm_pinctrl_info pinctrl_info; uint8_t cam_pinctrl_status; + //add by hzt 2021-9-4 for control external gpio + int imx582_avdd18_gpio; + //add by hzt 2021-9-4 for control external gpio }; struct cam_camera_slave_info { diff --git a/techpack/camera/drivers/cam_sensor_module/cam_sensor_utils/cam_sensor_util.c b/techpack/camera/drivers/cam_sensor_module/cam_sensor_utils/cam_sensor_util.c index 036d5f74230a..9f3c0dff92b0 100644 --- a/techpack/camera/drivers/cam_sensor_module/cam_sensor_utils/cam_sensor_util.c +++ b/techpack/camera/drivers/cam_sensor_module/cam_sensor_utils/cam_sensor_util.c @@ -8,6 +8,13 @@ #include "cam_mem_mgr.h" #include "cam_res_mgr_api.h" + + //add by hzt 2021-7-1 for debug +//#undef CAM_DBG +//#define CAM_DBG(fmt,args...) CAM_ERR(fmt,##args) + + + #define CAM_SENSOR_PINCTRL_STATE_SLEEP "cam_suspend" #define CAM_SENSOR_PINCTRL_STATE_DEFAULT "cam_default" @@ -1765,6 +1772,11 @@ int msm_cam_sensor_handle_reg_gpio(int seq_type, [gpio_offset], val); } + //add by hzt 2021-9-4 for control external gpio + //s_ctrl.imx582_avdd18_gpio = of_get_named_gpio(s_ctrl->of_node, "imx582_avdd18,pwr-gpio", 0); + //add by hzt 2021-9-4 control external gpio + + return 0; } @@ -1879,6 +1891,12 @@ int cam_sensor_core_power_up(struct cam_sensor_power_ctrl_t *ctrl, CAM_DBG(CAM_SENSOR, "power setting size: %d", ctrl->power_setting_size); + + + + + + for (index = 0; index < ctrl->power_setting_size; index++) { CAM_DBG(CAM_SENSOR, "index: %d", index); power_setting = &ctrl->power_setting[index]; @@ -1960,7 +1978,6 @@ int cam_sensor_core_power_up(struct cam_sensor_power_ctrl_t *ctrl, case SENSOR_STANDBY: case SENSOR_CUSTOM_GPIO1: case SENSOR_CUSTOM_GPIO2: - case SENSOR_VAF: if (no_gpio) { CAM_ERR(CAM_SENSOR, "request gpio failed"); goto power_up_failed; @@ -1986,6 +2003,7 @@ int cam_sensor_core_power_up(struct cam_sensor_power_ctrl_t *ctrl, case SENSOR_VANA: case SENSOR_VDIG: case SENSOR_VIO: + case SENSOR_VAF: case SENSOR_VAF_PWDM: case SENSOR_CUSTOM_REG1: case SENSOR_CUSTOM_REG2: @@ -2038,6 +2056,23 @@ int cam_sensor_core_power_up(struct cam_sensor_power_ctrl_t *ctrl, power_setting->seq_val, num_vreg); } + //add by hzt 2021-9-4 for control external gpio + if(power_setting->seq_type==SENSOR_VANA) + { + + CAM_DBG(CAM_SENSOR, "before to request imx582_avdd18_gpio:%d,,\n",ctrl->imx582_avdd18_gpio); + ret = gpio_request(ctrl->imx582_avdd18_gpio, "imx582_avdd18_gpio"); + if (ret < 0) { + CAM_DBG(CAM_SENSOR, "Failed to request imx582_avdd18_gpio:%d,,\n",ctrl->imx582_avdd18_gpio); + } + //gpio_set_value_cansleep(ctrl->imx582_avdd18_gpio, 1); + gpio_direction_output(ctrl->imx582_avdd18_gpio,1); + gpio_free(ctrl->imx582_avdd18_gpio); + } + //add by hzt 2021-9-4 control external gpio + + + rc = msm_cam_sensor_handle_reg_gpio( power_setting->seq_type, gpio_num_info, 1); @@ -2089,7 +2124,6 @@ int cam_sensor_core_power_up(struct cam_sensor_power_ctrl_t *ctrl, case SENSOR_STANDBY: case SENSOR_CUSTOM_GPIO1: case SENSOR_CUSTOM_GPIO2: - case SENSOR_VAF: if (!gpio_num_info) continue; if (!gpio_num_info->valid @@ -2102,6 +2136,7 @@ int cam_sensor_core_power_up(struct cam_sensor_power_ctrl_t *ctrl, case SENSOR_VANA: case SENSOR_VDIG: case SENSOR_VIO: + case SENSOR_VAF: case SENSOR_VAF_PWDM: case SENSOR_CUSTOM_REG1: case SENSOR_CUSTOM_REG2: @@ -2256,7 +2291,7 @@ int cam_sensor_util_power_down(struct cam_sensor_power_ctrl_t *ctrl, case SENSOR_STANDBY: case SENSOR_CUSTOM_GPIO1: case SENSOR_CUSTOM_GPIO2: - case SENSOR_VAF: + if (!gpio_num_info->valid[pd->seq_type]) continue; @@ -2269,6 +2304,7 @@ int cam_sensor_util_power_down(struct cam_sensor_power_ctrl_t *ctrl, case SENSOR_VANA: case SENSOR_VDIG: case SENSOR_VIO: + case SENSOR_VAF: case SENSOR_VAF_PWDM: case SENSOR_CUSTOM_REG1: case SENSOR_CUSTOM_REG2: @@ -2317,6 +2353,22 @@ int cam_sensor_util_power_down(struct cam_sensor_power_ctrl_t *ctrl, CAM_ERR(CAM_SENSOR, "error in power up/down seq"); + + //add by hzt 2021-9-4 for control external gpio + if(pd->seq_val==SENSOR_VANA) + { + + CAM_DBG(CAM_SENSOR, "before to request imx582_avdd18_gpio:%d,,\n",ctrl->imx582_avdd18_gpio); + ret = gpio_request(ctrl->imx582_avdd18_gpio, "imx582_avdd18_gpio"); + if (ret < 0) { + CAM_DBG(CAM_SENSOR, "Failed to request imx582_avdd18_gpio:%d,,\n",ctrl->imx582_avdd18_gpio); + } + //gpio_set_value_cansleep(ctrl->imx582_avdd18_gpio, 0); + gpio_direction_output(ctrl->imx582_avdd18_gpio,0); + gpio_free(ctrl->imx582_avdd18_gpio); + } + //add by hzt 2021-9-4 control external gpio + ret = msm_cam_sensor_handle_reg_gpio(pd->seq_type, gpio_num_info, GPIOF_OUT_INIT_LOW); diff --git a/techpack/camera/drivers/cam_smmu/cam_smmu_api.c b/techpack/camera/drivers/cam_smmu/cam_smmu_api.c index 0db36e698805..034de6ee564b 100644 --- a/techpack/camera/drivers/cam_smmu/cam_smmu_api.c +++ b/techpack/camera/drivers/cam_smmu/cam_smmu_api.c @@ -2683,13 +2683,14 @@ static int cam_smmu_map_stage2_buffer_and_add_to_list(int idx, int ion_fd, /* add to the list */ list_add(&mapping_info->list, &iommu_cb_set.cb_info[idx].smmu_buf_list); + return 0; + err_unmap_sg: dma_buf_unmap_attachment(attach, table, dma_dir); err_detach: dma_buf_detach(dmabuf, attach); err_put: - if (rc) - dma_buf_put(dmabuf); + dma_buf_put(dmabuf); err_out: return rc; } diff --git a/techpack/camera/drivers/cam_sync/cam_sync_private.h b/techpack/camera/drivers/cam_sync/cam_sync_private.h index 518673cdc4b8..a77dc5f7f126 100644 --- a/techpack/camera/drivers/cam_sync/cam_sync_private.h +++ b/techpack/camera/drivers/cam_sync/cam_sync_private.h @@ -25,7 +25,7 @@ #define CAM_SYNC_OBJ_NAME_LEN 64 #define CAM_SYNC_MAX_OBJS 1024 -#define CAM_SYNC_MAX_V4L2_EVENTS 200 +#define CAM_SYNC_MAX_V4L2_EVENTS 100 #define CAM_SYNC_DEBUG_FILENAME "cam_debug" #define CAM_SYNC_DEBUG_BASEDIR "cam" #define CAM_SYNC_DEBUG_BUF_SIZE 32 diff --git a/techpack/camera/drivers/cam_utils/Makefile b/techpack/camera/drivers/cam_utils/Makefile index 34c17f244344..e17c2f50bb95 100644 --- a/techpack/camera/drivers/cam_utils/Makefile +++ b/techpack/camera/drivers/cam_utils/Makefile @@ -5,6 +5,5 @@ ccflags-y += -I$(srctree)/techpack/camera/drivers/cam_core/ ccflags-y += -I$(srctree)/techpack/camera/drivers/cam_req_mgr/ ccflags-y += -I$(srctree)/techpack/camera/drivers/cam_smmu/ -obj-$(CONFIG_SPECTRA_CAMERA) += cam_utils.o -cam_utils-objs += cam_soc_util.o cam_io_util.o cam_packet_util.o -cam_utils-objs += cam_trace.o cam_common_util.o cam_cx_ipeak.o +obj-$(CONFIG_SPECTRA_CAMERA) += cam_soc_util.o cam_io_util.o cam_packet_util.o cam_debug_util.o cam_trace.o cam_common_util.o +obj-$(CONFIG_SPECTRA_CAMERA) += cam_cx_ipeak.o diff --git a/techpack/camera/drivers/cam_utils/cam_debug_util.h b/techpack/camera/drivers/cam_utils/cam_debug_util.h index 439dd13765e8..03f8019cfea2 100644 --- a/techpack/camera/drivers/cam_utils/cam_debug_util.h +++ b/techpack/camera/drivers/cam_utils/cam_debug_util.h @@ -57,10 +57,8 @@ * @fmt : Formatted string which needs to be print in the log * */ -static inline void cam_debug_log(unsigned int module_id, const char *func, - const int line, const char *fmt, ...) -{ -} +void cam_debug_log(unsigned int module_id, const char *func, const int line, + const char *fmt, ...); /* * cam_get_module_name() @@ -69,10 +67,7 @@ static inline void cam_debug_log(unsigned int module_id, const char *func, * * @module_id : Module ID which is using this function */ -static inline const char *cam_get_module_name(unsigned int module_id) -{ - return NULL; -} +const char *cam_get_module_name(unsigned int module_id); /* * CAM_ERR @@ -82,9 +77,9 @@ static inline const char *cam_get_module_name(unsigned int module_id) * @fmt : Formatted string which needs to be print in log * @args : Arguments which needs to be print in log */ -#define CAM_ERR(__module, fmt, args...) \ - cam_debug_log(__module, __func__, __LINE__, fmt, ##args) - +#define CAM_ERR(__module, fmt, args...) \ + pr_info("CAM_ERR: %s: %s: %d " fmt "\n", \ + cam_get_module_name(__module), __func__, __LINE__, ##args) /* * CAM_WARN * @brief : This Macro will print warning logs @@ -93,9 +88,9 @@ static inline const char *cam_get_module_name(unsigned int module_id) * @fmt : Formatted string which needs to be print in log * @args : Arguments which needs to be print in log */ -#define CAM_WARN(__module, fmt, args...) \ - cam_debug_log(__module, __func__, __LINE__, fmt, ##args) - +#define CAM_WARN(__module, fmt, args...) \ + pr_info("CAM_WARN: %s: %s: %d " fmt "\n", \ + cam_get_module_name(__module), __func__, __LINE__, ##args) /* * CAM_INFO * @brief : This Macro will print Information logs @@ -104,8 +99,9 @@ static inline const char *cam_get_module_name(unsigned int module_id) * @fmt : Formatted string which needs to be print in log * @args : Arguments which needs to be print in log */ -#define CAM_INFO(__module, fmt, args...) \ - cam_debug_log(__module, __func__, __LINE__, fmt, ##args) +#define CAM_INFO(__module, fmt, args...) \ + pr_info("CAM_INFO: %s: %s: %d " fmt "\n", \ + cam_get_module_name(__module), __func__, __LINE__, ##args) /* * CAM_INFO_RATE_LIMIT @@ -115,8 +111,9 @@ static inline const char *cam_get_module_name(unsigned int module_id) * @fmt : Formatted string which needs to be print in log * @args : Arguments which needs to be print in log */ -#define CAM_INFO_RATE_LIMIT(__module, fmt, args...) \ - cam_debug_log(__module, __func__, __LINE__, fmt, ##args) +#define CAM_INFO_RATE_LIMIT(__module, fmt, args...) \ + pr_info_ratelimited("CAM_INFO: %s: %s: %d " fmt "\n", \ + cam_get_module_name(__module), __func__, __LINE__, ##args) /* * CAM_DBG @@ -126,16 +123,16 @@ static inline const char *cam_get_module_name(unsigned int module_id) * @fmt : Formatted string which needs to be print in log * @args : Arguments which needs to be print in log */ -#define CAM_DBG(__module, fmt, args...) \ +#define CAM_DBG(__module, fmt, args...) \ cam_debug_log(__module, __func__, __LINE__, fmt, ##args) /* * CAM_ERR_RATE_LIMIT * @brief : This Macro will print error print logs with ratelimit */ -#define CAM_ERR_RATE_LIMIT(__module, fmt, args...) \ - cam_debug_log(__module, __func__, __LINE__, fmt, ##args) - +#define CAM_ERR_RATE_LIMIT(__module, fmt, args...) \ + pr_info_ratelimited("CAM_ERR: %s: %s: %d " fmt "\n", \ + cam_get_module_name(__module), __func__, __LINE__, ##args) /* * CAM_WARN_RATE_LIMIT * @brief : This Macro will print warning logs with ratelimit @@ -144,8 +141,9 @@ static inline const char *cam_get_module_name(unsigned int module_id) * @fmt : Formatted string which needs to be print in log * @args : Arguments which needs to be print in log */ -#define CAM_WARN_RATE_LIMIT(__module, fmt, args...) \ - cam_debug_log(__module, __func__, __LINE__, fmt, ##args) +#define CAM_WARN_RATE_LIMIT(__module, fmt, args...) \ + pr_info_ratelimited("CAM_WARN: %s: %s: %d " fmt "\n", \ + cam_get_module_name(__module), __func__, __LINE__, ##args) /* * CAM_WARN_RATE_LIMIT_CUSTOM @@ -157,7 +155,17 @@ static inline const char *cam_get_module_name(unsigned int module_id) * @fmt : Formatted string which needs to be print in log * @args : Arguments which needs to be print in log */ -#define CAM_WARN_RATE_LIMIT_CUSTOM(__module, interval, burst, fmt, args...) +#define CAM_WARN_RATE_LIMIT_CUSTOM(__module, interval, burst, fmt, args...) \ + ({ \ + static DEFINE_RATELIMIT_STATE(_rs, \ + (interval * HZ), \ + burst); \ + if (__ratelimit(&_rs)) \ + pr_info( \ + "CAM_WARN: %s: %s: %d " fmt "\n", \ + cam_get_module_name(__module), __func__, \ + __LINE__, ##args); \ + }) /* * CAM_INFO_RATE_LIMIT_CUSTOM @@ -169,7 +177,17 @@ static inline const char *cam_get_module_name(unsigned int module_id) * @fmt : Formatted string which needs to be print in log * @args : Arguments which needs to be print in log */ -#define CAM_INFO_RATE_LIMIT_CUSTOM(__module, interval, burst, fmt, args...) +#define CAM_INFO_RATE_LIMIT_CUSTOM(__module, interval, burst, fmt, args...) \ + ({ \ + static DEFINE_RATELIMIT_STATE(_rs, \ + (interval * HZ), \ + burst); \ + if (__ratelimit(&_rs)) \ + pr_info( \ + "CAM_INFO: %s: %s: %d " fmt "\n", \ + cam_get_module_name(__module), __func__, \ + __LINE__, ##args); \ + }) /* * CAM_ERR_RATE_LIMIT_CUSTOM @@ -181,6 +199,16 @@ static inline const char *cam_get_module_name(unsigned int module_id) * @fmt : Formatted string which needs to be print in log * @args : Arguments which needs to be print in log */ -#define CAM_ERR_RATE_LIMIT_CUSTOM(__module, interval, burst, fmt, args...) +#define CAM_ERR_RATE_LIMIT_CUSTOM(__module, interval, burst, fmt, args...) \ + ({ \ + static DEFINE_RATELIMIT_STATE(_rs, \ + (interval * HZ), \ + burst); \ + if (__ratelimit(&_rs)) \ + pr_info( \ + "CAM_ERR: %s: %s: %d " fmt "\n", \ + cam_get_module_name(__module), __func__, \ + __LINE__, ##args); \ + }) #endif /* _CAM_DEBUG_UTIL_H_ */ diff --git a/techpack/camera/include/uapi/media/cam_ope.h b/techpack/camera/include/uapi/media/cam_ope.h index 812212f3170b..a7d800844103 100644 --- a/techpack/camera/include/uapi/media/cam_ope.h +++ b/techpack/camera/include/uapi/media/cam_ope.h @@ -104,7 +104,7 @@ struct ope_stripe_info { * @direction: Direction of a buffer of a port(Input/Output) * @resource_type: Port type * @num_planes: Number of planes for a port - * @reserved: Reserved + * @pix_pattern: Pixel pattern for raw input * @num_stripes: Stripes per plane * @mem_handle: Memhandles of each Input/Output Port * @plane_offset: Offsets of planes @@ -120,7 +120,7 @@ struct ope_io_buf_info { uint32_t direction; uint32_t resource_type; uint32_t num_planes; - uint32_t reserved; + uint32_t pix_pattern; uint32_t num_stripes[OPE_MAX_PLANES]; uint32_t mem_handle[OPE_MAX_PLANES]; uint32_t plane_offset[OPE_MAX_PLANES]; diff --git a/techpack/display/config/konadisp.conf b/techpack/display/config/konadisp.conf index e98b37c19783..dbbf3c847dbb 100644 --- a/techpack/display/config/konadisp.conf +++ b/techpack/display/config/konadisp.conf @@ -6,8 +6,8 @@ export CONFIG_DRM_MSM_DP=y export CONFIG_QCOM_MDSS_DP_PLL=y export CONFIG_DSI_PARSER=y export CONFIG_DRM_SDE_WB=y -export CONFIG_DRM_MSM_REGISTER_LOGGING=n +export CONFIG_DRM_MSM_REGISTER_LOGGING=y export CONFIG_QCOM_MDSS_PLL=y export CONFIG_MSM_SDE_ROTATOR=y -export CONFIG_MSM_SDE_ROTATOR_EVTLOG_DEBUG=n +export CONFIG_MSM_SDE_ROTATOR_EVTLOG_DEBUG=y export CONFIG_DRM_SDE_RSC=y diff --git a/techpack/display/config/konadispconf.h b/techpack/display/config/konadispconf.h index 4998bacccbdf..690d4ec79f41 100644 --- a/techpack/display/config/konadispconf.h +++ b/techpack/display/config/konadispconf.h @@ -11,10 +11,10 @@ #define CONFIG_QCOM_MDSS_DP_PLL 1 #define CONFIG_DSI_PARSER 1 #define CONFIG_DRM_SDE_WB 1 -#define CONFIG_DRM_MSM_REGISTER_LOGGING 0 -#define CONFIG_DRM_SDE_EVTLOG_DEBUG 0 +#define CONFIG_DRM_MSM_REGISTER_LOGGING 1 +#define CONFIG_DRM_SDE_EVTLOG_DEBUG 1 #define CONFIG_QCOM_MDSS_PLL 1 #define CONFIG_MSM_SDE_ROTATOR 1 -#define CONFIG_MSM_SDE_ROTATOR_EVTLOG_DEBUG 0 +#define CONFIG_MSM_SDE_ROTATOR_EVTLOG_DEBUG 1 #define CONFIG_DRM_SDE_RSC 1 diff --git a/techpack/display/config/saipdisp.conf b/techpack/display/config/saipdisp.conf index b225d21642ec..dbbf3c847dbb 100644 --- a/techpack/display/config/saipdisp.conf +++ b/techpack/display/config/saipdisp.conf @@ -2,9 +2,12 @@ export CONFIG_DRM_MSM=y export CONFIG_DRM_MSM_SDE=y export CONFIG_SYNC_FILE=y export CONFIG_DRM_MSM_DSI=y +export CONFIG_DRM_MSM_DP=y export CONFIG_QCOM_MDSS_DP_PLL=y export CONFIG_DSI_PARSER=y export CONFIG_DRM_SDE_WB=y +export CONFIG_DRM_MSM_REGISTER_LOGGING=y export CONFIG_QCOM_MDSS_PLL=y export CONFIG_MSM_SDE_ROTATOR=y +export CONFIG_MSM_SDE_ROTATOR_EVTLOG_DEBUG=y export CONFIG_DRM_SDE_RSC=y diff --git a/techpack/display/config/saipdispconf.h b/techpack/display/config/saipdispconf.h index f3e3d1773232..049024839701 100644 --- a/techpack/display/config/saipdispconf.h +++ b/techpack/display/config/saipdispconf.h @@ -7,9 +7,13 @@ #define CONFIG_DRM_MSM_SDE 1 #define CONFIG_SYNC_FILE 1 #define CONFIG_DRM_MSM_DSI 1 +#define CONFIG_DRM_MSM_DP 1 #define CONFIG_QCOM_MDSS_DP_PLL 1 #define CONFIG_DSI_PARSER 1 #define CONFIG_DRM_SDE_WB 1 +#define CONFIG_DRM_MSM_REGISTER_LOGGING 1 +#define CONFIG_DRM_SDE_EVTLOG_DEBUG 1 #define CONFIG_QCOM_MDSS_PLL 1 #define CONFIG_MSM_SDE_ROTATOR 1 +#define CONFIG_MSM_SDE_ROTATOR_EVTLOG_DEBUG 1 #define CONFIG_DRM_SDE_RSC 1 diff --git a/techpack/display/msm/Makefile b/techpack/display/msm/Makefile index b7ac4dfb12b2..51b0edbf245a 100644 --- a/techpack/display/msm/Makefile +++ b/techpack/display/msm/Makefile @@ -68,8 +68,7 @@ msm_drm-$(CONFIG_DRM_MSM_SDE) += sde/sde_crtc.o \ sde/sde_hw_ds.o \ sde/sde_fence.o \ sde/sde_hw_qdss.o \ - -msm_drm-$(CONFIG_DEBUG_FS) += sde_dbg.o \ + sde_dbg.o \ sde_dbg_evtlog.o \ msm_drm-$(CONFIG_DRM_SDE_WB) += sde/sde_wb.o \ diff --git a/techpack/display/msm/dp/dp_debug.c b/techpack/display/msm/dp/dp_debug.c index 0459adceb1fa..6303c1cff30f 100644 --- a/techpack/display/msm/dp/dp_debug.c +++ b/techpack/display/msm/dp/dp_debug.c @@ -154,7 +154,7 @@ static ssize_t dp_debug_write_edid(struct file *file, edid = debug->edid; bail: kfree(buf); - debug->panel->set_edid(debug->panel, edid); + debug->panel->set_edid(debug->panel, edid, debug->edid_size); /* * print edid status as this code is executed @@ -1628,7 +1628,7 @@ static void dp_debug_set_sim_mode(struct dp_debug_private *debug, bool sim) debug->aux->set_sim_mode(debug->aux, false, NULL, NULL); debug->dp_debug.sim_mode = false; - debug->panel->set_edid(debug->panel, 0); + debug->panel->set_edid(debug->panel, 0, 0); if (debug->edid) { devm_kfree(debug->dev, debug->edid); debug->edid = NULL; diff --git a/techpack/display/msm/dp/dp_display.c b/techpack/display/msm/dp/dp_display.c index b2999d8d210b..af3bc7365aba 100644 --- a/techpack/display/msm/dp/dp_display.c +++ b/techpack/display/msm/dp/dp_display.c @@ -1340,6 +1340,7 @@ static void dp_display_attention_work(struct work_struct *work) { struct dp_display_private *dp = container_of(work, struct dp_display_private, attention_work); + int rc = 0; SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state); mutex_lock(&dp->session_lock); @@ -1403,16 +1404,20 @@ static void dp_display_attention_work(struct work_struct *work) if (dp->link->sink_request & DP_TEST_LINK_TRAINING) { SDE_EVT32_EXTERNAL(dp->state, DP_TEST_LINK_TRAINING); dp->link->send_test_response(dp->link); - dp->ctrl->link_maintenance(dp->ctrl); + rc = dp->ctrl->link_maintenance(dp->ctrl); } if (dp->link->sink_request & DP_LINK_STATUS_UPDATED) { SDE_EVT32_EXTERNAL(dp->state, DP_LINK_STATUS_UPDATED); - dp->ctrl->link_maintenance(dp->ctrl); + rc = dp->ctrl->link_maintenance(dp->ctrl); } - dp_audio_enable(dp, true); + if (!rc) + dp_audio_enable(dp, true); + mutex_unlock(&dp->session_lock); + if (rc) + goto end; if (dp->link->sink_request & (DP_TEST_LINK_PHY_TEST_PATTERN | DP_TEST_LINK_TRAINING)) @@ -1436,6 +1441,8 @@ static void dp_display_attention_work(struct work_struct *work) mst_attention: dp_display_mst_attention(dp); + +end: SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state); } diff --git a/techpack/display/msm/dp/dp_panel.c b/techpack/display/msm/dp/dp_panel.c index 74f915b5f797..1c01b0eee481 100644 --- a/techpack/display/msm/dp/dp_panel.c +++ b/techpack/display/msm/dp/dp_panel.c @@ -7,6 +7,7 @@ #include #include #include "dp_debug.h" +#include #define DP_KHZ_TO_HZ 1000 #define DP_PANEL_DEFAULT_BPP 24 @@ -1937,7 +1938,25 @@ static int dp_panel_set_default_link_params(struct dp_panel *dp_panel) return 0; } -static int dp_panel_set_edid(struct dp_panel *dp_panel, u8 *edid) +static bool dp_panel_validate_edid(struct edid *edid, size_t edid_size) +{ + if (!edid || (edid_size < EDID_LENGTH)) + return false; + + if (EDID_LENGTH * (edid->extensions + 1) > edid_size) { + DP_ERR("edid size does not match allocated.\n"); + return false; + } + + if (!drm_edid_is_valid(edid)) { + DP_ERR("invalid edid.\n"); + return false; + } + return true; +} + +static int dp_panel_set_edid(struct dp_panel *dp_panel, u8 *edid, + size_t edid_size) { struct dp_panel_private *panel; @@ -1948,7 +1967,7 @@ static int dp_panel_set_edid(struct dp_panel *dp_panel, u8 *edid) panel = container_of(dp_panel, struct dp_panel_private, dp_panel); - if (edid) { + if (edid && dp_panel_validate_edid((struct edid *)edid, edid_size)) { dp_panel->edid_ctrl->edid = (struct edid *)edid; panel->custom_edid = true; } else { diff --git a/techpack/display/msm/dp/dp_panel.h b/techpack/display/msm/dp/dp_panel.h index dbc5ba98bcaa..36629c3c36a7 100644 --- a/techpack/display/msm/dp/dp_panel.h +++ b/techpack/display/msm/dp/dp_panel.h @@ -146,7 +146,7 @@ struct dp_panel { int (*get_modes)(struct dp_panel *dp_panel, struct drm_connector *connector, struct dp_display_mode *mode); void (*handle_sink_request)(struct dp_panel *dp_panel); - int (*set_edid)(struct dp_panel *dp_panel, u8 *edid); + int (*set_edid)(struct dp_panel *dp_panel, u8 *edid, size_t edid_size); int (*set_dpcd)(struct dp_panel *dp_panel, u8 *dpcd); int (*setup_hdr)(struct dp_panel *dp_panel, struct drm_msm_ext_hdr_metadata *hdr_meta, diff --git a/techpack/display/msm/dsi/dsi_ctrl.c b/techpack/display/msm/dsi/dsi_ctrl.c index bf316d5dfbe5..c92bfbcef14d 100644 --- a/techpack/display/msm/dsi/dsi_ctrl.c +++ b/techpack/display/msm/dsi/dsi_ctrl.c @@ -263,6 +263,13 @@ static int dsi_ctrl_debugfs_deinit(struct dsi_ctrl *dsi_ctrl) static int dsi_ctrl_debugfs_init(struct dsi_ctrl *dsi_ctrl, struct dentry *parent) { + char dbg_name[DSI_DEBUG_NAME_LEN]; + + snprintf(dbg_name, DSI_DEBUG_NAME_LEN, "dsi%d_ctrl", + dsi_ctrl->cell_index); + sde_dbg_reg_register_base(dbg_name, + dsi_ctrl->hw.base, + msm_iomap_size(dsi_ctrl->pdev, "dsi_ctrl")); return 0; } static int dsi_ctrl_debugfs_deinit(struct dsi_ctrl *dsi_ctrl) @@ -2063,7 +2070,6 @@ static struct platform_driver dsi_ctrl_driver = { }, }; -#if defined(CONFIG_DEBUG_FS) void dsi_ctrl_debug_dump(u32 *entries, u32 size) { @@ -2085,7 +2091,6 @@ void dsi_ctrl_debug_dump(u32 *entries, u32 size) mutex_unlock(&dsi_ctrl_list_lock); } -#endif /** * dsi_ctrl_get() - get a dsi_ctrl handle from an of_node * @of_node: of_node of the DSI controller. diff --git a/techpack/display/msm/dsi/dsi_defs.h b/techpack/display/msm/dsi/dsi_defs.h index a8cd73e09a0d..5179edfebd02 100644 --- a/techpack/display/msm/dsi/dsi_defs.h +++ b/techpack/display/msm/dsi/dsi_defs.h @@ -307,10 +307,6 @@ enum dsi_cmd_set_type { DSI_CMD_SET_POST_TIMING_SWITCH, DSI_CMD_SET_QSYNC_ON, DSI_CMD_SET_QSYNC_OFF, - DSI_CMD_SET_CABC_ON, - DSI_CMD_SET_CABC_OFF, - DSI_CMD_SET_CABC_MOVIE_ON, - DSI_CMD_SET_CABC_STILL_ON, DSI_CMD_SET_MAX }; diff --git a/techpack/display/msm/dsi/dsi_display.c b/techpack/display/msm/dsi/dsi_display.c index 5c4d2031e478..acb1f7db43e1 100644 --- a/techpack/display/msm/dsi/dsi_display.c +++ b/techpack/display/msm/dsi/dsi_display.c @@ -7,8 +7,6 @@ #include #include #include -#include -#include #include "msm_drv.h" #include "sde_connector.h" @@ -35,15 +33,6 @@ #define DSI_CLOCK_BITRATE_RADIX 10 #define MAX_TE_SOURCE_ID 2 -static struct dsi_display *whitep_display; -extern char g_lcd_id[128]; -extern bool panel_init_judge; - -extern bool backlight_val; -struct dsi_whitep_display_para whitep_display_para = {0}; -#define X_coordinate 172 -#define Y_coordinate 192 - static char dsi_display_primary[MAX_CMDLINE_PARAM_LEN]; static char dsi_display_secondary[MAX_CMDLINE_PARAM_LEN]; static struct dsi_display_boot_param boot_displays[MAX_DSI_ACTIVE_DISPLAY] = { @@ -583,8 +572,8 @@ static bool dsi_display_validate_reg_read(struct dsi_panel *panel) for (i = 0; i < len; ++i) { if (config->return_buf[i] != config->status_value[group + i]) { - DRM_ERROR("mismatch: i = %d 0x%x\n", - i, config->return_buf[i]); + DRM_ERROR("mismatch: 0x%x\n", + config->return_buf[i]); break; } } @@ -634,94 +623,6 @@ static void dsi_display_parse_te_data(struct dsi_display *display) display->te_source = val; } - -static char dcs_cmd[2] = {0x00, 0x00}; /* DTYPE_DCS_READ */ -static struct dsi_cmd_desc dcs_read_cmd = { - {0, 6, MIPI_DSI_MSG_REQ_ACK, 0, 5, sizeof(dcs_cmd), dcs_cmd, 0, 0}, - 1, - 5, -}; - -static int dsi_display_read_reg(struct dsi_display_ctrl *ctrl, char cmd0, - char cmd1, char *rbuf, int len) -{ - int rc = 0; - struct dsi_cmd_desc *cmds; - u32 flags = 0; - - if (!ctrl || !ctrl->ctrl) - return -EINVAL; - - /* - * When DSI controller is not in initialized state, we do not want to - * report a false failure and hence we defer until next read - * happen. - */ - if (!dsi_ctrl_validate_host_state(ctrl->ctrl)) - return 1; - - dcs_cmd[0] = cmd0; - dcs_cmd[1] = cmd1; - - cmds = &dcs_read_cmd; - flags |= (DSI_CTRL_CMD_FETCH_MEMORY | DSI_CTRL_CMD_READ ); - - memset(rbuf, 0x0, SZ_4K); - if (cmds->last_command) { - cmds->msg.flags |= MIPI_DSI_MSG_LASTCOMMAND; - flags |= DSI_CTRL_CMD_LAST_COMMAND; - } - cmds->msg.rx_buf = rbuf; - cmds->msg.rx_len = len; - rc = dsi_ctrl_cmd_transfer(ctrl->ctrl, &cmds->msg, &flags); - if (rc <= 0) { - pr_err("rx cmd transfer failed rc=%d\n", rc); - return rc; - } - return rc; - } - -static char dcs_cmd_page[2] = {0x00, 0x00}; /* DTYPE_DCS_READ */ -static struct dsi_cmd_desc dcs_read_cmd_page = { - {0, 0x15, MIPI_DSI_MSG_REQ_ACK, 0, 5, sizeof(dcs_cmd_page), dcs_cmd_page, 0, 0}, - 1, - 5, -}; - -static int dsi_display_write_reg_page(struct dsi_display_ctrl *ctrl, char cmd0, - char cmd1, char *rbuf, int len) -{ - int rc = 0; - struct dsi_cmd_desc *cmds; - u32 flags = 0; - - if (!ctrl || !ctrl->ctrl) - return -EINVAL; - - if (!dsi_ctrl_validate_host_state(ctrl->ctrl)) - return 1; - - dcs_cmd_page[0] = cmd0; - dcs_cmd_page[1] = cmd1; - cmds = &dcs_read_cmd_page; - flags |= (DSI_CTRL_CMD_FETCH_MEMORY); - - memset(rbuf, 0x0, SZ_4K); - if (cmds->last_command) { - cmds->msg.flags |= MIPI_DSI_MSG_LASTCOMMAND; - flags |= DSI_CTRL_CMD_LAST_COMMAND; - } - cmds->msg.rx_buf = NULL; - cmds->msg.rx_len = 0; - rc = dsi_ctrl_cmd_transfer(ctrl->ctrl, &cmds->msg, &flags); - if (rc < 0) { - pr_err("peter rx cmd transfer failed rc=%d\n", rc); - return rc; - } - - return rc; - } - static int dsi_display_read_status(struct dsi_display_ctrl *ctrl, struct dsi_panel *panel) { @@ -1145,25 +1046,12 @@ int dsi_display_set_power(struct drm_connector *connector, { struct dsi_display *display = disp; int rc = 0; - struct drm_notify_data g_notify_data; - struct drm_device *dev = NULL; - int event = 0; if (!display || !display->panel) { DSI_ERR("invalid display/panel\n"); return -EINVAL; } - if (!connector || !connector->dev) { - pr_err("invalid connector/dev\n"); - return -EINVAL; - } else { - dev = connector->dev; - event = dev->doze_state; - } - - g_notify_data.data = &event; - switch (power_mode) { case SDE_MODE_DPMS_LP1: rc = dsi_panel_set_lp1(display->panel); @@ -1178,17 +1066,9 @@ int dsi_display_set_power(struct drm_connector *connector, break; case SDE_MODE_DPMS_OFF: default: - if (dev->pre_state != SDE_MODE_DPMS_LP1 && - dev->pre_state != SDE_MODE_DPMS_LP2) - break; - - drm_notifier_call_chain(DRM_EARLY_EVENT_BLANK, &g_notify_data); - rc = dsi_panel_set_nolp(display->panel); - drm_notifier_call_chain(DRM_EVENT_BLANK, &g_notify_data); return rc; } - dev->pre_state = power_mode; SDE_EVT32(display->panel->power_mode, power_mode, rc); DSI_DEBUG("Power mode transition from %d to %d %s", display->panel->power_mode, power_mode, @@ -5087,155 +4967,6 @@ static int dsi_display_force_update_dsi_clk(struct dsi_display *display) return rc; } -static ssize_t dsi_display_set_cabc(struct device *dev,struct device_attribute *attr,const char *buf,size_t len) -{ - - int rc = 0; - int param = 0; - struct dsi_display *display; - - display = dev_get_drvdata(dev); - if (!display) { - pr_err("Invalid display\n"); - return -EINVAL; - } - - rc = kstrtoint(buf, 10, ¶m); - if (rc) { - pr_err("kstrtoint failed. rc=%d\n", rc); - return rc; - } - - pr_info("hyper:_###_%s,set_cabc_cmd: %d\n",__func__, param); - switch(param) { - case 0x1: //cabc on - dsi_panel_set_feature(display->panel, DSI_CMD_SET_CABC_ON); - break; - case 0x2: //cabc off - dsi_panel_set_feature(display->panel, DSI_CMD_SET_CABC_OFF); - break; - default: - pr_err("unknow cmds: %d\n", param); - break; - } - pr_err("hyper:_##### cabc over ###\n"); - return len; -} -static ssize_t dsi_display_set_cabc_movie(struct device *dev,struct device_attribute *attr,const char *buf,size_t len) -{ - - int rc = 0; - int param = 0; - struct dsi_display *display; - - display = dev_get_drvdata(dev); - if (!display) { - pr_err("Invalid display\n"); - return -EINVAL; - } - - rc = kstrtoint(buf, 10, ¶m); - if (rc) { - pr_err("kstrtoint failed. rc=%d\n", rc); - return rc; - } - - switch(param) { - case 0x1: //cabc_movie on - dsi_panel_set_feature(display->panel, DSI_CMD_SET_CABC_MOVIE_ON); - break; - case 0x2: //cabc_movie off - dsi_panel_set_feature(display->panel, DSI_CMD_SET_CABC_OFF); - break; - default: - pr_err("unknow cmds: %d\n", param); - break; - } - pr_err("hyper:_##### cabc_movie over ###\n"); - return len; -} - -static ssize_t dsi_display_set_cabc_still(struct device *dev,struct device_attribute *attr,const char *buf,size_t len) -{ - - int rc = 0; - int param = 0; - struct dsi_display *display; - - display = dev_get_drvdata(dev); - if (!display) { - pr_err("Invalid display\n"); - return -EINVAL; - } - - rc = kstrtoint(buf, 10, ¶m); - if (rc) { - pr_err("kstrtoint failed. rc=%d\n", rc); - return rc; - } - - switch(param) { - case 0x1: //cabc_still on - dsi_panel_set_feature(display->panel, DSI_CMD_SET_CABC_STILL_ON); - break; - case 0x2: //cabc_still off - dsi_panel_set_feature(display->panel, DSI_CMD_SET_CABC_OFF); - break; - default: - pr_err("unknow cmds: %d\n", param); - break; - } - pr_err("hyper:_##### cabc_still over ###\n"); - return len; -} -unsigned int hbm_mode; -extern int dsi_hbm_set(enum backlight_hbm_mode hbm_mode); -static ssize_t dsi_display_set_hbm(struct device *dev,struct device_attribute *attr,const char *buf,size_t len) -{ - if ((!panel_init_judge) || (!backlight_val)) { - pr_err("hyper: con't set hbm\n"); - return -EINVAL; - } - sscanf(buf, "%d", &hbm_mode) ; - if (hbm_mode >= HBM_MODE_LEVEL_MAX) - hbm_mode = HBM_MODE_LEVEL_MAX - 1; - if (hbm_mode < HBM_MODE_DEFAULT) - hbm_mode = HBM_MODE_DEFAULT; - - dsi_hbm_set((enum backlight_hbm_mode)hbm_mode); - - return len; -} - -static DEVICE_ATTR(dsi_display_cabc, 0644, NULL, dsi_display_set_cabc); -static DEVICE_ATTR(dsi_display_hbm, 0644, NULL, dsi_display_set_hbm); -static DEVICE_ATTR(dsi_display_cabc_movie, 0644, NULL, dsi_display_set_cabc_movie); -static DEVICE_ATTR(dsi_display_cabc_still, 0644, NULL, dsi_display_set_cabc_still); - -static struct attribute *dsi_display_feature_attrs[] = { - &dev_attr_dsi_display_cabc.attr, - &dev_attr_dsi_display_hbm.attr, - &dev_attr_dsi_display_cabc_movie.attr, - &dev_attr_dsi_display_cabc_still.attr, - NULL, -}; -static struct attribute_group dsi_display_feature_attrs_group = { - .attrs = dsi_display_feature_attrs, -}; - -static int dsi_display_feature_create_sysfs(struct dsi_display *display){ - int ret =0; - struct device *dev = &display->pdev->dev; - - ret = sysfs_create_group(&dev->kobj, - &dsi_display_feature_attrs_group); - if(ret){ - pr_err("%s failed \n",__func__); - return -ENOMEM; - } - pr_info("hyper:%s success\n",__func__); - return ret; -} static int dsi_display_validate_split_link(struct dsi_display *display) { @@ -5273,154 +5004,6 @@ static int dsi_display_validate_split_link(struct dsi_display *display) return rc; } -static int dsi_display_get_point_init(void) -{ - if((strstr(g_lcd_id, "td4330")!= NULL)) { - whitep_display_para.white_point_r = 656335; - whitep_display_para.white_point_g = 299652; - whitep_display_para.white_point_b = 154054; - return 0; - } else if((strstr(g_lcd_id, "ft8719")!= NULL)) { - whitep_display_para.white_point_r = 657335; - whitep_display_para.white_point_g = 297657; - whitep_display_para.white_point_b = 155044; - return 0; - } else { - return -1; - } -} -static bool is_already_read = false; -static ssize_t dsi_display_get_whitepoint(struct device *dev, - struct device_attribute *attr, char *buf) -{ - - struct dsi_display_ctrl *ctrl = NULL; - - ssize_t rc = 0; - struct dsi_display *display; - if (is_already_read) - goto done; - - display = whitep_display; - if (!display) { - pr_err("hyper Invalid display\n"); - return -EINVAL; - } - - if (display->tx_cmd_buf == NULL) { - rc = dsi_host_alloc_cmd_tx_buffer(display); - if (rc) { - pr_err("hyper failed to allocate cmd tx buffer memory\n"); - goto done; - } - } - - rc = dsi_display_cmd_engine_enable(display); - if (rc) { - pr_err("hyper cmd engine enable failed\n"); - return -EPERM; - } - - ctrl = &display->ctrl[display->cmd_master_idx]; - - if((strstr(g_lcd_id, "ft8719")!= NULL)) { - pr_err("hyper whitepoint ft8719\n"); - rc = dsi_display_write_reg_page(ctrl, 0x00, 0x50, buf, sizeof(buf)); - rc = dsi_display_read_reg(ctrl, 0xf4, 0, buf, sizeof(buf)); - } else { - pr_err("hyper whitepoint td4330\n"); - rc = dsi_display_write_reg_page(ctrl, 0xff, 0x10, buf, sizeof(buf)); - rc = dsi_display_read_reg(ctrl, 0xa1, 0, buf, sizeof(buf)); - } - - if (rc <= 0) { - pr_err("hyper get whitepoint failed rc=%d\n", rc); - goto exit; - } - if(buf[0] == 0) - is_already_read = false; - else - is_already_read = true; - pr_err("hyper val0=%d,val1=%d\n",buf[0],buf[1]); - whitep_display_para.white_point_x = buf[0] + X_coordinate; - whitep_display_para.white_point_y = buf[1] + Y_coordinate; - -exit: - dsi_display_cmd_engine_disable(display); -done: - rc = snprintf(buf, PAGE_SIZE, "%3d%3d\n",whitep_display_para.white_point_x,whitep_display_para.white_point_y); - return rc; -} - -static ssize_t dsi_display_get_rpoint(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int ret; - ret = scnprintf(buf, PAGE_SIZE, "%6d\n", - whitep_display_para.white_point_r); - return ret; -} - -static ssize_t dsi_display_get_gpoint(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int ret; - ret = scnprintf(buf, PAGE_SIZE, "%6d\n", - whitep_display_para.white_point_g); - return ret; -} - -static ssize_t dsi_display_get_bpoint(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int ret; - ret = scnprintf(buf, PAGE_SIZE, "%6d\n", - whitep_display_para.white_point_b); - return ret; -} - -static DEVICE_ATTR(dsi_whitepoint, 0644, dsi_display_get_whitepoint,NULL ); -static DEVICE_ATTR(dsi_rpoint, 0644, dsi_display_get_rpoint,NULL ); -static DEVICE_ATTR(dsi_gpoint, 0644, dsi_display_get_gpoint,NULL ); -static DEVICE_ATTR(dsi_bpoint, 0644, dsi_display_get_bpoint,NULL ); -static struct kobject *msm_whitepoint; -static int dsi_display_whitepoint_create_sysfs(void){ - int ret; - msm_whitepoint=kobject_create_and_add("android_whitepoint",NULL); - if(msm_whitepoint==NULL){ - pr_info("msm_whitepoint_create_sysfs_ failed\n"); - ret=-ENOMEM; - return ret; - } - ret=sysfs_create_file(msm_whitepoint,&dev_attr_dsi_whitepoint.attr); - if(ret){ - pr_err("hyper:%s failed \n",__func__); - kobject_del(msm_whitepoint); - return ret; - } - ret=sysfs_create_file(msm_whitepoint,&dev_attr_dsi_rpoint.attr); - if(ret){ - pr_err("hyper:%s failed \n",__func__); - kobject_del(msm_whitepoint); - return ret; - } - ret=sysfs_create_file(msm_whitepoint,&dev_attr_dsi_gpoint.attr); - if(ret){ - pr_err("hyper:%s failed \n",__func__); - kobject_del(msm_whitepoint); - return ret; - } - ret=sysfs_create_file(msm_whitepoint,&dev_attr_dsi_bpoint.attr); - if(ret){ - pr_err("hyper:%s failed \n",__func__); - kobject_del(msm_whitepoint); - return ret; - } - dsi_display_get_point_init(); - pr_info("hyper:%s success\n",__func__); - return ret; -} - /** * dsi_display_bind - bind dsi device with controlling device * @dev: Pointer to base of platform device @@ -5626,8 +5209,7 @@ static int dsi_display_bind(struct device *dev, /* register te irq handler */ dsi_display_register_te_irq(display); - dsi_display_feature_create_sysfs(display); - dsi_display_whitepoint_create_sysfs(); + goto error; error_host_deinit: @@ -6482,7 +6064,10 @@ int dsi_display_get_info(struct drm_connector *connector, info->max_width = 1920; info->max_height = 1080; info->qsync_min_fps = - display->panel->qsync_min_fps; + display->panel->qsync_caps.qsync_min_fps; + info->has_qsync_min_fps_list = + (display->panel->qsync_caps.qsync_min_fps_list_len > 0) ? + true : false; switch (display->panel->panel_mode) { case DSI_OP_VIDEO_MODE: @@ -6927,6 +6512,25 @@ int dsi_display_get_default_lms(void *dsi_display, u32 *num_lm) return rc; } +int dsi_display_get_qsync_min_fps(void *display_dsi, u32 mode_fps) +{ + struct dsi_display *display = (struct dsi_display *)display_dsi; + struct dsi_panel *panel; + u32 i; + + if (display == NULL || display->panel == NULL) + return -EINVAL; + + panel = display->panel; + for (i = 0; i < panel->dfps_caps.dfps_list_len; i++) { + if (panel->dfps_caps.dfps_list[i] == mode_fps) + return panel->qsync_caps.qsync_min_fps_list[i]; + } + SDE_EVT32(mode_fps); + DSI_DEBUG("Invalid mode_fps %d\n", mode_fps); + return -EINVAL; +} + int dsi_display_find_mode(struct dsi_display *display, const struct dsi_display_mode *cmp, struct dsi_display_mode **out_mode) @@ -7551,7 +7155,6 @@ int dsi_display_prepare(struct dsi_display *display) return -EINVAL; } - whitep_display = display; SDE_EVT32(SDE_EVTLOG_FUNC_ENTRY); mutex_lock(&display->display_lock); @@ -7759,7 +7362,7 @@ static int dsi_display_qsync(struct dsi_display *display, bool enable) int i; int rc = 0; - if (!display->panel->qsync_min_fps) { + if (!display->panel->qsync_caps.qsync_min_fps) { DSI_ERR("%s:ERROR: qsync set, but no fps\n", __func__); return 0; } @@ -7787,7 +7390,7 @@ static int dsi_display_qsync(struct dsi_display *display, bool enable) } exit: - SDE_EVT32(enable, display->panel->qsync_min_fps, rc); + SDE_EVT32(enable, display->panel->qsync_caps.qsync_min_fps, rc); mutex_unlock(&display->display_lock); return rc; } diff --git a/techpack/display/msm/dsi/dsi_display.h b/techpack/display/msm/dsi/dsi_display.h index 00f6133ad2db..d254dfe739bd 100644 --- a/techpack/display/msm/dsi/dsi_display.h +++ b/techpack/display/msm/dsi/dsi_display.h @@ -394,6 +394,16 @@ void dsi_display_put_mode(struct dsi_display *display, */ int dsi_display_get_default_lms(void *dsi_display, u32 *num_lm); +/** + * dsi_display_get_qsync_min_fps() - get qsync min fps for given fps + * @display: Handle to display. + * @mode_fps: Fps value of current mode + * + * Return: error code. + */ +int dsi_display_get_qsync_min_fps(void *dsi_display, u32 mode_fps); + + /** * dsi_display_find_mode() - retrieve cached DSI mode given relevant params * @display: Handle to display. diff --git a/techpack/display/msm/dsi/dsi_drm.c b/techpack/display/msm/dsi/dsi_drm.c index 2ebab0b090f5..aeb89a7ed318 100644 --- a/techpack/display/msm/dsi/dsi_drm.c +++ b/techpack/display/msm/dsi/dsi_drm.c @@ -6,8 +6,6 @@ #include #include -#include -#include #include "msm_kms.h" #include "sde_connector.h" @@ -23,47 +21,12 @@ #define DEFAULT_PANEL_JITTER_ARRAY_SIZE 2 #define DEFAULT_PANEL_PREFILL_LINES 25 -static BLOCKING_NOTIFIER_HEAD(drm_notifier_list); - static struct dsi_display_mode_priv_info default_priv_info = { .panel_jitter_numer = DEFAULT_PANEL_JITTER_NUMERATOR, .panel_jitter_denom = DEFAULT_PANEL_JITTER_DENOMINATOR, .panel_prefill_lines = DEFAULT_PANEL_PREFILL_LINES, .dsc_enabled = false, }; -bool panel_init_judge; -struct drm_notify_data g_notify_data; - -/* - * drm_register_client - register a client notifier - * @nb:notifier block to callback when event happen - */ -int drm_register_client(struct notifier_block *nb) -{ - return blocking_notifier_chain_register(&drm_notifier_list, nb); -} -EXPORT_SYMBOL(drm_register_client); - -/* - * drm_unregister_client - unregister a client notifier - * @nb:notifier block to callback when event happen - */ -int drm_unregister_client(struct notifier_block *nb) -{ - return blocking_notifier_chain_unregister(&drm_notifier_list, nb); -} -EXPORT_SYMBOL(drm_unregister_client); - -/* - * drm_notifier_call_chain - notify clients of drm_event - * - */ - -int drm_notifier_call_chain(unsigned long val, void *v) -{ - return blocking_notifier_call_chain(&drm_notifier_list, val, v); -} -EXPORT_SYMBOL(drm_notifier_call_chain); static void convert_to_dsi_mode(const struct drm_display_mode *drm_mode, struct dsi_display_mode *dsi_mode) @@ -202,8 +165,6 @@ static void dsi_bridge_pre_enable(struct drm_bridge *bridge) { int rc = 0; struct dsi_bridge *c_bridge = to_dsi_bridge(bridge); - int event = DRM_BLANK_UNBLANK; - g_notify_data.data = &event; if (!bridge) { DSI_ERR("Invalid params\n"); @@ -215,8 +176,6 @@ static void dsi_bridge_pre_enable(struct drm_bridge *bridge) return; } - drm_notifier_call_chain(DRM_EARLY_EVENT_BLANK, &g_notify_data); - atomic_set(&c_bridge->display->panel->esd_recovery_pending, 0); /* By this point mode should have been validated through mode_fixup */ @@ -252,9 +211,6 @@ static void dsi_bridge_pre_enable(struct drm_bridge *bridge) c_bridge->id, rc); (void)dsi_display_unprepare(c_bridge->display); } - - drm_notifier_call_chain(DRM_EVENT_BLANK, &g_notify_data); - SDE_ATRACE_END("dsi_display_enable"); rc = dsi_display_splash_res_cleanup(c_bridge->display); @@ -317,7 +273,6 @@ static void dsi_bridge_disable(struct drm_bridge *bridge) sde_connector_helper_bridge_disable(display->drm_conn); } - panel_init_judge = false; rc = dsi_display_pre_disable(c_bridge->display); if (rc) { DSI_ERR("[%d] DSI display pre disable failed, rc=%d\n", @@ -329,16 +284,12 @@ static void dsi_bridge_post_disable(struct drm_bridge *bridge) { int rc = 0; struct dsi_bridge *c_bridge = to_dsi_bridge(bridge); - int event = DRM_BLANK_POWERDOWN; - g_notify_data.data = &event; if (!bridge) { DSI_ERR("Invalid params\n"); return; } - drm_notifier_call_chain(DRM_EARLY_EVENT_BLANK, &g_notify_data); - SDE_ATRACE_BEGIN("dsi_bridge_post_disable"); SDE_ATRACE_BEGIN("dsi_display_disable"); rc = dsi_display_disable(c_bridge->display); @@ -358,8 +309,6 @@ static void dsi_bridge_post_disable(struct drm_bridge *bridge) return; } SDE_ATRACE_END("dsi_bridge_post_disable"); - - drm_notifier_call_chain(DRM_EVENT_BLANK, &g_notify_data); } static void dsi_bridge_mode_set(struct drm_bridge *bridge, @@ -638,14 +587,16 @@ int dsi_conn_set_info_blob(struct drm_connector *connector, case DSI_OP_VIDEO_MODE: sde_kms_info_add_keystr(info, "panel mode", "video"); sde_kms_info_add_keystr(info, "qsync support", - panel->qsync_min_fps ? "true" : "false"); + panel->qsync_caps.qsync_min_fps ? + "true" : "false"); break; case DSI_OP_CMD_MODE: sde_kms_info_add_keystr(info, "panel mode", "command"); sde_kms_info_add_keyint(info, "mdp_transfer_time_us", mode_info->mdp_transfer_time_us); sde_kms_info_add_keystr(info, "qsync support", - panel->qsync_min_fps ? "true" : "false"); + panel->qsync_caps.qsync_min_fps ? + "true" : "false"); break; default: DSI_DEBUG("invalid panel type:%d\n", panel->panel_mode); diff --git a/techpack/display/msm/dsi/dsi_panel.c b/techpack/display/msm/dsi/dsi_panel.c index 77ba141709c3..63ba92f9badc 100644 --- a/techpack/display/msm/dsi/dsi_panel.c +++ b/techpack/display/msm/dsi/dsi_panel.c @@ -23,6 +23,8 @@ */ #define TOPOLOGY_SET_LEN 3 #define MAX_TOPOLOGY 5 +static unsigned int lcd_vcc_3v3_gpio=0xFFFF;//by eric.wang +int power_on_state=1; #define DSI_PANEL_DEFAULT_LABEL "Default dsi panel" @@ -32,10 +34,6 @@ #define MAX_PANEL_JITTER 10 #define DEFAULT_PANEL_PREFILL_LINES 25 #define MIN_PREFILL_LINES 35 -char g_lcd_id[128]; -extern bool panel_init_judge; - -bool backlight_val; enum dsi_dsc_ratio_type { DSC_8BPC_8BPP, @@ -99,7 +97,6 @@ static char dsi_dsc_rc_range_max_qp_1_1_scr1[][15] = { static char dsi_dsc_rc_range_bpg_offset[] = {2, 0, 0, -2, -4, -6, -8, -8, -8, -10, -10, -12, -12, -12, -12}; -extern int lcd_bl_set_led_brightness(int value); int dsi_dsc_create_pps_buf_cmd(struct msm_display_dsc_info *dsc, char *buf, int pps_id) { @@ -367,9 +364,6 @@ static int dsi_panel_reset(struct dsi_panel *panel) struct dsi_panel_reset_config *r_config = &panel->reset_config; int i; - printk("kook: dsi_panel_reset\n"); - printk("%d %s chenwenmin r_config->reset_gpio=%d\n", __LINE__, __func__, r_config->reset_gpio); - //return 0; if (gpio_is_valid(panel->reset_config.disp_en_gpio)) { rc = gpio_direction_output(panel->reset_config.disp_en_gpio, 1); if (rc) { @@ -391,7 +385,7 @@ static int dsi_panel_reset(struct dsi_panel *panel) gpio_set_value(r_config->reset_gpio, r_config->sequence[i].level); - printk("kook: reset value=%d, delay_time=%d", r_config->sequence[i].level, r_config->sequence[i].sleep_ms); + if (r_config->sequence[i].sleep_ms) usleep_range(r_config->sequence[i].sleep_ms * 1000, (r_config->sequence[i].sleep_ms * 1000) + 100); @@ -454,10 +448,29 @@ static int dsi_panel_set_pinctrl_state(struct dsi_panel *panel, bool enable) return rc; } +void set_lcd_vcc_3v3_gpio(int enable){ + static int count=0; + //pr_err("set_lcd_vcc_3v3_gpio by eric.wang\n"); + if(enable>0){ + //mdelay(10); + if(count==0){ + gpio_direction_output(lcd_vcc_3v3_gpio, 1);//bl enalbe + count=1; + pr_err("set_lcd_vcc_3v3_gpio:1 by eric.wang\n"); + } + }else{ + gpio_direction_output(lcd_vcc_3v3_gpio, 0);//bl disalbe + count=0; + pr_err("set_lcd_vcc_3v3_gpio:0 by eric.wang\n"); + } +} +EXPORT_SYMBOL(set_lcd_vcc_3v3_gpio);//by eric.wang + static int dsi_panel_power_on(struct dsi_panel *panel) { int rc = 0; + //DSI_ERR("[%s] dsi_panel_power_on\n",panel->name); rc = dsi_pwr_enable_regulator(&panel->power_info, true); if (rc) { @@ -493,24 +506,22 @@ static int dsi_panel_power_on(struct dsi_panel *panel) (void)dsi_pwr_enable_regulator(&panel->power_info, false); exit: + power_on_state=1; + pr_err("dsi_panel_power_on by eric.wang\n"); return rc; } static int dsi_panel_power_off(struct dsi_panel *panel) { int rc = 0; + DSI_ERR("[%s] dsi_panel_power_off\n",panel->name); - //return 0; if (gpio_is_valid(panel->reset_config.disp_en_gpio)) gpio_set_value(panel->reset_config.disp_en_gpio, 0); - - if (gpio_is_valid(panel->reset_config.reset_gpio)) - gpio_set_value(panel->reset_config.reset_gpio, 1);//set high, mod by chenwenmin - if (gpio_is_valid(panel->reset_config.reset_gpio) && !panel->reset_gpio_always_on) - gpio_set_value(panel->reset_config.reset_gpio, 1); + gpio_set_value(panel->reset_config.reset_gpio, 0); if (gpio_is_valid(panel->reset_config.lcd_mode_sel_gpio)) gpio_set_value(panel->reset_config.lcd_mode_sel_gpio, 0); @@ -532,7 +543,8 @@ static int dsi_panel_power_off(struct dsi_panel *panel) if (rc) DSI_ERR("[%s] failed to enable vregs, rc=%d\n", panel->name, rc); - + power_on_state=0; + pr_err("dsi_panel_power_off by eric.wang\n"); return rc; } static int dsi_panel_tx_cmd_set(struct dsi_panel *panel, @@ -654,7 +666,15 @@ static int dsi_panel_update_backlight(struct dsi_panel *panel, u32 bl_lvl) { int rc = 0; + static int old_bkl = 0; struct mipi_dsi_device *dsi; + //DSI_ERR("----------update dcs backlight:%d,by eric.wang------\n", bl_lvl); + if(bl_lvl==0&&old_bkl>0){ + set_lcd_vcc_3v3_gpio(0); + }else{ + //set_lcd_vcc_3v3_gpio(1); + } + old_bkl = bl_lvl;//by eric.wang if (!panel || (bl_lvl > 0xffff)) { DSI_ERR("invalid params\n"); @@ -673,7 +693,6 @@ static int dsi_panel_update_backlight(struct dsi_panel *panel, return rc; } -/* static int dsi_panel_update_pwm_backlight(struct dsi_panel *panel, u32 bl_lvl) { @@ -724,17 +743,12 @@ static int dsi_panel_update_pwm_backlight(struct dsi_panel *panel, error: return rc; } -*/ + int dsi_panel_set_backlight(struct dsi_panel *panel, u32 bl_lvl) { int rc = 0; struct dsi_backlight_config *bl = &panel->bl_config; - if(bl_lvl > 0) - backlight_val = true; - else - backlight_val = false; - if (panel->host_config.ext_bridge_mode) return 0; @@ -749,8 +763,7 @@ int dsi_panel_set_backlight(struct dsi_panel *panel, u32 bl_lvl) case DSI_BACKLIGHT_EXTERNAL: break; case DSI_BACKLIGHT_PWM: - lcd_bl_set_led_brightness((int)bl_lvl); - //rc = dsi_panel_update_pwm_backlight(panel, bl_lvl); + rc = dsi_panel_update_pwm_backlight(panel, bl_lvl); break; default: DSI_ERR("Backlight type(%d) not supported\n", bl->type); @@ -1334,8 +1347,15 @@ static int dsi_panel_parse_qsync_caps(struct dsi_panel *panel, struct device_node *of_node) { int rc = 0; - u32 val = 0; + u32 val = 0, i; + struct dsi_qsync_capabilities *qsync_caps = &panel->qsync_caps; + struct dsi_parser_utils *utils = &panel->utils; + const char *name = panel->name; + /** + * "mdss-dsi-qsync-min-refresh-rate" is defined in cmd mode and + * video mode when there is only one qsync min fps present. + */ rc = of_property_read_u32(of_node, "qcom,mdss-dsi-qsync-min-refresh-rate", &val); @@ -1343,8 +1363,75 @@ static int dsi_panel_parse_qsync_caps(struct dsi_panel *panel, DSI_DEBUG("[%s] qsync min fps not defined rc:%d\n", panel->name, rc); - panel->qsync_min_fps = val; + qsync_caps->qsync_min_fps = val; + /** + * "dsi-supported-qsync-min-fps-list" may be defined in video + * mode, only in dfps case when "qcom,dsi-supported-dfps-list" + * is defined. + */ + qsync_caps->qsync_min_fps_list_len = utils->count_u32_elems(utils->data, + "qcom,dsi-supported-qsync-min-fps-list"); + if (qsync_caps->qsync_min_fps_list_len < 1) + goto qsync_support; + + /** + * qcom,dsi-supported-qsync-min-fps-list cannot be defined + * along with qcom,mdss-dsi-qsync-min-refresh-rate. + */ + if (qsync_caps->qsync_min_fps_list_len >= 1 && + qsync_caps->qsync_min_fps) { + DSI_ERR("[%s] Both qsync nodes are defined\n", + name); + rc = -EINVAL; + goto error; + } + + if (panel->dfps_caps.dfps_list_len != + qsync_caps->qsync_min_fps_list_len) { + DSI_ERR("[%s] Qsync min fps list mismatch with dfps\n", name); + rc = -EINVAL; + goto error; + } + + qsync_caps->qsync_min_fps_list = + kcalloc(qsync_caps->qsync_min_fps_list_len, sizeof(u32), + GFP_KERNEL); + if (!qsync_caps->qsync_min_fps_list) { + rc = -ENOMEM; + goto error; + } + + rc = utils->read_u32_array(utils->data, + "qcom,dsi-supported-qsync-min-fps-list", + qsync_caps->qsync_min_fps_list, + qsync_caps->qsync_min_fps_list_len); + if (rc) { + DSI_ERR("[%s] Qsync min fps list parse failed\n", name); + rc = -EINVAL; + goto error; + } + + qsync_caps->qsync_min_fps = qsync_caps->qsync_min_fps_list[0]; + + for (i = 1; i < qsync_caps->qsync_min_fps_list_len; i++) { + if (qsync_caps->qsync_min_fps_list[i] < + qsync_caps->qsync_min_fps) + qsync_caps->qsync_min_fps = + qsync_caps->qsync_min_fps_list[i]; + } + +qsync_support: + /* allow qsync support only if DFPS is with VFP approach */ + if ((panel->dfps_caps.dfps_support) && + !(panel->dfps_caps.type == DSI_DFPS_IMMEDIATE_VFP)) + panel->qsync_caps.qsync_min_fps = 0; + +error: + if (rc < 0) { + qsync_caps->qsync_min_fps = 0; + qsync_caps->qsync_min_fps_list_len = 0; + } return rc; } @@ -1753,10 +1840,6 @@ const char *cmd_set_prop_map[DSI_CMD_SET_MAX] = { "qcom,mdss-dsi-post-mode-switch-on-command", "qcom,mdss-dsi-qsync-on-commands", "qcom,mdss-dsi-qsync-off-commands", - "qcom,mdss-dsi-cabc-on-command", - "qcom,mdss-dsi-cabc-off-command", - "qcom,mdss-dsi-cabc_movie-on-command", - "qcom,mdss-dsi-cabc_still-on-command", }; const char *cmd_set_state_map[DSI_CMD_SET_MAX] = { @@ -1783,10 +1866,6 @@ const char *cmd_set_state_map[DSI_CMD_SET_MAX] = { "qcom,mdss-dsi-post-mode-switch-on-command-state", "qcom,mdss-dsi-qsync-on-commands-state", "qcom,mdss-dsi-qsync-off-commands-state", - "qcom,mdss-dsi-cabc-on-command-state", - "qcom,mdss-dsi-cabc-off-command-state", - "qcom,mdss-dsi-cabc_movie-on-command-state", - "qcom,mdss-dsi-cabc_still-on-command-state", }; static int dsi_panel_get_cmd_pkt_count(const char *data, u32 length, u32 *cnt) @@ -2177,7 +2256,6 @@ static int dsi_panel_parse_gpios(struct dsi_panel *panel) panel->reset_config.reset_gpio = utils->get_named_gpio(utils->data, reset_gpio_name, 0); - DSI_ERR("kook: [%s], gpio=%d\n", panel->name, panel->reset_config.reset_gpio); if (!gpio_is_valid(panel->reset_config.reset_gpio) && !panel->host_config.ext_bridge_mode) { rc = panel->reset_config.reset_gpio; @@ -2199,6 +2277,7 @@ static int dsi_panel_parse_gpios(struct dsi_panel *panel) panel->name, rc); } } + lcd_vcc_3v3_gpio=panel->reset_config.disp_en_gpio;//by eric.wang panel->reset_config.lcd_mode_sel_gpio = utils->get_named_gpio( utils->data, mode_set_gpio_name, 0); @@ -3312,8 +3391,6 @@ struct dsi_panel *dsi_panel_get(struct device *parent, if (!panel->name) panel->name = DSI_PANEL_DEFAULT_LABEL; - strcpy(g_lcd_id,panel->name); - /* * Set panel type to LCD as default. */ @@ -3344,11 +3421,6 @@ struct dsi_panel *dsi_panel_get(struct device *parent, if (rc) DSI_DEBUG("failed to parse qsync features, rc=%d\n", rc); - /* allow qsync support only if DFPS is with VFP approach */ - if ((panel->dfps_caps.dfps_support) && - !(panel->dfps_caps.type == DSI_DFPS_IMMEDIATE_VFP)) - panel->qsync_min_fps = 0; - rc = dsi_panel_parse_dyn_clk_caps(panel); if (rc) DSI_ERR("failed to parse dynamic clk config, rc=%d\n", rc); @@ -4178,30 +4250,6 @@ static int dsi_panel_roi_prepare_dcs_cmds(struct dsi_panel_cmd_set *set, return rc; } -int dsi_panel_set_feature(struct dsi_panel *panel,enum dsi_cmd_set_type type) -{ - int rc = 0; - - if (!panel) { - pr_err("Invalid params\n"); - return -EINVAL; - } - - pr_info("hyper:%s panel_init_judge=%d type=%d,backlight_val = %d\n",__func__,panel_init_judge,type,backlight_val); - if ((!panel_init_judge) || (!backlight_val)) { - pr_err("hyper: con't set cmds type=%d\n",type); - return -EINVAL; - } - mutex_lock(&panel->panel_lock); - rc = dsi_panel_tx_cmd_set(panel, type); - if (rc) { - pr_err("[%s] failed to send DSI_CMD_SET_FEATURE_ON/OFF cmds, rc=%d,type=%d\n", - panel->name, rc,type); - } - mutex_unlock(&panel->panel_lock); - return rc; -} - int dsi_panel_send_qsync_on_dcs(struct dsi_panel *panel, int ctrl_idx) { @@ -4421,10 +4469,8 @@ int dsi_panel_enable(struct dsi_panel *panel) if (rc) DSI_ERR("[%s] failed to send DSI_CMD_SET_ON cmds, rc=%d\n", panel->name, rc); - else { + else panel->panel_initialized = true; - panel_init_judge = true; - } mutex_unlock(&panel->panel_lock); return rc; } @@ -4446,7 +4492,6 @@ int dsi_panel_post_enable(struct dsi_panel *panel) panel->name, rc); goto error; } - panel_init_judge = true; error: mutex_unlock(&panel->panel_lock); return rc; @@ -4472,8 +4517,6 @@ int dsi_panel_pre_disable(struct dsi_panel *panel) error: mutex_unlock(&panel->panel_lock); - panel->panel_initialized = false; - panel_init_judge = false; return rc; } @@ -4513,7 +4556,6 @@ int dsi_panel_disable(struct dsi_panel *panel) } } panel->panel_initialized = false; - panel_init_judge = false; panel->power_mode = SDE_MODE_DPMS_OFF; mutex_unlock(&panel->panel_lock); diff --git a/techpack/display/msm/dsi/dsi_panel.h b/techpack/display/msm/dsi/dsi_panel.h index 6b26102f0d97..9f0e1cb17e40 100644 --- a/techpack/display/msm/dsi/dsi_panel.h +++ b/techpack/display/msm/dsi/dsi_panel.h @@ -84,6 +84,13 @@ struct dsi_dfps_capabilities { bool dfps_support; }; +struct dsi_qsync_capabilities { + /* qsync disabled if qsync_min_fps = 0 */ + u32 qsync_min_fps; + u32 *qsync_min_fps_list; + int qsync_min_fps_list_len; +}; + struct dsi_dyn_clk_caps { bool dyn_clk_support; u32 *bit_clk_list; @@ -104,14 +111,6 @@ struct dsi_panel_phy_props { enum dsi_panel_rotation rotation; }; -struct dsi_whitep_display_para { - int white_point_x; - int white_point_y; - u32 white_point_r; - u32 white_point_g; - u32 white_point_b; -}; - struct dsi_backlight_config { enum dsi_backlight_type type; enum bl_update_flag bl_update; @@ -215,7 +214,7 @@ struct dsi_panel { bool panel_initialized; bool te_using_watchdog_timer; - u32 qsync_min_fps; + struct dsi_qsync_capabilities qsync_caps; char dsc_pps_cmd[DSI_CMD_PPS_SIZE]; enum dsi_dms_mode dms_mode; @@ -305,8 +304,6 @@ int dsi_panel_pre_disable(struct dsi_panel *panel); int dsi_panel_disable(struct dsi_panel *panel); -int dsi_panel_set_feature(struct dsi_panel *panel,enum dsi_cmd_set_type type); - int dsi_panel_unprepare(struct dsi_panel *panel); int dsi_panel_post_unprepare(struct dsi_panel *panel); diff --git a/techpack/display/msm/msm_drv.c b/techpack/display/msm/msm_drv.c index 3f1554e73cc9..abdf61e6be50 100644 --- a/techpack/display/msm/msm_drv.c +++ b/techpack/display/msm/msm_drv.c @@ -1497,6 +1497,13 @@ static int msm_release(struct inode *inode, struct file *filp) kfree(node); } + /** + * Handle preclose operation here for removing fb's whose + * refcount > 1. This operation is not triggered from upstream + * drm as msm_driver does not support DRIVER_LEGACY feature. + */ + msm_preclose(dev, file_priv); + return drm_release(inode, filp); } @@ -1657,7 +1664,6 @@ static struct drm_driver msm_driver = { DRIVER_ATOMIC | DRIVER_MODESET, .open = msm_open, - .preclose = msm_preclose, .postclose = msm_postclose, .lastclose = msm_lastclose, .irq_handler = msm_irq, diff --git a/techpack/display/msm/msm_drv.h b/techpack/display/msm/msm_drv.h index bd784d4ae57d..20969665356c 100644 --- a/techpack/display/msm/msm_drv.h +++ b/techpack/display/msm/msm_drv.h @@ -512,6 +512,7 @@ struct msm_resource_caps_info { * used instead of panel TE in cmd mode panels * @roi_caps: Region of interest capability info * @qsync_min_fps Minimum fps supported by Qsync feature + * @has_qsync_min_fps_list True if dsi-supported-qsync-min-fps-list exits * @te_source vsync source pin information */ struct msm_display_info { @@ -535,6 +536,8 @@ struct msm_display_info { struct msm_roi_caps roi_caps; uint32_t qsync_min_fps; + bool has_qsync_min_fps_list; + uint32_t te_source; }; diff --git a/techpack/display/msm/msm_notifier.c b/techpack/display/msm/msm_notifier.c index 3b1729d9435d..20be9ba81de5 100644 --- a/techpack/display/msm/msm_notifier.c +++ b/techpack/display/msm/msm_notifier.c @@ -53,7 +53,9 @@ static int msm_notifier_fps_chg_callback(struct notifier_block *nb, * Default will be FPS60 and sent to scheduler during suspend. */ fps = notifier_data->refresh_rate; - if (fps > FPS90) + if (fps > FPS120) + sched_fps = FPS144; + else if (fps > FPS90) sched_fps = FPS120; else if (fps > FPS60) sched_fps = FPS90; diff --git a/techpack/display/msm/sde/sde_connector.c b/techpack/display/msm/sde/sde_connector.c index d9b6610968fa..c8bb88bc2ecd 100644 --- a/techpack/display/msm/sde/sde_connector.c +++ b/techpack/display/msm/sde/sde_connector.c @@ -148,7 +148,7 @@ static int sde_backlight_setup(struct sde_connector *c_conn, display = (struct dsi_display *) c_conn->display; bl_config = &display->panel->bl_config; props.max_brightness = bl_config->brightness_max_level; - props.brightness = 1023; + props.brightness = bl_config->brightness_max_level; snprintf(bl_node_name, BL_NODE_NAME_SIZE, "panel%u-backlight", display_count); c_conn->bl_device = backlight_device_register(bl_node_name, dev->dev, @@ -1378,10 +1378,6 @@ static int sde_connector_atomic_set_property(struct drm_connector *connector, /* connector-specific property handling */ idx = msm_property_index(&c_conn->property_info, property); switch (idx) { - case CONNECTOR_PROP_LP: - if(connector->dev) - connector->dev->doze_state = val; - break; case CONNECTOR_PROP_OUT_FB: /* clear old fb, if present */ if (c_state->out_fb) @@ -2128,8 +2124,6 @@ static int sde_connector_atomic_check(struct drm_connector *connector, struct drm_connector_state *new_conn_state) { struct sde_connector *c_conn; - struct sde_connector_state *c_state; - bool qsync_dirty = false, has_modeset = false; if (!connector) { SDE_ERROR("invalid connector\n"); @@ -2142,19 +2136,6 @@ static int sde_connector_atomic_check(struct drm_connector *connector, } c_conn = to_sde_connector(connector); - c_state = to_sde_connector_state(new_conn_state); - - has_modeset = sde_crtc_atomic_check_has_modeset(new_conn_state->state, - new_conn_state->crtc); - qsync_dirty = msm_property_is_dirty(&c_conn->property_info, - &c_state->property_state, - CONNECTOR_PROP_QSYNC_MODE); - - SDE_DEBUG("has_modeset %d qsync_dirty %d\n", has_modeset, qsync_dirty); - if (has_modeset && qsync_dirty) { - SDE_ERROR("invalid qsync update during modeset\n"); - return -EINVAL; - } if (c_conn->ops.atomic_check) return c_conn->ops.atomic_check(connector, diff --git a/techpack/display/msm/sde/sde_connector.h b/techpack/display/msm/sde/sde_connector.h index 9389f135109e..cbc6bfa0ce7f 100644 --- a/techpack/display/msm/sde/sde_connector.h +++ b/techpack/display/msm/sde/sde_connector.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. */ #ifndef _SDE_CONNECTOR_H_ @@ -338,6 +338,14 @@ struct sde_connector_ops { */ int (*prepare_commit)(void *display, struct msm_display_conn_params *params); + + /** + * get_qsync_min_fps - Get qsync min fps from qsync-min-fps-list + * @display: Pointer to private display structure + * @mode_fps: Fps value in dfps list + * Returns: Qsync min fps value on success + */ + int (*get_qsync_min_fps)(void *display, u32 mode_fps); }; /** diff --git a/techpack/display/msm/sde/sde_crtc.c b/techpack/display/msm/sde/sde_crtc.c index 395299fcff47..5f3aede1f8ca 100644 --- a/techpack/display/msm/sde/sde_crtc.c +++ b/techpack/display/msm/sde/sde_crtc.c @@ -379,14 +379,34 @@ static ssize_t vsync_event_show(struct device *device, ktime_to_ns(sde_crtc->vblank_last_cb_time)); } +static ssize_t retire_frame_event_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct drm_crtc *crtc; + struct sde_crtc *sde_crtc; + + if (!device || !buf) { + SDE_ERROR("invalid input param(s)\n"); + return -EAGAIN; + } + + crtc = dev_get_drvdata(device); + sde_crtc = to_sde_crtc(crtc); + SDE_EVT32(DRMID(&sde_crtc->base)); + return scnprintf(buf, PAGE_SIZE, "RETIRE_FRAME_TIME=%llu\n", + ktime_to_ns(sde_crtc->retire_frame_event_time)); +} + static DEVICE_ATTR_RO(vsync_event); static DEVICE_ATTR_RO(measured_fps); static DEVICE_ATTR_RW(fps_periodicity_ms); +static DEVICE_ATTR_RO(retire_frame_event); static struct attribute *sde_crtc_dev_attrs[] = { &dev_attr_vsync_event.attr, &dev_attr_measured_fps.attr, &dev_attr_fps_periodicity_ms.attr, + &dev_attr_retire_frame_event.attr, NULL }; @@ -410,6 +430,8 @@ static void sde_crtc_destroy(struct drm_crtc *crtc) if (sde_crtc->vsync_event_sf) sysfs_put(sde_crtc->vsync_event_sf); + if (sde_crtc->retire_frame_event_sf) + sysfs_put(sde_crtc->retire_frame_event_sf); if (sde_crtc->sysfs_dev) device_unregister(sde_crtc->sysfs_dev); @@ -1703,8 +1725,12 @@ int sde_crtc_state_find_plane_fb_modes(struct drm_crtc_state *state, static void _sde_drm_fb_sec_dir_trans( struct sde_kms_smmu_state_data *smmu_state, uint32_t secure_level, - struct sde_mdss_cfg *catalog, bool old_valid_fb, int *ops) + struct sde_mdss_cfg *catalog, bool old_valid_fb, int *ops, + struct drm_crtc_state *old_crtc_state) { + struct sde_crtc_state *old_cstate = to_sde_crtc_state(old_crtc_state); + int old_secure_session = old_cstate->secure_session; + /* secure display usecase */ if ((smmu_state->state == ATTACHED) && (secure_level == SDE_DRM_SEC_ONLY)) { @@ -1725,6 +1751,10 @@ static void _sde_drm_fb_sec_dir_trans( smmu_state->secure_level = secure_level; smmu_state->transition_type = PRE_COMMIT; *ops |= SDE_KMS_OPS_SECURE_STATE_CHANGE; + if (old_secure_session == + SDE_SECURE_VIDEO_SESSION) + *ops |= (SDE_KMS_OPS_WAIT_FOR_TX_DONE | + SDE_KMS_OPS_CLEANUP_PLANE_FB); } } @@ -1850,7 +1880,7 @@ int sde_crtc_get_secure_transition_ops(struct drm_crtc *crtc, switch (translation_mode) { case SDE_DRM_FB_SEC_DIR_TRANS: _sde_drm_fb_sec_dir_trans(smmu_state, secure_level, - catalog, old_valid_fb, &ops); + catalog, old_valid_fb, &ops, old_crtc_state); if (clone_mode && (ops & SDE_KMS_OPS_SECURE_STATE_CHANGE)) ops |= SDE_KMS_OPS_WAIT_FOR_TX_DONE; break; @@ -2159,6 +2189,12 @@ static void sde_crtc_frame_event_cb(void *data, u32 event) } } + if ((event & SDE_ENCODER_FRAME_EVENT_SIGNAL_RETIRE_FENCE) && + (sde_crtc && sde_crtc->retire_frame_event_sf)) { + sde_crtc->retire_frame_event_time = ktime_get(); + sysfs_notify_dirent(sde_crtc->retire_frame_event_sf); + } + fevent->event = event; fevent->crtc = crtc; fevent->connector = cb_data->connector; @@ -2444,17 +2480,16 @@ static void _sde_crtc_set_input_fence_timeout(struct sde_crtc_state *cstate) cstate->input_fence_timeout_ns *= NSEC_PER_MSEC; } -/** - * _sde_crtc_clear_dim_layers_v1 - clear all dim layer settings - * @cstate: Pointer to sde crtc state - */ -static void _sde_crtc_clear_dim_layers_v1(struct sde_crtc_state *cstate) +void _sde_crtc_clear_dim_layers_v1(struct drm_crtc_state *state) { u32 i; + struct sde_crtc_state *cstate; - if (!cstate) + if (!state) return; + cstate = to_sde_crtc_state(state); + for (i = 0; i < cstate->num_dim_layers; i++) memset(&cstate->dim_layer[i], 0, sizeof(cstate->dim_layer[i])); @@ -2483,7 +2518,7 @@ static void _sde_crtc_set_dim_layer_v1(struct drm_crtc *crtc, if (!usr_ptr) { /* usr_ptr is null when setting the default property value */ - _sde_crtc_clear_dim_layers_v1(cstate); + _sde_crtc_clear_dim_layers_v1(&cstate->base); SDE_DEBUG("dim_layer data removed\n"); return; } @@ -4319,6 +4354,55 @@ static int _sde_crtc_check_secure_single_encoder(struct drm_crtc *crtc, return 0; } +static int _sde_crtc_check_secure_transition(struct drm_crtc *crtc, + struct drm_crtc_state *state, bool is_video_mode) +{ + struct sde_crtc_state *old_cstate = to_sde_crtc_state(crtc->state); + struct sde_crtc_state *new_cstate = to_sde_crtc_state(state); + int old_secure_session = old_cstate->secure_session; + int new_secure_session = new_cstate->secure_session; + int ret = 0; + + /* + * Direct transition from Secure Camera to Secure UI(&viceversa) + * is not allowed + */ + if ((old_secure_session == SDE_SECURE_CAMERA_SESSION && + new_secure_session == SDE_SECURE_UI_SESSION) || + (old_secure_session == SDE_SECURE_UI_SESSION && + new_secure_session == SDE_SECURE_CAMERA_SESSION)) { + SDE_EVT32(DRMID(crtc), old_secure_session, + new_secure_session, SDE_EVTLOG_ERROR); + ret = -EINVAL; + } + + /* + * In video mode, null commit is required for transition between + * secure video & secure camera + */ + if (is_video_mode && + ((old_secure_session == SDE_SECURE_CAMERA_SESSION && + new_secure_session == SDE_SECURE_VIDEO_SESSION) || + (old_secure_session == SDE_SECURE_VIDEO_SESSION && + new_secure_session == SDE_SECURE_CAMERA_SESSION))) { + SDE_EVT32(DRMID(crtc), old_secure_session, + new_secure_session, SDE_EVTLOG_ERROR); + ret = -EINVAL; + } + + if (old_secure_session != new_secure_session) + SDE_EVT32(DRMID(crtc), old_secure_session, + new_secure_session); + + SDE_DEBUG("old session: %d new session : %d\n", + old_secure_session, new_secure_session); + if (ret) + SDE_ERROR("invalid transition old:%d new:%d\n", + old_secure_session, new_secure_session); + + return ret; +} + static int _sde_crtc_check_secure_state_smmu_translation(struct drm_crtc *crtc, struct drm_crtc_state *state, struct sde_kms *sde_kms, int secure, int fb_ns, int fb_sec, int fb_sec_dir) @@ -4333,19 +4417,8 @@ static int _sde_crtc_check_secure_state_smmu_translation(struct drm_crtc *crtc, MSM_DISPLAY_VIDEO_MODE); } - /* - * Secure display to secure camera needs without direct - * transition is currently not allowed - */ - if (fb_sec_dir && secure == SDE_DRM_SEC_NON_SEC && - smmu_state->state != ATTACHED && - smmu_state->secure_level == SDE_DRM_SEC_ONLY) { - - SDE_EVT32(DRMID(crtc), fb_ns, fb_sec_dir, - smmu_state->state, smmu_state->secure_level, - secure); + if (_sde_crtc_check_secure_transition(crtc, state, is_video_mode)) goto sec_err; - } /* * In video mode check for null commit before transition @@ -4411,6 +4484,33 @@ static int _sde_crtc_check_secure_conn(struct drm_crtc *crtc, return 0; } +static int _sde_crtc_populate_secure_session(struct drm_crtc_state *state, + int secure, int fb_ns, int fb_sec, int fb_sec_dir) +{ + struct sde_crtc_state *cstate = to_sde_crtc_state(state); + + if (secure == SDE_DRM_SEC_ONLY && fb_sec_dir && !fb_sec && !fb_ns) + cstate->secure_session = SDE_SECURE_UI_SESSION; + else if (secure == SDE_DRM_SEC_NON_SEC && fb_sec_dir && !fb_sec) + cstate->secure_session = SDE_SECURE_CAMERA_SESSION; + else if (secure == SDE_DRM_SEC_NON_SEC && !fb_sec_dir && fb_sec) + cstate->secure_session = SDE_SECURE_VIDEO_SESSION; + else if (secure == SDE_DRM_SEC_NON_SEC && !fb_sec_dir && + !fb_sec && fb_ns) + cstate->secure_session = SDE_NON_SECURE_SESSION; + else if (!fb_sec_dir && !fb_sec && !fb_ns) + cstate->secure_session = SDE_NULL_SESSION; + else { + SDE_ERROR( + "invalid session sec:%d fb_sec_dir:%d fb_sec:%d fb_ns:%d\n", + cstate->secure_session, fb_sec_dir, + fb_sec, fb_ns); + return -EINVAL; + } + + return 0; +} + static int _sde_crtc_check_secure_state(struct drm_crtc *crtc, struct drm_crtc_state *state, struct plane_state pstates[], int cnt) @@ -4441,6 +4541,11 @@ static int _sde_crtc_check_secure_state(struct drm_crtc *crtc, if (rc) return rc; + rc = _sde_crtc_populate_secure_session(state, secure, + fb_ns, fb_sec, fb_sec_dir); + if (rc) + return rc; + rc = _sde_crtc_check_secure_blend_config(crtc, state, pstates, cstate, sde_kms, cnt, secure, fb_ns, fb_sec, fb_sec_dir); if (rc) @@ -6258,6 +6363,12 @@ int sde_crtc_post_init(struct drm_device *dev, struct drm_crtc *crtc) SDE_ERROR("crtc:%d vsync_event sysfs create failed\n", crtc->base.id); + sde_crtc->retire_frame_event_sf = sysfs_get_dirent( + sde_crtc->sysfs_dev->kobj.sd, "retire_frame_event"); + if (!sde_crtc->retire_frame_event_sf) + SDE_ERROR("crtc:%d retire frame event sysfs create failed\n", + crtc->base.id); + end: return rc; } diff --git a/techpack/display/msm/sde/sde_crtc.h b/techpack/display/msm/sde/sde_crtc.h index add97c5eb12a..e7330f7fba1a 100644 --- a/techpack/display/msm/sde/sde_crtc.h +++ b/techpack/display/msm/sde/sde_crtc.h @@ -35,6 +35,22 @@ /* Expand it to 2x for handling atleast 2 connectors safely */ #define SDE_CRTC_FRAME_EVENT_SIZE (4 * 2) +/** + * enum sde_session_type: session type + * @SDE_SECURE_UI_SESSION: secure UI usecase + * @SDE_SECURE_CAMERA_SESSION: secure camera usecase + * @SDE_SECURE_VIDEO_SESSION: secure video usecase + * @SDE_NON_SECURE_SESSION: non secure usecase + * @SDE_NULL_SESSION: null commit usecase + */ +enum sde_session_type { + SDE_SECURE_UI_SESSION, + SDE_SECURE_CAMERA_SESSION, + SDE_SECURE_VIDEO_SESSION, + SDE_NON_SECURE_SESSION, + SDE_NULL_SESSION, +}; + /** * enum sde_crtc_client_type: crtc client type * @RT_CLIENT: RealTime client like video/cmd mode display @@ -221,11 +237,13 @@ struct sde_crtc_misr_info { * @debugfs_root : Parent of debugfs node * @priv_handle : Pointer to external private handle, if present * @vblank_cb_count : count of vblank callback since last reset + * @retire_frame_event_time : ktime at last retire frame event * @play_count : frame count between crtc enable and disable * @vblank_cb_time : ktime at vblank count reset * @vblank_last_cb_time : ktime at last vblank notification * @sysfs_dev : sysfs device node for crtc * @vsync_event_sf : vsync event notifier sysfs device + * @retire_frame_event_sf :retire frame event notifier sysfs device * @enabled : whether the SDE CRTC is currently enabled. updated in the * commit-thread, not state-swap time which is earlier, so * safe to make decisions on during VBLANK on/off work @@ -295,10 +313,12 @@ struct sde_crtc { u32 vblank_cb_count; u64 play_count; ktime_t vblank_cb_time; + ktime_t retire_frame_event_time; ktime_t vblank_last_cb_time; struct sde_crtc_fps_info fps_info; struct device *sysfs_dev; struct kernfs_node *vsync_event_sf; + struct kernfs_node *retire_frame_event_sf; bool enabled; bool ds_reconfig; @@ -380,6 +400,7 @@ struct sde_crtc { * @ds_cfg: Destination scaler config * @scl3_lut_cfg: QSEED3 lut config * @new_perf: new performance state being requested + * @secure_session: Indicates the type of secure session */ struct sde_crtc_state { struct drm_crtc_state base; @@ -409,6 +430,7 @@ struct sde_crtc_state { struct sde_hw_scaler3_lut_cfg scl3_lut_cfg; struct sde_core_perf_params new_perf; + int secure_session; }; enum sde_crtc_irq_state { @@ -839,6 +861,12 @@ void sde_crtc_get_misr_info(struct drm_crtc *crtc, int sde_crtc_get_num_datapath(struct drm_crtc *crtc, struct drm_connector *connector); +/** + * _sde_crtc_clear_dim_layers_v1 - clear all dim layer settings + * @cstate: Pointer to drm crtc state + */ +void _sde_crtc_clear_dim_layers_v1(struct drm_crtc_state *state); + /* * sde_crtc_set_compression_ratio - set compression ratio src_bpp/target_bpp * @msm_mode_info: Mode info diff --git a/techpack/display/msm/sde/sde_encoder.c b/techpack/display/msm/sde/sde_encoder.c index 32c983ba2ba5..691bbc22ba38 100644 --- a/techpack/display/msm/sde/sde_encoder.c +++ b/techpack/display/msm/sde/sde_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2021, The Linux Foundation. All rights reserved. * Copyright (C) 2013 Red Hat * Author: Rob Clark * @@ -1113,6 +1113,7 @@ static int sde_encoder_virt_atomic_check( struct sde_crtc_state *sde_crtc_state = NULL; enum sde_rm_topology_name old_top; int ret = 0; + bool qsync_dirty = false, has_modeset = false; if (!drm_enc || !crtc_state || !conn_state) { SDE_ERROR("invalid arg(s), drm_enc %d, crtc/conn state %d/%d\n", @@ -1167,6 +1168,22 @@ static int sde_encoder_virt_atomic_check( } drm_mode_set_crtcinfo(adj_mode, 0); + + has_modeset = sde_crtc_atomic_check_has_modeset(conn_state->state, + conn_state->crtc); + qsync_dirty = msm_property_is_dirty(&sde_conn->property_info, + &sde_conn_state->property_state, + CONNECTOR_PROP_QSYNC_MODE); + + if (has_modeset && qsync_dirty && + (msm_is_mode_seamless_poms(adj_mode) || + msm_is_mode_seamless_dms(adj_mode) || + msm_is_mode_seamless_dyn_clk(adj_mode))) { + SDE_ERROR("invalid qsync update during modeset priv flag:%x\n", + adj_mode->private_flags); + return -EINVAL; + } + SDE_EVT32(DRMID(drm_enc), adj_mode->flags, adj_mode->private_flags); return ret; @@ -3865,10 +3882,12 @@ static void sde_encoder_frame_done_callback( static void sde_encoder_get_qsync_fps_callback( struct drm_encoder *drm_enc, - u32 *qsync_fps) + u32 *qsync_fps, u32 vrr_fps) { struct msm_display_info *disp_info; struct sde_encoder_virt *sde_enc; + int rc = 0; + struct sde_connector *sde_conn; if (!qsync_fps) return; @@ -3882,6 +3901,31 @@ static void sde_encoder_get_qsync_fps_callback( sde_enc = to_sde_encoder_virt(drm_enc); disp_info = &sde_enc->disp_info; *qsync_fps = disp_info->qsync_min_fps; + + /** + * If "dsi-supported-qsync-min-fps-list" is defined, get + * the qsync min fps corresponding to the fps in dfps list + */ + if (disp_info->has_qsync_min_fps_list) { + + if (!sde_enc->cur_master || + !(sde_enc->disp_info.capabilities & + MSM_DISPLAY_CAP_VID_MODE)) { + SDE_ERROR("invalid qsync settings %b\n", + !sde_enc->cur_master); + return; + } + sde_conn = to_sde_connector(sde_enc->cur_master->connector); + + if (sde_conn->ops.get_qsync_min_fps) + rc = sde_conn->ops.get_qsync_min_fps(sde_conn->display, + vrr_fps); + if (rc <= 0) { + SDE_ERROR("invalid qsync min fps %d\n", rc); + return; + } + *qsync_fps = rc; + } } int sde_encoder_idle_request(struct drm_encoder *drm_enc) diff --git a/techpack/display/msm/sde/sde_encoder_phys.h b/techpack/display/msm/sde/sde_encoder_phys.h index a7fa9bda1277..22355dc99f9e 100644 --- a/techpack/display/msm/sde/sde_encoder_phys.h +++ b/techpack/display/msm/sde/sde_encoder_phys.h @@ -82,7 +82,7 @@ struct sde_encoder_virt_ops { void (*handle_frame_done)(struct drm_encoder *parent, struct sde_encoder_phys *phys, u32 event); void (*get_qsync_fps)(struct drm_encoder *parent, - u32 *qsync_fps); + u32 *qsync_fps, u32 vrr_fps); }; /** diff --git a/techpack/display/msm/sde/sde_encoder_phys_cmd.c b/techpack/display/msm/sde/sde_encoder_phys_cmd.c index 4f99a496bcfa..f9f3873e3d11 100644 --- a/techpack/display/msm/sde/sde_encoder_phys_cmd.c +++ b/techpack/display/msm/sde/sde_encoder_phys_cmd.c @@ -949,7 +949,7 @@ static int _get_tearcheck_threshold(struct sde_encoder_phys *phys_enc, if (phys_enc->parent_ops.get_qsync_fps) phys_enc->parent_ops.get_qsync_fps( - phys_enc->parent, &qsync_min_fps); + phys_enc->parent, &qsync_min_fps, 0); if (!qsync_min_fps || !default_fps || !yres) { SDE_ERROR_CMDENC(cmd_enc, diff --git a/techpack/display/msm/sde/sde_encoder_phys_vid.c b/techpack/display/msm/sde/sde_encoder_phys_vid.c index 54faa7cb5cb6..7708cf8fa2ec 100644 --- a/techpack/display/msm/sde/sde_encoder_phys_vid.c +++ b/techpack/display/msm/sde/sde_encoder_phys_vid.c @@ -461,7 +461,7 @@ static void sde_encoder_phys_vid_setup_timing_engine( exit: if (phys_enc->parent_ops.get_qsync_fps) phys_enc->parent_ops.get_qsync_fps( - phys_enc->parent, &qsync_min_fps); + phys_enc->parent, &qsync_min_fps, mode.vrefresh); /* only panels which support qsync will have a non-zero min fps */ if (qsync_min_fps) { @@ -1125,13 +1125,21 @@ static void sde_encoder_phys_vid_handle_post_kickoff( static void sde_encoder_phys_vid_prepare_for_commit( struct sde_encoder_phys *phys_enc) { + struct drm_crtc *crtc; - if (!phys_enc) { + if (!phys_enc || !phys_enc->parent) { SDE_ERROR("invalid encoder parameters\n"); return; } - if (sde_connector_is_qsync_updated(phys_enc->connector)) + crtc = phys_enc->parent->crtc; + if (!crtc || !crtc->state) { + SDE_ERROR("invalid crtc or crtc state\n"); + return; + } + + if (!msm_is_mode_seamless_vrr(&crtc->state->adjusted_mode) && + sde_connector_is_qsync_updated(phys_enc->connector)) _sde_encoder_phys_vid_avr_ctrl(phys_enc); } diff --git a/techpack/display/msm/sde/sde_hw_catalog.c b/techpack/display/msm/sde/sde_hw_catalog.c index e49e4352a516..2e8bb2af68c0 100644 --- a/techpack/display/msm/sde/sde_hw_catalog.c +++ b/techpack/display/msm/sde/sde_hw_catalog.c @@ -1612,7 +1612,6 @@ static int sde_sspp_parse_dt(struct device_node *np, sde_cfg->mdp[j].clk_ctrls[sspp->clk_ctrl].bit_off = PROP_BITVALUE_ACCESS(prop_value, SSPP_CLK_CTRL, i, 1); - sde_cfg->mdp[j].clk_ctrls[sspp->clk_ctrl].val = -1; } SDE_DEBUG( @@ -2126,7 +2125,6 @@ static int sde_wb_parse_dt(struct device_node *np, struct sde_mdss_cfg *sde_cfg) sde_cfg->mdp[j].clk_ctrls[wb->clk_ctrl].bit_off = PROP_BITVALUE_ACCESS(prop_value, WB_CLK_CTRL, i, 1); - sde_cfg->mdp[j].clk_ctrls[wb->clk_ctrl].val = -1; } wb->format_list = sde_cfg->wb_formats; diff --git a/techpack/display/msm/sde/sde_hw_catalog.h b/techpack/display/msm/sde/sde_hw_catalog.h index d827e129baf6..bed02d96fbde 100644 --- a/techpack/display/msm/sde/sde_hw_catalog.h +++ b/techpack/display/msm/sde/sde_hw_catalog.h @@ -720,12 +720,10 @@ enum sde_clk_ctrl_type { /* struct sde_clk_ctrl_reg : Clock control register * @reg_off: register offset * @bit_off: bit offset - * @val: current bit value */ struct sde_clk_ctrl_reg { u32 reg_off; u32 bit_off; - int val; }; /* struct sde_mdp_cfg : MDP TOP-BLK instance info diff --git a/techpack/display/msm/sde/sde_hw_top.c b/techpack/display/msm/sde/sde_hw_top.c index 5af23dff8bc5..b02cd17cbb28 100644 --- a/techpack/display/msm/sde/sde_hw_top.c +++ b/techpack/display/msm/sde/sde_hw_top.c @@ -177,7 +177,6 @@ static void sde_hw_setup_cdm_output(struct sde_hw_mdp *mdp, static bool sde_hw_setup_clk_force_ctrl(struct sde_hw_mdp *mdp, enum sde_clk_ctrl_type clk_ctrl, bool enable) { - struct sde_clk_ctrl_reg *ctrl_reg; struct sde_hw_blk_reg_map *c; u32 reg_off, bit_off; u32 reg_val, new_val; @@ -191,12 +190,8 @@ static bool sde_hw_setup_clk_force_ctrl(struct sde_hw_mdp *mdp, if (clk_ctrl <= SDE_CLK_CTRL_NONE || clk_ctrl >= SDE_CLK_CTRL_MAX) return false; - ctrl_reg = (struct sde_clk_ctrl_reg *)&mdp->caps->clk_ctrls[clk_ctrl]; - if (cmpxchg(&ctrl_reg->val, !enable, enable) == enable) - return enable; - - reg_off = ctrl_reg->reg_off; - bit_off = ctrl_reg->bit_off; + reg_off = mdp->caps->clk_ctrls[clk_ctrl].reg_off; + bit_off = mdp->caps->clk_ctrls[clk_ctrl].bit_off; reg_val = SDE_REG_READ(c, reg_off); diff --git a/techpack/display/msm/sde/sde_hw_util.c b/techpack/display/msm/sde/sde_hw_util.c index ff4b5dfd08f0..8b65855d68c6 100644 --- a/techpack/display/msm/sde/sde_hw_util.c +++ b/techpack/display/msm/sde/sde_hw_util.c @@ -76,7 +76,10 @@ void sde_reg_write(struct sde_hw_blk_reg_map *c, if (c->log_mask & sde_hw_util_log_mask) SDE_DEBUG_DRIVER("[%s:0x%X] <= 0x%X\n", name, c->blk_off + reg_off, val); + SDE_EVT32_REGWRITE(c->blk_off, reg_off, val); writel_relaxed(val, c->base_off + c->blk_off + reg_off); + SDE_REG_LOG(c->log_mask ? ilog2(c->log_mask)+1 : 0, + val, c->blk_off + reg_off); } int sde_reg_read(struct sde_hw_blk_reg_map *c, u32 reg_off) diff --git a/techpack/display/msm/sde/sde_kms.c b/techpack/display/msm/sde/sde_kms.c index 050ba34e0b35..f2f5f9714dd4 100644 --- a/techpack/display/msm/sde/sde_kms.c +++ b/techpack/display/msm/sde/sde_kms.c @@ -1134,10 +1134,12 @@ static void sde_kms_check_for_ext_vote(struct sde_kms *sde_kms, * cases, allow the target to go through a gdsc toggle after * crtc is disabled. */ - if (!crtc_enabled && phandle->is_ext_vote_en) { + if (!crtc_enabled && (phandle->is_ext_vote_en || + !dev->dev->power.runtime_auto)) { pm_runtime_put_sync(sde_kms->dev->dev); - SDE_EVT32(phandle->is_ext_vote_en); pm_runtime_get_sync(sde_kms->dev->dev); + SDE_EVT32(phandle->is_ext_vote_en, + dev->dev->power.runtime_auto); } mutex_unlock(&phandle->ext_client_lock); @@ -1430,6 +1432,7 @@ static int _sde_kms_setup_displays(struct drm_device *dev, .cont_splash_config = dsi_display_cont_splash_config, .get_panel_vfp = dsi_display_get_panel_vfp, .get_default_lms = dsi_display_get_default_lms, + .get_qsync_min_fps = dsi_display_get_qsync_min_fps, }; static const struct sde_connector_ops wb_ops = { .post_init = sde_wb_connector_post_init, @@ -2028,6 +2031,48 @@ static void sde_kms_destroy(struct msm_kms *kms) kfree(sde_kms); } +static int sde_kms_set_crtc_for_conn(struct drm_device *dev, + struct drm_encoder *enc, struct drm_atomic_state *state) +{ + struct drm_connector *conn = NULL; + struct drm_connector *tmp_conn = NULL; + struct drm_connector_list_iter conn_iter; + struct drm_crtc_state *crtc_state = NULL; + struct drm_connector_state *conn_state = NULL; + int ret = 0; + + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(tmp_conn, &conn_iter) { + if (enc == tmp_conn->state->best_encoder) { + conn = tmp_conn; + break; + } + } + drm_connector_list_iter_end(&conn_iter); + + if (!conn) { + SDE_ERROR("error in finding conn for enc:%d\n", DRMID(enc)); + return -EINVAL; + } + + crtc_state = drm_atomic_get_crtc_state(state, enc->crtc); + conn_state = drm_atomic_get_connector_state(state, conn); + if (IS_ERR(conn_state)) { + SDE_ERROR("error %d getting connector %d state\n", + ret, DRMID(conn)); + return -EINVAL; + } + + crtc_state->active = true; + ret = drm_atomic_set_crtc_for_connector(conn_state, enc->crtc); + if (ret) + SDE_ERROR("error %d setting the crtc\n", ret); + + _sde_crtc_clear_dim_layers_v1(crtc_state); + + return 0; +} + static void _sde_kms_plane_force_remove(struct drm_plane *plane, struct drm_atomic_state *state) { @@ -2059,8 +2104,9 @@ static int _sde_kms_remove_fbs(struct sde_kms *sde_kms, struct drm_file *file, struct drm_framebuffer *fb, *tfb; struct list_head fbs; struct drm_plane *plane; + struct drm_crtc *crtc = NULL; + unsigned int crtc_mask = 0; int ret = 0; - u32 plane_mask = 0; INIT_LIST_HEAD(&fbs); @@ -2069,9 +2115,11 @@ static int _sde_kms_remove_fbs(struct sde_kms *sde_kms, struct drm_file *file, list_move_tail(&fb->filp_head, &fbs); drm_for_each_plane(plane, dev) { - if (plane->fb == fb) { - plane_mask |= - 1 << drm_plane_index(plane); + if (plane->state && + plane->state->fb == fb) { + if (plane->state->crtc) + crtc_mask |= drm_crtc_mask( + plane->state->crtc); _sde_kms_plane_force_remove( plane, state); } @@ -2084,11 +2132,22 @@ static int _sde_kms_remove_fbs(struct sde_kms *sde_kms, struct drm_file *file, if (list_empty(&fbs)) { SDE_DEBUG("skip commit as no fb(s)\n"); - drm_atomic_state_put(state); return 0; } - SDE_DEBUG("committing after removing all the pipes\n"); + drm_for_each_crtc(crtc, dev) { + if ((crtc_mask & drm_crtc_mask(crtc)) && crtc->state->active) { + struct drm_encoder *drm_enc; + + drm_for_each_encoder_mask(drm_enc, crtc->dev, + crtc->state->encoder_mask) + ret = sde_kms_set_crtc_for_conn( + dev, drm_enc, state); + } + } + + SDE_EVT32(state, crtc_mask); + SDE_DEBUG("null commit after removing all the pipes\n"); ret = drm_atomic_commit(state); if (ret) { @@ -2743,12 +2802,7 @@ static void _sde_kms_null_commit(struct drm_device *dev, struct drm_encoder *enc) { struct drm_modeset_acquire_ctx ctx; - struct drm_connector *conn = NULL; - struct drm_connector *tmp_conn = NULL; - struct drm_connector_list_iter conn_iter; struct drm_atomic_state *state = NULL; - struct drm_crtc_state *crtc_state = NULL; - struct drm_connector_state *conn_state = NULL; int retry_cnt = 0; int ret = 0; @@ -2772,32 +2826,10 @@ static void _sde_kms_null_commit(struct drm_device *dev, } state->acquire_ctx = &ctx; - drm_connector_list_iter_begin(dev, &conn_iter); - drm_for_each_connector_iter(tmp_conn, &conn_iter) { - if (enc == tmp_conn->state->best_encoder) { - conn = tmp_conn; - break; - } - } - drm_connector_list_iter_end(&conn_iter); - if (!conn) { - SDE_ERROR("error in finding conn for enc:%d\n", DRMID(enc)); - goto end; - } - - crtc_state = drm_atomic_get_crtc_state(state, enc->crtc); - conn_state = drm_atomic_get_connector_state(state, conn); - if (IS_ERR(conn_state)) { - SDE_ERROR("error %d getting connector %d state\n", - ret, DRMID(conn)); - goto end; - } - - crtc_state->active = true; - ret = drm_atomic_set_crtc_for_connector(conn_state, enc->crtc); + ret = sde_kms_set_crtc_for_conn(dev, enc, state); if (ret) - SDE_ERROR("error %d setting the crtc\n", ret); + goto end; ret = drm_atomic_commit(state); if (ret) diff --git a/techpack/display/msm/sde/sde_rm.c b/techpack/display/msm/sde/sde_rm.c index 7f3716b8bf28..76e908b4c044 100644 --- a/techpack/display/msm/sde/sde_rm.c +++ b/techpack/display/msm/sde/sde_rm.c @@ -2126,8 +2126,8 @@ struct sde_rm_rsvp *_sde_rm_poll_get_rsvp_nxt_locked(struct sde_rm *rm, usleep_range(sleep, sleep * 2); mutex_lock(&rm->rm_lock); } - - return rsvp_nxt; + /* make sure to get latest rsvp_next to avoid use after free issues */ + return _sde_rm_get_rsvp_nxt(rm, enc); } int sde_rm_reserve( diff --git a/techpack/display/msm/sde_dbg.c b/techpack/display/msm/sde_dbg.c index 6178e1bf1696..2723d01cc874 100644 --- a/techpack/display/msm/sde_dbg.c +++ b/techpack/display/msm/sde_dbg.c @@ -197,6 +197,7 @@ struct sde_dbg_regbuf { /** * struct sde_dbg_base - global sde debug base structure * @evtlog: event log instance + * @reglog: reg log instance * @reg_base_list: list of register dumping regions * @dev: device pointer * @mutex: mutex to serialize access to serialze dumps, debugfs access @@ -212,12 +213,15 @@ struct sde_dbg_regbuf { * @dsi_dbg_bus: dump dsi debug bus register * @regbuf: buffer data to track the register dumping in hw recovery * @cur_evt_index: index used for tracking event logs dump in hw recovery + * @cur_reglog_index: index used for tracking register logs dump in hw recovery * @dbgbus_dump_idx: index used for tracking dbg-bus dump in hw recovery * @vbif_dbgbus_dump_idx: index for tracking vbif dumps in hw recovery */ static struct sde_dbg_base { struct sde_dbg_evtlog *evtlog; + struct sde_dbg_reglog *reglog; struct list_head reg_base_list; + void *reg_dump_addr; struct device *dev; struct mutex mutex; @@ -238,6 +242,7 @@ static struct sde_dbg_base { struct sde_dbg_regbuf regbuf; u32 cur_evt_index; + u32 cur_reglog_index; u32 dbgbus_dump_idx; u32 vbif_dbgbus_dump_idx; enum sde_dbg_dump_context dump_mode; @@ -246,6 +251,9 @@ static struct sde_dbg_base { /* sde_dbg_base_evtlog - global pointer to main sde event log for macro use */ struct sde_dbg_evtlog *sde_dbg_base_evtlog; +/* sde_dbg_base_reglog - global pointer to main sde reg log for macro use */ +struct sde_dbg_reglog *sde_dbg_base_reglog; + static void _sde_debug_bus_xbar_dump(void __iomem *mem_base, struct sde_debug_bus_entry *entry, u32 val) { @@ -2922,6 +2930,7 @@ static void _sde_dump_reg(const char *dump_name, u32 reg_dump_flag, char *base_addr, char *addr, size_t len_bytes, u32 **dump_mem) { u32 in_log, in_mem, len_align, len_padded; + struct sde_dbg_base *dbg_base = &sde_dbg_base; u32 *dump_addr = NULL; char *end_addr; int i; @@ -2950,9 +2959,8 @@ static void _sde_dump_reg(const char *dump_name, u32 reg_dump_flag, if (in_mem) { if (dump_mem && !(*dump_mem)) { - phys_addr_t phys = 0; - *dump_mem = dma_alloc_coherent(sde_dbg_base.dev, - len_padded, &phys, GFP_KERNEL); + *dump_mem = dbg_base->reg_dump_addr; + dbg_base->reg_dump_addr += len_padded; } if (dump_mem && *dump_mem) { @@ -3026,6 +3034,49 @@ static u32 _sde_dbg_get_dump_range(struct sde_dbg_reg_offset *range_node, return length; } +static u32 _sde_dbg_get_reg_blk_size(struct sde_dbg_reg_base *dbg) +{ + u32 len, len_align, len_padded; + u32 size = 0; + struct sde_dbg_reg_range *range_node; + + if (!dbg || !dbg->base) { + pr_err("dbg base is null!\n"); + return 0; + } + + if (!list_empty(&dbg->sub_range_list)) { + list_for_each_entry(range_node, &dbg->sub_range_list, head) { + len = _sde_dbg_get_dump_range(&range_node->offset, + dbg->max_offset); + len_align = (len + REG_DUMP_ALIGN - 1) / REG_DUMP_ALIGN; + len_padded = len_align * REG_DUMP_ALIGN; + size += REG_BASE_NAME_LEN + RANGE_NAME_LEN + len_padded; + } + } else { + len = dbg->max_offset; + len_align = (len + REG_DUMP_ALIGN - 1) / REG_DUMP_ALIGN; + len_padded = len_align * REG_DUMP_ALIGN; + size += REG_BASE_NAME_LEN + RANGE_NAME_LEN + len_padded; + } + return size; +} + +static u32 _sde_dbg_get_reg_dump_size(void) +{ + struct sde_dbg_base *dbg_base = &sde_dbg_base; + struct sde_dbg_reg_base *blk_base; + u32 size = 0; + + if (!dbg_base) + return 0; + + list_for_each_entry(blk_base, &dbg_base->reg_base_list, reg_base_head) { + size += _sde_dbg_get_reg_blk_size(blk_base); + } + return size; +} + static int _sde_dump_reg_range_cmp(void *priv, struct list_head *a, struct list_head *b) { @@ -3071,6 +3122,7 @@ static void _sde_dump_reg_by_ranges(struct sde_dbg_reg_base *dbg, char *addr; size_t len; struct sde_dbg_reg_range *range_node; + struct sde_dbg_base *dbg_base = &sde_dbg_base; if (!dbg || !(dbg->base || dbg->cb)) { pr_err("dbg base is null!\n"); @@ -3100,6 +3152,12 @@ static void _sde_dump_reg_by_ranges(struct sde_dbg_reg_base *dbg, addr, range_node->offset.start, range_node->offset.end); + scnprintf(dbg_base->reg_dump_addr, REG_BASE_NAME_LEN, + dbg->name); + dbg_base->reg_dump_addr += REG_BASE_NAME_LEN; + scnprintf(dbg_base->reg_dump_addr, REG_BASE_NAME_LEN, + range_node->range_name); + dbg_base->reg_dump_addr += RANGE_NAME_LEN; _sde_dump_reg(range_node->range_name, reg_dump_flag, dbg->base, addr, len, &range_node->reg_dump); @@ -3112,6 +3170,10 @@ static void _sde_dump_reg_by_ranges(struct sde_dbg_reg_base *dbg, dbg->max_offset); addr = dbg->base; len = dbg->max_offset; + scnprintf(dbg_base->reg_dump_addr, REG_BASE_NAME_LEN, + dbg->name); + dbg_base->reg_dump_addr += REG_BASE_NAME_LEN; + dbg_base->reg_dump_addr += RANGE_NAME_LEN; _sde_dump_reg(dbg->name, reg_dump_flag, dbg->base, addr, len, &dbg->reg_dump); } @@ -3478,9 +3540,16 @@ static void _sde_dump_array(struct sde_dbg_reg_base *blk_arr[], bool dump_dbgbus_vbif_rt, bool dump_all, bool dump_secure) { int i; + u32 reg_dump_size; + struct sde_dbg_base *dbg_base = &sde_dbg_base; + phys_addr_t phys = 0; mutex_lock(&sde_dbg_base.mutex); + reg_dump_size = _sde_dbg_get_reg_dump_size(); + dbg_base->reg_dump_addr = dma_alloc_coherent(sde_dbg_base.dev, + reg_dump_size, &phys, GFP_KERNEL); + if (dump_all) sde_evtlog_dump_all(sde_dbg_base.evtlog); @@ -3658,7 +3727,7 @@ void sde_dbg_ctrl(const char *name, ...) va_end(args); } - +#ifdef CONFIG_DEBUG_FS /* * sde_dbg_debugfs_open - debugfs open handler for evtlog dump * @inode: debugfs inode @@ -4600,6 +4669,15 @@ int sde_dbg_debugfs_register(struct device *dev) return 0; } +#else + +int sde_dbg_debugfs_register(struct device *dev) +{ + return 0; +} + +#endif + static void _sde_dbg_debugfs_destroy(void) { } @@ -4665,6 +4743,12 @@ int sde_dbg_init(struct device *dev) sde_dbg_base_evtlog = sde_dbg_base.evtlog; + sde_dbg_base.reglog = sde_reglog_init(); + if (IS_ERR_OR_NULL(sde_dbg_base.reglog)) + return PTR_ERR(sde_dbg_base.reglog); + + sde_dbg_base_reglog = sde_dbg_base.reglog; + INIT_WORK(&sde_dbg_base.dump_work, _sde_dump_work); sde_dbg_base.work_panic = false; sde_dbg_base.panic_on_err = DEFAULT_PANIC; @@ -4709,6 +4793,8 @@ void sde_dbg_destroy(void) sde_dbg_base_evtlog = NULL; sde_evtlog_destroy(sde_dbg_base.evtlog); sde_dbg_base.evtlog = NULL; + sde_reglog_destroy(sde_dbg_base.reglog); + sde_dbg_base.reglog = NULL; sde_dbg_reg_base_destroy(); mutex_destroy(&sde_dbg_base.mutex); } diff --git a/techpack/display/msm/sde_dbg.h b/techpack/display/msm/sde_dbg.h index e336f3236978..61764c048bc9 100644 --- a/techpack/display/msm/sde_dbg.h +++ b/techpack/display/msm/sde_dbg.h @@ -35,6 +35,7 @@ enum sde_dbg_evtlog_flag { SDE_EVTLOG_IRQ = BIT(1), SDE_EVTLOG_VERBOSE = BIT(2), SDE_EVTLOG_EXTERNAL = BIT(3), + SDE_EVTLOG_REGWRITE = BIT(4), SDE_EVTLOG_ALWAYS = -1 }; @@ -49,6 +50,34 @@ enum sde_dbg_dump_context { SDE_DBG_DUMP_CLK_ENABLED_CTX, }; +/* + * Define blocks for register write logging. + */ +#define SDE_REG_LOG_DEFAULT 0 +#define SDE_REG_LOG_NONE 1 +#define SDE_REG_LOG_CDM 2 +#define SDE_REG_LOG_DSPP 3 +#define SDE_REG_LOG_INTF 4 +#define SDE_REG_LOG_LM 5 +#define SDE_REG_LOG_CTL 6 +#define SDE_REG_LOG_PINGPONG 7 +#define SDE_REG_LOG_SSPP 8 +#define SDE_REG_LOG_WB 9 +#define SDE_REG_LOG_TOP 10 +#define SDE_REG_LOG_VBIF 11 +#define SDE_REG_LOG_DSC 12 +#define SDE_REG_LOG_ROT 13 +#define SDE_REG_LOG_DS 14 +#define SDE_REG_LOG_REGDMA 15 +#define SDE_REG_LOG_UIDLE 16 +#define SDE_REG_LOG_SID 16 +#define SDE_REG_LOG_QDSS 17 +/* + * 0-32 are reserved for sde_reg_write due to log masks + * Additional blocks are assigned from 33 to avoid conflict + */ +#define SDE_REG_LOG_RSCC 33 + #define SDE_EVTLOG_DEFAULT_ENABLE (SDE_EVTLOG_CRITICAL | SDE_EVTLOG_IRQ | \ SDE_EVTLOG_EXTERNAL) @@ -103,6 +132,44 @@ struct sde_dbg_evtlog { extern struct sde_dbg_evtlog *sde_dbg_base_evtlog; +/* + * reglog keeps this number of entries in memory for debug purpose. This + * number must be greater than number of possible writes in at least one + * single commit. + */ +#define SDE_REGLOG_ENTRY 1024 + +struct sde_dbg_reglog_log { + s64 time; + u32 pid; + u32 addr; + u32 val; + u8 blk_id; +}; + +/** + * @last_dump: Index of last entry to be output during reglog dumps + * @filter_list: Linked list of currently active filter strings + */ +struct sde_dbg_reglog { + struct sde_dbg_reglog_log logs[SDE_REGLOG_ENTRY]; + u32 first; + u32 last; + u32 last_dump; + u32 curr; + u32 next; + u32 enable; + u32 enable_mask; + spinlock_t spin_lock; +}; + +extern struct sde_dbg_reglog *sde_dbg_base_reglog; + +/** + * SDE_REG_LOG - Write register write to the register log + */ +#define SDE_REG_LOG(blk_id, val, addr) sde_reglog_log(blk_id, val, addr) + /** * SDE_EVT32 - Write a list of 32bit values to the event log, default area * ... - variable arguments @@ -134,6 +201,13 @@ extern struct sde_dbg_evtlog *sde_dbg_base_evtlog; #define SDE_EVT32_EXTERNAL(...) sde_evtlog_log(sde_dbg_base_evtlog, __func__, \ __LINE__, SDE_EVTLOG_EXTERNAL, ##__VA_ARGS__, \ SDE_EVTLOG_DATA_LIMITER) +/** + * SDE_EVT32_REGWRITE - Write a list of 32bit values for register writes logging + * ... - variable arguments + */ +#define SDE_EVT32_REGWRITE(...) sde_evtlog_log(sde_dbg_base_evtlog, __func__, \ + __LINE__, SDE_EVTLOG_REGWRITE, ##__VA_ARGS__, \ + SDE_EVTLOG_DATA_LIMITER) /** * SDE_DBG_DUMP - trigger dumping of all sde_dbg facilities @@ -175,7 +249,6 @@ extern struct sde_dbg_evtlog *sde_dbg_base_evtlog; #define SDE_DBG_CTRL(...) sde_dbg_ctrl(__func__, ##__VA_ARGS__, \ SDE_DBG_DUMP_DATA_LIMITER) -#if defined(CONFIG_DEBUG_FS) /** * sde_evtlog_init - allocate a new event log object @@ -183,6 +256,12 @@ extern struct sde_dbg_evtlog *sde_dbg_base_evtlog; */ struct sde_dbg_evtlog *sde_evtlog_init(void); +/** + * sde_reglog_init - allocate a new reg log object + * Returns: reglog or -ERROR + */ +struct sde_dbg_reglog *sde_reglog_init(void); + /** * sde_evtlog_destroy - destroy previously allocated event log * @evtlog: pointer to evtlog @@ -190,6 +269,13 @@ struct sde_dbg_evtlog *sde_evtlog_init(void); */ void sde_evtlog_destroy(struct sde_dbg_evtlog *evtlog); +/** + * sde_reglog_destroy - destroy previously allocated reg log + * @reglog: pointer to reglog + * Returns: none + */ +void sde_reglog_destroy(struct sde_dbg_reglog *reglog); + /** * sde_evtlog_log - log an entry into the event log. * log collection may be enabled/disabled entirely via debugfs @@ -203,6 +289,15 @@ void sde_evtlog_destroy(struct sde_dbg_evtlog *evtlog); void sde_evtlog_log(struct sde_dbg_evtlog *evtlog, const char *name, int line, int flag, ...); +/** + * sde_reglog_log - log an entry into the reg log. + * log collection may be enabled/disabled entirely via debugfs + * log area collection may be filtered by user provided flags via debugfs. + * @reglog: pointer to evtlog + * Returns: none + */ +void sde_reglog_log(u8 blk_id, u32 val, u32 addr); + /** * sde_evtlog_dump_all - print all entries in event log to kernel log * @evtlog: pointer to evtlog @@ -371,101 +466,4 @@ void sde_rsc_debug_dump(u32 mux_sel); */ void dsi_ctrl_debug_dump(u32 *entries, u32 size); -#else -static inline struct sde_dbg_evtlog *sde_evtlog_init(void) -{ - return NULL; -} - -static inline void sde_evtlog_destroy(struct sde_dbg_evtlog *evtlog) -{ -} - -static inline void sde_evtlog_log(struct sde_dbg_evtlog *evtlog, - const char *name, int line, int flag, ...) -{ -} - -static inline void sde_evtlog_dump_all(struct sde_dbg_evtlog *evtlog) -{ -} - -static inline bool sde_evtlog_is_enabled(struct sde_dbg_evtlog *evtlog, - u32 flag) -{ - return false; -} - -static inline ssize_t sde_evtlog_dump_to_buffer(struct sde_dbg_evtlog *evtlog, - char *evtlog_buf, ssize_t evtlog_buf_size, - bool update_last_entry) -{ - return 0; -} - -static inline void sde_dbg_init_dbg_buses(u32 hwversion) -{ -} - -static inline int sde_dbg_init(struct device *dev) -{ - return 0; -} - -static inline int sde_dbg_debugfs_register(struct device *dev) -{ - return 0; -} - -static inline void sde_dbg_destroy(void) -{ -} - -static inline void sde_dbg_dump(enum sde_dbg_dump_context mode, - const char *name, ...) -{ -} - -static inline void sde_dbg_ctrl(const char *name, ...) -{ -} - -static inline int sde_dbg_reg_register_base(const char *name, - void __iomem *base, size_t max_offset) -{ - return 0; -} - -static inline void sde_dbg_reg_register_dump_range(const char *base_name, - const char *range_name, u32 offset_start, u32 offset_end, - uint32_t xin_id) -{ -} - -static inline void sde_dbg_set_sde_top_offset(u32 blk_off) -{ -} - -static inline void sde_evtlog_set_filter( - struct sde_dbg_evtlog *evtlog, char *filter) -{ -} - -static inline int sde_evtlog_get_filter(struct sde_dbg_evtlog *evtlog, - int index, char *buf, size_t bufsz) -{ - return -EINVAL; -} - -static inline void sde_rsc_debug_dump(u32 mux_sel) -{ -} - -static inline void dsi_ctrl_debug_dump(u32 *entries, u32 size) -{ -} - -#endif /* defined(CONFIG_DEBUG_FS) */ - - #endif /* SDE_DBG_H_ */ diff --git a/techpack/display/msm/sde_dbg_evtlog.c b/techpack/display/msm/sde_dbg_evtlog.c index 71ec32830d62..ddb4c996c021 100644 --- a/techpack/display/msm/sde_dbg_evtlog.c +++ b/techpack/display/msm/sde_dbg_evtlog.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "sde_dbg:[%s] " fmt, __func__ @@ -101,6 +101,31 @@ void sde_evtlog_log(struct sde_dbg_evtlog *evtlog, const char *name, int line, spin_unlock_irqrestore(&evtlog->spin_lock, flags); } +void sde_reglog_log(u8 blk_id, u32 val, u32 addr) +{ + unsigned long flags; + struct sde_dbg_reglog_log *log; + struct sde_dbg_reglog *reglog = sde_dbg_base_reglog; + + if (!reglog) + return; + + spin_lock_irqsave(®log->spin_lock, flags); + + log = ®log->logs[reglog->curr]; + + log->blk_id = blk_id; + log->val = val; + log->addr = addr; + log->time = local_clock(); + log->pid = current->pid; + + reglog->curr = (reglog->curr + 1) % SDE_REGLOG_ENTRY; + reglog->last++; + + spin_unlock_irqrestore(®log->spin_lock, flags); +} + /* always dump the last entries which are not dumped yet */ static bool _sde_evtlog_dump_calc_range(struct sde_dbg_evtlog *evtlog, bool update_last_entry, bool full_dump) @@ -211,6 +236,19 @@ struct sde_dbg_evtlog *sde_evtlog_init(void) return evtlog; } +struct sde_dbg_reglog *sde_reglog_init(void) +{ + struct sde_dbg_reglog *reglog; + + reglog = kzalloc(sizeof(*reglog), GFP_KERNEL); + if (!reglog) + return ERR_PTR(-ENOMEM); + + spin_lock_init(®log->spin_lock); + + return reglog; +} + int sde_evtlog_get_filter(struct sde_dbg_evtlog *evtlog, int index, char *buf, size_t bufsz) { @@ -312,3 +350,11 @@ void sde_evtlog_destroy(struct sde_dbg_evtlog *evtlog) } kfree(evtlog); } + +void sde_reglog_destroy(struct sde_dbg_reglog *reglog) +{ + if (!reglog) + return; + + kfree(reglog); +} diff --git a/techpack/display/msm/sde_io_util.c b/techpack/display/msm/sde_io_util.c index 09649c59819a..ad6e89f7b321 100644 --- a/techpack/display/msm/sde_io_util.c +++ b/techpack/display/msm/sde_io_util.c @@ -9,6 +9,7 @@ #include #include #include +#include "sde_dbg.h" #define MAX_I2C_CMDS 16 void dss_reg_w(struct dss_io_data *io, u32 offset, u32 value, u32 debug) @@ -33,7 +34,9 @@ void dss_reg_w(struct dss_io_data *io, u32 offset, u32 value, u32 debug) DEV_DBG("[%08x] => %08x [%08x]\n", (u32)(unsigned long)(io->base + offset), value, in_val); + SDE_EVT32_REGWRITE(io->base, offset, value, in_val); } + SDE_REG_LOG(SDE_REG_LOG_RSCC, value, offset); } /* dss_reg_w */ EXPORT_SYMBOL(dss_reg_w); diff --git a/techpack/video/msm/vidc/hfi_iris2.c b/techpack/video/msm/vidc/hfi_iris2.c index 91f91ef83ed4..179fb2abaefb 100644 --- a/techpack/video/msm/vidc/hfi_iris2.c +++ b/techpack/video/msm/vidc/hfi_iris2.c @@ -167,8 +167,7 @@ void __setup_ucregion_memory_map_iris2(struct venus_hfi_device *device, u32 sid) (u32)device->qdss.align_device_addr, sid); /* update queues vaddr for debug purpose */ __write_register(device, CPU_CS_VCICMDARG0_IRIS2, - (u32)((u64)device->iface_q_table.align_virtual_addr >> 32), - sid); + (u32)device->iface_q_table.align_virtual_addr, sid); __write_register(device, CPU_CS_VCICMDARG1_IRIS2, (u32)((u64)device->iface_q_table.align_virtual_addr >> 32), sid); diff --git a/techpack/video/msm/vidc/msm_v4l2_vidc.c b/techpack/video/msm/vidc/msm_v4l2_vidc.c index ae2950be0a19..0fd761e87343 100644 --- a/techpack/video/msm/vidc/msm_v4l2_vidc.c +++ b/techpack/video/msm/vidc/msm_v4l2_vidc.c @@ -772,7 +772,7 @@ static int __init msm_vidc_init(void) mutex_init(&vidc_driver->lock); vidc_driver->debugfs_root = msm_vidc_debugfs_init_drv(); if (!vidc_driver->debugfs_root) -// d_vpr_e("Failed to create debugfs for msm_vidc\n"); + d_vpr_e("Failed to create debugfs for msm_vidc\n"); rc = platform_driver_register(&msm_vidc_driver); if (rc) { diff --git a/techpack/video/msm/vidc/msm_venc.c b/techpack/video/msm/vidc/msm_venc.c index 0ad13a833435..80e8e00f3dd4 100644 --- a/techpack/video/msm/vidc/msm_venc.c +++ b/techpack/video/msm/vidc/msm_venc.c @@ -1113,26 +1113,6 @@ u32 v4l2_to_hfi_flip(struct msm_vidc_inst *inst) return flip; } -inline bool vidc_scalar_enabled(struct msm_vidc_inst *inst) -{ - struct v4l2_format *f; - u32 output_height, output_width, input_height, input_width; - bool scalar_enable = false; - - f = &inst->fmts[OUTPUT_PORT].v4l2_fmt; - output_height = f->fmt.pix_mp.height; - output_width = f->fmt.pix_mp.width; - f = &inst->fmts[INPUT_PORT].v4l2_fmt; - input_height = f->fmt.pix_mp.height; - input_width = f->fmt.pix_mp.width; - - if (output_height != input_height || output_width != input_width) - scalar_enable = true; - - return scalar_enable; -} - - static int msm_venc_set_csc(struct msm_vidc_inst *inst, u32 color_primaries, u32 custom_matrix); diff --git a/techpack/video/msm/vidc/msm_vidc.c b/techpack/video/msm/vidc/msm_vidc.c index ca71e8dfc422..cbb46852cbb9 100644 --- a/techpack/video/msm/vidc/msm_vidc.c +++ b/techpack/video/msm/vidc/msm_vidc.c @@ -1447,7 +1447,7 @@ void *msm_vidc_open(int core_id, int session_type) goto err_invalid_sid; } - pr_debug(VIDC_DBG_TAG "Opening video instance: %pK, %d\n", + pr_info(VIDC_DBG_TAG "Opening video instance: %pK, %d\n", "high", inst->sid, get_codec_name(inst->sid), inst, session_type); mutex_init(&inst->sync_lock); @@ -1717,7 +1717,7 @@ int msm_vidc_destroy(struct msm_vidc_inst *inst) msm_vidc_debugfs_deinit_inst(inst); - pr_debug(VIDC_DBG_TAG "Closed video instance: %pK\n", + pr_info(VIDC_DBG_TAG "Closed video instance: %pK\n", "high", inst->sid, get_codec_name(inst->sid), inst); put_sid(inst->sid); diff --git a/techpack/video/msm/vidc/msm_vidc_buffer_calculations.c b/techpack/video/msm/vidc/msm_vidc_buffer_calculations.c index fbe9b9537202..a2d2aa35f700 100644 --- a/techpack/video/msm/vidc/msm_vidc_buffer_calculations.c +++ b/techpack/video/msm/vidc/msm_vidc_buffer_calculations.c @@ -273,6 +273,7 @@ #define HFI_VENUS_HEIGHT_ALIGNMENT 32 #define SYSTEM_LAL_TILE10 192 +#define NUM_MBS_480P (((640 + 15) >> 4) * ((480 + 15) >> 4)) #define NUM_MBS_720P (((1280 + 15) >> 4) * ((720 + 15) >> 4)) #define NUM_MBS_4k (((4096 + 15) >> 4) * ((2304 + 15) >> 4)) #define MB_SIZE_IN_PIXEL (16 * 16) @@ -324,7 +325,8 @@ static inline u32 calculate_vp8e_scratch1_size(struct msm_vidc_inst *inst, u32 width, u32 height, u32 num_ref, bool ten_bit, u32 num_vpp_pipes); static inline u32 calculate_enc_scratch2_size(struct msm_vidc_inst *inst, - u32 width, u32 height, u32 num_ref, bool ten_bit); + u32 width, u32 height, u32 num_ref, bool ten_bit, bool downscale, + u32 rotation_val, u32 flip); static inline u32 calculate_enc_persist_size(void); @@ -475,6 +477,10 @@ int msm_vidc_get_num_ref_frames(struct msm_vidc_inst *inst) struct v4l2_ctrl *layer_ctrl; u32 codec; + codec = get_v4l2_codec(inst); + if (codec == V4L2_PIX_FMT_VP8) + num_ref = num_ref << 1; + bframe_ctrl = get_ctrl(inst, V4L2_CID_MPEG_VIDEO_B_FRAMES); num_bframes = bframe_ctrl->val; if (num_bframes > 0) @@ -489,17 +495,16 @@ int msm_vidc_get_num_ref_frames(struct msm_vidc_inst *inst) layer_ctrl = get_ctrl(inst, V4L2_CID_MPEG_VIDC_VIDEO_HEVC_MAX_HIER_CODING_LAYER); num_hp_layers = layer_ctrl->val; - codec = get_v4l2_codec(inst); - if (num_hp_layers > 0) { + if (num_hp_layers > 1) { /* LTR and B - frame not supported with hybrid HP */ if (inst->hybrid_hp) - num_ref = (num_hp_layers - 1); + num_ref = (num_hp_layers + 1) >> 1; else if (codec == V4L2_PIX_FMT_HEVC) num_ref = ((num_hp_layers + 1) / 2) + ltr_count; - else if ((codec == V4L2_PIX_FMT_H264) && (num_hp_layers <= 4)) - num_ref = ((1 << (num_hp_layers - 1)) - 1) + ltr_count; + else if ((codec == V4L2_PIX_FMT_H264) && (num_hp_layers < 4)) + num_ref = (num_hp_layers - 1) + ltr_count; else - num_ref = ((num_hp_layers + 1) / 2) + ltr_count; + num_ref = num_hp_layers + ltr_count; } return num_ref; } @@ -508,9 +513,10 @@ int msm_vidc_get_encoder_internal_buffer_sizes(struct msm_vidc_inst *inst) { struct msm_vidc_enc_buff_size_calculators *enc_calculators; u32 width, height, i, num_ref, num_vpp_pipes; - bool is_tenbit = false; + u32 rotation_val = 0, flip = 0; + bool is_tenbit = false, is_downscale = false; int num_bframes; - struct v4l2_ctrl *bframe; + struct v4l2_ctrl *bframe, *rotation, *hflip, *vflip; struct v4l2_format *f; if (!inst || !inst->core || !inst->core->platform_data) { @@ -537,18 +543,30 @@ int msm_vidc_get_encoder_internal_buffer_sizes(struct msm_vidc_inst *inst) return -EINVAL; } - f = &inst->fmts[OUTPUT_PORT].v4l2_fmt; - width = f->fmt.pix_mp.width; - height = f->fmt.pix_mp.height; bframe = get_ctrl(inst, V4L2_CID_MPEG_VIDEO_B_FRAMES); num_bframes = bframe->val; if (num_bframes < 0) { s_vpr_e(inst->sid, "%s: get num bframe failed\n", __func__); return -EINVAL; } + f = &inst->fmts[OUTPUT_PORT].v4l2_fmt; + rotation = get_ctrl(inst, V4L2_CID_ROTATE); + rotation_val = rotation->val; + if (rotation_val == 90 || rotation_val == 270) { + /* Internal buffer size calc are based on rotated wxh */ + width = f->fmt.pix_mp.height; + height = f->fmt.pix_mp.width; + } else { + width = f->fmt.pix_mp.width; + height = f->fmt.pix_mp.height; + } + hflip = get_ctrl(inst, V4L2_CID_HFLIP); + vflip = get_ctrl(inst, V4L2_CID_VFLIP); + flip = hflip->val | vflip->val; num_ref = msm_vidc_get_num_ref_frames(inst); is_tenbit = (inst->bit_depth == MSM_VIDC_BIT_DEPTH_10); + is_downscale = vidc_scalar_enabled(inst); for (i = 0; i < HAL_BUFFER_MAX; i++) { struct hal_buffer_requirements *curr_req; @@ -574,7 +592,8 @@ int msm_vidc_get_encoder_internal_buffer_sizes(struct msm_vidc_inst *inst) curr_req->buffer_size = enc_calculators->calculate_scratch2_size( inst, width, height, num_ref, - is_tenbit); + is_tenbit, is_downscale, rotation_val, + flip); valid_buffer_type = true; } else if (curr_req->buffer_type == HAL_BUFFER_INTERNAL_PERSIST) { @@ -1004,8 +1023,9 @@ u32 msm_vidc_calculate_enc_output_frame_size(struct msm_vidc_inst *inst) f = &inst->fmts[OUTPUT_PORT].v4l2_fmt; /* * Encoder output size calculation: 32 Align width/height - * For resolution < 720p : YUVsize * 4 - * For resolution > 720p & <= 4K : YUVsize / 2 + * For CQ or heic session : YUVsize * 2 + * For resolution <= 480p : YUVsize * 2 + * For resolution > 480p & <= 4K : YUVsize / 2 * For resolution > 4k : YUVsize / 4 * Initially frame_size = YUVsize * 2; */ @@ -1018,24 +1038,29 @@ u32 msm_vidc_calculate_enc_output_frame_size(struct msm_vidc_inst *inst) mbs_per_frame = NUM_MBS_PER_FRAME(width, height); frame_size = (width * height * 3); - if (mbs_per_frame < NUM_MBS_720P) - frame_size = frame_size << 1; + if (inst->rc_type == V4L2_MPEG_VIDEO_BITRATE_MODE_CQ || + is_grid_session(inst) || is_image_session(inst)) + goto calc_done; + + if (mbs_per_frame <= NUM_MBS_480P) + goto calc_done; /* Default frame_size = YUVsize * 2 */ else if (mbs_per_frame <= NUM_MBS_4k) frame_size = frame_size >> 2; else frame_size = frame_size >> 3; - if ((inst->rc_type == RATE_CONTROL_OFF) || - (inst->rc_type == V4L2_MPEG_VIDEO_BITRATE_MODE_CQ)) + if (inst->rc_type == RATE_CONTROL_OFF) frame_size = frame_size << 1; if (inst->rc_type == RATE_CONTROL_LOSSLESS) frame_size = (width * height * 9) >> 2; /* multiply by 10/8 (1.25) to get size for 10 bit case */ - if (f->fmt.pix_mp.pixelformat == V4L2_PIX_FMT_HEVC) + if (inst->core->platform_data->vpu_ver != VPU_VERSION_AR50_LITE && + f->fmt.pix_mp.pixelformat == V4L2_PIX_FMT_HEVC) frame_size = frame_size + (frame_size >> 2); +calc_done: return ALIGN(frame_size, SZ_4K); } @@ -1809,8 +1834,8 @@ static inline u32 hfi_ubwc_uv_metadata_plane_bufheight(u32 height, tile_height_pels), metadata_height_multi); } -static inline u32 calculate_enc_scratch2_size(struct msm_vidc_inst *inst, - u32 width, u32 height, u32 num_ref, bool ten_bit) +static inline u32 hfi_iris2_enc_dpb_buffer_size(u32 width, u32 height, + bool ten_bit) { u32 aligned_width, aligned_height, chroma_height, ref_buf_height; u32 luma_size, chroma_size; @@ -1835,7 +1860,6 @@ static inline u32 calculate_enc_scratch2_size(struct msm_vidc_inst *inst, metadata_stride, meta_buf_height); size = (aligned_height + chroma_height) * aligned_width + meta_size_y + meta_size_c; - size = (size * (num_ref + 2)) + 4096; } else { ref_buf_height = (height + (HFI_VENUS_HEIGHT_ALIGNMENT - 1)) & (~(HFI_VENUS_HEIGHT_ALIGNMENT - 1)); @@ -1868,7 +1892,29 @@ static inline u32 calculate_enc_scratch2_size(struct msm_vidc_inst *inst, meta_size_c = hfi_ubwc_metadata_plane_buffer_size( metadata_stride, meta_buf_height); size = ref_buf_size + meta_size_y + meta_size_c; - size = (size * (num_ref+3)) + 4096; + } + return size; +} + +static inline u32 calculate_enc_scratch2_size(struct msm_vidc_inst *inst, + u32 width, u32 height, u32 num_ref, bool ten_bit, bool downscale, + u32 rotation_val, u32 flip) +{ + u32 size; + + size = hfi_iris2_enc_dpb_buffer_size(width, height, ten_bit); + size = size * (num_ref + 1) + 4096; + if (downscale && (rotation_val || flip)) { + /* VPSS output is always 128 x 32 aligned for 8-bit + * and 192 x 16 aligned for 10-bit + */ + if (rotation_val == 90 || rotation_val == 270) + size += hfi_iris2_enc_dpb_buffer_size(height, width, + ten_bit); + else + size += hfi_iris2_enc_dpb_buffer_size(width, height, + ten_bit); + size += 4096; } return size; } diff --git a/techpack/video/msm/vidc/msm_vidc_buffer_calculations.h b/techpack/video/msm/vidc/msm_vidc_buffer_calculations.h index a94bc485e32b..3525a4429080 100644 --- a/techpack/video/msm/vidc/msm_vidc_buffer_calculations.h +++ b/techpack/video/msm/vidc/msm_vidc_buffer_calculations.h @@ -26,7 +26,8 @@ struct msm_vidc_enc_buff_size_calculators { u32 width, u32 height, u32 num_ref, bool ten_bit, u32 num_vpp_pipes); u32 (*calculate_scratch2_size)(struct msm_vidc_inst *inst, - u32 width, u32 height, u32 num_ref, bool ten_bit); + u32 width, u32 height, u32 num_ref, bool ten_bit, + bool downscale, u32 rotation_val, u32 flip); u32 (*calculate_persist_size)(void); }; diff --git a/techpack/video/msm/vidc/msm_vidc_common.c b/techpack/video/msm/vidc/msm_vidc_common.c index 21adfe4d560e..2619d1f89d41 100644 --- a/techpack/video/msm/vidc/msm_vidc_common.c +++ b/techpack/video/msm/vidc/msm_vidc_common.c @@ -693,6 +693,25 @@ enum multi_stream msm_comm_get_stream_output_mode(struct msm_vidc_inst *inst) return HAL_VIDEO_DECODER_PRIMARY; } +bool vidc_scalar_enabled(struct msm_vidc_inst *inst) +{ + struct v4l2_format *f; + u32 output_height, output_width, input_height, input_width; + bool scalar_enable = false; + + f = &inst->fmts[OUTPUT_PORT].v4l2_fmt; + output_height = f->fmt.pix_mp.height; + output_width = f->fmt.pix_mp.width; + f = &inst->fmts[INPUT_PORT].v4l2_fmt; + input_height = f->fmt.pix_mp.height; + input_width = f->fmt.pix_mp.width; + + if (output_height != input_height || output_width != input_width) + scalar_enable = true; + + return scalar_enable; +} + bool is_single_session(struct msm_vidc_inst *inst, u32 ignore_flags) { bool single = true; diff --git a/techpack/video/msm/vidc/msm_vidc_common.h b/techpack/video/msm/vidc/msm_vidc_common.h index 1c64e3ceeef9..a7e1cc23b50c 100644 --- a/techpack/video/msm/vidc/msm_vidc_common.h +++ b/techpack/video/msm/vidc/msm_vidc_common.h @@ -223,6 +223,7 @@ static inline bool is_valid_operating_rate(struct msm_vidc_inst *inst, s32 val) return true; } +bool vidc_scalar_enabled(struct msm_vidc_inst *inst); bool is_single_session(struct msm_vidc_inst *inst, u32 ignore_flags); int msm_comm_get_num_perf_sessions(struct msm_vidc_inst *inst); bool is_batching_allowed(struct msm_vidc_inst *inst); diff --git a/techpack/video/msm/vidc/msm_vidc_debug.c b/techpack/video/msm/vidc/msm_vidc_debug.c index 3f1617aa3010..e803269aa6bd 100644 --- a/techpack/video/msm/vidc/msm_vidc_debug.c +++ b/techpack/video/msm/vidc/msm_vidc_debug.c @@ -273,7 +273,7 @@ struct dentry *msm_vidc_debugfs_init_core(struct msm_vidc_core *core, dir = debugfs_create_dir(debugfs_name, parent); if (IS_ERR_OR_NULL(dir)) { dir = NULL; -// d_vpr_e("Failed to create debugfs for msm_vidc\n"); + d_vpr_e("Failed to create debugfs for msm_vidc\n"); goto failed_create_dir; } if (!debugfs_create_file("info", 0444, dir, core, &core_info_fops)) { @@ -494,7 +494,7 @@ struct dentry *msm_vidc_debugfs_init_inst(struct msm_vidc_inst *inst, dir = debugfs_create_dir(debugfs_name, parent); if (IS_ERR_OR_NULL(dir)) { dir = NULL; -// s_vpr_e(inst->sid, "Failed to create debugfs for msm_vidc\n"); + s_vpr_e(inst->sid, "Failed to create debugfs for msm_vidc\n"); goto failed_create_dir; } diff --git a/techpack/video/msm/vidc/msm_vidc_internal.h b/techpack/video/msm/vidc/msm_vidc_internal.h index 8fdbf3434fca..363571997e90 100644 --- a/techpack/video/msm/vidc/msm_vidc_internal.h +++ b/techpack/video/msm/vidc/msm_vidc_internal.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2020, 2021 The Linux Foundation. All rights reserved. */ #ifndef _MSM_VIDC_INTERNAL_H_ @@ -47,7 +47,7 @@ #define MAX_NUM_INPUT_BUFFERS VIDEO_MAX_FRAME // same as VB2_MAX_FRAME #define MAX_NUM_OUTPUT_BUFFERS VIDEO_MAX_FRAME // same as VB2_MAX_FRAME -#define MAX_SUPPORTED_INSTANCES 16 +#define MAX_SUPPORTED_INSTANCES 24 /* Maintains the number of FTB's between each FBD over a window */ #define DCVS_FTB_WINDOW 16 diff --git a/techpack/video/msm/vidc/msm_vidc_platform.c b/techpack/video/msm/vidc/msm_vidc_platform.c index 0168b82e9168..3b97cc23dcd5 100644 --- a/techpack/video/msm/vidc/msm_vidc_platform.c +++ b/techpack/video/msm/vidc/msm_vidc_platform.c @@ -1339,7 +1339,7 @@ static struct msm_vidc_common_data kona_common_data[] = { }, { .key = "qcom,decode-batching", - .value = 0, + .value = 1, }, { .key = "qcom,batch-timeout",